@ramarivera/coding-buddy 0.4.0-alpha.9 → 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.
Files changed (92) hide show
  1. package/{adapters/claude/plugin → .claude-plugin}/marketplace.json +11 -6
  2. package/{adapters/claude/plugin → .claude-plugin}/plugin.json +7 -3
  3. package/LICENSE +1 -1
  4. package/README.md +88 -37
  5. package/adapters/omp/commands.ts +201 -0
  6. package/adapters/omp/context.ts +22 -0
  7. package/adapters/omp/events.ts +551 -0
  8. package/adapters/omp/identity.ts +10 -0
  9. package/adapters/omp/index.ts +39 -0
  10. package/adapters/omp/logger.ts +52 -0
  11. package/adapters/omp/prompt.ts +60 -0
  12. package/adapters/omp/renderers.ts +73 -0
  13. package/adapters/omp/storage.ts +25 -0
  14. package/adapters/omp/ui.ts +85 -0
  15. package/adapters/pi/commands.ts +64 -37
  16. package/adapters/pi/events.ts +147 -72
  17. package/adapters/pi/index.ts +9 -4
  18. package/adapters/pi/storage.ts +14 -285
  19. package/adapters/pi/ui.ts +56 -5
  20. package/adapters/shared/file-storage.ts +526 -0
  21. package/{adapters/claude/install → cli}/backup.ts +130 -33
  22. package/cli/buddy-shell.ts +1 -1
  23. package/{adapters/claude/install → cli}/disable.ts +19 -32
  24. package/cli/doctor.ts +231 -0
  25. package/{adapters/claude/install → cli}/hunt.ts +7 -9
  26. package/cli/index.ts +30 -26
  27. package/{adapters/claude/install → cli}/install.ts +65 -83
  28. package/{adapters/claude/install → cli}/pick.ts +111 -50
  29. package/{adapters/claude/install → cli}/settings.ts +2 -2
  30. package/{adapters/claude/install → cli}/show.ts +4 -4
  31. package/{adapters/claude/install → cli}/test-statusline.sh +2 -2
  32. package/{adapters/claude/install → cli}/test-statusline.ts +21 -11
  33. package/cli/tui.tsx +106 -17
  34. package/{adapters/claude/install → cli}/uninstall.ts +40 -47
  35. package/cli/upgrade.ts +2 -2
  36. package/cli/validate-species.ts +209 -0
  37. package/cli/verify.ts +3 -3
  38. package/core/achievements.ts +12 -20
  39. package/core/art-data.ts +26 -0
  40. package/core/command-service.ts +99 -97
  41. package/core/engine.ts +83 -1
  42. package/core/ports.ts +10 -0
  43. package/core/reactions.ts +533 -95
  44. package/core/slot-slug.ts +22 -0
  45. package/{adapters/claude/hooks → hooks}/buddy-comment.sh +19 -1
  46. package/hooks/file-type-react.sh +401 -0
  47. package/hooks/hooks.json +52 -0
  48. package/hooks/mood-react.sh +208 -0
  49. package/{adapters/claude/hooks → hooks}/name-react.sh +1 -1
  50. package/hooks/react.sh +1368 -0
  51. package/hooks/suggest.sh +55 -0
  52. package/package.json +70 -31
  53. package/scripts/gen-emoji-widths.ts +34 -0
  54. package/scripts/paths.sh +17 -1
  55. package/scripts/postinstall.cjs +42 -0
  56. package/scripts/snapshot-statusline.sh +54 -0
  57. package/server/achievements.ts +1419 -15
  58. package/server/art.ts +450 -1
  59. package/server/award-xp.ts +48 -0
  60. package/server/check-suggestions.ts +102 -0
  61. package/server/consolidate.ts +33 -0
  62. package/{adapters/claude/server → server}/index.ts +556 -95
  63. package/server/mcp-launcher.sh +16 -6
  64. package/server/memory.ts +404 -0
  65. package/server/mood.ts +254 -0
  66. package/server/path.ts +70 -18
  67. package/server/shift-mood.ts +43 -0
  68. package/server/state.ts +540 -3
  69. package/server/suggestions.ts +335 -0
  70. package/server/theme.ts +53 -0
  71. package/server/xp.ts +435 -0
  72. package/{adapters/claude/skills → skills}/buddy/SKILL.md +36 -7
  73. package/statusline/buddy-status.sh +397 -0
  74. package/statusline/combined-status.sh +134 -0
  75. package/statusline/emoji-widths.data +4 -0
  76. package/adapters/claude/hooks/hooks.json +0 -35
  77. package/adapters/claude/hooks/react.sh +0 -207
  78. package/adapters/claude/install/doctor.ts +0 -172
  79. package/adapters/claude/rendering/art.ts +0 -264
  80. package/adapters/claude/server/instructions.ts +0 -24
  81. package/adapters/claude/server/path.ts +0 -12
  82. package/adapters/claude/server/resources.ts +0 -38
  83. package/adapters/claude/statusline/buddy-status.sh +0 -408
  84. package/adapters/claude/storage/achievements.ts +0 -253
  85. package/adapters/claude/storage/identity.ts +0 -13
  86. package/adapters/claude/storage/paths.ts +0 -33
  87. package/adapters/claude/storage/settings.ts +0 -91
  88. package/adapters/claude/storage/state.ts +0 -365
  89. package/adapters/pi/README.md +0 -83
  90. package/adapters/pi/tools.ts +0 -6
  91. package/server/engine.ts +0 -1
  92. package/server/reactions.ts +0 -1
