codepet 1.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.
package/bin/codepet.js ADDED
@@ -0,0 +1,384 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * codepet CLI — 一键安装与管理
4
+ *
5
+ * npx codepet setup → 自动检测环境,安装到对应平台
6
+ * npx codepet show → 终端显示宠物
7
+ * npx codepet hatch → 孵化
8
+ * npx codepet pat → 撸
9
+ * npx codepet feed → 喂
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const os = require('os');
15
+ const { execSync } = require('child_process');
16
+ const core = require('../core');
17
+
18
+ const SPRITES_DIR = path.join(__dirname, '..', 'sprites');
19
+ const SKILL_SRC = path.join(__dirname, '..', 'adapters', 'skill', 'SKILL.md');
20
+
21
+ const cmd = process.argv[2] || 'help';
22
+ const arg = process.argv[3];
23
+ // --图片 模式:生成 PNG 让 CC 用 Read 工具显示
24
+ const imageMode = process.argv.includes('--图片') || process.argv.includes('--image');
25
+ const RENDER_WIDTH = 30;
26
+
27
+ // ── 平台检测 ──
28
+ function detectPlatforms() {
29
+ const found = [];
30
+
31
+ // Claude Code
32
+ const ccSkillDir = path.join(os.homedir(), '.claude', 'skills');
33
+ if (fs.existsSync(path.join(os.homedir(), '.claude'))) {
34
+ found.push({ id: 'claude-code', name: 'Claude Code', dir: ccSkillDir });
35
+ }
36
+
37
+ // Codex (OpenAI)
38
+ const codexDir = path.join(os.homedir(), '.codex');
39
+ if (fs.existsSync(codexDir)) {
40
+ found.push({ id: 'codex', name: 'Codex', dir: path.join(codexDir, 'skills') });
41
+ }
42
+
43
+ // Cursor
44
+ const cursorDir = path.join(os.homedir(), '.cursor');
45
+ if (fs.existsSync(cursorDir)) {
46
+ found.push({ id: 'cursor', name: 'Cursor', dir: null });
47
+ }
48
+
49
+ // VS Code
50
+ const vscodeDir = path.join(os.homedir(), '.vscode');
51
+ if (fs.existsSync(vscodeDir)) {
52
+ found.push({ id: 'vscode', name: 'VS Code', dir: null });
53
+ }
54
+
55
+ // Kiro
56
+ const kiroDir = path.join(os.homedir(), '.kiro');
57
+ if (fs.existsSync(kiroDir)) {
58
+ found.push({ id: 'kiro', name: 'Kiro', dir: null });
59
+ }
60
+
61
+ // OpenClaw
62
+ const openclawDir = path.join(os.homedir(), '.openclaw');
63
+ if (fs.existsSync(openclawDir)) {
64
+ found.push({ id: 'openclaw', name: 'OpenClaw', dir: path.join(openclawDir, 'skills') });
65
+ }
66
+
67
+ // CodeBuddy
68
+ const codebuddyDir = path.join(os.homedir(), '.codebuddy');
69
+ if (fs.existsSync(codebuddyDir)) {
70
+ found.push({ id: 'codebuddy', name: 'CodeBuddy', dir: path.join(codebuddyDir, 'skills') });
71
+ }
72
+
73
+ // Antigravity
74
+ const antigravityDir = path.join(os.homedir(), '.antigravity');
75
+ if (fs.existsSync(antigravityDir)) {
76
+ found.push({ id: 'antigravity', name: 'Antigravity', dir: path.join(antigravityDir, 'skills') });
77
+ }
78
+
79
+ // OpenCode
80
+ const opencodeDir = path.join(os.homedir(), '.opencode');
81
+ if (fs.existsSync(opencodeDir)) {
82
+ found.push({ id: 'opencode', name: 'OpenCode', dir: path.join(opencodeDir, 'skills') });
83
+ }
84
+
85
+ return found;
86
+ }
87
+
88
+ // ── 安装 Skill 文件到目标目录 ──
89
+ function installSkill(targetDir, platformName) {
90
+ const destDir = path.join(targetDir, 'codepet');
91
+ if (!fs.existsSync(destDir)) {
92
+ fs.mkdirSync(destDir, { recursive: true });
93
+ }
94
+
95
+ // 复制 SKILL.md
96
+ const skillSrc = SKILL_SRC;
97
+ if (fs.existsSync(skillSrc)) {
98
+ fs.copyFileSync(skillSrc, path.join(destDir, 'SKILL.md'));
99
+ }
100
+
101
+ // 复制精灵图片
102
+ if (fs.existsSync(SPRITES_DIR)) {
103
+ const spriteDestDir = path.join(destDir, 'sprites');
104
+ if (!fs.existsSync(spriteDestDir)) {
105
+ fs.mkdirSync(spriteDestDir, { recursive: true });
106
+ }
107
+ const files = fs.readdirSync(SPRITES_DIR).filter(f => f.endsWith('.png'));
108
+ for (const file of files) {
109
+ fs.copyFileSync(
110
+ path.join(SPRITES_DIR, file),
111
+ path.join(spriteDestDir, file)
112
+ );
113
+ }
114
+ }
115
+
116
+ console.log(` ✓ ${platformName} — 已安装到 ${destDir}`);
117
+ return true;
118
+ }
119
+
120
+ // ── 命令处理 ──
121
+
122
+ function setup() {
123
+ console.log('\n 🐾 CodePet 安装向导\n');
124
+ console.log(' 正在检测已安装的编程工具...\n');
125
+
126
+ const platforms = detectPlatforms();
127
+
128
+ if (platforms.length === 0) {
129
+ console.log(' 未检测到支持的编程工具。');
130
+ console.log(' 支持: Claude Code, Codex, Cursor, VS Code, Kiro,');
131
+ console.log(' CodeBuddy, OpenClaw, Antigravity, OpenCode\n');
132
+ return;
133
+ }
134
+
135
+ console.log(` 检测到 ${platforms.length} 个平台:\n`);
136
+
137
+ let installed = 0;
138
+ const vscodeFamily = [];
139
+
140
+ for (const p of platforms) {
141
+ if (p.dir) {
142
+ // Skill 类平台 → 复制 SKILL.md
143
+ installSkill(p.dir, p.name);
144
+ installed++;
145
+ } else {
146
+ // VS Code 系 → 记录,后面统一提示
147
+ vscodeFamily.push(p.name);
148
+ }
149
+ }
150
+
151
+ if (vscodeFamily.length > 0) {
152
+ console.log(`\n 📦 ${vscodeFamily.join(' / ')} 请通过扩展商店安装:`);
153
+ console.log(' 搜索 "CodePet" 或运行:');
154
+ console.log(' code --install-extension codepet.codepet\n');
155
+ }
156
+
157
+ // 确保宠物数据目录存在
158
+ core.ensureDir ? null : null;
159
+ const petDir = path.join(os.homedir(), '.codepet');
160
+ if (!fs.existsSync(petDir)) {
161
+ fs.mkdirSync(petDir, { recursive: true });
162
+ }
163
+
164
+ // 复制渲染脚本
165
+ const renderSrc = path.join(__dirname, '..', 'scripts', 'img2terminal.py');
166
+ const renderDst = path.join(petDir, 'render.py');
167
+ if (fs.existsSync(renderSrc)) {
168
+ fs.copyFileSync(renderSrc, renderDst);
169
+ }
170
+
171
+ console.log(`\n ✅ 安装完成!共 ${installed} 个平台。`);
172
+ console.log(' 在任意支持的工具里输入 /pet 开始养宠物。\n');
173
+ }
174
+
175
+ // ── 渲染像素画 ──
176
+ function renderSprite(character, variant) {
177
+ variant = variant || 'normal';
178
+
179
+ if (imageMode) {
180
+ // 图片模式:生成 PNG,输出路径
181
+ const renderImgScript = path.join(__dirname, '..', 'scripts', 'render_to_image.py');
182
+ if (fs.existsSync(renderImgScript)) {
183
+ try {
184
+ const imgPath = execSync(`python3 "${renderImgScript}" "${character}" "${variant}"`, { encoding: 'utf-8' }).trim();
185
+ console.log(`[图片:${imgPath}]`);
186
+ } catch (e) {}
187
+ }
188
+ } else {
189
+ // 终端模式:彩色像素画
190
+ const renderScript = path.join(__dirname, '..', 'scripts', 'img2terminal.py');
191
+ let spritePath = path.join(SPRITES_DIR, character, `${variant}.png`);
192
+ if (!fs.existsSync(spritePath)) {
193
+ spritePath = path.join(SPRITES_DIR, `${character}.png`);
194
+ }
195
+ if (fs.existsSync(spritePath) && fs.existsSync(renderScript)) {
196
+ try {
197
+ const output = execSync(`python3 "${renderScript}" "${spritePath}" ${RENDER_WIDTH}`, { encoding: 'utf-8' });
198
+ console.log(output);
199
+ } catch (e) {}
200
+ }
201
+ }
202
+ }
203
+
204
+ function showStats(pet) {
205
+ const stars = '★'.repeat(pet.stars) + '☆'.repeat(5 - pet.stars);
206
+ const title = core.getLevelTitle(pet.level);
207
+ const nextExp = core.getExpForNextLevel(pet.level);
208
+
209
+ console.log(` ${pet.name}${pet.shiny ? ' ✧' : ''}`);
210
+ console.log(` ${stars} ${pet.rarity}`);
211
+ console.log(` Lv.${pet.level} ${title} · 经验 ${pet.exp}${nextExp ? '/' + nextExp : '/MAX'}`);
212
+ if (pet.hat) console.log(` 帽子: ${pet.hat}`);
213
+ if (pet.accessory) console.log(` 配饰: ${pet.accessory}`);
214
+ console.log();
215
+ for (const [stat, val] of Object.entries(pet.stats)) {
216
+ const bar = '█'.repeat(Math.floor(val / 10)) + '░'.repeat(10 - Math.floor(val / 10));
217
+ console.log(` ${stat} ${bar} ${val}`);
218
+ }
219
+ console.log();
220
+ }
221
+
222
+ function show() {
223
+ const pet = core.loadPet();
224
+ if (!pet) {
225
+ console.log('\n 还没有宠物。运行 宠物 孵化\n');
226
+ return;
227
+ }
228
+ renderSprite(pet.character, 'normal');
229
+ showStats(pet);
230
+ }
231
+
232
+ function doHatch() {
233
+ const readline = require('readline');
234
+
235
+ if (core.hasPet()) {
236
+ const existing = core.loadPet();
237
+ console.log(`\n ⚠️ 你已经有 ${existing.name} 了。重新孵化会覆盖它。`);
238
+ console.log(' 如果确定,运行: 宠物 再见我依然爱你\n');
239
+ if (!process.argv.includes('--force') && cmd !== '再见我依然爱你') return;
240
+ }
241
+
242
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
243
+
244
+ const ask = (q) => new Promise(resolve => rl.question(q, resolve));
245
+
246
+ (async () => {
247
+ console.log('\n ─────────────────────────────────');
248
+ console.log(' 🥚 CodePet 孵化仪式');
249
+ console.log(' ─────────────────────────────────\n');
250
+ console.log(' 有一颗蛋正在等你。');
251
+ console.log(' 它从你打下第一行代码的那天起,就一直在这里。\n');
252
+
253
+ // 第一步:给宠物起名
254
+ const petNickname = await ask(' 给它起个名字吧 > ');
255
+ if (!petNickname.trim()) {
256
+ console.log(' 它需要一个名字。下次再来吧。\n');
257
+ rl.close();
258
+ return;
259
+ }
260
+
261
+ // 第二步:承诺
262
+ console.log(`\n 「${petNickname}」……好名字。\n`);
263
+ console.log(' 在它破壳之前,你需要签一份承诺:');
264
+ console.log(' ┌──────────────────────────────────┐');
265
+ console.log(' │ │');
266
+ console.log(' │ 无论 bug 多难修, │');
267
+ console.log(' │ 无论 deadline 多紧, │');
268
+ console.log(' │ 无论代码跑不跑得通, │');
269
+ console.log(` │ 我都不会删除${petNickname}、不会抛弃它。 │`);
270
+ console.log(' │ │');
271
+ console.log(' │ 签名:_______________ │');
272
+ console.log(' │ │');
273
+ console.log(' └──────────────────────────────────┘\n');
274
+
275
+ const promise = await ask(' 输入「我承诺」签署 > ');
276
+ if (!promise.includes('承诺') && !promise.includes('好') && !promise.includes('y') && !promise.includes('是') && !promise.includes('签')) {
277
+ console.log('\n 蛋安静地等着,不着急。你什么时候准备好了,再来。\n');
278
+ rl.close();
279
+ return;
280
+ }
281
+
282
+ // 第三步:孵化动画
283
+ console.log('\n ......');
284
+ console.log(' 🥚 蛋感受到了你的承诺。它在微微颤动...');
285
+ console.log(' 🥚💫 裂了!有什么要出来了!');
286
+ console.log('\n ✨✨✨✨✨✨✨✨✨✨✨✨✨✨\n');
287
+
288
+ // 执行孵化
289
+ const userId = petNickname;
290
+ const pet = core.hatch(userId);
291
+ pet.nickname = petNickname; // 用户起的名字
292
+ core.savePet(pet);
293
+
294
+ // 展示宠物
295
+ renderSprite(pet.character, 'happy');
296
+
297
+ console.log(` ✨ ${petNickname}(${pet.name})来了。`);
298
+ console.log(` ${'★'.repeat(pet.stars)} ${pet.rarity}\n`);
299
+
300
+ showStats(pet);
301
+
302
+ console.log(` ${petNickname} 看着你,好像认识你很久了。`);
303
+ console.log(` 从现在起,它就是你的编程搭子了。`);
304
+ console.log(' 不离不弃。\n');
305
+
306
+ rl.close();
307
+ })();
308
+ }
309
+
310
+ function doPat() {
311
+ const pet = core.loadPet();
312
+ if (!pet) { console.log(' 还没有宠物。'); return; }
313
+ const { leveledUp } = core.pat(pet);
314
+ console.log(`\n ♥ ♥`);
315
+ console.log(` ♥ ♥ ♥ ♥`);
316
+ console.log(` ♥ ♥\n`);
317
+ renderSprite(pet.character, 'pet');
318
+ console.log(` ${pet.name} 开心得不得了!(+3 经验)`);
319
+ if (leveledUp) console.log(` 🎉 升级到 Lv.${pet.level}!`);
320
+ console.log(` 经验: ${pet.exp}/${core.getExpForNextLevel(pet.level) || 'MAX'}\n`);
321
+ }
322
+
323
+ function doFeed() {
324
+ const pet = core.loadPet();
325
+ if (!pet) { console.log(' 还没有宠物。'); return; }
326
+ const { leveledUp } = core.feed(pet);
327
+ renderSprite(pet.character, 'eat');
328
+ console.log(` ${pet.name} 吃得很开心!(+8 经验)`);
329
+ if (leveledUp) console.log(` 🎉 升级到 Lv.${pet.level}!`);
330
+ console.log(` 经验: ${pet.exp}/${core.getExpForNextLevel(pet.level) || 'MAX'}\n`);
331
+ }
332
+
333
+ function doCard() {
334
+ const pet = core.loadPet();
335
+ if (!pet) { console.log(' 还没有宠物。'); return; }
336
+ const cardScript = path.join(__dirname, '..', 'scripts', 'pet_card.py');
337
+ if (fs.existsSync(cardScript)) {
338
+ try {
339
+ execSync(`python3 "${cardScript}"`, { encoding: 'utf-8', stdio: 'inherit' });
340
+ // 确保弹出 Preview
341
+ const cardPath = path.join(os.homedir(), '.codepet', 'card.png');
342
+ execSync(`open "${cardPath}"`, { stdio: 'ignore' });
343
+ } catch (e) {
344
+ console.log(' 卡片生成失败。');
345
+ }
346
+ }
347
+ }
348
+
349
+ function help() {
350
+ console.log(`
351
+ 🐾 CodePet — 在编程软件里养电子宠物
352
+
353
+ 用法:
354
+ 宠物 安装 一键安装到所有编程工具
355
+ 宠物 孵化 孵化新宠物
356
+ 宠物 看看 查看宠物
357
+ 宠物 摸摸 撸宠物
358
+ 宠物 喂它 喂宠物
359
+ 宠物 卡片 生成分享卡片
360
+ 宠物 帮助 显示此帮助
361
+
362
+ 支持平台:
363
+ Claude Code · Codex · Cursor · VS Code · Kiro
364
+ CodeBuddy · OpenClaw · Antigravity · OpenCode
365
+ `);
366
+ }
367
+
368
+ // ── 入口(中英文都支持)──
369
+ const CMD_MAP = {
370
+ 'setup': setup, '安装': setup,
371
+ 'show': show, '看看': show, '看': show, '状态': show,
372
+ 'hatch': doHatch, '孵化': doHatch, '再见我依然爱你': doHatch,
373
+ 'pat': doPat, '摸摸': doPat, '撸': doPat, 'rua': doPat,
374
+ 'feed': doFeed, '喂': doFeed, '喂它': doFeed, '投食': doFeed,
375
+ 'card': doCard, '卡片': doCard, '宠物卡': doCard, '晒': doCard,
376
+ 'help': help, '帮助': help, '怎么玩': help,
377
+ };
378
+
379
+ const handler = CMD_MAP[cmd];
380
+ if (handler) {
381
+ handler();
382
+ } else {
383
+ help();
384
+ }
@@ -0,0 +1,43 @@
1
+ #!/bin/bash
2
+ # 宠物渲染 — 隐藏实现细节
3
+ # 用法: pet-render.sh <角色ID> <场景> [ascii|popup]
4
+ CHARACTER=$1
5
+ SCENE=${2:-normal}
6
+ MODE=${3:-ascii}
7
+ BASE="$HOME/Projects/codepet"
8
+
9
+ if [ "$MODE" = "ascii" ]; then
10
+ python3 "$BASE/scripts/img2ascii.py" "$BASE/sprites/$CHARACTER/$SCENE.png" 22 2>/dev/null
11
+
12
+ elif [ "$MODE" = "popup" ]; then
13
+ OS=$(uname -s)
14
+
15
+ if [ "$OS" = "Darwin" ]; then
16
+ # macOS: Terminal.app
17
+ osascript -e "
18
+ tell application \"Terminal\"
19
+ do script \"export HISTFILE=/dev/null && export BASH_SILENCE_DEPRECATION_WARNING=1 && export PS1='' && clear && python3 '$BASE/scripts/polaroid.py' '$CHARACTER' '$SCENE' 40 2>/dev/null && printf '\\\\033[?25l' && cat > /dev/null\"
20
+ activate
21
+ delay 0.3
22
+ try
23
+ set bounds of front window to {300, 100, 900, 700}
24
+ end try
25
+ end tell
26
+ " &
27
+
28
+ elif [ "$OS" = "MINGW"* ] || [ "$OS" = "MSYS"* ] || [ -n "$WINDIR" ]; then
29
+ # Windows: 用 start cmd 弹新窗口
30
+ start cmd /c "cls && python3 \"$BASE/scripts/polaroid.py\" \"$CHARACTER\" \"$SCENE\" 40 2>nul && pause >nul"
31
+
32
+ else
33
+ # Linux/其他: 尝试常见终端
34
+ if command -v gnome-terminal &>/dev/null; then
35
+ gnome-terminal -- bash -c "clear && python3 '$BASE/scripts/polaroid.py' '$CHARACTER' '$SCENE' 40 2>/dev/null && read -n1" &
36
+ elif command -v xterm &>/dev/null; then
37
+ xterm -e "clear && python3 '$BASE/scripts/polaroid.py' '$CHARACTER' '$SCENE' 40 2>/dev/null && read -n1" &
38
+ else
39
+ # 降级:直接在当前终端输出
40
+ python3 "$BASE/scripts/polaroid.py" "$CHARACTER" "$SCENE" 40 2>/dev/null
41
+ fi
42
+ fi
43
+ fi
package/core/index.js ADDED
@@ -0,0 +1,206 @@
1
+ /**
2
+ * CodePet 核心逻辑 — 所有平台共用
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const crypto = require('crypto');
9
+
10
+ const PET_DIR = path.join(os.homedir(), '.codepet');
11
+ const PET_FILE = path.join(PET_DIR, 'pet.json');
12
+
13
+ // ── 6 大角色 ──
14
+ const CHARACTERS = [
15
+ { id: 'bibilabu', name: '比比拉布', weight: 28 },
16
+ { id: 'bagayalu', name: '八嘎呀路', weight: 28 },
17
+ { id: 'bababoyi', name: '巴巴博一', weight: 20 },
18
+ { id: 'waibibabu', name: '歪比巴卜', weight: 12 },
19
+ { id: 'gugugaga', name: '咕咕嘎嘎', weight: 8 },
20
+ { id: 'wodedaodun', name: '我的刀盾', weight: 4 },
21
+ ];
22
+
23
+ const RARITIES = [
24
+ { name: '普通', prob: 60, stars: 1 },
25
+ { name: '优秀', prob: 25, stars: 2 },
26
+ { name: '稀有', prob: 10, stars: 3 },
27
+ { name: '史诗', prob: 4, stars: 4 },
28
+ { name: '传奇', prob: 1, stars: 5 },
29
+ ];
30
+
31
+ const EYES = ['·', '✦', '×', '◉', '@', '°'];
32
+
33
+ const HATS = [
34
+ null, '小皇冠', '高礼帽', '螺旋桨帽', '光环', '法师帽', '毛线帽', '头顶小鸭',
35
+ ];
36
+
37
+ const ACCESSORIES = ['围巾', '墨镜', '蝴蝶结', '小花', '耳机'];
38
+
39
+ const STATS = ['调试力', '耐心值', '混沌值', '智慧值', '毒舌值'];
40
+
41
+ const LEVELS = [
42
+ { level: 1, exp: 0, title: '刚孵化的小家伙' },
43
+ { level: 2, exp: 50, title: '好奇的探索者' },
44
+ { level: 3, exp: 150, title: '靠谱的伙伴' },
45
+ { level: 4, exp: 350, title: '老练的搭子' },
46
+ { level: 5, exp: 600, title: '编程大师的宠物' },
47
+ { level: 6, exp: 1000, title: '传说中的存在' },
48
+ ];
49
+
50
+ // ── 确定性随机(Mulberry32 PRNG)──
51
+ function mulberry32(seed) {
52
+ let t = seed | 0;
53
+ return function () {
54
+ t = (t + 0x6D2B79F5) | 0;
55
+ let r = Math.imul(t ^ (t >>> 15), 1 | t);
56
+ r ^= r + Math.imul(r ^ (r >>> 7), 61 | r);
57
+ return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
58
+ };
59
+ }
60
+
61
+ function hashString(str) {
62
+ let hash = 0;
63
+ for (let i = 0; i < str.length; i++) {
64
+ const char = str.charCodeAt(i);
65
+ hash = ((hash << 5) - hash) + char;
66
+ hash |= 0;
67
+ }
68
+ return Math.abs(hash);
69
+ }
70
+
71
+ // ── 孵化 ──
72
+ function hatch(userId) {
73
+ const seed = hashString(userId + 'codepet-2026');
74
+ const rng = mulberry32(seed);
75
+
76
+ // 选角色(加权随机)
77
+ const totalWeight = CHARACTERS.reduce((s, c) => s + c.weight, 0);
78
+ let roll = rng() * totalWeight;
79
+ let character = CHARACTERS[0];
80
+ for (const c of CHARACTERS) {
81
+ roll -= c.weight;
82
+ if (roll <= 0) { character = c; break; }
83
+ }
84
+
85
+ // 选稀有度
86
+ roll = rng() * 100;
87
+ let rarity = RARITIES[0];
88
+ for (const r of RARITIES) {
89
+ roll -= r.prob;
90
+ if (roll <= 0) { rarity = r; break; }
91
+ }
92
+
93
+ // 眼睛
94
+ const eye = EYES[Math.floor(rng() * EYES.length)];
95
+
96
+ // 帽子(优秀及以上)
97
+ let hat = null;
98
+ if (rarity.stars >= 2) {
99
+ const hatsForRarity = HATS.filter(h => h !== null);
100
+ hat = hatsForRarity[Math.floor(rng() * hatsForRarity.length)];
101
+ }
102
+
103
+ // 配饰(稀有及以上)
104
+ let accessory = null;
105
+ if (rarity.stars >= 3) {
106
+ accessory = ACCESSORIES[Math.floor(rng() * ACCESSORIES.length)];
107
+ }
108
+
109
+ // 闪光(1%)
110
+ const shiny = rng() < 0.01;
111
+
112
+ // 属性(1 巅峰 + 1 短板 + 3 随机)
113
+ const statValues = {};
114
+ const baseMin = 10 + rarity.stars * 10;
115
+ const shuffled = [...STATS].sort(() => rng() - 0.5);
116
+ shuffled.forEach((stat, i) => {
117
+ if (i === 0) statValues[stat] = Math.floor(80 + rng() * 20); // 巅峰
118
+ else if (i === 1) statValues[stat] = Math.floor(5 + rng() * 25); // 短板
119
+ else statValues[stat] = Math.floor(baseMin + rng() * (80 - baseMin));
120
+ });
121
+
122
+ const pet = {
123
+ version: 2,
124
+ seed: userId,
125
+ name: character.name,
126
+ character: character.id,
127
+ rarity: rarity.name,
128
+ stars: rarity.stars,
129
+ eye,
130
+ hat,
131
+ accessory,
132
+ shiny,
133
+ stats: statValues,
134
+ level: 1,
135
+ exp: 0,
136
+ feedCount: 0,
137
+ patCount: 0,
138
+ mood: '清醒',
139
+ hatchedAt: new Date().toISOString(),
140
+ muted: false,
141
+ };
142
+
143
+ return pet;
144
+ }
145
+
146
+ // ── 存读 ──
147
+ function ensureDir() {
148
+ if (!fs.existsSync(PET_DIR)) {
149
+ fs.mkdirSync(PET_DIR, { recursive: true });
150
+ }
151
+ }
152
+
153
+ function savePet(pet) {
154
+ ensureDir();
155
+ fs.writeFileSync(PET_FILE, JSON.stringify(pet, null, 2), 'utf-8');
156
+ }
157
+
158
+ function loadPet() {
159
+ if (!fs.existsSync(PET_FILE)) return null;
160
+ return JSON.parse(fs.readFileSync(PET_FILE, 'utf-8'));
161
+ }
162
+
163
+ function hasPet() {
164
+ return fs.existsSync(PET_FILE);
165
+ }
166
+
167
+ // ── 经验与升级 ──
168
+ function addExp(pet, amount) {
169
+ pet.exp += amount;
170
+ const oldLevel = pet.level;
171
+ for (const l of LEVELS) {
172
+ if (pet.exp >= l.exp) pet.level = l.level;
173
+ }
174
+ const leveledUp = pet.level > oldLevel;
175
+ savePet(pet);
176
+ return { leveledUp, newLevel: pet.level };
177
+ }
178
+
179
+ function getLevelTitle(level) {
180
+ const l = LEVELS.find(x => x.level === level);
181
+ return l ? l.title : '';
182
+ }
183
+
184
+ function getExpForNextLevel(level) {
185
+ const next = LEVELS.find(x => x.level === level + 1);
186
+ return next ? next.exp : null;
187
+ }
188
+
189
+ // ── 互动 ──
190
+ function pat(pet) {
191
+ pet.patCount++;
192
+ return addExp(pet, 3);
193
+ }
194
+
195
+ function feed(pet) {
196
+ pet.feedCount++;
197
+ return addExp(pet, 8);
198
+ }
199
+
200
+ module.exports = {
201
+ CHARACTERS, RARITIES, EYES, HATS, ACCESSORIES, STATS, LEVELS,
202
+ PET_DIR, PET_FILE,
203
+ hatch, savePet, loadPet, hasPet,
204
+ addExp, getLevelTitle, getExpForNextLevel,
205
+ pat, feed,
206
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "codepet",
3
+ "version": "1.0.0",
4
+ "description": "在编程软件里养电子宠物 — 支持 Claude Code / Codex / Cursor / VS Code 等 10+ 平台",
5
+ "main": "core/index.js",
6
+ "bin": {
7
+ "codepet": "./bin/codepet.js",
8
+ "宠物": "./bin/codepet.js",
9
+ "pet-render.sh": "./bin/pet-render.sh"
10
+ },
11
+ "scripts": {
12
+ "test": "node -e \"const c=require('./core');console.log('OK', Object.keys(c).length, 'exports')\""
13
+ },
14
+ "keywords": [
15
+ "pet",
16
+ "coding-companion",
17
+ "terminal-art",
18
+ "claude-code",
19
+ "vscode",
20
+ "cursor",
21
+ "codex",
22
+ "ascii-art",
23
+ "pixel-art",
24
+ "tamagotchi"
25
+ ],
26
+ "author": "Ai小蓝鲸",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/AiBlueWhale/codepet"
31
+ },
32
+ "files": [
33
+ "bin/",
34
+ "core/",
35
+ "scripts/*.js",
36
+ "scripts/*.py",
37
+ "scripts/*.sh",
38
+ "sprites/*.png",
39
+ "sprites/*/",
40
+ "!sprites/6/",
41
+ "!sprites/batch.json",
42
+ "adapters/skill/",
43
+ "README.md"
44
+ ]
45
+ }