codepet 1.0.4 → 1.0.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.
Files changed (39) hide show
  1. package/bin/codepet.js +135 -16
  2. package/package.json +13 -4
  3. package/scripts/pet_card.py +47 -12
  4. package/sprites/bababoyi/eat.png +0 -0
  5. package/sprites/bababoyi/happy.png +0 -0
  6. package/sprites/bababoyi/normal.png +0 -0
  7. package/sprites/bababoyi/pet.png +0 -0
  8. package/sprites/bababoyi/sleep.png +0 -0
  9. package/sprites/bababoyi/worry.png +0 -0
  10. package/sprites/bagayalu/eat.png +0 -0
  11. package/sprites/bagayalu/happy.png +0 -0
  12. package/sprites/bagayalu/normal.png +0 -0
  13. package/sprites/bagayalu/pet.png +0 -0
  14. package/sprites/bagayalu/sleep.png +0 -0
  15. package/sprites/bagayalu/worry.png +0 -0
  16. package/sprites/bibilabu/eat.png +0 -0
  17. package/sprites/bibilabu/happy.png +0 -0
  18. package/sprites/bibilabu/normal.png +0 -0
  19. package/sprites/bibilabu/pet.png +0 -0
  20. package/sprites/bibilabu/sleep.png +0 -0
  21. package/sprites/bibilabu/worry.png +0 -0
  22. package/sprites/gugugaga/eat.png +0 -0
  23. package/sprites/gugugaga/happy.png +0 -0
  24. package/sprites/gugugaga/normal.png +0 -0
  25. package/sprites/gugugaga/pet.png +0 -0
  26. package/sprites/gugugaga/sleep.png +0 -0
  27. package/sprites/gugugaga/worry.png +0 -0
  28. package/sprites/waibibabu/eat.png +0 -0
  29. package/sprites/waibibabu/happy.png +0 -0
  30. package/sprites/waibibabu/normal.png +0 -0
  31. package/sprites/waibibabu/pet.png +0 -0
  32. package/sprites/waibibabu/sleep.png +0 -0
  33. package/sprites/waibibabu/worry.png +0 -0
  34. package/sprites/wodedaodun/eat.png +0 -0
  35. package/sprites/wodedaodun/happy.png +0 -0
  36. package/sprites/wodedaodun/normal.png +0 -0
  37. package/sprites/wodedaodun/pet.png +0 -0
  38. package/sprites/wodedaodun/sleep.png +0 -0
  39. package/sprites/wodedaodun/worry.png +0 -0
package/bin/codepet.js CHANGED
@@ -25,6 +25,23 @@ function getPython() {
25
25
  return null;
26
26
  }
27
27
  const PYTHON = getPython();
28
+
29
+ // 自动安装 Python 依赖 (Pillow, wcwidth)
30
+ function ensurePythonDeps() {
31
+ if (!PYTHON) return;
32
+ try {
33
+ execSync(`${PYTHON} -c "import PIL, wcwidth"`, { stdio: 'ignore' });
34
+ } catch {
35
+ console.log(' 正在安装依赖 (Pillow, wcwidth)...');
36
+ try {
37
+ execSync(`${PYTHON} -m pip install Pillow wcwidth --quiet`, { stdio: 'inherit' });
38
+ } catch {
39
+ // pip might need --user on some systems
40
+ try { execSync(`${PYTHON} -m pip install Pillow wcwidth --user --quiet`, { stdio: 'inherit' }); } catch {}
41
+ }
42
+ }
43
+ }
44
+
28
45
  const core = require('../core');
29
46
 
30
47
  const SPRITES_DIR = path.join(__dirname, '..', 'sprites');
@@ -110,18 +127,28 @@ function installSkill(targetDir, platformName) {
110
127
  fs.copyFileSync(skillSrc, path.join(destDir, 'SKILL.md'));
111
128
  }
112
129
 
