ccstatusline-usage 2.3.0 → 2.3.2

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
@@ -31,15 +31,17 @@
31
31
  This fork adds API-based usage widgets beyond the upstream:
32
32
 
33
33
  - **Session/Weekly Usage** - Real utilization from Anthropic API with progress bars
34
+ - **Weekly Pace** - Pendulum bar showing if you're ahead or behind expected usage pace
34
35
  - **Reset Timer** - Time until 5-hour session window resets
35
36
  - **Context Window Display** - Visual bar showing context usage
37
+ - **Off Peak** - Shows peak/off-peak 2x status during Anthropic usage promotions
36
38
  - **Two-line Layout** - Session info on line 1, context on line 2
37
39
 
38
40
  ### Enhanced Status Line Preview
39
41
 
40
42
  ```
41
43
  Session: [████░░░░░░░░░░░] 27.0% | Weekly: [███████████████] 100.0% | Extra: €2.50/€50.00 | Model: Opus 4.6 | Session ID: 0109b99d...
42
- Context: [███████░░░░░░░░] 103k/200k (51%)
44
+ Context: [███████░░░░░░░░] 103k/200k (51%) | Pace: [░░░░░░░|██░░░░░] D5/7 +12% | Off peak: Off-peak 2x
43
45
  ```
44
46
 
