cc-cream 0.3.4 → 0.3.6

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/CHANGELOG.md CHANGED
@@ -6,6 +6,23 @@ All notable changes to cc-cream are documented here. Format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.6] — 2026-06-04
10
+
11
+ ### Changed
12
+ - **Plugin is now distributed via a lean catalogue repo (`bart-turczynski/claude-plugins`) instead of the full dev repo.** Previously the marketplace pointed at `bart-turczynski/cc-cream`, so every user who registered the marketplace got a full clone of the development repository — `features/`, `fixtures/`, `package-lock.json` (114 KB), `CHANGELOG.md` (31 KB), and the full git history — landing in `~/.claude/plugins/marketplaces/`. The marketplace source is now `bart-turczynski/claude-plugins`, a purpose-built catalogue containing only the plugin payload and `marketplace.json`. The marketplaces clone shrinks from ~1 MB to ~260 KB (irreducible floor: `.git/` + `.claude-plugin/`). Existing installs should remove and re-add the marketplace: `claude plugin marketplace remove bart-turczynski && claude plugin marketplace add bart-turczynski/claude-plugins`.
13
+
14
+ ### Infrastructure
15
+ - Added `.github/workflows/sync-catalogue.yml`: on each GitHub Release, syncs `plugin/` contents to `cc-cream/` in the `bart-turczynski/claude-plugins` catalogue repo so the distribution copy stays current without manual maintenance.
16
+
17
+ ## [0.3.5] — 2026-06-04
18
+
19
+ ### Fixed
20
+ - **`/cc-cream:setup --force` now actually replaces an existing statusLine.** The setup slash command shelled out to `install.js --plugin` but never forwarded `$ARGUMENTS`, so `--force` (and any other flag) was silently dropped — `--force` appeared to do nothing when another tool already owned the statusLine. `commands/setup.md` now passes `$ARGUMENTS` through, matching `uninstall.md`. `install.js` already honored `--force`; only the command wiring was broken.
21
+ - **Plugin install no longer balloons the cache to ~114 MB.** Claude Code's plugin installer runs `npm install` whenever it finds a `package.json` in the cached plugin tree, which pulled the entire devDependency tree (Biome, Cucumber, knip, c8, …) into `~/.claude/plugins/cache/cc-cream/`. The plugin payload now lives in a `plugin/` subdirectory and the marketplace `source` points to `"./plugin"`, so only `plugin/`'s contents (`src/`, `commands/`, `hooks/`, `.claude-plugin/plugin.json`) are copied to the cache — with no `package.json` there, the installer has nothing to install. `package.json`, dev configs, and `marketplace.json` stay at the repo root; npm `bin`/`files` now point at `plugin/src/`. No behavior change to the rendered bar.
22
+
23
+ ### Added
24
+ - **The `peak` segment now shows when the window closes and when the next one opens (CREAM-scwwzbxh).** Inside the faster-drain window it reads `peak until HH:MM` — the close time in your **local** timezone, not PT — so you can see how long the elevated drain lasts. In the hour before the window opens it counts down `peak in Nm`. The new `peak.lead` config key sets how many minutes ahead the countdown appears (default `60`). Previously the segment showed only the bare word `peak` while in-window and nothing otherwise.
25
+
9
26
  ## [0.3.4] — 2026-05-31
10
27
 
11
28
  ### Changed
package/README.md CHANGED
@@ -19,7 +19,7 @@ With all segments enabled:
19
19
 
20
20
  ```
21
21
  ctx:21% [43k] | cache:99% | write:2% | ttl:60 | effort:high | think:on | ∿ api:74% | ~$0.23
22
- 5h:13% ↺2h57m | ~3h12m | 7d:6% ↺Sat 21:00 | peak
22
+ 5h:13% ↺2h57m | ~3h12m | 7d:6% ↺Sat 21:00 | peak until 11:00
23
23
  Sonnet 4.6 | My project session
24
24
  ```
25
25
 
@@ -35,7 +35,7 @@ Sonnet 4.6 | My project session
35
35
 
36
36
  **Rate-limit budgets.** `5h` and `7d` show how much of your rolling usage is gone and when each window resets. `burn` adds a live projection based on your current pace — useful before committing to a long agent run.
37
37
 
