claude-buddy-reroll 1.8.13 → 1.8.14
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 +3 -0
- package/package.json +1 -1
- package/src/cli.mjs +43 -24
- package/src/collection.mjs +7 -0
- package/src/display.mjs +2 -1
- package/src/profile-state.mjs +5 -0
- package/src/ui.mjs +17 -4
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ npm i -g claude-buddy-reroll
|
|
|
19
19
|
|
|
20
20
|
기본 화면은 스피키 스타일의 리치 터미널 홈으로 시작하고, `dex`는 발견한 개체를 다시 적용할 수 있게 설계돼 있어요.
|
|
21
21
|
지금은 `fullscreen dex`, `animated preview`, `form collection`, `one-by-one gacha reveal`까지 들어가 있어요.
|
|
22
|
+
여러 Claude 계정이 있어도 현재 계정을 직접 읽어서 계정별 프로필 상태를 따로 저장해요.
|
|
22
23
|
|
|
23
24
|
처음 설치할 때 런타임 셋업이 자동으로 Bun과 Claude 연동을 준비해요. 자동 셋업이 실패했거나 `Bun is required` 에러가 보이면 아래를 한 번 실행하세요.
|
|
24
25
|
|
|
@@ -27,6 +28,7 @@ bdy setup
|
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
`bdy update`도 업데이트 후 같은 셋업을 자동으로 다시 확인해요.
|
|
31
|
+
그리고 현재 계정 기준으로 도감 정합성도 다시 맞춰요.
|
|
30
32
|
|
|
31
33
|
---
|
|
32
34
|
|
|
@@ -51,6 +53,7 @@ bdy setup # Bun / 런타임 셋업 복구
|
|
|
51
53
|
- species progress와 form collection을 분리한 도감
|
|
52
54
|
- 10연차 one-by-one reveal + highlight-aware skip
|
|
53
55
|
- 오른쪽 상세 패널에 animated preview / flavor text / rarity track / form gallery
|
|
56
|
+
- 현재 Claude 계정을 매번 직접 읽고 계정별 프로필 상태를 분리 저장
|
|
54
57
|
- JSON 모드는 유지해서 에이전트/스크립트 자동화도 가능
|
|
55
58
|
|
|
56
59
|
### Agent DX (JSON 모드)
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { roll, multiRoll, ORIGINAL_SALT, EYES, HATS, STATS, RARITY_STARS, SPECIE
|
|
|
18
18
|
import { patchSalt, clearSoul, restoreOriginal } from './patcher.mjs';
|
|
19
19
|
import { MIN_SUPPORTED_CLAUDE_VERSION, resolveClaudeContext, updatePatchedSalt } from './context.mjs';
|
|
20
20
|
import { renderCard, renderMiniCard } from './display.mjs';
|
|
21
|
-
import { addBatchToCollection, getCollection, getLatestVariant, getPreferredVariant, getRarityCompletion, getShinyVariant, renderCollection } from './collection.mjs';
|
|
21
|
+
import { addBatchToCollection, getCollection, getLatestVariant, getPreferredVariant, getRarityCompletion, getShinyVariant, renderCollection, resyncCollection } from './collection.mjs';
|
|
22
22
|
import { select } from './selector.mjs';
|
|
23
23
|
import { playHatchAnimation } from './animation.mjs';
|
|
24
24
|
import { createInterface, emitKeypressEvents } from 'readline';
|
|
@@ -30,7 +30,7 @@ import { renderSprite } from './sprites.mjs';
|
|
|
30
30
|
import { formatEye, formatStars, toTerminalSafeText } from './terminal.mjs';
|
|
31
31
|
import { findNextHighlightIndex, getRevealAction } from './gacha-reveal.mjs';
|
|
32
32
|
import { BUDDY_LORE, wrapLore } from './buddy-lore.mjs';
|
|
33
|
-
import { loadProfileState, saveProfileState } from './profile-state.mjs';
|
|
33
|
+
import { listKnownProfiles, loadProfileState, saveProfileState } from './profile-state.mjs';
|
|
34
34
|
|
|
35
35
|
const BOLD = '\x1b[1m';
|
|
36
36
|
const DIM = '\x1b[2m';
|
|
@@ -119,6 +119,10 @@ function mapResult(result, index = null) {
|
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
function formatProfileBadge(userId) {
|
|
123
|
+
return userId === 'anon' ? 'anonymous' : `${userId.slice(0, 8)}...`;
|
|
124
|
+
}
|
|
125
|
+
|
|
122
126
|
// ─── Star check ─────────────────────────────────────
|
|
123
127
|
|
|
124
128
|
const _starCache = new Map();
|
|
@@ -366,14 +370,15 @@ function buildTamagotchiPreview(species, entry, tick = 0) {
|
|
|
366
370
|
...lines.map((line) => ` | ${toTerminalSafeText(line).padEnd(width)} |`),
|
|
367
371
|
' +----------------------+',
|
|
368
372
|
];
|
|
369
|
-
const
|
|
373
|
+
const fit = (value) => toTerminalSafeText(String(value || '').slice(0, width)).padEnd(width);
|
|
374
|
+
const formLine = (label, value) => fit(`${label} ${value}`);
|
|
370
375
|
|
|
371
376
|
const variant = entry ? getPreferredVariant(entry) : null;
|
|
372
377
|
if (!variant) {
|
|
373
378
|
return [
|
|
374
379
|
`${MAGENTA}${BOLD} PIXEL PREVIEW${RESET}`,
|
|
375
380
|
` ${DIM}undiscovered buddy${RESET}`,
|
|
376
|
-
...panel('PROFILE', ['best --', 'latest --', 'shiny
|
|
381
|
+
...panel('PROFILE', ['best --', 'latest --', 'shiny no', 'forms 0']),
|
|
377
382
|
...panel('PREVIEW', [
|
|
378
383
|
' .------. ',
|
|
379
384
|
' / ?? ?? \\ ',
|
|
@@ -383,24 +388,28 @@ function buildTamagotchiPreview(species, entry, tick = 0) {
|
|
|
383
388
|
'',
|
|
384
389
|
]),
|
|
385
390
|
...panel('FLAVOR TEXT', ['Find this species', 'in gacha first,', 'then come back to', 'unlock its card.']),
|
|
386
|
-
...panel('RARITY TRACK', ['
|
|
387
|
-
...panel('FORMS', [
|
|
391
|
+
...panel('RARITY TRACK', ['[ ] common', '[ ] uncommon', '[ ] rare', '[ ] epic', '[ ] legendary']),
|
|
392
|
+
...panel('FORMS', ['1. --', '2. --', '3. --', '4. --']),
|
|
388
393
|
].join('\n');
|
|
389
394
|
}
|
|
390
395
|
|
|
391
396
|
const latestVariant = getLatestVariant(entry);
|
|
392
397
|
const shinyVariant = getShinyVariant(entry);
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
398
|
+
const rarityTrack = getRarityCompletion(entry).map(({ rarity, found }) =>
|
|
399
|
+
fit(`${found ? '[x]' : '[ ]'} ${rarity.padEnd(9)} ${formatStars(RARITY_STARS[rarity])}`),
|
|
400
|
+
);
|
|
396
401
|
const gallery = entry.variants
|
|
397
402
|
.slice(0, 4)
|
|
398
|
-
.map((item) => {
|
|
399
|
-
const
|
|
400
|
-
|
|
403
|
+
.map((item, index) => {
|
|
404
|
+
const tags = [
|
|
405
|
+
item.salt === variant.salt ? 'BEST' : null,
|
|
406
|
+
latestVariant && item.salt === latestVariant.salt ? 'NEW' : null,
|
|
407
|
+
item.bones.shiny ? 'SHN' : null,
|
|
408
|
+
].filter(Boolean).join('/');
|
|
409
|
+
const core = `${formatStars(RARITY_STARS[item.bones.rarity])} ${formatEye(item.bones.eye)} ${toTerminalSafeText(item.bones.hat)}`;
|
|
410
|
+
return fit(`${index + 1}. ${core}${tags ? ` ${tags}` : ''}`);
|
|
401
411
|
});
|
|
402
|
-
while (gallery.length < 4) gallery.push('--');
|
|
403
|
-
const rarityTrack = getRarityCompletion(entry).map(({ rarity, found }) => `${found ? formatStars(RARITY_STARS[rarity]) : '--'} ${rarity}`);
|
|
412
|
+
while (gallery.length < 4) gallery.push(fit('--'));
|
|
404
413
|
|
|
405
414
|
const stars = formatStars(RARITY_STARS[variant.bones.rarity]);
|
|
406
415
|
const loreLines = wrapLore(BUDDY_LORE[species] || '', 20, 4);
|
|
@@ -423,14 +432,14 @@ function buildTamagotchiPreview(species, entry, tick = 0) {
|
|
|
423
432
|
` ${DIM}${variant.bones.rarity} • x${entry.count}${variant.bones.shiny ? ' • shiny' : ''}${RESET}`,
|
|
424
433
|
...panel('PREVIEW', sprite),
|
|
425
434
|
...panel('PROFILE', [
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
435
|
+
formLine('eye', `${formatEye(variant.bones.eye)} • ${toTerminalSafeText(variant.bones.hat)}`),
|
|
436
|
+
formLine('best', `${formatStars(RARITY_STARS[variant.bones.rarity])} ${variant.bones.rarity}`),
|
|
437
|
+
formLine('latest', latestVariant ? `${formatStars(RARITY_STARS[latestVariant.bones.rarity])} ${latestVariant.bones.rarity}` : '--'),
|
|
438
|
+
formLine('shiny', `${shinyVariant ? 'yes' : 'no'} • forms ${entry.variants.length}`),
|
|
430
439
|
]),
|
|
431
440
|
...panel('FLAVOR TEXT', loreLines),
|
|
432
|
-
...panel('RARITY TRACK',
|
|
433
|
-
...panel('FORMS',
|
|
441
|
+
...panel('RARITY TRACK', rarityTrack),
|
|
442
|
+
...panel('FORMS', gallery),
|
|
434
443
|
].join('\n');
|
|
435
444
|
}
|
|
436
445
|
|
|
@@ -583,6 +592,7 @@ async function cmdCheck() {
|
|
|
583
592
|
output({
|
|
584
593
|
command: 'check',
|
|
585
594
|
account: userId.slice(0, 8),
|
|
595
|
+
profiles: listKnownProfiles(),
|
|
586
596
|
install: install ? { type: install.type, path: install.path } : null,
|
|
587
597
|
salt: salt,
|
|
588
598
|
patched: salt !== ORIGINAL_SALT,
|
|
@@ -602,6 +612,7 @@ async function cmdCheck() {
|
|
|
602
612
|
quotaLines: renderQuotaSummary({ used, limit, starred, eventRemaining }),
|
|
603
613
|
buddyCard: renderCard(buddy, { showSalt: true }),
|
|
604
614
|
}));
|
|
615
|
+
console.log(`${DIM}Profile: ${formatProfileBadge(userId)} | Known profiles: ${listKnownProfiles().length}${RESET}\n`);
|
|
605
616
|
}
|
|
606
617
|
|
|
607
618
|
async function cmdGacha(count = 10) {
|
|
@@ -1177,6 +1188,7 @@ function cmdSchema(subCmd) {
|
|
|
1177
1188
|
|
|
1178
1189
|
async function cmdUpdate() {
|
|
1179
1190
|
const { execSync } = await import('child_process');
|
|
1191
|
+
const context = resolveClaudeContext();
|
|
1180
1192
|
|
|
1181
1193
|
if (flags.json) {
|
|
1182
1194
|
try {
|
|
@@ -1184,8 +1196,9 @@ async function cmdUpdate() {
|
|
|
1184
1196
|
const current = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8')).version;
|
|
1185
1197
|
execSync('npm install -g claude-buddy-reroll@latest', { stdio: 'pipe' });
|
|
1186
1198
|
runRuntimeSetup({ stdio: 'pipe' });
|
|
1199
|
+
resyncCollection(context.userId);
|
|
1187
1200
|
const updated = execSync('npm info claude-buddy-reroll version', { encoding: 'utf-8' }).trim();
|
|
1188
|
-
output({ command: 'update', previous: current, current: updated, updated: current !== updated, setup: true });
|
|
1201
|
+
output({ command: 'update', previous: current, current: updated, updated: current !== updated, setup: true, resynced: true });
|
|
1189
1202
|
} catch (e) {
|
|
1190
1203
|
errorJson('UPDATE_FAILED', e.message);
|
|
1191
1204
|
}
|
|
@@ -1200,11 +1213,13 @@ async function cmdUpdate() {
|
|
|
1200
1213
|
execSync('npm install -g claude-buddy-reroll@latest', { stdio: 'inherit' });
|
|
1201
1214
|
console.log(`\n${DIM} 런타임 셋업 확인 중...${RESET}`);
|
|
1202
1215
|
runRuntimeSetup({ stdio: 'inherit' });
|
|
1216
|
+
console.log(`${DIM} 현재 계정 도감 정합성 재검증 중...${RESET}`);
|
|
1217
|
+
resyncCollection(context.userId);
|
|
1203
1218
|
const newVer = execSync('npm info claude-buddy-reroll version', { encoding: 'utf-8' }).trim();
|
|
1204
1219
|
if (newVer !== pkg.version) {
|
|
1205
|
-
console.log(`\n${GREEN} ✓ v${pkg.version} → v${newVer} 업데이트 완료!
|
|
1220
|
+
console.log(`\n${GREEN} ✓ v${pkg.version} → v${newVer} 업데이트 완료! 도감도 갱신했어요.${RESET}\n`);
|
|
1206
1221
|
} else {
|
|
1207
|
-
console.log(`\n${GREEN} ✓ 이미 최신이에요! (v${pkg.version})
|
|
1222
|
+
console.log(`\n${GREEN} ✓ 이미 최신이에요! (v${pkg.version}) 도감 정합성만 다시 맞췄어요.${RESET}\n`);
|
|
1208
1223
|
}
|
|
1209
1224
|
} catch (e) {
|
|
1210
1225
|
console.log(`\n${RED} ✗ 업데이트 실패: ${e.message}${RESET}`);
|
|
@@ -1218,10 +1233,12 @@ function runRuntimeSetup({ stdio = 'inherit' } = {}) {
|
|
|
1218
1233
|
}
|
|
1219
1234
|
|
|
1220
1235
|
async function cmdSetup() {
|
|
1236
|
+
const context = resolveClaudeContext();
|
|
1221
1237
|
if (flags.json) {
|
|
1222
1238
|
try {
|
|
1223
1239
|
runRuntimeSetup({ stdio: 'pipe' });
|
|
1224
|
-
|
|
1240
|
+
resyncCollection(context.userId);
|
|
1241
|
+
output({ command: 'setup', success: true, resynced: true });
|
|
1225
1242
|
} catch (error) {
|
|
1226
1243
|
errorJson('SETUP_FAILED', error?.message || String(error));
|
|
1227
1244
|
}
|
|
@@ -1231,6 +1248,8 @@ async function cmdSetup() {
|
|
|
1231
1248
|
console.log(`\n${MAGENTA}${BOLD} 쪼아요~! 런타임 셋업 확인할게요.${RESET}\n`);
|
|
1232
1249
|
try {
|
|
1233
1250
|
runRuntimeSetup({ stdio: 'inherit' });
|
|
1251
|
+
console.log(`${DIM} 현재 계정 도감 정합성 재검증 중...${RESET}`);
|
|
1252
|
+
resyncCollection(context.userId);
|
|
1234
1253
|
console.log(`\n${GREEN} ✓ setup complete${RESET}\n`);
|
|
1235
1254
|
} catch (error) {
|
|
1236
1255
|
console.log(`\n${RED} ✗ setup failed: ${error?.message || String(error)}${RESET}\n`);
|
package/src/collection.mjs
CHANGED
|
@@ -187,6 +187,13 @@ export function getCollection(ownerId = null) {
|
|
|
187
187
|
return ingestCollectionResults(profile.collection || {}, [], ownerId);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
export function resyncCollection(ownerId = null) {
|
|
191
|
+
const profile = loadProfileState(ownerId || 'anon');
|
|
192
|
+
profile.collection = ingestCollectionResults(profile.collection || {}, [], ownerId);
|
|
193
|
+
saveProfileState(ownerId || 'anon', profile);
|
|
194
|
+
return profile.collection;
|
|
195
|
+
}
|
|
196
|
+
|
|
190
197
|
export function getCollectionStats(ownerId = null) {
|
|
191
198
|
const col = getCollection(ownerId);
|
|
192
199
|
const collected = Object.keys(col).length;
|
package/src/display.mjs
CHANGED
|
@@ -79,12 +79,13 @@ export function renderMiniCard(result, index) {
|
|
|
79
79
|
const { rarity, species, eye, shiny } = bones;
|
|
80
80
|
const style = RARITY_STYLE[rarity];
|
|
81
81
|
const stars = formatStars(RARITY_STARS[rarity]);
|
|
82
|
+
const badge = style.badge.padEnd(9);
|
|
82
83
|
const shinyTag = shiny
|
|
83
84
|
? (isAsciiOnlyTerminal() ? '[SHINY]' : formatShinyTag('✨'))
|
|
84
85
|
: '';
|
|
85
86
|
|
|
86
87
|
const idx = String(index + 1).padStart(2, ' ');
|
|
87
|
-
return `${style.color}${idx}. ${formatEye(eye)} ${species.padEnd(10)} ${stars.padEnd(5)} ${shinyTag.padEnd(8)}${RESET}`;
|
|
88
|
+
return `${style.color}${idx}. ${formatEye(eye)} ${species.padEnd(10)} ${stars.padEnd(5)} ${badge} ${shinyTag.padEnd(8)}${RESET}`;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
// Dex rendering is handled directly in cli.mjs
|
package/src/profile-state.mjs
CHANGED
|
@@ -25,6 +25,11 @@ function readRootState() {
|
|
|
25
25
|
return {};
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export function listKnownProfiles() {
|
|
29
|
+
const root = readRootState();
|
|
30
|
+
return Object.keys(root.profiles || {}).sort();
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
function migrateLegacyIntoProfile(root, userId) {
|
|
29
34
|
const hasLegacy = LEGACY_KEYS.some((key) => key in root);
|
|
30
35
|
if (!hasLegacy) return;
|
package/src/ui.mjs
CHANGED
|
@@ -9,6 +9,15 @@ const MAGENTA = '\x1b[35m';
|
|
|
9
9
|
const YELLOW = '\x1b[33m';
|
|
10
10
|
const RED = '\x1b[31m';
|
|
11
11
|
const WHITE = '\x1b[37m';
|
|
12
|
+
const GRAY = '\x1b[90m';
|
|
13
|
+
|
|
14
|
+
const RARITY_TONE = {
|
|
15
|
+
common: WHITE,
|
|
16
|
+
uncommon: GREEN,
|
|
17
|
+
rare: CYAN,
|
|
18
|
+
epic: MAGENTA,
|
|
19
|
+
legendary: '\x1b[33;1m',
|
|
20
|
+
};
|
|
12
21
|
|
|
13
22
|
export function renderBanner(title = 'SPEAKI BUDDY LAB', subtitle = 'npm-only buddy reroll terminal') {
|
|
14
23
|
const border = '═'.repeat(54);
|
|
@@ -24,11 +33,14 @@ export function renderBanner(title = 'SPEAKI BUDDY LAB', subtitle = 'npm-only bu
|
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
export function renderPanel(title, lines = [], tone = CYAN) {
|
|
27
|
-
const
|
|
36
|
+
const width = Math.max(title.length + 6, ...lines.map((line) => String(line).length), 28);
|
|
37
|
+
const content = lines.map((line) => ` ${tone}│${RESET} ${String(line).padEnd(width - 4)} ${tone}│${RESET}`);
|
|
28
38
|
return [
|
|
29
|
-
`${tone}
|
|
30
|
-
`${tone}${'
|
|
39
|
+
`${tone}┌${'─'.repeat(width - 2)}┐${RESET}`,
|
|
40
|
+
`${tone}│${RESET} ${tone}${BOLD}${title}${RESET}${' '.repeat(Math.max(0, width - title.length - 4))}${tone}│${RESET}`,
|
|
41
|
+
`${tone}├${'─'.repeat(width - 2)}┤${RESET}`,
|
|
31
42
|
...content,
|
|
43
|
+
`${tone}└${'─'.repeat(width - 2)}┘${RESET}`,
|
|
32
44
|
'',
|
|
33
45
|
].join('\n');
|
|
34
46
|
}
|
|
@@ -45,7 +57,8 @@ export function renderQuotaSummary({ used = 0, limit = 0, starred = false, event
|
|
|
45
57
|
export function renderRarityOverview() {
|
|
46
58
|
return RARITIES.map((rarity) => {
|
|
47
59
|
const pct = `${RARITY_WEIGHTS[rarity]}%`.padStart(3);
|
|
48
|
-
|
|
60
|
+
const color = RARITY_TONE[rarity] || WHITE;
|
|
61
|
+
return `${color}${RARITY_STARS[rarity].padEnd(6)}${RESET} ${color}${rarity.padEnd(10)}${RESET} ${GRAY}${pct}${RESET}`;
|
|
49
62
|
});
|
|
50
63
|
}
|
|
51
64
|
|