45
47
  ![Demo](https://raw.githubusercontent.com/sirmalloc/ccstatusline/main/screenshots/demo.gif)
@@ -64,6 +66,16 @@ Session: [████░░░░░░░░░░░] 27.0% | Weekly: [██
64
66
 
65
67
  ## 🆕 Recent Updates
66
68
 
69
+ ### [v2.3.2](https://github.com/pcvelz/ccstatusline-usage/releases/tag/v2.3.2) - Off Peak widget + WeeklyPace compact fix
70
+
71
+ - [pcvelz/ccstatusline-usage](https://github.com/pcvelz/ccstatusline-usage): **Off Peak widget** — New widget showing `Off peak: Off-peak 2x` or `Off peak: Peak` during the Anthropic March 2026 Spring Break promotion (off-peak = weekdays outside 8 AM–2 PM ET, weekends all day). Widget automatically hides after March 28, 2026 23:59 PT.
72
+ - [pcvelz/ccstatusline-usage](https://github.com/pcvelz/ccstatusline-usage): **WeeklyPace compact fix** — Pendulum bar now falls back to text mode (`D6/7: -18%`) on narrow terminals (< 80 chars)
73
+ - [pcvelz/ccstatusline-usage](https://github.com/pcvelz/ccstatusline-usage): **Sonnet [1m] extra usage fix** — Extra usage spending now shown when a charged `[1m]` model (e.g. Sonnet) is active, regardless of weekly limit status
74
+
75
+ ### [v2.3.1](https://github.com/pcvelz/ccstatusline-usage/releases/tag/v2.3.1) - Weekly Pace widget
76
+
77
+ - [pcvelz/ccstatusline-usage](https://github.com/pcvelz/ccstatusline-usage): **Weekly Pace widget** — New widget showing if weekly usage pace is on track, with pendulum bar or text display mode (PR #1 by @BenIsLegit)
78
+
67
79
  ### [v2.3.0](https://github.com/pcvelz/ccstatusline-usage/releases/tag/v2.3.0) - Upstream sync + Vim Mode widget
68
80
 
69
81
  - [pcvelz/ccstatusline-usage](https://github.com/pcvelz/ccstatusline-usage): **Upstream sync** — Merged 14 upstream commits: Vim Mode widget, hyperlink support for GitBranch/GitRootDir, ESLint 10 upgrade, timer day display fix (≥24h), macOS keychain token fallback, ink-gradient 4.0.0
@@ -538,12 +550,14 @@ bun run example
538
550
  - **Battery** *(ccstatusline-usage)* - Shows battery percentage on macOS and Linux (only visible when on battery power, hidden when charging)
539
551
  - **Session Usage** - Shows daily/session API usage percentage
540
552
  - **Weekly Usage** - Shows weekly API usage percentage
553
+ - **Weekly Pace** - Pendulum bar or text label showing if usage pace is on track, overcooking, or underutilized (toggle with `p` key in TUI)
541
554
  - **Block Reset Timer** - Shows time remaining until current 5-hour block reset window
542
555
  - **Weekly Reset Timer** - Shows time remaining until weekly usage reset
543
556
  - **Context Bar** - Shows context usage as a progress bar with short/full display modes
544
557
  - **Skills** - Shows skill activity as last used, total count, or unique list (with optional list limit and hide-when-empty toggle)
545
558
  - **Thinking Effort** - Shows the current Claude Code thinking effort level
546
559
  - **Vim Mode** - Displays current vim editor mode
560
+ - **Off Peak** *(ccstatusline-usage)* - Shows `Off peak: Off-peak 2x` or `Off peak: Peak` during Anthropic usage promotions (auto-hides when promotion ends)
547
561
  - **Separator** - Visual divider between widgets (available when Powerline mode is off and no default separator is configured)
548
562
  - **Flex Separator** - Expands to fill available space (available when Powerline mode is off)
549
563
 
@@ -631,6 +645,7 @@ Widget-specific shortcuts:
631
645
  - **Block Timer**: `p` cycle display mode (time/full bar/short bar)
632
646
  - **Block Reset Timer**: `p` cycle display mode (time/full bar/short bar)
633
647
  - **Weekly Reset Timer**: `p` cycle display mode (time/full bar/short bar)
648
+ - **Weekly Pace**: `p` toggle pendulum bar / text label
634
649
  - **Current Working Dir**: `h` home abbreviation, `s` segment editor, `f` fish-style path
635
650
  - **Custom Command**: `e` command, `w` max width, `t` timeout, `p` preserve ANSI colors
636
651
  - **Link**: `u` URL, `e` link text
@@ -52428,7 +52428,9 @@ var init_Settings = __esm(() => {
52428
52428
  { id: "session-id", type: "claude-session-id", color: "cyan" }
52429
52429
  ],
52430
52430
  [
52431
- { id: "context-bar", type: "context-bar", color: "blue" }
52431
+ { id: "context-bar", type: "context-bar", color: "blue" },
52432
+ { id: "sep-off-peak", type: "separator" },
52433
+ { id: "off-peak", type: "off-peak", color: "green" }
52432
52434
  ],
52433
52435
  []
52434
52436
  ]),
@@ -54069,7 +54071,7 @@ function getTerminalWidth() {
54069
54071
  function canDetectTerminalWidth() {
54070
54072
  return probeTerminalWidth() !== null;
54071
54073
  }
54072
- var __dirname = "/Users/peter/Documents/Code/ccstatusline-usage/src/utils", PACKAGE_VERSION = "2.3.0";
54074
+ var __dirname = "/Users/peter/Documents/Code/ccstatusline-usage/src/utils", PACKAGE_VERSION = "2.3.2";
54073
54075
  var init_terminal = () => {};
54074
54076
 
54075
54077
  // src/utils/renderer.ts
@@ -60440,6 +60442,19 @@ function resolveUsageWindowWithFallback(usageData, blockMetrics, nowMs = Date.no
60440
60442
  }
60441
60443
  return getUsageWindowFromBlockMetrics(fallbackMetrics, nowMs);
60442
60444
  }
60445
+ function getWeeklyUsageWindowFromResetAt(weeklyResetAt, nowMs = Date.now()) {
60446
+ if (!weeklyResetAt) {
60447
+ return null;
60448
+ }
60449
+ const resetAtMs = Date.parse(weeklyResetAt);
60450
+ if (Number.isNaN(resetAtMs)) {
60451
+ return null;
60452
+ }
60453
+ return buildUsageWindow(resetAtMs, nowMs, SEVEN_DAY_WINDOW_MS);
60454
+ }
60455
+ function resolveWeeklyUsageWindow(usageData, nowMs = Date.now()) {
60456
+ return getWeeklyUsageWindowFromResetAt(usageData.weeklyResetAt, nowMs);
60457
+ }
60443
60458
  function formatUsageDuration(durationMs, compact2 = false, useDays = true) {
60444
60459
  const clampedMs = Math.max(0, durationMs);
60445
60460
  const totalHours = Math.floor(clampedMs / (1000 * 60 * 60));
@@ -60451,6 +60466,28 @@ function formatUsageDuration(durationMs, compact2 = false, useDays = true) {
60451
60466
  const parts = [d > 0 && `${d}d`, h > 0 && `${h}${hLabel}`, m > 0 && `${m}m`].filter(Boolean);
60452
60467
  return parts.length > 0 ? parts.join(sep2) : "0m";
60453
60468
  }
60469
+ function getUsageErrorMessage(error48) {
60470
+ switch (error48) {
60471
+ case "no-credentials":
60472
+ return "[No credentials]";
60473
+ case "timeout":
60474
+ return "[Timeout]";
60475
+ case "rate-limited":
60476
+ return "[Rate limited]";
60477
+ case "api-error":
60478
+ return "[API Error]";
60479
+ case "parse-error":
60480
+ return "[Parse Error]";
60481
+ }
60482
+ }
60483
+ function makePendulumBar(delta, halfWidth = 7) {
60484
+ const clamped = Math.max(-100, Math.min(100, delta));
60485
+ const fill2 = Math.min(halfWidth, Math.round(Math.abs(clamped) / 100 * halfWidth));
60486
+ if (clamped < 0) {
60487
+ return "[" + "░".repeat(halfWidth - fill2) + "█".repeat(fill2) + "|" + "░".repeat(halfWidth) + "]";
60488
+ }
60489
+ return "[" + "░".repeat(halfWidth) + "|" + "█".repeat(fill2) + "░".repeat(halfWidth - fill2) + "]";
60490
+ }
60454
60491
  var init_usage_windows = __esm(() => {
60455
60492
  init_jsonl();
60456
60493
  init_usage_types();
@@ -61708,7 +61745,11 @@ class ResetTimerWidget {
61708
61745
  const data = fetchApiData();
61709
61746
  if (data.error)
61710
61747
  return getErrorMessage(data.error);
61711
- if (data.extraUsageEnabled && data.weeklyUsage !== undefined && data.weeklyUsage >= 100 && data.extraUsageUsed !== undefined && data.extraUsageLimit !== undefined) {
61748
+ const modelId = context.data?.model?.id ?? "";
61749
+ const is1mModel = modelId.includes("[1m]");
61750
+ const isOpus = modelId.includes("opus");
61751
+ const isChargedModel = is1mModel && !isOpus;
61752
+ if (data.extraUsageEnabled && data.extraUsageUsed !== undefined && data.extraUsageLimit !== undefined && (data.weeklyUsage !== undefined && data.weeklyUsage >= 100 || isChargedModel)) {
61712
61753
  const used = formatCents(data.extraUsageUsed);
61713
61754
  const displayLimit = settings.extraUsageBalance ?? data.extraUsageLimit;
61714
61755
  const limit = formatCents(displayLimit);
@@ -62453,6 +62494,169 @@ var init_VimMode = __esm(() => {
62453
62494
  FORMATS = ["icon-dash-letter", "icon-letter", "icon", "letter", "word"];
62454
62495
  });
62455
62496
 
62497
+ // src/widgets/WeeklyPace.ts
62498
+ function getPaceDisplayMode(item) {
62499
+ return item.metadata?.display === "pendulum" ? "pendulum" : "text";
62500
+ }
62501
+ function computePace(actualPercent, expectedPercent) {
62502
+ const delta = actualPercent - expectedPercent;
62503
+ const dayOfWeek = Math.max(1, Math.min(7, Math.ceil(expectedPercent * 7 / 100)));
62504
+ let status;
62505
+ if (delta > 15) {
62506
+ status = `Overcooking +${Math.round(delta)}%`;
62507
+ } else if (delta > 5) {
62508
+ status = `Warm +${Math.round(delta)}%`;
62509
+ } else if (delta < -15) {
62510
+ status = `Underusing ${Math.round(delta)}%`;
62511
+ } else if (delta < -5) {
62512
+ status = `Cool ${Math.round(delta)}%`;
62513
+ } else {
62514
+ status = "On Pace";
62515
+ }
62516
+ return { delta, dayOfWeek, status };
62517
+ }
62518
+
62519
+ class WeeklyPaceWidget {
62520
+ getDefaultColor() {
62521
+ return "brightYellow";
62522
+ }
62523
+ getDescription() {
62524
+ return "Shows if weekly usage pace is on track, overcooking, or underutilized";
62525
+ }
62526
+ getDisplayName() {
62527
+ return "Weekly Pace";
62528
+ }
62529
+ getCategory() {
62530
+ return "Usage";
62531
+ }
62532
+ getEditorDisplay(item) {
62533
+ const mode = getPaceDisplayMode(item);
62534
+ const modifiers = [];
62535
+ if (mode === "pendulum") {
62536
+ modifiers.push("pendulum bar");
62537
+ }
62538
+ return {
62539
+ displayText: this.getDisplayName(),
62540
+ modifierText: makeModifierText(modifiers)
62541
+ };
62542
+ }
62543
+ handleEditorAction(action, item) {
62544
+ if (action !== "toggle-pendulum") {
62545
+ return null;
62546
+ }
62547
+ const currentMode = getPaceDisplayMode(item);
62548
+ const nextMode = currentMode === "text" ? "pendulum" : "text";
62549
+ return {
62550
+ ...item,
62551
+ metadata: {
62552
+ ...item.metadata ?? {},
62553
+ display: nextMode
62554
+ }
62555
+ };
62556
+ }
62557
+ render(item, context, settings) {
62558
+ const displayMode = getPaceDisplayMode(item);
62559
+ if (context.isPreview) {
62560
+ if (displayMode === "pendulum") {
62561
+ const barDisplay = `${makePendulumBar(10)} D4/7 +10%`;
62562
+ return formatRawOrLabeledValue(item, "Pace: ", barDisplay);
62563
+ }
62564
+ return formatRawOrLabeledValue(item, "", "D4/7: On Pace");
62565
+ }
62566
+ const data = context.usageData;
62567
+ if (!data)
62568
+ return null;
62569
+ if (data.error)
62570
+ return getUsageErrorMessage(data.error);
62571
+ if (data.weeklyUsage === undefined)
62572
+ return null;
62573
+ const window2 = resolveWeeklyUsageWindow(data);
62574
+ if (!window2)
62575
+ return null;
62576
+ const actualPercent = Math.max(0, Math.min(100, data.weeklyUsage));
62577
+ const { delta, dayOfWeek, status } = computePace(actualPercent, window2.elapsedPercent);
62578
+ const mobile = (context.terminalWidth ?? 0) > 0 && (context.terminalWidth ?? 0) < MOBILE_THRESHOLD3;
62579
+ if (displayMode === "pendulum" && !mobile) {
62580
+ const sign = delta >= 0 ? "+" : "";
62581
+ const barDisplay = `${makePendulumBar(delta)} D${dayOfWeek}/7 ${sign}${Math.round(delta)}%`;
62582
+ return formatRawOrLabeledValue(item, "Pace: ", barDisplay);
62583
+ }
62584
+ return formatRawOrLabeledValue(item, "", `D${dayOfWeek}/7: ${status}`);
62585
+ }
62586
+ getCustomKeybinds() {
62587
+ return [
62588
+ { key: "p", label: "(p)endulum toggle", action: "toggle-pendulum" }
62589
+ ];
62590
+ }
62591
+ supportsRawValue() {
62592
+ return true;
62593
+ }
62594
+ supportsColors(item) {
62595
+ return true;
62596
+ }
62597
+ }
62598
+ var MOBILE_THRESHOLD3 = 80;
62599
+ var init_WeeklyPace = __esm(() => {
62600
+ init_usage();
62601
+ });
62602
+
62603
+ // src/widgets/OffPeak.ts
62604
+ function isOffPeak(now2) {
62605
+ const ms = now2.getTime();
62606
+ if (ms < PROMO_START_MS || ms >= PROMO_END_MS) {
62607
+ return null;
62608
+ }
62609
+ const dayOfWeek = now2.getUTCDay();
62610
+ const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
62611
+ if (isWeekend)
62612
+ return true;
62613
+ const utcHour = now2.getUTCHours();
62614
+ const isPeak = utcHour >= PEAK_START_UTC_HOUR && utcHour < PEAK_END_UTC_HOUR;
62615
+ return !isPeak;
62616
+ }
62617
+
62618
+ class OffPeakWidget {
62619
+ getDefaultColor() {
62620
+ return "green";
62621
+ }
62622
+ getDescription() {
62623
+ return "Shows peak / off-peak 2x status during the March 2026 Anthropic usage promotion";
62624
+ }
62625
+ getDisplayName() {
62626
+ return "Off Peak";
62627
+ }
62628
+ getCategory() {
62629
+ return "Usage";
62630
+ }
62631
+ getEditorDisplay(_item) {
62632
+ return { displayText: this.getDisplayName() };
62633
+ }
62634
+ render(item, context, _settings) {
62635
+ if (context.isPreview) {
62636
+ return item.rawValue ? "Off-peak 2x" : "Off peak: Off-peak 2x";
62637
+ }
62638
+ const result2 = isOffPeak(new Date);
62639
+ if (result2 === null)
62640
+ return null;
62641
+ const value = result2 ? "Off-peak 2x" : "Peak";
62642
+ const mobile = (context.terminalWidth ?? 0) > 0 && (context.terminalWidth ?? 0) < 80;
62643
+ if (item.rawValue || mobile)
62644
+ return value;
62645
+ return `Off peak: ${value}`;
62646
+ }
62647
+ supportsRawValue() {
62648
+ return true;
62649
+ }
62650
+ supportsColors(_item) {
62651
+ return true;
62652
+ }
62653
+ }
62654
+ var PROMO_START_MS, PROMO_END_MS, PEAK_START_UTC_HOUR = 12, PEAK_END_UTC_HOUR = 18;
62655
+ var init_OffPeak = __esm(() => {
62656
+ PROMO_START_MS = Date.UTC(2026, 2, 13, 7, 0, 0);
62657
+ PROMO_END_MS = Date.UTC(2026, 2, 29, 6, 59, 0);
62658
+ });
62659
+
62456
62660
  // src/widgets/index.ts
62457
62661
  var init_widgets = __esm(async () => {
62458
62662
  init_GitBranch();
@@ -62471,6 +62675,8 @@ var init_widgets = __esm(async () => {
62471
62675
  init_ThinkingEffort();
62472
62676
  init_Battery();
62473
62677
  init_VimMode();
62678
+ init_WeeklyPace();
62679
+ init_OffPeak();
62474
62680
  await __promiseAll([
62475
62681
  init_TokensInput(),
62476
62682
  init_TokensOutput(),
@@ -62530,7 +62736,9 @@ var init_widget_manifest = __esm(async () => {
62530
62736
  { type: "skills", create: () => new SkillsWidget },
62531
62737
  { type: "thinking-effort", create: () => new ThinkingEffortWidget },
62532
62738
  { type: "battery", create: () => new BatteryWidget },
62533
- { type: "vim-mode", create: () => new VimModeWidget }
62739
+ { type: "vim-mode", create: () => new VimModeWidget },
62740
+ { type: "weekly-pace", create: () => new WeeklyPaceWidget },
62741
+ { type: "off-peak", create: () => new OffPeakWidget }
62534
62742
  ];
62535
62743
  LAYOUT_WIDGET_MANIFEST = [
62536
62744
  {
@@ -68934,7 +69142,8 @@ var USAGE_WIDGET_TYPES = new Set([
68934
69142
  "weekly-usage",
68935
69143
  "block-timer",
68936
69144
  "reset-timer",
68937
- "weekly-reset-timer"
69145
+ "weekly-reset-timer",
69146
+ "weekly-pace"
68938
69147
  ]);
68939
69148
  function hasUsageDependentWidgets(lines) {
68940
69149
  return lines.some((line) => line.some((item) => USAGE_WIDGET_TYPES.has(item.type)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline-usage",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",