@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.
Files changed (203) hide show
  1. package/README.md +164 -0
  2. package/bin/claudemon.js +52 -0
  3. package/bunfig.toml +2 -0
  4. package/cli/doctor.ts +334 -0
  5. package/cli/index.ts +42 -0
  6. package/cli/install.ts +248 -0
  7. package/cli/shared.ts +102 -0
  8. package/cli/uninstall.ts +155 -0
  9. package/cli/update.ts +318 -0
  10. package/hooks/post-tool-use.sh +127 -0
  11. package/hooks/stop.sh +49 -0
  12. package/hooks/user-prompt-submit.sh +73 -0
  13. package/package.json +68 -0
  14. package/scripts/download-colorscripts.ts +311 -0
  15. package/skills/buddy/SKILL.md +47 -0
  16. package/sprites/colorscripts/small/1-bulbasaur.txt +11 -0
  17. package/sprites/colorscripts/small/10-caterpie.txt +9 -0
  18. package/sprites/colorscripts/small/100-voltorb.txt +8 -0
  19. package/sprites/colorscripts/small/101-electrode.txt +9 -0
  20. package/sprites/colorscripts/small/102-exeggcute.txt +10 -0
  21. package/sprites/colorscripts/small/103-exeggutor.txt +23 -0
  22. package/sprites/colorscripts/small/104-cubone.txt +11 -0
  23. package/sprites/colorscripts/small/105-marowak.txt +16 -0
  24. package/sprites/colorscripts/small/106-hitmonlee.txt +16 -0
  25. package/sprites/colorscripts/small/107-hitmonchan.txt +19 -0
  26. package/sprites/colorscripts/small/108-lickitung.txt +10 -0
  27. package/sprites/colorscripts/small/109-koffing.txt +14 -0
  28. package/sprites/colorscripts/small/11-metapod.txt +10 -0
  29. package/sprites/colorscripts/small/110-weezing.txt +23 -0
  30. package/sprites/colorscripts/small/111-rhyhorn.txt +11 -0
  31. package/sprites/colorscripts/small/112-rhydon.txt +20 -0
  32. package/sprites/colorscripts/small/113-chansey.txt +11 -0
  33. package/sprites/colorscripts/small/114-tangela.txt +10 -0
  34. package/sprites/colorscripts/small/115-kangaskhan.txt +18 -0
  35. package/sprites/colorscripts/small/116-horsea.txt +10 -0
  36. package/sprites/colorscripts/small/117-seadra.txt +11 -0
  37. package/sprites/colorscripts/small/118-goldeen.txt +11 -0
  38. package/sprites/colorscripts/small/119-seaking.txt +16 -0
  39. package/sprites/colorscripts/small/12-butterfree.txt +20 -0
  40. package/sprites/colorscripts/small/120-staryu.txt +10 -0
  41. package/sprites/colorscripts/small/121-starmie.txt +17 -0
  42. package/sprites/colorscripts/small/122-mr-mime.txt +18 -0
  43. package/sprites/colorscripts/small/123-scyther.txt +21 -0
  44. package/sprites/colorscripts/small/124-jynx.txt +18 -0
  45. package/sprites/colorscripts/small/125-electabuzz.txt +19 -0
  46. package/sprites/colorscripts/small/126-magmar.txt +19 -0
  47. package/sprites/colorscripts/small/127-pinsir.txt +19 -0
  48. package/sprites/colorscripts/small/128-tauros.txt +20 -0
  49. package/sprites/colorscripts/small/129-magikarp.txt +13 -0
  50. package/sprites/colorscripts/small/13-weedle.txt +10 -0
  51. package/sprites/colorscripts/small/130-gyarados.txt +21 -0
  52. package/sprites/colorscripts/small/131-lapras.txt +19 -0
  53. package/sprites/colorscripts/small/132-ditto.txt +8 -0
  54. package/sprites/colorscripts/small/133-eevee.txt +10 -0
  55. package/sprites/colorscripts/small/134-vaporeon.txt +16 -0
  56. package/sprites/colorscripts/small/135-jolteon.txt +17 -0
  57. package/sprites/colorscripts/small/136-flareon.txt +18 -0
  58. package/sprites/colorscripts/small/137-porygon.txt +10 -0
  59. package/sprites/colorscripts/small/138-omanyte.txt +10 -0
  60. package/sprites/colorscripts/small/139-omastar.txt +18 -0
  61. package/sprites/colorscripts/small/14-kakuna.txt +10 -0
  62. package/sprites/colorscripts/small/140-kabuto.txt +8 -0
  63. package/sprites/colorscripts/small/141-kabutops.txt +17 -0
  64. package/sprites/colorscripts/small/142-aerodactyl.txt +17 -0
  65. package/sprites/colorscripts/small/143-snorlax.txt +21 -0
  66. package/sprites/colorscripts/small/144-articuno.txt +24 -0
  67. package/sprites/colorscripts/small/145-zapdos.txt +20 -0
  68. package/sprites/colorscripts/small/146-moltres.txt +23 -0
  69. package/sprites/colorscripts/small/147-dratini.txt +10 -0
  70. package/sprites/colorscripts/small/148-dragonair.txt +12 -0
  71. package/sprites/colorscripts/small/149-dragonite.txt +21 -0
  72. package/sprites/colorscripts/small/15-beedrill.txt +13 -0
  73. package/sprites/colorscripts/small/150-mewtwo.txt +22 -0
  74. package/sprites/colorscripts/small/151-mew.txt +14 -0
  75. package/sprites/colorscripts/small/16-pidgey.txt +10 -0
  76. package/sprites/colorscripts/small/17-pidgeotto.txt +11 -0
  77. package/sprites/colorscripts/small/18-pidgeot.txt +18 -0
  78. package/sprites/colorscripts/small/19-rattata.txt +12 -0
  79. package/sprites/colorscripts/small/2-ivysaur.txt +11 -0
  80. package/sprites/colorscripts/small/20-raticate.txt +12 -0
  81. package/sprites/colorscripts/small/21-spearow.txt +9 -0
  82. package/sprites/colorscripts/small/22-fearow.txt +12 -0
  83. package/sprites/colorscripts/small/23-ekans.txt +12 -0
  84. package/sprites/colorscripts/small/24-arbok.txt +16 -0
  85. package/sprites/colorscripts/small/25-pikachu.txt +11 -0
  86. package/sprites/colorscripts/small/26-raichu.txt +19 -0
  87. package/sprites/colorscripts/small/27-sandshrew.txt +10 -0
  88. package/sprites/colorscripts/small/28-sandslash.txt +16 -0
  89. package/sprites/colorscripts/small/29-nidoran-f.txt +11 -0
  90. package/sprites/colorscripts/small/3-venusaur.txt +21 -0
  91. package/sprites/colorscripts/small/30-nidorina.txt +12 -0
  92. package/sprites/colorscripts/small/31-nidoqueen.txt +19 -0
  93. package/sprites/colorscripts/small/32-nidoran-m.txt +11 -0
  94. package/sprites/colorscripts/small/33-nidorino.txt +12 -0
  95. package/sprites/colorscripts/small/34-nidoking.txt +18 -0
  96. package/sprites/colorscripts/small/35-clefairy.txt +11 -0
  97. package/sprites/colorscripts/small/36-clefable.txt +17 -0
  98. package/sprites/colorscripts/small/37-vulpix.txt +11 -0
  99. package/sprites/colorscripts/small/38-ninetales.txt +18 -0
  100. package/sprites/colorscripts/small/39-jigglypuff.txt +11 -0
  101. package/sprites/colorscripts/small/4-charmander.txt +11 -0
  102. package/sprites/colorscripts/small/40-wigglytuff.txt +20 -0
  103. package/sprites/colorscripts/small/41-zubat.txt +11 -0
  104. package/sprites/colorscripts/small/42-golbat.txt +18 -0
  105. package/sprites/colorscripts/small/43-oddish.txt +11 -0
  106. package/sprites/colorscripts/small/44-gloom.txt +12 -0
  107. package/sprites/colorscripts/small/45-vileplume.txt +17 -0
  108. package/sprites/colorscripts/small/46-paras.txt +11 -0
  109. package/sprites/colorscripts/small/47-parasect.txt +12 -0
  110. package/sprites/colorscripts/small/48-venonat.txt +14 -0
  111. package/sprites/colorscripts/small/49-venomoth.txt +19 -0
  112. package/sprites/colorscripts/small/5-charmeleon.txt +13 -0
  113. package/sprites/colorscripts/small/50-diglett.txt +8 -0
  114. package/sprites/colorscripts/small/51-dugtrio.txt +18 -0
  115. package/sprites/colorscripts/small/52-meowth.txt +12 -0
  116. package/sprites/colorscripts/small/53-persian.txt +20 -0
  117. package/sprites/colorscripts/small/54-psyduck.txt +12 -0
  118. package/sprites/colorscripts/small/55-golduck.txt +17 -0
  119. package/sprites/colorscripts/small/56-mankey.txt +11 -0
  120. package/sprites/colorscripts/small/57-primeape.txt +13 -0
  121. package/sprites/colorscripts/small/58-growlithe.txt +12 -0
  122. package/sprites/colorscripts/small/59-arcanine.txt +20 -0
  123. package/sprites/colorscripts/small/6-charizard.txt +21 -0
  124. package/sprites/colorscripts/small/60-poliwag.txt +9 -0
  125. package/sprites/colorscripts/small/61-poliwhirl.txt +11 -0
  126. package/sprites/colorscripts/small/62-poliwrath.txt +17 -0
  127. package/sprites/colorscripts/small/63-abra.txt +12 -0
  128. package/sprites/colorscripts/small/64-kadabra.txt +14 -0
  129. package/sprites/colorscripts/small/65-alakazam.txt +19 -0
  130. package/sprites/colorscripts/small/66-machop.txt +11 -0
  131. package/sprites/colorscripts/small/67-machoke.txt +12 -0
  132. package/sprites/colorscripts/small/68-machamp.txt +19 -0
  133. package/sprites/colorscripts/small/69-bellsprout.txt +9 -0
  134. package/sprites/colorscripts/small/7-squirtle.txt +10 -0
  135. package/sprites/colorscripts/small/70-weepinbell.txt +11 -0
  136. package/sprites/colorscripts/small/71-victreebel.txt +17 -0
  137. package/sprites/colorscripts/small/72-tentacool.txt +12 -0
  138. package/sprites/colorscripts/small/73-tentacruel.txt +20 -0
  139. package/sprites/colorscripts/small/74-geodude.txt +9 -0
  140. package/sprites/colorscripts/small/75-graveler.txt +12 -0
  141. package/sprites/colorscripts/small/76-golem.txt +18 -0
  142. package/sprites/colorscripts/small/77-ponyta.txt +13 -0
  143. package/sprites/colorscripts/small/78-rapidash.txt +18 -0
  144. package/sprites/colorscripts/small/79-slowpoke.txt +12 -0
  145. package/sprites/colorscripts/small/8-wartortle.txt +12 -0
  146. package/sprites/colorscripts/small/80-slowbro.txt +18 -0
  147. package/sprites/colorscripts/small/81-magnemite.txt +9 -0
  148. package/sprites/colorscripts/small/82-magneton.txt +18 -0
  149. package/sprites/colorscripts/small/83-farfetchd.txt +12 -0
  150. package/sprites/colorscripts/small/84-doduo.txt +10 -0
  151. package/sprites/colorscripts/small/85-dodrio.txt +17 -0
  152. package/sprites/colorscripts/small/86-seel.txt +13 -0
  153. package/sprites/colorscripts/small/87-dewgong.txt +20 -0
  154. package/sprites/colorscripts/small/88-grimer.txt +10 -0
  155. package/sprites/colorscripts/small/89-muk.txt +14 -0
  156. package/sprites/colorscripts/small/9-blastoise.txt +20 -0
  157. package/sprites/colorscripts/small/90-shellder.txt +10 -0
  158. package/sprites/colorscripts/small/91-cloyster.txt +18 -0
  159. package/sprites/colorscripts/small/92-gastly.txt +12 -0
  160. package/sprites/colorscripts/small/93-haunter.txt +14 -0
  161. package/sprites/colorscripts/small/94-gengar.txt +19 -0
  162. package/sprites/colorscripts/small/95-onix.txt +22 -0
  163. package/sprites/colorscripts/small/96-drowzee.txt +12 -0
  164. package/sprites/colorscripts/small/97-hypno.txt +19 -0
  165. package/sprites/colorscripts/small/98-krabby.txt +12 -0
  166. package/sprites/colorscripts/small/99-kingler.txt +20 -0
  167. package/src/engine/constants.ts +121 -0
  168. package/src/engine/encounter-pool.ts +71 -0
  169. package/src/engine/encounters.ts +308 -0
  170. package/src/engine/evolution-data.ts +535 -0
  171. package/src/engine/evolution.ts +310 -0
  172. package/src/engine/pokemon-data.ts +1838 -0
  173. package/src/engine/reactions.ts +877 -0
  174. package/src/engine/starter-pool.ts +47 -0
  175. package/src/engine/stats.ts +97 -0
  176. package/src/engine/types.ts +312 -0
  177. package/src/engine/xp.ts +135 -0
  178. package/src/gamification/achievements.ts +204 -0
  179. package/src/gamification/legendary-quests.ts +265 -0
  180. package/src/gamification/milestones.ts +86 -0
  181. package/src/hooks/award-xp.ts +131 -0
  182. package/src/hooks/increment-counter.ts +27 -0
  183. package/src/server/index.ts +78 -0
  184. package/src/server/instructions.ts +194 -0
  185. package/src/server/tools/achievements.ts +118 -0
  186. package/src/server/tools/catch.ts +295 -0
  187. package/src/server/tools/display-helpers.ts +35 -0
  188. package/src/server/tools/evolve.ts +236 -0
  189. package/src/server/tools/legendary.ts +78 -0
  190. package/src/server/tools/party.ts +251 -0
  191. package/src/server/tools/pet.ts +124 -0
  192. package/src/server/tools/pokedex.ts +286 -0
  193. package/src/server/tools/rename.ts +63 -0
  194. package/src/server/tools/show.ts +136 -0
  195. package/src/server/tools/starter.ts +175 -0
  196. package/src/server/tools/stats.ts +123 -0
  197. package/src/server/tools/visibility.ts +65 -0
  198. package/src/sprites/index.ts +45 -0
  199. package/src/state/io.ts +91 -0
  200. package/src/state/schemas.ts +131 -0
  201. package/src/state/state-manager.ts +321 -0
  202. package/statusline/buddy-status.sh +233 -0
  203. 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
@@ -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
@@ -0,0 +1,2 @@
1
+ [test]
2
+ root = "."
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
+ }