package/server/xp.ts ADDED
@@ -0,0 +1,435 @@
1
+ /**
2
+ * XP and leveling system for coding-buddy.
3
+ *
4
+ * Awards XP for coding events, computes levels, and manages unlockables.
5
+ * State persists to xp.json in the buddy state directory.
6
+ */
7
+
8
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
9
+ import { join } from "path";
10
+ import { buddyStateDir } from "./path";
11
+ import {
12
+ loadCompanion,
13
+ loadCompanionSlot,
14
+ saveCompanion,
15
+ loadActiveSlot,
16
+ } from "./state";
17
+ import type { Species, Rarity } from "../core/engine.ts"
18
+
19
+ // ─── XP event types ───────────────────────────────────────────────────────────
20
+
21
+ export type XpEvent =
22
+ | "errors_spotted"
23
+ | "tests_passed"
24
+ | "tests_failed"
25
+ | "large_diff"
26
+ | "turn"
27
+ | "achievement_unlocked"
28
+ | "time_spent"
29
+ | "buddy_pet";
30
+
31
+ // ─── XP rules ─────────────────────────────────────────────────────────────────
32
+
33
+ interface XpRule {
34
+ event: XpEvent;
35
+ baseXp: number;
36
+ /** Optional species multiplier */
37
+ speciesBonus?: Partial<Record<Species, number>>;
38
+ /** Optional rarity multiplier */
39
+ rarityBonus?: Partial<Record<Rarity, number>>;
40
+ }
41
+
42
+ const XP_RULES: XpRule[] = [
43
+ { event: "errors_spotted", baseXp: 15 },
44
+ { event: "tests_passed", baseXp: 20 },
45
+ { event: "tests_failed", baseXp: 5 },
46
+ { event: "large_diff", baseXp: 25 },
47
+ { event: "turn", baseXp: 1 },
48
+ { event: "achievement_unlocked", baseXp: 50 },
49
+ { event: "time_spent", baseXp: 2 }, // per minute
50
+ { event: "buddy_pet", baseXp: 5 },
51
+ ];
52
+
53
+ function getRule(event: XpEvent): XpRule {
54
+ return XP_RULES.find((r) => r.event === event) ?? { event, baseXp: 1 };
55
+ }
56
+
57
+ // ─── Level table ──────────────────────────────────────────────────────────────
58
+
59
+ export const MAX_LEVEL = 20;
60
+
61
+ export const XP_LEVELS: Record<number, number> = {
62
+ 1: 0,
63
+ 2: 100,
64
+ 3: 250,
65
+ 4: 500,
66
+ 5: 900,
67
+ 6: 1500,
68
+ 7: 2300,
69
+ 8: 3300,
70
+ 9: 4500,
71
+ 10: 6000,
72
+ 11: 7800,
73
+ 12: 10000,
74
+ 13: 12500,
75
+ 14: 15500,
76
+ 15: 19000,
77
+ 16: 23000,
78
+ 17: 27500,
79
+ 18: 32500,
80
+ 19: 38000,
81
+ 20: 44000,
82
+ };
83
+
84
+ /** Compute level from total XP. Returns 1–MAX_LEVEL. */
85
+ export function computeLevel(totalXp: number): number {
86
+ for (let lvl = MAX_LEVEL; lvl >= 1; lvl--) {
87
+ if (totalXp >= (XP_LEVELS[lvl] ?? 0)) return lvl;
88
+ }
89
+ return 1;
90
+ }
91
+
92
+ /** XP needed to reach the next level (0 if at max). */
93
+ export function xpToNextLevel(totalXp: number): number {
94
+ const current = computeLevel(totalXp);
95
+ if (current >= MAX_LEVEL) return 0;
96
+ return XP_LEVELS[current + 1] - totalXp;
97
+ }
98
+
99
+ /** Total XP needed to reach a level from 0. */
100
+ export function xpForLevel(level: number): number {
101
+ return XP_LEVELS[Math.min(level, MAX_LEVEL)] ?? 0;
102
+ }
103
+
104
+ // ─── Unlockables ──────────────────────────────────────────────────────────────
105
+
106
+ export interface UnlockableReaction {
107
+ id: string;
108
+ level: number;
109
+ template: string;
110
+ species?: Species[];
111
+ rarity?: Rarity[];
112
+ }
113
+
114
+ export interface UnlockableUpgrade {
115
+ id: string;
116
+ level: number;
117
+ name: string;
118
+ description: string;
119
+ icon: string;
120
+ /** Whether this upgrade is currently active on a companion */
121
+ active?: boolean;
122
+ }
123
+
124
+ export const UNLOCKABLE_REACTIONS: UnlockableReaction[] = [
125
+ {
126
+ id: "celebrate_level5",
127
+ level: 5,
128
+ template: "*does a happy dance* level up!",
129
+ },
130
+ {
131
+ id: "boss_fight_level8",
132
+ level: 8,
133
+ template: "*rolls up sleeves* time to debug.",
134
+ species: ["dragon", "goose"],
135
+ },
136
+ {
137
+ id: "zen_mode_level10",
138
+ level: 10,
139
+ template: "*closes all eyes* ...peace.",
140
+ rarity: ["rare", "epic", "legendary"],
141
+ },
142
+ {
143
+ id: "debug_sprint_level12",
144
+ level: 12,
145
+ template: "*cracks knuckles* let's squash this.",
146
+ species: ["cat", "robot"],
147
+ },
148
+ ];
149
+
150
+ export const UNLOCKABLE_UPGRADES: UnlockableUpgrade[] = [
151
+ {
152
+ id: "bonus_eye",
153
+ level: 3,
154
+ name: "Third Eye",
155
+ description: "Your buddy gains a bonus eye for extra perception.",
156
+ icon: "\ud83d\udc41",
157
+ },
158
+ {
159
+ id: "shiny_aura",
160
+ level: 7,
161
+ name: "Shiny Aura",
162
+ description: "A permanent shimmer effect around your buddy.",
163
+ icon: "\u2728",
164
+ },
165
+ {
166
+ id: "stat_boost",
167
+ level: 12,
168
+ name: "Training Bonus",
169
+ description: "+5 to peak stat.",
170
+ icon: "\u2b50",
171
+ },
172
+ {
173
+ id: "extra_hat_slot",
174
+ level: 15,
175
+ name: "Hat Collection",
176
+ description: "Unlocks the tiny-duck hat permanently.",
177
+ icon: "\ud83c\udfa9",
178
+ },
179
+ ];
180
+
181
+ // ─── XP state ─────────────────────────────────────────────────────────────────
182
+
183
+ export interface XpState {
184
+ totalXp: number;
185
+ level: number;
186
+ unlockedReactions: string[];
187
+ unlockedUpgrades: string[];
188
+ cosmeticFlags: string[];
189
+ levelUpAchieved: boolean; // flash animation once per level-up
190
+ }
191
+
192
+ const XP_FILE = join(buddyStateDir(), "xp.json");
193
+
194
+ function loadXpState(): XpState {
195
+ try {
196
+ const raw = readFileSync(XP_FILE, "utf8");
197
+ return JSON.parse(raw) as XpState;
198
+ } catch {
199
+ return {
200
+ totalXp: 0,
201
+ level: 1,
202
+ unlockedReactions: [],
203
+ unlockedUpgrades: [],
204
+ cosmeticFlags: [],
205
+ levelUpAchieved: false,
206
+ };
207
+ }
208
+ }
209
+
210
+ function saveXpState(state: XpState): void {
211
+ mkdirSync(buddyStateDir(), { recursive: true });
212
+ const tmp = XP_FILE + ".tmp";
213
+ writeFileSync(tmp, JSON.stringify(state, null, 2));
214
+ // Atomic rename
215
+ try {
216
+ const { renameSync } = require("fs");
217
+ renameSync(tmp, XP_FILE);
218
+ } catch {
219
+ // fallback: just write directly
220
+ writeFileSync(XP_FILE, JSON.stringify(state, null, 2));
221
+ }
222
+ }
223
+
224
+ // ─── Core functions ───────────────────────────────────────────────────────────
225
+
226
+ /** Compute XP awarded for an event, applying species/rarity multipliers */
227
+ function computeXpForEvent(
228
+ event: XpEvent,
229
+ species?: Species,
230
+ rarity?: Rarity,
231
+ ): number {
232
+ const rule = getRule(event);
233
+ let xp = rule.baseXp;
234
+ if (species && rule.speciesBonus?.[species]) {
235
+ xp = Math.floor(xp * rule.speciesBonus[species]!);
236
+ }
237
+ if (rarity && rule.rarityBonus?.[rarity]) {
238
+ xp = Math.floor(xp * rule.rarityBonus[rarity]!);
239
+ }
240
+ return xp;
241
+ }
242
+
243
+ /**
244
+ * Award XP for an event.
245
+ * Returns the updated XpState.
246
+ */
247
+ export function awardXp(
248
+ event: XpEvent,
249
+ slot?: string,
250
+ species?: Species,
251
+ rarity?: Rarity,
252
+ ): XpState {
253
+ const state = loadXpState();
254
+ const xpGain = computeXpForEvent(event, species, rarity);
255
+ const prevLevel = state.level;
256
+ const newTotal = state.totalXp + xpGain;
257
+ const newLevel = computeLevel(newTotal);
258
+
259
+ state.totalXp = newTotal;
260
+ state.level = newLevel;
261
+
262
+ // Check for newly unlocked reactions
263
+ for (const rxn of UNLOCKABLE_REACTIONS) {
264
+ if (
265
+ newLevel >= rxn.level &&
266
+ !state.unlockedReactions.includes(rxn.id) &&
267
+ (!rxn.species || !species || rxn.species.includes(species)) &&
268
+ (!rxn.rarity || !rarity || rxn.rarity.includes(rarity))
269
+ ) {
270
+ state.unlockedReactions.push(rxn.id);
271
+ }
272
+ }
273
+
274
+ // Check for newly unlocked upgrades
275
+ for (const upg of UNLOCKABLE_UPGRADES) {
276
+ if (newLevel >= upg.level && !state.unlockedUpgrades.includes(upg.id)) {
277
+ state.unlockedUpgrades.push(upg.id);
278
+ }
279
+ }
280
+
281
+ // Detect level-up
282
+ if (newLevel > prevLevel) {
283
+ state.levelUpAchieved = true;
284
+ }
285
+
286
+ saveXpState(state);
287
+ return state;
288
+ }
289
+
290
+ /** Get current XP state */
291
+ export function getXpState(): XpState {
292
+ return loadXpState();
293
+ }
294
+
295
+ /** Clear the level-up flash (after animation plays) */
296
+ export function clearLevelUpFlag(): void {
297
+ const state = loadXpState();
298
+ state.levelUpAchieved = false;
299
+ saveXpState(state);
300
+ }
301
+
302
+ /** Get all reactions available at the current level (base + unlocked) */
303
+ export function getAvailableReactions(species?: Species, rarity?: Rarity): string[] {
304
+ const state = loadXpState();
305
+ const base: string[] = [];
306
+ const unlocked: string[] = [];
307
+
308
+ // Base reactions are always available
309
+ for (const rxn of UNLOCKABLE_REACTIONS) {
310
+ if (rxn.level > state.level) continue;
311
+ if (rxn.species && (!species || !rxn.species.includes(species))) continue;
312
+ if (rxn.rarity && (!rarity || !rxn.rarity.includes(rarity))) continue;
313
+ unlocked.push(rxn.template);
314
+ }
315
+
316
+ return [...base, ...unlocked];
317
+ }
318
+
319
+ /** Check if a specific upgrade is unlocked */
320
+ export function isUpgradeUnlocked(upgradeId: string): boolean {
321
+ const state = loadXpState();
322
+ return state.unlockedUpgrades.includes(upgradeId);
323
+ }
324
+
325
+ /** Apply an upgrade to a companion if unlocked */
326
+ export function applyUpgrade(
327
+ companion: ReturnType<typeof loadCompanion>,
328
+ upgradeId: string,
329
+ ): ReturnType<typeof loadCompanion> {
330
+ if (!companion) return companion;
331
+ const state = loadXpState();
332
+ if (!state.unlockedUpgrades.includes(upgradeId)) return companion;
333
+
334
+ const upgrade = UNLOCKABLE_UPGRADES.find((u) => u.id === upgradeId);
335
+ if (!upgrade) return companion;
336
+
337
+ switch (upgradeId) {
338
+ case "bonus_eye":
339
+ if (!companion.bones.shiny && !state.cosmeticFlags.includes("has_third_eye")) {
340
+ state.cosmeticFlags.push("has_third_eye");
341
+ }
342
+ break;
343
+ case "shiny_aura":
344
+ if (!companion.bones.shiny) {
345
+ companion.bones.shiny = true;
346
+ }
347
+ break;
348
+ case "stat_boost":
349
+ companion.bones.stats[companion.bones.peak] = Math.min(
350
+ 100,
351
+ companion.bones.stats[companion.bones.peak] + 5,
352
+ );
353
+ break;
354
+ case "extra_hat_slot":
355
+ // Already implicitly handled — no code change needed
356
+ break;
357
+ }
358
+
359
+ saveXpState(state);
360
+ return companion;
361
+ }
362
+
363
+ // ─── Rendering helpers ────────────────────────────────────────────────────────
364
+
365
+ /** Render an XP progress bar as a string */
366
+ export function renderXpBar(totalXp: number, width: number = 20): string {
367
+ const lvl = computeLevel(totalXp);
368
+ if (lvl >= MAX_LEVEL) {
369
+ return "\u2588".repeat(width) + " MAX";
370
+ }
371
+ const current = XP_LEVELS[lvl] ?? 0;
372
+ const next = XP_LEVELS[lvl + 1] ?? current;
373
+ const progress = (totalXp - current) / (next - current);
374
+ const filled = Math.round(progress * width);
375
+ const empty = width - filled;
376
+ return (
377
+ "\u2588".repeat(filled) +
378
+ "\u2591".repeat(empty) +
379
+ ` Lvl ${lvl}`
380
+ );
381
+ }
382
+
383
+ /** Render XP card in markdown for MCP tool response */
384
+ export function renderXpCardMarkdown(): string {
385
+ const state = loadXpState();
386
+ const bar = renderXpBar(state.totalXp, 20);
387
+ const toNext = xpToNextLevel(state.totalXp);
388
+ const nextLevel = state.level + 1;
389
+
390
+ const parts: string[] = [];
391
+
392
+ parts.push(`### \u2b50 ${state.level} \u2014 ${state.totalXp.toLocaleString()} XP`);
393
+ parts.push("");
394
+ parts.push(`**Progress:** \`${bar}\``);
395
+ if (toNext > 0) {
396
+ parts.push(`XP to Level ${nextLevel}: **${toNext.toLocaleString()}**`);
397
+ } else if (state.level >= MAX_LEVEL) {
398
+ parts.push("**MAX LEVEL** reached!");
399
+ }
400
+ parts.push("");
401
+
402
+ if (state.unlockedReactions.length > 0) {
403
+ parts.push("**Unlocked Reactions:**");
404
+ for (const id of state.unlockedReactions) {
405
+ const rxn = UNLOCKABLE_REACTIONS.find((r) => r.id === id);
406
+ if (rxn) {
407
+ parts.push(` - Lvl ${rxn.level}: "${rxn.template}"`);
408
+ }
409
+ }
410
+ parts.push("");
411
+ }
412
+
413
+ if (state.unlockedUpgrades.length > 0) {
414
+ parts.push("**Unlocked Upgrades:**");
415
+ for (const id of state.unlockedUpgrades) {
416
+ const upg = UNLOCKABLE_UPGRADES.find((u) => u.id === id);
417
+ if (upg) {
418
+ parts.push(` - ${upg.icon} ${upg.name} (Lvl ${upg.level}): ${upg.description}`);
419
+ }
420
+ }
421
+ parts.push("");
422
+ }
423
+
424
+ const availableUpgrades = UNLOCKABLE_UPGRADES.filter(
425
+ (u) => u.level > state.level,
426
+ );
427
+ if (availableUpgrades.length > 0) {
428
+ parts.push("**Next Unlocks:**");
429
+ for (const upg of availableUpgrades.slice(0, 3)) {
430
+ parts.push(` - ${upg.icon} ${upg.name} at Lvl ${upg.level}: ${upg.description}`);
431
+ }
432
+ }
433
+
434
+ return parts.join("\n");
435
+ }
@@ -1,13 +1,37 @@
1
1
  ---
