@ramarivera/coding-buddy 0.4.0-alpha.8 → 0.6.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.
- package/{adapters/claude/plugin → .claude-plugin}/marketplace.json +11 -6
- package/{adapters/claude/plugin → .claude-plugin}/plugin.json +9 -7
- package/LICENSE +1 -1
- package/README.md +102 -72
- package/adapters/omp/commands.ts +201 -0
- package/adapters/omp/context.ts +22 -0
- package/adapters/omp/events.ts +551 -0
- package/adapters/omp/identity.ts +10 -0
- package/adapters/omp/index.ts +39 -0
- package/adapters/omp/logger.ts +52 -0
- package/adapters/omp/prompt.ts +60 -0
- package/adapters/omp/renderers.ts +73 -0
- package/adapters/omp/storage.ts +25 -0
- package/adapters/omp/ui.ts +85 -0
- package/adapters/pi/commands.ts +64 -37
- package/adapters/pi/events.ts +147 -72
- package/adapters/pi/index.ts +9 -4
- package/adapters/pi/storage.ts +14 -285
- package/adapters/pi/ui.ts +56 -5
- package/adapters/shared/file-storage.ts +526 -0
- package/{adapters/claude/install → cli}/backup.ts +59 -44
- package/cli/biomes.ts +309 -0
- package/cli/buddy-shell.ts +818 -0
- package/{adapters/claude/install → cli}/disable.ts +14 -32
- package/{adapters/claude/install → cli}/doctor.ts +55 -57
- package/{adapters/claude/install → cli}/hunt.ts +7 -9
- package/cli/index.ts +36 -25
- package/{adapters/claude/install → cli}/install.ts +100 -145
- package/{adapters/claude/install → cli}/pick.ts +111 -50
- package/{adapters/claude/install → cli}/settings.ts +2 -2
- package/{adapters/claude/install → cli}/show.ts +4 -4
- package/{adapters/claude/install → cli}/test-statusline.sh +2 -2
- package/{adapters/claude/install → cli}/test-statusline.ts +11 -11
- package/cli/tui.tsx +2333 -0
- package/{adapters/claude/install → cli}/uninstall.ts +23 -38
- package/cli/upgrade.ts +213 -0
- package/cli/validate-species.ts +209 -0
- package/cli/verify.ts +3 -3
- package/core/achievements.ts +12 -20
- package/core/art-data.ts +26 -0
- package/core/command-service.ts +99 -97
- package/core/engine.ts +83 -1
- package/core/ports.ts +10 -0
- package/core/reactions.ts +533 -95
- package/core/slot-slug.ts +22 -0
- package/{adapters/claude/hooks → hooks}/buddy-comment.sh +23 -2
- package/hooks/file-type-react.sh +401 -0
- package/hooks/hooks.json +52 -0
- package/hooks/mood-react.sh +208 -0
- package/{adapters/claude/hooks → hooks}/name-react.sh +5 -2
- package/hooks/react.sh +1368 -0
- package/hooks/suggest.sh +55 -0
- package/package.json +113 -59
- package/scripts/gen-emoji-widths.ts +34 -0
- package/scripts/paths.sh +56 -0
- package/scripts/postinstall.cjs +42 -0
- package/scripts/snapshot-statusline.sh +54 -0
- package/server/achievements.ts +1419 -0
- package/server/art.ts +450 -0
- package/server/award-xp.ts +48 -0
- package/server/check-suggestions.ts +102 -0
- package/server/consolidate.ts +33 -0
- package/server/index.ts +1340 -0
- package/server/mcp-launcher.sh +26 -0
- package/server/memory.ts +404 -0
- package/server/mood.ts +254 -0
- package/server/path.ts +82 -0
- package/server/shift-mood.ts +43 -0
- package/{adapters/claude/storage → server}/state.ts +200 -7
- package/server/suggestions.ts +335 -0
- package/server/theme.ts +53 -0
- package/server/xp.ts +435 -0
- package/{adapters/claude/skills → skills}/buddy/SKILL.md +48 -4
- package/statusline/buddy-status.sh +397 -0
- package/statusline/combined-status.sh +134 -0
- package/statusline/emoji-widths.data +4 -0
- package/adapters/claude/hooks/hooks.json +0 -35
- package/adapters/claude/hooks/react.sh +0 -204
- package/adapters/claude/popup/buddy-popup.sh +0 -92
- package/adapters/claude/popup/buddy-render.sh +0 -540
- package/adapters/claude/popup/popup-manager.sh +0 -355
- package/adapters/claude/rendering/art.ts +0 -264
- package/adapters/claude/server/index.ts +0 -752
- package/adapters/claude/server/instructions.ts +0 -24
- package/adapters/claude/server/resources.ts +0 -38
- package/adapters/claude/statusline/buddy-status.sh +0 -389
- package/adapters/claude/storage/achievements.ts +0 -253
- package/adapters/claude/storage/identity.ts +0 -13
- package/adapters/claude/storage/paths.ts +0 -24
- package/adapters/claude/storage/settings.ts +0 -41
- package/adapters/pi/README.md +0 -83
- package/adapters/pi/tools.ts +0 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* coding-buddy backup — snapshot all coding-buddy related state
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
6
|
* bun run backup Create a new snapshot
|
|
@@ -10,15 +10,20 @@
|
|
|
10
10
|
* bun run backup restore <ts> Restore a specific backup
|
|
11
11
|
* bun run backup delete <ts> Delete a specific backup
|
|
12
12
|
*
|
|
13
|
-
* Backups are stored in
|
|
13
|
+
* Backups are stored in <state-dir>/backups/YYYY-MM-DD-HHMMSS/, where
|
|
14
|
+
* <state-dir> resolves via server/paths.ts ($CLAUDE_CONFIG_DIR/buddy-state
|
|
15
|
+
* when that env var is set, else ~/.claude-buddy).
|
|
14
16
|
*
|
|
15
17
|
* What gets backed up:
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
20
|
-
* -
|
|
21
|
-
* -
|
|
18
|
+
* - settings.json (full file)
|
|
19
|
+
* - .claude.json mcpServers["claude-buddy"] block (only our entry)
|
|
20
|
+
* - skills/buddy/SKILL.md
|
|
21
|
+
* - <state-dir>/menagerie.json
|
|
22
|
+
* - <state-dir>/config.json
|
|
23
|
+
* - <state-dir>/status.json
|
|
24
|
+
* - <state-dir>/events.json
|
|
25
|
+
* - <state-dir>/unlocked.json
|
|
26
|
+
* - <state-dir>/active_days.json
|
|
22
27
|
*/
|
|
23
28
|
|
|
24
29
|
import {
|
|
@@ -26,15 +31,19 @@ import {
|
|
|
26
31
|
readdirSync, statSync, rmSync, copyFileSync,
|
|
27
32
|
} from "fs";
|
|
28
33
|
import { dirname, join } from "path";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const
|
|
34
|
+
import {
|
|
35
|
+
buddyStateDir,
|
|
36
|
+
claudeSettingsPath,
|
|
37
|
+
claudeSkillDir,
|
|
38
|
+
claudeUserConfigPath,
|
|
39
|
+
} from "../server/path.ts";
|
|
40
|
+
|
|
41
|
+
const SETTINGS = claudeSettingsPath();
|
|
42
|
+
const CLAUDE_JSON = claudeUserConfigPath();
|
|
43
|
+
const SKILL_DIR = claudeSkillDir("buddy");
|
|
44
|
+
const SKILL = join(SKILL_DIR, "SKILL.md");
|
|
45
|
+
const STATE_DIR = buddyStateDir();
|
|
46
|
+
const BACKUPS_DIR = join(STATE_DIR, "backups");
|
|
38
47
|
|
|
39
48
|
const RED = "\x1b[31m";
|
|
40
49
|
const GREEN = "\x1b[32m";
|
|
@@ -74,16 +83,16 @@ function createBackup(): string {
|
|
|
74
83
|
const dir = join(BACKUPS_DIR, ts);
|
|
75
84
|
mkdirSync(dir, { recursive: true });
|
|
76
85
|
|
|
77
|
-
const manifest:
|
|
86
|
+
const manifest: Record<string, any> = { timestamp: ts, files: [] };
|
|
78
87
|
|
|
79
88
|
// 1. settings.json
|
|
80
89
|
const settings = tryRead(SETTINGS);
|
|
81
90
|
if (settings) {
|
|
82
91
|
writeFileSync(join(dir, "settings.json"), settings);
|
|
83
92
|
manifest.files.push("settings.json");
|
|
84
|
-
ok(`Backed up:
|
|
93
|
+
ok(`Backed up: ${SETTINGS}`);
|
|
85
94
|
} else {
|
|
86
|
-
warn(`Skipped:
|
|
95
|
+
warn(`Skipped: ${SETTINGS} (not found)`);
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
// 2. claude.json mcpServers["claude-buddy"]
|
|
@@ -95,12 +104,12 @@ function createBackup(): string {
|
|
|
95
104
|
if (ourMcp) {
|
|
96
105
|
writeFileSync(join(dir, "mcpserver.json"), JSON.stringify(ourMcp, null, 2));
|
|
97
106
|
manifest.files.push("mcpserver.json");
|
|
98
|
-
ok(`Backed up:
|
|
107
|
+
ok(`Backed up: ${CLAUDE_JSON} → mcpServers["claude-buddy"]`);
|
|
99
108
|
} else {
|
|
100
|
-
warn(`Skipped:
|
|
109
|
+
warn(`Skipped: ${CLAUDE_JSON} mcpServers["claude-buddy"] (not registered)`);
|
|
101
110
|
}
|
|
102
111
|
} catch {
|
|
103
|
-
err(`Failed to parse
|
|
112
|
+
err(`Failed to parse ${CLAUDE_JSON}`);
|
|
104
113
|
}
|
|
105
114
|
}
|
|
106
115
|
|
|
@@ -109,21 +118,21 @@ function createBackup(): string {
|
|
|
109
118
|
if (skill) {
|
|
110
119
|
writeFileSync(join(dir, "SKILL.md"), skill);
|
|
111
120
|
manifest.files.push("SKILL.md");
|
|
112
|
-
ok(`Backed up:
|
|
121
|
+
ok(`Backed up: ${SKILL}`);
|
|
113
122
|
} else {
|
|
114
|
-
warn(`Skipped:
|
|
123
|
+
warn(`Skipped: ${SKILL} (not found)`);
|
|
115
124
|
}
|
|
116
125
|
|
|
117
|
-
// 4
|
|
126
|
+
// 4+. ~/.claude-buddy/ state files (don't back up the backups dir itself)
|
|
118
127
|
const stateDestDir = join(dir, "claude-buddy");
|
|
119
128
|
mkdirSync(stateDestDir, { recursive: true });
|
|
120
|
-
const stateFiles = ["
|
|
129
|
+
const stateFiles = ["menagerie.json", "config.json", "status.json", "events.json", "unlocked.json", "active_days.json"];
|
|
121
130
|
for (const f of stateFiles) {
|
|
122
131
|
const src = join(STATE_DIR, f);
|
|
123
132
|
if (existsSync(src)) {
|
|
124
133
|
copyFileSync(src, join(stateDestDir, f));
|
|
125
134
|
manifest.files.push(`claude-buddy/${f}`);
|
|
126
|
-
ok(`Backed up:
|
|
135
|
+
ok(`Backed up: ${join(STATE_DIR, f)}`);
|
|
127
136
|
}
|
|
128
137
|
}
|
|
129
138
|
|
|
@@ -141,7 +150,7 @@ function cmdList() {
|
|
|
141
150
|
info(`Run '${BOLD}bun run backup${NC}' to create one.`);
|
|
142
151
|
return;
|
|
143
152
|
}
|
|
144
|
-
console.log(`\n${BOLD}
|
|
153
|
+
console.log(`\n${BOLD}coding-buddy backups${NC}\n`);
|
|
145
154
|
for (const ts of backups) {
|
|
146
155
|
const manifestPath = join(BACKUPS_DIR, ts, "manifest.json");
|
|
147
156
|
const manifest = tryRead(manifestPath);
|
|
@@ -196,29 +205,32 @@ function restoreBackup(ts: string) {
|
|
|
196
205
|
if (existsSync(settingsBak)) {
|
|
197
206
|
mkdirSync(dirname(SETTINGS), { recursive: true });
|
|
198
207
|
copyFileSync(settingsBak, SETTINGS);
|
|
199
|
-
ok(
|
|
208
|
+
ok(`Restored: ${SETTINGS}`);
|
|
200
209
|
}
|
|
201
210
|
|
|
202
211
|
// 2. claude.json mcpServers — merge our entry back in
|
|
203
212
|
const mcpBak = join(dir, "mcpserver.json");
|
|
204
213
|
if (existsSync(mcpBak)) {
|
|
205
214
|
const ourMcp = JSON.parse(readFileSync(mcpBak, "utf8"));
|
|
206
|
-
let claudeJson:
|
|
215
|
+
let claudeJson: Record<string, any> = {};
|
|
207
216
|
try {
|
|
208
|
-
claudeJson = JSON.parse(readFileSync(CLAUDE_JSON, "utf8"))
|
|
217
|
+
claudeJson = JSON.parse(readFileSync(CLAUDE_JSON, "utf8"));
|
|
209
218
|
} catch { /* empty */ }
|
|
210
219
|
if (!claudeJson.mcpServers) claudeJson.mcpServers = {};
|
|
211
220
|
claudeJson.mcpServers["claude-buddy"] = ourMcp;
|
|
221
|
+
// Under CLAUDE_CONFIG_DIR the parent dir is not guaranteed to exist
|
|
222
|
+
// if settings.json wasn't in this backup (and so step 1 was skipped).
|
|
223
|
+
mkdirSync(dirname(CLAUDE_JSON), { recursive: true });
|
|
212
224
|
writeFileSync(CLAUDE_JSON, JSON.stringify(claudeJson, null, 2));
|
|
213
|
-
ok(
|
|
225
|
+
ok(`Restored: ${CLAUDE_JSON} → mcpServers["claude-buddy"]`);
|
|
214
226
|
}
|
|
215
227
|
|
|
216
228
|
// 3. SKILL.md
|
|
217
229
|
const skillBak = join(dir, "SKILL.md");
|
|
218
230
|
if (existsSync(skillBak)) {
|
|
219
|
-
mkdirSync(
|
|
231
|
+
mkdirSync(SKILL_DIR, { recursive: true });
|
|
220
232
|
copyFileSync(skillBak, SKILL);
|
|
221
|
-
ok(
|
|
233
|
+
ok(`Restored: ${SKILL}`);
|
|
222
234
|
}
|
|
223
235
|
|
|
224
236
|
// 4. ~/.claude-buddy/ state files
|
|
@@ -227,7 +239,7 @@ function restoreBackup(ts: string) {
|
|
|
227
239
|
mkdirSync(STATE_DIR, { recursive: true });
|
|
228
240
|
for (const f of readdirSync(stateDir)) {
|
|
229
241
|
copyFileSync(join(stateDir, f), join(STATE_DIR, f));
|
|
230
|
-
ok(`Restored:
|
|
242
|
+
ok(`Restored: ${join(STATE_DIR, f)}`);
|
|
231
243
|
}
|
|
232
244
|
}
|
|
233
245
|
|
|
@@ -254,7 +266,7 @@ const arg = process.argv[3];
|
|
|
254
266
|
switch (action) {
|
|
255
267
|
case "create":
|
|
256
268
|
case undefined: {
|
|
257
|
-
console.log(`\n${BOLD}Creating
|
|
269
|
+
console.log(`\n${BOLD}Creating coding-buddy backup...${NC}\n`);
|
|
258
270
|
const ts = createBackup();
|
|
259
271
|
console.log(`\n${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`);
|
|
260
272
|
console.log(`${GREEN} Backup created: ${ts}${NC}`);
|
|
@@ -307,7 +319,7 @@ switch (action) {
|
|
|
307
319
|
case "--help":
|
|
308
320
|
case "-h":
|
|
309
321
|
console.log(`
|
|
310
|
-
${BOLD}
|
|
322
|
+
${BOLD}coding-buddy backup${NC} — snapshot and restore all coding-buddy state
|
|
311
323
|
|
|
312
324
|
${BOLD}Commands:${NC}
|
|
313
325
|
bun run backup Create a new snapshot
|
|
@@ -318,12 +330,15 @@ ${BOLD}Commands:${NC}
|
|
|
318
330
|
bun run backup delete <ts> Delete a specific backup
|
|
319
331
|
|
|
320
332
|
${BOLD}What gets backed up:${NC}
|
|
321
|
-
-
|
|
322
|
-
-
|
|
323
|
-
-
|
|
324
|
-
-
|
|
325
|
-
-
|
|
326
|
-
-
|
|
333
|
+
- ${SETTINGS}
|
|
334
|
+
- ${CLAUDE_JSON} mcpServers["claude-buddy"] (only our entry)
|
|
335
|
+
- ${SKILL}
|
|
336
|
+
- ${STATE_DIR}/menagerie.json
|
|
337
|
+
- ${STATE_DIR}/config.json
|
|
338
|
+
- ${STATE_DIR}/status.json
|
|
339
|
+
- ${STATE_DIR}/events.json
|
|
340
|
+
- ${STATE_DIR}/unlocked.json
|
|
341
|
+
- ${STATE_DIR}/active_days.json
|
|
327
342
|
|
|
328
343
|
${BOLD}Backup location:${NC}
|
|
329
344
|
${BACKUPS_DIR}/<timestamp>/
|
package/cli/biomes.ts
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* biomes.ts — landscape themes for the buddy panel
|
|
3
|
+
*
|
|
4
|
+
* 5 default biomes (mapped to rarity) + 10 extra biomes selectable via --biome flag.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx tsx cli/buddy-shell.ts claude # auto-detect by rarity
|
|
8
|
+
* npx tsx cli/buddy-shell.ts claude --biome volcano # force a specific biome
|
|
9
|
+
* npx tsx cli/buddy-shell.ts --biomes # list all available biomes
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const ESC = "\x1b";
|
|
13
|
+
const CSI = `${ESC}[`;
|
|
14
|
+
const NC = `${CSI}0m`;
|
|
15
|
+
|
|
16
|
+
function c(r: number, g: number, b: number): string {
|
|
17
|
+
return `${CSI}38;2;${r};${g};${b}m`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ─── Biome definition ───────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export interface Biome {
|
|
23
|
+
name: string;
|
|
24
|
+
sky: string;
|
|
25
|
+
ground: string;
|
|
26
|
+
particle: { color: string; chars: string[] };
|
|
27
|
+
groundChars: string[];
|
|
28
|
+
structure: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
32
|
+
// DEFAULT BIOMES (by rarity)
|
|
33
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
34
|
+
|
|
35
|
+
const meadow: Biome = {
|
|
36
|
+
name: "meadow",
|
|
37
|
+
sky: c(40, 40, 60),
|
|
38
|
+
ground: c(60, 90, 40),
|
|
39
|
+
particle: { color: c(150, 150, 130), chars: ["✦", "·", "·"] },
|
|
40
|
+
groundChars: ["▄", "▄", "▄", "▓", "▄", "▒"],
|
|
41
|
+
structure: [
|
|
42
|
+
`${c(120, 80, 60)} ░░${NC}`,
|
|
43
|
+
`${c(160, 60, 50)} ▄████████▄${NC}`,
|
|
44
|
+
`${c(160, 60, 50)} ▟${c(140, 90, 50)}██${c(200, 200, 100)}▪▪${c(140, 90, 50)}██${c(200, 200, 100)}▪▪${c(140, 90, 50)}██${c(160, 60, 50)}▙${NC}`,
|
|
45
|
+
`${c(140, 90, 50)} ██${c(200, 200, 100)}▪▪${c(140, 90, 50)}██${c(200, 200, 100)}▪▪${c(140, 90, 50)}████${NC}`,
|
|
46
|
+
`${c(140, 90, 50)} ██${c(200, 200, 100)}▪▪${c(140, 90, 50)}█${c(100, 60, 30)}▐██▌${c(140, 90, 50)}█${c(200, 200, 100)}▪▪${c(140, 90, 50)}██${NC}`,
|
|
47
|
+
`${c(140, 90, 50)} ██${c(200, 200, 100)}▪▪${c(140, 90, 50)}█${c(100, 60, 30)}▐██▌${c(140, 90, 50)}█${c(200, 200, 100)}▪▪${c(140, 90, 50)}██${NC}`,
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const forest: Biome = {
|
|
52
|
+
name: "forest",
|
|
53
|
+
sky: c(15, 35, 25),
|
|
54
|
+
ground: c(30, 80, 35),
|
|
55
|
+
particle: { color: c(80, 160, 80), chars: ["✦", "·", "·"] },
|
|
56
|
+
groundChars: ["▄", "▄", "▓", "▄", "▒", "▄"],
|
|
57
|
+
structure: [
|
|
58
|
+
`${c(40, 110, 40)} ▄██████▄${NC}`,
|
|
59
|
+
`${c(50, 130, 50)}▄██${c(200, 50, 30)}●${c(50, 130, 50)}███${c(200, 50, 30)}●${c(50, 130, 50)}██▄${NC}`,
|
|
60
|
+
`${c(60, 150, 60)}████${c(200, 50, 30)}●${c(60, 150, 60)}███████${NC}`,
|
|
61
|
+
`${c(60, 150, 60)}██████${c(200, 50, 30)}●${c(60, 150, 60)}████${NC}`,
|
|
62
|
+
`${c(50, 130, 50)} ▀████████▀${NC}`,
|
|
63
|
+
`${c(80, 60, 40)} ▟${c(60, 100, 50)}██${c(200, 200, 100)}▪▪${c(60, 100, 50)}██${c(80, 60, 40)}▙${NC}`,
|
|
64
|
+
`${c(80, 60, 40)} ║${c(60, 100, 50)}██${c(200, 200, 100)}▪▪${c(60, 100, 50)}██${c(80, 60, 40)}║${NC}`,
|
|
65
|
+
`${c(80, 60, 40)} ║██${c(100, 60, 30)}▐██▌${c(80, 60, 40)}██║${NC}`,
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const ocean: Biome = {
|
|
70
|
+
name: "ocean",
|
|
71
|
+
sky: c(15, 20, 55),
|
|
72
|
+
ground: c(40, 90, 140),
|
|
73
|
+
particle: { color: c(100, 140, 220), chars: ["✦", "·", "~"] },
|
|
74
|
+
groundChars: ["▄", "~", "≈", "▄", "~", "▄"],
|
|
75
|
+
structure: [
|
|
76
|
+
`${c(200, 200, 100)} ◉${NC}`,
|
|
77
|
+
`${c(200, 50, 50)} ▄███▄${NC}`,
|
|
78
|
+
`${c(220, 220, 220)} ▐${c(200, 50, 50)}█${c(220, 220, 220)}███${c(200, 50, 50)}█${c(220, 220, 220)}▌${NC}`,
|
|
79
|
+
`${c(220, 220, 220)} ▐█████▌${NC}`,
|
|
80
|
+
`${c(220, 220, 220)} ▐█████▌${NC}`,
|
|
81
|
+
`${c(180, 180, 180)} ▟███████▙${NC}`,
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const cyberpunk: Biome = {
|
|
86
|
+
name: "cyberpunk",
|
|
87
|
+
sky: c(20, 10, 35),
|
|
88
|
+
ground: c(40, 30, 60),
|
|
89
|
+
particle: { color: c(180, 80, 220), chars: ["✦", "·", "░"] },
|
|
90
|
+
groundChars: ["▀", "▄", "█", "▀", "▄", "░"],
|
|
91
|
+
structure: [
|
|
92
|
+
`${c(255, 0, 100)} ▄${c(0, 255, 200)}█${c(255, 0, 100)}█${c(0, 255, 200)}█${c(255, 0, 100)}▄${NC}`,
|
|
93
|
+
`${c(80, 60, 100)} █${c(0, 255, 200)}▪${c(80, 60, 100)}█${c(255, 0, 100)}▪${c(80, 60, 100)}█${NC}`,
|
|
94
|
+
`${c(80, 60, 100)} █${c(255, 0, 100)}▪${c(80, 60, 100)}█${c(0, 255, 200)}▪${c(80, 60, 100)}█${NC}`,
|
|
95
|
+
`${c(80, 60, 100)} █${c(0, 255, 200)}▪${c(80, 60, 100)}█${c(255, 0, 100)}▪${c(80, 60, 100)}█${NC}`,
|
|
96
|
+
`${c(80, 60, 100)} ██${c(0, 255, 200)}▪${c(80, 60, 100)}█${c(255, 0, 100)}▪${c(80, 60, 100)}██${NC}`,
|
|
97
|
+
`${c(80, 60, 100)} ██${c(255, 200, 0)}▐▌${c(80, 60, 100)}█${c(255, 200, 0)}▐▌${c(80, 60, 100)}██${NC}`,
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const space: Biome = {
|
|
102
|
+
name: "space",
|
|
103
|
+
sky: c(8, 5, 20),
|
|
104
|
+
ground: c(50, 40, 70),
|
|
105
|
+
particle: { color: c(255, 200, 50), chars: ["✦", "·", "·", "✧"] },
|
|
106
|
+
groundChars: ["▄", "░", "▒", "▄", "▓", "░"],
|
|
107
|
+
structure: [
|
|
108
|
+
`${c(255, 200, 50)} ✨${NC}`,
|
|
109
|
+
`${c(100, 80, 150)} ▄████▄${NC}`,
|
|
110
|
+
`${c(120, 100, 170)} ▟${c(150, 130, 200)}██${c(255, 220, 100)}◆◆${c(150, 130, 200)}██${c(120, 100, 170)}▙${NC}`,
|
|
111
|
+
`${c(150, 130, 200)} ██${c(255, 220, 100)}◆◆${c(150, 130, 200)}████${NC}`,
|
|
112
|
+
`${c(150, 130, 200)} ██${c(255, 220, 100)}◆◆${c(150, 130, 200)}█${c(200, 180, 255)}▐██▌${c(150, 130, 200)}██${NC}`,
|
|
113
|
+
`${c(150, 130, 200)} ████████████${NC}`,
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
118
|
+
// EXTRA BIOMES (selectable via --biome flag)
|
|
119
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
120
|
+
|
|
121
|
+
const volcano: Biome = {
|
|
122
|
+
name: "volcano",
|
|
123
|
+
sky: c(50, 15, 5),
|
|
124
|
+
ground: c(80, 30, 10),
|
|
125
|
+
particle: { color: c(255, 120, 20), chars: ["✦", "·", "▪"] },
|
|
126
|
+
groundChars: ["▄", "▓", "▄", "█", "▒", "▄"],
|
|
127
|
+
structure: [
|
|
128
|
+
`${c(255, 80, 0)} ▓${NC}`,
|
|
129
|
+
`${c(255, 120, 20)} ▄${c(200, 50, 10)}█${c(255, 120, 20)}▄${NC}`,
|
|
130
|
+
`${c(200, 50, 10)} ▟███▙${NC}`,
|
|
131
|
+
`${c(150, 40, 10)} ▟█████▙${NC}`,
|
|
132
|
+
`${c(120, 30, 10)} ▟███████▙${NC}`,
|
|
133
|
+
`${c(100, 25, 8)} ▟█████████▙${NC}`,
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const arctic: Biome = {
|
|
138
|
+
name: "arctic",
|
|
139
|
+
sky: c(20, 30, 50),
|
|
140
|
+
ground: c(180, 200, 220),
|
|
141
|
+
particle: { color: c(200, 220, 255), chars: ["❄", "·", "✧"] },
|
|
142
|
+
groundChars: ["▄", "▄", "░", "▄", "▄", "▒"],
|
|
143
|
+
structure: [
|
|
144
|
+
`${c(200, 220, 240)} ▄▄▄${NC}`,
|
|
145
|
+
`${c(200, 220, 240)} ▄█████▄${NC}`,
|
|
146
|
+
`${c(220, 230, 245)} █████████${NC}`,
|
|
147
|
+
`${c(220, 230, 245)} ███${c(60, 80, 120)}▐██▌${c(220, 230, 245)}███${NC}`,
|
|
148
|
+
`${c(220, 230, 245)} █████████${NC}`,
|
|
149
|
+
`${c(200, 220, 240)} █████████${NC}`,
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const desert: Biome = {
|
|
154
|
+
name: "desert",
|
|
155
|
+
sky: c(40, 25, 15),
|
|
156
|
+
ground: c(180, 150, 80),
|
|
157
|
+
particle: { color: c(200, 180, 100), chars: ["✦", "·", "·"] },
|
|
158
|
+
groundChars: ["▄", "░", "▄", "▄", "░", "▄"],
|
|
159
|
+
structure: [
|
|
160
|
+
`${c(200, 180, 100)} ▲${NC}`,
|
|
161
|
+
`${c(200, 180, 100)} ▟█▙${NC}`,
|
|
162
|
+
`${c(190, 170, 90)} ▟███▙${NC}`,
|
|
163
|
+
`${c(180, 160, 80)} ▟█████▙${NC}`,
|
|
164
|
+
`${c(170, 150, 70)} ▟███████▙${NC}`,
|
|
165
|
+
`${c(160, 140, 60)} ▟█████████▙${NC}`,
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const haunted: Biome = {
|
|
170
|
+
name: "haunted",
|
|
171
|
+
sky: c(15, 10, 25),
|
|
172
|
+
ground: c(40, 35, 50),
|
|
173
|
+
particle: { color: c(100, 80, 140), chars: ["✦", "·", "👻"] },
|
|
174
|
+
groundChars: ["▄", "▒", "▄", "▓", "▄", "▒"],
|
|
175
|
+
structure: [
|
|
176
|
+
`${c(60, 50, 80)} ▄█▄${NC}`,
|
|
177
|
+
`${c(60, 50, 80)} ▄█████▄${NC}`,
|
|
178
|
+
`${c(50, 40, 70)} ██${c(200, 50, 50)}▪▪▪${c(50, 40, 70)}██${NC}`,
|
|
179
|
+
`${c(50, 40, 70)} ██${c(200, 50, 50)}▪▪▪${c(50, 40, 70)}██${NC}`,
|
|
180
|
+
`${c(50, 40, 70)} ████${c(30, 20, 40)}▐▌${c(50, 40, 70)}████${NC}`,
|
|
181
|
+
`${c(40, 30, 60)} ████████████${NC}`,
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const sakura: Biome = {
|
|
186
|
+
name: "sakura",
|
|
187
|
+
sky: c(40, 20, 35),
|
|
188
|
+
ground: c(80, 60, 50),
|
|
189
|
+
particle: { color: c(255, 150, 180), chars: ["✿", "·", "✦"] },
|
|
190
|
+
groundChars: ["▄", "▄", "▓", "▄", "▄", "▒"],
|
|
191
|
+
structure: [
|
|
192
|
+
`${c(200, 50, 80)} ▄▄▄▄▄${NC}`,
|
|
193
|
+
`${c(200, 50, 80)} ▄███████▄${NC}`,
|
|
194
|
+
`${c(160, 40, 60)} ▀███████▀${NC}`,
|
|
195
|
+
`${c(120, 70, 50)} ║║${NC}`,
|
|
196
|
+
`${c(120, 70, 50)} ║║${NC}`,
|
|
197
|
+
`${c(120, 70, 50)} ▄▄║║▄▄${NC}`,
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const underwater: Biome = {
|
|
202
|
+
name: "underwater",
|
|
203
|
+
sky: c(10, 30, 60),
|
|
204
|
+
ground: c(40, 80, 60),
|
|
205
|
+
particle: { color: c(80, 180, 220), chars: ["°", "·", "○"] },
|
|
206
|
+
groundChars: ["▄", "▒", "▓", "▄", "▒", "▄"],
|
|
207
|
+
structure: [
|
|
208
|
+
`${c(80, 200, 100)} \\│//${NC}`,
|
|
209
|
+
`${c(80, 200, 100)} \\│/${NC}`,
|
|
210
|
+
`${c(80, 200, 100)} │${NC}`,
|
|
211
|
+
`${c(255, 100, 80)} ▄███▄${NC}`,
|
|
212
|
+
`${c(255, 80, 60)} ▟█████▙${NC}`,
|
|
213
|
+
`${c(255, 60, 40)} ▟███████▙${NC}`,
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const candyland: Biome = {
|
|
218
|
+
name: "candyland",
|
|
219
|
+
sky: c(50, 20, 50),
|
|
220
|
+
ground: c(200, 100, 150),
|
|
221
|
+
particle: { color: c(255, 150, 200), chars: ["✦", "♥", "·"] },
|
|
222
|
+
groundChars: ["▄", "▄", "░", "▄", "▄", "▒"],
|
|
223
|
+
structure: [
|
|
224
|
+
`${c(255, 200, 100)} ★${NC}`,
|
|
225
|
+
`${c(200, 100, 50)} ▄█████▄${NC}`,
|
|
226
|
+
`${c(220, 150, 100)} █${c(255, 100, 150)}▪${c(100, 200, 255)}▪${c(255, 255, 100)}▪${c(100, 255, 100)}▪${c(255, 100, 150)}▪${c(220, 150, 100)}█${NC}`,
|
|
227
|
+
`${c(220, 150, 100)} █${c(100, 255, 100)}▪${c(255, 100, 150)}▪${c(100, 200, 255)}▪${c(255, 255, 100)}▪${c(100, 255, 100)}▪${c(220, 150, 100)}█${NC}`,
|
|
228
|
+
`${c(220, 150, 100)} █████${c(180, 80, 50)}▐▌${c(220, 150, 100)}████${NC}`,
|
|
229
|
+
`${c(220, 150, 100)} █████████████${NC}`,
|
|
230
|
+
],
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const dungeon: Biome = {
|
|
234
|
+
name: "dungeon",
|
|
235
|
+
sky: c(20, 18, 15),
|
|
236
|
+
ground: c(60, 55, 45),
|
|
237
|
+
particle: { color: c(255, 150, 30), chars: ["✦", "·", "·"] },
|
|
238
|
+
groundChars: ["▄", "█", "▄", "▓", "▄", "█"],
|
|
239
|
+
structure: [
|
|
240
|
+
`${c(80, 75, 65)} ▄█▄█▄█▄█▄${NC}`,
|
|
241
|
+
`${c(80, 75, 65)} ██████████${NC}`,
|
|
242
|
+
`${c(70, 65, 55)} ██${c(255, 150, 30)}▪▪${c(70, 65, 55)}██${c(255, 150, 30)}▪▪${c(70, 65, 55)}██${NC}`,
|
|
243
|
+
`${c(70, 65, 55)} ██████████${NC}`,
|
|
244
|
+
`${c(70, 65, 55)} ██${c(40, 35, 25)}▐██▌${c(70, 65, 55)}████${NC}`,
|
|
245
|
+
`${c(60, 55, 45)} ██████████${NC}`,
|
|
246
|
+
],
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const cloudkingdom: Biome = {
|
|
250
|
+
name: "cloudkingdom",
|
|
251
|
+
sky: c(30, 40, 70),
|
|
252
|
+
ground: c(180, 190, 220),
|
|
253
|
+
particle: { color: c(200, 210, 240), chars: ["✦", "☁", "·"] },
|
|
254
|
+
groundChars: ["▄", "░", "▄", "▄", "░", "░"],
|
|
255
|
+
structure: [
|
|
256
|
+
`${c(200, 180, 255)} ▲${NC}`,
|
|
257
|
+
`${c(200, 180, 255)} ▟█▙${NC}`,
|
|
258
|
+
`${c(180, 160, 240)} ▟███▙${NC}`,
|
|
259
|
+
`${c(180, 160, 240)} █${c(255, 220, 100)}◆◆${c(180, 160, 240)}██${NC}`,
|
|
260
|
+
`${c(180, 160, 240)} █████${NC}`,
|
|
261
|
+
`${c(200, 200, 230)} ▟█████████▙${NC}`,
|
|
262
|
+
],
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const matrix: Biome = {
|
|
266
|
+
name: "matrix",
|
|
267
|
+
sky: c(0, 10, 0),
|
|
268
|
+
ground: c(0, 40, 0),
|
|
269
|
+
particle: { color: c(0, 200, 0), chars: ["0", "1", "·"] },
|
|
270
|
+
groundChars: ["▄", "█", "▄", "▀", "▄", "█"],
|
|
271
|
+
structure: [
|
|
272
|
+
`${c(0, 150, 0)} ▄██████▄${NC}`,
|
|
273
|
+
`${c(0, 120, 0)} █${c(0, 255, 0)}01${c(0, 120, 0)}██${c(0, 255, 0)}10${c(0, 120, 0)}█${NC}`,
|
|
274
|
+
`${c(0, 120, 0)} █${c(0, 255, 0)}10${c(0, 120, 0)}██${c(0, 255, 0)}01${c(0, 120, 0)}█${NC}`,
|
|
275
|
+
`${c(0, 120, 0)} █${c(0, 255, 0)}01${c(0, 120, 0)}██${c(0, 255, 0)}10${c(0, 120, 0)}█${NC}`,
|
|
276
|
+
`${c(0, 100, 0)} █${c(0, 200, 0)}▐▌${c(0, 100, 0)}██${c(0, 200, 0)}▐▌${c(0, 100, 0)}█${NC}`,
|
|
277
|
+
`${c(0, 80, 0)} ▀██████▀${NC}`,
|
|
278
|
+
],
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// ─── Registry ───────────────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
const RARITY_BIOMES: Record<string, Biome> = {
|
|
284
|
+
common: meadow,
|
|
285
|
+
uncommon: forest,
|
|
286
|
+
rare: ocean,
|
|
287
|
+
epic: cyberpunk,
|
|
288
|
+
legendary: space,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const ALL_BIOMES: Record<string, Biome> = {
|
|
292
|
+
// Defaults (by rarity name)
|
|
293
|
+
meadow, forest, ocean, cyberpunk, space,
|
|
294
|
+
// Extras
|
|
295
|
+
volcano, arctic, desert, haunted, sakura,
|
|
296
|
+
underwater, candyland, dungeon, cloudkingdom, matrix,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export function getBiome(rarity: string, override?: string): Biome {
|
|
300
|
+
if (override && ALL_BIOMES[override]) return ALL_BIOMES[override];
|
|
301
|
+
return RARITY_BIOMES[rarity] ?? meadow;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function listBiomes(): { name: string; isDefault: boolean }[] {
|
|
305
|
+
return Object.keys(ALL_BIOMES).map(name => ({
|
|
306
|
+
name,
|
|
307
|
+
isDefault: Object.values(RARITY_BIOMES).some(b => b.name === name),
|
|
308
|
+
}));
|
|
309
|
+
}
|