clauddy 1.14.0 → 1.15.0

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/auth.js CHANGED
@@ -152,6 +152,32 @@ function win(o) {
152
152
  : null
153
153
  }
154
154
 
155
+ // Weekly limits scoped to one model (e.g. "Fable" on Max) live in the `limits`
156
+ // array. The older seven_day_<model> windows are kept as a fallback for plans
157
+ // that still report them that way.
158
+ function scopedWeeks(j) {
159
+ const out = []
160
+ for (const l of Array.isArray(j.limits) ? j.limits : []) {
161
+ if (l?.kind !== 'weekly_scoped' || typeof l.percent !== 'number') continue
162
+ const label = l.scope?.model?.display_name
163
+ if (!label) continue
164
+ out.push({
165
+ label,
166
+ pct: l.percent,
167
+ resetMs: l.resets_at ? Date.parse(l.resets_at) - Date.now() : null,
168
+ })
169
+ }
170
+ if (out.length) return out
171
+ for (const [key, label] of [
172
+ ['seven_day_opus', 'Opus'],
173
+ ['seven_day_sonnet', 'Sonnet'],
174
+ ]) {
175
+ const w = win(j[key])
176
+ if (w) out.push({ label, ...w })
177
+ }
178
+ return out
179
+ }
180
+
155
181
  // Step 3: fetch the authoritative usage
156
182
  async function fetchUsage() {
157
183
  const token = await validToken()
@@ -173,8 +199,7 @@ async function fetchUsage() {
173
199
  return {
174
200
  session: win(j.five_hour) || { pct: 0, resetMs: null },
175
201
  week: win(j.seven_day) || { pct: 0, resetMs: null },
176
- sonnet: win(j.seven_day_sonnet),
177
- opus: win(j.seven_day_opus),
202
+ scoped: scopedWeeks(j),
178
203
  }
179
204
  }
180
205
 
package/main.js CHANGED
@@ -95,12 +95,22 @@ function loadConfig() {
95
95
  // native notification when usage crosses a threshold
96
96
  const armed = new Set()
97
97
  function checkAlerts(config, d) {
98
- if (!config.alerts || !Notification.isSupported()) return
99
- const ths = config.alertThresholds || [80, 95]
100
- const scopes = [
98
+ alertScopes(config, [
101
99
  ['session', d.session.pct],
102
100
  ['weekly usage', d.week.pct],
103
- ]
101
+ ])
102
+ }
103
+ // per-model weekly limits only exist on the account side, so they're checked
104
+ // off the OAuth poll rather than the local tick
105
+ function checkScopedAlerts(config, u) {
106
+ alertScopes(
107
+ config,
108
+ (u?.scoped || []).map((s) => [`${s.label} weekly usage`, s.pct]),
109
+ )
110
+ }
111
+ function alertScopes(config, scopes) {
112
+ if (!config.alerts || !Notification.isSupported()) return
113
+ const ths = config.alertThresholds || [80, 95]
104
114
  for (const [name, pct] of scopes) {
105
115
  for (const t of ths) {
106
116
  const key = `${name}:${t}`
@@ -361,6 +371,7 @@ function updateTray() {
361
371
  function pushRealUsage(u) {
362
372
  sessionPct = u?.session ? u.session.pct : null
363
373
  updateTray()
374
+ checkScopedAlerts(config, u)
364
375
  if (win && !win.isDestroyed()) win.webContents.send('real-usage', u)
365
376
  }
366
377
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clauddy",
3
3
  "desktopName": "clauddy.desktop",
4
- "version": "1.14.0",
4
+ "version": "1.15.0",
5
5
  "description": "A cute desktop pet that tracks your Claude Code usage",
6
6
  "main": "main.js",
7
7
  "bin": {
@@ -253,6 +253,8 @@
253
253
  <div class="track"><div class="fill week" id="week-fill"></div></div>
254
254
  <div class="sub" id="week-sub">—</div>
255
255
  </div>
256
+ <!-- per-model weekly limits (e.g. Fable), one meter each -->
257
+ <div id="scoped-meters"></div>
256
258
  </div>
257
259
  <button id="limits-connect">
258
260
  <svg class="lc-ico" viewBox="0 0 24 24" aria-hidden="true">
package/renderer/pet.js CHANGED
@@ -404,6 +404,27 @@ function fitNames(box) {
404
404
  box.style.setProperty('--name-w', `${w}px`)
405
405
  }
406
406
 
407
+ // per-model weekly limits (e.g. "Fable" on Max) — one meter each, in the same
408
+ // shape as the all-models one. The token count pairs the limit with the local
409
+ // log totals for that model family (a "Fable" limit covers every Fable row).
410
+ function renderScoped(list, byModel) {
411
+ const box = el('scoped-meters')
412
+ box.innerHTML = ''
413
+ for (const s of list) {
414
+ const key = s.label.toLowerCase()
415
+ const tokens = byModel
416
+ .filter((m) => m.label.toLowerCase().startsWith(key))
417
+ .reduce((n, m) => n + m.tokens, 0)
418
+ const m = document.createElement('div')
419
+ m.className = 'meter'
420
+ m.innerHTML =
421
+ `<div class="meter-top"><span>weekly · ${esc(key)}</span><span>${Math.round(s.pct)}%</span></div>` +
422
+ `<div class="track"><div class="fill week${s.pct >= 80 ? ' high' : ''}" style="width:${s.pct}%"></div></div>` +
423
+ `<div class="sub">${s.resetMs != null ? `resets in ${fmtReset(s.resetMs)} · ` : ''}${fmtTokens(tokens)} tokens</div>`
424
+ box.appendChild(m)
425
+ }
426
+ }
427
+
407
428
  // by model (7 days)
408
429
  function renderModels(list) {
409
430
  renderBars('bymodel-list', list, 4)
@@ -571,6 +592,7 @@ function render(d) {
571
592
  ? `resets in ${fmtReset(wkReset)} · ${fmtTokens(d.week.tokens)} tokens`
572
593
  : `${fmtTokens(d.week.tokens)} tokens · last 7 days`
573
594
 
595
+ renderScoped(liveOn ? realUsage.scoped || [] : [], d.byModel || [])
574
596
  renderModels(d.byModel || [])
575
597
  renderProjects(d.byProject || [])
576
598
  renderHeat(d.days30 || [])
@@ -1679,7 +1679,8 @@ body.state-tired #status-text {
1679
1679
  }
1680
1680
 
1681
1681
  /* meters */
1682
- .meter + .meter {
1682
+ .meter + .meter,
1683
+ #scoped-meters .meter {
1683
1684
  margin-top: 9px;
1684
1685
  }
1685
1686
  .meter-top {