ccstatusline 2.2.10 โ†’ 2.2.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 CHANGED
@@ -47,19 +47,24 @@
47
47
 
48
48
  ## ๐Ÿ†• Recent Updates
49
49
 
50
- ### v2.2.9 - v2.2.10 - GitLab support, reset timers, context, and git widgets
50
+ ### v2.2.9 - v2.2.12 - GitLab support, reset timers, context, compaction, and git widgets
51
51
 
52
52
  - **๐ŸฆŠ GitLab PR/MR support** - `Git Branch` and `Git PR/MR` now support GitHub, GitLab, and compatible self-hosted remotes, using `gh` or `glab` as appropriate.
53
53
  - **๐Ÿ”„ Status line refresh interval** - Installed configs can set Claude Code's `statusLine.refreshInterval` from the TUI when Claude Code >=2.1.97 supports it.
54
54
  - **๐Ÿงญ Wrap-around TUI navigation** - Menu/list navigation and move/reorder modes now wrap at the first and last items.
55
55
  - **๐Ÿ“‹ Clone widget shortcut** - Press `k` in the item editor to duplicate the selected widget, with fresh Powerline background color for cloned Powerline items.
56
56
  - **๐Ÿ“Š Short bar display modes** - Context percentage, Context Bar, Session Usage, Weekly Usage, Block Timer, and reset timer widgets can use compact bar variants.
57
+ - **โฑ๏ธ Usage time cursor** - Session Usage and Weekly Usage progress bars can show the elapsed time position within the current usage window.
57
58
  - **๐Ÿ•’ Reset timer timestamps** - Block and Weekly Reset Timer widgets can show exact reset timestamps with compact formatting, 12/24-hour display, IANA time zones, and locale selection.
58
59
  - **๐ŸชŸ Context Window widget** - Added a `Context Window` widget for total model window size, keeping `Context Length` focused on current context usage.
60
+ - **๐Ÿ” Compaction Counter widget** - Added a `Compaction Counter` widget that tracks session context compactions, with icon/text/number formats, optional Nerd Font icon, and hide-when-zero behavior.
59
61
  - **๐Ÿงฎ Git file status widgets** - Added `Git Staged Files`, `Git Unstaged Files`, `Git Untracked Files`, and `Git Clean Status` for file counts and clean/dirty state.
60
- - **๐Ÿง  Thinking Effort updates** - Added `xhigh`, show `default` when no effort is set, and mark unknown future effort levels with `?`.
62
+ - **๐Ÿท๏ธ Clear context percentage labels** - `Context %` and `Context % (usable)` now label rendered values as used or left when toggling used/remaining mode.
63
+ - **โšก More Powerline caps** - The Powerline separator editor now supports more than three start/end caps.
64
+ - **๐Ÿง  Thinking Effort updates** - Added `xhigh`, show `default` when no effort is set, mark unknown future effort levels with `?`, and track live status JSON plus `/effort` command changes.
61
65
  - **๐Ÿงฎ More accurate token counts** - Streaming duplicate JSONL entries are deduped so token widgets do not overcount live Claude Code output.
62
66
  - **๐Ÿท๏ธ Cleaner model display** - The Model widget strips trailing context suffixes like `(1M context)`; use `Context Window` when you want the total window size shown.
67
+ - **๐Ÿงน Cleaner empty-widget separators** - Manual separators now collapse around widgets that render empty, avoiding dangling separators when hide-when-empty widgets disappear.
63
68
  - **๐Ÿงฑ More resilient Git helpers** - Git widgets handle missing or unusual git command output more defensively.
64
69
 
65
70
  ### v2.2.8 - Git widgets, smarter picker search, and minimalist mode
@@ -192,7 +197,7 @@
192
197
 
193
198
  ## โœจ Features
194
199
 
195
- - **๐Ÿ“Š Real-time Metrics** - Display model name, git branch, token usage, session duration, block timer, and more
200
+ - **๐Ÿ“Š Real-time Metrics** - Display model name, git branch, token usage, session duration, compaction count, block timer, and more
196
201
  - **๐ŸŽจ Fully Customizable** - Choose what to display and customize colors for each element
197
202
  - **โšก Powerline Support** - Beautiful Powerline-style rendering with arrow separators, caps, and custom fonts
198
203
  - **๐Ÿ“ Multi-line Support** - Configure multiple independent status lines
