Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ claude-buddy/
| `/buddy position [top\|left]` | Bubble position (tmux only) |
| `/buddy rarity [on\|off]` | Show or hide stars + rarity line (tmux only) |
| `/buddy width [10-60]` | Set bubble text width in chars (tmux only) |
| `/buddy margin [0-20]` | Set right-side margin (tmux only) |
| `/buddy margin [0-20]` | Set distance from terminal right edge to buddy art |
| `/buddy bars_offset [0-40]` | Set distance from terminal left edge to usage bars (combined mode) |
| `/buddy statusline [on\|off]` | Enable or disable buddy in the status line |
| `/buddy statusline combined` | Show rate-limit usage bars alongside buddy (needs python3) |
| `/buddy statusline basic` | Switch back to buddy-only status line |
Expand Down
9 changes: 9 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 52 additions & 11 deletions cli/hunt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
*/

import {
searchBuddy, renderBuddy, SPECIES, RARITIES, STAT_NAMES,
type Species, type Rarity, type StatName, type SearchCriteria,
searchBuddy, renderBuddy, renderFace, HAT_ART, SPECIES, RARITIES, EYES, HATS, STAT_NAMES,
type Eye, type Hat, type Species, type Rarity, type StatName, type SearchCriteria,
} from "../server/engine.ts";
import { generatePersonality } from "../server/generation.ts";
import {
saveCompanionSlot, saveActiveSlot, writeStatusState,
slugify, unusedName, listCompanionSlots,
Expand Down Expand Up @@ -108,7 +109,7 @@ ${CYAN}╚═══════════════════════
const r = top[i];
const stats = STAT_NAMES.map((n) => `${n.slice(0, 3)}:${r.bones.stats[n]}`).join(" ");
const shiny = r.bones.shiny ? "✨ " : " ";
console.log(` ${CYAN}${i + 1}${NC}) ${shiny}eye=${r.bones.eye} hat=${r.bones.hat} ${stats}`);
console.log(` ${CYAN}${i + 1}${NC}) ${shiny}${stats}`);
}

const pickAns = await ask(`\n Apply which? [1-${top.length}, q to cancel]: `);
Expand All @@ -122,19 +123,49 @@ ${CYAN}╚═══════════════════════
}

const chosen = top[pickIdx];

// ─── Eye picker ────────────────────────────────────────────────────────────
console.log(`\n${BOLD}Eye glyph:${NC}`);
EYES.forEach((e, i) => {
const face = renderFace(chosen.bones.species, e);
console.log(` ${CYAN}${(i + 1).toString().padStart(2)}${NC}) ${e} ${face}`);
});
let eye: Eye = EYES[0];
while (true) {
const ans = await ask(`\n Choice [1-${EYES.length}]: `);
const idx = parseInt(ans) - 1;
if (idx >= 0 && idx < EYES.length) { eye = EYES[idx]; break; }
console.log(` ${RED}Invalid. Enter 1-${EYES.length}.${NC}`);
}
chosen.bones.eye = eye;
console.log(`${GREEN}✓${NC} eye: ${eye}`);

// ─── Hat picker ────────────────────────────────────────────────────────────
console.log(`\n${BOLD}Hat:${NC}`);
HATS.forEach((h, i) => {
const art = HAT_ART[h] ? HAT_ART[h].trim() : "(none)";
console.log(` ${CYAN}${(i + 1).toString().padStart(2)}${NC}) ${h.padEnd(10)} ${art}`);
});
let hat: Hat = HATS[0];
while (true) {
const ans = await ask(`\n Choice [1-${HATS.length}]: `);
const idx = parseInt(ans) - 1;
if (idx >= 0 && idx < HATS.length) { hat = HATS[idx]; break; }
console.log(` ${RED}Invalid. Enter 1-${HATS.length}.${NC}`);
}
chosen.bones.hat = hat;
console.log(`${GREEN}✓${NC} hat: ${hat}`);

console.log(`\n${renderBuddy(chosen.bones)}\n`);

// ─── Ask for a name ────────────────────────────────────────────────────────
// ─── Name ──────────────────────────────────────────────────────────────────
const existing = new Set(listCompanionSlots().map((e) => slugify(e.companion.name)));
const suggested = unusedName();
console.log(`\n${DIM} Existing buddies: ${[...existing].join(", ") || "none"}${NC}`);
console.log(`${DIM} Existing buddies: ${[...existing].join(", ") || "none"}${NC}`);

let chosenName = "";
while (true) {
const raw = await ask(
` Name this buddy (Enter for "${suggested}"): `,
);
chosenName = raw.trim() || suggested;
const raw = await ask(` Name [leave blank for random]: `);
chosenName = raw.trim() || unusedName();
const slot = slugify(chosenName);
if (existing.has(slot)) {
console.log(` ${YELLOW}⚠${NC} Slot "${slot}" already taken — pick another name.`);
Expand All @@ -143,11 +174,21 @@ ${CYAN}╚═══════════════════════
}
}

// ─── Personality ───────────────────────────────────────────────────────────
const personalityRaw = await ask(` Personality [leave blank for random]: `);
let personality: string;
if (personalityRaw.trim()) {
personality = personalityRaw.trim();
} else {
console.log(`\n${DIM} Generating personality...${NC}`);
personality = await generatePersonality(chosen.bones);
}

const slot = slugify(chosenName);
const companion = {
bones: chosen.bones,
name: chosenName,
personality: `A ${chosen.bones.rarity} ${chosen.bones.species} who watches code with quiet intensity.`,
personality,
hatchedAt: Date.now(),
userId: chosen.userId,
};
Expand Down
Loading