38
- **Peak hours.** Anthropic's rate-limit drain accelerates Mon–Fri during Pacific business hours. The `peak` segment lights up when you're in that window so you can pace yourself accordingly. No other Claude Code status tool surfaces this.
38
+ **Peak hours.** Anthropic's rate-limit drain accelerates Mon–Fri during Pacific business hours. The `peak` segment tells you when the current window closes (`peak until 11:00`, in your local time) while you're in it, and counts down to the next one (`peak in 47m`) in the hour before it opens — so you can pace yourself or wait it out. No other Claude Code status tool surfaces this.
39
39
 
40
40
  **Context window.** `ctx` shows occupancy and input-token magnitude. On large-context models where "50% of window" still means 500k tokens, you can set a fixed-token ceiling instead — warnings fire at the same absolute count regardless of window size.
41
41
 
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "cc-cream",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "See cache health, context fill, token burn, rate limits, and peak hours in Claude Code CLI. The status line for tokenminning - cache rules everything around me - dolla, dolla bill, y'all.",
5
5
  "directories": {
6
6
  "doc": "docs"
7
7
  },
8
8
  "scripts": {
9
- "lint": "biome lint src/ hooks/",
9
+ "lint": "biome lint plugin/src/ plugin/hooks/",
10
10
  "knip": "knip",
11
- "validate": "command -v claude >/dev/null 2>&1 && claude plugin validate . || echo 'cc-cream: claude CLI not found — skipping plugin validation'",
11
+ "validate": "command -v claude >/dev/null 2>&1 && claude plugin validate plugin || echo 'cc-cream: claude CLI not found — skipping plugin validation'",
12
12
  "pretest": "npm run lint && npm run knip && npm run validate",
13
13
  "test": "cucumber-js",
14
14
  "test:manual": "cucumber-js --profile manual",
@@ -23,11 +23,11 @@
23
23
  "pre-push": "npm run coverage"
24
24
  },
25
25
  "bin": {
26
- "cc-cream": "src/cc-cream.js",
27
- "cc-cream-setup": "src/install.js"
26
+ "cc-cream": "plugin/src/cc-cream.js",
27
+ "cc-cream-setup": "plugin/src/install.js"
28
28
  },