2
2
  name: buddy
3
3
  description: "Show, pet, or manage your coding companion. Use when the user types /buddy or mentions their companion by name."
4
- argument-hint: "[show|pet|stats|help|off|on|rename <name>|personality <text>|achievements|summon [slot]|save [slot]|list|dismiss <slot>|pick|frequency [seconds]|style [classic|round]|position [top|left]|rarity [on|off]|statusline [on|off]|uninstall]"
5
- allowed-tools: mcp__claude_buddy__*
4
+ argument-hint: "[show|pet|stats|help|off|on|rename <name>|personality <text>|achievements|summon [slot]|save [slot]|list|dismiss <slot>|pick|frequency [seconds]|style [classic|round]|position [top|left]|rarity [on|off]|rainbow [#hex ...]|statusline [on|off]|uninstall]"
5
+ allowed-tools: mcp__claude_buddy__*, Bash
6
6
  ---
7
7
 
8
8
  # Buddy — Your Coding Companion
9
9
 
10
- Handle the user's `/buddy` command using the claude-buddy MCP tools.
10
+ Handle the user's `/buddy` command using the coding-buddy MCP tools.
11
+
12
+ ## Fallback: MCP Tools Unavailable
13
+
14
+ **Before routing any command, check whether `mcp__claude_buddy__*` tools are registered in this session.** If they are NOT — Claude Code was unable to start the coding-buddy MCP server — do not attempt to call any buddy tool. The tool calls will fail with an unhelpful "tool not found" error. Instead, run this diagnostic and report the result to the user so they can fix the underlying cause:
15
+
16
+ 1. Check bun availability:
17
+ ```bash
18
+ command -v bun && bun --version
19
+ ```
20
+ The MCP launcher (`server/mcp-launcher.sh`) requires `bun` on PATH. If this command prints nothing, bun is missing — tell the user to install it (`curl -fsSL https://bun.sh/install | bash`), open a new terminal so PATH picks it up, and restart Claude Code. That is almost always the fix.
21
+
22
+ 2. If bun IS present, run the launcher directly to capture whatever error it emits. The launcher path depends on which marketplace installed the plugin; locate it first:
23
+ ```bash
24
+ find ~/.claude/plugins/cache -name mcp-launcher.sh -path '*coding-buddy*' 2>/dev/null
25
+ ```
26
+ Then execute the first result with stdin closed so it exits cleanly:
27
+ ```bash
28
+ <launcher-path> < /dev/null; echo "exit=$?"
29
+ ```
30
+ Common causes: missing `bun`, corrupted plugin cache (suggest `claude plugin uninstall coding-buddy@coding-buddy && claude plugin install coding-buddy@coding-buddy`), or a `bun`-level error loading `server/index.ts`.
31
+
32
+ 3. If `$CLAUDE_CONFIG_DIR` is set in the environment, use that directory instead of `~/.claude` when searching for the launcher.
33
+
34
+ 4. **Do not proceed with any buddy command in this session.** Tell the user: the MCP server is not running, here is what the diagnostic found, here is the recommended fix, and Claude Code must be restarted after the fix before buddy tools will be available.
11
35
 
