clauddy 1.13.0 → 1.14.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/README.md CHANGED
@@ -4,7 +4,7 @@ A cute pixel-art desktop pet for macOS that tracks your Claude Code usage — mi
4
4
 
5
5
  <p align="center">
6
6
 
7
- https://github.com/user-attachments/assets/76d87b6f-2876-4000-b6db-13ba2207ae31
7
+ https://github.com/user-attachments/assets/dbe00d9a-b49c-48ea-941c-76c517dec358
8
8
 
9
9
  <em>A little terracotta creature that lives in the corner of your screen, eats your tokens, and naps when you're idle.</em>
10
10
  </p>
@@ -142,6 +142,18 @@ chmod +x Clauddy-*.AppImage
142
142
  ./Clauddy-*.AppImage
143
143
  ```
144
144
 
145
+ Want the pet icon to show up in your app menu / taskbar too, instead of a generic icon? Running the AppImage directly doesn't register it anywhere — GNOME (and most Wayland desktops) only pick up an app's icon from an installed `.desktop` entry. One-time setup, right next to the AppImage:
146
+
147
+ ```bash
148
+ ./Clauddy-*.AppImage --appimage-extract clauddy.desktop >/dev/null
149
+ ./Clauddy-*.AppImage --appimage-extract usr/share/icons/hicolor/512x512/apps/clauddy.png >/dev/null
150
+ mkdir -p ~/.local/share/applications ~/.local/share/icons/hicolor/512x512/apps
151
+ cp squashfs-root/usr/share/icons/hicolor/512x512/apps/clauddy.png ~/.local/share/icons/hicolor/512x512/apps/clauddy.png
152
+ sed "s|^Exec=.*|Exec=$(readlink -f Clauddy-*.AppImage) --no-sandbox %U|" squashfs-root/clauddy.desktop > ~/.local/share/applications/clauddy.desktop
153
+ rm -rf squashfs-root
154
+ update-desktop-database ~/.local/share/applications 2>/dev/null
155
+ ```
156
+
145
157
  > The system tray icon needs an indicator extension on vanilla GNOME (e.g. "AppIndicator and KStatusNotifier Item Support") — it works out of the box on Cinnamon, KDE, and XFCE. Autostart-at-login is wired up via an XDG `.desktop` entry in `~/.config/autostart/`.
146
158
 
147
159
  > The app keeps its data in `~/.claude-usage-monitor`, regardless of platform or how you run it.
package/config.json CHANGED
@@ -4,6 +4,7 @@
4
4
  "alerts": true,
5
5
  "alertThresholds": [80, 95],
6
6
  "fireThreshold": 90,
7
+ "zoom": 100,
7
8
  "pollIntervalMs": 4000,
8
9
  "activeThresholdMs": 20000,
9
10
  "sleepThresholdMs": 300000
package/main.js CHANGED
@@ -65,6 +65,7 @@ function publicConfig(c) {
65
65
  alerts: c.alerts,
66
66
  alertThresholds: c.alertThresholds,
67
67
  fireThreshold: c.fireThreshold,
68
+ zoom: c.zoom,
68
69
  }
69
70
  }
70
71
 
@@ -78,6 +79,7 @@ function loadConfig() {
78
79
  alerts: true,
79
80
  alertThresholds: [80, 95],
80
81
  fireThreshold: 90, // session % at which the pet catches fire (tired still fixed at 100)
82
+ zoom: 100, // widget scale %, 100-200
81
83
  pollIntervalMs: 4000,
82
84
  activeThresholdMs: 20000,
83
85
  sleepThresholdMs: 300000,
@@ -137,6 +139,7 @@ function createWindow() {
137
139
  skipTaskbar: true,
138
140
  hasShadow: false,
139
141
  fullscreenable: false,
142
+ icon: path.join(__dirname, 'build', 'icon.png'),
140
143
  webPreferences: {
141
144
  preload: path.join(__dirname, 'preload.js'),
142
145
  contextIsolation: true,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clauddy",
3
3
  "desktopName": "clauddy.desktop",
4
- "version": "1.13.0",
4
+ "version": "1.14.0",
5
5
  "description": "A cute desktop pet that tracks your Claude Code usage",
6
6
  "main": "main.js",
7
7
  "bin": {
@@ -328,6 +328,22 @@
328
328
  </span>
329
329
  </div>
330
330
 
331
+ <div class="set-section">
332
+ <div class="set-sec-title">Zoom</div>
333
+ <div class="set-thresholds">
334
+ <span class="thr">
335
+ <span class="thr-cap">size</span>
336
+ <span class="num">
337
+ <input id="set-zoom" type="number" min="100" max="200" step="25" />
338
+ <span class="num-spin">
339
+ <button type="button" class="num-btn" data-for="set-zoom" data-step="25">▲</button>
340
+ <button type="button" class="num-btn" data-for="set-zoom" data-step="-25">▼</button>
341
+ </span>
342
+ </span>
343
+ </span>
344
+ </div>
345
+ </div>
346
+
331
347
  <div class="set-section">
332
348
  <div class="set-sec-title">
333
349
  Alerts
package/renderer/pet.js CHANGED
@@ -392,12 +392,15 @@ function renderBars(boxId, list, limit) {
392
392
  function fitNames(box) {
393
393
  const names = [...box.querySelectorAll('.mname')]
394
394
  // a collapsed section measures zero — it re-fits when it opens
395
- if (!names.length || !box.getBoundingClientRect().width) return
395
+ if (!names.length || !box.offsetWidth) return
396
396
  box.style.setProperty('--name-w', 'auto')
397
+ // offsetWidth, not getBoundingClientRect: the width we hand back is a layout px,
398
+ // so it has to be measured in layout px too. A CSS transform on an ancestor
399
+ // (the video stage scales the card) inflates the rect and squeezes the bars away.
397
400
  let widest = 0
398
- for (const n of names) widest = Math.max(widest, n.getBoundingClientRect().width)
399
- const room = box.getBoundingClientRect().width - BAR_MIN
400
- const w = Math.max(NAME_MIN, Math.min(Math.ceil(widest) + 1, room))
401
+ for (const n of names) widest = Math.max(widest, n.offsetWidth)
402
+ const room = box.offsetWidth - BAR_MIN
403
+ const w = Math.max(NAME_MIN, Math.min(widest + 2, room))
401
404
  box.style.setProperty('--name-w', `${w}px`)
402
405
  }
403
406
 
@@ -593,8 +596,12 @@ let lastW = 0
593
596
  function fitSize() {
594
597
  requestAnimationFrame(() => {
595
598
  const collapsed = document.body.classList.contains('collapsed')
596
- const w = collapsed ? 140 : 276
597
- const h = el('card').offsetHeight + 24 // 12px margin top + bottom
599
+ const zoom = Number.parseFloat(document.body.style.zoom) || 1
600
+ const w = (collapsed ? 140 : 276) * zoom
601
+ // offsetHeight never reflects a CSS zoom applied to an ancestor (confirmed
602
+ // empirically against this Electron build) — so measure the unzoomed content
603
+ // height, then scale the whole thing (content + margin) ourselves
604
+ const h = (el('card').offsetHeight + 24) * zoom // 12px margin top + bottom
598
605
  if (Math.abs(h - lastH) > 2 || w !== lastW) {
599
606
  lastH = h
600
607
  lastW = w
@@ -603,6 +610,12 @@ function fitSize() {
603
610
  })
604
611
  }
605
612
 
613
+ // widget scale, applied as a CSS zoom (not transform) so offsetWidth/Height
614
+ // keep reflecting it — see fitNames()'s comment on why transform can't be used here
615
+ function applyZoom(z) {
616
+ document.body.style.zoom = (z || 100) / 100
617
+ }
618
+
606
619
  let currentConfig = {}
607
620
  let realUsage = null
608
621
  let debugState = null
@@ -621,6 +634,8 @@ window.api.onError((msg) => {
621
634
  window.api.onConfig((cfg) => {
622
635
  currentConfig = cfg || {}
623
636
  document.body.classList.toggle('is-menubar', currentConfig.mode === 'menubar')
637
+ applyZoom(currentConfig.zoom)
638
+ fitSize()
624
639
  })
625
640
  window.api.onRealUsage((u) => {
626
641
  realUsage = u || null
@@ -806,6 +821,7 @@ function snapshotSettings() {
806
821
  t1: el('set-t1').value,
807
822
  t2: el('set-t2').value,
808
823
  fire: el('set-fire').value,
824
+ zoom: el('set-zoom').value,
809
825
  })
810
826
  }
811
827
  function refreshSaveDirty() {
@@ -824,6 +840,7 @@ function populateSettings() {
824
840
  el('set-t1').value = th[0] != null ? th[0] : 80
825
841
  el('set-t2').value = th[1] != null ? th[1] : 95
826
842
  el('set-fire').value = c.fireThreshold != null ? c.fireThreshold : 90
843
+ el('set-zoom').value = c.zoom != null ? c.zoom : 100
827
844
  clearSaveDirty() // fields now match the saved config
828
845
  }
829
846
  function openSettings() {
@@ -847,7 +864,7 @@ for (const b of document.querySelectorAll('.num-btn')) {
847
864
  })
848
865
  }
849
866
  // light up Save whenever an editable field changes
850
- for (const id of ['set-alerts', 'set-t1', 'set-t2', 'set-fire']) {
867
+ for (const id of ['set-alerts', 'set-t1', 'set-t2', 'set-fire', 'set-zoom']) {
851
868
  el(id).addEventListener('input', refreshSaveDirty)
852
869
  el(id).addEventListener('change', refreshSaveDirty)
853
870
  }
@@ -866,6 +883,9 @@ el('set-cancel').addEventListener('click', () => {
866
883
  el('set-save').addEventListener('click', () => {
867
884
  const num = (id) => parseFloat(el(id).value)
868
885
  const fire = num('set-fire')
886
+ const zoomRaw = num('set-zoom')
887
+ const zoom = zoomRaw >= 100 && zoomRaw <= 200 ? Math.round(zoomRaw) : 100
888
+ applyZoom(zoom) // apply immediately so the fitSize() below measures the new size
869
889
  window.api.saveConfig({
870
890
  mode: selectedMode,
871
891
  alerts: el('set-alerts').checked,
@@ -873,6 +893,7 @@ el('set-save').addEventListener('click', () => {
873
893
  .filter((n) => n >= 1 && n <= 100)
874
894
  .sort((a, b) => a - b),
875
895
  fireThreshold: fire >= 1 && fire <= 99 ? fire : 90,
896
+ zoom,
876
897
  })
877
898
  clearSaveDirty()
878
899
  document.body.classList.remove('settings-open')