113
- // 复制精灵图片
130
+ // 复制精灵图片(包括子目录中的表情图)
114
131
  if (fs.existsSync(SPRITES_DIR)) {
115
132
  const spriteDestDir = path.join(destDir, 'sprites');
116
133
  if (!fs.existsSync(spriteDestDir)) {
117
134
  fs.mkdirSync(spriteDestDir, { recursive: true });
118
135
  }
119
- const files = fs.readdirSync(SPRITES_DIR).filter(f => f.endsWith('.png'));
120
- for (const file of files) {
121
- fs.copyFileSync(
122
- path.join(SPRITES_DIR, file),
123
- path.join(spriteDestDir, file)
124
- );
136
+ const entries = fs.readdirSync(SPRITES_DIR, { withFileTypes: true });
137
+ for (const entry of entries) {
138
+ const srcPath = path.join(SPRITES_DIR, entry.name);
139
+ const dstPath = path.join(spriteDestDir, entry.name);
140
+ if (entry.isFile() && entry.name.endsWith('.png')) {
141
+ fs.copyFileSync(srcPath, dstPath);
142
+ } else if (entry.isDirectory() && entry.name !== '6') {
143
+ // Copy subdirectory (character expression sprites)
144
+ if (!fs.existsSync(dstPath)) {
145
+ fs.mkdirSync(dstPath, { recursive: true });
146
+ }
147
+ const subFiles = fs.readdirSync(srcPath).filter(f => f.endsWith('.png'));
148
+ for (const sf of subFiles) {
149
+ fs.copyFileSync(path.join(srcPath, sf), path.join(dstPath, sf));
150
+ }
151
+ }
125
152
  }
126
153
  }
127
154
 
@@ -180,10 +207,92 @@ function setup() {
180
207
  fs.copyFileSync(renderSrc, renderDst);
181
208
  }
182
209
 
210
+ // ── 配置 Claude Code hooks ──
211
+ configureCCHooks();
212
+
183
213
  console.log(`\n ✅ 安装完成!共 ${installed} 个平台。`);
184
214
  console.log(' 在任意支持的工具里输入 /pet 开始养宠物。\n');
185
215
  }
186
216
 
217
+ // ── 配置 Claude Code settings.json hooks ──
218
+ function configureCCHooks() {
219
+ const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
220
+ if (!fs.existsSync(path.dirname(settingsPath))) return; // Claude Code not installed
221
+
222
+ // Detect package root (works for both npm global install and local dev)
223
+ const pkgRoot = path.join(__dirname, '..');
224
+ const scriptsDir = path.join(pkgRoot, 'scripts');
225
+
226
+ // Build cross-platform hook commands
227
+ const pythonCmd = PYTHON || 'python3';
228
+ const petBubbleScript = path.join(scriptsDir, 'pet_bubble.js');
229
+ const autoExpScript = path.join(scriptsDir, 'auto_exp.py');
230
+
231
+ let petBubbleHook, autoExpHook;
232
+ if (process.platform === 'win32') {
233
+ // Windows: use node for .js, python for .py
234
+ petBubbleHook = `node "${petBubbleScript}"`;
235
+ autoExpHook = `${pythonCmd} "${autoExpScript}"`;
236
+ } else {
237
+ petBubbleHook = `node "${petBubbleScript}"`;
238
+ autoExpHook = `${pythonCmd} "${autoExpScript}"`;
239
+ }
240
+
241
+ // Read existing settings
242
+ let settings = {};
243
+ if (fs.existsSync(settingsPath)) {
244
+ try {
245
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
246
+ } catch {
247
+ settings = {};
248
+ }
249
+ }
250
+
251
+ // Merge hooks - don't overwrite existing hooks, append if not present
252
+ if (!settings.hooks) settings.hooks = {};
253
+
254
+ // pet_bubble hook (runs after each assistant response)
255
+ const hookKey = 'PostToolExecution';
256
+ if (!settings.hooks[hookKey]) settings.hooks[hookKey] = [];
257
+ const existingBubble = settings.hooks[hookKey].find(h =>
258
+ (typeof h === 'string' ? h : h.command || '').includes('pet_bubble')
259
+ );
260
+ if (!existingBubble) {
261
+ settings.hooks[hookKey].push({
262
+ command: petBubbleHook,
263
+ description: 'CodePet bubble reaction'
264
+ });
265
+ }
266
+
267
+ // auto_exp hook (gains exp on tool execution)
268
+ const existingExp = settings.hooks[hookKey].find(h =>
269
+ (typeof h === 'string' ? h : h.command || '').includes('auto_exp')
270
+ );
271
+ if (!existingExp) {
272
+ settings.hooks[hookKey].push({
273
+ command: autoExpHook,
274
+ description: 'CodePet auto experience gain'
275
+ });
276
+ }
277
+
278
+ // Add bypassPermissions for codepet commands
279
+ if (!settings.bypassPermissions) settings.bypassPermissions = [];
280
+ const codepetPerms = [
281
+ 'codepet *',
282
+ 'node */codepet/bin/codepet.js *',
283
+ 'node */codepet/scripts/*',
284
+ ];
285
+ for (const perm of codepetPerms) {
286
+ if (!settings.bypassPermissions.includes(perm)) {
287
+ settings.bypassPermissions.push(perm);
288
+ }
289
+ }
290
+
291
+ // Write back
292
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
293
+ console.log(' ✓ Claude Code — hooks 已配置');
294
+ }
295
+
187
296
  // ── 渲染像素画 ──