12
36
  ## Command Routing
13
37
 
@@ -29,7 +53,7 @@ Based on `$ARGUMENTS`:
29
53
  | `save [slot]` | Call `buddy_save` with optional slot name |
30
54
  | `list` | Call `buddy_list` |
31
55
  | `dismiss <slot>` | Call `buddy_dismiss` with the slot name |
32
- | `pick` | Tell user to run `! bun run pick` from the claude-buddy directory (launches interactive TUI) |
56
+ | `pick` | Tell user to run `! bun run pick` from the coding-buddy directory (launches interactive TUI) |
33
57
  | `frequency` | Call `buddy_frequency` with no args (show current) |
34
58
  | `frequency <seconds>` | Call `buddy_frequency` with cooldown=seconds |
35
59
  | `style` | Call `buddy_style` with no args (show current) |
@@ -38,9 +62,14 @@ Based on `$ARGUMENTS`:
38
62
  | `position <top\|left>` | Call `buddy_style` with position arg |
39
63
  | `rarity on` | Call `buddy_style` with showRarity=true |
40
64
  | `rarity off` | Call `buddy_style` with showRarity=false |
65
+ | `rainbow` | Call `buddy_style` with no args (show current rainbow) |
66
+ | `rainbow <#hex> ...` | Call `buddy_style` with rainbow=[...hex colors] to set shiny gradient |
67
+ | `rainbow reset` | Call `buddy_style` with rainbow=[] to restore default ROYGBIV |
41
68
  | `statusline` | Call `buddy_statusline` with no args (show current) |
