clauddy 1.13.1 → 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.1",
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
@@ -596,8 +596,12 @@ let lastW = 0
596
596
  function fitSize() {
597
597
  requestAnimationFrame(() => {
598
598
  const collapsed = document.body.classList.contains('collapsed')
599
- const w = collapsed ? 140 : 276
600
- 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
601
605
  if (Math.abs(h - lastH) > 2 || w !== lastW) {
602
606
  lastH = h
603
607
  lastW = w
@@ -606,6 +610,12 @@ function fitSize() {
606
610
  })
607
611
  }
608
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
+
609
619
  let currentConfig = {}
610
620
  let realUsage = null
611
621
  let debugState = null
@@ -624,6 +634,8 @@ window.api.onError((msg) => {
624
634
  window.api.onConfig((cfg) => {
625
635
  currentConfig = cfg || {}
626
636
  document.body.classList.toggle('is-menubar', currentConfig.mode === 'menubar')
637
+ applyZoom(currentConfig.zoom)
638
+ fitSize()
627
639
  })
628
640
  window.api.onRealUsage((u) => {
629
641
  realUsage = u || null
@@ -809,6 +821,7 @@ function snapshotSettings() {
809
821
  t1: el('set-t1').value,
810
822
  t2: el('set-t2').value,
811
823
  fire: el('set-fire').value,
824
+ zoom: el('set-zoom').value,
812
825
  })
813
826
  }
814
827
  function refreshSaveDirty() {
@@ -827,6 +840,7 @@ function populateSettings() {
827
840
  el('set-t1').value = th[0] != null ? th[0] : 80
828
841
  el('set-t2').value = th[1] != null ? th[1] : 95
829
842
  el('set-fire').value = c.fireThreshold != null ? c.fireThreshold : 90
843
+ el('set-zoom').value = c.zoom != null ? c.zoom : 100
830
844
  clearSaveDirty() // fields now match the saved config
831
845
  }
832
846
  function openSettings() {
@@ -850,7 +864,7 @@ for (const b of document.querySelectorAll('.num-btn')) {
850
864
  })
851
865
  }
852
866
  // light up Save whenever an editable field changes
853
- 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']) {
854
868
  el(id).addEventListener('input', refreshSaveDirty)
855
869
  el(id).addEventListener('change', refreshSaveDirty)
856
870
  }
@@ -869,6 +883,9 @@ el('set-cancel').addEventListener('click', () => {
869
883
  el('set-save').addEventListener('click', () => {
870
884
  const num = (id) => parseFloat(el(id).value)
871
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
872
889
  window.api.saveConfig({
873
890
  mode: selectedMode,
874
891
  alerts: el('set-alerts').checked,
@@ -876,6 +893,7 @@ el('set-save').addEventListener('click', () => {
876
893
  .filter((n) => n >= 1 && n <= 100)
877
894
  .sort((a, b) => a - b),
878
895
  fireThreshold: fire >= 1 && fire <= 99 ? fire : 90,
896
+ zoom,
879
897
  })
880
898
  clearSaveDirty()
881
899
  document.body.classList.remove('settings-open')