29
29
  "files": [
30
- "src/",
30
+ "plugin/src/",
31
31
  "LICENSE",
32
32
  "README.md",
33
33
  "CHANGELOG.md"
@@ -48,6 +48,7 @@ const SEGMENT_FIELDS = {
48
48
  display: ctxDisplayOr,
49
49
  start: hourOr,
50
50
  end: hourOr,
51
+ lead: posOr,
51
52
  };
52
53
 
53
54
  function mergeConfig(parsed) {
@@ -19,10 +19,12 @@ export const DEFAULTS = {
19
19
  cost: { on: true, row: 1, order: 5 },
20
20
  '5h': { on: true, row: 2, order: 1, amber: 75, red: 90 },
21
21
  '7d': { on: true, row: 2, order: 2, amber: 75, red: 90 },
22
- // peak: amber "peak" word during Anthropic's faster-drain window (PRDv2 §2).
23
- // start/end are Pacific-time hours (0–23, exclusive end); weekday (Mon–Fri)
24
- // and the America/Los_Angeles timezone are hardcoded policy facts, not config.
25
- peak: { on: true, row: 2, order: 3, start: 5, end: 11 },
22
+ // peak: amber peak-window indicator (PRDv2 §2). start/end are Pacific-time
23
+ // hours (0–23, exclusive end); weekday (Mon–Fri) and the America/Los_Angeles
24
+ // timezone are hardcoded policy facts, not config. Inside the window it reads
25
+ // "peak until HH:MM" (local close time); the `lead` minutes before it opens it
26
+ // counts down "peak in Nm".
27
+ peak: { on: true, row: 2, order: 3, start: 5, end: 11, lead: 60 },
26
28
  burn: { on: true, row: 2, order: 1.5 },
27
29
  effort: { on: false, row: 1, order: 6 },
28
30
  thinking: { on: false, row: 1, order: 7 },
@@ -1,4 +1,4 @@
1
- import { band, countdown, flipPct, fmtNum, isNum, isPeak, numOr, pad2 } from './utils.js';
1
+ import { band, countdown, flipPct, fmtNum, isNum, localHM, numOr, pad2, peakStatus } from './utils.js';
2
2
  import { hasWindow } from './ttl.js';
3
3
 
4
4
  function magnitudeTokens(cw) {
@@ -125,8 +125,12 @@ function segCacheWrite(data) {
125
125
  function segPeak(data, cfg, now, tz) {
126
126
  // peak rides the account-budget row, so it shows only when that row has windows.
127
127
  if (!hasWindow(data?.rate_limits)) return null;
128
- if (!isPeak(now, cfg, tz)) return null;
129
- return { text: 'peak', color: 'amber' };
128
+ const st = peakStatus(now, cfg, tz);
129
+ if (!st) return null;
130
+ const text = st.state === 'approaching'
131
+ ? `peak in ${st.startsInMin}m` // counting down to the window opening
132
+ : `peak until ${localHM(st.endsAtMs)}`; // inside it: local clock time it closes
133
+ return { text, color: 'amber' };
130
134
  }
131
135
 
132
136
  function segBurn(fiveHour, prev, now) {
@@ -75,24 +75,48 @@ export const flipPct = (consumedShown, cfg) => (
75
75
  cfg.percentage === 'remaining' ? 100 - consumedShown : consumedShown
76
76
  );
77
77
 
78
- // True during Anthropic's faster-drain "peak" window (PRDv2 §2): a weekday
79
- // (Mon–Fri) within [start, end) Pacific-time hours.
80
- export function isPeak(now, cfg, tz = 'America/Los_Angeles') {
78
+ // Resolve the "peak" window (PRDv2 §2) relative to `now`. Anthropic drains the
79
+ // 5h budget faster on weekdays (Mon–Fri) within [start, end) Pacific-time hours.
80
+ // Returns one of:
81
+ // { state: 'in', endsAtMs } — inside [start, end); endsAtMs is the
82
+ // window close as an epoch (format local)
83
+ // { state: 'approaching', startsInMin } — inside [start-lead, start)
84
+ // null — off-window, weekend, or Intl failure
85
+ // `lead` (minutes, default 60) is how early the approaching countdown appears.
86
+ export function peakStatus(now, cfg, tz = 'America/Los_Angeles') {
81
87
  const s = cfg?.segments?.peak ?? {};
82
88
  const start = isNum(s.start) ? s.start : 5;
83
89
  const end = isNum(s.end) ? s.end : 11;
90
+ const lead = isNum(s.lead) && s.lead > 0 ? s.lead : 60;
84
91
  try {
85
92
  const parts = new Intl.DateTimeFormat('en-US', {
86
- timeZone: tz, hour: 'numeric', hour12: false, weekday: 'short',
93
+ timeZone: tz, hour: 'numeric', minute: 'numeric', hour12: false, weekday: 'short',
87
94
  }).formatToParts(new Date(now));
88
95
  const p = Object.fromEntries(parts.map((x) => [x.type, x.value]));
89
- const hour = Number(p.hour) % 24; // some ICU builds emit "24" for midnight
90
- return !['Sat', 'Sun'].includes(p.weekday) && hour >= start && hour < end;
96
+ if (['Sat', 'Sun'].includes(p.weekday)) return null;
97
+ const ptMin = (Number(p.hour) % 24) * 60 + Number(p.minute); // some ICU builds emit "24" for midnight
98
+ const startMin = start * 60;
99
+ const endMin = end * 60;
100
+ if (ptMin >= startMin && ptMin < endMin) {
101
+ return { state: 'in', endsAtMs: now + (endMin - ptMin) * 60000 };
102
+ }
103
+ if (ptMin >= startMin - lead && ptMin < startMin) {
104
+ return { state: 'approaching', startsInMin: startMin - ptMin };
105
+ }
106
+ return null;
91
107
  } catch {
92
- return false;
108
+ return null;
93
109
  }
94
110
  }
95
111
 
112
+ // Back-compat predicate: true only inside the window itself (not while approaching).
113
+ export const isPeak = (now, cfg, tz = 'America/Los_Angeles') => peakStatus(now, cfg, tz)?.state === 'in';
114
+
115
+ // Wall-clock HH:MM in the host's local timezone (TZ-driven, like countdown's day branch).
116
+ export const localHM = (ms) => new Date(ms).toLocaleTimeString(undefined, {
117
+ hour: '2-digit', minute: '2-digit', hour12: false,
118
+ });
119
+
96
120
  // resets_at - now, on the §4.4 format ladder: >=1d -> "Fri 23:45", >=1h -> HhMMm, else MMm.
97
121
  export function countdown(resetsAt, now) {
98
122
  const t = toEpochMs(resetsAt);
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes