any-buddy 1.0.6 → 1.0.7
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 -3
- package/lib/preflight.mjs +125 -0
- package/lib/tui.mjs +19 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Pick any Claude Code companion pet you want.
|
|
3
3
|
|
|
4
4
|
```bash
|
|
5
|
-
npx any-buddy
|
|
5
|
+
npx any-buddy@latest
|
|
6
6
|
```
|
|
7
7
|
|
|
8
8
|
That's it. Follow the prompts to choose your species, rarity, eyes, hat, and name.
|
|
@@ -37,7 +37,7 @@ The patch is safe — it uses `rename()` to atomically swap the binary, which is
|
|
|
37
37
|
| Platform | Status | Binary location (auto-detected) |
|
|
38
38
|
|---|---|---|
|
|
39
39
|
| Linux | Tested | `~/.local/share/claude/versions/<ver>` |
|
|
40
|
-
| macOS |
|
|
40
|
+
| macOS | Tested | `~/.local/bin/claude`, `/opt/homebrew/bin/claude`, `~/.claude/local/claude` |
|
|
41
41
|
| Windows | Should work | `%LOCALAPPDATA%\Programs\claude\claude.exe`, npm global shim |
|
|
42
42
|
|
|
43
43
|
The binary is found automatically via `which`/`where` and platform-specific known paths. If auto-detection fails, set `CLAUDE_BINARY=/path/to/binary` manually.
|
|
@@ -262,7 +262,7 @@ This patches the salt back to the original, removes the SessionStart hook, and c
|
|
|
262
262
|
|
|
263
263
|
## Limitations
|
|
264
264
|
|
|
265
|
-
- **Tested on Linux
|
|
265
|
+
- **Tested on Linux and macOS** — Windows should work but is not yet tested. Please [open an issue](https://github.com/cpaczek/any-buddy/issues) if you hit problems
|
|
266
266
|
- **Requires Bun** — needed for matching Claude Code's wyhash implementation
|
|
267
267
|
- **Salt string dependent** — if Anthropic changes the salt from `friend-2026-401` in a future version, the patch logic would need updating (but the tool will detect this and warn you)
|
|
268
268
|
- **Stats partially selectable** — you can pick which stat is highest (peak) and lowest (dump), but not exact values
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { platform } from 'os';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { ORIGINAL_SALT } from './constants.mjs';
|
|
6
|
+
import { findClaudeBinary, verifySalt } from './patcher.mjs';
|
|
7
|
+
import { getClaudeUserId } from './config.mjs';
|
|
8
|
+
|
|
9
|
+
const ISSUE_URL = 'https://github.com/cpaczek/any-buddy/issues';
|
|
10
|
+
|
|
11
|
+
// Run all preflight checks before doing anything destructive.
|
|
12
|
+
// Returns { ok, binaryPath, userId, saltCount } or throws with a helpful message.
|
|
13
|
+
export function runPreflight({ requireBinary = true } = {}) {
|
|
14
|
+
const errors = [];
|
|
15
|
+
const warnings = [];
|
|
16
|
+
|
|
17
|
+
// ── 1. Check bun is installed ──
|
|
18
|
+
let bunVersion = null;
|
|
19
|
+
try {
|
|
20
|
+
bunVersion = execSync('bun --version', { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
21
|
+
} catch {
|
|
22
|
+
errors.push(
|
|
23
|
+
'Bun is not installed or not on PATH.\n' +
|
|
24
|
+
' any-buddy needs Bun to compute the correct hash (Claude Code uses Bun.hash/wyhash).\n' +
|
|
25
|
+
' Install Bun: https://bun.sh'
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── 2. Check Claude config exists and has a userId ──
|
|
30
|
+
const userId = getClaudeUserId();
|
|
31
|
+
if (userId === 'anon') {
|
|
32
|
+
warnings.push(
|
|
33
|
+
'No user ID found in ~/.claude.json (using "anon").\n' +
|
|
34
|
+
' This usually means Claude Code hasn\'t been set up yet.\n' +
|
|
35
|
+
' The generated pet may not match what Claude Code shows.'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── 3. Find and validate the binary ──
|
|
40
|
+
let binaryPath = null;
|
|
41
|
+
let saltCount = 0;
|
|
42
|
+
|
|
43
|
+
if (requireBinary) {
|
|
44
|
+
try {
|
|
45
|
+
binaryPath = findClaudeBinary();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
errors.push(err.message);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (binaryPath) {
|
|
51
|
+
// Check binary size — should be substantial (>1MB), not a shell script or shim
|
|
52
|
+
try {
|
|
53
|
+
const size = statSync(binaryPath).size;
|
|
54
|
+
if (size < 1_000_000) {
|
|
55
|
+
warnings.push(
|
|
56
|
+
`Binary at ${binaryPath} is only ${(size / 1024).toFixed(0)}KB.\n` +
|
|
57
|
+
' This might be a shell script, symlink wrapper, or npm shim rather than the actual binary.\n' +
|
|
58
|
+
' If patching fails, try setting CLAUDE_BINARY to the real compiled binary.'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
} catch { /* ignore */ }
|
|
62
|
+
|
|
63
|
+
// Check that the salt exists in the binary
|
|
64
|
+
try {
|
|
65
|
+
const result = verifySalt(binaryPath, ORIGINAL_SALT);
|
|
66
|
+
saltCount = result.found;
|
|
67
|
+
|
|
68
|
+
if (saltCount === 0) {
|
|
69
|
+
// Check if it might already be patched with a different salt
|
|
70
|
+
errors.push(
|
|
71
|
+
`Salt "${ORIGINAL_SALT}" not found in ${binaryPath}.\n` +
|
|
72
|
+
' Possible reasons:\n' +
|
|
73
|
+
' - Binary is already patched with a custom salt (run `any-buddy restore` first)\n' +
|
|
74
|
+
' - This binary format doesn\'t contain the salt as a plain string\n' +
|
|
75
|
+
' - Claude Code changed the salt in a new version\n' +
|
|
76
|
+
`\n Platform: ${platform()}, binary: ${binaryPath}` +
|
|
77
|
+
`\n Please report this at: ${ISSUE_URL}`
|
|
78
|
+
);
|
|
79
|
+
} else if (saltCount < 2) {
|
|
80
|
+
warnings.push(
|
|
81
|
+
`Salt found only ${saltCount} time(s) in binary (expected 3 on Linux).\n` +
|
|
82
|
+
' This might work but the patch may be incomplete.\n' +
|
|
83
|
+
` Platform: ${platform()}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
errors.push(
|
|
88
|
+
`Could not read binary at ${binaryPath}: ${err.message}\n` +
|
|
89
|
+
' Check file permissions.'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Platform-specific warnings
|
|
94
|
+
const plat = platform();
|
|
95
|
+
if (plat === 'win32') {
|
|
96
|
+
warnings.push(
|
|
97
|
+
'Windows support is experimental.\n' +
|
|
98
|
+
' You must close all Claude Code windows before patching.\n' +
|
|
99
|
+
' If you encounter issues, please report them at: ' + ISSUE_URL
|
|
100
|
+
);
|
|
101
|
+
} else if (plat === 'darwin') {
|
|
102
|
+
warnings.push(
|
|
103
|
+
'macOS support is experimental.\n' +
|
|
104
|
+
' If the binary is code-signed, patching may invalidate the signature.\n' +
|
|
105
|
+
' If Claude Code won\'t launch after patching, run `any-buddy restore`.\n' +
|
|
106
|
+
' Please report issues at: ' + ISSUE_URL
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Print results ──
|
|
113
|
+
for (const w of warnings) {
|
|
114
|
+
console.log(chalk.yellow(` Warning: ${w}\n`));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (errors.length > 0) {
|
|
118
|
+
for (const e of errors) {
|
|
119
|
+
console.log(chalk.red(` Error: ${e}\n`));
|
|
120
|
+
}
|
|
121
|
+
return { ok: false, binaryPath, userId, saltCount, bunVersion };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { ok: true, binaryPath, userId, saltCount, bunVersion };
|
|
125
|
+
}
|
package/lib/tui.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { roll } from './generation.mjs';
|
|
|
5
5
|
import { renderSprite, renderFace } from './sprites.mjs';
|
|
6
6
|
import { findSalt, estimateAttempts } from './finder.mjs';
|
|
7
7
|
import { findClaudeBinary, getCurrentSalt, patchBinary, verifySalt, restoreBinary, isClaudeRunning } from './patcher.mjs';
|
|
8
|
+
import { runPreflight } from './preflight.mjs';
|
|
8
9
|
import { getClaudeUserId, savePetConfig, loadPetConfig, isHookInstalled, installHook, removeHook, getCompanionName, renameCompanion, getCompanionPersonality, setCompanionPersonality, deleteCompanion } from './config.mjs';
|
|
9
10
|
import { DEFAULT_PERSONALITIES } from './personalities.mjs';
|
|
10
11
|
|
|
@@ -69,7 +70,9 @@ function showPet(bones, label = 'Your pet') {
|
|
|
69
70
|
|
|
70
71
|
export async function runCurrent() {
|
|
71
72
|
banner();
|
|
72
|
-
const
|
|
73
|
+
const preflight = runPreflight({ requireBinary: false });
|
|
74
|
+
if (!preflight.ok) process.exit(1);
|
|
75
|
+
const userId = preflight.userId;
|
|
73
76
|
console.log(chalk.dim(` User ID: ${userId.slice(0, 12)}...`));
|
|
74
77
|
|
|
75
78
|
// Show what the original salt produces
|
|
@@ -86,6 +89,8 @@ export async function runCurrent() {
|
|
|
86
89
|
|
|
87
90
|
export async function runPreview(flags = {}) {
|
|
88
91
|
banner();
|
|
92
|
+
const preflight = runPreflight({ requireBinary: false });
|
|
93
|
+
if (!preflight.ok) process.exit(1);
|
|
89
94
|
|
|
90
95
|
const species = validateFlag('species', flags.species, SPECIES) ?? await selectSpecies();
|
|
91
96
|
const eye = validateFlag('eye', flags.eye, EYES) ?? await selectEyes(species);
|
|
@@ -226,13 +231,18 @@ export async function runRehatch() {
|
|
|
226
231
|
export async function runInteractive(flags = {}) {
|
|
227
232
|
banner();
|
|
228
233
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
234
|
+
// ─── Preflight checks ───
|
|
235
|
+
const preflight = runPreflight({ requireBinary: true });
|
|
236
|
+
if (!preflight.ok) {
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
const userId = preflight.userId;
|
|
240
|
+
console.log(chalk.dim(` User ID: ${userId.slice(0, 12)}...`));
|
|
241
|
+
console.log(chalk.dim(` Binary: ${preflight.binaryPath} (salt found ${preflight.saltCount}x)`));
|
|
242
|
+
if (preflight.bunVersion) {
|
|
243
|
+
console.log(chalk.dim(` Bun: v${preflight.bunVersion}`));
|
|
235
244
|
}
|
|
245
|
+
console.log();
|
|
236
246
|
|
|
237
247
|
// Show current pet
|
|
238
248
|
const currentBones = roll(userId, ORIGINAL_SALT).bones;
|
|
@@ -304,25 +314,8 @@ export async function runInteractive(flags = {}) {
|
|
|
304
314
|
const foundBones = roll(userId, result.salt).bones;
|
|
305
315
|
showPet(foundBones, 'Your new pet');
|
|
306
316
|
|
|
307
|
-
// ─── Patch binary ───
|
|
308
|
-
|
|
309
|
-
try {
|
|
310
|
-
binaryPath = findClaudeBinary();
|
|
311
|
-
} catch (err) {
|
|
312
|
-
console.error(chalk.red(`\n ${err.message}`));
|
|
313
|
-
console.log(chalk.dim(` Salt saved. You can manually apply later with: claude-code-any-buddy apply\n`));
|
|
314
|
-
savePetConfig({
|
|
315
|
-
salt: result.salt,
|
|
316
|
-
species: desired.species,
|
|
317
|
-
rarity: desired.rarity,
|
|
318
|
-
eye: desired.eye,
|
|
319
|
-
hat: desired.hat,
|
|
320
|
-
appliedAt: new Date().toISOString(),
|
|
321
|
-
});
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
console.log(chalk.dim(` Binary: ${binaryPath}`));
|
|
317
|
+
// ─── Patch binary (path already validated by preflight) ───
|
|
318
|
+
const binaryPath = preflight.binaryPath;
|
|
326
319
|
|
|
327
320
|
// Find what's currently in the binary
|
|
328
321
|
const current = getCurrentSalt(binaryPath);
|