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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-buddy-reroll",
3
- "version": "1.8.13",
3
+ "version": "1.8.14",
4
4
  "description": "Speaki-styled npm terminal for buddy rerolls, dex replay, and SALT patching.",
5
5
  "type": "module",
6
6
  "bin": {
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 padSlot = (value) => value.padEnd(10);
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 --', 'forms 0']),
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', ['-- common -- uncommon', '-- rare -- epic', '-- legendary']),
387
- ...panel('FORMS', [`${padSlot('--')} ${padSlot('--')}`, `${padSlot('--')} ${padSlot('--')}`]),
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 rarityCompletion = getRarityCompletion(entry)
394
- .map(({ rarity, found }) => `${found ? formatStars(RARITY_STARS[rarity]) : '--'} ${rarity}`)
395
- .join(' ');
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 shinyMark = item.bones.shiny ? ' ✨' : '';
400
- return `${formatStars(RARITY_STARS[item.bones.rarity])} ${formatEye(item.bones.eye)} ${toTerminalSafeText(item.bones.hat)}${shinyMark}`.slice(0, 10);
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
- `eye ${formatEye(variant.bones.eye)} • ${toTerminalSafeText(variant.bones.hat)}`,
427
- `best ${formatStars(RARITY_STARS[variant.bones.rarity])}`,
428
- `latest ${latestVariant ? formatStars(RARITY_STARS[latestVariant.bones.rarity]) : '--'}`,
429
- `shiny ${shinyVariant ? 'yes' : 'no'} • forms ${entry.variants.length}`,
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', [`${rarityTrack[0]} ${rarityTrack[1]}`, `${rarityTrack[2]} ${rarityTrack[3]}`, `${rarityTrack[4]}`]),
433
- ...panel('FORMS', [`${padSlot(gallery[0])} ${padSlot(gallery[1])}`, `${padSlot(gallery[2])} ${padSlot(gallery[3])}`]),
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} 업데이트 완료! 쪼아요~!${RESET}\n`);
1220
+ console.log(`\n${GREEN} ✓ v${pkg.version} → v${newVer} 업데이트 완료! 도감도 갱신했어요.${RESET}\n`);
1206
1221
  } else {
1207
- console.log(`\n${GREEN} ✓ 이미 최신이에요! (v${pkg.version}) 쪼아요~${RESET}\n`);
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
- output({ command: 'setup', success: true });
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`);
@@ -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
@@ -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 content = lines.map((line) => ` ${line}`);
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}${BOLD}${title}${RESET}`,
30
- `${tone}${''.repeat(Math.max(18, title.length + 6))}${RESET}`,
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
- return `${WHITE}${RARITY_STARS[rarity].padEnd(6)}${RESET} ${rarity.padEnd(10)} ${DIM}${pct}${RESET}`;
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