claude-pet 2.0.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.

Potentially problematic release.


This version of claude-pet might be problematic. Click here for more details.

Files changed (133) hide show
  1. package/.claude/commands/feed.md +28 -0
  2. package/.claude/commands/name.md +28 -0
  3. package/.claude/commands/pet.md +29 -0
  4. package/.claude/commands/play.md +29 -0
  5. package/.claude/settings.local.json +41 -0
  6. package/.github/workflows/AGENTS.md +60 -0
  7. package/.github/workflows/build.yml +87 -0
  8. package/AGENTS.md +66 -0
  9. package/LICENSE +15 -0
  10. package/README.md +292 -0
  11. package/bin/claude-pet.js +42 -0
  12. package/build/AGENTS.md +50 -0
  13. package/build/dmg-background.png +0 -0
  14. package/build/entitlements.mac.plist +14 -0
  15. package/build/icon.ico +0 -0
  16. package/build/icon.png +0 -0
  17. package/build/installerHeader.bmp +0 -0
  18. package/build/installerSidebar.bmp +0 -0
  19. package/build/tray-icon.png +0 -0
  20. package/dist/main/core/badge-manager.js +49 -0
  21. package/dist/main/core/badge-registry.js +72 -0
  22. package/dist/main/core/badge-triggers.js +45 -0
  23. package/dist/main/core/contextual-messages.js +372 -0
  24. package/dist/main/core/messages.js +440 -0
  25. package/dist/main/core/mood-engine.js +145 -0
  26. package/dist/main/core/pet-messages.js +612 -0
  27. package/dist/main/core/pet-state-engine.js +232 -0
  28. package/dist/main/core/quote-collection.js +60 -0
  29. package/dist/main/core/quote-registry.js +175 -0
  30. package/dist/main/core/quote-triggers.js +62 -0
  31. package/dist/main/core/usage-tracker.js +625 -0
  32. package/dist/main/main/auto-launch.js +39 -0
  33. package/dist/main/main/auto-updater.js +98 -0
  34. package/dist/main/main/event-watcher.js +174 -0
  35. package/dist/main/main/ipc-handlers.js +89 -0
  36. package/dist/main/main/main.js +422 -0
  37. package/dist/main/main/preload.js +93 -0
  38. package/dist/main/main/settings-window.js +49 -0
  39. package/dist/main/main/share-card.js +139 -0
  40. package/dist/main/main/skin-manager.js +118 -0
  41. package/dist/main/main/tray.js +88 -0
  42. package/dist/main/shared/i18n.js +392 -0
  43. package/dist/main/shared/types.js +25 -0
  44. package/dist/main/shared/utils.js +9 -0
  45. package/dist/renderer/assets/claude-pet.png +0 -0
  46. package/dist/renderer/assets/index-BMnMEuOf.js +9 -0
  47. package/dist/renderer/assets/index-qzlrlqpX.css +1 -0
  48. package/dist/renderer/index.html +30 -0
  49. package/dist/renderer/share-card-template/card.html +148 -0
  50. package/docs/AGENTS.md +42 -0
  51. package/docs/images/angry.png +0 -0
  52. package/docs/images/character.webp +0 -0
  53. package/docs/images/claude-mama.png +0 -0
  54. package/docs/images/happy.png +0 -0
  55. package/docs/images/proud.png +0 -0
  56. package/docs/images/share-card-example.png +0 -0
  57. package/docs/images/worried_1.png +0 -0
  58. package/docs/images/worried_2.png +0 -0
  59. package/docs/spritesheet-bugs.md +240 -0
  60. package/docs/superpowers/plans/2026-03-10-compact-widget.md +888 -0
  61. package/docs/superpowers/plans/2026-03-10-viral-features.md +1874 -0
  62. package/docs/superpowers/plans/2026-03-14-update-ux.md +362 -0
  63. package/docs/superpowers/plans/2026-03-14-v1.1-features.md +2139 -0
  64. package/docs/superpowers/specs/2026-03-10-compact-widget-design.md +150 -0
  65. package/docs/superpowers/specs/2026-03-10-viral-features-design.md +217 -0
  66. package/docs/superpowers/specs/2026-03-14-streak-calendar-design.md +26 -0
  67. package/docs/superpowers/specs/2026-03-14-update-ux-design.md +172 -0
  68. package/docs/superpowers/specs/2026-03-14-v1.1-features-design.md +342 -0
  69. package/electron-builder.yml +75 -0
  70. package/package.json +48 -0
  71. package/scripts/AGENTS.md +60 -0
  72. package/scripts/install.ps1 +47 -0
  73. package/scripts/install.sh +98 -0
  74. package/scripts/make-icon.js +119 -0
  75. package/scripts/notarize.js +18 -0
  76. package/src/AGENTS.md +47 -0
  77. package/src/core/AGENTS.md +58 -0
  78. package/src/core/__tests__/AGENTS.md +60 -0
  79. package/src/core/__tests__/badge-triggers.test.ts +83 -0
  80. package/src/core/__tests__/contextual-messages.test.ts +87 -0
  81. package/src/core/__tests__/pet-state-engine.test.ts +350 -0
  82. package/src/core/__tests__/quote-collection.test.ts +62 -0
  83. package/src/core/__tests__/quote-triggers.test.ts +110 -0
  84. package/src/core/badge-manager.ts +50 -0
  85. package/src/core/badge-registry.ts +71 -0
  86. package/src/core/badge-triggers.ts +41 -0
  87. package/src/core/contextual-messages.ts +381 -0
  88. package/src/core/pet-messages.ts +615 -0
  89. package/src/core/pet-state-engine.ts +272 -0
  90. package/src/core/quote-collection.ts +63 -0
  91. package/src/core/quote-registry.ts +181 -0
  92. package/src/core/quote-triggers.ts +64 -0
  93. package/src/core/usage-tracker.ts +680 -0
  94. package/src/main/AGENTS.md +70 -0
  95. package/src/main/auto-launch.ts +38 -0
  96. package/src/main/auto-updater.ts +106 -0
  97. package/src/main/event-watcher.ts +159 -0
  98. package/src/main/ipc-handlers.ts +107 -0
  99. package/src/main/main.ts +425 -0
  100. package/src/main/preload.ts +111 -0
  101. package/src/main/settings-window.ts +50 -0
  102. package/src/main/share-card.ts +153 -0
  103. package/src/main/skin-manager.ts +119 -0
  104. package/src/main/tray.ts +94 -0
  105. package/src/renderer/AGENTS.md +62 -0
  106. package/src/renderer/App.tsx +270 -0
  107. package/src/renderer/assets/claude-mama.png +0 -0
  108. package/src/renderer/assets/claude-pet.png +0 -0
  109. package/src/renderer/components/AGENTS.md +50 -0
  110. package/src/renderer/components/Character.tsx +327 -0
  111. package/src/renderer/components/SpeechBubble.tsx +182 -0
  112. package/src/renderer/components/UsageIndicator.tsx +268 -0
  113. package/src/renderer/electron.d.ts +34 -0
  114. package/src/renderer/hooks/AGENTS.md +55 -0
  115. package/src/renderer/hooks/usePetState.ts +59 -0
  116. package/src/renderer/hooks/useWidgetMode.ts +18 -0
  117. package/src/renderer/index.html +29 -0
  118. package/src/renderer/main.tsx +13 -0
  119. package/src/renderer/pages/AGENTS.md +53 -0
  120. package/src/renderer/pages/Collection.tsx +252 -0
  121. package/src/renderer/pages/Settings.tsx +815 -0
  122. package/src/renderer/share-card-template/card.html +148 -0
  123. package/src/renderer/styles/AGENTS.md +50 -0
  124. package/src/renderer/styles/styles.css +166 -0
  125. package/src/shared/AGENTS.md +48 -0
  126. package/src/shared/i18n.ts +395 -0
  127. package/src/shared/types.ts +163 -0
  128. package/src/shared/utils.ts +6 -0
  129. package/tsconfig.json +16 -0
  130. package/tsconfig.main.json +12 -0
  131. package/tsconfig.renderer.json +12 -0
  132. package/vite.config.ts +47 -0
  133. package/vitest.config.ts +9 -0
