@umang-boss/claudemon 1.0.0 → 1.1.1
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 +81 -67
- package/bin/claudemon.js +38 -28
- package/cli/doctor.ts +26 -18
- package/cli/install.ts +8 -8
- package/cli/shared.ts +32 -13
- package/cli/uninstall.ts +8 -6
- package/cli/update.ts +13 -14
- package/dist/cli/doctor.js +280 -0
- package/dist/cli/index.js +40 -0
- package/dist/cli/install.js +190 -0
- package/dist/cli/shared.js +72 -0
- package/dist/cli/uninstall.js +120 -0
- package/dist/cli/update.js +257 -0
- package/dist/src/engine/constants.js +98 -0
- package/dist/src/engine/encounter-pool.js +48 -0
- package/dist/src/engine/encounters.js +242 -0
- package/dist/src/engine/evolution-data.js +454 -0
- package/dist/src/engine/evolution.js +238 -0
- package/dist/src/engine/pokemon-data.js +1751 -0
- package/dist/src/engine/reactions.js +834 -0
- package/dist/src/engine/starter-pool.js +46 -0
- package/dist/src/engine/stats.js +79 -0
- package/dist/src/engine/types.js +76 -0
- package/dist/src/engine/xp.js +108 -0
- package/dist/src/gamification/achievements.js +176 -0
- package/dist/src/gamification/legendary-quests.js +233 -0
- package/dist/src/gamification/milestones.js +65 -0
- package/dist/src/hooks/award-xp.js +109 -0
- package/dist/src/hooks/increment-counter.js +20 -0
- package/dist/src/server/index.js +67 -0
- package/dist/src/server/instructions.js +155 -0
- package/dist/src/server/tools/achievements.js +97 -0
- package/dist/src/server/tools/catch.js +250 -0
- package/dist/src/server/tools/display-helpers.js +27 -0
- package/dist/src/server/tools/evolve.js +189 -0
- package/dist/src/server/tools/legendary.js +58 -0
- package/dist/src/server/tools/party.js +206 -0
- package/dist/src/server/tools/pet.js +101 -0
- package/dist/src/server/tools/pokedex.js +235 -0
- package/dist/src/server/tools/rename.js +49 -0
- package/dist/src/server/tools/show.js +111 -0
- package/dist/src/server/tools/starter.js +127 -0
- package/dist/src/server/tools/stats.js +98 -0
- package/dist/src/server/tools/visibility.js +52 -0
- package/dist/src/sprites/index.js +43 -0
- package/dist/src/state/io.js +78 -0
- package/dist/src/state/schemas.js +97 -0
- package/dist/src/state/state-manager.js +272 -0
- package/hooks/post-tool-use.sh +20 -6
- package/package.json +12 -11
- package/src/sprites/index.ts +5 -2
- package/src/state/io.ts +12 -23
- package/src/state/state-manager.ts +12 -3
- package/statusline/buddy-status.sh +14 -4
- package/{tsconfig.json → tsconfig.build.json} +9 -15
package/README.md
CHANGED
|
@@ -15,41 +15,62 @@ fill your Pokedex -- all while you code.
|
|
|
15
15
|
- **Level up & evolve** -- Charmander -> Charmeleon -> Charizard
|
|
16
16
|
- **Wild encounters** -- Pokemon appear based on your coding activity
|
|
17
17
|
- **Catch 'em all** -- fill your 151-entry Pokedex
|
|
18
|
-
- **Achievements** --
|
|
18
|
+
- **Achievements** -- 17 milestones to unlock
|
|
19
19
|
- **Legendary quests** -- multi-step challenges for Articuno, Zapdos, Moltres, Mewtwo, Mew
|
|
20
20
|
- **Colored terminal sprites** -- hand-crafted pixel art in your terminal
|
|
21
|
+
- **Status line** -- sprite + name + model + buddy speech on the prompt line
|
|
21
22
|
- **Type personalities** -- 15 unique reaction styles
|
|
23
|
+
- **Nickname your Pokemon** -- give them custom names
|
|
22
24
|
|
|
23
|
-
##
|
|
25
|
+
## Install
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
|
|
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
|
|
28
|
+
npx @umang-boss/claudemon install
|
|
40
29
|
```
|
|
41
30
|
|
|
31
|
+
That's it! Start a new Claude Code session and type `/buddy`.
|
|
32
|
+
|
|
33
|
+
**Requirements:** Node.js 18+ (Bun optional, auto-detected for faster startup)
|
|
34
|
+
|
|
35
|
+
### Other CLI Commands
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx @umang-boss/claudemon doctor # Check installation health
|
|
39
|
+
npx @umang-boss/claudemon update # Re-register after updates
|
|
40
|
+
npx @umang-boss/claudemon uninstall # Remove (preserves save data)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Commands
|
|
44
|
+
|
|
45
|
+
Once installed, use `/buddy` in Claude Code:
|
|
46
|
+
|
|
47
|
+
| Command | What it does |
|
|
48
|
+
|---------|-------------|
|
|
49
|
+
| `/buddy` | Show your Pokemon |
|
|
50
|
+
| `/buddy pet` | Bond with your buddy (+XP, +happiness) |
|
|
51
|
+
| `/buddy stats` | Detailed stat breakdown |
|
|
52
|
+
| `/buddy rename Sparky` | Give a nickname |
|
|
53
|
+
| `/buddy rename` | Reset to species name |
|
|
54
|
+
| `/buddy evolve` | Check/trigger evolution |
|
|
55
|
+
| `/buddy catch` | Catch wild Pokemon |
|
|
56
|
+
| `/buddy catch confirm` | Throw a Pokeball! |
|
|
57
|
+
| `/buddy party` | View party (6 max) |
|
|
58
|
+
| `/buddy switch 2` | Switch active Pokemon |
|
|
59
|
+
| `/buddy pokedex` | Track your 151 collection |
|
|
60
|
+
| `/buddy achievements` | View progress |
|
|
61
|
+
| `/buddy legendary` | Legendary quest chains |
|
|
62
|
+
| `/buddy hide` | Hide sprite from status line |
|
|
63
|
+
| `/buddy unhide` | Show sprite |
|
|
64
|
+
|
|
42
65
|
## How It Works
|
|
43
66
|
|
|
44
67
|
Claudemon runs as an MCP (Model Context Protocol) server alongside Claude Code.
|
|
45
68
|
It uses hooks to detect your coding activity and award XP automatically.
|
|
46
69
|
|
|
47
|
-
### Architecture
|
|
48
|
-
|
|
49
70
|
```
|
|
50
71
|
Claude Code -> MCP Server (Claudemon)
|
|
51
72
|
-> Hooks (PostToolUse, Stop, UserPromptSubmit)
|
|
52
|
-
-> Status Line (name +
|
|
73
|
+
-> Status Line (sprite + name + model + speech)
|
|
53
74
|
-> /buddy Skill (slash commands)
|
|
54
75
|
```
|
|
55
76
|
|
|
@@ -70,10 +91,10 @@ Claude Code -> MCP Server (Claudemon)
|
|
|
70
91
|
### Evolution
|
|
71
92
|
|
|
72
93
|
Pokemon evolve at the same levels as the original Gen 1 games:
|
|
73
|
-
- Level-based
|
|
74
|
-
- Badge-based
|
|
75
|
-
- Collaboration
|
|
76
|
-
- Stat-based
|
|
94
|
+
- **Level-based:** Charmander -> Charmeleon (L16) -> Charizard (L36)
|
|
95
|
+
- **Badge-based:** Pikachu -> Raichu (Spark Badge -- 200 commits)
|
|
96
|
+
- **Collaboration:** Kadabra -> Alakazam (10 PRs merged)
|
|
97
|
+
- **Stat-based:** Eevee -> Flareon/Vaporeon/Jolteon (dominant coding stat)
|
|
77
98
|
|
|
78
99
|
### Badges
|
|
79
100
|
|
|
@@ -85,73 +106,66 @@ Pokemon evolve at the same levels as the original Gen 1 games:
|
|
|
85
106
|
| Lunar Badge | 30-day streak | Moon Stone evolutions |
|
|
86
107
|
| Growth Badge | Edit 500 files | Leaf Stone evolutions |
|
|
87
108
|
|
|
88
|
-
###
|
|
109
|
+
### Wild Encounters
|
|
89
110
|
|
|
90
|
-
|
|
111
|
+
As you code, wild Pokemon appear based on your activity:
|
|
112
|
+
- Fixing bugs -> Bug/Poison types
|
|
113
|
+
- Writing tests -> Fighting/Normal types
|
|
114
|
+
- Large refactors -> Psychic/Dragon types
|
|
115
|
+
- Build/compile -> Fire/Rock types
|
|
91
116
|
|
|
92
|
-
|
|
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 |
|
|
117
|
+
Encounters trigger roughly every 500 XP earned. Use `/buddy catch` to try catching them!
|
|
104
118
|
|
|
105
|
-
|
|
119
|
+
### Legendary Quests
|
|
106
120
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
121
|
+
5 multi-step quest chains for legendary Pokemon:
|
|
122
|
+
- **Articuno** -- The Ice Bird of Endurance (100-day streak)
|
|
123
|
+
- **Zapdos** -- The Thunder of Testing (1000 tests passed)
|
|
124
|
+
- **Moltres** -- The Flame of Debugging (500 bugs fixed)
|
|
125
|
+
- **Mewtwo** -- The Ultimate Creation (140 Pokedex entries)
|
|
126
|
+
- **Mew** -- The Myth (365-day coding streak)
|
|
127
|
+
|
|
128
|
+
## Status Line
|
|
110
129
|
|
|
111
|
-
|
|
112
|
-
bun run cli/uninstall.ts
|
|
130
|
+
The status line shows your Pokemon sprite on the right side of the input prompt:
|
|
113
131
|
|
|
114
|
-
# Diagnose installation issues
|
|
115
|
-
bun run cli/doctor.ts
|
|
116
132
|
```
|
|
133
|
+
*Pikachu hums softly* Pikachu Lv.10
|
|
134
|
+
Opus 4.6 [colored sprite]
|
|
135
|
+
[colored sprite]
|
|
136
|
+
[colored sprite]
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- Buddy speech rotates every 30 seconds (zero API cost -- hardcoded messages)
|
|
140
|
+
- Model name from Claude Code
|
|
141
|
+
- `/buddy hide` to toggle sprite visibility
|
|
117
142
|
|
|
118
143
|
## Development
|
|
119
144
|
|
|
120
145
|
```bash
|
|
121
|
-
#
|
|
122
|
-
|
|
146
|
+
# Clone and install
|
|
147
|
+
git clone https://github.com/umang-dabhi/claudemon.git
|
|
148
|
+
cd claudemon
|
|
149
|
+
bun install
|
|
123
150
|
|
|
124
|
-
# Run tests
|
|
151
|
+
# Run tests (309 tests)
|
|
125
152
|
bun test
|
|
126
153
|
|
|
127
154
|
# Type checking
|
|
128
155
|
bun run typecheck
|
|
129
156
|
|
|
130
|
-
# Format
|
|
157
|
+
# Format
|
|
131
158
|
bun run format
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Project Structure
|
|
135
159
|
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
160
|
+
# Build for npm (compiles TS to JS)
|
|
161
|
+
npm run build
|
|
149
162
|
```
|
|
150
163
|
|
|
151
164
|
## Requirements
|
|
152
165
|
|
|
153
|
-
- [Claude Code](https://claude.ai/code)
|
|
154
|
-
- [Bun](https://bun.sh)
|
|
166
|
+
- [Claude Code](https://claude.ai/code)
|
|
167
|
+
- Node.js 18+ (or [Bun](https://bun.sh) for faster startup)
|
|
168
|
+
- `jq` for status line (`sudo apt install jq` on Ubuntu)
|
|
155
169
|
|
|
156
170
|
## Disclaimer
|
|
157
171
|
|
package/bin/claudemon.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Claudemon CLI
|
|
4
|
-
*
|
|
3
|
+
* Claudemon CLI — works with Node.js (no Bun required for users).
|
|
4
|
+
* Routes to install/uninstall/update/doctor.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
@@ -10,43 +10,53 @@ import { resolve, dirname } from "node:path";
|
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
|
|
12
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const cliEntry = resolve(__dirname, "..", "cli", "index.ts");
|
|
14
13
|
const args = process.argv.slice(2);
|
|
15
14
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
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();
|
|
15
|
+
// Try compiled JS first (dist/), then fall back to TS with bun
|
|
16
|
+
const distCli = resolve(__dirname, "..", "dist", "cli", "index.js");
|
|
17
|
+
const srcCli = resolve(__dirname, "..", "cli", "index.ts");
|
|
34
18
|
|
|
35
|
-
if (
|
|
36
|
-
|
|
19
|
+
if (existsSync(distCli)) {
|
|
20
|
+
// Run compiled JS with node
|
|
21
|
+
const result = spawnSync("node", [distCli, ...args], {
|
|
37
22
|
stdio: "inherit",
|
|
38
23
|
env: process.env,
|
|
39
24
|
});
|
|
40
25
|
process.exit(result.status ?? 1);
|
|
41
26
|
} else {
|
|
27
|
+
// Fallback: try bun with source TS
|
|
28
|
+
const home = process.env.HOME || "";
|
|
29
|
+
const bunPaths = [`${home}/.bun/bin/bun`, "/usr/local/bin/bun", "/usr/bin/bun"];
|
|
30
|
+
let bunPath = null;
|
|
31
|
+
|
|
32
|
+
for (const p of bunPaths) {
|
|
33
|
+
if (existsSync(p)) {
|
|
34
|
+
bunPath = p;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!bunPath) {
|
|
40
|
+
const which = spawnSync("which", ["bun"], { stdio: "pipe" });
|
|
41
|
+
if (which.status === 0) bunPath = "bun";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (bunPath && existsSync(srcCli)) {
|
|
45
|
+
const result = spawnSync(bunPath, ["run", srcCli, ...args], {
|
|
46
|
+
stdio: "inherit",
|
|
47
|
+
env: process.env,
|
|
48
|
+
});
|
|
49
|
+
process.exit(result.status ?? 1);
|
|
50
|
+
}
|
|
51
|
+
|
|
42
52
|
console.error(`
|
|
43
|
-
|
|
53
|
+
Error: Could not find compiled files (dist/) or Bun runtime.
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
If you installed via npm, try reinstalling:
|
|
56
|
+
npm install -g @umang-boss/claudemon
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
Or install Bun and run from source:
|
|
59
|
+
curl -fsSL https://bun.sh/install | bash
|
|
50
60
|
`);
|
|
51
61
|
process.exit(1);
|
|
52
62
|
}
|
package/cli/doctor.ts
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* Usage: bun run cli/doctor.ts
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { access, stat, unlink, readdir } from "node:fs/promises";
|
|
8
|
+
import { access, stat, unlink, readdir, readFile } from "node:fs/promises";
|
|
9
9
|
import { constants as fsConstants } from "node:fs";
|
|
10
10
|
import { resolve, dirname } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
11
13
|
import { StateManager } from "../src/state/state-manager.js";
|
|
12
14
|
import { PlayerStateSchema } from "../src/state/schemas.js";
|
|
13
15
|
|
|
@@ -28,7 +30,9 @@ const STATE_FILE = `${STATE_DIR}/state.json`;
|
|
|
28
30
|
const LOCK_FILE = `${STATE_DIR}/state.lock`;
|
|
29
31
|
const LOCK_MAX_AGE_MS = 5000;
|
|
30
32
|
const EXPECTED_SPRITE_COUNT = 151;
|
|
31
|
-
const
|
|
33
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
34
|
+
const __dirname = dirname(__filename);
|
|
35
|
+
const COLORSCRIPT_DIR = resolve(dirname(__dirname), "sprites/colorscripts/small");
|
|
32
36
|
|
|
33
37
|
// ── Helpers ──────────────────────────────────────────────────
|
|
34
38
|
|
|
@@ -47,10 +51,10 @@ function formatCheck(result: CheckResult): string {
|
|
|
47
51
|
|
|
48
52
|
async function checkBun(): Promise<CheckResult> {
|
|
49
53
|
try {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return { label: "Bun runtime", passed: true, detail: `v${output
|
|
54
|
+
const result = spawnSync("bun", ["--version"], { stdio: "pipe" });
|
|
55
|
+
if (result.error) throw result.error;
|
|
56
|
+
const output = result.stdout?.toString().trim();
|
|
57
|
+
return { label: "Bun runtime", passed: true, detail: `v${output}` };
|
|
54
58
|
} catch {
|
|
55
59
|
return { label: "Bun runtime", passed: false, detail: "not found" };
|
|
56
60
|
}
|
|
@@ -74,13 +78,14 @@ async function checkStateDir(): Promise<CheckResult> {
|
|
|
74
78
|
// ── Check 3: State File ──────────────────────────────────────
|
|
75
79
|
|
|
76
80
|
async function checkStateFile(): Promise<CheckResult> {
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
try {
|
|
82
|
+
await access(STATE_FILE, fsConstants.F_OK);
|
|
83
|
+
} catch {
|
|
79
84
|
return { label: "State file", passed: false, detail: "not found (run /buddy starter first)" };
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
try {
|
|
83
|
-
const text = await
|
|
88
|
+
const text = await readFile(STATE_FILE, "utf-8");
|
|
84
89
|
const data = JSON.parse(text) as Record<string, unknown>;
|
|
85
90
|
|
|
86
91
|
// Check for active pokemon
|
|
@@ -150,11 +155,12 @@ async function checkHooks(): Promise<CheckResult> {
|
|
|
150
155
|
// ── Check 6: Skill ───────────────────────────────────────────
|
|
151
156
|
|
|
152
157
|
async function checkSkill(): Promise<CheckResult> {
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
try {
|
|
159
|
+
await access(SKILL_DEST, fsConstants.F_OK);
|
|
155
160
|
return { label: "Skill", passed: true, detail: "/buddy command installed" };
|
|
161
|
+
} catch {
|
|
162
|
+
return { label: "Skill", passed: false, detail: "~/.claude/skills/buddy/SKILL.md not found" };
|
|
156
163
|
}
|
|
157
|
-
return { label: "Skill", passed: false, detail: "~/.claude/skills/buddy/SKILL.md not found" };
|
|
158
164
|
}
|
|
159
165
|
|
|
160
166
|
// ── Check 7: Hook Script Executable ──────────────────────────
|
|
@@ -164,15 +170,16 @@ async function checkHookScript(): Promise<CheckResult> {
|
|
|
164
170
|
await access(HOOK_SCRIPT, fsConstants.X_OK);
|
|
165
171
|
return { label: "Hook script", passed: true, detail: "executable" };
|
|
166
172
|
} catch {
|
|
167
|
-
|
|
168
|
-
|
|
173
|
+
try {
|
|
174
|
+
await access(HOOK_SCRIPT, fsConstants.F_OK);
|
|
169
175
|
return {
|
|
170
176
|
label: "Hook script",
|
|
171
177
|
passed: false,
|
|
172
178
|
detail: "exists but not executable (run chmod +x)",
|
|
173
179
|
};
|
|
180
|
+
} catch {
|
|
181
|
+
return { label: "Hook script", passed: false, detail: "not found" };
|
|
174
182
|
}
|
|
175
|
-
return { label: "Hook script", passed: false, detail: "not found" };
|
|
176
183
|
}
|
|
177
184
|
}
|
|
178
185
|
|
|
@@ -255,13 +262,14 @@ async function checkSpriteCount(): Promise<CheckResult> {
|
|
|
255
262
|
// ── Check 11: State File Validity (Zod) ─────────────────────
|
|
256
263
|
|
|
257
264
|
async function checkStateValidity(): Promise<CheckResult> {
|
|
258
|
-
|
|
259
|
-
|
|
265
|
+
try {
|
|
266
|
+
await access(STATE_FILE, fsConstants.F_OK);
|
|
267
|
+
} catch {
|
|
260
268
|
return { label: "State validity", passed: true, detail: "no state file yet" };
|
|
261
269
|
}
|
|
262
270
|
|
|
263
271
|
try {
|
|
264
|
-
const text = await
|
|
272
|
+
const text = await readFile(STATE_FILE, "utf-8");
|
|
265
273
|
if (!text.trim()) {
|
|
266
274
|
return { label: "State validity", passed: false, detail: "state file is empty" };
|
|
267
275
|
}
|
package/cli/install.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { mkdir, copyFile, chmod, access } from "node:fs/promises";
|
|
9
9
|
import { constants as fsConstants } from "node:fs";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
10
11
|
|
|
11
12
|
import type { ClaudeConfig, ClaudeSettings, HookCommand, HookMatcher } from "./shared.js";
|
|
12
13
|
import {
|
|
@@ -25,8 +26,7 @@ import {
|
|
|
25
26
|
STOP_HOOK_SCRIPT,
|
|
26
27
|
USER_PROMPT_HOOK_SCRIPT,
|
|
27
28
|
STATUSLINE_SCRIPT,
|
|
28
|
-
|
|
29
|
-
getBunPath,
|
|
29
|
+
getRuntime,
|
|
30
30
|
} from "./shared.js";
|
|
31
31
|
|
|
32
32
|
// ── Step 1: Check Prerequisites ──────────────────────────────
|
|
@@ -36,10 +36,10 @@ async function checkPrerequisites(): Promise<boolean> {
|
|
|
36
36
|
|
|
37
37
|
// Check bun (sanity)
|
|
38
38
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
ok(`Bun runtime: v${output
|
|
39
|
+
const result = spawnSync("bun", ["--version"], { stdio: "pipe" });
|
|
40
|
+
if (result.error) throw result.error;
|
|
41
|
+
const output = result.stdout?.toString().trim();
|
|
42
|
+
ok(`Bun runtime: v${output}`);
|
|
43
43
|
} catch {
|
|
44
44
|
fail("Bun runtime not found. Install from https://bun.sh");
|
|
45
45
|
allGood = false;
|
|
@@ -76,8 +76,8 @@ async function registerMcpServer(): Promise<void> {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
config.mcpServers["claudemon"] = {
|
|
79
|
-
command:
|
|
80
|
-
args: [
|
|
79
|
+
command: getRuntime().command,
|
|
80
|
+
args: [getRuntime().serverEntry],
|
|
81
81
|
env: {},
|
|
82
82
|
};
|
|
83
83
|
|
package/cli/shared.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { resolve, dirname } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
8
9
|
// ── Config shape interfaces ──────────────────────────────────
|
|
9
10
|
|
|
@@ -50,7 +51,9 @@ if (!HOME) {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export const CLI_HOME = HOME;
|
|
53
|
-
|
|
54
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
55
|
+
const __dirname = dirname(__filename);
|
|
56
|
+
export const PROJECT_DIR = resolve(dirname(__dirname));
|
|
54
57
|
export const CLAUDE_DIR = `${HOME}/.claude`;
|
|
55
58
|
export const CLAUDE_CONFIG = `${HOME}/.claude.json`;
|
|
56
59
|
export const CLAUDE_SETTINGS = `${CLAUDE_DIR}/settings.json`;
|
|
@@ -62,16 +65,28 @@ export const HOOK_SCRIPT = `${PROJECT_DIR}/hooks/post-tool-use.sh`;
|
|
|
62
65
|
export const STOP_HOOK_SCRIPT = `${PROJECT_DIR}/hooks/stop.sh`;
|
|
63
66
|
export const USER_PROMPT_HOOK_SCRIPT = `${PROJECT_DIR}/hooks/user-prompt-submit.sh`;
|
|
64
67
|
export const STATUSLINE_SCRIPT = `${PROJECT_DIR}/statusline/buddy-status.sh`;
|
|
65
|
-
export const
|
|
68
|
+
export const SERVER_ENTRY_TS = `${PROJECT_DIR}/src/server/index.ts`;
|
|
69
|
+
export const SERVER_ENTRY_JS = `${PROJECT_DIR}/dist/src/server/index.js`;
|
|
66
70
|
|
|
67
|
-
/**
|
|
68
|
-
export function
|
|
71
|
+
/** Find the best runtime and server entry — prefers bun (fast), falls back to node (compiled JS) */
|
|
72
|
+
export function getRuntime(): { command: string; serverEntry: string } {
|
|
69
73
|
const { existsSync } = require("node:fs") as { existsSync: (p: string) => boolean };
|
|
70
|
-
|
|
71
|
-
for (
|
|
72
|
-
|
|
74
|
+
|
|
75
|
+
// Check for bun (can run .ts directly)
|
|
76
|
+
const bunCandidates = [`${HOME}/.bun/bin/bun`, "/usr/local/bin/bun", "/usr/bin/bun"];
|
|
77
|
+
for (const p of bunCandidates) {
|
|
78
|
+
if (existsSync(p)) {
|
|
79
|
+
return { command: p, serverEntry: SERVER_ENTRY_TS };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Fall back to node (needs compiled .js from dist/)
|
|
84
|
+
if (existsSync(SERVER_ENTRY_JS)) {
|
|
85
|
+
return { command: "node", serverEntry: SERVER_ENTRY_JS };
|
|
73
86
|
}
|
|
74
|
-
|
|
87
|
+
|
|
88
|
+
// Last resort: assume bun in PATH
|
|
89
|
+
return { command: "bun", serverEntry: SERVER_ENTRY_TS };
|
|
75
90
|
}
|
|
76
91
|
|
|
77
92
|
// ── Helpers ──────────────────────────────────────────────────
|
|
@@ -89,14 +104,18 @@ export function info(msg: string): void {
|
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
export async function readJson<T>(path: string): Promise<T | null> {
|
|
92
|
-
const
|
|
93
|
-
|
|
107
|
+
const { readFile, access } = await import("node:fs/promises");
|
|
108
|
+
const { constants } = await import("node:fs");
|
|
109
|
+
try {
|
|
110
|
+
await access(path, constants.F_OK);
|
|
111
|
+
const text = await readFile(path, "utf-8");
|
|
112
|
+
return JSON.parse(text) as T;
|
|
113
|
+
} catch {
|
|
94
114
|
return null;
|
|
95
115
|
}
|
|
96
|
-
const text = await file.text();
|
|
97
|
-
return JSON.parse(text) as T;
|
|
98
116
|
}
|
|
99
117
|
|
|
100
118
|
export async function writeJson(path: string, data: unknown): Promise<void> {
|
|
101
|
-
|
|
119
|
+
const { writeFile } = await import("node:fs/promises");
|
|
120
|
+
await writeFile(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
102
121
|
}
|
package/cli/uninstall.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Usage: bun run cli/uninstall.ts
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { rm } from "node:fs/promises";
|
|
8
|
+
import { rm, access } from "node:fs/promises";
|
|
9
|
+
import { constants as fsConstants } from "node:fs";
|
|
9
10
|
|
|
10
11
|
import type { ClaudeConfig, ClaudeSettings } from "./shared.js";
|
|
11
12
|
import {
|
|
@@ -106,8 +107,9 @@ async function removeHooks(): Promise<void> {
|
|
|
106
107
|
// ── Step 3: Remove Skill ─────────────────────────────────────
|
|
107
108
|
|
|
108
109
|
async function removeSkill(): Promise<void> {
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
try {
|
|
111
|
+
await access(`${SKILL_DEST_DIR}/SKILL.md`, fsConstants.F_OK);
|
|
112
|
+
} catch {
|
|
111
113
|
info("Skill: ~/.claude/skills/buddy/ not found (nothing to remove)");
|
|
112
114
|
return;
|
|
113
115
|
}
|
|
@@ -119,10 +121,10 @@ async function removeSkill(): Promise<void> {
|
|
|
119
121
|
// ── Step 4: Preserve State Data ──────────────────────────────
|
|
120
122
|
|
|
121
123
|
async function preserveStateData(): Promise<void> {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
try {
|
|
125
|
+
await access(`${STATE_DIR}/state.json`, fsConstants.F_OK);
|
|
124
126
|
info(`Your Pokemon data is preserved at ${STATE_DIR}/. Delete manually to remove.`);
|
|
125
|
-
}
|
|
127
|
+
} catch {
|
|
126
128
|
info(`State directory: ${STATE_DIR}/ (kept, no state file found)`);
|
|
127
129
|
}
|
|
128
130
|
}
|
package/cli/update.ts
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { mkdir, copyFile, chmod, access } from "node:fs/promises";
|
|
13
|
-
import { constants as fsConstants } from "node:fs";
|
|
13
|
+
import { constants as fsConstants, existsSync, readdirSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
|
-
import {
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
16
|
|
|
17
17
|
import type { ClaudeConfig, ClaudeSettings, HookCommand, HookMatcher } from "./shared.js";
|
|
18
18
|
import {
|
|
@@ -32,9 +32,8 @@ import {
|
|
|
32
32
|
STOP_HOOK_SCRIPT,
|
|
33
33
|
USER_PROMPT_HOOK_SCRIPT,
|
|
34
34
|
STATUSLINE_SCRIPT,
|
|
35
|
-
SERVER_ENTRY,
|
|
36
35
|
PROJECT_DIR,
|
|
37
|
-
|
|
36
|
+
getRuntime,
|
|
38
37
|
} from "./shared.js";
|
|
39
38
|
|
|
40
39
|
// ── Step 1: Check Prerequisites ──────────────────────────────
|
|
@@ -43,10 +42,10 @@ async function checkPrerequisites(): Promise<boolean> {
|
|
|
43
42
|
let allGood = true;
|
|
44
43
|
|
|
45
44
|
try {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
ok(`Bun runtime: v${output
|
|
45
|
+
const result = spawnSync("bun", ["--version"], { stdio: "pipe" });
|
|
46
|
+
if (result.error) throw result.error;
|
|
47
|
+
const output = result.stdout?.toString().trim();
|
|
48
|
+
ok(`Bun runtime: v${output}`);
|
|
50
49
|
} catch {
|
|
51
50
|
fail("Bun runtime not found.");
|
|
52
51
|
allGood = false;
|
|
@@ -122,8 +121,8 @@ async function registerMcpServer(): Promise<void> {
|
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
config.mcpServers["claudemon"] = {
|
|
125
|
-
command:
|
|
126
|
-
args: [
|
|
124
|
+
command: getRuntime().command,
|
|
125
|
+
args: [getRuntime().serverEntry],
|
|
127
126
|
env: {},
|
|
128
127
|
};
|
|
129
128
|
|
|
@@ -240,7 +239,7 @@ async function checkColorscripts(): Promise<{ missing: number; total: number }>
|
|
|
240
239
|
}
|
|
241
240
|
|
|
242
241
|
// Check for files matching the pattern
|
|
243
|
-
const files =
|
|
242
|
+
const files = readdirSync(smallDir).filter((f) => f.startsWith(prefix) && f.endsWith(".txt"));
|
|
244
243
|
if (files.length === 0) {
|
|
245
244
|
missing++;
|
|
246
245
|
}
|
|
@@ -252,10 +251,10 @@ async function checkColorscripts(): Promise<{ missing: number; total: number }>
|
|
|
252
251
|
// ── Step 5: State Preservation Check ─────────────────────────
|
|
253
252
|
|
|
254
253
|
async function checkStatePreserved(): Promise<void> {
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
try {
|
|
255
|
+
await access(`${STATE_DIR}/state.json`, fsConstants.F_OK);
|
|
257
256
|
ok("State data: preserved (not modified)");
|
|
258
|
-
}
|
|
257
|
+
} catch {
|
|
259
258
|
info("State data: no save file found (run /buddy starter to begin)");
|
|
260
259
|
}
|
|
261
260
|
}
|