188
297
  function renderSprite(character, variant) {
189
298
  variant = variant || 'normal';
@@ -233,6 +342,7 @@ function showStats(pet) {
233
342
  }
234
343
 
235
344
  function show() {
345
+ ensurePythonDeps();
236
346
  const pet = core.loadPet();
237
347
  if (!pet) {
238
348
  console.log('\n 还没有宠物。运行 宠物 孵化\n');
@@ -344,6 +454,7 @@ function doFeed() {
344
454
  }
345
455
 
346
456
  function doAscii() {
457
+ ensurePythonDeps();
347
458
  const pet = core.loadPet();
348
459
  if (!pet) { console.log(' 还没有宠物。'); return; }
349
460
  if (!PYTHON) { console.log(' 需要安装 Python。'); return; }
@@ -360,6 +471,7 @@ function doAscii() {
360
471
  }
361
472
 
362
473
  function doPopup() {
474
+ ensurePythonDeps();
363
475
  const pet = core.loadPet();
364
476
  if (!pet) { console.log(' 还没有宠物。'); return; }
365
477
  if (!PYTHON) { console.log(' 需要安装 Python。'); return; }
@@ -368,7 +480,8 @@ function doPopup() {
368
480
 
369
481
  if (process.platform === 'win32') {
370
482
  // Windows: 用 start cmd 弹新窗口
371
- execSync(`start "" cmd /c "${PYTHON} \\"${script}\\" ${pet.character} ${scene} 40 2>nul & pause >nul"`, { shell: true, stdio: 'ignore' });
483
+ // /k keeps CMD open so user can see the polaroid; they close it manually
484
+ execSync(`start "" cmd /k "${PYTHON} \\"${script}\\" ${pet.character} ${scene} 40 2>nul"`, { shell: true, stdio: 'ignore' });
372
485
  } else if (process.platform === 'darwin') {
373
486
  // macOS: 用 osascript 弹 Terminal
374
487
  const escapedScript = script.replace(/'/g, "'\\''");
@@ -382,6 +495,7 @@ function doPopup() {
382
495
  }
383
496
 
384
497
  function doCard() {
498
+ ensurePythonDeps();
385
499
  const pet = core.loadPet();
386
500
  if (!pet) { console.log(' 还没有宠物。'); return; }
387
501
  const cardScript = path.join(__dirname, '..', 'scripts', 'pet_card.py');
@@ -402,14 +516,19 @@ function help() {
402
516
  console.log(`
403
517
  🐾 CodePet — 在编程软件里养电子宠物
404
518
 
405
- 用法:
406
- 宠物 安装 一键安装到所有编程工具
407
- 宠物 孵化 孵化新宠物
408
- 宠物 看看 查看宠物
409
- 宠物 摸摸 撸宠物
410
- 宠物 喂它 喂宠物
411
- 宠物 卡片 生成分享卡片
412
- 宠物 帮助 显示此帮助
519
+ 用法 (macOS/Linux 可用 "宠物",Windows 请用 "codepet"):
520
+ codepet setup 一键安装到所有编程工具
521
+ codepet hatch 孵化新宠物
522
+ codepet show 查看宠物
523
+ codepet pat 撸宠物
524
+ codepet feed 喂宠物
525
+ codepet card 生成分享卡片
526
+ codepet popup 拍照弹窗
527
+ codepet ascii ASCII 画
528
+ codepet help 显示此帮助
529
+
530
+ 中文别名 (macOS/Linux):
531
+ 宠物 安装 / 孵化 / 看看 / 摸摸 / 喂它 / 卡片 / 帮助
413
532
 
414
533
  支持平台:
415
534
  Claude Code · Codex · Cursor · VS Code · Kiro
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codepet",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "在编程软件里养电子宠物 — 支持 Claude Code / Codex / Cursor / VS Code 等 10+ 平台",
5
5
  "main": "core/index.js",
6
6
  "bin": {
@@ -13,15 +13,25 @@
13
13
  },
14
14
  "keywords": [
15
15
  "pet",
16
+ "codepet",
17
+ "宠物",
18
+ "chongwu",
16
19
  "coding-companion",
17
20
  "terminal-art",
18
21
  "claude-code",
19
22
  "vscode",
20
23
  "cursor",
21
24
  "codex",
25
+ "kiro",
26
+ "codebuddy",
27
+ "openclaw",
28
+ "antigravity",
29
+ "opencode",
22
30
  "ascii-art",
23
31
  "pixel-art",
24
- "tamagotchi"
32
+ "tamagotchi",
33
+ "ai-pet",
34
+ "ai小蓝鲸"
25
35
  ],
26
36
  "author": "Ai小蓝鲸",
27
37
  "license": "MIT",
@@ -42,8 +52,7 @@
42
52
  "scripts/show_all.py",
43
53
  "scripts/show_all_expressions.py",
44
54
  "scripts/*.sh",
45
- "sprites/*.png",
46
- "sprites/*/",
55
+ "sprites/",
47
56
  "!sprites/6/",
48
57
  "!sprites/batch.json",
49
58
  "adapters/skill/",
@@ -121,23 +121,53 @@ STAT_COLORS = {
121
121
  # ─── Font loading ────────────────────────────────────────────────────────────
122
122
 
123
123
  def load_font(size, bold=False):
124
- """Try macOS system fonts with CJK support, fallback gracefully."""
125
- candidates = [
126
- "/System/Library/Fonts/PingFang.ttc",
127
- "/System/Library/Fonts/STHeiti Light.ttc",
128
- "/System/Library/Fonts/Hiragino Sans GB.ttc",
129
- "/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
130
- "/System/Library/Fonts/Helvetica.ttc",
131
- ]
124
+ """Cross-platform font loading with CJK support. Tries macOS, Windows, Linux fonts."""
125
+ import platform
126
+ system = platform.system()
127
+
128
+ candidates = []
129
+ if system == "Darwin":
130
+ # macOS
131
+ candidates = [
132
+ "/System/Library/Fonts/PingFang.ttc",
133
+ "/System/Library/Fonts/STHeiti Light.ttc",
134
+ "/System/Library/Fonts/Hiragino Sans GB.ttc",
135
+ "/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
136
+ "/System/Library/Fonts/Helvetica.ttc",
137
+ ]
138
+ elif system == "Windows":
139
+ # Windows - fonts are in C:\Windows\Fonts
140
+ winfonts = os.path.join(os.environ.get("WINDIR", r"C:\Windows"), "Fonts")
141
+ candidates = [
142
+ os.path.join(winfonts, "msyh.ttc"), # 微软雅黑
143
+ os.path.join(winfonts, "msyhbd.ttc"), # 微软雅黑 Bold
144
+ os.path.join(winfonts, "simhei.ttf"), # 黑体
145
+ os.path.join(winfonts, "simsun.ttc"), # 宋体
146
+ os.path.join(winfonts, "arial.ttf"), # Arial fallback
147
+ os.path.join(winfonts, "segoeui.ttf"), # Segoe UI
148
+ ]
149
+ else:
150
+ # Linux
151
+ candidates = [
152
+ "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
153
+ "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc",
154
+ "/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc",
155
+ "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
156
+ "/usr/share/fonts/noto/NotoSansCJK-Regular.ttc",
157
+ "/usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc",
158
+ "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
159
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
160
+ ]
161
+
132
162
  # For bold, try index 1 in TTC (usually medium/bold weight)
133
163
  font_index = 1 if bold else 0
134
- for path in candidates:
135
- if os.path.exists(path):
164
+ for fpath in candidates:
165
+ if os.path.exists(fpath):
136
166
  try:
137
- return ImageFont.truetype(path, size, index=font_index)
167
+ return ImageFont.truetype(fpath, size, index=font_index)
138
168
  except Exception:
139
169
  try:
140
- return ImageFont.truetype(path, size, index=0)
170
+ return ImageFont.truetype(fpath, size, index=0)
141
171
  except Exception:
142
172
  continue
143
173
  return ImageFont.load_default()
@@ -463,8 +493,13 @@ def generate_card(style="default"):
463
493
  card_rgb.save(out_path, "PNG", quality=95)
464
494
  print(f"Card saved to {out_path}")
465
495
 
496
+ # Cross-platform open
466
497
  if sys.platform == "darwin":
467
498
  os.system(f'open "{out_path}"')
499
+ elif sys.platform == "win32":
500
+ os.startfile(out_path)
501
+ else:
502
+ os.system(f'xdg-open "{out_path}" 2>/dev/null &')
468
503
 
469
504
 
470
505
  if __name__ == "__main__":
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file