@@ -56010,17 +56010,26 @@ function getContextConfig(modelIdentifier, contextWindowSize) {
56010
56010
  var DEFAULT_CONTEXT_WINDOW_SIZE = 200000, USABLE_CONTEXT_RATIO = 0.8;
56011
56011
 
56012
56012
  // src/utils/context-percentage.ts
56013
- function calculateContextPercentage(context) {
56013
+ function calculateContextPercentageMetrics(context) {
56014
56014
  const contextWindowMetrics = getContextWindowMetrics(context.data);
56015
+ const modelIdentifier = getModelContextIdentifier(context.data?.model);
56016
+ const contextConfig = getContextConfig(modelIdentifier, contextWindowMetrics.windowSize);
56015
56017
  if (contextWindowMetrics.usedPercentage !== null) {
56016
- return contextWindowMetrics.usedPercentage;
56018
+ return {
56019
+ usedPercentage: contextWindowMetrics.usedPercentage,
56020
+ windowSize: contextConfig.maxTokens
56021
+ };
56017
56022
  }
56018
56023
  if (!context.tokenMetrics) {
56019
- return 0;
56024
+ return null;
56020
56025
  }
56021
- const modelIdentifier = getModelContextIdentifier(context.data?.model);
56022
- const contextConfig = getContextConfig(modelIdentifier, contextWindowMetrics.windowSize);
56023
- return Math.min(100, context.tokenMetrics.contextLength / contextConfig.maxTokens * 100);
56026
+ return {
56027
+ usedPercentage: Math.min(100, context.tokenMetrics.contextLength / contextConfig.maxTokens * 100),
56028
+ windowSize: contextConfig.maxTokens
56029
+ };
56030
+ }
56031
+ function calculateContextPercentage(context) {
56032
+ return calculateContextPercentageMetrics(context)?.usedPercentage ?? 0;
56024
56033
  }
56025
56034
  var init_context_percentage = () => {};
56026
56035
 
@@ -56127,7 +56136,7 @@ function getTerminalWidth() {
56127
56136
  function canDetectTerminalWidth() {
56128
56137
  return probeTerminalWidth() !== null;
56129
56138
  }
56130
- var __dirname = "/Users/sirmalloc/Projects/Personal/ccstatusline/src/utils", PACKAGE_VERSION = "2.2.10";
56139
+ var __dirname = "/Users/sirmalloc/Projects/Personal/ccstatusline/src/utils", PACKAGE_VERSION = "2.2.12";
56131
56140
  var init_terminal = () => {};
56132
56141
 
56133
56142
  // src/utils/renderer.ts
@@ -56532,12 +56541,12 @@ function renderStatusLine(widgets, settings, context, preRenderedWidgets, preCal
56532
56541
  let hasContentBefore = false;
56533
56542
  for (let j = i - 1;j >= 0; j--) {
56534
56543
  const prevWidget = widgets[j];
56535
- if (prevWidget && prevWidget.type !== "separator" && prevWidget.type !== "flex-separator") {
56536
- if (preRenderedWidgets[j]?.content) {
56537
- hasContentBefore = true;
56538
- break;
56539
- }
56540
- }
56544
+ if (!prevWidget)
56545
+ continue;
56546
+ if (prevWidget.type === "separator" || prevWidget.type === "flex-separator")
56547
+ continue;
56548
+ hasContentBefore = Boolean(preRenderedWidgets[j]?.content);
56549
+ break;
56541
56550
  }
56542
56551
  if (!hasContentBefore)
56543
56552
  continue;
@@ -57118,11 +57127,21 @@ function isUsageProgressMode(mode) {
57118
57127
  function isUsageSliderMode(mode) {
57119
57128
  return mode === "slider" || mode === "slider-only";
57120
57129
  }
57121
- function makeSliderBar(percent, width = SLIDER_WIDTH) {
57130
+ function makeSliderBar(percent, width = SLIDER_WIDTH, options) {
57122
57131
  const clamped = Math.max(0, Math.min(100, percent));
57123
57132
  const filled = Math.round(clamped / 100 * width);
57124
- const empty = width - filled;
57125
- return "โ–“".repeat(filled) + "โ–‘".repeat(empty);
57133
+ const cursorPos = options?.cursorPercent !== undefined ? Math.min(Math.floor(Math.max(0, Math.min(100, options.cursorPercent)) / 100 * width), width - 1) : -1;
57134
+ let bar = "";
57135
+ for (let i = 0;i < width; i++) {
57136
+ if (i === cursorPos) {
57137
+ bar += "โ”‚";
57138
+ } else if (i < filled) {
57139
+ bar += "โ–“";
57140
+ } else {
57141
+ bar += "โ–‘";
57142
+ }
57143
+ }
57144
+ return bar;
57126
57145
  }
57127
57146
  function getUsageProgressBarWidth(mode) {
57128
57147
  return mode === "progress" ? 32 : 16;
@@ -57133,6 +57152,12 @@ function isUsageInverted(item) {
57133
57152
  function isUsageCompact(item) {
57134
57153
  return isMetadataFlagEnabled(item, "compact");
57135
57154
  }
57155
+ function isUsageCursorEnabled(item) {
57156
+ return isMetadataFlagEnabled(item, "cursor");
57157
+ }
57158
+ function toggleUsageCursor(item) {
57159
+ return toggleMetadataFlag(item, "cursor");
57160
+ }
57136
57161
  function isUsageDateMode(item) {
57137
57162
  return isMetadataFlagEnabled(item, "absolute");
57138
57163
  }
@@ -57204,6 +57229,9 @@ function getUsageDisplayModifierText(item, options = {}) {
57204
57229
  if (isUsageInverted(item)) {
57205
57230
  modifiers.push("inverted");
57206
57231
  }
57232
+ if (isUsageCursorEnabled(item) && (isUsageProgressMode(mode) || isUsageSliderMode(mode))) {
57233
+ modifiers.push("time cursor");
57234
+ }
57207
57235
  if (options.includeCompact && !isUsageProgressMode(mode) && isUsageCompact(item)) {
57208
57236
  modifiers.push("compact");
57209
57237
  }
@@ -57231,7 +57259,8 @@ function cycleUsageDisplayMode(item, disabledInProgressKeys = [], includeSlider
57231
57259
  } else {
57232
57260
  nextMode = currentMode === "time" ? "progress" : currentMode === "progress" ? "progress-short" : "time";
57233
57261
  }
57234
- const nextItem = removeMetadataKeys(item, nextMode === "time" ? ["invert"] : disabledInProgressKeys);
57262
+ const keysToRemove = nextMode === "time" ? ["invert", "cursor"] : disabledInProgressKeys;
57263
+ const nextItem = removeMetadataKeys(item, keysToRemove);
57235
57264
  const nextMetadata = {
57236
57265
  ...nextItem.metadata ?? {},
57237
57266
  display: nextMode
@@ -57251,6 +57280,9 @@ function getUsagePercentCustomKeybinds(item) {
57251
57280
  if (isUsageProgressMode(mode) || isUsageSliderMode(mode)) {
57252
57281
  keybinds.push(INVERT_TOGGLE_KEYBIND);
57253
57282
  }
57283
+ if (isUsageProgressMode(mode) || isUsageSliderMode(mode)) {
57284
+ keybinds.push(CURSOR_TOGGLE_KEYBIND);
57285
+ }
57254
57286
  }
57255
57287
  return keybinds;
57256
57288
  }
@@ -57277,12 +57309,13 @@ function getUsageTimerCustomKeybinds(item, options = {}) {
57277
57309
  }
57278
57310
  return keybinds;
57279
57311
  }
57280
- var SLIDER_WIDTH = 10, PROGRESS_TOGGLE_KEYBIND, INVERT_TOGGLE_KEYBIND, COMPACT_TOGGLE_KEYBIND, DATE_TOGGLE_KEYBIND, HOUR_FORMAT_TOGGLE_KEYBIND, TIMEZONE_KEYBIND, LOCALE_KEYBIND;
57312
+ var SLIDER_WIDTH = 10, PROGRESS_TOGGLE_KEYBIND, INVERT_TOGGLE_KEYBIND, COMPACT_TOGGLE_KEYBIND, CURSOR_TOGGLE_KEYBIND, DATE_TOGGLE_KEYBIND, HOUR_FORMAT_TOGGLE_KEYBIND, TIMEZONE_KEYBIND, LOCALE_KEYBIND;
57281
57313
  var init_usage_display = __esm(() => {
57282
57314
  init_locales2();
57283
57315
  PROGRESS_TOGGLE_KEYBIND = { key: "p", label: "(p)rogress toggle", action: "toggle-progress" };
57284
57316
  INVERT_TOGGLE_KEYBIND = { key: "v", label: "in(v)ert fill", action: "toggle-invert" };
57285
57317
  COMPACT_TOGGLE_KEYBIND = { key: "s", label: "(s)hort time", action: "toggle-compact" };
57318
+ CURSOR_TOGGLE_KEYBIND = { key: "t", label: "(t)ime cursor", action: "toggle-cursor" };
57286
57319
  DATE_TOGGLE_KEYBIND = { key: "t", label: "(t)imestamp", action: "toggle-date" };
57287
57320
  HOUR_FORMAT_TOGGLE_KEYBIND = { key: "h", label: "12/24 (h)our", action: "toggle-hour-format" };
57288
57321
  TIMEZONE_KEYBIND = { key: "z", label: "time(z)one", action: "edit-timezone" };
@@ -57377,34 +57410,19 @@ class ContextPercentageWidget {
57377
57410
  }
57378
57411
  render(item, context, settings) {
57379
57412
  const isInverse = isContextInverse(item);
57413
+ const label = isInverse ? "Ctx Left: " : "Ctx Used: ";
57380
57414
  const sliderMode = getContextSliderMode(item);
57381
- const contextWindowMetrics = getContextWindowMetrics(context.data);
57382
- if (context.isPreview) {
57383
- const previewPercent = isInverse ? 90.7 : 9.3;
57384
- const sliderResult = renderContextSlider(sliderMode, previewPercent);
57385
- if (sliderResult !== null) {
57386
- return formatRawOrLabeledValue(item, "Ctx: ", sliderResult);
57387
- }
57388
- return formatRawOrLabeledValue(item, "Ctx: ", `${previewPercent.toFixed(1)}%`);
57389
- }
57390
- if (contextWindowMetrics.usedPercentage !== null) {
57391
- const displayPercentage = isInverse ? 100 - contextWindowMetrics.usedPercentage : contextWindowMetrics.usedPercentage;
57415
+ const contextPercentageMetrics = calculateContextPercentageMetrics(context);
57416
+ const formatContextPercentage = (displayPercentage) => {
57392
57417
  const sliderResult = renderContextSlider(sliderMode, displayPercentage);
57393
- if (sliderResult !== null) {
57394
- return formatRawOrLabeledValue(item, "Ctx: ", sliderResult);
57395
- }
57396
- return formatRawOrLabeledValue(item, "Ctx: ", `${displayPercentage.toFixed(1)}%`);
57418
+ return formatRawOrLabeledValue(item, label, sliderResult ?? `${displayPercentage.toFixed(1)}%`);
57419
+ };
57420
+ if (context.isPreview) {
57421
+ return formatContextPercentage(isInverse ? 90.7 : 9.3);
57397
57422
  }
57398
- if (context.tokenMetrics) {
57399
- const modelIdentifier = getModelContextIdentifier(context.data?.model);
57400
- const contextConfig = getContextConfig(modelIdentifier, contextWindowMetrics.windowSize);
57401
- const usedPercentage = Math.min(100, context.tokenMetrics.contextLength / contextConfig.maxTokens * 100);
57402
- const displayPercentage = isInverse ? 100 - usedPercentage : usedPercentage;
57403
- const sliderResult = renderContextSlider(sliderMode, displayPercentage);
57404
- if (sliderResult !== null) {
57405
- return formatRawOrLabeledValue(item, "Ctx: ", sliderResult);
57406
- }
57407
- return formatRawOrLabeledValue(item, "Ctx: ", `${displayPercentage.toFixed(1)}%`);
57423
+ if (contextPercentageMetrics !== null) {
57424
+ const displayPercentage = isInverse ? 100 - contextPercentageMetrics.usedPercentage : contextPercentageMetrics.usedPercentage;
57425
+ return formatContextPercentage(displayPercentage);
57408
57426
  }
57409
57427
  return null;
57410
57428
  }
@@ -57422,6 +57440,7 @@ class ContextPercentageWidget {
57422
57440
  }
57423
57441
  }
57424
57442
  var init_ContextPercentage = __esm(() => {
57443
+ init_context_percentage();
57425
57444
  init_context_inverse();
57426
57445
  init_context_slider();
57427
57446
  });
@@ -57458,35 +57477,27 @@ class ContextPercentageUsableWidget {
57458
57477
  }
57459
57478
  render(item, context, settings) {
57460
57479
  const isInverse = isContextInverse(item);
57480
+ const label = isInverse ? "Ctx(u) Left: " : "Ctx(u) Used: ";
57461
57481
  const sliderMode = getContextSliderMode(item);
57462
57482
  const modelIdentifier = getModelContextIdentifier(context.data?.model);
57463
57483
  const contextWindowMetrics = getContextWindowMetrics(context.data);
57464
57484
  const contextConfig = getContextConfig(modelIdentifier, contextWindowMetrics.windowSize);
57485
+ const formatContextPercentage = (displayPercentage) => {
57486
+ const sliderResult = renderContextSlider(sliderMode, displayPercentage);
57487
+ return formatRawOrLabeledValue(item, label, sliderResult ?? `${displayPercentage.toFixed(1)}%`);
57488
+ };
57465
57489
  if (context.isPreview) {
57466
- const previewPercent = isInverse ? 88.4 : 11.6;
57467
- const sliderResult = renderContextSlider(sliderMode, previewPercent);
57468
- if (sliderResult !== null) {
57469
- return formatRawOrLabeledValue(item, "Ctx(u): ", sliderResult);
57470
- }
57471
- return formatRawOrLabeledValue(item, "Ctx(u): ", `${previewPercent.toFixed(1)}%`);
57490
+ return formatContextPercentage(isInverse ? 88.4 : 11.6);
57472
57491
  }
57473
57492
  if (contextWindowMetrics.contextLengthTokens !== null) {
57474
57493
  const usedPercentage = Math.min(100, contextWindowMetrics.contextLengthTokens / contextConfig.usableTokens * 100);
57475
57494
  const displayPercentage = isInverse ? 100 - usedPercentage : usedPercentage;
57476
- const sliderResult = renderContextSlider(sliderMode, displayPercentage);
57477
- if (sliderResult !== null) {
57478
- return formatRawOrLabeledValue(item, "Ctx(u): ", sliderResult);
57479
- }
57480
- return formatRawOrLabeledValue(item, "Ctx(u): ", `${displayPercentage.toFixed(1)}%`);
57495
+ return formatContextPercentage(displayPercentage);
57481
57496
  }
57482
57497
  if (context.tokenMetrics) {
57483
57498
  const usedPercentage = Math.min(100, context.tokenMetrics.contextLength / contextConfig.usableTokens * 100);
57484
57499
  const displayPercentage = isInverse ? 100 - usedPercentage : usedPercentage;
57485
- const sliderResult = renderContextSlider(sliderMode, displayPercentage);
57486
- if (sliderResult !== null) {
57487
- return formatRawOrLabeledValue(item, "Ctx(u): ", sliderResult);
57488
- }
57489
- return formatRawOrLabeledValue(item, "Ctx(u): ", `${displayPercentage.toFixed(1)}%`);
57500
+ return formatContextPercentage(displayPercentage);
57490
57501
  }
57491
57502
  return null;
57492
57503
  }
@@ -63063,6 +63074,12 @@ function getTranscriptThinkingEffort(transcriptPath) {
63063
63074
  continue;
63064
63075
  }
63065
63076
  const visibleContent = getVisibleText(entry.message.content).trim();
63077
+ if (visibleContent.startsWith(EFFORT_STDOUT_PREFIX)) {
63078
+ const effortMatch = EFFORT_STDOUT_REGEX.exec(visibleContent);
63079
+ if (effortMatch) {
63080
+ return normalizeThinkingEffort(effortMatch[1]);
63081
+ }
63082
+ }
63066
63083
  if (!visibleContent.startsWith(MODEL_STDOUT_PREFIX)) {
63067
63084
  continue;
63068
63085
  }
@@ -63074,13 +63091,14 @@ function getTranscriptThinkingEffort(transcriptPath) {
63074
63091
  }
63075
63092
  return;
63076
63093
  }
63077
- var KNOWN_THINKING_EFFORTS, KNOWN_THINKING_EFFORTS_SET, MODEL_STDOUT_PREFIX = "<local-command-stdout>Set model to ", MODEL_STDOUT_EFFORT_REGEX, UNKNOWN_EFFORT_PATTERN;
63094
+ var KNOWN_THINKING_EFFORTS, KNOWN_THINKING_EFFORTS_SET, MODEL_STDOUT_PREFIX = "<local-command-stdout>Set model to ", MODEL_STDOUT_EFFORT_REGEX, EFFORT_STDOUT_PREFIX = "<local-command-stdout>Set effort level to ", EFFORT_STDOUT_REGEX, UNKNOWN_EFFORT_PATTERN;
63078
63095
  var init_jsonl_metadata = __esm(() => {
63079
63096
  init_ansi();
63080
63097
  init_jsonl_lines();
63081
63098
  KNOWN_THINKING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
63082
63099
  KNOWN_THINKING_EFFORTS_SET = new Set(KNOWN_THINKING_EFFORTS);
63083
63100
  MODEL_STDOUT_EFFORT_REGEX = /^<local-command-stdout>Set model to[\s\S]*? with ([a-zA-Z0-9-]+) effort<\/local-command-stdout>$/i;
63101
+ EFFORT_STDOUT_REGEX = /^<local-command-stdout>Set effort level to ([a-zA-Z0-9-]+)\b/i;
63084
63102
  UNKNOWN_EFFORT_PATTERN = /^(?=.*[a-z0-9])[a-z0-9-]{2,20}$/;
63085
63103
  });
63086
63104
 
@@ -64175,6 +64193,24 @@ class SessionNameWidget {
64175
64193
  }
64176
64194
  var init_SessionName = () => {};
64177
64195
 
64196
+ // src/widgets/shared/progress-bar.ts
64197
+ function makeTimerProgressBar2(percent, width, options) {
64198
+ const clampedPercent = Math.max(0, Math.min(100, percent));
64199
+ const filledWidth = Math.round(clampedPercent / 100 * width);
64200
+ const cursorPos = options?.cursorPercent !== undefined ? Math.min(Math.floor(Math.max(0, Math.min(100, options.cursorPercent)) / 100 * width), width - 1) : -1;
64201
+ let bar = "";
64202
+ for (let i = 0;i < width; i++) {
64203
+ if (i === cursorPos) {
64204
+ bar += "โ”‚";
64205
+ } else if (i < filledWidth) {
64206
+ bar += "โ–ˆ";
64207
+ } else {
64208
+ bar += "โ–‘";
64209
+ }
64210
+ }
64211
+ return bar;
64212
+ }
64213
+
64178
64214
  // src/widgets/SessionUsage.ts
64179
64215
  class SessionUsageWidget {
64180
64216
  getDefaultColor() {
@@ -64202,21 +64238,26 @@ class SessionUsageWidget {
64202
64238
  if (action === "toggle-invert") {
64203
64239
  return toggleUsageInverted(item);
64204
64240
  }
64241
+ if (action === "toggle-cursor") {
64242
+ return toggleUsageCursor(item);
64243
+ }
64205
64244
  return null;
64206
64245
  }
64207
64246
  render(item, context, settings) {
64208
64247
  const displayMode = getUsageDisplayMode(item);
64209
64248
  const inverted = isUsageInverted(item);
64249
+ const showCursor = isUsageCursorEnabled(item);
64210
64250
  if (context.isPreview) {
64211
64251
  const previewPercent = 20;
64212
64252
  const renderedPercent2 = inverted ? 100 - previewPercent : previewPercent;
64213
64253
  if (isUsageProgressMode(displayMode)) {
64214
64254
  const width = getUsageProgressBarWidth(displayMode);
64215
- const progressDisplay = `${makeUsageProgressBar(renderedPercent2, width)} ${renderedPercent2.toFixed(1)}%`;
64255
+ const progressBar = makeTimerProgressBar2(renderedPercent2, width, showCursor ? { cursorPercent: 50 } : undefined);
64256
+ const progressDisplay = `[${progressBar}] ${renderedPercent2.toFixed(1)}%`;
64216
64257
  return formatRawOrLabeledValue(item, "Session: ", progressDisplay);
64217
64258
  }
64218
64259
  if (isUsageSliderMode(displayMode)) {
64219
- const slider = makeSliderBar(renderedPercent2);
64260
+ const slider = makeSliderBar(renderedPercent2, undefined, showCursor ? { cursorPercent: 50 } : undefined);
64220
64261
  const sliderDisplay = displayMode === "slider" ? `${slider} ${renderedPercent2.toFixed(1)}%` : slider;
64221
64262
  return formatRawOrLabeledValue(item, "Session: ", sliderDisplay);
64222
64263
  }
@@ -64229,13 +64270,21 @@ class SessionUsageWidget {
64229
64270
  return null;
64230
64271
  const percent = Math.max(0, Math.min(100, data.sessionUsage));
64231
64272
  const renderedPercent = inverted ? 100 - percent : percent;
64273
+ const getCursorOptions = () => {
64274
+ if (!showCursor) {
64275
+ return;
64276
+ }
64277
+ const window2 = resolveUsageWindowWithFallback(data, context.blockMetrics);
64278
+ return window2 ? { cursorPercent: window2.elapsedPercent } : undefined;
64279
+ };
64232
64280
  if (isUsageProgressMode(displayMode)) {
64233
64281
  const width = getUsageProgressBarWidth(displayMode);
64234
- const progressDisplay = `${makeUsageProgressBar(renderedPercent, width)} ${renderedPercent.toFixed(1)}%`;
64282
+ const progressBar = makeTimerProgressBar2(renderedPercent, width, getCursorOptions());
64283
+ const progressDisplay = `[${progressBar}] ${renderedPercent.toFixed(1)}%`;
64235
64284
  return formatRawOrLabeledValue(item, "Session: ", progressDisplay);
64236
64285
  }
64237
64286
  if (isUsageSliderMode(displayMode)) {
64238
- const slider = makeSliderBar(renderedPercent);
64287
+ const slider = makeSliderBar(renderedPercent, undefined, getCursorOptions());
64239
64288
  const sliderDisplay = displayMode === "slider" ? `${slider} ${renderedPercent.toFixed(1)}%` : slider;
64240
64289
  return formatRawOrLabeledValue(item, "Session: ", sliderDisplay);
64241
64290
  }
@@ -64283,21 +64332,26 @@ class WeeklyUsageWidget {
64283
64332
  if (action === "toggle-invert") {
64284
64333
  return toggleUsageInverted(item);
64285
64334
  }
64335
+ if (action === "toggle-cursor") {
64336
+ return toggleUsageCursor(item);
64337
+ }
64286
64338
  return null;
64287
64339
  }
64288
64340
  render(item, context, settings) {
64289
64341
  const displayMode = getUsageDisplayMode(item);
64290
64342
  const inverted = isUsageInverted(item);
64343
+ const showCursor = isUsageCursorEnabled(item);
64291
64344
  if (context.isPreview) {
64292
64345
  const previewPercent = 12;
64293
64346
  const renderedPercent2 = inverted ? 100 - previewPercent : previewPercent;
64294
64347
  if (isUsageProgressMode(displayMode)) {
64295
64348
  const width = getUsageProgressBarWidth(displayMode);
64296
- const progressDisplay = `${makeUsageProgressBar(renderedPercent2, width)} ${renderedPercent2.toFixed(1)}%`;
64349
+ const progressBar = makeTimerProgressBar2(renderedPercent2, width, showCursor ? { cursorPercent: 50 } : undefined);
64350
+ const progressDisplay = `[${progressBar}] ${renderedPercent2.toFixed(1)}%`;
64297
64351
  return formatRawOrLabeledValue(item, "Weekly: ", progressDisplay);
64298
64352
  }
64299
64353
  if (isUsageSliderMode(displayMode)) {
64300
- const slider = makeSliderBar(renderedPercent2);
64354
+ const slider = makeSliderBar(renderedPercent2, undefined, showCursor ? { cursorPercent: 50 } : undefined);
64301
64355
  const sliderDisplay = displayMode === "slider" ? `${slider} ${renderedPercent2.toFixed(1)}%` : slider;
64302
64356
  return formatRawOrLabeledValue(item, "Weekly: ", sliderDisplay);
64303
64357
  }
@@ -64310,13 +64364,21 @@ class WeeklyUsageWidget {
64310
64364
  return null;
64311
64365
  const percent = Math.max(0, Math.min(100, data.weeklyUsage));
64312
64366
  const renderedPercent = inverted ? 100 - percent : percent;
64367
+ const getCursorOptions = () => {
64368
+ if (!showCursor) {
64369
+ return;
64370
+ }
64371
+ const window2 = resolveWeeklyUsageWindow(data);
64372
+ return window2 ? { cursorPercent: window2.elapsedPercent } : undefined;
64373
+ };
64313
64374
  if (isUsageProgressMode(displayMode)) {
64314
64375
  const width = getUsageProgressBarWidth(displayMode);
64315
- const progressDisplay = `${makeUsageProgressBar(renderedPercent, width)} ${renderedPercent.toFixed(1)}%`;
64376
+ const progressBar = makeTimerProgressBar2(renderedPercent, width, getCursorOptions());
64377
+ const progressDisplay = `[${progressBar}] ${renderedPercent.toFixed(1)}%`;
64316
64378
  return formatRawOrLabeledValue(item, "Weekly: ", progressDisplay);
64317
64379
  }
64318
64380
  if (isUsageSliderMode(displayMode)) {
64319
- const slider = makeSliderBar(renderedPercent);
64381
+ const slider = makeSliderBar(renderedPercent, undefined, getCursorOptions());
64320
64382
  const sliderDisplay = displayMode === "slider" ? `${slider} ${renderedPercent.toFixed(1)}%` : slider;
64321
64383
  return formatRawOrLabeledValue(item, "Weekly: ", sliderDisplay);
64322
64384
  }
@@ -64789,7 +64851,7 @@ var init_timezone_editor = __esm(async () => {
64789
64851
  });
64790
64852
 
64791
64853
  // src/widgets/BlockResetTimer.ts
64792
- function makeTimerProgressBar2(percent, width) {
64854
+ function makeTimerProgressBar3(percent, width) {
64793
64855
  const clampedPercent = Math.max(0, Math.min(100, percent));
64794
64856
  const filledWidth = Math.floor(clampedPercent / 100 * width);
64795
64857
  const emptyWidth = width - filledWidth;
@@ -64842,7 +64904,7 @@ class BlockResetTimerWidget {
64842
64904
  const previewPercent = inverted ? 90 : 10;
64843
64905
  if (isUsageProgressMode(displayMode)) {
64844
64906
  const barWidth = getUsageProgressBarWidth(displayMode);
64845
- const progressBar = makeTimerProgressBar2(previewPercent, barWidth);
64907
+ const progressBar = makeTimerProgressBar3(previewPercent, barWidth);
64846
64908
  return formatRawOrLabeledValue(item, "Reset ", `[${progressBar}] ${previewPercent.toFixed(1)}%`);
64847
64909
  }
64848
64910
  if (dateMode) {
@@ -64862,7 +64924,7 @@ class BlockResetTimerWidget {
64862
64924
  if (isUsageProgressMode(displayMode)) {
64863
64925
  const barWidth = getUsageProgressBarWidth(displayMode);
64864
64926
  const percent = inverted ? window2.remainingPercent : window2.elapsedPercent;
64865
- const progressBar = makeTimerProgressBar2(percent, barWidth);
64927
+ const progressBar = makeTimerProgressBar3(percent, barWidth);
64866
64928
  const percentage = percent.toFixed(1);
64867
64929
  return formatRawOrLabeledValue(item, "Reset ", `[${progressBar}] ${percentage}%`);
64868
64930
  }
@@ -64912,7 +64974,7 @@ var init_BlockResetTimer = __esm(async () => {
64912
64974
  });
64913
64975
 
64914
64976
  // src/widgets/WeeklyResetTimer.ts
64915
- function makeTimerProgressBar3(percent, width) {
64977
+ function makeTimerProgressBar4(percent, width) {
64916
64978
  const clampedPercent = Math.max(0, Math.min(100, percent));
64917
64979
  const filledWidth = Math.floor(clampedPercent / 100 * width);
64918
64980
  const emptyWidth = width - filledWidth;
@@ -65010,7 +65072,7 @@ class WeeklyResetTimerWidget {
65010
65072
  const previewPercent = inverted ? 90 : 10;
65011
65073
  if (isUsageProgressMode(displayMode)) {
65012
65074
  const barWidth = getUsageProgressBarWidth(displayMode);
65013
- const progressBar = makeTimerProgressBar3(previewPercent, barWidth);
65075
+ const progressBar = makeTimerProgressBar4(previewPercent, barWidth);
65014
65076
  return formatRawOrLabeledValue(item, "Weekly Reset ", `[${progressBar}] ${previewPercent.toFixed(1)}%`);
65015
65077
  }
65016
65078
  if (dateMode) {
@@ -65030,7 +65092,7 @@ class WeeklyResetTimerWidget {
65030
65092
  if (isUsageProgressMode(displayMode)) {
65031
65093
  const barWidth = getUsageProgressBarWidth(displayMode);
65032
65094
  const percent = inverted ? window2.remainingPercent : window2.elapsedPercent;
65033
- const progressBar = makeTimerProgressBar3(percent, barWidth);
65095
+ const progressBar = makeTimerProgressBar4(percent, barWidth);
65034
65096
  const percentage = percent.toFixed(1);
65035
65097
  return formatRawOrLabeledValue(item, "Weekly Reset ", `[${progressBar}] ${percentage}%`);
65036
65098
  }
@@ -65587,6 +65649,13 @@ var init_Skills = __esm(async () => {
65587
65649
  });
65588
65650
 
65589
65651
  // src/widgets/ThinkingEffort.ts
65652
+ function resolveThinkingEffortFromStatusJson(context) {
65653
+ const effort = context.data?.effort;
65654
+ if (!effort || !("level" in effort)) {
65655
+ return;
65656
+ }
65657
+ return typeof effort.level === "string" ? normalizeThinkingEffort(effort.level) : null;
65658
+ }
65590
65659
  function resolveThinkingEffortFromSettings() {
65591
65660
  try {
65592
65661
  const settings = loadClaudeSettingsSync({ logErrors: false });
@@ -65595,6 +65664,10 @@ function resolveThinkingEffortFromSettings() {
65595
65664
  return;
65596
65665
  }
65597
65666
  function resolveThinkingEffort(context) {
65667
+ const statusEffort = resolveThinkingEffortFromStatusJson(context);
65668
+ if (statusEffort !== undefined) {
65669
+ return statusEffort;
65670
+ }
65598
65671
  return getTranscriptThinkingEffort(context.data?.transcript_path) ?? resolveThinkingEffortFromSettings() ?? null;
65599
65672
  }
65600
65673
  function formatEffort(resolved) {
@@ -65893,6 +65966,149 @@ class GitWorktreeOriginalBranchWidget {
65893
65966
  }
65894
65967
  }
65895
65968
 
65969
+ // src/widgets/CompactionCounter.ts
65970
+ function getFormat2(item) {
65971
+ const format = item.metadata?.format;
65972
+ return FORMATS2.includes(format ?? "") ? format : DEFAULT_FORMAT2;
65973
+ }
65974
+ function removeNerdFont(item) {
65975
+ const { [NERD_FONT_METADATA_KEY2]: removedNerdFont, ...restMetadata } = item.metadata ?? {};
65976
+ return {
65977
+ ...item,
65978
+ metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined
65979
+ };
65980
+ }
65981
+ function setFormat2(item, format) {
65982
+ if (format === DEFAULT_FORMAT2) {
65983
+ const { format: removedFormat, ...restMetadata2 } = item.metadata ?? {};
65984
+ return {
65985
+ ...item,
65986
+ metadata: Object.keys(restMetadata2).length > 0 ? restMetadata2 : undefined
65987
+ };
65988
+ }
65989
+ const { [NERD_FONT_METADATA_KEY2]: removedNerdFont, ...restMetadata } = item.metadata ?? {};
65990
+ return {
65991
+ ...item,
65992
+ metadata: {
65993
+ ...restMetadata,
65994
+ format
65995
+ }
65996
+ };
65997
+ }
65998
+ function isNerdFontEnabled2(item) {
65999
+ return item.metadata?.[NERD_FONT_METADATA_KEY2] === "true" && getFormat2(item) === DEFAULT_FORMAT2;
66000
+ }
66001
+ function isHideZeroEnabled(item) {
66002
+ return item.metadata?.[HIDE_ZERO_METADATA_KEY] === "true";
66003
+ }
66004
+ function toggleHideZero(item) {
66005
+ return {
66006
+ ...item,
66007
+ metadata: {
66008
+ ...item.metadata ?? {},
66009
+ [HIDE_ZERO_METADATA_KEY]: (!isHideZeroEnabled(item)).toString()
66010
+ }
66011
+ };
66012
+ }
66013
+ function toggleNerdFont2(item) {
66014
+ if (getFormat2(item) !== DEFAULT_FORMAT2) {
66015
+ return removeNerdFont(item);
66016
+ }
66017
+ if (!isNerdFontEnabled2(item)) {
66018
+ return {
66019
+ ...item,
66020
+ metadata: {
66021
+ ...item.metadata ?? {},
66022
+ [NERD_FONT_METADATA_KEY2]: "true"
66023
+ }
66024
+ };
66025
+ }
66026
+ return removeNerdFont(item);
66027
+ }
66028
+ function formatCount(count, format, icon) {
66029
+ switch (format) {
66030
+ case "icon-space-number":
66031
+ return `${icon} ${count}`;
66032
+ case "text-and-number":
66033
+ return `Compactions: ${count}`;
66034
+ case "number":
66035
+ return String(count);
66036
+ }
66037
+ }
66038
+
66039
+ class CompactionCounterWidget {
66040
+ getDefaultColor() {
66041
+ return "yellow";
66042
+ }
66043
+ getDescription() {
66044
+ return "Count of context compaction events in the current session.";
66045
+ }
66046
+ getDisplayName() {
66047
+ return "Compaction Counter";
66048
+ }
66049
+ getCategory() {
66050
+ return "Context";
66051
+ }
66052
+ getEditorDisplay(item) {
66053
+ const modifiers = [getFormat2(item)];
66054
+ if (isNerdFontEnabled2(item)) {
66055
+ modifiers.push("nerd font");
66056
+ }
66057
+ if (isHideZeroEnabled(item)) {
66058
+ modifiers.push("hide zero");
66059
+ }
66060
+ return {
66061
+ displayText: "Compaction Counter",
66062
+ modifierText: `(${modifiers.join(", ")})`
66063
+ };
66064
+ }
66065
+ handleEditorAction(action, item) {
66066
+ if (action === CYCLE_FORMAT_ACTION2) {
66067
+ const currentFormat = getFormat2(item);
66068
+ const nextFormat = FORMATS2[(FORMATS2.indexOf(currentFormat) + 1) % FORMATS2.length] ?? DEFAULT_FORMAT2;
66069
+ return setFormat2(item, nextFormat);
66070
+ }
66071
+ if (action === TOGGLE_HIDE_ZERO_ACTION) {
66072
+ return toggleHideZero(item);
66073
+ }
66074
+ if (action === TOGGLE_NERD_FONT_ACTION2) {
66075
+ return toggleNerdFont2(item);
66076
+ }
66077
+ return null;
66078
+ }
66079
+ render(item, context, settings) {
66080
+ const format = getFormat2(item);
66081
+ const icon = isNerdFontEnabled2(item) ? COMPACTION_NERD_FONT_ICON : COMPACTION_ICON;
66082
+ if (context.isPreview) {
66083
+ return formatCount(2, format, icon);
66084
+ }
66085
+ const count = context.compactionData?.count ?? 0;
66086
+ if (count === 0 && isHideZeroEnabled(item))
66087
+ return null;
66088
+ return formatCount(count, format, icon);
66089
+ }
66090
+ getCustomKeybinds(item) {
66091
+ const keybinds = [
66092
+ { key: "f", label: "(f)ormat", action: CYCLE_FORMAT_ACTION2 }
66093
+ ];
66094
+ if (item === undefined || getFormat2(item) === DEFAULT_FORMAT2) {
66095
+ keybinds.push({ key: "n", label: "(n)erd font", action: TOGGLE_NERD_FONT_ACTION2 });
66096
+ }
66097
+ keybinds.push({ key: "h", label: "(h)ide when zero", action: TOGGLE_HIDE_ZERO_ACTION });
66098
+ return keybinds;
66099
+ }
66100
+ supportsRawValue() {
66101
+ return false;
66102
+ }
66103
+ supportsColors(item) {
66104
+ return true;
66105
+ }
66106
+ }
66107
+ var COMPACTION_ICON = "โ†ป", COMPACTION_NERD_FONT_ICON = "๏€ก", FORMATS2, DEFAULT_FORMAT2 = "icon-space-number", CYCLE_FORMAT_ACTION2 = "cycle-format", TOGGLE_HIDE_ZERO_ACTION = "toggle-hide-zero", TOGGLE_NERD_FONT_ACTION2 = "toggle-nerd-font", HIDE_ZERO_METADATA_KEY = "hideZero", NERD_FONT_METADATA_KEY2 = "nerdFont";
66108
+ var init_CompactionCounter = __esm(() => {
66109
+ FORMATS2 = ["icon-space-number", "text-and-number", "number"];
66110
+ });
66111
+
65896
66112
  // src/widgets/index.ts
65897
66113
  var init_widgets = __esm(async () => {
65898
66114
  init_GitBranch();
@@ -65926,6 +66142,7 @@ var init_widgets = __esm(async () => {
65926
66142
  init_FreeMemory();
65927
66143
  init_SessionName();
65928
66144
  init_VimMode();
66145
+ init_CompactionCounter();
65929
66146
  await __promiseAll([
65930
66147
  init_TokensInput(),
65931
66148
  init_TokensOutput(),
@@ -66021,7 +66238,8 @@ var init_widget_manifest = __esm(async () => {
66021
66238
  { type: "worktree-mode", create: () => new GitWorktreeModeWidget },
66022
66239
  { type: "worktree-name", create: () => new GitWorktreeNameWidget },
66023
66240
  { type: "worktree-branch", create: () => new GitWorktreeBranchWidget },
66024
- { type: "worktree-original-branch", create: () => new GitWorktreeOriginalBranchWidget }
66241
+ { type: "worktree-original-branch", create: () => new GitWorktreeOriginalBranchWidget },
66242
+ { type: "compaction-counter", create: () => new CompactionCounterWidget }
66025
66243
  ];
66026
66244
  LAYOUT_WIDGET_MANIFEST = [
66027
66245
  {
@@ -70693,7 +70911,7 @@ var PowerlineSeparatorEditor = ({
70693
70911
  newInvertBgs[selectedIndex] = isLeftFacing;
70694
70912
  }
70695
70913
  updateSeparators(newSeparators, mode === "separator" ? newInvertBgs : undefined);
70696
- } else if ((input === "a" || input === "A") && (mode === "separator" || separators.length < 3)) {
70914
+ } else if (input === "a" || input === "A") {
70697
70915
  const newSeparators = [...separators];
70698
70916
  const newInvertBgs = mode === "separator" ? [...invertBgs] : [];
70699
70917
  const defaultChar = presetSeparators[0]?.char ?? "๎‚ฐ";
@@ -70713,7 +70931,7 @@ var PowerlineSeparatorEditor = ({
70713
70931
  updateSeparators(newSeparators, newInvertBgs);
70714
70932
  setSelectedIndex(selectedIndex + 1);
70715
70933
  }
70716
- } else if ((input === "i" || input === "I") && (mode === "separator" || separators.length < 3)) {
70934
+ } else if (input === "i" || input === "I") {
70717
70935
  const newSeparators = [...separators];
70718
70936
  const newInvertBgs = mode === "separator" ? [...invertBgs] : [];
70719
70937
  const defaultChar = presetSeparators[0]?.char ?? "๎‚ฐ";
@@ -70765,7 +70983,6 @@ var PowerlineSeparatorEditor = ({
70765
70983
  return "Powerline End Cap Configuration";
70766
70984
  }
70767
70985
  };
70768
- const canAdd = mode === "separator" || separators.length < 3;
70769
70986
  const canDelete = mode !== "separator" || separators.length > 1;
70770
70987
  return /* @__PURE__ */ jsx_dev_runtime18.jsxDEV(Box_default, {
70771
70988
  flexDirection: "column",
@@ -70817,7 +71034,7 @@ var PowerlineSeparatorEditor = ({
70817
71034
  /* @__PURE__ */ jsx_dev_runtime18.jsxDEV(Box_default, {
70818
71035
  children: /* @__PURE__ */ jsx_dev_runtime18.jsxDEV(Text, {
70819
71036
  dimColor: true,
70820
- children: `โ†‘โ†“ select, โ† โ†’ cycle${canAdd ? ", (a)dd, (i)nsert" : ""}${canDelete ? ", (d)elete" : ""}, (c)lear, (h)ex${mode === "separator" ? ", (t)oggle invert" : ""}, ESC back`
71037
+ children: `โ†‘โ†“ select, โ† โ†’ cycle, (a)dd, (i)nsert${canDelete ? ", (d)elete" : ""}, (c)lear, (h)ex${mode === "separator" ? ", (t)oggle invert" : ""}, ESC back`
70821
71038
  }, undefined, false, undefined, this)
70822
71039
  }, undefined, false, undefined, this),
70823
71040
  /* @__PURE__ */ jsx_dev_runtime18.jsxDEV(Box_default, {
@@ -72668,6 +72885,7 @@ var StatusJSONSchema = exports_external.looseObject({
72668
72885
  }).optional(),
72669
72886
  version: exports_external.string().optional(),
72670
72887
  output_style: exports_external.object({ name: exports_external.string().optional() }).optional(),
72888
+ effort: exports_external.object({ level: exports_external.string().nullable().optional() }).nullable().optional(),
72671
72889
  cost: exports_external.object({
72672
72890
  total_cost_usd: CoercedNumberSchema.optional(),
72673
72891
  total_duration_ms: CoercedNumberSchema.optional(),
@@ -72708,6 +72926,115 @@ var StatusJSONSchema = exports_external.looseObject({
72708
72926
  // src/ccstatusline.ts
72709
72927
  init_ansi();
72710
72928
  init_colors();
72929
+
72930
+ // src/utils/compaction.ts
72931
+ init_zod();
72932
+ import * as crypto from "crypto";
72933
+ import * as fs13 from "fs";
72934
+ import * as os13 from "os";
72935
+ import * as path10 from "path";
72936
+ var DEFAULT_DROP_THRESHOLD = 2;
72937
+ var FRESH_PREV_CTX_PCT = -1;
72938
+ var MAX_CACHE_FILE_BYTES = 4096;
72939
+ var SESSION_ID_HASH_HEX_LEN = 32;
72940
+ var FRESH = { count: 0, prevCtxPct: FRESH_PREV_CTX_PCT };
72941
+ var CompactionStateSchema = exports_external.object({
72942
+ count: exports_external.number().int().nonnegative().default(0),
72943
+ prevCtxPct: exports_external.number().default(FRESH_PREV_CTX_PCT),
72944
+ prevWindowSize: exports_external.number().positive().nullable().optional()
72945
+ });
72946
+ function normalizeWindowSize(value) {
72947
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
72948
+ return null;
72949
+ }
72950
+ return value;
72951
+ }
72952
+ function normalizeOptions(options) {
72953
+ if (typeof options === "number") {
72954
+ return { dropThreshold: options, windowSize: null };
72955
+ }
72956
+ const dropThreshold = typeof options.dropThreshold === "number" && Number.isFinite(options.dropThreshold) ? options.dropThreshold : DEFAULT_DROP_THRESHOLD;
72957
+ return { dropThreshold, windowSize: options.windowSize ?? null };
72958
+ }
72959
+ function detectCompaction(currentCtxPct, state, options = DEFAULT_DROP_THRESHOLD) {
72960
+ if (!Number.isFinite(currentCtxPct) || currentCtxPct < 0) {
72961
+ return state;
72962
+ }
72963
+ const { dropThreshold, windowSize } = normalizeOptions(options);
72964
+ const currentWindowSize = normalizeWindowSize(windowSize);
72965
+ const prevWindowSize = normalizeWindowSize(state.prevWindowSize);
72966
+ let { count } = state;
72967
+ const { prevCtxPct } = state;
72968
+ const hasKnownWindowChange = currentWindowSize !== null && prevWindowSize !== null && currentWindowSize !== prevWindowSize;
72969
+ const isLearningWindowSize = currentWindowSize !== null && prevWindowSize === null && prevCtxPct >= 0;
72970
+ if (!hasKnownWindowChange && !isLearningWindowSize && prevCtxPct >= 0 && currentCtxPct < prevCtxPct - dropThreshold) {
72971
+ count += 1;
72972
+ }
72973
+ return {
72974
+ count,
72975
+ prevCtxPct: currentCtxPct,
72976
+ ...currentWindowSize !== null ? { prevWindowSize: currentWindowSize } : {}
72977
+ };
72978
+ }
72979
+ function getCacheDir2() {
72980
+ return path10.join(os13.homedir(), ".cache", "ccstatusline", "compaction");
72981
+ }
72982
+ function sanitizeSessionId(sessionId) {
72983
+ const sanitized = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
72984
+ if (!sanitized || sanitized !== sessionId) {
72985
+ return crypto.createHash("sha256").update(sessionId).digest("hex").slice(0, SESSION_ID_HASH_HEX_LEN);
72986
+ }
72987
+ return sanitized;
72988
+ }
72989
+ function getStatePath(sessionId) {
72990
+ return path10.join(getCacheDir2(), `compaction-${sanitizeSessionId(sessionId)}.json`);
72991
+ }
72992
+ function loadCompactionState(sessionId) {
72993
+ const statePath = getStatePath(sessionId);
72994
+ let fd = null;
72995
+ try {
72996
+ fd = fs13.openSync(statePath, fs13.constants.O_RDONLY | fs13.constants.O_NOFOLLOW);
72997
+ const stats = fs13.fstatSync(fd);
72998
+ if (!stats.isFile() || stats.size > MAX_CACHE_FILE_BYTES) {
72999
+ return FRESH;
73000
+ }
73001
+ const raw = JSON.parse(fs13.readFileSync(fd, "utf-8"));
73002
+ const result2 = CompactionStateSchema.safeParse(raw);
73003
+ return result2.success ? result2.data : FRESH;
73004
+ } catch {
73005
+ return FRESH;
73006
+ } finally {
73007
+ if (fd !== null) {
73008
+ try {
73009
+ fs13.closeSync(fd);
73010
+ } catch {}
73011
+ }
73012
+ }
73013
+ }
73014
+ function saveCompactionState(sessionId, state) {
73015
+ let tmpPath = null;
73016
+ try {
73017
+ const dir = getCacheDir2();
73018
+ if (!fs13.existsSync(dir)) {
73019
+ fs13.mkdirSync(dir, { recursive: true });
73020
+ }
73021
+ const targetPath = getStatePath(sessionId);
73022
+ tmpPath = `${targetPath}.tmp.${process.pid}.${crypto.randomBytes(4).toString("hex")}`;
73023
+ fs13.writeFileSync(tmpPath, JSON.stringify(state) + `
73024
+ `);
73025
+ fs13.renameSync(tmpPath, targetPath);
73026
+ tmpPath = null;
73027
+ } catch {
73028
+ if (tmpPath !== null) {
73029
+ try {
73030
+ fs13.unlinkSync(tmpPath);
73031
+ } catch {}
73032
+ }
73033
+ }
73034
+ }
73035
+
73036
+ // src/ccstatusline.ts
73037
+ init_context_percentage();
72711
73038
  await __promiseAll([
72712
73039
  init_config(),
72713
73040
  init_jsonl()
@@ -72715,23 +73042,23 @@ await __promiseAll([
72715
73042
  await init_renderer2();
72716
73043
 
72717
73044
  // src/utils/skills.ts
72718
- import * as fs13 from "fs";
72719
- import * as os13 from "os";
72720
- import * as path10 from "path";
73045
+ import * as fs14 from "fs";
73046
+ import * as os14 from "os";
73047
+ import * as path11 from "path";
72721
73048
  var EMPTY = { totalInvocations: 0, uniqueSkills: [], lastSkill: null };
72722
73049
  function getSkillsDir() {
72723
- return path10.join(os13.homedir(), ".cache", "ccstatusline", "skills");
73050
+ return path11.join(os14.homedir(), ".cache", "ccstatusline", "skills");
72724
73051
  }
72725
73052
  function getSkillsFilePath(sessionId) {
72726
- return path10.join(getSkillsDir(), `skills-${sessionId}.jsonl`);
73053
+ return path11.join(getSkillsDir(), `skills-${sessionId}.jsonl`);
72727
73054
  }
72728
73055
  function getSkillsMetrics(sessionId) {
72729
73056
  const filePath = getSkillsFilePath(sessionId);
72730
- if (!fs13.existsSync(filePath)) {
73057
+ if (!fs14.existsSync(filePath)) {
72731
73058
  return EMPTY;
72732
73059
  }
72733
73060
  try {
72734
- const invocations = fs13.readFileSync(filePath, "utf-8").trim().split(`
73061
+ const invocations = fs14.readFileSync(filePath, "utf-8").trim().split(`
72735
73062
  `).filter((line) => line.trim()).map((line) => {
72736
73063
  try {
72737
73064
  return JSON.parse(line);
@@ -72880,6 +73207,20 @@ async function renderMultipleLines(data) {
72880
73207
  if (data.session_id) {
72881
73208
  skillsMetrics = getSkillsMetrics(data.session_id);
72882
73209
  }
73210
+ let compactionCount = 0;
73211
+ const hasCompactionWidget = lines.some((line) => line.some((item) => item.type === "compaction-counter"));
73212
+ if (hasCompactionWidget && data.session_id) {
73213
+ const prevState = loadCompactionState(data.session_id);
73214
+ compactionCount = prevState.count;
73215
+ const contextPercentageMetrics = calculateContextPercentageMetrics({ data, tokenMetrics });
73216
+ if (contextPercentageMetrics !== null) {
73217
+ const newState = detectCompaction(contextPercentageMetrics.usedPercentage, prevState, { windowSize: contextPercentageMetrics.windowSize });
73218
+ if (newState.count !== prevState.count || newState.prevCtxPct !== prevState.prevCtxPct || newState.prevWindowSize !== prevState.prevWindowSize) {
73219
+ saveCompactionState(data.session_id, newState);
73220
+ }
73221
+ compactionCount = newState.count;
73222
+ }
73223
+ }
72883
73224
  const context = {
72884
73225
  data,
72885
73226
  tokenMetrics,
@@ -72888,6 +73229,7 @@ async function renderMultipleLines(data) {
72888
73229
  usageData,
72889
73230
  sessionDuration,
72890
73231
  skillsMetrics,
73232
+ compactionData: hasCompactionWidget ? { count: compactionCount } : null,
72891
73233
  isPreview: false,
72892
73234
  minimalist: settings.minimalistMode
72893
73235
  };
@@ -72974,16 +73316,16 @@ async function handleHook() {
72974
73316
  return;
72975
73317
  }
72976
73318
  const filePath = getSkillsFilePath(sessionId);
72977
- const fs14 = await import("fs");
72978
- const path11 = await import("path");
72979
- fs14.mkdirSync(path11.dirname(filePath), { recursive: true });
73319
+ const fs15 = await import("fs");
73320
+ const path12 = await import("path");
73321
+ fs15.mkdirSync(path12.dirname(filePath), { recursive: true });
72980
73322
  const entry = JSON.stringify({
72981
73323
  timestamp: new Date().toISOString(),
72982
73324
  session_id: sessionId,
72983
73325
  skill: skillName,
72984
73326
  source: data.hook_event_name
72985
73327
  });
72986
- fs14.appendFileSync(filePath, entry + `
73328
+ fs15.appendFileSync(filePath, entry + `
72987
73329
  `);
72988
73330
  } catch {}
72989
73331
  console.log("{}");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline",
3
- "version": "2.2.10",
3
+ "version": "2.2.12",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",
@@ -45,7 +45,7 @@
45
45
  "strip-ansi": "^7.1.0",
46
46
  "tinyglobby": "^0.2.14",
47
47
  "typedoc": "^0.28.12",
48
- "typescript": "^5.9.2",
48
+ "typescript": "^6.0.2",
49
49
  "typescript-eslint": "^8.39.1",
50
50
  "vitest": "^4.0.18",
51
51
  "zod": "^4.0.17"