@umang-boss/claudemon 1.0.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/README.md +164 -0
- package/bin/claudemon.js +52 -0
- package/bunfig.toml +2 -0
- package/cli/doctor.ts +334 -0
- package/cli/index.ts +42 -0
- package/cli/install.ts +248 -0
- package/cli/shared.ts +102 -0
- package/cli/uninstall.ts +155 -0
- package/cli/update.ts +318 -0
- package/hooks/post-tool-use.sh +127 -0
- package/hooks/stop.sh +49 -0
- package/hooks/user-prompt-submit.sh +73 -0
- package/package.json +68 -0
- package/scripts/download-colorscripts.ts +311 -0
- package/skills/buddy/SKILL.md +47 -0
- package/sprites/colorscripts/small/1-bulbasaur.txt +11 -0
- package/sprites/colorscripts/small/10-caterpie.txt +9 -0
- package/sprites/colorscripts/small/100-voltorb.txt +8 -0
- package/sprites/colorscripts/small/101-electrode.txt +9 -0
- package/sprites/colorscripts/small/102-exeggcute.txt +10 -0
- package/sprites/colorscripts/small/103-exeggutor.txt +23 -0
- package/sprites/colorscripts/small/104-cubone.txt +11 -0
- package/sprites/colorscripts/small/105-marowak.txt +16 -0
- package/sprites/colorscripts/small/106-hitmonlee.txt +16 -0
- package/sprites/colorscripts/small/107-hitmonchan.txt +19 -0
- package/sprites/colorscripts/small/108-lickitung.txt +10 -0
- package/sprites/colorscripts/small/109-koffing.txt +14 -0
- package/sprites/colorscripts/small/11-metapod.txt +10 -0
- package/sprites/colorscripts/small/110-weezing.txt +23 -0
- package/sprites/colorscripts/small/111-rhyhorn.txt +11 -0
- package/sprites/colorscripts/small/112-rhydon.txt +20 -0
- package/sprites/colorscripts/small/113-chansey.txt +11 -0
- package/sprites/colorscripts/small/114-tangela.txt +10 -0
- package/sprites/colorscripts/small/115-kangaskhan.txt +18 -0
- package/sprites/colorscripts/small/116-horsea.txt +10 -0
- package/sprites/colorscripts/small/117-seadra.txt +11 -0
- package/sprites/colorscripts/small/118-goldeen.txt +11 -0
- package/sprites/colorscripts/small/119-seaking.txt +16 -0
- package/sprites/colorscripts/small/12-butterfree.txt +20 -0
- package/sprites/colorscripts/small/120-staryu.txt +10 -0
- package/sprites/colorscripts/small/121-starmie.txt +17 -0
- package/sprites/colorscripts/small/122-mr-mime.txt +18 -0
- package/sprites/colorscripts/small/123-scyther.txt +21 -0
- package/sprites/colorscripts/small/124-jynx.txt +18 -0
- package/sprites/colorscripts/small/125-electabuzz.txt +19 -0
- package/sprites/colorscripts/small/126-magmar.txt +19 -0
- package/sprites/colorscripts/small/127-pinsir.txt +19 -0
- package/sprites/colorscripts/small/128-tauros.txt +20 -0
- package/sprites/colorscripts/small/129-magikarp.txt +13 -0
- package/sprites/colorscripts/small/13-weedle.txt +10 -0
- package/sprites/colorscripts/small/130-gyarados.txt +21 -0
- package/sprites/colorscripts/small/131-lapras.txt +19 -0
- package/sprites/colorscripts/small/132-ditto.txt +8 -0
- package/sprites/colorscripts/small/133-eevee.txt +10 -0
- package/sprites/colorscripts/small/134-vaporeon.txt +16 -0
- package/sprites/colorscripts/small/135-jolteon.txt +17 -0
- package/sprites/colorscripts/small/136-flareon.txt +18 -0
- package/sprites/colorscripts/small/137-porygon.txt +10 -0
- package/sprites/colorscripts/small/138-omanyte.txt +10 -0
- package/sprites/colorscripts/small/139-omastar.txt +18 -0
- package/sprites/colorscripts/small/14-kakuna.txt +10 -0
- package/sprites/colorscripts/small/140-kabuto.txt +8 -0
- package/sprites/colorscripts/small/141-kabutops.txt +17 -0
- package/sprites/colorscripts/small/142-aerodactyl.txt +17 -0
- package/sprites/colorscripts/small/143-snorlax.txt +21 -0
- package/sprites/colorscripts/small/144-articuno.txt +24 -0
- package/sprites/colorscripts/small/145-zapdos.txt +20 -0
- package/sprites/colorscripts/small/146-moltres.txt +23 -0
- package/sprites/colorscripts/small/147-dratini.txt +10 -0
- package/sprites/colorscripts/small/148-dragonair.txt +12 -0
- package/sprites/colorscripts/small/149-dragonite.txt +21 -0
- package/sprites/colorscripts/small/15-beedrill.txt +13 -0
- package/sprites/colorscripts/small/150-mewtwo.txt +22 -0
- package/sprites/colorscripts/small/151-mew.txt +14 -0
- package/sprites/colorscripts/small/16-pidgey.txt +10 -0
- package/sprites/colorscripts/small/17-pidgeotto.txt +11 -0
- package/sprites/colorscripts/small/18-pidgeot.txt +18 -0
- package/sprites/colorscripts/small/19-rattata.txt +12 -0
- package/sprites/colorscripts/small/2-ivysaur.txt +11 -0
- package/sprites/colorscripts/small/20-raticate.txt +12 -0
- package/sprites/colorscripts/small/21-spearow.txt +9 -0
- package/sprites/colorscripts/small/22-fearow.txt +12 -0
- package/sprites/colorscripts/small/23-ekans.txt +12 -0
- package/sprites/colorscripts/small/24-arbok.txt +16 -0
- package/sprites/colorscripts/small/25-pikachu.txt +11 -0
- package/sprites/colorscripts/small/26-raichu.txt +19 -0
- package/sprites/colorscripts/small/27-sandshrew.txt +10 -0
- package/sprites/colorscripts/small/28-sandslash.txt +16 -0
- package/sprites/colorscripts/small/29-nidoran-f.txt +11 -0
- package/sprites/colorscripts/small/3-venusaur.txt +21 -0
- package/sprites/colorscripts/small/30-nidorina.txt +12 -0
- package/sprites/colorscripts/small/31-nidoqueen.txt +19 -0
- package/sprites/colorscripts/small/32-nidoran-m.txt +11 -0
- package/sprites/colorscripts/small/33-nidorino.txt +12 -0
- package/sprites/colorscripts/small/34-nidoking.txt +18 -0
- package/sprites/colorscripts/small/35-clefairy.txt +11 -0
- package/sprites/colorscripts/small/36-clefable.txt +17 -0
- package/sprites/colorscripts/small/37-vulpix.txt +11 -0
- package/sprites/colorscripts/small/38-ninetales.txt +18 -0
- package/sprites/colorscripts/small/39-jigglypuff.txt +11 -0
- package/sprites/colorscripts/small/4-charmander.txt +11 -0
- package/sprites/colorscripts/small/40-wigglytuff.txt +20 -0
- package/sprites/colorscripts/small/41-zubat.txt +11 -0
- package/sprites/colorscripts/small/42-golbat.txt +18 -0
- package/sprites/colorscripts/small/43-oddish.txt +11 -0
- package/sprites/colorscripts/small/44-gloom.txt +12 -0
- package/sprites/colorscripts/small/45-vileplume.txt +17 -0
- package/sprites/colorscripts/small/46-paras.txt +11 -0
- package/sprites/colorscripts/small/47-parasect.txt +12 -0
- package/sprites/colorscripts/small/48-venonat.txt +14 -0
- package/sprites/colorscripts/small/49-venomoth.txt +19 -0
- package/sprites/colorscripts/small/5-charmeleon.txt +13 -0
- package/sprites/colorscripts/small/50-diglett.txt +8 -0
- package/sprites/colorscripts/small/51-dugtrio.txt +18 -0
- package/sprites/colorscripts/small/52-meowth.txt +12 -0
- package/sprites/colorscripts/small/53-persian.txt +20 -0
- package/sprites/colorscripts/small/54-psyduck.txt +12 -0
- package/sprites/colorscripts/small/55-golduck.txt +17 -0
- package/sprites/colorscripts/small/56-mankey.txt +11 -0
- package/sprites/colorscripts/small/57-primeape.txt +13 -0
- package/sprites/colorscripts/small/58-growlithe.txt +12 -0
- package/sprites/colorscripts/small/59-arcanine.txt +20 -0
- package/sprites/colorscripts/small/6-charizard.txt +21 -0
- package/sprites/colorscripts/small/60-poliwag.txt +9 -0
- package/sprites/colorscripts/small/61-poliwhirl.txt +11 -0
- package/sprites/colorscripts/small/62-poliwrath.txt +17 -0
- package/sprites/colorscripts/small/63-abra.txt +12 -0
- package/sprites/colorscripts/small/64-kadabra.txt +14 -0
- package/sprites/colorscripts/small/65-alakazam.txt +19 -0
- package/sprites/colorscripts/small/66-machop.txt +11 -0
- package/sprites/colorscripts/small/67-machoke.txt +12 -0
- package/sprites/colorscripts/small/68-machamp.txt +19 -0
- package/sprites/colorscripts/small/69-bellsprout.txt +9 -0
- package/sprites/colorscripts/small/7-squirtle.txt +10 -0
- package/sprites/colorscripts/small/70-weepinbell.txt +11 -0
- package/sprites/colorscripts/small/71-victreebel.txt +17 -0
- package/sprites/colorscripts/small/72-tentacool.txt +12 -0
- package/sprites/colorscripts/small/73-tentacruel.txt +20 -0
- package/sprites/colorscripts/small/74-geodude.txt +9 -0
- package/sprites/colorscripts/small/75-graveler.txt +12 -0
- package/sprites/colorscripts/small/76-golem.txt +18 -0
- package/sprites/colorscripts/small/77-ponyta.txt +13 -0
- package/sprites/colorscripts/small/78-rapidash.txt +18 -0
- package/sprites/colorscripts/small/79-slowpoke.txt +12 -0
- package/sprites/colorscripts/small/8-wartortle.txt +12 -0
- package/sprites/colorscripts/small/80-slowbro.txt +18 -0
- package/sprites/colorscripts/small/81-magnemite.txt +9 -0
- package/sprites/colorscripts/small/82-magneton.txt +18 -0
- package/sprites/colorscripts/small/83-farfetchd.txt +12 -0
- package/sprites/colorscripts/small/84-doduo.txt +10 -0
- package/sprites/colorscripts/small/85-dodrio.txt +17 -0
- package/sprites/colorscripts/small/86-seel.txt +13 -0
- package/sprites/colorscripts/small/87-dewgong.txt +20 -0
- package/sprites/colorscripts/small/88-grimer.txt +10 -0
- package/sprites/colorscripts/small/89-muk.txt +14 -0
- package/sprites/colorscripts/small/9-blastoise.txt +20 -0
- package/sprites/colorscripts/small/90-shellder.txt +10 -0
- package/sprites/colorscripts/small/91-cloyster.txt +18 -0
- package/sprites/colorscripts/small/92-gastly.txt +12 -0
- package/sprites/colorscripts/small/93-haunter.txt +14 -0
- package/sprites/colorscripts/small/94-gengar.txt +19 -0
- package/sprites/colorscripts/small/95-onix.txt +22 -0
- package/sprites/colorscripts/small/96-drowzee.txt +12 -0
- package/sprites/colorscripts/small/97-hypno.txt +19 -0
- package/sprites/colorscripts/small/98-krabby.txt +12 -0
- package/sprites/colorscripts/small/99-kingler.txt +20 -0
- package/src/engine/constants.ts +121 -0
- package/src/engine/encounter-pool.ts +71 -0
- package/src/engine/encounters.ts +308 -0
- package/src/engine/evolution-data.ts +535 -0
- package/src/engine/evolution.ts +310 -0
- package/src/engine/pokemon-data.ts +1838 -0
- package/src/engine/reactions.ts +877 -0
- package/src/engine/starter-pool.ts +47 -0
- package/src/engine/stats.ts +97 -0
- package/src/engine/types.ts +312 -0
- package/src/engine/xp.ts +135 -0
- package/src/gamification/achievements.ts +204 -0
- package/src/gamification/legendary-quests.ts +265 -0
- package/src/gamification/milestones.ts +86 -0
- package/src/hooks/award-xp.ts +131 -0
- package/src/hooks/increment-counter.ts +27 -0
- package/src/server/index.ts +78 -0
- package/src/server/instructions.ts +194 -0
- package/src/server/tools/achievements.ts +118 -0
- package/src/server/tools/catch.ts +295 -0
- package/src/server/tools/display-helpers.ts +35 -0
- package/src/server/tools/evolve.ts +236 -0
- package/src/server/tools/legendary.ts +78 -0
- package/src/server/tools/party.ts +251 -0
- package/src/server/tools/pet.ts +124 -0
- package/src/server/tools/pokedex.ts +286 -0
- package/src/server/tools/rename.ts +63 -0
- package/src/server/tools/show.ts +136 -0
- package/src/server/tools/starter.ts +175 -0
- package/src/server/tools/stats.ts +123 -0
- package/src/server/tools/visibility.ts +65 -0
- package/src/sprites/index.ts +45 -0
- package/src/state/io.ts +91 -0
- package/src/state/schemas.ts +131 -0
- package/src/state/state-manager.ts +321 -0
- package/statusline/buddy-status.sh +233 -0
- package/tsconfig.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Claudemon
|
|
2
|
+
|
|
3
|
+
> Pokemon Gen 1 coding companion for Claude Code -- Gotta code 'em all!
|
|
4
|
+
|
|
5
|
+
## What is Claudemon?
|
|
6
|
+
|
|
7
|
+
A gamified coding companion that lives inside Claude Code. Pick a starter Pokemon,
|
|
8
|
+
earn XP from your coding activity, level up, evolve, catch wild Pokemon, and
|
|
9
|
+
fill your Pokedex -- all while you code.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **151 Gen 1 Pokemon** with authentic base stats and evolution chains
|
|
14
|
+
- **XP from coding** -- commits, tests, builds, edits, and more
|
|
15
|
+
- **Level up & evolve** -- Charmander -> Charmeleon -> Charizard
|
|
16
|
+
- **Wild encounters** -- Pokemon appear based on your coding activity
|
|
17
|
+
- **Catch 'em all** -- fill your 151-entry Pokedex
|
|
18
|
+
- **Achievements** -- 18 milestones to unlock
|
|
19
|
+
- **Legendary quests** -- multi-step challenges for Articuno, Zapdos, Moltres, Mewtwo, Mew
|
|
20
|
+
- **Colored terminal sprites** -- hand-crafted pixel art in your terminal
|
|
21
|
+
- **Type personalities** -- 15 unique reaction styles
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Install
|
|
27
|
+
bun run cli/install.ts
|
|
28
|
+
|
|
29
|
+
# Start a new Claude Code session, then:
|
|
30
|
+
/buddy # Pick your starter
|
|
31
|
+
/buddy show # See your Pokemon
|
|
32
|
+
/buddy pet # Bond with your companion
|
|
33
|
+
/buddy stats # Detailed stats
|
|
34
|
+
/buddy evolve # Check evolution status
|
|
35
|
+
/buddy catch # Catch wild Pokemon
|
|
36
|
+
/buddy party # Manage your party
|
|
37
|
+
/buddy pokedex # Track your collection
|
|
38
|
+
/buddy achievements # View progress
|
|
39
|
+
/buddy legendary # Legendary quest chains
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## How It Works
|
|
43
|
+
|
|
44
|
+
Claudemon runs as an MCP (Model Context Protocol) server alongside Claude Code.
|
|
45
|
+
It uses hooks to detect your coding activity and award XP automatically.
|
|
46
|
+
|
|
47
|
+
### Architecture
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Claude Code -> MCP Server (Claudemon)
|
|
51
|
+
-> Hooks (PostToolUse, Stop, UserPromptSubmit)
|
|
52
|
+
-> Status Line (name + level + XP bar)
|
|
53
|
+
-> /buddy Skill (slash commands)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### XP Sources
|
|
57
|
+
|
|
58
|
+
| Activity | XP | Stat Boosted |
|
|
59
|
+
|----------|-----|-------------|
|
|
60
|
+
| Git commit | +15 | VELOCITY |
|
|
61
|
+
| Tests pass | +12 | STABILITY |
|
|
62
|
+
| Test written | +10 | STABILITY |
|
|
63
|
+
| Build success | +10 | STABILITY |
|
|
64
|
+
| Bug fix | +8 | DEBUGGING |
|
|
65
|
+
| Lint fix | +6 | DEBUGGING |
|
|
66
|
+
| File created | +5 | VELOCITY |
|
|
67
|
+
| File edited | +3 | VELOCITY |
|
|
68
|
+
| Large refactor | +20 | WISDOM |
|
|
69
|
+
|
|
70
|
+
### Evolution
|
|
71
|
+
|
|
72
|
+
Pokemon evolve at the same levels as the original Gen 1 games:
|
|
73
|
+
- Level-based: Charmander -> Charmeleon (L16) -> Charizard (L36)
|
|
74
|
+
- Badge-based: Pikachu -> Raichu (Spark Badge -- 200 commits)
|
|
75
|
+
- Collaboration: Kadabra -> Alakazam (10 PRs merged)
|
|
76
|
+
- Stat-based: Eevee -> Flareon/Vaporeon/Jolteon (dominant coding stat)
|
|
77
|
+
|
|
78
|
+
### Badges
|
|
79
|
+
|
|
80
|
+
| Badge | Condition | Unlocks |
|
|
81
|
+
|-------|-----------|---------|
|
|
82
|
+
| Blaze Badge | Fix 50 bugs | Fire Stone evolutions |
|
|
83
|
+
| Flow Badge | Pass 100 tests | Water Stone evolutions |
|
|
84
|
+
| Spark Badge | 200 commits | Thunder Stone evolutions |
|
|
85
|
+
| Lunar Badge | 30-day streak | Moon Stone evolutions |
|
|
86
|
+
| Growth Badge | Edit 500 files | Leaf Stone evolutions |
|
|
87
|
+
|
|
88
|
+
### Trainer Titles
|
|
89
|
+
|
|
90
|
+
Your title progresses as your Pokemon levels up:
|
|
91
|
+
|
|
92
|
+
| Level | Title |
|
|
93
|
+
|-------|-------|
|
|
94
|
+
| 1 | Bug Catcher |
|
|
95
|
+
| 6 | Youngster |
|
|
96
|
+
| 11 | Hiker |
|
|
97
|
+
| 21 | Ace Trainer |
|
|
98
|
+
| 31 | Cooltrainer |
|
|
99
|
+
| 41 | Veteran |
|
|
100
|
+
| 51 | Elite Four |
|
|
101
|
+
| 61 | Champion |
|
|
102
|
+
| 76 | Pokemon Master |
|
|
103
|
+
| 91 | Professor |
|
|
104
|
+
|
|
105
|
+
## CLI Tools
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Install Claudemon into Claude Code
|
|
109
|
+
bun run cli/install.ts
|
|
110
|
+
|
|
111
|
+
# Uninstall (preserves your save data)
|
|
112
|
+
bun run cli/uninstall.ts
|
|
113
|
+
|
|
114
|
+
# Diagnose installation issues
|
|
115
|
+
bun run cli/doctor.ts
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Run the MCP server directly
|
|
122
|
+
bun run server
|
|
123
|
+
|
|
124
|
+
# Run tests
|
|
125
|
+
bun test
|
|
126
|
+
|
|
127
|
+
# Type checking
|
|
128
|
+
bun run typecheck
|
|
129
|
+
|
|
130
|
+
# Format code
|
|
131
|
+
bun run format
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Project Structure
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
src/
|
|
138
|
+
engine/ # Game logic: XP, evolution, encounters, stats
|
|
139
|
+
gamification/ # Achievements, milestones, legendary quests
|
|
140
|
+
server/ # MCP server and tool handlers
|
|
141
|
+
sprites/ # Terminal sprite rendering
|
|
142
|
+
state/ # Save state management
|
|
143
|
+
cli/ # Install, uninstall, doctor scripts
|
|
144
|
+
hooks/ # PostToolUse, Stop, UserPromptSubmit shell scripts
|
|
145
|
+
skills/buddy/ # /buddy slash command definition
|
|
146
|
+
sprites/full/ # 151 colored terminal sprite files
|
|
147
|
+
statusline/ # Status bar shell script
|
|
148
|
+
tests/ # Test suites
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Requirements
|
|
152
|
+
|
|
153
|
+
- [Claude Code](https://claude.ai/code) v2.1.80+
|
|
154
|
+
- [Bun](https://bun.sh) v1.0+
|
|
155
|
+
|
|
156
|
+
## Disclaimer
|
|
157
|
+
|
|
158
|
+
Pokemon is a trademark of Nintendo/Game Freak/The Pokemon Company.
|
|
159
|
+
Claudemon is a fan project, not affiliated with or endorsed by them.
|
|
160
|
+
Terminal art from [pokemon-colorscripts](https://github.com/tageraf1n/pokemon-colorscripts) (MIT).
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT
|
package/bin/claudemon.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claudemon CLI wrapper — detects Bun and delegates to it.
|
|
4
|
+
* Works with `npx claudemon` even on systems with only Node.js.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { resolve, dirname } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const cliEntry = resolve(__dirname, "..", "cli", "index.ts");
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
|
|
16
|
+
// Find bun binary
|
|
17
|
+
function findBun() {
|
|
18
|
+
const home = process.env.HOME || "";
|
|
19
|
+
const candidates = [
|
|
20
|
+
`${home}/.bun/bin/bun`,
|
|
21
|
+
"/usr/local/bin/bun",
|
|
22
|
+
"/usr/bin/bun",
|
|
23
|
+
];
|
|
24
|
+
for (const p of candidates) {
|
|
25
|
+
if (existsSync(p)) return p;
|
|
26
|
+
}
|
|
27
|
+
// Try PATH
|
|
28
|
+
const result = spawnSync("which", ["bun"], { stdio: "pipe" });
|
|
29
|
+
if (result.status === 0) return "bun";
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const bunPath = findBun();
|
|
34
|
+
|
|
35
|
+
if (bunPath) {
|
|
36
|
+
const result = spawnSync(bunPath, ["run", cliEntry, ...args], {
|
|
37
|
+
stdio: "inherit",
|
|
38
|
+
env: process.env,
|
|
39
|
+
});
|
|
40
|
+
process.exit(result.status ?? 1);
|
|
41
|
+
} else {
|
|
42
|
+
console.error(`
|
|
43
|
+
Claudemon requires Bun (https://bun.sh) to run.
|
|
44
|
+
|
|
45
|
+
Install Bun:
|
|
46
|
+
curl -fsSL https://bun.sh/install | bash
|
|
47
|
+
|
|
48
|
+
Then run:
|
|
49
|
+
npx claudemon install
|
|
50
|
+
`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
package/bunfig.toml
ADDED
package/cli/doctor.ts
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claudemon Doctor — diagnostic tool.
|
|
3
|
+
* Checks the health of the Claudemon installation.
|
|
4
|
+
*
|
|
5
|
+
* Usage: bun run cli/doctor.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { access, stat, unlink, readdir } from "node:fs/promises";
|
|
9
|
+
import { constants as fsConstants } from "node:fs";
|
|
10
|
+
import { resolve, dirname } from "node:path";
|
|
11
|
+
import { StateManager } from "../src/state/state-manager.js";
|
|
12
|
+
import { PlayerStateSchema } from "../src/state/schemas.js";
|
|
13
|
+
|
|
14
|
+
import type { ClaudeConfig, ClaudeSettings } from "./shared.js";
|
|
15
|
+
import {
|
|
16
|
+
readJson,
|
|
17
|
+
info,
|
|
18
|
+
CLAUDE_CONFIG,
|
|
19
|
+
CLAUDE_SETTINGS,
|
|
20
|
+
STATE_DIR,
|
|
21
|
+
SKILL_DEST,
|
|
22
|
+
HOOK_SCRIPT,
|
|
23
|
+
} from "./shared.js";
|
|
24
|
+
|
|
25
|
+
// ── Local Constants ─────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const STATE_FILE = `${STATE_DIR}/state.json`;
|
|
28
|
+
const LOCK_FILE = `${STATE_DIR}/state.lock`;
|
|
29
|
+
const LOCK_MAX_AGE_MS = 5000;
|
|
30
|
+
const EXPECTED_SPRITE_COUNT = 151;
|
|
31
|
+
const COLORSCRIPT_DIR = resolve(dirname(import.meta.dir), "sprites/colorscripts/small");
|
|
32
|
+
|
|
33
|
+
// ── Helpers ──────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
interface CheckResult {
|
|
36
|
+
label: string;
|
|
37
|
+
passed: boolean;
|
|
38
|
+
detail: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function formatCheck(result: CheckResult): string {
|
|
42
|
+
const icon = result.passed ? "\u2713" : "\u2717";
|
|
43
|
+
return `[${icon}] ${result.label}: ${result.detail}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Check 1: Bun Version ─────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
async function checkBun(): Promise<CheckResult> {
|
|
49
|
+
try {
|
|
50
|
+
const proc = Bun.spawn(["bun", "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
51
|
+
const output = await new Response(proc.stdout).text();
|
|
52
|
+
await proc.exited;
|
|
53
|
+
return { label: "Bun runtime", passed: true, detail: `v${output.trim()}` };
|
|
54
|
+
} catch {
|
|
55
|
+
return { label: "Bun runtime", passed: false, detail: "not found" };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Check 2: State Directory ─────────────────────────────────
|
|
60
|
+
|
|
61
|
+
async function checkStateDir(): Promise<CheckResult> {
|
|
62
|
+
try {
|
|
63
|
+
await access(STATE_DIR, fsConstants.F_OK);
|
|
64
|
+
return { label: "State directory", passed: true, detail: "~/.claudemon/" };
|
|
65
|
+
} catch {
|
|
66
|
+
return {
|
|
67
|
+
label: "State directory",
|
|
68
|
+
passed: false,
|
|
69
|
+
detail: "~/.claudemon/ not found (run installer)",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Check 3: State File ──────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
async function checkStateFile(): Promise<CheckResult> {
|
|
77
|
+
const file = Bun.file(STATE_FILE);
|
|
78
|
+
if (!(await file.exists())) {
|
|
79
|
+
return { label: "State file", passed: false, detail: "not found (run /buddy starter first)" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const text = await file.text();
|
|
84
|
+
const data = JSON.parse(text) as Record<string, unknown>;
|
|
85
|
+
|
|
86
|
+
// Check for active pokemon
|
|
87
|
+
const party = data["party"];
|
|
88
|
+
if (Array.isArray(party) && party.length > 0) {
|
|
89
|
+
const active = party.find(
|
|
90
|
+
(p: unknown) =>
|
|
91
|
+
typeof p === "object" &&
|
|
92
|
+
p !== null &&
|
|
93
|
+
"isActive" in p &&
|
|
94
|
+
(p as Record<string, unknown>)["isActive"] === true,
|
|
95
|
+
);
|
|
96
|
+
if (active && typeof active === "object" && "pokemonId" in active) {
|
|
97
|
+
return {
|
|
98
|
+
label: "State file",
|
|
99
|
+
passed: true,
|
|
100
|
+
detail: `valid, active Pokemon #${(active as Record<string, unknown>)["pokemonId"]}`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return { label: "State file", passed: true, detail: "valid, but no active Pokemon set" };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { label: "State file", passed: true, detail: "valid JSON, no party members yet" };
|
|
107
|
+
} catch {
|
|
108
|
+
return { label: "State file", passed: false, detail: "exists but invalid JSON" };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Check 4: MCP Server Registration ─────────────────────────
|
|
113
|
+
|
|
114
|
+
async function checkMcpServer(): Promise<CheckResult> {
|
|
115
|
+
const config = await readJson<ClaudeConfig>(CLAUDE_CONFIG);
|
|
116
|
+
if (!config) {
|
|
117
|
+
return { label: "MCP server", passed: false, detail: "~/.claude.json not found" };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (config.mcpServers && config.mcpServers["claudemon"]) {
|
|
121
|
+
return { label: "MCP server", passed: true, detail: "registered in ~/.claude.json" };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { label: "MCP server", passed: false, detail: "not registered in ~/.claude.json" };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Check 5: Hooks ───────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
async function checkHooks(): Promise<CheckResult> {
|
|
130
|
+
const settings = await readJson<ClaudeSettings>(CLAUDE_SETTINGS);
|
|
131
|
+
if (!settings) {
|
|
132
|
+
return { label: "Hooks", passed: false, detail: "~/.claude/settings.json not found" };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!settings.hooks || !settings.hooks["PostToolUse"]) {
|
|
136
|
+
return { label: "Hooks", passed: false, detail: "no PostToolUse hooks configured" };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const hasOurHook = settings.hooks["PostToolUse"].some((m) =>
|
|
140
|
+
m.hooks.some((h) => h.command.includes("post-tool-use.sh")),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (hasOurHook) {
|
|
144
|
+
return { label: "Hooks", passed: true, detail: "PostToolUse configured" };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { label: "Hooks", passed: false, detail: "PostToolUse exists but missing Claudemon hook" };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── Check 6: Skill ───────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
async function checkSkill(): Promise<CheckResult> {
|
|
153
|
+
const file = Bun.file(SKILL_DEST);
|
|
154
|
+
if (await file.exists()) {
|
|
155
|
+
return { label: "Skill", passed: true, detail: "/buddy command installed" };
|
|
156
|
+
}
|
|
157
|
+
return { label: "Skill", passed: false, detail: "~/.claude/skills/buddy/SKILL.md not found" };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── Check 7: Hook Script Executable ──────────────────────────
|
|
161
|
+
|
|
162
|
+
async function checkHookScript(): Promise<CheckResult> {
|
|
163
|
+
try {
|
|
164
|
+
await access(HOOK_SCRIPT, fsConstants.X_OK);
|
|
165
|
+
return { label: "Hook script", passed: true, detail: "executable" };
|
|
166
|
+
} catch {
|
|
167
|
+
const file = Bun.file(HOOK_SCRIPT);
|
|
168
|
+
if (await file.exists()) {
|
|
169
|
+
return {
|
|
170
|
+
label: "Hook script",
|
|
171
|
+
passed: false,
|
|
172
|
+
detail: "exists but not executable (run chmod +x)",
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return { label: "Hook script", passed: false, detail: "not found" };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Check 8: State Manager Load ──────────────────────────────
|
|
180
|
+
|
|
181
|
+
async function checkStateManager(): Promise<CheckResult> {
|
|
182
|
+
try {
|
|
183
|
+
StateManager.resetInstance();
|
|
184
|
+
const manager = StateManager.getInstance();
|
|
185
|
+
const state = await manager.load();
|
|
186
|
+
StateManager.resetInstance();
|
|
187
|
+
|
|
188
|
+
if (state) {
|
|
189
|
+
return { label: "State manager", passed: true, detail: "loaded successfully" };
|
|
190
|
+
}
|
|
191
|
+
return { label: "State manager", passed: true, detail: "no state file yet (first run)" };
|
|
192
|
+
} catch (err: unknown) {
|
|
193
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
194
|
+
StateManager.resetInstance();
|
|
195
|
+
return { label: "State manager", passed: false, detail: `load failed: ${message}` };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ── Check 9: Stale Lock File ────────────────────────────────
|
|
200
|
+
|
|
201
|
+
async function checkStaleLock(): Promise<CheckResult> {
|
|
202
|
+
try {
|
|
203
|
+
const lockStat = await stat(LOCK_FILE);
|
|
204
|
+
const lockAge = Date.now() - lockStat.mtimeMs;
|
|
205
|
+
|
|
206
|
+
if (lockAge > LOCK_MAX_AGE_MS) {
|
|
207
|
+
// Offer cleanup
|
|
208
|
+
info(`Stale lock file found (${Math.round(lockAge / 1000)}s old). Cleaning up...`);
|
|
209
|
+
try {
|
|
210
|
+
await unlink(LOCK_FILE);
|
|
211
|
+
return { label: "Lock file", passed: true, detail: "stale lock cleaned up" };
|
|
212
|
+
} catch {
|
|
213
|
+
return { label: "Lock file", passed: false, detail: "stale lock found, cleanup failed" };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
label: "Lock file",
|
|
219
|
+
passed: true,
|
|
220
|
+
detail: `active (${Math.round(lockAge / 1000)}s old)`,
|
|
221
|
+
};
|
|
222
|
+
} catch {
|
|
223
|
+
// No lock file — this is the normal case
|
|
224
|
+
return { label: "Lock file", passed: true, detail: "no lock (clean)" };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── Check 10: Sprite Count ──────────────────────────────────
|
|
229
|
+
|
|
230
|
+
async function checkSpriteCount(): Promise<CheckResult> {
|
|
231
|
+
try {
|
|
232
|
+
await access(COLORSCRIPT_DIR, fsConstants.F_OK);
|
|
233
|
+
const entries = await readdir(COLORSCRIPT_DIR);
|
|
234
|
+
const spriteFiles = entries.filter((f) => f.endsWith(".txt"));
|
|
235
|
+
const count = spriteFiles.length;
|
|
236
|
+
|
|
237
|
+
if (count >= EXPECTED_SPRITE_COUNT) {
|
|
238
|
+
return {
|
|
239
|
+
label: "Sprites",
|
|
240
|
+
passed: true,
|
|
241
|
+
detail: `${count}/${EXPECTED_SPRITE_COUNT} sprite files`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
label: "Sprites",
|
|
247
|
+
passed: false,
|
|
248
|
+
detail: `only ${count}/${EXPECTED_SPRITE_COUNT} sprite files (some sprites missing)`,
|
|
249
|
+
};
|
|
250
|
+
} catch {
|
|
251
|
+
return { label: "Sprites", passed: false, detail: "colorscripts directory not found" };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── Check 11: State File Validity (Zod) ─────────────────────
|
|
256
|
+
|
|
257
|
+
async function checkStateValidity(): Promise<CheckResult> {
|
|
258
|
+
const file = Bun.file(STATE_FILE);
|
|
259
|
+
if (!(await file.exists())) {
|
|
260
|
+
return { label: "State validity", passed: true, detail: "no state file yet" };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const text = await file.text();
|
|
265
|
+
if (!text.trim()) {
|
|
266
|
+
return { label: "State validity", passed: false, detail: "state file is empty" };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const data = JSON.parse(text) as unknown;
|
|
270
|
+
const result = PlayerStateSchema.safeParse(data);
|
|
271
|
+
|
|
272
|
+
if (result.success) {
|
|
273
|
+
return { label: "State validity", passed: true, detail: "schema valid" };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Report first few issues
|
|
277
|
+
const issues = result.error.issues.slice(0, 3);
|
|
278
|
+
const details = issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
279
|
+
return {
|
|
280
|
+
label: "State validity",
|
|
281
|
+
passed: false,
|
|
282
|
+
detail: `schema errors: ${details}`,
|
|
283
|
+
};
|
|
284
|
+
} catch {
|
|
285
|
+
return { label: "State validity", passed: false, detail: "invalid JSON" };
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── Main ─────────────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
export async function doctor(): Promise<void> {
|
|
292
|
+
console.log("");
|
|
293
|
+
console.log("Claudemon Doctor");
|
|
294
|
+
console.log("================");
|
|
295
|
+
console.log("");
|
|
296
|
+
|
|
297
|
+
const checks: CheckResult[] = [
|
|
298
|
+
await checkBun(),
|
|
299
|
+
await checkStateDir(),
|
|
300
|
+
await checkStateFile(),
|
|
301
|
+
await checkMcpServer(),
|
|
302
|
+
await checkHooks(),
|
|
303
|
+
await checkSkill(),
|
|
304
|
+
await checkHookScript(),
|
|
305
|
+
await checkStateManager(),
|
|
306
|
+
await checkStaleLock(),
|
|
307
|
+
await checkSpriteCount(),
|
|
308
|
+
await checkStateValidity(),
|
|
309
|
+
];
|
|
310
|
+
|
|
311
|
+
for (const check of checks) {
|
|
312
|
+
console.log(formatCheck(check));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const passed = checks.filter((c) => c.passed).length;
|
|
316
|
+
const total = checks.length;
|
|
317
|
+
|
|
318
|
+
console.log("");
|
|
319
|
+
console.log(`Result: ${passed}/${total} checks passed`);
|
|
320
|
+
|
|
321
|
+
if (passed < total) {
|
|
322
|
+
console.log("");
|
|
323
|
+
console.log("Run 'bun run cli/install.ts' to fix missing components.");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
console.log("");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Run if executed directly
|
|
330
|
+
doctor().catch((err: unknown) => {
|
|
331
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
332
|
+
console.error(`\nDoctor failed: ${message}`);
|
|
333
|
+
process.exit(1);
|
|
334
|
+
});
|
package/cli/index.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Claudemon CLI entry point.
|
|
4
|
+
* Routes to install, uninstall, update, or doctor based on first argument.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx claudemon install
|
|
8
|
+
* npx claudemon uninstall
|
|
9
|
+
* npx claudemon update
|
|
10
|
+
* npx claudemon doctor
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export {};
|
|
14
|
+
const command = process.argv[2];
|
|
15
|
+
|
|
16
|
+
switch (command) {
|
|
17
|
+
case "install":
|
|
18
|
+
await import("./install.js");
|
|
19
|
+
break;
|
|
20
|
+
case "uninstall":
|
|
21
|
+
await import("./uninstall.js");
|
|
22
|
+
break;
|
|
23
|
+
case "update":
|
|
24
|
+
await import("./update.js");
|
|
25
|
+
break;
|
|
26
|
+
case "doctor":
|
|
27
|
+
await import("./doctor.js");
|
|
28
|
+
break;
|
|
29
|
+
default:
|
|
30
|
+
console.log(`
|
|
31
|
+
Claudemon — Pokemon Gen 1 coding companion for Claude Code
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
claudemon install Set up Claudemon (MCP server, hooks, skill, status line)
|
|
35
|
+
claudemon uninstall Remove Claudemon from Claude Code
|
|
36
|
+
claudemon update Re-register everything (preserves save data)
|
|
37
|
+
claudemon doctor Run diagnostics
|
|
38
|
+
|
|
39
|
+
After install, start a new Claude Code session and type /buddy
|
|
40
|
+
`);
|
|
41
|
+
break;
|
|
42
|
+
}
|