@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
@@ -1,18 +1,18 @@
1
1
  {
2
- "name": "claude-buddy",
2
+ "name": "coding-buddy",
3
3
  "owner": {
4
4
  "name": "1270011"
5
5
  },
6
6
  "metadata": {
7
- "description": "Permanent coding companion for Claude Code (requires bun: https://bun.sh/install)",
8
- "version": "0.4.0-alpha.9"
7
+ "description": "Persistent coding companion for Claude Code, Pi, and Oh My Pi (requires bun: https://bun.sh/install)",
8
+ "version": "0.6.0"
9
9
  },
10
10
  "plugins": [
11
11
  {
12
- "name": "claude-buddy",
12
+ "name": "coding-buddy",
13
13
  "source": "./",
14
- "description": "Permanent coding companion for Claude Code \u2014 survives any update. MCP-based terminal pet with ASCII art, stats, reactions, and personality. Requires bun runtime (https://bun.sh/install).",
15
- "version": "0.4.0-alpha.9",
14
+ "description": "Persistent coding companion for Claude Code, Pi, and Oh My Pi. MCP-based terminal pet with ASCII art, stats, reactions, and personality. Requires bun runtime (https://bun.sh/install).",
15
+ "version": "0.6.0",
16
16
  "author": {
17
17
  "name": "1270011"
18
18
  },
@@ -20,7 +20,11 @@
20
20
  "repository": "https://github.com/1270011/claude-buddy",
21
21
  "license": "MIT",
22
22
  "keywords": [
23
+ "coding-buddy",
23
24
  "claude-code",
25
+ "pi",
26
+ "oh-my-pi",
27
+ "omp",
24
28
  "buddy",
25
29
  "companion",
26
30
  "mcp",
@@ -29,6 +33,7 @@
29
33
  ],
30
34
  "category": "companion",
31
35
  "tags": [
36
+ "coding-buddy",
32
37
  "buddy",
33
38
  "companion",
34
39
  "mcp",
@@ -1,7 +1,7 @@
1
1
  {
2
- "name": "claude-buddy",
3
- "version": "0.4.0-alpha.9",
4
- "description": "Permanent coding companion for Claude Code \u2014 survives any update. MCP-based terminal pet with ASCII art, stats, reactions, and personality.",
2
+ "name": "coding-buddy",
3
+ "version": "0.6.0",
4
+ "description": "Persistent coding companion for Claude Code, Pi, and Oh My Pi. MCP-based terminal pet with ASCII art, stats, reactions, and personality.",
5
5
  "author": {
6
6
  "name": "1270011"
7
7
  },
@@ -9,7 +9,11 @@
9
9
  "repository": "https://github.com/1270011/claude-buddy",
10
10
  "homepage": "https://github.com/1270011/claude-buddy#readme",
11
11
  "keywords": [
12
+ "coding-buddy",
12
13
  "claude-code",
14
+ "pi",
15
+ "oh-my-pi",
16
+ "omp",
13
17
  "buddy",
14
18
  "companion",
15
19
  "mcp",
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 claude-buddy contributors
3
+ Copyright (c) 2026 coding-buddy contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -4,11 +4,10 @@
4
4
  <!-- LOGO / HERO -->
5
5
  <!-- Later replace with: docs/logo.png -->
6
6
  <!-- ============================================================ -->
7
- <img src="https://placehold.co/120x120/6366f1/ffffff?text=%F0%9F%A6%89" alt="claude-buddy logo" width="120" />
7
+ <img src="https://placehold.co/120x120/6366f1/ffffff?text=%F0%9F%A6%89" alt="coding-buddy logo" width="120" />
8
8
 
9
- # Claude Code Buddy
10
-
11
- ### Your Claude Code buddy — permanently. Survives every update.
9
+ # Coding Buddy
10
+ ### Your persistent coding companion — for Claude Code, Pi, and Oh My Pi. Survives every update.
12
11
 
13
12
  <!-- ============================================================ -->
14
13
  <!-- BADGES -->
@@ -27,7 +26,7 @@
27
26
 
28
27
  <br>
29
28
 
30
- <img src="docs/hero.gif" alt="claude-buddy in action" width="800" />
29
+ <img src="docs/hero.gif" alt="coding-buddy in action" width="800" />
31
30
 
32
31
  <br><br>
33
32
 
@@ -48,7 +47,7 @@
48
47
  </td>
49
48
  <td align="center" width="25%">
50
49
  <h3>🎨</h3>
51
- <b>18 Species</b><br>
50
+ <b>19 Species</b><br>
52
51
  <sub>From ducks to dragons — each with animated ASCII art and rarity colors.</sub>
53
52
  </td>
54
53
  <td align="center" width="25%">
@@ -72,34 +71,77 @@
72
71
  <!-- QUICK START -->
73
72
  <!-- ============================================================ -->
74
73
 
74
+ <!-- ============================================================ -->
75
+ <!-- CONTINUITY / MIGRATION -->
76
+ <!-- ============================================================ -->
77
+
78
+ ## 📦 Continuity / Migration
79
+
80
+ The `claude-buddy` package ended at **v0.5.2**.
81
+ **`@ramarivera/coding-buddy` begins at v0.6.0** and is its continuation.
82
+
83
+ - Your existing buddy identity, menagerie, stats, and unlocks remain fully compatible.
84
+ - Legacy state and config identifiers (for example `~/.claude-buddy/`, `mcpServers["claude-buddy"]`) are intentionally preserved so current installs keep working without reconfiguration.
85
+
86
+ <!-- ============================================================ -->
75
87
  ## 📋 Requirements
76
88
 
77
- - **[bun](https://bun.sh/install)** on `PATH` — claude-buddy's MCP server runs on bun. Install once: `curl -fsSL https://bun.sh/install | bash`
78
- - **Claude Code v2.1.80+**
89
+ - **[bun](https://bun.sh/install)** on `PATH` — the MCP server and extension runtime require Bun. Install once: `curl -fsSL https://bun.sh/install | bash`
79
90
  - **Linux or macOS** (Windows is experimental)
80
91
 
92
+ | Host | Minimum version | Identity source |
93
+ |---|---|---|
94
+ | **Claude Code** | v2.1.80+ | `accountUuid` from `~/.claude.json` |
95
+ | **Pi** | `mariozechner/pi-coding-agent` workspace | Stable random UUID persisted in `~/.pi/agent/buddy/identity.json` |
96
+ | **Oh My Pi (OMP)** | OMP harness with `omp.extensions` support | Stable random UUID persisted in `~/.omp/agent/buddy/identity.json` |
97
+
81
98
  ## 🚀 Quick Start
82
99
 
100
+ ### Claude Code
101
+
102
+ ```bash
103
+ npx -y @ramarivera/coding-buddy
104
+ ```
105
+
106
+ This installs the MCP server, skill, hooks, and status line. Restart Claude Code and type `/buddy`.
107
+
108
+ ### Pi
109
+
110
+ ```bash
111
+ pi install npm:@ramarivera/coding-buddy
112
+ ```
113
+
114
+ Pi registers the package's native extension and stores companion state under `~/.pi/agent/buddy`.
115
+
116
+ ### Oh My Pi
117
+
118
+ ```bash
119
+ omp plugin install @ramarivera/coding-buddy
120
+ ```
121
+
122
+ OMP registers the package's native extension and stores companion state under `~/.omp/agent/buddy`.
123
+
124
+ ### From source
125
+
83
126
  ```bash
84
- git clone https://github.com/1270011/claude-buddy
85
- cd claude-buddy
127
+ git clone https://github.com/1270011/claude-buddy coding-buddy
128
+ cd coding-buddy
86
129
  bun install
87
130
  bun run install-buddy
88
131
  ```
89
132
 
90
- Then restart Claude Code and type `/buddy`. That's it.
91
-
92
- <sub>💡 Want a global `claude-buddy` command? → `bun link`</sub>
133
+ <sub>💡 Want a global `coding-buddy` command? → `bun link`</sub>
93
134
  <br>
94
- <sub>💡 Need help? → `bun run help` or `claude-buddy help` (if linked) in terminal · `/buddy help` in Claude Code</sub>
135
+ <sub>💡 Need help? → `bun run help`, `npx -y @ramarivera/coding-buddy help`, or `coding-buddy help` (if linked) · `/buddy help` in Claude Code</sub>
136
+
95
137
 
96
138
  ### Multiple Claude profiles?
97
139
 
98
140
  If you run Claude Code with `CLAUDE_CONFIG_DIR` set (e.g. separate work and personal accounts), pass the same env var to install so buddy lands in the active profile and gets its own menagerie:
99
141
 
100
142
  ```bash
101
- CLAUDE_CONFIG_DIR=~/.claude-personal bun run install-buddy
102
- CLAUDE_CONFIG_DIR=~/.claude-personal bun run uninstall
143
+ CLAUDE_CONFIG_DIR=~/.claude-personal npx -y @ramarivera/coding-buddy install
144
+ CLAUDE_CONFIG_DIR=~/.claude-personal npx -y @ramarivera/coding-buddy uninstall
103
145
  ```
104
146
 
105
147
  The installer prints `Target profile: <path>` at the top so you can see at a glance which profile you're targeting. Each profile gets its own MCP entry, skill, hooks, status line, and `$CLAUDE_CONFIG_DIR/buddy-state/` menagerie — installs in one profile don't touch another. With `CLAUDE_CONFIG_DIR` unset, behaviour is identical to single-profile (`~/.claude/`, `~/.claude-buddy/`).
@@ -113,11 +155,11 @@ The installer prints `Target profile: <path>` at the top so you can see at a gla
113
155
  <!-- ============================================================ -->
114
156
 
115
157
  <details>
116
- <summary><b>🐙 &nbsp; Meet the 18 Species</b></summary>
158
+ <summary><b>🐙 &nbsp; Meet the 19 Species</b></summary>
117
159
 
118
160
  <br>
119
161
 
120
- Every buddy is uniquely generated from your Claude Code account — same species, same stats, same personality every time. 18 species, each with 3 idle animation frames + a blink.
162
+ Every buddy is uniquely generated — same species, same stats, same personality every time. On **Claude Code** the seed is derived from your `accountUuid` in `~/.claude.json`; on **Pi** and **Oh My Pi** a stable random UUID is generated once and persisted in `~/.pi/agent/buddy/identity.json` or `~/.omp/agent/buddy/identity.json` (deleting the file and letting it regenerate will change your buddy). 19 species, each with 3 idle animation frames + a blink.
121
163
 
122
164
  <!-- Later replace with: docs/species-grid.png -->
123
165
  <p align="center">
@@ -171,7 +213,7 @@ High SNARK buddies are sarcastic. High WISDOM ones are insightful. High CHAOS on
171
213
 
172
214
  <br>
173
215
 
174
- Five integration points, zero binary dependencies. When Claude Code updates, your buddy stays.
216
+ Five integration points, zero binary dependencies. When Claude Code, Pi, or Oh My Pi updates, your buddy stays.
175
217
 
176
218
  ```
177
219
  ┌────────────── Claude Code (any version) ──────────────┐
@@ -181,7 +223,7 @@ Five integration points, zero binary dependencies. When Claude Code updates, you
181
223
  └───────────────────────┬────────────────────────────────┘
182
224
  │ stable extension points
183
225
  ┌──────────┴──────────┐
184
- │ claude-buddy │
226
+ │ coding-buddy │
185
227
  │ │
186
228
  │ wyhash + mulberry32│
187
229
  │ 18 species, 3 anim │
@@ -203,18 +245,19 @@ Five integration points, zero binary dependencies. When Claude Code updates, you
203
245
  |---|:---:|:---:|:---:|---|
204
246
  | Binary patching | ❌ | ❌ | ❌ | Breaks on update |
205
247
  | Pin old version | ❌ | ✅ | ✅ | No security fixes |
206
- | **claude-buddy** | **✅** | **✅** | **✅** | **None** |
248
+ | **coding-buddy** | **✅** | **✅** | **✅** | **None** |
207
249
 
208
250
  MCP is an industry-standard protocol. Skills are Markdown files. Hooks and status line are shell scripts. Nothing depends on Claude Code's binary internals.
209
251
 
210
252
  ### Repository Layout
211
253
 
212
254
  ```
213
- claude-buddy/
214
- ├── core/ # shared buddy domain logic — engine, reactions, achievements, models
215
- ├── adapters/claude/ # Claude adapter — MCP server, storage, skills, hooks, UI, installer
216
- ├── adapters/pi/ # pi adapter — native pi extension (planned)
217
- └── cli/ # host-agnostic CLI entrypoints/utilities
255
+ coding-buddy/
256
+ ├── server/ # MCP server — tools, engine, art, reactions, state
257
+ ├── skills/buddy/ # /buddy slash command
258
+ ├── hooks/ # PostToolUse + Stop hooks (error & comment detection)
259
+ ├── statusline/ # Animated right-aligned buddy display
260
+ └── cli/ # install, show, hunt, verify, doctor, backup, uninstall
218
261
  ```
219
262
 
220
263
  </details>
@@ -240,18 +283,26 @@ claude-buddy/
240
283
  | `/buddy save [slot]` | Save current buddy to a named slot |
241
284
  | `/buddy list` | List all saved buddies |
242
285
  | `/buddy dismiss <slot>` | Remove a saved buddy slot |
243
- | `/buddy pick` | Launch interactive TUI picker (`! bun run pick`) |
286
+ | `/buddy pick` | Launch interactive TUI picker (`! npx -y @ramarivera/coding-buddy pick`) |
244
287
  | `/buddy frequency [seconds]` | Show or set comment cooldown |
245
288
  | `/buddy style [classic\|round]` | Bubble border style (tmux only) |
246
289
  | `/buddy position [top\|left]` | Bubble position (tmux only) |
247
290
  | `/buddy rarity [on\|off]` | Show or hide stars + rarity line (tmux only) |
291
+ | `/buddy width [10-60]` | Set bubble text width in chars (tmux only) |
292
+ | `/buddy margin [0-20]` | Set right-side margin (tmux only) |
293
+ | `/buddy statusline [on\|off]` | Enable or disable buddy in the status line |
294
+ | `/buddy statusline combined` | Show rate-limit usage bars alongside buddy (needs python3) |
295
+ | `/buddy statusline basic` | Switch back to buddy-only status line |
248
296
  | `/buddy help` | Show all buddy commands |
249
297
 
250
298
  ### CLI
299
+ You can run any command with `npx -y @ramarivera/coding-buddy <command>` from anywhere, or `bun run <script>` from a local clone.
300
+
251
301
 
252
302
  | Command | Description |
253
303
  |---|---|
254
304
  | `bun run install-buddy` | Automated setup |
305
+ | `bun run upgrade` | Pull latest version and reinstall integrations |
255
306
  | `bun run show` | Show buddy in terminal |
256
307
  | `bun run pick` | Interactive TUI to find and save your dream buddy |
257
308
  | `bun run hunt` | Legacy search (use `pick` instead) |
@@ -262,16 +313,16 @@ claude-buddy/
262
313
  | `bun run disable` | Temporarily deactivate buddy |
263
314
  | `bun run enable` | Re-enable buddy |
264
315
  | `bun run help` | Full CLI reference |
265
- | `bun run cli/uninstall.ts` | Clean removal |
316
+ | `bun run uninstall` | Clean removal |
266
317
 
267
- <sub>💡 Want a global `claude-buddy` command? → `cd claude-buddy && bun link`</sub>
318
+ <sub>💡 Want a global `coding-buddy` command? → `cd coding-buddy && bun link`</sub>
268
319
 
269
320
  ### 🎯 Buddy Pick
270
321
 
271
322
  Want a specific species, rarity, or stat distribution?
272
323
 
273
324
  ```bash
274
- bun run pick
325
+ npx -y @ramarivera/coding-buddy pick
275
326
  ```
276
327
 
277
328
  Interactive TUI with saved buddies, criteria search, vim keys, and two-pane preview. Uses the exact same `wyhash + mulberry32` algorithm as Claude Code.
@@ -285,7 +336,7 @@ Interactive TUI with saved buddies, criteria search, vim keys, and two-pane prev
285
336
 
286
337
  <br>
287
338
 
288
- claude-buddy ships with built-in diagnostics for debugging across terminals and platforms.
339
+ coding-buddy ships with built-in diagnostics for debugging across terminals and platforms.
289
340
 
290
341
  ### `bun run doctor`
291
342
  Complete diagnostic — environment, terminal info, state, settings, padding test, live status line output. **Always run this first when filing a bug report.**
@@ -300,7 +351,7 @@ bun run test-statusline restore # restore buddy
300
351
  ```
301
352
 
302
353
  ### `bun run backup`
303
- Snapshot all claude-buddy state to a timestamped backup. Use before experiments.
354
+ Snapshot all coding-buddy state to a timestamped backup. Use before experiments.
304
355
 
305
356
  ```bash
306
357
  bun run backup # create snapshot
@@ -327,7 +378,7 @@ bun run backup restore <ts> # restore specific
327
378
 
328
379
  ### Buddy comments not showing
329
380
 
330
- The buddy comment mechanism uses an MCP server `instructions` field that Claude only reads at **session start**. If you installed claude-buddy in an existing session, restart Claude Code.
381
+ The buddy comment mechanism uses an MCP server `instructions` field that Claude only reads at **session start**. If you installed coding-buddy in an existing session, restart Claude Code.
331
382
 
332
383
  ```bash
333
384
  jq '.mcpServers["claude-buddy"]' ~/.claude.json
@@ -346,7 +397,7 @@ To help us fix it:
346
397
 
347
398
  ```bash
348
399
  bun run backup restore # restore latest backup
349
- bun run cli/uninstall.ts # full clean removal
400
+ bun run uninstall # full clean removal
350
401
  ```
351
402
 
352
403
  </details>
@@ -364,7 +415,7 @@ bun run cli/uninstall.ts # full clean removal
364
415
  | **Claude Code** v2.1.80+ | Any version with MCP support |
365
416
  | **jq** | `apt install jq` / `brew install jq` / [`windows: download and add 'jq.exe' from jqlang/jq to path`](https://github.com/jqlang/jq/releases/latest)|
366
417
 
367
- > **Will I get the same buddy I had?** Yes. claude-buddy uses the exact same algorithm as the original (`wyhash + mulberry32`, same salt, same identity resolution). If your `~/.claude.json` still has your `accountUuid`, you'll get the identical species, rarity, stats, and cosmetics.
418
+ > **Will I get the same buddy I had?** Yes. coding-buddy uses the exact same algorithm as the original (`wyhash + mulberry32`, same salt, same identity resolution). For Claude Code, if your `~/.claude.json` still has your `accountUuid`, you'll get the identical species, rarity, stats, and cosmetics. For Pi/OMP, keep `~/.pi/agent/buddy/identity.json` or `~/.omp/agent/buddy/identity.json`; the same stable UUID produces the same buddy. Resetting (deleting) that identity file generates a new random UUID and changes your buddy.
368
419
 
369
420
  </details>
370
421
 
@@ -381,8 +432,8 @@ bun run cli/uninstall.ts # full clean removal
381
432
  - [ ] **Mood system** — shifts based on code quality, tests, time of day
382
433
  - [x] **Achievement badges** — "1000 lines reviewed", "week streak", etc. 💜[ndcorder](https://github.com/ndcorder)💜
383
434
  - [ ] **Light theme colors** — auto-detect and match light theme RGB
384
- - [ ] **New species + community art** — submit your own designs
385
- - [ ] **`npx claude-buddy`** — one-command install without cloning
435
+ - [x] **New species + community art** — wyvern added 💜[@jpmalone0](https://github.com/jpmalone0)💜 (community contributions welcome)
436
+ - [ ] **`npx -y @ramarivera/coding-buddy <command>`** — one-command usage without cloning
386
437
 
387
438
  <br>
388
439
 
@@ -0,0 +1,201 @@
1
+ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
2
+ import { BuddyCommandService, mergeAchievements } from "../../core/command-service.ts";
3
+ import { renderBuddyStats } from "./renderers.ts";
4
+ import { OmpBuddyStorage, slugifySlot } from "./storage.ts";
5
+ import { OmpBuddyUI } from "./ui.ts";
6
+ import type { OmpBuddyUiContext } from "./context.ts";
7
+
8
+ interface RegisterBuddyCommandsDeps {
9
+ service: BuddyCommandService;
10
+ storage: OmpBuddyStorage;
11
+ ui: OmpBuddyUI;
12
+ }
13
+
14
+ export function registerBuddyCommands(pi: ExtensionAPI, deps: RegisterBuddyCommandsDeps): void {
15
+ pi.registerCommand("buddy", {
16
+ description: "Manage your coding companion",
17
+ handler: async (args: string, ctx: OmpBuddyUiContext) => {
18
+ const input = args.trim();
19
+ const [command, ...rest] = input ? input.split(/\s+/) : [];
20
+ const remainder = rest.join(" ").trim();
21
+
22
+ try {
23
+ switch (command) {
24
+ case undefined:
25
+ case "show": {
26
+ const ensured = deps.service.ensureCompanion();
27
+ const commandAchievements = deps.service.incrementCommandsRun();
28
+ const achievements = mergeAchievements(ensured.achievements, commandAchievements);
29
+ const reaction = deps.storage.loadLatest();
30
+ deps.ui.refresh(ctx, ensured.companion, reaction, achievements);
31
+ if (ensured.created) {
32
+ ctx.ui.notify(`Meet ${ensured.companion.name}!`, "info");
33
+ } else {
34
+ ctx.ui.notify(deps.service.formatCompanionSummary(ensured.companion), "info");
35
+ }
36
+ deps.ui.notifyAchievements(ctx, achievements);
37
+ return;
38
+ }
39
+
40
+ case "pet": {
41
+ const commandAchievements = deps.service.incrementCommandsRun();
42
+ const result = deps.service.petBuddy();
43
+ const achievements = mergeAchievements(commandAchievements, result.achievements);
44
+ deps.ui.refresh(ctx, result.companion, result.state, achievements);
45
+ ctx.ui.notify(`${result.companion.name} seems pleased.`, "info");
46
+ deps.ui.notifyAchievements(ctx, achievements);
47
+ return;
48
+ }
49
+
50
+ case "stats": {
51
+ const ensured = deps.service.ensureCompanion();
52
+ const commandAchievements = deps.service.incrementCommandsRun();
53
+ const achievements = mergeAchievements(ensured.achievements, commandAchievements);
54
+ ctx.ui.setWidget("buddy", renderBuddyStats(ensured.companion));
55
+ ctx.ui.notify(`${ensured.companion.name}'s stats`, "info");
56
+ deps.ui.notifyAchievements(ctx, achievements);
57
+ return;
58
+ }
59
+
60
+ case "rename": {
61
+ if (!remainder) throw new Error("Usage: /buddy rename <name>");
62
+ const commandAchievements = deps.service.incrementCommandsRun();
63
+ const companion = deps.service.renameBuddy(remainder);
64
+ const ensured = deps.service.ensureCompanion();
65
+ const achievements = mergeAchievements(commandAchievements, ensured.achievements);
66
+ deps.ui.refresh(ctx, companion, deps.storage.loadLatest(), achievements);
67
+ ctx.ui.notify(`Buddy renamed to ${companion.name}.`, "info");
68
+ deps.ui.notifyAchievements(ctx, achievements);
69
+ return;
70
+ }
71
+
72
+ case "personality": {
73
+ if (!remainder) throw new Error("Usage: /buddy personality <text>");
74
+ const commandAchievements = deps.service.incrementCommandsRun();
75
+ const companion = deps.service.setPersonality(remainder);
76
+ const ensured = deps.service.ensureCompanion();
77
+ const achievements = mergeAchievements(commandAchievements, ensured.achievements);
78
+ deps.ui.refresh(ctx, companion, deps.storage.loadLatest(), achievements);
79
+ ctx.ui.notify(`${companion.name}'s personality was updated.`, "info");
80
+ deps.ui.notifyAchievements(ctx, achievements);
81
+ return;
82
+ }
83
+
84
+ case "off": {
85
+ const commandAchievements = deps.service.incrementCommandsRun();
86
+ deps.storage.setMuted(true);
87
+ const ensured = deps.service.ensureCompanion();
88
+ const achievements = mergeAchievements(commandAchievements, ensured.achievements);
89
+ deps.ui.refresh(ctx, ensured.companion, null, achievements);
90
+ ctx.ui.notify(`${ensured.companion.name} goes quiet.`, "info");
91
+ deps.ui.notifyAchievements(ctx, achievements);
92
+ return;
93
+ }
94
+
95
+ case "on": {
96
+ const commandAchievements = deps.service.incrementCommandsRun();
97
+ deps.storage.setMuted(false);
98
+ const result = deps.service.recordComment("*stretches* I'm back!");
99
+ const achievements = mergeAchievements(commandAchievements, result.achievements);
100
+ deps.ui.refresh(ctx, result.companion, result.state, achievements);
101
+ ctx.ui.notify(`${result.companion.name} is back.`, "info");
102
+ deps.ui.notifyAchievements(ctx, achievements);
103
+ return;
104
+ }
105
+
106
+ case "save": {
107
+ const commandAchievements = deps.service.incrementCommandsRun();
108
+ const result = deps.service.saveBuddy(remainder || undefined);
109
+ const ensured = deps.service.ensureCompanion();
110
+ const achievements = mergeAchievements(commandAchievements, ensured.achievements);
111
+ deps.ui.refresh(ctx, result.companion, deps.storage.loadLatest(), achievements);
112
+ ctx.ui.notify(`Saved ${result.companion.name} to [${result.slot}].`, "info");
113
+ deps.ui.notifyAchievements(ctx, achievements);
114
+ return;
115
+ }
116
+
117
+ case "summon": {
118
+ const commandAchievements = deps.service.incrementCommandsRun();
119
+ const result = deps.service.summonBuddy(remainder || undefined);
120
+ if (!result) throw new Error("No saved buddy found for that slot.");
121
+ const ensured = deps.service.ensureCompanion();
122
+ const achievements = mergeAchievements(commandAchievements, ensured.achievements);
123
+ deps.ui.refresh(ctx, result.companion, deps.storage.loadLatest(), achievements);
124
+ ctx.ui.notify(`${result.companion.name} arrives from [${result.slot}].`, "info");
125
+ deps.ui.notifyAchievements(ctx, achievements);
126
+ return;
127
+ }
128
+
129
+ case "list": {
130
+ const commandAchievements = deps.service.incrementCommandsRun();
131
+ const active = deps.storage.loadActiveSlot();
132
+ const companions = deps.service.listCompanions();
133
+ if (companions.length === 0) {
134
+ ctx.ui.notify("No saved buddies yet.", "info");
135
+ deps.ui.notifyAchievements(ctx, commandAchievements);
136
+ return;
137
+ }
138
+ ctx.ui.setWidget(
139
+ "buddy",
140
+ companions.map(({ slot, companion }) => {
141
+ const marker = slot === active ? " <- active" : "";
142
+ return `${companion.name} [${slot}] - ${companion.bones.rarity} ${companion.bones.species}${marker}`;
143
+ }),
144
+ );
145
+ ctx.ui.notify(`Saved buddies: ${companions.length}`, "info");
146
+ deps.ui.notifyAchievements(ctx, commandAchievements);
147
+ return;
148
+ }
149
+
150
+ case "dismiss": {
151
+ if (!remainder) throw new Error("Usage: /buddy dismiss <slot>");
152
+ const commandAchievements = deps.service.incrementCommandsRun();
153
+ const result = deps.service.dismissBuddy(remainder);
154
+ const ensured = deps.service.ensureCompanion();
155
+ const achievements = mergeAchievements(commandAchievements, ensured.achievements);
156
+ deps.ui.refresh(ctx, ensured.companion, deps.storage.loadLatest(), achievements);
157
+ ctx.ui.notify(`Dismissed ${result.companion.name} [${result.slot}].`, "info");
158
+ deps.ui.notifyAchievements(ctx, achievements);
159
+ return;
160
+ }
161
+
162
+ case "achievements": {
163
+ const commandAchievements = deps.service.incrementCommandsRun();
164
+ const activeSlot = deps.storage.loadActiveSlot() ?? undefined;
165
+ const progress = deps.service.getAchievementProgress(activeSlot);
166
+ deps.ui.showAchievements(ctx, progress.unlocked, progress.remaining);
167
+ deps.ui.notifyAchievements(ctx, commandAchievements);
168
+ return;
169
+ }
170
+
171
+ case "help":
172
+ default: {
173
+ const commandAchievements = deps.service.incrementCommandsRun();
174
+ ctx.ui.setWidget("buddy", [
175
+ "/buddy",
176
+ "/buddy pet",
177
+ "/buddy stats",
178
+ "/buddy rename <name>",
179
+ "/buddy personality <text>",
180
+ "/buddy off | on",
181
+ "/buddy save [slot]",
182
+ "/buddy summon [slot]",
183
+ "/buddy list",
184
+ "/buddy dismiss <slot>",
185
+ "/buddy achievements",
186
+ ]);
187
+ ctx.ui.notify("Buddy commands ready.", "info");
188
+ deps.ui.notifyAchievements(ctx, commandAchievements);
189
+ return;
190
+ }
191
+ }
192
+ } catch (error) {
193
+ const message = error instanceof Error ? error.message : String(error);
194
+ ctx.ui.notify(message, "error");
195
+ if (command === "save" && remainder) {
196
+ ctx.ui.notify(`Try a different slot, for example: ${slugifySlot(remainder)}-2`, "info");
197
+ }
198
+ }
199
+ },
200
+ });
201
+ }
@@ -0,0 +1,22 @@
1
+ import type { ExtensionContext } from "@oh-my-pi/pi-coding-agent";
2
+
3
+ export interface OmpBuddyUi {
4
+ notify(message: string, type?: "info" | "warning" | "error"): void;
5
+ setStatus(key: string, text: string | undefined): void;
6
+ setWidget(key: string, content: string[] | undefined): void;
7
+ }
8
+
9
+ export interface OmpBuddyUiContext {
10
+ ui: OmpBuddyUi;
11
+ setTimeout?: (callback: () => void, ms?: number) => Timer;
12
+ clearTimer?: (timer: Timer) => void;
13
+ }
14
+
15
+ export interface OmpBuddyContext extends OmpBuddyUiContext {
16
+ model: ExtensionContext["model"];
17
+ modelRegistry: Pick<
18
+ ExtensionContext["modelRegistry"],
19
+ "find" | "getApiKey" | "getProviderHeaders"
20
+ >;
21
+ sessionManager: Pick<ExtensionContext["sessionManager"], "getBranch">;
22
+ }