ccstatusline 1.1.2 → 1.1.3

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.
Files changed (2) hide show
  1. package/dist/ccstatusline.js +582 -777
  2. package/package.json +6 -5
@@ -1,17 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/ccstatusline.ts
4
- import chalk2 from "chalk";
5
- import { execSync as execSync2 } from "child_process";
6
-
7
3
  // src/tui.tsx
8
4
  import { useState, useEffect } from "react";
9
5
  import { render, Box, Text, useInput, useApp } from "ink";
10
6
  import SelectInput from "ink-select-input";
11
- import chalk from "chalk";
12
- import { execSync } from "child_process";
7
+ import chalk2 from "chalk";
8
+ import { execSync as execSync2 } from "child_process";
13
9
 
14
- // src/config.ts
10
+ // src/utils/config.ts
15
11
  import * as fs from "fs";
16
12
  import * as path from "path";
17
13
  import * as os from "os";
@@ -123,7 +119,7 @@ async function saveSettings(settings) {
123
119
  await writeFile2(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
124
120
  }
125
121
 
126
- // src/claude-settings.ts
122
+ // src/utils/claude-settings.ts
127
123
  import * as fs2 from "fs";
128
124
  import * as path2 from "path";
129
125
  import * as os2 from "os";
@@ -174,20 +170,36 @@ async function getExistingStatusLine() {
174
170
  }
175
171
 
176
172
  // src/tui.tsx
177
- import * as fs3 from "fs";
173
+ import * as fs4 from "fs";
178
174
  import * as path3 from "path";
179
- var __dirname = "/Users/sirmalloc/Projects/Personal/ccstatusline/src";
180
- import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
181
- function getPackageVersion() {
182
- try {
183
- const packageJsonPath = path3.join(__dirname, "..", "package.json");
184
- const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
185
- return packageJson.version || "";
186
- } catch {
187
- return "";
175
+
176
+ // src/utils/renderer.ts
177
+ import chalk from "chalk";
178
+ import { execSync } from "child_process";
179
+ import * as fs3 from "fs";
180
+ import { promisify as promisify3 } from "util";
181
+ var readFile6 = fs3.promises?.readFile || promisify3(fs3.readFile);
182
+ chalk.level = 3;
183
+ function applyColors(text, foregroundColor, backgroundColor, bold) {
184
+ let result = text;
185
+ if (foregroundColor && foregroundColor !== "dim") {
186
+ const fgFunc = chalk[foregroundColor];
187
+ if (fgFunc) {
188
+ result = fgFunc(result);
189
+ }
190
+ }
191
+ if (backgroundColor && backgroundColor !== "none") {
192
+ const bgFunc = chalk[backgroundColor];
193
+ if (bgFunc) {
194
+ result = bgFunc(result);
195
+ }
196
+ }
197
+ if (bold) {
198
+ result = chalk.bold(result);
188
199
  }
200
+ return result;
189
201
  }
190
- function getDefaultColor(type) {
202
+ function getItemDefaultColor(type) {
191
203
  switch (type) {
192
204
  case "model":
193
205
  return "cyan";
@@ -225,7 +237,14 @@ function getDefaultColor(type) {
225
237
  return "white";
226
238
  }
227
239
  }
228
- function canDetectTerminalWidth() {
240
+ function formatTokens(count) {
241
+ if (count >= 1e6)
242
+ return `${(count / 1e6).toFixed(1)}M`;
243
+ if (count >= 1000)
244
+ return `${(count / 1000).toFixed(1)}k`;
245
+ return count.toString();
246
+ }
247
+ function getTerminalWidth() {
229
248
  try {
230
249
  const tty = execSync("ps -o tty= -p $(ps -o ppid= -p $$)", {
231
250
  encoding: "utf8",
@@ -240,7 +259,7 @@ function canDetectTerminalWidth() {
240
259
  }).trim();
241
260
  const parsed = parseInt(width, 10);
242
261
  if (!isNaN(parsed) && parsed > 0) {
243
- return true;
262
+ return parsed;
244
263
  }
245
264
  }
246
265
  } catch {}
@@ -250,194 +269,453 @@ function canDetectTerminalWidth() {
250
269
  stdio: ["pipe", "pipe", "ignore"]
251
270
  }).trim();
252
271
  const parsed = parseInt(width, 10);
253
- return !isNaN(parsed) && parsed > 0;
272
+ if (!isNaN(parsed) && parsed > 0) {
273
+ return parsed;
274
+ }
275
+ } catch {}
276
+ return null;
277
+ }
278
+ function getGitBranch() {
279
+ try {
280
+ const branch = execSync("git branch --show-current 2>/dev/null", {
281
+ encoding: "utf8",
282
+ stdio: ["pipe", "pipe", "ignore"]
283
+ }).trim();
284
+ return branch || null;
254
285
  } catch {
255
- return false;
286
+ return null;
256
287
  }
257
288
  }
258
- var applyColors = (text, foregroundColor, backgroundColor, bold) => {
259
- let result = text;
260
- if (foregroundColor && foregroundColor !== "dim") {
261
- const fgFunc = chalk[foregroundColor];
262
- if (fgFunc) {
263
- result = fgFunc(result);
289
+ function getGitChanges() {
290
+ try {
291
+ let totalInsertions = 0;
292
+ let totalDeletions = 0;
293
+ const unstagedStat = execSync("git diff --shortstat 2>/dev/null", {
294
+ encoding: "utf8",
295
+ stdio: ["pipe", "pipe", "ignore"]
296
+ }).trim();
297
+ const stagedStat = execSync("git diff --cached --shortstat 2>/dev/null", {
298
+ encoding: "utf8",
299
+ stdio: ["pipe", "pipe", "ignore"]
300
+ }).trim();
301
+ if (unstagedStat) {
302
+ const insertMatch = unstagedStat.match(/(\d+) insertion/);
303
+ const deleteMatch = unstagedStat.match(/(\d+) deletion/);
304
+ totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
305
+ totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
264
306
  }
307
+ if (stagedStat) {
308
+ const insertMatch = stagedStat.match(/(\d+) insertion/);
309
+ const deleteMatch = stagedStat.match(/(\d+) deletion/);
310
+ totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
311
+ totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
312
+ }
313
+ return { insertions: totalInsertions, deletions: totalDeletions };
314
+ } catch {
315
+ return null;
265
316
  }
266
- if (backgroundColor && backgroundColor !== "none") {
267
- const bgFunc = chalk[backgroundColor];
268
- if (bgFunc) {
269
- result = bgFunc(result);
317
+ }
318
+ async function getSessionDuration(transcriptPath) {
319
+ try {
320
+ if (!fs3.existsSync(transcriptPath)) {
321
+ return null;
322
+ }
323
+ const content = await readFile6(transcriptPath, "utf-8");
324
+ const lines = content.trim().split(`
325
+ `).filter((line) => line.trim());
326
+ if (lines.length === 0) {
327
+ return null;
328
+ }
329
+ let firstTimestamp = null;
330
+ let lastTimestamp = null;
331
+ for (const line of lines) {
332
+ try {
333
+ const data = JSON.parse(line);
334
+ if (data.timestamp) {
335
+ firstTimestamp = new Date(data.timestamp);
336
+ break;
337
+ }
338
+ } catch {}
339
+ }
340
+ for (let i = lines.length - 1;i >= 0; i--) {
341
+ try {
342
+ const data = JSON.parse(lines[i]);
343
+ if (data.timestamp) {
344
+ lastTimestamp = new Date(data.timestamp);
345
+ break;
346
+ }
347
+ } catch {}
348
+ }
349
+ if (!firstTimestamp || !lastTimestamp) {
350
+ return null;
351
+ }
352
+ const durationMs = lastTimestamp.getTime() - firstTimestamp.getTime();
353
+ const totalMinutes = Math.floor(durationMs / (1000 * 60));
354
+ if (totalMinutes < 1) {
355
+ return "<1m";
356
+ }
357
+ const hours = Math.floor(totalMinutes / 60);
358
+ const minutes = totalMinutes % 60;
359
+ if (hours === 0) {
360
+ return `${minutes}m`;
361
+ } else if (minutes === 0) {
362
+ return `${hours}hr`;
363
+ } else {
364
+ return `${hours}hr ${minutes}m`;
270
365
  }
366
+ } catch {
367
+ return null;
271
368
  }
272
- if (bold) {
273
- result = chalk.bold(result);
369
+ }
370
+ async function getTokenMetrics(transcriptPath) {
371
+ try {
372
+ if (!fs3.existsSync(transcriptPath)) {
373
+ return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
374
+ }
375
+ const content = await readFile6(transcriptPath, "utf-8");
376
+ const lines = content.trim().split(`
377
+ `);
378
+ let inputTokens = 0;
379
+ let outputTokens = 0;
380
+ let cachedTokens = 0;
381
+ let contextLength = 0;
382
+ let mostRecentMainChainEntry = null;
383
+ let mostRecentTimestamp = null;
384
+ for (const line of lines) {
385
+ try {
386
+ const data = JSON.parse(line);
387
+ if (data.message?.usage) {
388
+ inputTokens += data.message.usage.input_tokens || 0;
389
+ outputTokens += data.message.usage.output_tokens || 0;
390
+ cachedTokens += data.message.usage.cache_read_input_tokens || 0;
391
+ cachedTokens += data.message.usage.cache_creation_input_tokens || 0;
392
+ if (data.isSidechain !== true && data.timestamp) {
393
+ const entryTime = new Date(data.timestamp);
394
+ if (!mostRecentTimestamp || entryTime > mostRecentTimestamp) {
395
+ mostRecentTimestamp = entryTime;
396
+ mostRecentMainChainEntry = data;
397
+ }
398
+ }
399
+ }
400
+ } catch {}
401
+ }
402
+ if (mostRecentMainChainEntry?.message?.usage) {
403
+ const usage = mostRecentMainChainEntry.message.usage;
404
+ contextLength = (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0) + (usage.cache_creation_input_tokens || 0);
405
+ }
406
+ const totalTokens = inputTokens + outputTokens + cachedTokens;
407
+ return { inputTokens, outputTokens, cachedTokens, totalTokens, contextLength };
408
+ } catch {
409
+ return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
274
410
  }
275
- return result;
276
- };
277
- var renderSingleLine = (items, terminalWidth, widthDetectionAvailable, settings) => {
278
- const applyItemColors = (text, item, defaultColor) => {
279
- let fgColor = item.color || defaultColor;
280
- if (settings?.overrideForegroundColor && settings.overrideForegroundColor !== "none") {
411
+ }
412
+ function renderStatusLine(items, settings, context) {
413
+ const applyColorsWithOverride = (text, foregroundColor, backgroundColor, bold) => {
414
+ let fgColor = foregroundColor;
415
+ if (settings.overrideForegroundColor && settings.overrideForegroundColor !== "none") {
281
416
  fgColor = settings.overrideForegroundColor;
282
417
  }
283
- let bgColor = item.backgroundColor;
284
- if (settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== "none") {
418
+ let bgColor = backgroundColor;
419
+ if (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== "none") {
285
420
  bgColor = settings.overrideBackgroundColor;
286
421
  }
287
- const shouldBold = settings?.globalBold || item.bold;
422
+ const shouldBold = settings.globalBold || bold;
288
423
  return applyColors(text, fgColor, bgColor, shouldBold);
289
424
  };
290
- let effectiveWidth = null;
291
- if (widthDetectionAvailable && terminalWidth) {
292
- const flexMode = settings?.flexMode || "full-minus-40";
293
- if (flexMode === "full") {
294
- effectiveWidth = terminalWidth - 2;
295
- } else if (flexMode === "full-minus-40") {
296
- effectiveWidth = terminalWidth - 40;
297
- } else if (flexMode === "full-until-compact") {
298
- effectiveWidth = terminalWidth - 2;
425
+ const detectedWidth = context.terminalWidth || getTerminalWidth();
426
+ let terminalWidth = null;
427
+ if (detectedWidth) {
428
+ const flexMode = settings.flexMode || "full-minus-40";
429
+ if (context.isPreview) {
430
+ if (flexMode === "full") {
431
+ terminalWidth = detectedWidth - 6;
432
+ } else if (flexMode === "full-minus-40") {
433
+ terminalWidth = detectedWidth - 43;
434
+ } else if (flexMode === "full-until-compact") {
435
+ terminalWidth = detectedWidth - 6;
436
+ }
437
+ } else {
438
+ if (flexMode === "full") {
439
+ terminalWidth = detectedWidth - 4;
440
+ } else if (flexMode === "full-minus-40") {
441
+ terminalWidth = detectedWidth - 41;
442
+ } else if (flexMode === "full-until-compact") {
443
+ const threshold = settings.compactThreshold || 60;
444
+ const contextPercentage = context.tokenMetrics ? Math.min(100, context.tokenMetrics.contextLength / 200000 * 100) : 0;
445
+ if (contextPercentage >= threshold) {
446
+ terminalWidth = detectedWidth - 40;
447
+ } else {
448
+ terminalWidth = detectedWidth - 4;
449
+ }
450
+ }
299
451
  }
300
452
  }
301
- const width = terminalWidth;
302
- const flexWidth = effectiveWidth;
303
- const rawElements = [];
453
+ const elements = [];
304
454
  let hasFlexSeparator = false;
305
- items.forEach((item, index) => {
455
+ for (const item of items) {
306
456
  switch (item.type) {
307
457
  case "model":
308
- const modelText = item.rawValue ? "Claude" : "Model: Claude";
309
- rawElements.push({ content: applyItemColors(modelText, item, "cyan"), type: "model", item });
458
+ if (context.isPreview) {
459
+ const modelText = item.rawValue ? "Claude" : "Model: Claude";
460
+ elements.push({ content: applyColorsWithOverride(modelText, item.color || "cyan", item.backgroundColor, item.bold), type: "model", item });
461
+ } else if (context.data?.model) {
462
+ const text = item.rawValue ? context.data.model.display_name : `Model: ${context.data.model.display_name}`;
463
+ elements.push({ content: applyColorsWithOverride(text, item.color || "cyan", item.backgroundColor, item.bold), type: "model", item });
464
+ }
310
465
  break;
311
466
  case "git-branch":
312
- const branchText = "⎇ main";
313
- rawElements.push({ content: applyItemColors(branchText, item, "magenta"), type: "git-branch", item });
467
+ if (context.isPreview) {
468
+ const branchText = item.rawValue ? "main" : "⎇ main";
469
+ elements.push({ content: applyColorsWithOverride(branchText, item.color || "magenta", item.backgroundColor, item.bold), type: "git-branch", item });
470
+ } else {
471
+ const branch = context.gitBranch || getGitBranch();
472
+ if (branch) {
473
+ const text = item.rawValue ? branch : `⎇ ${branch}`;
474
+ elements.push({ content: applyColorsWithOverride(text, item.color || "magenta", item.backgroundColor, item.bold), type: "git-branch", item });
475
+ }
476
+ }
314
477
  break;
315
478
  case "git-changes":
316
- const changesText = "(+42,-10)";
317
- rawElements.push({ content: applyItemColors(changesText, item, "yellow"), type: "git-changes", item });
479
+ if (context.isPreview) {
480
+ const changesText = "(+42,-10)";
481
+ elements.push({ content: applyColorsWithOverride(changesText, item.color || "yellow", item.backgroundColor, item.bold), type: "git-changes", item });
482
+ } else {
483
+ const changes = context.gitChanges || getGitChanges();
484
+ if (changes !== null) {
485
+ const changeStr = `(+${changes.insertions},-${changes.deletions})`;
486
+ elements.push({ content: applyColorsWithOverride(changeStr, item.color || "yellow", item.backgroundColor, item.bold), type: "git-changes", item });
487
+ }
488
+ }
318
489
  break;
319
490
  case "tokens-input":
320
- const inputText = item.rawValue ? "15.2k" : "In: 15.2k";
321
- rawElements.push({ content: applyItemColors(inputText, item, "blue"), type: "tokens-input", item });
491
+ if (context.isPreview) {
492
+ const inputText = item.rawValue ? "15.2k" : "In: 15.2k";
493
+ elements.push({ content: applyColorsWithOverride(inputText, item.color || "blue", item.backgroundColor, item.bold), type: "tokens-input", item });
494
+ } else if (context.tokenMetrics) {
495
+ const text = item.rawValue ? formatTokens(context.tokenMetrics.inputTokens) : `In: ${formatTokens(context.tokenMetrics.inputTokens)}`;
496
+ elements.push({ content: applyColorsWithOverride(text, item.color || "blue", item.backgroundColor, item.bold), type: "tokens-input", item });
497
+ }
322
498
  break;
323
499
  case "tokens-output":
324
- const outputText = item.rawValue ? "3.4k" : "Out: 3.4k";
325
- rawElements.push({ content: applyItemColors(outputText, item, "white"), type: "tokens-output", item });
500
+ if (context.isPreview) {
501
+ const outputText = item.rawValue ? "3.4k" : "Out: 3.4k";
502
+ elements.push({ content: applyColorsWithOverride(outputText, item.color || "white", item.backgroundColor, item.bold), type: "tokens-output", item });
503
+ } else if (context.tokenMetrics) {
504
+ const text = item.rawValue ? formatTokens(context.tokenMetrics.outputTokens) : `Out: ${formatTokens(context.tokenMetrics.outputTokens)}`;
505
+ elements.push({ content: applyColorsWithOverride(text, item.color || "white", item.backgroundColor, item.bold), type: "tokens-output", item });
506
+ }
326
507
  break;
327
508
  case "tokens-cached":
328
- const cachedText = item.rawValue ? "12k" : "Cached: 12k";
329
- rawElements.push({ content: applyItemColors(cachedText, item, "cyan"), type: "tokens-cached", item });
509
+ if (context.isPreview) {
510
+ const cachedText = item.rawValue ? "12k" : "Cached: 12k";
511
+ elements.push({ content: applyColorsWithOverride(cachedText, item.color || "cyan", item.backgroundColor, item.bold), type: "tokens-cached", item });
512
+ } else if (context.tokenMetrics) {
513
+ const text = item.rawValue ? formatTokens(context.tokenMetrics.cachedTokens) : `Cached: ${formatTokens(context.tokenMetrics.cachedTokens)}`;
514
+ elements.push({ content: applyColorsWithOverride(text, item.color || "cyan", item.backgroundColor, item.bold), type: "tokens-cached", item });
515
+ }
330
516
  break;
331
517
  case "tokens-total":
332
- const totalText = item.rawValue ? "30.6k" : "Total: 30.6k";
333
- rawElements.push({ content: applyItemColors(totalText, item, "cyan"), type: "tokens-total", item });
518
+ if (context.isPreview) {
519
+ const totalText = item.rawValue ? "30.6k" : "Total: 30.6k";
520
+ elements.push({ content: applyColorsWithOverride(totalText, item.color || "cyan", item.backgroundColor, item.bold), type: "tokens-total", item });
521
+ } else if (context.tokenMetrics) {
522
+ const text = item.rawValue ? formatTokens(context.tokenMetrics.totalTokens) : `Total: ${formatTokens(context.tokenMetrics.totalTokens)}`;
523
+ elements.push({ content: applyColorsWithOverride(text, item.color || "cyan", item.backgroundColor, item.bold), type: "tokens-total", item });
524
+ }
334
525
  break;
335
526
  case "context-length":
336
- const ctxText = item.rawValue ? "18.6k" : "Ctx: 18.6k";
337
- rawElements.push({ content: applyItemColors(ctxText, item, "gray"), type: "context-length", item });
527
+ if (context.isPreview) {
528
+ const ctxText = item.rawValue ? "18.6k" : "Ctx: 18.6k";
529
+ elements.push({ content: applyColorsWithOverride(ctxText, item.color || "gray", item.backgroundColor, item.bold), type: "context-length", item });
530
+ } else if (context.tokenMetrics) {
531
+ const text = item.rawValue ? formatTokens(context.tokenMetrics.contextLength) : `Ctx: ${formatTokens(context.tokenMetrics.contextLength)}`;
532
+ elements.push({ content: applyColorsWithOverride(text, item.color || "gray", item.backgroundColor, item.bold), type: "context-length", item });
533
+ }
338
534
  break;
339
535
  case "context-percentage":
340
- const ctxPctText = item.rawValue ? "9.3%" : "Ctx: 9.3%";
341
- rawElements.push({ content: applyItemColors(ctxPctText, item, "blue"), type: "context-percentage", item });
536
+ if (context.isPreview) {
537
+ const ctxPctText = item.rawValue ? "9.3%" : "Ctx: 9.3%";
538
+ elements.push({ content: applyColorsWithOverride(ctxPctText, item.color || "blue", item.backgroundColor, item.bold), type: "context-percentage", item });
539
+ } else if (context.tokenMetrics) {
540
+ const percentage = Math.min(100, context.tokenMetrics.contextLength / 200000 * 100);
541
+ const text = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx: ${percentage.toFixed(1)}%`;
542
+ elements.push({ content: applyColorsWithOverride(text, item.color || "blue", item.backgroundColor, item.bold), type: "context-percentage", item });
543
+ }
342
544
  break;
343
545
  case "context-percentage-usable":
344
- const ctxUsableText = item.rawValue ? "11.6%" : "Ctx(u): 11.6%";
345
- rawElements.push({ content: applyItemColors(ctxUsableText, item, "green"), type: "context-percentage-usable", item });
546
+ if (context.isPreview) {
547
+ const ctxUsableText = item.rawValue ? "11.6%" : "Ctx(u): 11.6%";
548
+ elements.push({ content: applyColorsWithOverride(ctxUsableText, item.color || "green", item.backgroundColor, item.bold), type: "context-percentage-usable", item });
549
+ } else if (context.tokenMetrics) {
550
+ const percentage = Math.min(100, context.tokenMetrics.contextLength / 160000 * 100);
551
+ const text = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx(u): ${percentage.toFixed(1)}%`;
552
+ elements.push({ content: applyColorsWithOverride(text, item.color || "green", item.backgroundColor, item.bold), type: "context-percentage-usable", item });
553
+ }
554
+ break;
555
+ case "terminal-width":
556
+ const width = terminalWidth || getTerminalWidth();
557
+ if (context.isPreview) {
558
+ const detectedWidth2 = width || "??";
559
+ const termText = item.rawValue ? `${detectedWidth2}` : `Term: ${detectedWidth2}`;
560
+ elements.push({ content: applyColorsWithOverride(termText, item.color || "gray", item.backgroundColor, item.bold), type: "terminal-width", item });
561
+ } else if (width) {
562
+ const text = item.rawValue ? `${width}` : `Term: ${width}`;
563
+ elements.push({ content: applyColorsWithOverride(text, item.color || "gray", item.backgroundColor, item.bold), type: "terminal-width", item });
564
+ }
346
565
  break;
347
566
  case "session-clock":
348
- const sessionText = item.rawValue ? "2hr 15m" : "Session: 2hr 15m";
349
- rawElements.push({ content: applyItemColors(sessionText, item, "yellow"), type: "session-clock", item });
567
+ if (context.isPreview) {
568
+ const sessionText = item.rawValue ? "2hr 15m" : "Session: 2hr 15m";
569
+ elements.push({ content: applyColorsWithOverride(sessionText, item.color || "yellow", item.backgroundColor, item.bold), type: "session-clock", item });
570
+ } else if (context.sessionDuration) {
571
+ const text = item.rawValue ? context.sessionDuration : `Session: ${context.sessionDuration}`;
572
+ elements.push({ content: applyColorsWithOverride(text, item.color || "yellow", item.backgroundColor, item.bold), type: "session-clock", item });
573
+ }
350
574
  break;
351
575
  case "version":
352
- const versionText = item.rawValue ? "1.0.72" : "Version: 1.0.72";
353
- rawElements.push({ content: applyItemColors(versionText, item, "green"), type: "version", item });
354
- break;
355
- case "terminal-width":
356
- const detectedWidth = canDetectTerminalWidth() ? terminalWidth : "??";
357
- const termText = item.rawValue ? `${detectedWidth}` : `Term: ${detectedWidth}`;
358
- rawElements.push({ content: applyItemColors(termText, item, "gray"), type: "terminal-width", item });
576
+ if (context.isPreview) {
577
+ const versionText = item.rawValue ? "1.0.72" : "Version: 1.0.72";
578
+ elements.push({ content: applyColorsWithOverride(versionText, item.color || "green", item.backgroundColor, item.bold), type: "version", item });
579
+ } else if (context.data?.version) {
580
+ const versionString = context.data.version || "Unknown";
581
+ const versionText = item.rawValue ? versionString : `Version: ${versionString}`;
582
+ elements.push({ content: applyColorsWithOverride(versionText, item.color || "green", item.backgroundColor, item.bold), type: "version", item });
583
+ }
359
584
  break;
360
585
  case "separator":
361
586
  const sepChar = item.character || "|";
362
- let sepContent;
587
+ let sepText;
363
588
  if (sepChar === ",") {
364
- sepContent = `${sepChar} `;
589
+ sepText = `${sepChar} `;
365
590
  } else if (sepChar === " ") {
366
- sepContent = " ";
591
+ sepText = " ";
367
592
  } else {
368
- sepContent = ` ${sepChar} `;
593
+ sepText = ` ${sepChar} `;
369
594
  }
370
- rawElements.push({ content: applyItemColors(sepContent, item, "gray"), type: "separator", item });
595
+ const sepContent = applyColorsWithOverride(sepText, item.color || "gray", item.backgroundColor, item.bold);
596
+ elements.push({ content: sepContent, type: "separator", item });
371
597
  break;
372
598
  case "flex-separator":
373
- rawElements.push({ content: "FLEX", type: "flex-separator", item });
599
+ elements.push({ content: "FLEX", type: "flex-separator", item });
374
600
  hasFlexSeparator = true;
375
601
  break;
376
602
  case "custom-text":
377
603
  const customText = item.customText || "";
378
- rawElements.push({ content: applyItemColors(customText, item, "white"), type: "custom-text", item });
604
+ elements.push({ content: applyColorsWithOverride(customText, item.color || "white", item.backgroundColor, item.bold), type: "custom-text", item });
379
605
  break;
380
606
  case "custom-command":
381
- const cmdText = item.commandPath ? `[cmd: ${item.commandPath.substring(0, 20)}${item.commandPath.length > 20 ? "..." : ""}]` : "[No command]";
382
- if (!item.preserveColors) {
383
- rawElements.push({ content: applyItemColors(cmdText, item, "white"), type: "custom-command", item });
384
- } else {
385
- rawElements.push({ content: cmdText, type: "custom-command", item });
386
- }
387
- break;
388
- }
389
- });
390
- const elements = [];
391
- const padding = settings?.defaultPadding || "";
392
- const defaultSep = settings?.defaultSeparator || "";
393
- rawElements.forEach((elem, index) => {
394
- if (defaultSep && index > 0) {
395
- if (settings?.inheritSeparatorColors && index > 0) {
396
- const prevElem = rawElements[index - 1];
397
- if (prevElem && prevElem.item) {
398
- const fgColor = settings?.overrideForegroundColor && settings.overrideForegroundColor !== "none" ? settings.overrideForegroundColor : prevElem.item.color || getDefaultColor(prevElem.item.type);
399
- const bgColor = settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== "none" ? settings.overrideBackgroundColor : prevElem.item.backgroundColor;
400
- const shouldBold = settings?.globalBold || prevElem.item.bold;
401
- const coloredSep = applyColors(defaultSep, fgColor, bgColor, shouldBold);
402
- elements.push(coloredSep);
403
- } else {
404
- elements.push(defaultSep);
405
- }
406
- } else if (settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== "none" || settings?.overrideForegroundColor && settings.overrideForegroundColor !== "none") {
407
- const fgColor = settings?.overrideForegroundColor && settings.overrideForegroundColor !== "none" ? settings.overrideForegroundColor : undefined;
408
- const coloredSep = applyColors(defaultSep, fgColor, settings.overrideBackgroundColor, settings?.globalBold);
409
- elements.push(coloredSep);
410
- } else {
411
- elements.push(defaultSep);
412
- }
413
- }
414
- if (elem.type === "separator" || elem.type === "flex-separator") {
415
- elements.push(elem.content);
416
- } else {
417
- const fgColor = settings?.overrideForegroundColor && settings.overrideForegroundColor !== "none" ? settings.overrideForegroundColor : undefined;
418
- const bgColor = settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== "none" ? settings.overrideBackgroundColor : elem.item?.backgroundColor;
419
- if (padding && (bgColor || fgColor)) {
420
- const paddedContent = applyColors(padding, fgColor, bgColor, settings?.globalBold) + elem.content + applyColors(padding, fgColor, bgColor, settings?.globalBold);
421
- elements.push(paddedContent);
607
+ if (context.isPreview) {
608
+ const cmdText = item.commandPath ? `[cmd: ${item.commandPath.substring(0, 20)}${item.commandPath.length > 20 ? "..." : ""}]` : "[No command]";
609
+ if (!item.preserveColors) {
610
+ elements.push({ content: applyColorsWithOverride(cmdText, item.color || "white", item.backgroundColor, item.bold), type: "custom-command", item });
611
+ } else {
612
+ elements.push({ content: cmdText, type: "custom-command", item });
613
+ }
614
+ } else if (item.commandPath && context.data) {
615
+ try {
616
+ const timeout = item.timeout || 1000;
617
+ const output = execSync(item.commandPath, {
618
+ encoding: "utf8",
619
+ input: JSON.stringify(context.data),
620
+ stdio: ["pipe", "pipe", "ignore"],
621
+ timeout
622
+ }).trim();
623
+ if (output) {
624
+ let finalOutput = output;
625
+ if (item.maxWidth && item.maxWidth > 0) {
626
+ const plainLength = output.replace(/\x1b\[[0-9;]*m/g, "").length;
627
+ if (plainLength > item.maxWidth) {
628
+ let truncated = "";
629
+ let currentLength = 0;
630
+ let inAnsiCode = false;
631
+ let ansiBuffer = "";
632
+ for (let i = 0;i < output.length; i++) {
633
+ const char = output[i];
634
+ if (char === "\x1B") {
635
+ inAnsiCode = true;
636
+ ansiBuffer = char;
637
+ } else if (inAnsiCode) {
638
+ ansiBuffer += char;
639
+ if (char === "m") {
640
+ truncated += ansiBuffer;
641
+ inAnsiCode = false;
642
+ ansiBuffer = "";
643
+ }
644
+ } else {
645
+ if (currentLength < item.maxWidth) {
646
+ truncated += char;
647
+ currentLength++;
648
+ } else {
649
+ break;
650
+ }
651
+ }
652
+ }
653
+ finalOutput = truncated;
654
+ }
655
+ }
656
+ if (!item.preserveColors) {
657
+ const stripped = finalOutput.replace(/\x1b\[[0-9;]*m/g, "");
658
+ elements.push({ content: applyColorsWithOverride(stripped, item.color || "white", item.backgroundColor, item.bold), type: "custom-command", item });
659
+ } else {
660
+ elements.push({ content: finalOutput, type: "custom-command", item });
661
+ }
662
+ }
663
+ } catch {}
664
+ }
665
+ break;
666
+ }
667
+ }
668
+ if (elements.length === 0)
669
+ return "";
670
+ while (elements.length > 0 && elements[elements.length - 1]?.type === "separator") {
671
+ elements.pop();
672
+ }
673
+ const finalElements = [];
674
+ const padding = settings.defaultPadding || "";
675
+ const defaultSep = settings.defaultSeparator || "";
676
+ elements.forEach((elem, index) => {
677
+ const prevElem = index > 0 ? elements[index - 1] : null;
678
+ const shouldAddSeparator = defaultSep && index > 0 && elem.type !== "flex-separator" && prevElem?.type !== "flex-separator";
679
+ if (shouldAddSeparator) {
680
+ if (settings.inheritSeparatorColors && index > 0) {
681
+ const prevElem2 = elements[index - 1];
682
+ if (prevElem2 && prevElem2.item) {
683
+ const itemColor = prevElem2.item.color || getItemDefaultColor(prevElem2.item.type);
684
+ const coloredSep = applyColorsWithOverride(defaultSep, itemColor, prevElem2.item.backgroundColor, prevElem2.item.bold);
685
+ finalElements.push(coloredSep);
686
+ } else {
687
+ finalElements.push(defaultSep);
688
+ }
689
+ } else if (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== "none" || settings.overrideForegroundColor && settings.overrideForegroundColor !== "none") {
690
+ const coloredSep = applyColorsWithOverride(defaultSep, undefined, undefined);
691
+ finalElements.push(coloredSep);
692
+ } else {
693
+ finalElements.push(defaultSep);
694
+ }
695
+ }
696
+ if (elem.type === "separator" || elem.type === "flex-separator") {
697
+ finalElements.push(elem.content);
698
+ } else {
699
+ const hasColorOverride = settings.overrideBackgroundColor && settings.overrideBackgroundColor !== "none" || settings.overrideForegroundColor && settings.overrideForegroundColor !== "none";
700
+ if (padding && (elem.item?.backgroundColor || hasColorOverride)) {
701
+ const paddedContent = applyColorsWithOverride(padding, undefined, elem.item?.backgroundColor) + elem.content + applyColorsWithOverride(padding, undefined, elem.item?.backgroundColor);
702
+ finalElements.push(paddedContent);
422
703
  } else {
423
- elements.push(padding + elem.content + padding);
704
+ finalElements.push(padding + elem.content + padding);
424
705
  }
425
706
  }
426
707
  });
427
708
  let statusLine = "";
428
- if (hasFlexSeparator && flexWidth) {
709
+ if (hasFlexSeparator && terminalWidth) {
429
710
  const parts = [[]];
430
711
  let currentPart = 0;
431
- for (let i = 0;i < items.length; i++) {
432
- const item = items[i];
433
- if (item && item.type === "flex-separator") {
712
+ for (let i = 0;i < finalElements.length; i++) {
713
+ const elem = finalElements[i];
714
+ if (elem === "FLEX") {
434
715
  currentPart++;
435
716
  parts[currentPart] = [];
436
717
  } else {
437
- const element = elements[i];
438
- if (element !== "FLEX" && parts[currentPart]) {
439
- parts[currentPart].push(element);
440
- }
718
+ parts[currentPart]?.push(elem);
441
719
  }
442
720
  }
443
721
  const partLengths = parts.map((part) => {
@@ -446,7 +724,7 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable, settings)
446
724
  });
447
725
  const totalContentLength = partLengths.reduce((sum, len) => sum + len, 0);
448
726
  const flexCount = parts.length - 1;
449
- const totalSpace = Math.max(0, flexWidth - totalContentLength);
727
+ const totalSpace = Math.max(0, terminalWidth - totalContentLength);
450
728
  const spacePerFlex = flexCount > 0 ? Math.floor(totalSpace / flexCount) : 0;
451
729
  const extraSpace = flexCount > 0 ? totalSpace % flexCount : 0;
452
730
  statusLine = "";
@@ -461,16 +739,21 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable, settings)
461
739
  }
462
740
  }
463
741
  } else {
464
- statusLine = elements.map((e) => e === "FLEX" ? chalk.gray(" | ") : e).join("");
742
+ if (hasFlexSeparator && !terminalWidth) {
743
+ statusLine = finalElements.map((e) => e === "FLEX" ? chalk.gray(" | ") : e).join("");
744
+ } else {
745
+ statusLine = finalElements.join("");
746
+ }
465
747
  }
466
- if (width && width > 0) {
748
+ const maxWidth = terminalWidth || detectedWidth;
749
+ if (maxWidth && maxWidth > 0) {
467
750
  const plainLength = statusLine.replace(/\x1b\[[0-9;]*m/g, "").length;
468
- if (plainLength > width) {
751
+ if (plainLength > maxWidth) {
469
752
  let truncated = "";
470
753
  let currentLength = 0;
471
754
  let inAnsiCode = false;
472
755
  let ansiBuffer = "";
473
- const targetLength = width - 5;
756
+ const targetLength = context.isPreview ? maxWidth - 3 : maxWidth - 3;
474
757
  for (let i = 0;i < statusLine.length; i++) {
475
758
  const char = statusLine[i];
476
759
  if (char === "\x1B") {
@@ -496,26 +779,72 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable, settings)
496
779
  }
497
780
  }
498
781
  return statusLine;
782
+ }
783
+
784
+ // src/tui.tsx
785
+ var __dirname = "/Users/sirmalloc/Projects/Personal/ccstatusline/src";
786
+ import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
787
+ function getPackageVersion() {
788
+ try {
789
+ const packageJsonPath = path3.join(__dirname, "..", "package.json");
790
+ const packageJson = JSON.parse(fs4.readFileSync(packageJsonPath, "utf-8"));
791
+ return packageJson.version || "";
792
+ } catch {
793
+ return "";
794
+ }
795
+ }
796
+ var getDefaultColor = getItemDefaultColor;
797
+ function canDetectTerminalWidth() {
798
+ try {
799
+ const tty = execSync2("ps -o tty= -p $(ps -o ppid= -p $$)", {
800
+ encoding: "utf8",
801
+ stdio: ["pipe", "pipe", "ignore"],
802
+ shell: "/bin/sh"
803
+ }).trim();
804
+ if (tty && tty !== "??" && tty !== "?") {
805
+ const width = execSync2(`stty size < /dev/${tty} | awk '{print $2}'`, {
806
+ encoding: "utf8",
807
+ stdio: ["pipe", "pipe", "ignore"],
808
+ shell: "/bin/sh"
809
+ }).trim();
810
+ const parsed = parseInt(width, 10);
811
+ if (!isNaN(parsed) && parsed > 0) {
812
+ return true;
813
+ }
814
+ }
815
+ } catch {}
816
+ try {
817
+ const width = execSync2("tput cols 2>/dev/null", {
818
+ encoding: "utf8",
819
+ stdio: ["pipe", "pipe", "ignore"]
820
+ }).trim();
821
+ const parsed = parseInt(width, 10);
822
+ return !isNaN(parsed) && parsed > 0;
823
+ } catch {
824
+ return false;
825
+ }
826
+ }
827
+ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable, settings) => {
828
+ const context = {
829
+ terminalWidth,
830
+ isPreview: true
831
+ };
832
+ return renderStatusLine(items, settings || {}, context);
499
833
  };
500
834
  var StatusLinePreview = ({ lines, terminalWidth, settings }) => {
501
835
  const widthDetectionAvailable = canDetectTerminalWidth();
502
- const boxWidth = Math.min(terminalWidth - 4, process.stdout.columns - 4 || 76);
503
- const topLine = chalk.gray("╭" + "─".repeat(Math.max(0, boxWidth - 2)) + "╮");
504
- const middleLine = chalk.gray("│") + " > " + " ".repeat(Math.max(0, boxWidth - 5)) + chalk.gray("│");
505
- const bottomLine = chalk.gray("╰" + "─".repeat(Math.max(0, boxWidth - 2)) + "╯");
506
- const availableWidth = boxWidth - 2;
507
- const renderedLines = lines.map((lineItems) => lineItems.length > 0 ? renderSingleLine(lineItems, availableWidth, widthDetectionAvailable, settings) : "").filter((line) => line !== "");
836
+ const renderedLines = lines.map((lineItems) => lineItems.length > 0 ? renderSingleLine(lineItems, terminalWidth, widthDetectionAvailable, settings) : "").filter((line) => line !== "");
508
837
  return /* @__PURE__ */ jsxDEV(Box, {
509
838
  flexDirection: "column",
510
839
  children: [
511
- /* @__PURE__ */ jsxDEV(Text, {
512
- children: topLine
513
- }, undefined, false, undefined, this),
514
- /* @__PURE__ */ jsxDEV(Text, {
515
- children: middleLine
516
- }, undefined, false, undefined, this),
517
- /* @__PURE__ */ jsxDEV(Text, {
518
- children: bottomLine
840
+ /* @__PURE__ */ jsxDEV(Box, {
841
+ borderStyle: "round",
842
+ borderColor: "gray",
843
+ width: "100%",
844
+ paddingLeft: 1,
845
+ children: /* @__PURE__ */ jsxDEV(Text, {
846
+ children: ">"
847
+ }, undefined, false, undefined, this)
519
848
  }, undefined, false, undefined, this),
520
849
  renderedLines.map((line, index) => /* @__PURE__ */ jsxDEV(Text, {
521
850
  children: [
@@ -596,7 +925,7 @@ var MainMenu = ({ onSelect, isClaudeInstalled, hasChanges, initialSelection = 0
596
925
  const items = [
597
926
  { label: "\uD83D\uDCDD Edit Lines", value: "lines" },
598
927
  { label: "\uD83C\uDFA8 Configure Colors", value: "colors" },
599
- { label: " Flex Options", value: "flex" },
928
+ { label: "\uD83D\uDCCF Terminal Width Options", value: "terminalWidth" },
600
929
  { label: "\uD83D\uDD27 Global Options", value: "globalOptions" },
601
930
  { label: isClaudeInstalled ? "\uD83D\uDDD1️ Uninstall from Claude Code" : "\uD83D\uDCE6 Install to Claude Code", value: "install" }
602
931
  ];
@@ -661,11 +990,15 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
661
990
  setTextCursorPos(0);
662
991
  } else if (key.ctrl && key.rightArrow) {
663
992
  setTextCursorPos(textInput.length);
664
- } else if (key.backspace || key.delete) {
993
+ } else if (key.backspace) {
665
994
  if (textCursorPos > 0) {
666
995
  setTextInput(textInput.slice(0, textCursorPos - 1) + textInput.slice(textCursorPos));
667
996
  setTextCursorPos(textCursorPos - 1);
668
997
  }
998
+ } else if (key.delete) {
999
+ if (textCursorPos < textInput.length) {
1000
+ setTextInput(textInput.slice(0, textCursorPos) + textInput.slice(textCursorPos + 1));
1001
+ }
669
1002
  } else if (input && input.length === 1) {
670
1003
  setTextInput(textInput.slice(0, textCursorPos) + input + textInput.slice(textCursorPos));
671
1004
  setTextCursorPos(textCursorPos + 1);
@@ -693,11 +1026,15 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
693
1026
  setCommandCursorPos(0);
694
1027
  } else if (key.ctrl && key.rightArrow) {
695
1028
  setCommandCursorPos(commandInput.length);
696
- } else if (key.backspace || key.delete) {
1029
+ } else if (key.backspace) {
697
1030
  if (commandCursorPos > 0) {
698
1031
  setCommandInput(commandInput.slice(0, commandCursorPos - 1) + commandInput.slice(commandCursorPos));
699
1032
  setCommandCursorPos(commandCursorPos - 1);
700
1033
  }
1034
+ } else if (key.delete) {
1035
+ if (commandCursorPos < commandInput.length) {
1036
+ setCommandInput(commandInput.slice(0, commandCursorPos) + commandInput.slice(commandCursorPos + 1));
1037
+ }
701
1038
  } else if (input) {
702
1039
  setCommandInput(commandInput.slice(0, commandCursorPos) + input + commandInput.slice(commandCursorPos));
703
1040
  setCommandCursorPos(commandCursorPos + input.length);
@@ -721,9 +1058,9 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
721
1058
  } else if (key.escape) {
722
1059
  setEditingMaxWidth(false);
723
1060
  setMaxWidthInput("");
724
- } else if (key.backspace || key.delete) {
1061
+ } else if (key.backspace) {
725
1062
  setMaxWidthInput(maxWidthInput.slice(0, -1));
726
- } else if (input && /\d/.test(input)) {
1063
+ } else if (key.delete) {} else if (input && /\d/.test(input)) {
727
1064
  setMaxWidthInput(maxWidthInput + input);
728
1065
  }
729
1066
  } else if (editingTimeout) {
@@ -745,9 +1082,9 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
745
1082
  } else if (key.escape) {
746
1083
  setEditingTimeout(false);
747
1084
  setTimeoutInput("");
748
- } else if (key.backspace || key.delete) {
1085
+ } else if (key.backspace) {
749
1086
  setTimeoutInput(timeoutInput.slice(0, -1));
750
- } else if (input && /\d/.test(input)) {
1087
+ } else if (key.delete) {} else if (input && /\d/.test(input)) {
751
1088
  setTimeoutInput(timeoutInput + input);
752
1089
  }
753
1090
  } else if (moveMode) {
@@ -926,7 +1263,7 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
926
1263
  });
927
1264
  const getItemDisplay = (item) => {
928
1265
  const colorName = item.color || getDefaultColor(item.type);
929
- const colorFunc = chalk[colorName] || chalk.white;
1266
+ const colorFunc = chalk2[colorName] || chalk2.white;
930
1267
  switch (item.type) {
931
1268
  case "model":
932
1269
  return colorFunc("Model");
@@ -940,7 +1277,7 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
940
1277
  return applyColors(`Separator ${charDisplay}`, item.color || "gray", item.backgroundColor, item.bold);
941
1278
  }
942
1279
  case "flex-separator":
943
- return chalk.yellow("Flex Separator");
1280
+ return chalk2.yellow("Flex Separator");
944
1281
  case "tokens-input":
945
1282
  return colorFunc("Tokens Input");
946
1283
  case "tokens-output":
@@ -970,7 +1307,7 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
970
1307
  if (!item.preserveColors) {
971
1308
  return colorFunc(`Custom Command (${truncatedCmd})`);
972
1309
  } else {
973
- return chalk.white(`Custom Command (${truncatedCmd}) [preserving colors]`);
1310
+ return chalk2.white(`Custom Command (${truncatedCmd}) [preserving colors]`);
974
1311
  }
975
1312
  }
976
1313
  };
@@ -1138,7 +1475,13 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
1138
1475
  }, undefined, true, undefined, this);
1139
1476
  };
1140
1477
  var ColorMenu = ({ items, onUpdate, onBack }) => {
1141
- const colorableItems = items.filter((item) => ["model", "git-branch", "git-changes", "tokens-input", "tokens-output", "tokens-cached", "tokens-total", "context-length", "context-percentage", "context-percentage-usable", "session-clock", "terminal-width", "version", "custom-text", "custom-command"].includes(item.type) && !(item.type === "custom-command" && item.preserveColors));
1478
+ const [showSeparators, setShowSeparators] = useState(false);
1479
+ const colorableItems = items.filter((item) => {
1480
+ if (item.type === "separator") {
1481
+ return showSeparators;
1482
+ }
1483
+ return ["model", "git-branch", "git-changes", "tokens-input", "tokens-output", "tokens-cached", "tokens-total", "context-length", "context-percentage", "context-percentage-usable", "session-clock", "terminal-width", "version", "custom-text", "custom-command"].includes(item.type) && !(item.type === "custom-command" && item.preserveColors);
1484
+ });
1142
1485
  const [highlightedItemId, setHighlightedItemId] = useState(colorableItems[0]?.id || null);
1143
1486
  const [editingBackground, setEditingBackground] = useState(false);
1144
1487
  const hasNoItems = colorableItems.length === 0;
@@ -1153,6 +1496,17 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
1153
1496
  } else {
1154
1497
  onBack();
1155
1498
  }
1499
+ } else if (input === "s" || input === "S") {
1500
+ setShowSeparators(!showSeparators);
1501
+ const newColorableItems = items.filter((item) => {
1502
+ if (item.type === "separator") {
1503
+ return !showSeparators;
1504
+ }
1505
+ return ["model", "git-branch", "git-changes", "tokens-input", "tokens-output", "tokens-cached", "tokens-total", "context-length", "context-percentage", "context-percentage-usable", "session-clock", "terminal-width", "version", "custom-text", "custom-command"].includes(item.type) && !(item.type === "custom-command" && item.preserveColors);
1506
+ });
1507
+ if (newColorableItems.length > 0) {
1508
+ setHighlightedItemId(newColorableItems[0].id);
1509
+ }
1156
1510
  } else if (input === "f" || input === "F") {
1157
1511
  if (colorableItems.length > 0) {
1158
1512
  setEditingBackground(!editingBackground);
@@ -1242,6 +1596,8 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
1242
1596
  return "Terminal Width";
1243
1597
  case "version":
1244
1598
  return "Version";
1599
+ case "separator":
1600
+ return `Separator (${item.character || "|"})`;
1245
1601
  case "custom-text":
1246
1602
  return `Custom Text (${item.customText || "Empty"})`;
1247
1603
  case "custom-command": {
@@ -1345,14 +1701,14 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
1345
1701
  let colorDisplay;
1346
1702
  if (editingBackground) {
1347
1703
  if (currentColor === "none") {
1348
- colorDisplay = chalk.gray("(no background)");
1704
+ colorDisplay = chalk2.gray("(no background)");
1349
1705
  } else {
1350
1706
  const bgColorName = currentColor.replace(/^bg/, "").toLowerCase();
1351
- const bgFunc = chalk[currentColor];
1352
- colorDisplay = bgFunc ? bgFunc(` ${bgColorName} `) : chalk.white(currentColor);
1707
+ const bgFunc = chalk2[currentColor];
1708
+ colorDisplay = bgFunc ? bgFunc(` ${bgColorName} `) : chalk2.white(currentColor);
1353
1709
  }
1354
1710
  } else {
1355
- colorDisplay = chalk[currentColor] ? chalk[currentColor](currentColor) : chalk.white(currentColor);
1711
+ colorDisplay = chalk2[currentColor] ? chalk2[currentColor](currentColor) : chalk2.white(currentColor);
1356
1712
  }
1357
1713
  return /* @__PURE__ */ jsxDEV(Box, {
1358
1714
  flexDirection: "column",
@@ -1361,7 +1717,7 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
1361
1717
  bold: true,
1362
1718
  children: [
1363
1719
  "Configure Colors ",
1364
- editingBackground && chalk.yellow("[Background Mode]")
1720
+ editingBackground && chalk2.yellow("[Background Mode]")
1365
1721
  ]
1366
1722
  }, undefined, true, undefined, this),
1367
1723
  /* @__PURE__ */ jsxDEV(Text, {
@@ -1372,6 +1728,13 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
1372
1728
  ", (f) to toggle bg/fg, (b)old, (r)eset, ESC to go back"
1373
1729
  ]
1374
1730
  }, undefined, true, undefined, this),
1731
+ /* @__PURE__ */ jsxDEV(Text, {
1732
+ dimColor: true,
1733
+ children: [
1734
+ "(s)how separators: ",
1735
+ showSeparators ? chalk2.green("ON") : chalk2.gray("OFF")
1736
+ ]
1737
+ }, undefined, true, undefined, this),
1375
1738
  selectedItem && /* @__PURE__ */ jsxDEV(Box, {
1376
1739
  marginTop: 1,
1377
1740
  children: /* @__PURE__ */ jsxDEV(Text, {
@@ -1384,7 +1747,7 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
1384
1747
  colorList.length,
1385
1748
  "): ",
1386
1749
  colorDisplay,
1387
- selectedItem.bold && chalk.bold(" [BOLD]")
1750
+ selectedItem.bold && chalk2.bold(" [BOLD]")
1388
1751
  ]
1389
1752
  }, undefined, true, undefined, this)
1390
1753
  }, undefined, false, undefined, this),
@@ -1408,24 +1771,14 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
1408
1771
  /* @__PURE__ */ jsxDEV(Text, {
1409
1772
  dimColor: true,
1410
1773
  wrap: "wrap",
1411
- children: 'If colors appear incorrect in the VSCode integrated terminal, the "Terminal › Integrated: Minimum Contrast Ratio"'
1412
- }, undefined, false, undefined, this),
1413
- /* @__PURE__ */ jsxDEV(Text, {
1414
- dimColor: true,
1415
- wrap: "wrap",
1416
- children: "(`terminal.integrated.minimumContrastRatio`) setting is forcing a minimum contrast between foreground and background colors."
1417
- }, undefined, false, undefined, this),
1418
- /* @__PURE__ */ jsxDEV(Text, {
1419
- dimColor: true,
1420
- wrap: "wrap",
1421
- children: "You can adjust this setting to 1 to disable the contrast enforcement, or use a standalone terminal for accurate colors."
1774
+ children: 'If colors appear incorrect in the VSCode integrated terminal, the "Terminal › Integrated: Minimum Contrast Ratio" (`terminal.integrated.minimumContrastRatio`) setting is forcing a minimum contrast between foreground and background colors. You can adjust this setting to 1 to disable the contrast enforcement, or use a standalone terminal for accurate colors.'
1422
1775
  }, undefined, false, undefined, this)
1423
1776
  ]
1424
1777
  }, undefined, true, undefined, this)
1425
1778
  ]
1426
1779
  }, undefined, true, undefined, this);
1427
1780
  };
1428
- var FlexOptions = ({ settings, onUpdate, onBack }) => {
1781
+ var TerminalWidthOptions = ({ settings, onUpdate, onBack }) => {
1429
1782
  const [selectedOption, setSelectedOption] = useState(settings.flexMode || "full-minus-40");
1430
1783
  const [compactThreshold, setCompactThreshold] = useState(settings.compactThreshold || 60);
1431
1784
  const [editingThreshold, setEditingThreshold] = useState(false);
@@ -1455,10 +1808,10 @@ var FlexOptions = ({ settings, onUpdate, onBack }) => {
1455
1808
  setThresholdInput(String(compactThreshold));
1456
1809
  setEditingThreshold(false);
1457
1810
  setValidationError(null);
1458
- } else if (key.backspace || key.delete) {
1811
+ } else if (key.backspace) {
1459
1812
  setThresholdInput(thresholdInput.slice(0, -1));
1460
1813
  setValidationError(null);
1461
- } else if (input && /\d/.test(input)) {
1814
+ } else if (key.delete) {} else if (input && /\d/.test(input)) {
1462
1815
  const newValue = thresholdInput + input;
1463
1816
  if (newValue.length <= 2) {
1464
1817
  setThresholdInput(newValue);
@@ -1481,7 +1834,7 @@ NOTE: If /ide integration is enabled, it's not recommended to use this mode as s
1481
1834
  },
1482
1835
  {
1483
1836
  value: "full-minus-40",
1484
- label: "Full width minus 40",
1837
+ label: "Full width minus 40 (default)",
1485
1838
  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."
1486
1839
  },
1487
1840
  {
@@ -1516,11 +1869,16 @@ NOTE: If /ide integration is enabled, it's not recommended to use this mode as s
1516
1869
  children: [
1517
1870
  /* @__PURE__ */ jsxDEV(Text, {
1518
1871
  bold: true,
1519
- children: "Flex Separator Width Options"
1872
+ children: "Terminal Width Options"
1873
+ }, undefined, false, undefined, this),
1874
+ /* @__PURE__ */ jsxDEV(Text, {
1875
+ color: "white",
1876
+ children: "These settings affect where long lines are truncated, and where right-alignment occurs when using flex separators"
1520
1877
  }, undefined, false, undefined, this),
1521
1878
  /* @__PURE__ */ jsxDEV(Text, {
1522
1879
  dimColor: true,
1523
- children: "Select how flex separators calculate available width"
1880
+ wrap: "wrap",
1881
+ children: "These settings are necessary because claude code does not currently provide an available width variable for the statusline and features like IDE integration, auto-compaction notices, and rate limit messages can all cause the statusline to wrap if we do not truncate it"
1524
1882
  }, undefined, false, undefined, this),
1525
1883
  editingThreshold ? /* @__PURE__ */ jsxDEV(Box, {
1526
1884
  marginTop: 1,
@@ -1652,9 +2010,9 @@ var GlobalOptionsMenu = ({ settings, onUpdate, onBack }) => {
1652
2010
  } else if (key.escape) {
1653
2011
  setPaddingInput(settings.defaultPadding || "");
1654
2012
  setEditingPadding(false);
1655
- } else if (key.backspace || key.delete) {
2013
+ } else if (key.backspace) {
1656
2014
  setPaddingInput(paddingInput.slice(0, -1));
1657
- } else if (input) {
2015
+ } else if (key.delete) {} else if (input) {
1658
2016
  setPaddingInput(paddingInput + input);
1659
2017
  }
1660
2018
  } else if (editingSeparator) {
@@ -1668,9 +2026,9 @@ var GlobalOptionsMenu = ({ settings, onUpdate, onBack }) => {
1668
2026
  } else if (key.escape) {
1669
2027
  setSeparatorInput(settings.defaultSeparator || "");
1670
2028
  setEditingSeparator(false);
1671
- } else if (key.backspace || key.delete) {
2029
+ } else if (key.backspace) {
1672
2030
  setSeparatorInput(separatorInput.slice(0, -1));
1673
- } else if (input) {
2031
+ } else if (key.delete) {} else if (input) {
1674
2032
  setSeparatorInput(separatorInput + input);
1675
2033
  }
1676
2034
  } else {
@@ -1855,7 +2213,7 @@ var GlobalOptionsMenu = ({ settings, onUpdate, onBack }) => {
1855
2213
  }, undefined, false, undefined, this);
1856
2214
  } else {
1857
2215
  const bgColorName = bgColor.replace(/^bg/, "").toLowerCase();
1858
- const bgFunc = chalk[bgColor];
2216
+ const bgFunc = chalk2[bgColor];
1859
2217
  const display = bgFunc ? bgFunc(` ${bgColorName} `) : bgColorName;
1860
2218
  return /* @__PURE__ */ jsxDEV(Text, {
1861
2219
  children: display
@@ -1881,7 +2239,7 @@ var GlobalOptionsMenu = ({ settings, onUpdate, onBack }) => {
1881
2239
  children: "(none)"
1882
2240
  }, undefined, false, undefined, this);
1883
2241
  } else {
1884
- const fgFunc = chalk[fgColor];
2242
+ const fgFunc = chalk2[fgColor];
1885
2243
  const display = fgFunc ? fgFunc(fgColor) : fgColor;
1886
2244
  return /* @__PURE__ */ jsxDEV(Text, {
1887
2245
  children: display
@@ -1936,17 +2294,7 @@ var GlobalOptionsMenu = ({ settings, onUpdate, onBack }) => {
1936
2294
  /* @__PURE__ */ jsxDEV(Text, {
1937
2295
  dimColor: true,
1938
2296
  wrap: "wrap",
1939
- children: 'If colors appear incorrect in the VSCode integrated terminal, the "Terminal › Integrated: Minimum Contrast Ratio"'
1940
- }, undefined, false, undefined, this),
1941
- /* @__PURE__ */ jsxDEV(Text, {
1942
- dimColor: true,
1943
- wrap: "wrap",
1944
- children: "(`terminal.integrated.minimumContrastRatio`) setting is forcing a minimum contrast between foreground and background colors."
1945
- }, undefined, false, undefined, this),
1946
- /* @__PURE__ */ jsxDEV(Text, {
1947
- dimColor: true,
1948
- wrap: "wrap",
1949
- children: "You can adjust this setting to 1 to disable the contrast enforcement, or use a standalone terminal for accurate colors."
2297
+ children: 'If colors appear incorrect in the VSCode integrated terminal, the "Terminal › Integrated: Minimum Contrast Ratio" (`terminal.integrated.minimumContrastRatio`) setting is forcing a minimum contrast between foreground and background colors. You can adjust this setting to 1 to disable the contrast enforcement, or use a standalone terminal for accurate colors.'
1950
2298
  }, undefined, false, undefined, this)
1951
2299
  ]
1952
2300
  }, undefined, true, undefined, this)
@@ -2051,8 +2399,8 @@ Continue?`;
2051
2399
  case "colors":
2052
2400
  setScreen("colors");
2053
2401
  break;
2054
- case "flex":
2055
- setScreen("flex");
2402
+ case "terminalWidth":
2403
+ setScreen("terminalWidth");
2056
2404
  break;
2057
2405
  case "globalOptions":
2058
2406
  setScreen("globalOptions");
@@ -2116,7 +2464,7 @@ Continue?`;
2116
2464
  const menuMap = {
2117
2465
  lines: 0,
2118
2466
  colors: 1,
2119
- flex: 2,
2467
+ terminalWidth: 2,
2120
2468
  globalOptions: 3,
2121
2469
  install: 4
2122
2470
  };
@@ -2173,7 +2521,7 @@ Continue?`;
2173
2521
  setScreen("main");
2174
2522
  }
2175
2523
  }, undefined, false, undefined, this),
2176
- screen === "flex" && /* @__PURE__ */ jsxDEV(FlexOptions, {
2524
+ screen === "terminalWidth" && /* @__PURE__ */ jsxDEV(TerminalWidthOptions, {
2177
2525
  settings,
2178
2526
  onUpdate: (updatedSettings) => {
2179
2527
  setSettings(updatedSettings);
@@ -2212,29 +2560,6 @@ function runTUI() {
2212
2560
  }
2213
2561
 
2214
2562
  // src/ccstatusline.ts
2215
- import * as fs4 from "fs";
2216
- import { promisify as promisify3 } from "util";
2217
- var readFile6 = fs4.promises?.readFile || promisify3(fs4.readFile);
2218
- chalk2.level = 3;
2219
- function applyColors2(text, foregroundColor, backgroundColor, bold) {
2220
- let result = text;
2221
- if (foregroundColor && foregroundColor !== "dim") {
2222
- const fgFunc = chalk2[foregroundColor];
2223
- if (fgFunc) {
2224
- result = fgFunc(result);
2225
- }
2226
- }
2227
- if (backgroundColor && backgroundColor !== "none") {
2228
- const bgFunc = chalk2[backgroundColor];
2229
- if (bgFunc) {
2230
- result = bgFunc(result);
2231
- }
2232
- }
2233
- if (bold) {
2234
- result = chalk2.bold(result);
2235
- }
2236
- return result;
2237
- }
2238
2563
  async function readStdin() {
2239
2564
  if (process.stdin.isTTY) {
2240
2565
  return null;
@@ -2257,548 +2582,28 @@ async function readStdin() {
2257
2582
  return null;
2258
2583
  }
2259
2584
  }
2260
- function getItemDefaultColor(type) {
2261
- switch (type) {
2262
- case "model":
2263
- return "cyan";
2264
- case "git-branch":
2265
- return "magenta";
2266
- case "git-changes":
2267
- return "yellow";
2268
- case "session-clock":
2269
- return "yellow";
2270
- case "version":
2271
- return "green";
2272
- case "tokens-input":
2273
- return "blue";
2274
- case "tokens-output":
2275
- return "white";
2276
- case "tokens-cached":
2277
- return "cyan";
2278
- case "tokens-total":
2279
- return "cyan";
2280
- case "context-length":
2281
- return "gray";
2282
- case "context-percentage":
2283
- return "blue";
2284
- case "context-percentage-usable":
2285
- return "green";
2286
- case "terminal-width":
2287
- return "gray";
2288
- case "custom-text":
2289
- return "white";
2290
- case "custom-command":
2291
- return "white";
2292
- case "separator":
2293
- return "gray";
2294
- default:
2295
- return "white";
2296
- }
2297
- }
2298
- function getTerminalWidth() {
2299
- try {
2300
- const tty = execSync2("ps -o tty= -p $(ps -o ppid= -p $$)", {
2301
- encoding: "utf8",
2302
- stdio: ["pipe", "pipe", "ignore"],
2303
- shell: "/bin/sh"
2304
- }).trim();
2305
- if (tty && tty !== "??" && tty !== "?") {
2306
- const width = execSync2(`stty size < /dev/${tty} | awk '{print $2}'`, {
2307
- encoding: "utf8",
2308
- stdio: ["pipe", "pipe", "ignore"],
2309
- shell: "/bin/sh"
2310
- }).trim();
2311
- const parsed = parseInt(width, 10);
2312
- if (!isNaN(parsed) && parsed > 0) {
2313
- return parsed;
2314
- }
2315
- }
2316
- } catch {}
2317
- try {
2318
- const width = execSync2("tput cols 2>/dev/null", {
2319
- encoding: "utf8",
2320
- stdio: ["pipe", "pipe", "ignore"]
2321
- }).trim();
2322
- const parsed = parseInt(width, 10);
2323
- if (!isNaN(parsed) && parsed > 0) {
2324
- return parsed;
2325
- }
2326
- } catch {}
2327
- return null;
2328
- }
2329
- function getGitBranch() {
2330
- try {
2331
- const branch = execSync2("git branch --show-current 2>/dev/null", {
2332
- encoding: "utf8",
2333
- stdio: ["pipe", "pipe", "ignore"]
2334
- }).trim();
2335
- return branch || null;
2336
- } catch {
2337
- return null;
2338
- }
2339
- }
2340
- function getGitChanges() {
2341
- try {
2342
- let totalInsertions = 0;
2343
- let totalDeletions = 0;
2344
- const unstagedStat = execSync2("git diff --shortstat 2>/dev/null", {
2345
- encoding: "utf8",
2346
- stdio: ["pipe", "pipe", "ignore"]
2347
- }).trim();
2348
- const stagedStat = execSync2("git diff --cached --shortstat 2>/dev/null", {
2349
- encoding: "utf8",
2350
- stdio: ["pipe", "pipe", "ignore"]
2351
- }).trim();
2352
- if (unstagedStat) {
2353
- const insertMatch = unstagedStat.match(/(\d+) insertion/);
2354
- const deleteMatch = unstagedStat.match(/(\d+) deletion/);
2355
- totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
2356
- totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
2357
- }
2358
- if (stagedStat) {
2359
- const insertMatch = stagedStat.match(/(\d+) insertion/);
2360
- const deleteMatch = stagedStat.match(/(\d+) deletion/);
2361
- totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
2362
- totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
2363
- }
2364
- return { insertions: totalInsertions, deletions: totalDeletions };
2365
- } catch {
2366
- return null;
2367
- }
2368
- }
2369
- async function getSessionDuration(transcriptPath) {
2370
- try {
2371
- if (!fs4.existsSync(transcriptPath)) {
2372
- return null;
2373
- }
2374
- const content = await readFile6(transcriptPath, "utf-8");
2375
- const lines = content.trim().split(`
2376
- `).filter((line) => line.trim());
2377
- if (lines.length === 0) {
2378
- return null;
2379
- }
2380
- let firstTimestamp = null;
2381
- let lastTimestamp = null;
2382
- for (const line of lines) {
2383
- try {
2384
- const data = JSON.parse(line);
2385
- if (data.timestamp) {
2386
- firstTimestamp = new Date(data.timestamp);
2387
- break;
2388
- }
2389
- } catch {}
2390
- }
2391
- for (let i = lines.length - 1;i >= 0; i--) {
2392
- try {
2393
- const data = JSON.parse(lines[i]);
2394
- if (data.timestamp) {
2395
- lastTimestamp = new Date(data.timestamp);
2396
- break;
2397
- }
2398
- } catch {}
2399
- }
2400
- if (!firstTimestamp || !lastTimestamp) {
2401
- return null;
2402
- }
2403
- const durationMs = lastTimestamp.getTime() - firstTimestamp.getTime();
2404
- const totalMinutes = Math.floor(durationMs / (1000 * 60));
2405
- if (totalMinutes < 1) {
2406
- return "<1m";
2407
- }
2408
- const hours = Math.floor(totalMinutes / 60);
2409
- const minutes = totalMinutes % 60;
2410
- if (hours === 0) {
2411
- return `${minutes}m`;
2412
- } else if (minutes === 0) {
2413
- return `${hours}hr`;
2414
- } else {
2415
- return `${hours}hr ${minutes}m`;
2416
- }
2417
- } catch {
2418
- return null;
2419
- }
2420
- }
2421
- async function getTokenMetrics(transcriptPath) {
2422
- try {
2423
- if (!fs4.existsSync(transcriptPath)) {
2424
- return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
2425
- }
2426
- const content = await readFile6(transcriptPath, "utf-8");
2427
- const lines = content.trim().split(`
2428
- `);
2429
- let inputTokens = 0;
2430
- let outputTokens = 0;
2431
- let cachedTokens = 0;
2432
- let contextLength = 0;
2433
- let mostRecentMainChainEntry = null;
2434
- let mostRecentTimestamp = null;
2435
- for (const line of lines) {
2436
- try {
2437
- const data = JSON.parse(line);
2438
- if (data.message?.usage) {
2439
- inputTokens += data.message.usage.input_tokens || 0;
2440
- outputTokens += data.message.usage.output_tokens || 0;
2441
- cachedTokens += data.message.usage.cache_read_input_tokens || 0;
2442
- cachedTokens += data.message.usage.cache_creation_input_tokens || 0;
2443
- if (data.isSidechain !== true && data.timestamp) {
2444
- const entryTime = new Date(data.timestamp);
2445
- if (!mostRecentTimestamp || entryTime > mostRecentTimestamp) {
2446
- mostRecentTimestamp = entryTime;
2447
- mostRecentMainChainEntry = data;
2448
- }
2449
- }
2450
- }
2451
- } catch {}
2452
- }
2453
- if (mostRecentMainChainEntry?.message?.usage) {
2454
- const usage = mostRecentMainChainEntry.message.usage;
2455
- contextLength = (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0) + (usage.cache_creation_input_tokens || 0);
2456
- }
2457
- const totalTokens = inputTokens + outputTokens + cachedTokens;
2458
- return { inputTokens, outputTokens, cachedTokens, totalTokens, contextLength };
2459
- } catch {
2460
- return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
2461
- }
2462
- }
2463
- function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration) {
2464
- const applyColorsWithOverride = (text, foregroundColor, backgroundColor, bold) => {
2465
- let fgColor = foregroundColor;
2466
- if (settings.overrideForegroundColor && settings.overrideForegroundColor !== "none") {
2467
- fgColor = settings.overrideForegroundColor;
2468
- }
2469
- let bgColor = backgroundColor;
2470
- if (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== "none") {
2471
- bgColor = settings.overrideBackgroundColor;
2472
- }
2473
- const shouldBold = settings.globalBold || bold;
2474
- return applyColors2(text, fgColor, bgColor, shouldBold);
2475
- };
2476
- const detectedWidth = getTerminalWidth();
2477
- let terminalWidth = null;
2478
- if (detectedWidth) {
2479
- const flexMode = settings.flexMode || "full-minus-40";
2480
- if (flexMode === "full") {
2481
- terminalWidth = detectedWidth - 4;
2482
- } else if (flexMode === "full-minus-40") {
2483
- terminalWidth = detectedWidth - 40;
2484
- } else if (flexMode === "full-until-compact") {
2485
- const threshold = settings.compactThreshold || 60;
2486
- const contextPercentage = tokenMetrics ? Math.min(100, tokenMetrics.contextLength / 200000 * 100) : 0;
2487
- if (contextPercentage >= threshold) {
2488
- terminalWidth = detectedWidth - 40;
2489
- } else {
2490
- terminalWidth = detectedWidth - 4;
2491
- }
2492
- }
2493
- }
2494
- const elements = [];
2495
- let hasFlexSeparator = false;
2496
- const formatTokens = (count) => {
2497
- if (count >= 1e6)
2498
- return `${(count / 1e6).toFixed(1)}M`;
2499
- if (count >= 1000)
2500
- return `${(count / 1000).toFixed(1)}k`;
2501
- return count.toString();
2502
- };
2503
- for (const item of items) {
2504
- switch (item.type) {
2505
- case "model":
2506
- if (data.model) {
2507
- const text = item.rawValue ? data.model.display_name : `Model: ${data.model.display_name}`;
2508
- elements.push({ content: applyColorsWithOverride(text, item.color || "cyan", item.backgroundColor, item.bold), type: "model", item });
2509
- }
2510
- break;
2511
- case "git-branch":
2512
- const branch = getGitBranch();
2513
- if (branch) {
2514
- const text = item.rawValue ? branch : `⎇ ${branch}`;
2515
- elements.push({ content: applyColorsWithOverride(text, item.color || "magenta", item.backgroundColor, item.bold), type: "git-branch", item });
2516
- }
2517
- break;
2518
- case "git-changes":
2519
- const changes = getGitChanges();
2520
- if (changes !== null) {
2521
- const changeStr = `(+${changes.insertions},-${changes.deletions})`;
2522
- elements.push({ content: applyColorsWithOverride(changeStr, item.color || "yellow", item.backgroundColor, item.bold), type: "git-changes", item });
2523
- }
2524
- break;
2525
- case "tokens-input":
2526
- if (tokenMetrics) {
2527
- const text = item.rawValue ? formatTokens(tokenMetrics.inputTokens) : `In: ${formatTokens(tokenMetrics.inputTokens)}`;
2528
- elements.push({ content: applyColorsWithOverride(text, item.color || "blue", item.backgroundColor, item.bold), type: "tokens-input", item });
2529
- }
2530
- break;
2531
- case "tokens-output":
2532
- if (tokenMetrics) {
2533
- const text = item.rawValue ? formatTokens(tokenMetrics.outputTokens) : `Out: ${formatTokens(tokenMetrics.outputTokens)}`;
2534
- elements.push({ content: applyColorsWithOverride(text, item.color || "white", item.backgroundColor, item.bold), type: "tokens-output", item });
2535
- }
2536
- break;
2537
- case "tokens-cached":
2538
- if (tokenMetrics) {
2539
- const text = item.rawValue ? formatTokens(tokenMetrics.cachedTokens) : `Cached: ${formatTokens(tokenMetrics.cachedTokens)}`;
2540
- elements.push({ content: applyColorsWithOverride(text, item.color || "cyan", item.backgroundColor, item.bold), type: "tokens-cached", item });
2541
- }
2542
- break;
2543
- case "tokens-total":
2544
- if (tokenMetrics) {
2545
- const text = item.rawValue ? formatTokens(tokenMetrics.totalTokens) : `Total: ${formatTokens(tokenMetrics.totalTokens)}`;
2546
- elements.push({ content: applyColorsWithOverride(text, item.color || "cyan", item.backgroundColor, item.bold), type: "tokens-total", item });
2547
- }
2548
- break;
2549
- case "context-length":
2550
- if (tokenMetrics) {
2551
- const text = item.rawValue ? formatTokens(tokenMetrics.contextLength) : `Ctx: ${formatTokens(tokenMetrics.contextLength)}`;
2552
- elements.push({ content: applyColorsWithOverride(text, item.color || "gray", item.backgroundColor, item.bold), type: "context-length", item });
2553
- }
2554
- break;
2555
- case "context-percentage":
2556
- if (tokenMetrics) {
2557
- const percentage = Math.min(100, tokenMetrics.contextLength / 200000 * 100);
2558
- const text = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx: ${percentage.toFixed(1)}%`;
2559
- elements.push({ content: applyColorsWithOverride(text, item.color || "blue", item.backgroundColor, item.bold), type: "context-percentage", item });
2560
- }
2561
- break;
2562
- case "context-percentage-usable":
2563
- if (tokenMetrics) {
2564
- const percentage = Math.min(100, tokenMetrics.contextLength / 160000 * 100);
2565
- const text = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx(u): ${percentage.toFixed(1)}%`;
2566
- elements.push({ content: applyColorsWithOverride(text, item.color || "green", item.backgroundColor, item.bold), type: "context-percentage-usable", item });
2567
- }
2568
- break;
2569
- case "terminal-width":
2570
- const width = terminalWidth || getTerminalWidth();
2571
- if (width) {
2572
- const text = item.rawValue ? `${width}` : `Term: ${width}`;
2573
- elements.push({ content: applyColorsWithOverride(text, item.color || "gray", item.backgroundColor, item.bold), type: "terminal-width", item });
2574
- }
2575
- break;
2576
- case "session-clock":
2577
- if (sessionDuration) {
2578
- const text = item.rawValue ? sessionDuration : `Session: ${sessionDuration}`;
2579
- elements.push({ content: applyColorsWithOverride(text, item.color || "yellow", item.backgroundColor, item.bold), type: "session-clock", item });
2580
- }
2581
- break;
2582
- case "version":
2583
- const versionString = data.version || "Unknown";
2584
- const versionText = item.rawValue ? versionString : `Version: ${versionString}`;
2585
- elements.push({ content: applyColorsWithOverride(versionText, item.color || "green", item.backgroundColor, item.bold), type: "version", item });
2586
- break;
2587
- case "separator":
2588
- const lastElement = elements[elements.length - 1];
2589
- if (elements.length > 0 && lastElement && lastElement.type !== "separator") {
2590
- const sepChar = item.character || "|";
2591
- let sepText;
2592
- if (sepChar === ",") {
2593
- sepText = `${sepChar} `;
2594
- } else if (sepChar === " ") {
2595
- sepText = " ";
2596
- } else {
2597
- sepText = ` ${sepChar} `;
2598
- }
2599
- const sepContent = applyColorsWithOverride(sepText, item.color || "gray", item.backgroundColor, item.bold);
2600
- elements.push({ content: sepContent, type: "separator", item });
2601
- }
2602
- break;
2603
- case "flex-separator":
2604
- elements.push({ content: "FLEX", type: "flex-separator", item });
2605
- hasFlexSeparator = true;
2606
- break;
2607
- case "custom-text":
2608
- const customText = item.customText || "";
2609
- elements.push({ content: applyColorsWithOverride(customText, item.color || "white", item.backgroundColor, item.bold), type: "custom-text", item });
2610
- break;
2611
- case "custom-command":
2612
- if (item.commandPath) {
2613
- try {
2614
- const timeout = item.timeout || 1000;
2615
- const output = execSync2(item.commandPath, {
2616
- encoding: "utf8",
2617
- input: JSON.stringify(data),
2618
- stdio: ["pipe", "pipe", "ignore"],
2619
- timeout
2620
- }).trim();
2621
- if (output) {
2622
- let finalOutput = output;
2623
- if (item.maxWidth && item.maxWidth > 0) {
2624
- const plainLength = output.replace(/\x1b\[[0-9;]*m/g, "").length;
2625
- if (plainLength > item.maxWidth) {
2626
- let truncated = "";
2627
- let currentLength = 0;
2628
- let inAnsiCode = false;
2629
- let ansiBuffer = "";
2630
- for (let i = 0;i < output.length; i++) {
2631
- const char = output[i];
2632
- if (char === "\x1B") {
2633
- inAnsiCode = true;
2634
- ansiBuffer = char;
2635
- } else if (inAnsiCode) {
2636
- ansiBuffer += char;
2637
- if (char === "m") {
2638
- truncated += ansiBuffer;
2639
- inAnsiCode = false;
2640
- ansiBuffer = "";
2641
- }
2642
- } else {
2643
- if (currentLength < item.maxWidth) {
2644
- truncated += char;
2645
- currentLength++;
2646
- } else {
2647
- break;
2648
- }
2649
- }
2650
- }
2651
- finalOutput = truncated;
2652
- }
2653
- }
2654
- if (!item.preserveColors) {
2655
- const stripped = finalOutput.replace(/\x1b\[[0-9;]*m/g, "");
2656
- elements.push({ content: applyColorsWithOverride(stripped, item.color || "white", item.backgroundColor, item.bold), type: "custom-command", item });
2657
- } else {
2658
- elements.push({ content: finalOutput, type: "custom-command", item });
2659
- }
2660
- }
2661
- } catch {}
2662
- }
2663
- break;
2664
- }
2665
- }
2666
- if (elements.length === 0)
2667
- return "";
2668
- while (elements.length > 0 && elements[elements.length - 1]?.type === "separator") {
2669
- elements.pop();
2670
- }
2671
- const finalElements = [];
2672
- const padding = settings.defaultPadding || "";
2673
- const defaultSep = settings.defaultSeparator || "";
2674
- elements.forEach((elem, index) => {
2675
- if (defaultSep && index > 0) {
2676
- if (settings.inheritSeparatorColors && index > 0) {
2677
- const prevElem = elements[index - 1];
2678
- if (prevElem && prevElem.item) {
2679
- const itemColor = prevElem.item.color || getItemDefaultColor(prevElem.item.type);
2680
- const coloredSep = applyColorsWithOverride(defaultSep, itemColor, prevElem.item.backgroundColor, prevElem.item.bold);
2681
- finalElements.push(coloredSep);
2682
- } else {
2683
- finalElements.push(defaultSep);
2684
- }
2685
- } else if (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== "none" || settings.overrideForegroundColor && settings.overrideForegroundColor !== "none") {
2686
- const coloredSep = applyColorsWithOverride(defaultSep, undefined, undefined);
2687
- finalElements.push(coloredSep);
2688
- } else {
2689
- finalElements.push(defaultSep);
2690
- }
2691
- }
2692
- if (elem.type === "separator" || elem.type === "flex-separator") {
2693
- finalElements.push(elem.content);
2694
- } else {
2695
- const hasColorOverride = settings.overrideBackgroundColor && settings.overrideBackgroundColor !== "none" || settings.overrideForegroundColor && settings.overrideForegroundColor !== "none";
2696
- if (padding && (elem.item?.backgroundColor || hasColorOverride)) {
2697
- const paddedContent = applyColorsWithOverride(padding, undefined, elem.item?.backgroundColor) + elem.content + applyColorsWithOverride(padding, undefined, elem.item?.backgroundColor);
2698
- finalElements.push(paddedContent);
2699
- } else {
2700
- finalElements.push(padding + elem.content + padding);
2701
- }
2702
- }
2703
- });
2704
- let statusLine = "";
2705
- if (hasFlexSeparator && terminalWidth) {
2706
- const parts = [[]];
2707
- let currentPart = 0;
2708
- for (let i = 0;i < finalElements.length; i++) {
2709
- const elem = finalElements[i];
2710
- if (elem === "FLEX" || elements[i] && elements[i]?.type === "flex-separator") {
2711
- currentPart++;
2712
- parts[currentPart] = [];
2713
- } else {
2714
- parts[currentPart]?.push(elem);
2715
- }
2716
- }
2717
- const partLengths = parts.map((part) => {
2718
- const joined = part.join("");
2719
- return joined.replace(/\x1b\[[0-9;]*m/g, "").length;
2720
- });
2721
- const totalContentLength = partLengths.reduce((sum, len) => sum + len, 0);
2722
- const flexCount = parts.length - 1;
2723
- const totalSpace = Math.max(0, terminalWidth - totalContentLength);
2724
- const spacePerFlex = flexCount > 0 ? Math.floor(totalSpace / flexCount) : 0;
2725
- const extraSpace = flexCount > 0 ? totalSpace % flexCount : 0;
2726
- statusLine = "";
2727
- for (let i = 0;i < parts.length; i++) {
2728
- const part = parts[i];
2729
- if (part) {
2730
- statusLine += part.join("");
2731
- }
2732
- if (i < parts.length - 1) {
2733
- const spaces = spacePerFlex + (i < extraSpace ? 1 : 0);
2734
- statusLine += " ".repeat(spaces);
2735
- }
2736
- }
2737
- } else {
2738
- if (hasFlexSeparator && !terminalWidth) {
2739
- statusLine = finalElements.map((e) => e === "FLEX" ? chalk2.gray(" | ") : e).join("");
2740
- } else {
2741
- statusLine = finalElements.join("");
2742
- }
2743
- }
2744
- if (detectedWidth && detectedWidth > 0) {
2745
- const plainLength = statusLine.replace(/\x1b\[[0-9;]*m/g, "").length;
2746
- if (plainLength > detectedWidth) {
2747
- let truncated = "";
2748
- let currentLength = 0;
2749
- let inAnsiCode = false;
2750
- let ansiBuffer = "";
2751
- const targetLength = detectedWidth - 7;
2752
- for (let i = 0;i < statusLine.length; i++) {
2753
- const char = statusLine[i];
2754
- if (char === "\x1B") {
2755
- inAnsiCode = true;
2756
- ansiBuffer = char;
2757
- } else if (inAnsiCode) {
2758
- ansiBuffer += char;
2759
- if (char === "m") {
2760
- truncated += ansiBuffer;
2761
- inAnsiCode = false;
2762
- ansiBuffer = "";
2763
- }
2764
- } else {
2765
- if (currentLength < targetLength) {
2766
- truncated += char;
2767
- currentLength++;
2768
- } else {
2769
- break;
2770
- }
2771
- }
2772
- }
2773
- statusLine = truncated + "...";
2774
- }
2775
- }
2776
- return statusLine;
2777
- }
2778
- async function renderStatusLine(data) {
2779
- const settings = await loadSettings();
2780
- let lines = [];
2781
- if (settings.lines) {
2782
- lines = settings.lines;
2783
- } else if (settings.items) {
2784
- lines = [settings.items];
2785
- } else {
2786
- lines = [[]];
2787
- }
2788
- const hasTokenItems = lines.some((line) => line.some((item) => ["tokens-input", "tokens-output", "tokens-cached", "tokens-total", "context-length", "context-percentage", "context-percentage-usable"].includes(item.type)));
2789
- const hasSessionClock = lines.some((line) => line.some((item) => item.type === "session-clock"));
2790
- let tokenMetrics = null;
2791
- if (hasTokenItems && data.transcript_path) {
2792
- tokenMetrics = await getTokenMetrics(data.transcript_path);
2585
+ async function renderMultipleLines(data) {
2586
+ const settings = await loadSettings();
2587
+ let lines = settings.lines || (settings.items ? [settings.items] : [[]]);
2588
+ const hasTokenItems = lines.some((line) => line.some((item) => ["tokens-input", "tokens-output", "tokens-cached", "tokens-total", "context-length", "context-percentage", "context-percentage-usable"].includes(item.type)));
2589
+ const hasSessionClock = lines.some((line) => line.some((item) => item.type === "session-clock"));
2590
+ let tokenMetrics = null;
2591
+ if (hasTokenItems && data.transcript_path) {
2592
+ tokenMetrics = await getTokenMetrics(data.transcript_path);
2793
2593
  }
2794
2594
  let sessionDuration = null;
2795
2595
  if (hasSessionClock && data.transcript_path) {
2796
2596
  sessionDuration = await getSessionDuration(data.transcript_path);
2797
2597
  }
2798
- for (let i = 0;i < lines.length; i++) {
2799
- const lineItems = lines[i];
2598
+ const context = {
2599
+ data,
2600
+ tokenMetrics,
2601
+ sessionDuration,
2602
+ isPreview: false
2603
+ };
2604
+ for (const lineItems of lines) {
2800
2605
  if (lineItems && lineItems.length > 0) {
2801
- const line = renderSingleLine2(lineItems, settings, data, tokenMetrics, sessionDuration);
2606
+ const line = renderStatusLine(lineItems, settings, context);
2802
2607
  const outputLine = line.replace(/ /g, " ");
2803
2608
  console.log(outputLine);
2804
2609
  }
@@ -2810,7 +2615,7 @@ async function main() {
2810
2615
  if (input && input.trim() !== "") {
2811
2616
  try {
2812
2617
  const data = JSON.parse(input);
2813
- await renderStatusLine(data);
2618
+ await renderMultipleLines(data);
2814
2619
  } catch (error) {
2815
2620
  console.error("Error parsing JSON:", error);
2816
2621
  process.exit(1);