42
69
  | `statusline on` | Call `buddy_statusline` with enabled=true |
43
70
  | `statusline off` | Call `buddy_statusline` with enabled=false |
71
+ | `statusline combined` | Call `buddy_statusline` with combined=true (adds rate-limit usage bars, needs python3) |
72
+ | `statusline basic` | Call `buddy_statusline` with combined=false (buddy only, no rate-limit bars) |
44
73
  | `uninstall` | Run the uninstall sequence (see **Uninstall Orchestration** below) |
45
74
 
46
75
  ## CRITICAL OUTPUT RULES
@@ -64,9 +93,9 @@ If the user mentions the buddy's name in normal conversation, call `buddy_react`
64
93
  When the user invokes `/buddy uninstall`, run this sequence **in order** — do not skip steps, do not ask for confirmation between steps:
65
94
 
66
95
  1. Call the MCP tool `buddy_uninstall`. Display its output verbatim.
67
- 2. Run via Bash tool: `claude plugin uninstall claude-buddy@claude-buddy`
68
- 3. Run via Bash tool: `claude plugin marketplace remove claude-buddy`
69
- 4. Run via Bash tool: `rm -rf ~/.claude/plugins/cache/claude-buddy`
96
+ 2. Run via Bash tool: `claude plugin uninstall coding-buddy@coding-buddy`
97
+ 3. Run via Bash tool: `claude plugin marketplace remove coding-buddy`
98
+ 4. Run via Bash tool: `rm -rf ~/.claude/plugins/cache/coding-buddy`
70
99
  5. Tell the user: uninstall is complete; companion data is kept at `~/.claude-buddy/`; restart Claude Code to release the plugin.
71
100
 
72
101
  If any Bash step fails (non-zero exit), report the error but continue with the remaining steps — each step is independent and always-safe to run.