buddy-reroll 0.2.0 → 0.2.1
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/index.js +38 -10
- package/package.json +1 -1
- package/ui.jsx +10 -21
package/index.js
CHANGED
|
@@ -100,8 +100,11 @@ function getClaudeConfigDir() {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
function findBinaryPath() {
|
|
103
|
+
const isWin = platform() === "win32";
|
|
104
|
+
|
|
103
105
|
try {
|
|
104
|
-
const
|
|
106
|
+
const cmd = isWin ? "where.exe claude 2>nul" : "which -a claude 2>/dev/null";
|
|
107
|
+
const allPaths = execSync(cmd, { encoding: "utf-8" }).trim().split("\n");
|
|
105
108
|
for (const entry of allPaths) {
|
|
106
109
|
try {
|
|
107
110
|
const resolved = realpathSync(entry.trim());
|
|
@@ -110,8 +113,12 @@ function findBinaryPath() {
|
|
|
110
113
|
}
|
|
111
114
|
} catch {}
|
|
112
115
|
|
|
113
|
-
const
|
|
114
|
-
|
|
116
|
+
const versionsDirs = [
|
|
117
|
+
join(homedir(), ".local", "share", "claude", "versions"),
|
|
118
|
+
...(isWin ? [join(process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local"), "Claude", "versions")] : []),
|
|
119
|
+
];
|
|
120
|
+
for (const versionsDir of versionsDirs) {
|
|
121
|
+
if (!existsSync(versionsDir)) continue;
|
|
115
122
|
try {
|
|
116
123
|
const versions = readdirSync(versionsDir)
|
|
117
124
|
.filter((f) => !f.includes(".backup"))
|
|
@@ -133,6 +140,12 @@ function findConfigPath() {
|
|
|
133
140
|
const defaultPath = join(home, ".claude.json");
|
|
134
141
|
if (existsSync(defaultPath)) return defaultPath;
|
|
135
142
|
|
|
143
|
+
// Windows: check AppData\Roaming\Claude
|
|
144
|
+
if (platform() === "win32" && process.env.APPDATA) {
|
|
145
|
+
const appDataPath = join(process.env.APPDATA, "Claude", "config.json");
|
|
146
|
+
if (existsSync(appDataPath)) return appDataPath;
|
|
147
|
+
}
|
|
148
|
+
|
|
136
149
|
return null;
|
|
137
150
|
}
|
|
138
151
|
|
|
@@ -146,9 +159,9 @@ function getUserId(configPath) {
|
|
|
146
159
|
function findCurrentSalt(binaryData, userId) {
|
|
147
160
|
if (binaryData.includes(Buffer.from(ORIGINAL_SALT))) return ORIGINAL_SALT;
|
|
148
161
|
|
|
149
|
-
const text = binaryData.toString("
|
|
162
|
+
const text = binaryData.toString("latin1");
|
|
150
163
|
|
|
151
|
-
// Scan for previously patched salts
|
|
164
|
+
// Scan for previously patched salts
|
|
152
165
|
const patterns = [
|
|
153
166
|
new RegExp(`x{${SALT_LEN - 8}}\\d{8}`, "g"),
|
|
154
167
|
new RegExp(`friend-\\d{4}-.{${SALT_LEN - 12}}`, "g"),
|
|
@@ -160,7 +173,6 @@ function findCurrentSalt(binaryData, userId) {
|
|
|
160
173
|
}
|
|
161
174
|
}
|
|
162
175
|
|
|
163
|
-
// Contextual scan near companion code markers
|
|
164
176
|
const saltRegex = new RegExp(`"([a-zA-Z0-9_-]{${SALT_LEN}})"`, "g");
|
|
165
177
|
const candidates = new Set();
|
|
166
178
|
const markers = ["rollRarity", "CompanionBones", "inspirationSeed", "companionUserId"];
|
|
@@ -174,7 +186,6 @@ function findCurrentSalt(binaryData, userId) {
|
|
|
174
186
|
}
|
|
175
187
|
}
|
|
176
188
|
|
|
177
|
-
// Filter: real salts contain digits or hyphens (rules out "projectSettings" etc.)
|
|
178
189
|
for (const c of candidates) {
|
|
179
190
|
if (/[\d-]/.test(c)) return c;
|
|
180
191
|
}
|
|
@@ -233,6 +244,10 @@ function matches(roll, target) {
|
|
|
233
244
|
|
|
234
245
|
function isClaudeRunning() {
|
|
235
246
|
try {
|
|
247
|
+
if (platform() === "win32") {
|
|
248
|
+
const out = execSync('tasklist /FI "IMAGENAME eq claude.exe" /FO CSV 2>nul', { encoding: "utf-8" });
|
|
249
|
+
return out.toLowerCase().includes("claude.exe");
|
|
250
|
+
}
|
|
236
251
|
const out = execSync("pgrep -af claude 2>/dev/null", { encoding: "utf-8" });
|
|
237
252
|
return out.split("\n").some((line) => !line.includes("buddy-reroll") && line.trim().length > 0);
|
|
238
253
|
} catch {
|
|
@@ -261,8 +276,20 @@ function patchBinary(binaryPath, oldSalt, newSalt) {
|
|
|
261
276
|
|
|
262
277
|
if (count === 0) throw new Error(`Salt "${oldSalt}" not found in binary`);
|
|
263
278
|
|
|
264
|
-
|
|
265
|
-
|
|
279
|
+
const isWin = platform() === "win32";
|
|
280
|
+
const maxRetries = isWin ? 3 : 1;
|
|
281
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
282
|
+
try {
|
|
283
|
+
writeFileSync(binaryPath, data);
|
|
284
|
+
return count;
|
|
285
|
+
} catch (err) {
|
|
286
|
+
if (isWin && (err.code === "EACCES" || err.code === "EPERM" || err.code === "EBUSY") && attempt < maxRetries - 1) {
|
|
287
|
+
execSync("timeout /t 2 /nobreak >nul 2>&1", { shell: true, stdio: "ignore" });
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
throw new Error(`Failed to write binary: ${err.message}${isWin ? " (ensure Claude Code is fully closed)" : ""}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
266
293
|
}
|
|
267
294
|
|
|
268
295
|
function resignBinary(binaryPath) {
|
|
@@ -305,7 +332,8 @@ function formatCompanionCard(result) {
|
|
|
305
332
|
}
|
|
306
333
|
|
|
307
334
|
for (const [k, v] of Object.entries(result.stats)) {
|
|
308
|
-
const
|
|
335
|
+
const filled = Math.min(10, Math.max(0, Math.round(v / 10)));
|
|
336
|
+
const bar = colorFn("█".repeat(filled) + "░".repeat(10 - filled));
|
|
309
337
|
lines.push(` ${k.padEnd(10)} ${bar} ${String(v).padStart(3)}`);
|
|
310
338
|
}
|
|
311
339
|
|
package/package.json
CHANGED
package/ui.jsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from "react";
|
|
2
2
|
import { render, Box, Text, useApp, useInput } from "ink";
|
|
3
|
-
|
|
3
|
+
import { renderSprite, RARITY_STARS, RARITY_COLORS } from "./sprites.js";
|
|
4
|
+
import { existsSync, copyFileSync } from "fs";
|
|
5
|
+
|
|
4
6
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
5
7
|
function Spinner({ label }) {
|
|
6
8
|
const [frame, setFrame] = useState(0);
|
|
@@ -10,10 +12,8 @@ function Spinner({ label }) {
|
|
|
10
12
|
}, []);
|
|
11
13
|
return <Text><Text color="cyan">{SPINNER_FRAMES[frame]}</Text> {label}</Text>;
|
|
12
14
|
}
|
|
13
|
-
import { renderSprite, RARITY_STARS, RARITY_COLORS } from "./sprites.js";
|
|
14
|
-
import { existsSync, copyFileSync } from "fs";
|
|
15
15
|
|
|
16
|
-
// ──
|
|
16
|
+
// ── Components ──────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
18
|
function KeyHint({ children }) {
|
|
19
19
|
return <Text italic dimColor>{children}</Text>;
|
|
@@ -86,8 +86,6 @@ function ConfirmSelect({ label, onConfirm, onCancel, onBack, isActive }) {
|
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
// ── PreviewCard ─────────────────────────────────────────────────────────
|
|
90
|
-
|
|
91
89
|
function PreviewCard({ species, rarity, eye, hat, shiny, stats }) {
|
|
92
90
|
const color = RARITY_COLORS[rarity] ?? "white";
|
|
93
91
|
const stars = RARITY_STARS[rarity] ?? "";
|
|
@@ -127,8 +125,6 @@ function PreviewCard({ species, rarity, eye, hat, shiny, stats }) {
|
|
|
127
125
|
);
|
|
128
126
|
}
|
|
129
127
|
|
|
130
|
-
// ── Step Components ─────────────────────────────────────────────────────
|
|
131
|
-
|
|
132
128
|
function ShowCurrentStep({ isActive }) {
|
|
133
129
|
const { exit } = useApp();
|
|
134
130
|
|
|
@@ -233,21 +229,16 @@ function DoneStep({ messages, isActive }) {
|
|
|
233
229
|
);
|
|
234
230
|
}
|
|
235
231
|
|
|
236
|
-
// ── Step Flow ───────────────────────────────────────────────────────────
|
|
237
|
-
|
|
238
232
|
const STEP_ORDER = ["action", "species", "rarity", "eye", "hat", "shiny", "confirm"];
|
|
239
233
|
|
|
240
234
|
function getPrevStep(current, rarity) {
|
|
241
235
|
const idx = STEP_ORDER.indexOf(current);
|
|
242
236
|
if (idx <= 0) return null;
|
|
243
237
|
let prev = STEP_ORDER[idx - 1];
|
|
244
|
-
// Skip hat when going back if common
|
|
245
238
|
if (prev === "hat" && rarity === "common") prev = "eye";
|
|
246
239
|
return prev;
|
|
247
240
|
}
|
|
248
241
|
|
|
249
|
-
// ── Main App ────────────────────────────────────────────────────────────
|
|
250
|
-
|
|
251
242
|
function App({ opts }) {
|
|
252
243
|
const { exit } = useApp();
|
|
253
244
|
const {
|
|
@@ -267,6 +258,8 @@ function App({ opts }) {
|
|
|
267
258
|
|
|
268
259
|
const showStats = step === "showCurrent" || step === "result" || step === "done";
|
|
269
260
|
const displayRoll = found ? found.result : { species, rarity, eye, hat, shiny, stats: currentRoll.stats };
|
|
261
|
+
const effectiveHat = rarity === "common" ? "none" : hat;
|
|
262
|
+
const buildTarget = (s = shiny) => ({ species, rarity, eye, hat: effectiveHat, shiny: s });
|
|
270
263
|
|
|
271
264
|
const goBack = (toStep) => {
|
|
272
265
|
const prev = toStep || getPrevStep(step, rarity);
|
|
@@ -379,8 +372,7 @@ function App({ opts }) {
|
|
|
379
372
|
isActive={step === "shiny"}
|
|
380
373
|
onConfirm={() => {
|
|
381
374
|
setShiny(true);
|
|
382
|
-
|
|
383
|
-
if (matches(currentRoll, target)) {
|
|
375
|
+
if (matches(currentRoll, buildTarget(true))) {
|
|
384
376
|
setDoneMessages(["Already matching! No changes needed."]);
|
|
385
377
|
setStep("done");
|
|
386
378
|
} else {
|
|
@@ -389,8 +381,7 @@ function App({ opts }) {
|
|
|
389
381
|
}}
|
|
390
382
|
onCancel={() => {
|
|
391
383
|
setShiny(false);
|
|
392
|
-
|
|
393
|
-
if (matches(currentRoll, target)) {
|
|
384
|
+
if (matches(currentRoll, buildTarget(false))) {
|
|
394
385
|
setDoneMessages(["Already matching! No changes needed."]);
|
|
395
386
|
setStep("done");
|
|
396
387
|
} else {
|
|
@@ -403,7 +394,7 @@ function App({ opts }) {
|
|
|
403
394
|
|
|
404
395
|
{step === "confirm" && (
|
|
405
396
|
<Box flexDirection="column">
|
|
406
|
-
<Text>Target: <Text bold>{species}</Text> / <Text bold>{rarity}</Text> / eye:{eye} / hat:{
|
|
397
|
+
<Text>Target: <Text bold>{species}</Text> / <Text bold>{rarity}</Text> / eye:{eye} / hat:{effectiveHat}{shiny ? " / shiny" : ""}</Text>
|
|
407
398
|
{isClaudeRunning() && <Text color="yellow">⚠ Claude Code appears to be running. Quit it before patching.</Text>}
|
|
408
399
|
<ConfirmSelect
|
|
409
400
|
label="Search and apply?"
|
|
@@ -418,7 +409,7 @@ function App({ opts }) {
|
|
|
418
409
|
{step === "search" && (
|
|
419
410
|
<SearchStep
|
|
420
411
|
userId={userId}
|
|
421
|
-
target={
|
|
412
|
+
target={buildTarget()}
|
|
422
413
|
bruteForce={bruteForce}
|
|
423
414
|
onFound={(f) => { setFound(f); setStep("result"); }}
|
|
424
415
|
onFail={() => {
|
|
@@ -461,8 +452,6 @@ function App({ opts }) {
|
|
|
461
452
|
);
|
|
462
453
|
}
|
|
463
454
|
|
|
464
|
-
// ── Export ───────────────────────────────────────────────────────────────
|
|
465
|
-
|
|
466
455
|
export async function runInteractiveUI(opts) {
|
|
467
456
|
const { waitUntilExit } = render(<App opts={opts} />);
|
|
468
457
|
await waitUntilExit();
|