buddydex 0.1.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.
Files changed (2) hide show
  1. package/dist/buddydex.js +450 -0
  2. package/package.json +20 -0
@@ -0,0 +1,450 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // packages/engine/src/constants.ts
5
+ var SPECIES = [
6
+ "duck",
7
+ "goose",
8
+ "blob",
9
+ "cat",
10
+ "dragon",
11
+ "octopus",
12
+ "owl",
13
+ "penguin",
14
+ "turtle",
15
+ "snail",
16
+ "ghost",
17
+ "axolotl",
18
+ "capybara",
19
+ "cactus",
20
+ "robot",
21
+ "rabbit",
22
+ "mushroom",
23
+ "chonk"
24
+ ];
25
+ var RARITIES = ["common", "uncommon", "rare", "epic", "legendary"];
26
+ var RARITY_WEIGHTS = [60, 25, 10, 4, 1];
27
+ var EYES = ["\xB7", "\u2726", "\xD7", "\u25C9", "@", "\xB0"];
28
+ var HATS = ["none", "crown", "tophat", "propeller", "halo", "wizard", "beanie", "tinyduck"];
29
+ var STAT_FLOORS = { common: 5, uncommon: 15, rare: 25, epic: 35, legendary: 50 };
30
+ var STAT_NAMES = ["debugging", "patience", "chaos", "wisdom", "snark"];
31
+ var SALT = "friend-2026-401";
32
+
33
+ // packages/cli/commands/hunt.ts
34
+ import { parseArgs } from "util";
35
+
36
+ // packages/engine/src/hash.ts
37
+ var _wyhashSync = null;
38
+ var _encoder = new TextEncoder;
39
+ function wyhashFallback(str) {
40
+ const buf = _encoder.encode(str);
41
+ const raw = _wyhashSync(buf, 0n);
42
+ return Number(BigInt.asUintN(64, raw) & 0xffffffffn);
43
+ }
44
+ function hashString(str) {
45
+ if (typeof globalThis.Bun !== "undefined") {
46
+ return Number(BigInt(Bun.hash(str)) & 0xffffffffn);
47
+ }
48
+ if (!_wyhashSync) {
49
+ throw new Error("Hash not initialized. Call initHash() first.");
50
+ }
51
+ return wyhashFallback(str);
52
+ }
53
+ // packages/engine/src/prng.ts
54
+ class Mulberry32 {
55
+ state;
56
+ constructor(seed) {
57
+ this.state = seed >>> 0;
58
+ }
59
+ nextU32() {
60
+ this.state = this.state + 1831565813 >>> 0;
61
+ let t = (this.state ^ this.state >>> 15) >>> 0;
62
+ t = Math.imul(t, 1 | this.state) >>> 0;
63
+ t = t + (Math.imul((t ^ t >>> 7) >>> 0, 61 | t) >>> 0) ^ t;
64
+ return (t ^ t >>> 14) >>> 0;
65
+ }
66
+ nextF64() {
67
+ return this.nextU32() / 4294967296;
68
+ }
69
+ }
70
+ // packages/engine/src/roll.ts
71
+ function rollBones(userId) {
72
+ const seed = hashString(userId + SALT);
73
+ const rng = new Mulberry32(seed);
74
+ let roll = rng.nextF64() * 100;
75
+ let rarity = "common";
76
+ for (let i = 0;i < RARITY_WEIGHTS.length; i++) {
77
+ roll -= RARITY_WEIGHTS[i];
78
+ if (roll < 0) {
79
+ rarity = RARITIES[i];
80
+ break;
81
+ }
82
+ }
83
+ const species = SPECIES[Math.floor(rng.nextF64() * SPECIES.length) % SPECIES.length];
84
+ const eye = EYES[Math.floor(rng.nextF64() * EYES.length) % EYES.length];
85
+ const hat = rarity === "common" ? "none" : HATS[Math.floor(rng.nextF64() * HATS.length) % HATS.length];
86
+ const shiny = rng.nextF64() < 0.01;
87
+ const floor = STAT_FLOORS[rarity];
88
+ const peakIdx = Math.floor(rng.nextF64() * 5) % 5;
89
+ let dumpIdx = Math.floor(rng.nextF64() * 5) % 5;
90
+ if (dumpIdx === peakIdx)
91
+ dumpIdx = (dumpIdx + 1) % 5;
92
+ const stats = {};
93
+ for (let i = 0;i < 5; i++) {
94
+ const name = STAT_NAMES[i];
95
+ if (i === peakIdx) {
96
+ stats[name] = Math.min(100, Math.floor(floor + 50 + rng.nextF64() * 30));
97
+ } else if (i === dumpIdx) {
98
+ stats[name] = Math.max(1, Math.floor(floor - 10 + rng.nextF64() * 15));
99
+ } else {
100
+ stats[name] = Math.floor(floor + rng.nextF64() * 40);
101
+ }
102
+ }
103
+ const totalStats = Object.values(stats).reduce((a, b) => a + b, 0);
104
+ return { rarity, species, eye, hat, shiny, stats, totalStats };
105
+ }
106
+ // packages/engine/src/uuid.ts
107
+ function generateUuid() {
108
+ const hex = () => Math.floor(Math.random() * 16).toString(16);
109
+ const h = (n) => Array.from({ length: n }, hex).join("");
110
+ return `${h(8)}-${h(4)}-${h(4)}-${h(4)}-${h(12)}`;
111
+ }
112
+ // packages/cli/lib/format.ts
113
+ var isBun = typeof Bun !== "undefined";
114
+ function printHeader(hashMode) {
115
+ const effectiveHash = hashMode === "fnv1a" ? "FNV-1a (forced)" : isBun ? "Bun.hash (Wyhash)" : "Wyhash (WASM)";
116
+ console.log(`
117
+ BuddyDex`);
118
+ console.log(` Runtime: ${isBun ? "Bun" : "Node.js"} Hash: ${effectiveHash}`);
119
+ }
120
+ function printBuddy(label, bones, userId) {
121
+ const shinyTag = bones.shiny ? " SHINY" : "";
122
+ const statBar = STAT_NAMES.map((s) => `${s.slice(0, 3).toUpperCase()}:${bones.stats[s]}`).join(" ");
123
+ console.log(`
124
+ ${label}`);
125
+ if (userId) {
126
+ const display = userId.length > 24 ? userId.slice(0, 12) + "..." : userId;
127
+ console.log(` ID: ${display}`);
128
+ }
129
+ console.log(` Result: ${bones.rarity.toUpperCase()} ${bones.species}${shinyTag}`);
130
+ console.log(` Eye: ${bones.eye} Hat: ${bones.hat} Total: ${bones.totalStats}`);
131
+ console.log(` Stats: ${statBar}`);
132
+ }
133
+
134
+ // packages/cli/commands/hunt.ts
135
+ function cmdHunt(argv) {
136
+ const { values: args } = parseArgs({
137
+ args: argv,
138
+ options: {
139
+ species: { type: "string" },
140
+ rarity: { type: "string" },
141
+ shiny: { type: "boolean", default: false },
142
+ "min-stat": { type: "string" },
143
+ "min-total": { type: "string" },
144
+ perfect: { type: "string" },
145
+ tries: { type: "string", default: "1000000" },
146
+ limit: { type: "string", default: "10" }
147
+ }
148
+ });
149
+ const maxTries = parseInt(args.tries);
150
+ const maxResults = parseInt(args.limit);
151
+ const wantSpecies = args.species?.toLowerCase();
152
+ const wantRarity = args.rarity?.toLowerCase();
153
+ const wantShiny = args.shiny;
154
+ const wantMinStat = args["min-stat"]?.split(":");
155
+ const wantMinTotal = args["min-total"] ? parseInt(args["min-total"]) : null;
156
+ const wantPerfect = args.perfect ? parseInt(args.perfect) : null;
157
+ if (wantSpecies && !SPECIES.includes(wantSpecies)) {
158
+ console.error(`Unknown species: ${wantSpecies}
159
+ Valid: ${SPECIES.join(", ")}`);
160
+ process.exit(1);
161
+ }
162
+ if (wantRarity && !RARITIES.includes(wantRarity)) {
163
+ console.error(`Unknown rarity: ${wantRarity}
164
+ Valid: ${RARITIES.join(", ")}`);
165
+ process.exit(1);
166
+ }
167
+ printHeader();
168
+ const filters = [
169
+ `species=${wantSpecies || "any"}`,
170
+ `rarity=${wantRarity || "any"}`,
171
+ `shiny=${wantShiny}`,
172
+ wantMinStat ? `min-stat=${args["min-stat"]}` : null,
173
+ wantMinTotal ? `min-total=${wantMinTotal}` : null,
174
+ wantPerfect ? `perfect=${wantPerfect}` : null
175
+ ].filter(Boolean).join(" ");
176
+ console.log(`
177
+ Filters: ${filters}`);
178
+ console.log(` Trying ${maxTries.toLocaleString()} random UUIDs, showing up to ${maxResults} results.
179
+ `);
180
+ const results = [];
181
+ const startTime = Date.now();
182
+ for (let i = 0;i < maxTries && results.length < maxResults; i++) {
183
+ const fakeId = generateUuid();
184
+ const bones = rollBones(fakeId);
185
+ if (wantSpecies && bones.species !== wantSpecies)
186
+ continue;
187
+ if (wantRarity && bones.rarity !== wantRarity)
188
+ continue;
189
+ if (wantShiny && !bones.shiny)
190
+ continue;
191
+ if (wantMinStat) {
192
+ const [statName, minVal] = wantMinStat;
193
+ if (!bones.stats[statName] || bones.stats[statName] < parseInt(minVal))
194
+ continue;
195
+ }
196
+ if (wantMinTotal && bones.totalStats < wantMinTotal)
197
+ continue;
198
+ if (wantPerfect && !STAT_NAMES.every((s) => bones.stats[s] >= wantPerfect))
199
+ continue;
200
+ results.push({ fakeId, ...bones });
201
+ }
202
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
203
+ if (results.length === 0) {
204
+ console.log(` No matches found in ${maxTries.toLocaleString()} tries (${elapsed}s).`);
205
+ console.log(` Try increasing --tries or relaxing filters.
206
+ `);
207
+ } else {
208
+ console.log(` Found ${results.length} match(es) in ${elapsed}s:
209
+ `);
210
+ for (const r of results) {
211
+ const shinyTag = r.shiny ? " SHINY" : "";
212
+ const statBar = STAT_NAMES.map((s) => `${s.slice(0, 3).toUpperCase()}:${r.stats[s]}`).join(" ");
213
+ console.log(` ${r.rarity.toUpperCase()} ${r.species}${shinyTag} [${r.eye}] hat:${r.hat} total:${r.totalStats}`);
214
+ console.log(` Stats: ${statBar}`);
215
+ console.log(` Seed: ${r.fakeId}`);
216
+ console.log();
217
+ }
218
+ console.log(` To inject: buddydex inject <seed>
219
+ `);
220
+ }
221
+ }
222
+
223
+ // packages/cli/commands/inject.ts
224
+ import { parseArgs as parseArgs2 } from "util";
225
+
226
+ // packages/cli/lib/config.ts
227
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
228
+ import { homedir } from "os";
229
+ import { join, dirname } from "path";
230
+ var CONFIG_PATH = join(homedir(), ".claude.json");
231
+ var BACKUP_PATH = join(homedir(), ".claude", "buddy-backup.json");
232
+ function readJson(path) {
233
+ try {
234
+ return JSON.parse(readFileSync(path, "utf-8"));
235
+ } catch {
236
+ return null;
237
+ }
238
+ }
239
+ function isValidUuid(str) {
240
+ return /^[0-9a-f]{32}$/i.test(str.replace(/-/g, ""));
241
+ }
242
+ function getRealUserId() {
243
+ const backup = readJson(BACKUP_PATH);
244
+ if (backup?.originalAccountUuid)
245
+ return backup.originalAccountUuid;
246
+ if (backup?.originalUserID)
247
+ return backup.originalUserID;
248
+ const config = readJson(CONFIG_PATH);
249
+ return config?.oauthAccount?.accountUuid ?? config?.userID ?? "anon";
250
+ }
251
+ function getEffectiveUserId() {
252
+ const config = readJson(CONFIG_PATH);
253
+ return config?.oauthAccount?.accountUuid ?? config?.userID ?? "anon";
254
+ }
255
+ function backup(config) {
256
+ if (existsSync(BACKUP_PATH))
257
+ return;
258
+ const dir = dirname(BACKUP_PATH);
259
+ if (!existsSync(dir))
260
+ mkdirSync(dir, { recursive: true });
261
+ writeFileSync(BACKUP_PATH, JSON.stringify({
262
+ originalAccountUuid: config.oauthAccount?.accountUuid || null,
263
+ originalUserID: config.userID || null,
264
+ backedUpAt: new Date().toISOString()
265
+ }, null, 2) + `
266
+ `);
267
+ }
268
+ function writeConfig(config) {
269
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
270
+ `);
271
+ }
272
+
273
+ // packages/cli/commands/inject.ts
274
+ function cmdInject(argv) {
275
+ const { values: args, positionals } = parseArgs2({
276
+ args: argv,
277
+ options: { preview: { type: "boolean", default: false } },
278
+ allowPositionals: true
279
+ });
280
+ const seed = positionals[0];
281
+ if (!seed) {
282
+ console.error(`
283
+ Usage: buddydex inject <seed> [--preview]
284
+ `);
285
+ process.exit(1);
286
+ }
287
+ const config = readJson(CONFIG_PATH);
288
+ if (!config) {
289
+ console.error(`
290
+ Cannot read ${CONFIG_PATH}
291
+ `);
292
+ process.exit(1);
293
+ }
294
+ const isOAuth = !!config.oauthAccount?.accountUuid;
295
+ printHeader();
296
+ console.log(` Auth: ${isOAuth ? "OAuth (accountUuid)" : "Legacy (userID)"}`);
297
+ if (isOAuth && !isValidUuid(seed)) {
298
+ console.error(`
299
+ ERROR: Seed "${seed}" is not a valid UUID.`);
300
+ console.error(` The binary expects 32 hex chars. Non-UUID values crash on launch.
301
+ `);
302
+ process.exit(1);
303
+ }
304
+ const bones = rollBones(seed);
305
+ printBuddy("New buddy:", bones, seed);
306
+ if (args.preview) {
307
+ console.log(`
308
+ (preview only)
309
+ `);
310
+ process.exit(0);
311
+ }
312
+ backup(config);
313
+ if (isOAuth) {
314
+ config.oauthAccount.accountUuid = seed;
315
+ console.log(`
316
+ Spoofed oauthAccount.accountUuid`);
317
+ } else {
318
+ config.userID = seed;
319
+ console.log(`
320
+ Spoofed userID`);
321
+ }
322
+ delete config.companion;
323
+ writeConfig(config);
324
+ console.log(` Companion soul cleared.`);
325
+ console.log(`
326
+ Restart Claude Code to meet your new buddy!`);
327
+ console.log(` Run: buddydex restore
328
+ `);
329
+ }
330
+
331
+ // packages/cli/commands/restore.ts
332
+ import { existsSync as existsSync2 } from "fs";
333
+ function cmdRestore() {
334
+ if (!existsSync2(BACKUP_PATH)) {
335
+ console.error(`
336
+ No backup found at ${BACKUP_PATH}
337
+ `);
338
+ process.exit(1);
339
+ }
340
+ const bk = readJson(BACKUP_PATH);
341
+ const config = readJson(CONFIG_PATH);
342
+ if (!config) {
343
+ console.error(`
344
+ Cannot read ${CONFIG_PATH}
345
+ `);
346
+ process.exit(1);
347
+ }
348
+ printHeader();
349
+ if (bk?.originalAccountUuid && config.oauthAccount) {
350
+ config.oauthAccount.accountUuid = bk.originalAccountUuid;
351
+ console.log(`
352
+ Restored oauthAccount.accountUuid`);
353
+ }
354
+ if (bk?.originalUserID) {
355
+ config.userID = bk.originalUserID;
356
+ console.log(` Restored userID`);
357
+ }
358
+ delete config.companion;
359
+ writeConfig(config);
360
+ const restoredId = getEffectiveUserId();
361
+ const bones = rollBones(restoredId);
362
+ printBuddy("Restored buddy:", bones, restoredId);
363
+ console.log(`
364
+ Restart Claude Code to get your original buddy back.
365
+ `);
366
+ }
367
+
368
+ // packages/cli/commands/show.ts
369
+ function cmdShow() {
370
+ printHeader();
371
+ const realId = getRealUserId();
372
+ const effectiveId = getEffectiveUserId();
373
+ const realBones = rollBones(realId);
374
+ printBuddy("Your buddy:", realBones, realId);
375
+ if (effectiveId !== realId) {
376
+ const currentBones = rollBones(effectiveId);
377
+ printBuddy("Currently active (spoofed):", currentBones, effectiveId);
378
+ }
379
+ console.log();
380
+ }
381
+
382
+ // packages/cli/commands/roll.ts
383
+ import { parseArgs as parseArgs3 } from "util";
384
+ function cmdRoll(argv) {
385
+ const { positionals } = parseArgs3({
386
+ args: argv,
387
+ options: {},
388
+ allowPositionals: true
389
+ });
390
+ const userId = positionals[0];
391
+ if (!userId) {
392
+ console.error(`
393
+ Usage: buddydex roll <uuid>
394
+ `);
395
+ process.exit(1);
396
+ }
397
+ printHeader();
398
+ const bones = rollBones(userId);
399
+ printBuddy("Roll result:", bones, userId);
400
+ console.log();
401
+ }
402
+
403
+ // packages/cli/bin.ts
404
+ var USAGE = `
405
+ BuddyDex CLI
406
+
407
+ Commands:
408
+ hunt Search for buddies matching filters
409
+ inject Inject a seed UUID into ~/.claude.json
410
+ restore Restore your original UUID from backup
411
+ show Show your current buddy
412
+ roll Roll a specific UUID to see its buddy
413
+
414
+ Hunt options:
415
+ --species <name> Filter by species
416
+ --rarity <level> Filter by rarity (${RARITIES.join(", ")})
417
+ --shiny Only shiny buddies
418
+ --min-stat <name:val> Min value for a stat (e.g. chaos:80)
419
+ --min-total <val> Min total stat points (e.g. 400)
420
+ --perfect <val> ALL stats >= this value (e.g. 70)
421
+ --tries <n> UUIDs to try (default: 1000000)
422
+ --limit <n> Max results (default: 10)
423
+
424
+ Inject options:
425
+ --preview Preview without saving
426
+
427
+ Examples:
428
+ buddydex hunt --species dragon --rarity legendary
429
+ buddydex hunt --rarity legendary --perfect 70
430
+ buddydex inject <seed>
431
+ buddydex show
432
+ `;
433
+ var [command, ...rest] = process.argv.slice(2);
434
+ var commands = {
435
+ hunt: cmdHunt,
436
+ inject: cmdInject,
437
+ restore: cmdRestore,
438
+ show: cmdShow,
439
+ roll: cmdRoll
440
+ };
441
+ if (command && command in commands) {
442
+ commands[command](rest);
443
+ } else {
444
+ console.log(USAGE);
445
+ if (command && command !== "--help" && command !== "-h") {
446
+ console.error(` Unknown command: ${command}
447
+ `);
448
+ process.exit(1);
449
+ }
450
+ }
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "buddydex",
3
+ "version": "0.1.0",
4
+ "description": "Hunt, browse, and inject Claude Code buddies",
5
+ "type": "module",
6
+ "bin": {
7
+ "buddydex": "dist/buddydex.js"
8
+ },
9
+ "files": ["dist"],
10
+ "keywords": ["claude", "claude-code", "buddy", "companion", "wyhash"],
11
+ "author": "iammatthias",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/iammatthias/buddydex.git"
16
+ },
17
+ "dependencies": {
18
+ "@buddy/engine": "workspace:*"
19
+ }
20
+ }