package/README.md ADDED
@@ -0,0 +1,292 @@
1
+ <div align="center">
2
+
3
+ # 👩‍👦 Claude Mama
4
+
5
+ **Your API quota is rotting. Mom is disappointed.**
6
+
7
+ A desktop mascot that guilt-trips you into using your Claude Code tokens — just like a real Korean mom.
8
+
9
+ [![Build](https://github.com/scm1400/claude-mama/actions/workflows/build.yml/badge.svg)](https://github.com/scm1400/claude-mama/actions/workflows/build.yml)
10
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
11
+ [![Electron](https://img.shields.io/badge/Electron-40-47848F?logo=electron)](https://www.electronjs.org/)
12
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
13
+
14
+ <br/>
15
+
16
+ <img src="docs/images/claude-mama.png" width="100%" alt="Claude Mama character" />
17
+
18
+ <br/>
19
+
20
+ | Angry | Worried | Happy | Proud |
21
+ |:---:|:---:|:---:|:---:|
22
+ | 😡 "You haven't used any today?!" | 😟 "Everyone else is using theirs..." | 😊 "That's my kid!" | 🥹 "Mom's buying chicken tonight~" |
23
+ | < 15% usage | 15–50% usage | 50–85% usage | 85%+ usage |
24
+
25
+ </div>
26
+
27
+ ---
28
+
29
+ ## What is this?
30
+
31
+ Claude Mama is a tiny desktop widget that monitors your [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) API usage and reacts with the emotional range of a Korean mother who just found out you skipped dinner.
32
+
33
+ - **Using too little?** She's angry. She didn't raise you to waste a perfectly good API quota.
34
+ - **Using a moderate amount?** She's worried. The neighbors' kids are using more.
35
+ - **Using a healthy amount?** She's happy. Finally, some return on investment.
36
+ - **Maxing it out?** She's proud. Tears are streaming. Chicken is being ordered.
37
+
38
+ > *"Other moms worry their kids use too much computer. Claude Mama worries you don't use enough."*
39
+
40
+ ## Features
41
+
42
+ - **Real-time usage tracking** — Pulls 7-day and 5-hour utilization from the Anthropic OAuth API, with smart JSONL session fallback when rate limited
43
+ - **Pixel art character** — A pixel-art mama with curler hair and 6 mood expressions (angry, worried, happy, proud, confused, sleeping)
44
+ - **Guilt-powered messages** — Randomized mom-style messages that rotate every 2 minutes
45
+ - **Contextual messages** — Situation-aware messages for weekends, unused streaks, usage spikes, and reset warnings (200+ new localized messages across 4 languages)
46
+ - **5-hour burnout warning** — "Take a break~ You're almost at the limit!" (she cares, in her own way)
47
+ - **Achievement Badges** — Unlock 9 badges across Bronze, Silver, and Gold tiers for streaks, milestones, and achievements
48
+ - **Share Report Card** — Save a shareable PNG card with your current mood, usage stats, and reset countdown
49
+ - **Quote Collection (도감)** — Collect 86 unique mama quotes across 4 rarity tiers (Common, Rare, Legendary, Secret)
50
+ - **Custom Character Skins** — Upload your own character images with single, per-mood, or sprite sheet modes
51
+ - **Always on Top** — Toggle in Settings or tray menu (default ON) to keep mama visible above other windows
52
+ - **System tray** — Lives quietly in your taskbar, judging you silently
53
+ - **Settings panel** — Position, auto-start, language selection, collection viewer, and skin customization
54
+ - **4 languages** — 한국어, English, 日本語, 中文
55
+ - **Auto-start** — Boots with your OS so you can never escape mom's watchful eye
56
+ - **Auto-update** — Mom keeps herself up to date via GitHub Releases
57
+
58
+ ### Share Report Card
59
+
60
+ Save your current mama status as a PNG image — perfect for sharing on social media.
61
+
62
+ <div align="center">
63
+ <img src="docs/images/share-card-example.png" width="500" alt="Share Report Card example" />
64
+ </div>
65
+
66
+ The card includes mood, quote, 7-day/5-hour usage bars with reset countdowns, and a UTC timestamp.
67
+
68
+ ### Quote Collection
69
+
70
+ Mama has 86 unique quotes spread across 4 rarity tiers:
71
+
72
+ | Rarity | Count | How to Unlock |
73
+ |--------|------:|---------------|
74
+ | ⚪ Common | 73 | Displayed during normal use |
75
+ | 🔵 Rare | 5 | Hit specific usage milestones (0%, 50%, 100% of 5hr, etc.) |
76
+ | 🟡 Legendary | 3 | Achieve streaks and lifetime milestones |
77
+ | 🔴 Secret | 5 | Use the app on holidays or at 3 AM |
78
+
79
+ ### Achievement Badges
80
+
81
+ Unlock badges for hitting milestones and maintaining streaks. View your collection in **Collection tab → Badges**.
82
+
83
+ | Tier | Badge | How to Unlock |
84
+ |------|-------|---------------|
85
+ | 🥉 Bronze | First Steps | Make your first API call |
86
+ | 🥉 Bronze | 3-Day Streak | Use Claude Code on 3 consecutive days |
87
+ | 🥉 Bronze | 7 Days with Mom | Use the app for 7 consecutive days |
88
+ | 🥈 Silver | Halfway There | Hit 50% of your weekly quota |
89
+ | 🥈 Silver | 7-Day Streak | Maintain a 7-day usage streak |
90
+ | 🥈 Silver | Mom's Pride | Get 10 "proud" expressions (85%+ usage) |
91
+ | 🥇 Gold | Full Power | Reach 100% of your weekly quota |
92
+ | 🥇 Gold | 30-Day Streak | Maintain a 30-day usage streak |
93
+ | 🥇 Gold | Survivor | Get 10 "angry" expressions AND keep using the app |
94
+
95
+ ### Custom Character Skins
96
+
97
+ Give mama a new look with custom character images:
98
+
99
+ - **Single Image Mode** — One image displayed for all moods
100
+ - **Per-Mood Mode** — 6 images (one for each mood expression: angry, worried, happy, proud, confused, sleeping)
101
+ - **Sprite Sheet Mode** — Advanced mode with one image and grid configuration for frame positioning
102
+
103
+ Upload your custom skins in **Settings → Skins** and switch between them anytime. Mom might judge your art choices, but she'll wear them anyway.
104
+
105
+ ## Installation
106
+
107
+ ### Prerequisites
108
+
109
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) must be installed and logged in (OAuth)
110
+ - That's it. Mom doesn't ask for much.
111
+
112
+ ### Quick Install (One-liner)
113
+
114
+ **npm** (all platforms, requires Node.js):
115
+ ```bash
116
+ npm install -g claude-pet
117
+ claude-pet
118
+ ```
119
+
120
+ **macOS / Linux:**
121
+ ```bash
122
+ curl -fsSL https://raw.githubusercontent.com/scm1400/claude-pet/main/scripts/install.sh | bash
123
+ ```
124
+
125
+ **Windows (PowerShell):**
126
+ ```powershell
127
+ irm https://raw.githubusercontent.com/scm1400/claude-pet/main/scripts/install.ps1 | iex
128
+ ```
129
+
130
+ ### Download Installer
131
+
132
+ Or grab the latest installer from [**Releases**](https://github.com/scm1400/claude-mama/releases):
133
+
134
+ | Platform | File | Arch |
135
+ |----------|------|------|
136
+ | Windows | `Claude Pet Setup x.x.x.exe` | x64 |
137
+ | macOS | `Claude Pet-x.x.x.dmg` | Universal (Intel + Apple Silicon) |
138
+ | Linux | `Claude Pet-x.x.x.AppImage` | x64 |
139
+ | Linux | `claude-pet_x.x.x_amd64.deb` | x64 |
140
+
141
+ > **⚠️ Not code-signed:** The app is not code-signed yet, so your OS will show a security warning on first install.
142
+ > - **Windows:** SmartScreen → **More info** → **Run anyway**
143
+ > - **macOS:** **System Settings** → **Privacy & Security** → **Open Anyway**
144
+ > - **Linux (AppImage):** `chmod +x Claude\ Pet-*.AppImage && ./Claude\ Pet-*.AppImage`
145
+ > - **Linux (deb):** `sudo dpkg -i claude-pet_*.deb`
146
+
147
+ ### Claude Code Integration
148
+
149
+ Claude Pet includes slash commands that work inside Claude Code:
150
+
151
+ | Command | Effect |
152
+ |---------|--------|
153
+ | `/feed` | Feed your pet (hunger -30%) |
154
+ | `/play` | Play with your pet (happiness +25%) |
155
+ | `/pet` | Pet your pet (hunger -10%, happiness +10%, energy +10%) |
156
+ | `/name <name>` | Give your pet a name |
157
+
158
+ Each command shows a live status line with level, growth stage, and stat changes.
159
+
160
+ ### First Launch
161
+
162
+ 1. Make sure Claude Code is installed and logged in (`claude` in terminal)
163
+ 2. Claude Mama will automatically detect your API usage via OAuth
164
+ 3. If rate-limited (429), it falls back to local JSONL session parsing — no extra config needed
165
+ 4. Mama starts polling every 5 minutes. Just let her do her thing.
166
+
167
+ ### Auto-Start & Auto-Update
168
+
169
+ - **Auto-start:** Enabled by default. Claude Mama starts with your OS. Toggle in Settings if you dare.
170
+ - **Auto-update:** Updates are downloaded automatically from GitHub Releases. When a new version is ready, mama will ask you to restart.
171
+
172
+ ## How It Works
173
+
174
+ ```
175
+ ┌─────────────────┐ ┌──────────────┐ ┌─────────────┐
176
+ │ Anthropic OAuth │────>│ Usage Tracker │───>│ Mood Engine │
177
+ │ Usage API │ │ (5min poll) │ │ │
178
+ └─────────────────┘ └──────┬───────┘ └──────┬──────┘
179
+ │ │
180
+ ┌──────┴───────┐ ┌───────┘
181
+ │ JSONL Session│ │
182
+ │ Parser (429) │ v
183
+ └──────────────┘ ┌──────────────┐
184
+ │ Pixel Art │
185
+ ┌──────────────┐ ┌──────────────┐ │ Character │
186
+ │ Speech │ │ Usage Bar │ └──────────────┘
187
+ │ Bubble │ │ Indicator │
188
+ └──────────────┘ └──────────────┘
189
+ ```
190
+
191
+ 1. **Polls** the Anthropic usage API every 5 minutes
192
+ 2. **Falls back to JSONL session parsing** when rate limited (429) — scans `~/.claude/projects/` session files to estimate usage with dynamic calibration
193
+ 3. **Computes mood** based on weekly utilization thresholds
194
+ 4. **Renders** a pixel-art mama character with mood-appropriate expression and message
195
+ 5. **Nags you** until you use your tokens like a responsible adult
196
+
197
+ ### Mood Thresholds
198
+
199
+ | Weekly Usage | Mood | Mom Says |
200
+ |:---:|:---:|---|
201
+ | 0–14% | 😡 Angry | "Your quota is rotting away!" |
202
+ | 15–49% | 😟 Worried | "Mom is worried about you..." |
203
+ | 50–84% | 😊 Happy | "Now that's what I like~" |
204
+ | 85–100% | 🥹 Proud | "Perfect! I'm tearing up..." |
205
+ | 5hr > 90% | ⚠️ Warning | "Take a break~ 5-hour limit almost reached!" |
206
+ | Rate limited | 😵 Confused | Uses local session data, or "Collecting data..." while calibrating |
207
+ | API error | 😵 Confused | "Something went wrong..." |
208
+ | No login | 😴 Sleeping | "Log in first!" |
209
+
210
+ > **Note:** The Claude Code usage API (`api/oauth/usage`) has strict rate limits. When rate limited (429), Claude Mama parses local session JSONL files (`~/.claude/projects/`) to estimate usage. It learns the token-to-percent ratio from successful API responses (dynamic calibration), so estimates improve over time. While waiting for the first successful calibration, mama shows "Collecting data..." and retries every 10 seconds.
211
+
212
+ ## Development
213
+
214
+ ```bash
215
+ # Install dependencies
216
+ npm install
217
+
218
+ # Run in development mode
219
+ npm run dev
220
+
221
+ # Run tests
222
+ npm test
223
+
224
+ # Build for production
225
+ npm run build
226
+
227
+ # Build Windows installer
228
+ npm run build:win
229
+
230
+ # Build macOS installer (requires macOS)
231
+ npm run build:mac
232
+ ```
233
+
234
+ ## FAQ
235
+
236
+ **Q: Can I hide from Claude Mama?**
237
+ A: No. She auto-starts with your OS. You can disable it in settings, but she'll know.
238
+
239
+ **Q: Why does she speak Korean by default?**
240
+ A: Because Korean moms are the gold standard of guilt-tripping. You can switch to English, Japanese, or Chinese in settings if your guilt receptors are calibrated differently.
241
+
242
+ **Q: My usage is at 0% but I've been coding all day?**
243
+ A: Make sure Claude Code is logged in. Mom can't monitor what she can't see.
244
+
245
+ **Q: How do I unlock secret quotes?**
246
+ A: Use the app on holidays (New Year, Chuseok, Christmas) or stay up coding past 3 AM. Mom notices everything.
247
+
248
+ **Q: Is this a joke?**
249
+ A: The guilt is real. The chicken reward is not (yet).
250
+
251
+ **Q: Will there be a Claude Dad version?**
252
+ A: Claude Dad went out for tokens and never came back.
253
+
254
+ ## Contributing
255
+
256
+ PRs welcome! Whether it's new languages, more guilt-inducing messages, or pixel art improvements — mom appreciates the help.
257
+
258
+ 1. Fork this repo
259
+ 2. Create your feature branch (`git checkout -b feature/more-guilt`)
260
+ 3. Commit your changes
261
+ 4. Push to the branch
262
+ 5. Open a Pull Request
263
+
264
+ ## Privacy Policy
265
+
266
+ This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it.
267
+
268
+ - **Anthropic API**: The app calls the Anthropic OAuth Usage API to retrieve your token usage statistics. This is initiated by you when you log in with your Claude Code credentials.
269
+ - **GitHub (Auto-Update)**: The app checks GitHub Releases for new versions. No personal data is transmitted.
270
+ - **Local Storage**: Your API credentials and settings are stored locally on your machine using `electron-store`. They are never sent to any third-party server.
271
+
272
+ ## Code Signing Policy
273
+
274
+ Free code signing provided by [SignPath.io](https://signpath.io), certificate by [SignPath Foundation](https://signpath.org).
275
+
276
+ **Team roles:**
277
+ - Committers and reviewers: [Members](https://github.com/orgs/scm1400/people)
278
+ - Approvers: [Owners](https://github.com/orgs/scm1400/people?query=role%3Aowner)
279
+
280
+ ## License
281
+
282
+ [ISC](LICENSE) — Free as in "mom's love" (unconditional, but with expectations).
283
+
284
+ ---
285
+
286
+ <div align="center">
287
+
288
+ **Built with guilt and ❤️**
289
+
290
+ *If this made you mass-consume your Claude API quota, please star the repo. Mom would be proud.*
291
+
292
+ </div>
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const { spawn } = require('child_process');
6
+
7
+ const PET_DIR = path.join(os.homedir(), '.claude-pet');
8
+ const LOCK_FILE = path.join(PET_DIR, 'app.pid');
9
+
10
+ // Check if process with given PID is running
11
+ function isRunning(pid) {
12
+ try {
13
+ process.kill(pid, 0);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ // Check lock file for existing instance
21
+ if (fs.existsSync(LOCK_FILE)) {
22
+ const pid = parseInt(fs.readFileSync(LOCK_FILE, 'utf-8').trim(), 10);
23
+ if (pid && isRunning(pid)) {
24
+ // Already running — exit silently
25
+ process.exit(0);
26
+ }
27
+ }
28
+
29
+ // Launch Electron app
30
+ const electronPath = require('electron');
31
+ const appPath = path.join(__dirname, '..', 'dist', 'main', 'main', 'main.js');
32
+
33
+ const child = spawn(electronPath, [appPath], {
34
+ stdio: 'ignore',
35
+ detached: true,
36
+ });
37
+
38
+ // Write PID lock file
39
+ fs.mkdirSync(PET_DIR, { recursive: true });
40
+ fs.writeFileSync(LOCK_FILE, String(child.pid));
41
+
42
+ child.unref();
@@ -0,0 +1,50 @@
1
+ <!-- Parent: ../AGENTS.md -->
2
+ <!-- Generated: 2026-03-14 -->
3
+
4
+ # build/
5
+
6
+ ## Purpose
7
+ Static build assets consumed by electron-builder at packaging time. All files in this directory are generated by `scripts/make-icon.js` from the source artwork at `docs/images/character.webp`. Do not edit these files manually — regenerate them by running `npm run make-icon`.
8
+
9
+ ## Key Files
10
+ | File | Description |
11
+ |------|-------------|
12
+ | `icon.png` | 512×512 PNG — macOS application icon |
13
+ | `icon.ico` | 256×256 ICO — Windows application icon |
14
+ | `tray-icon.png` | 32×32 PNG — system tray icon (used at runtime by `src/main/tray.ts`) |
15
+ | `installerSidebar.bmp` | 164×314 BMP — NSIS Windows installer sidebar graphic (pink gradient + character) |
16
+ | `installerHeader.bmp` | 150×57 BMP — NSIS Windows installer header graphic |
17
+ | `dmg-background.png` | 540×380 PNG — macOS DMG window background |
18
+ | `entitlements.mac.plist` | macOS entitlements file for code signing (hardened runtime permissions) |
19
+
20
+ ## Subdirectories
21
+ | Directory | Purpose |
22
+ |-----------|---------|
23
+ | *(none)* | — |
24
+
25
+ ## For AI Agents
26
+
27
+ ### Working In This Directory
28
+ - Do not edit image files directly. If the character artwork changes, update `docs/images/character.webp` and run `npm run make-icon` to regenerate everything.
29
+ - `entitlements.mac.plist` is hand-maintained and controls macOS sandbox permissions. Edit it only when adding new capabilities (e.g., network access, keychain access). Changes here require re-signing and re-notarizing the app.
30
+ - BMP dimensions are fixed requirements from NSIS: sidebar must be exactly 164×314, header exactly 150×57. The DMG background must be exactly 540×380.
31
+ - `tray-icon.png` is referenced at runtime by `src/main/tray.ts` via `path.join(app.getAppPath(), 'build/tray-icon.png')` — do not rename or move it.
32
+ - `icon.png` and `icon.ico` are referenced in `electron-builder.yml` — do not rename them.
33
+
34
+ ### Testing Requirements
35
+ - After running `npm run make-icon`, visually confirm that all six image files render correctly and have the expected dimensions.
36
+ - Run `npm run build:win` or `npm run build:mac` to verify that electron-builder picks up all assets without errors.
37
+
38
+ ### Common Patterns
39
+ - All images are generated with `sharp`'s `contain` fit mode and a transparent background, then composited onto colored SVG backgrounds for installer graphics.
40
+
41
+ ## Dependencies
42
+ ### Internal
43
+ - Generated from `docs/images/character.webp` by `scripts/make-icon.js`
44
+ - Referenced by `electron-builder.yml` (icons, installer images)
45
+ - Referenced at runtime by `src/main/tray.ts` (tray-icon.png)
46
+
47
+ ### External
48
+ - electron-builder reads this directory during packaging
49
+
50
+ <!-- MANUAL: -->
Binary file
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>com.apple.security.cs.allow-jit</key>
6
+ <true/>
7
+ <key>com.apple.security.cs.allow-dyld-environment-variables</key>
8
+ <true/>
9
+ <key>com.apple.security.cs.disable-library-validation</key>
10
+ <true/>
11
+ <key>com.apple.security.automation.apple-events</key>
12
+ <true/>
13
+ </dict>
14
+ </plist>
package/build/icon.ico ADDED
Binary file
package/build/icon.png ADDED
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BadgeManager = void 0;
4
+ const badge_registry_1 = require("./badge-registry");
5
+ class BadgeManager {
6
+ unlocked;
7
+ constructor(persisted) {
8
+ this.unlocked = new Map(persisted.map((b) => [b.id, b]));
9
+ }
10
+ unlock(id, now) {
11
+ if (this.unlocked.has(id))
12
+ return false;
13
+ if (!(0, badge_registry_1.getBadgeById)(id))
14
+ return false;
15
+ this.unlocked.set(id, { id, unlockedAt: now.toISOString() });
16
+ return true;
17
+ }
18
+ processTriggered(triggeredIds, now) {
19
+ const newlyUnlocked = [];
20
+ for (const id of triggeredIds) {
21
+ if (this.unlock(id, now)) {
22
+ newlyUnlocked.push(id);
23
+ }
24
+ }
25
+ return newlyUnlocked;
26
+ }
27
+ getState() {
28
+ const byTier = {
29
+ bronze: { unlocked: 0, total: 0 },
30
+ silver: { unlocked: 0, total: 0 },
31
+ gold: { unlocked: 0, total: 0 },
32
+ };
33
+ for (const entry of badge_registry_1.BADGE_REGISTRY) {
34
+ byTier[entry.tier].total++;
35
+ if (this.unlocked.has(entry.id)) {
36
+ byTier[entry.tier].unlocked++;
37
+ }
38
+ }
39
+ return {
40
+ unlocked: [...this.unlocked.values()],
41
+ totalCount: badge_registry_1.BADGE_REGISTRY.length,
42
+ byTier,
43
+ };
44
+ }
45
+ serialize() {
46
+ return [...this.unlocked.values()];
47
+ }
48
+ }
49
+ exports.BadgeManager = BadgeManager;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BADGE_REGISTRY = void 0;
4
+ exports.getBadgeById = getBadgeById;
5
+ exports.BADGE_REGISTRY = [
6
+ {
7
+ id: 'badge_first_call',
8
+ tier: 'bronze',
9
+ name: { ko: '첫 만남', en: 'First Meeting', ja: '初めまして', zh: '初次见面' },
10
+ description: { ko: '첫 API 호출', en: 'First API call', ja: '初めてのAPI呼び出し', zh: '第一次API调用' },
11
+ icon: '🐾',
12
+ },
13
+ {
14
+ id: 'badge_streak_3',
15
+ tier: 'bronze',
16
+ name: { ko: '3일 함께', en: '3 Days Together', ja: '3日一緒', zh: '3天陪伴' },
17
+ description: { ko: '3일 연속 사용', en: '3 consecutive days', ja: '3日連続使用', zh: '连续使用3天' },
18
+ icon: '🔥',
19
+ },
20
+ {
21
+ id: 'badge_7days',
22
+ tier: 'bronze',
23
+ name: { ko: '일주일 친구', en: 'Week-long Buddy', ja: '一週間の仲間', zh: '一周好友' },
24
+ description: { ko: '설치 후 7일', en: '7 days since install', ja: 'インストールから7日', zh: '安装后7天' },
25
+ icon: '🏠',
26
+ },
27
+ {
28
+ id: 'badge_half',
29
+ tier: 'silver',
30
+ name: { ko: '반타작', en: 'Halfway There', ja: '半分達成', zh: '半程达成' },
31
+ description: { ko: '주간 50% 달성', en: 'Reach 50% weekly', ja: '週間50%達成', zh: '周使用率50%' },
32
+ icon: '⚡',
33
+ },
34
+ {
35
+ id: 'badge_streak_7',
36
+ tier: 'silver',
37
+ name: { ko: '7일 연속', en: '7-Day Streak', ja: '7日連続', zh: '7天连续' },
38
+ description: { ko: '7일 연속 사용', en: '7 consecutive days', ja: '7日連続使用', zh: '连续使用7天' },
39
+ icon: '💪',
40
+ },
41
+ {
42
+ id: 'badge_happy_10',
43
+ tier: 'silver',
44
+ name: { ko: '꼬리흔들기 달인', en: 'Tail Wag Master', ja: 'しっぽ振り名人', zh: '摇尾达人' },
45
+ description: { ko: 'happy 상태 10회', en: 'Happy mood 10 times', ja: 'happy状態10回', zh: 'happy状态10次' },
46
+ icon: '🏆',
47
+ },
48
+ {
49
+ id: 'badge_full',
50
+ tier: 'gold',
51
+ name: { ko: '풀 가동', en: 'Full Power', ja: 'フル稼働', zh: '全力运转' },
52
+ description: { ko: '주간 100% 달성', en: 'Reach 100% weekly', ja: '週間100%達成', zh: '周使用率100%' },
53
+ icon: '🚀',
54
+ },
55
+ {
56
+ id: 'badge_streak_30',
57
+ tier: 'gold',
58
+ name: { ko: '30일 동반자', en: '30-Day Companion', ja: '30日の相棒', zh: '30天伙伴' },
59
+ description: { ko: '30일 연속 사용', en: '30 consecutive days', ja: '30日連続使用', zh: '连续使用30天' },
60
+ icon: '👑',
61
+ },
62
+ {
63
+ id: 'badge_survivor',
64
+ tier: 'gold',
65
+ name: { ko: '충실한 친구', en: 'Loyal Companion', ja: '忠実な友', zh: '忠实伙伴' },
66
+ description: { ko: 'worried 10회 후에도 사용', en: 'Keep using after 10 worried moods', ja: 'worried10回後も使い続ける', zh: '经历10次worried后继续' },
67
+ icon: '🛡️',
68
+ },
69
+ ];
70
+ function getBadgeById(id) {
71
+ return exports.BADGE_REGISTRY.find((b) => b.id === id);
72
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evaluateBadgeTriggers = evaluateBadgeTriggers;
4
+ function evaluateBadgeTriggers(ctx) {
5
+ const triggered = [];
6
+ const { weeklyUtilization, dailyHistory, installDate, firstApiCallSeen, now, happyCount, worriedCount } = ctx;
7
+ // Bronze: first call
8
+ if (!firstApiCallSeen && weeklyUtilization !== null && weeklyUtilization > 0) {
9
+ triggered.push('badge_first_call');
10
+ }
11
+ // Streak calculation
12
+ if (dailyHistory.length > 0) {
13
+ const sorted = [...dailyHistory].sort((a, b) => b.date.localeCompare(a.date));
14
+ let streak = 0;
15
+ for (const d of sorted) {
16
+ if (d.percent > 0)
17
+ streak++;
18
+ else
19
+ break;
20
+ }
21
+ if (streak >= 3)
22
+ triggered.push('badge_streak_3');
23
+ if (streak >= 7)
24
+ triggered.push('badge_streak_7');
25
+ if (streak >= 30)
26
+ triggered.push('badge_streak_30');
27
+ }
28
+ // Days since install
29
+ const installDt = new Date(installDate);
30
+ const daysSince = Math.floor((now.getTime() - installDt.getTime()) / (24 * 60 * 60 * 1000));
31
+ if (daysSince >= 7)
32
+ triggered.push('badge_7days');
33
+ // Usage milestones
34
+ if (weeklyUtilization !== null && weeklyUtilization >= 50)
35
+ triggered.push('badge_half');
36
+ if (weeklyUtilization !== null && weeklyUtilization >= 100)
37
+ triggered.push('badge_full');
38
+ // Mood counts
39
+ if (happyCount >= 10)
40
+ triggered.push('badge_happy_10');
41
+ if (worriedCount >= 10 && weeklyUtilization !== null && weeklyUtilization > 0) {
42
+ triggered.push('badge_survivor');
43
+ }
44
+ return triggered;
45
+ }