@umang-boss/claudemon 1.1.3 → 1.2.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 +3 -3
- package/dist/src/server/tools/starter.js +50 -12
- package/package.json +6 -3
- package/src/server/tools/starter.ts +53 -12
- package/statusline/buddy-status.sh +47 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Claudemon
|
|
2
2
|
|
|
3
|
-
> Pokemon
|
|
3
|
+
> Pokemon coding companion for Claude Code -- Gotta code 'em all!
|
|
4
4
|
|
|
5
5
|
## What is Claudemon?
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ fill your Pokedex -- all while you code.
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **151
|
|
13
|
+
- **151 Pokemon** with authentic base stats and evolution chains
|
|
14
14
|
- **XP from coding** -- commits, tests, builds, edits, and more
|
|
15
15
|
- **Level up & evolve** -- Charmander -> Charmeleon -> Charizard
|
|
16
16
|
- **Wild encounters** -- Pokemon appear based on your coding activity
|
|
@@ -90,7 +90,7 @@ Claude Code -> MCP Server (Claudemon)
|
|
|
90
90
|
|
|
91
91
|
### Evolution
|
|
92
92
|
|
|
93
|
-
Pokemon evolve at the same levels as the original
|
|
93
|
+
Pokemon evolve at the same levels as the original games:
|
|
94
94
|
- **Level-based:** Charmander -> Charmeleon (L16) -> Charizard (L36)
|
|
95
95
|
- **Badge-based:** Pikachu -> Raichu (Spark Badge -- 200 commits)
|
|
96
96
|
- **Collaboration:** Kadabra -> Alakazam (10 PRs merged)
|
|
@@ -30,19 +30,56 @@ function seededShuffle(array, seed) {
|
|
|
30
30
|
}
|
|
31
31
|
return result;
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
const year = now.getFullYear();
|
|
37
|
-
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
38
|
-
const day = String(now.getDate()).padStart(2, "0");
|
|
39
|
-
return `${year}-${month}-${day}`;
|
|
33
|
+
/** Path to stored starter options */
|
|
34
|
+
function getStarterOptionsPath() {
|
|
35
|
+
return `${process.env["HOME"] ?? "~"}/.claudemon/starter-options.json`;
|
|
40
36
|
}
|
|
41
|
-
/**
|
|
42
|
-
function
|
|
43
|
-
const
|
|
37
|
+
/** Get or generate 3 random starters. Stored for 24 hours so user sees same 3. */
|
|
38
|
+
async function getStarters() {
|
|
39
|
+
const optionsPath = getStarterOptionsPath();
|
|
40
|
+
// Try to load previously generated options (valid for 24 hours)
|
|
41
|
+
try {
|
|
42
|
+
const { readFile, stat, access: fsAccess } = await import("node:fs/promises");
|
|
43
|
+
const { constants } = await import("node:fs");
|
|
44
|
+
await fsAccess(optionsPath, constants.F_OK);
|
|
45
|
+
const fileStat = await stat(optionsPath);
|
|
46
|
+
const ageMs = Date.now() - fileStat.mtimeMs;
|
|
47
|
+
const ONE_DAY = 24 * 60 * 60 * 1000;
|
|
48
|
+
if (ageMs < ONE_DAY) {
|
|
49
|
+
const text = await readFile(optionsPath, "utf-8");
|
|
50
|
+
const saved = JSON.parse(text);
|
|
51
|
+
if (Array.isArray(saved) && saved.length === 3) {
|
|
52
|
+
return saved;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// No saved options or expired — generate new ones
|
|
58
|
+
}
|
|
59
|
+
// Generate fresh random 3
|
|
60
|
+
const seed = hashString(`claudemon-${Date.now()}-${Math.random()}`);
|
|
44
61
|
const shuffled = seededShuffle(STARTER_POOL, seed);
|
|
45
|
-
|
|
62
|
+
const starters = [shuffled[0], shuffled[1], shuffled[2]];
|
|
63
|
+
// Save for consistency until they pick
|
|
64
|
+
try {
|
|
65
|
+
const { writeFile, mkdir } = await import("node:fs/promises");
|
|
66
|
+
await mkdir(`${process.env["HOME"] ?? "~"}/.claudemon`, { recursive: true });
|
|
67
|
+
await writeFile(optionsPath, JSON.stringify(starters), "utf-8");
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Non-critical — they'll just get new options next time
|
|
71
|
+
}
|
|
72
|
+
return starters;
|
|
73
|
+
}
|
|
74
|
+
/** Clear saved starter options (called after picking) */
|
|
75
|
+
async function clearStarterOptions() {
|
|
76
|
+
try {
|
|
77
|
+
const { unlink } = await import("node:fs/promises");
|
|
78
|
+
await unlink(getStarterOptionsPath());
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Ignore
|
|
82
|
+
}
|
|
46
83
|
}
|
|
47
84
|
/** Generate a UUID v4. */
|
|
48
85
|
function generateUUID() {
|
|
@@ -65,7 +102,7 @@ export function registerStarterTool(server) {
|
|
|
65
102
|
isError: true,
|
|
66
103
|
};
|
|
67
104
|
}
|
|
68
|
-
const starterIds =
|
|
105
|
+
const starterIds = await getStarters();
|
|
69
106
|
// No choice provided — show the 3 options
|
|
70
107
|
if (params.choice === undefined) {
|
|
71
108
|
const lines = ["Welcome to Claudemon! Choose your coding companion:", ""];
|
|
@@ -118,6 +155,7 @@ export function registerStarterTool(server) {
|
|
|
118
155
|
const trainerName = "Trainer";
|
|
119
156
|
await stateManager.initializePlayer(trainerId, trainerName, starter);
|
|
120
157
|
await stateManager.writeStatus();
|
|
158
|
+
await clearStarterOptions();
|
|
121
159
|
const lines = [];
|
|
122
160
|
lines.push(`Congratulations! You chose **${species.name}**!`, "", ` Species: ${species.name} (#${String(species.id).padStart(3, "0")})`, ` Type: ${formatTypes(species.types)}`, ` Level: ${STARTER_LEVEL}`, ` Exp Group: ${species.expGroup}`, "", ` "${species.description}"`, "", `Your coding journey begins now. Write code, fix bugs, and watch ${species.name} grow!`, "", "Use buddy_show to see your Pokemon, or buddy_pet to bond with them.");
|
|
123
161
|
return {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umang-boss/claudemon",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Pokemon
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Pokemon coding companion for Claude Code — Gotta code 'em all!",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/server/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"bunfig.toml"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
|
+
"prepare": "husky",
|
|
24
25
|
"build": "tsc -p tsconfig.build.json",
|
|
25
26
|
"prepublishOnly": "npm run build",
|
|
26
27
|
"server": "node dist/src/server/index.js",
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
"test": "bun test",
|
|
33
34
|
"typecheck": "tsc --noEmit",
|
|
34
35
|
"format": "prettier --write \"src/**/*.ts\" \"cli/**/*.ts\" \"scripts/**/*.ts\" \"tests/**/*.ts\" --ignore-unknown",
|
|
35
|
-
"format:check": "prettier --check \"src/**/*.ts\" \"cli/**/*.ts\" \"scripts/**/*.ts\" \"tests/**/*.ts\" --ignore-unknown"
|
|
36
|
+
"format:check": "prettier --check \"src/**/*.ts\" \"cli/**/*.ts\" \"scripts/**/*.ts\" \"tests/**/*.ts\" --ignore-unknown",
|
|
37
|
+
"prepare": "husky"
|
|
36
38
|
},
|
|
37
39
|
"dependencies": {
|
|
38
40
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
@@ -40,6 +42,7 @@
|
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@types/bun": "latest",
|
|
45
|
+
"husky": "^9.1.7",
|
|
43
46
|
"prettier": "^3.8.2",
|
|
44
47
|
"typescript": "^5.8.3"
|
|
45
48
|
},
|
|
@@ -36,20 +36,60 @@ function seededShuffle<T>(array: readonly T[], seed: number): T[] {
|
|
|
36
36
|
return result;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
const year = now.getFullYear();
|
|
43
|
-
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
44
|
-
const day = String(now.getDate()).padStart(2, "0");
|
|
45
|
-
return `${year}-${month}-${day}`;
|
|
39
|
+
/** Path to stored starter options */
|
|
40
|
+
function getStarterOptionsPath(): string {
|
|
41
|
+
return `${process.env["HOME"] ?? "~"}/.claudemon/starter-options.json`;
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
/**
|
|
49
|
-
function
|
|
50
|
-
const
|
|
44
|
+
/** Get or generate 3 random starters. Stored for 24 hours so user sees same 3. */
|
|
45
|
+
async function getStarters(): Promise<number[]> {
|
|
46
|
+
const optionsPath = getStarterOptionsPath();
|
|
47
|
+
|
|
48
|
+
// Try to load previously generated options (valid for 24 hours)
|
|
49
|
+
try {
|
|
50
|
+
const { readFile, stat, access: fsAccess } = await import("node:fs/promises");
|
|
51
|
+
const { constants } = await import("node:fs");
|
|
52
|
+
await fsAccess(optionsPath, constants.F_OK);
|
|
53
|
+
const fileStat = await stat(optionsPath);
|
|
54
|
+
const ageMs = Date.now() - fileStat.mtimeMs;
|
|
55
|
+
const ONE_DAY = 24 * 60 * 60 * 1000;
|
|
56
|
+
|
|
57
|
+
if (ageMs < ONE_DAY) {
|
|
58
|
+
const text = await readFile(optionsPath, "utf-8");
|
|
59
|
+
const saved = JSON.parse(text) as number[];
|
|
60
|
+
if (Array.isArray(saved) && saved.length === 3) {
|
|
61
|
+
return saved;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// No saved options or expired — generate new ones
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Generate fresh random 3
|
|
69
|
+
const seed = hashString(`claudemon-${Date.now()}-${Math.random()}`);
|
|
51
70
|
const shuffled = seededShuffle(STARTER_POOL, seed);
|
|
52
|
-
|
|
71
|
+
const starters = [shuffled[0]!, shuffled[1]!, shuffled[2]!];
|
|
72
|
+
|
|
73
|
+
// Save for consistency until they pick
|
|
74
|
+
try {
|
|
75
|
+
const { writeFile, mkdir } = await import("node:fs/promises");
|
|
76
|
+
await mkdir(`${process.env["HOME"] ?? "~"}/.claudemon`, { recursive: true });
|
|
77
|
+
await writeFile(optionsPath, JSON.stringify(starters), "utf-8");
|
|
78
|
+
} catch {
|
|
79
|
+
// Non-critical — they'll just get new options next time
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return starters;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Clear saved starter options (called after picking) */
|
|
86
|
+
async function clearStarterOptions(): Promise<void> {
|
|
87
|
+
try {
|
|
88
|
+
const { unlink } = await import("node:fs/promises");
|
|
89
|
+
await unlink(getStarterOptionsPath());
|
|
90
|
+
} catch {
|
|
91
|
+
// Ignore
|
|
92
|
+
}
|
|
53
93
|
}
|
|
54
94
|
|
|
55
95
|
/** Generate a UUID v4. */
|
|
@@ -80,7 +120,7 @@ export function registerStarterTool(server: McpServer): void {
|
|
|
80
120
|
};
|
|
81
121
|
}
|
|
82
122
|
|
|
83
|
-
const starterIds =
|
|
123
|
+
const starterIds = await getStarters();
|
|
84
124
|
|
|
85
125
|
// No choice provided — show the 3 options
|
|
86
126
|
if (params.choice === undefined) {
|
|
@@ -149,6 +189,7 @@ export function registerStarterTool(server: McpServer): void {
|
|
|
149
189
|
const trainerName = "Trainer";
|
|
150
190
|
await stateManager.initializePlayer(trainerId, trainerName, starter);
|
|
151
191
|
await stateManager.writeStatus();
|
|
192
|
+
await clearStarterOptions();
|
|
152
193
|
|
|
153
194
|
const lines: string[] = [];
|
|
154
195
|
|
|
@@ -159,7 +159,7 @@ elif [ -n "$REACTION" ]; then
|
|
|
159
159
|
SPEECH="$REACTION"
|
|
160
160
|
else
|
|
161
161
|
NOW=$(date +%s)
|
|
162
|
-
IDX=$(( (NOW / 30) %
|
|
162
|
+
IDX=$(( (NOW / 30) % 60 ))
|
|
163
163
|
SPEECHES=(
|
|
164
164
|
"*${NAME} looks at your code curiously*"
|
|
165
165
|
""
|
|
@@ -173,6 +173,52 @@ else
|
|
|
173
173
|
""
|
|
174
174
|
"*${NAME} bounces excitedly*"
|
|
175
175
|
""
|
|
176
|
+
"*${NAME} waits patiently*"
|
|
177
|
+
""
|
|
178
|
+
"*${NAME} tilts head at the screen*"
|
|
179
|
+
""
|
|
180
|
+
"*${NAME} chirps encouragingly*"
|
|
181
|
+
""
|
|
182
|
+
"*${NAME} peers at a variable name*"
|
|
183
|
+
""
|
|
184
|
+
"*${NAME} sniffs at a function*"
|
|
185
|
+
""
|
|
186
|
+
"*${NAME} sits on the keyboard*"
|
|
187
|
+
""
|
|
188
|
+
"*${NAME} chases the cursor*"
|
|
189
|
+
""
|
|
190
|
+
"*${NAME} judges your indentation*"
|
|
191
|
+
""
|
|
192
|
+
"*${NAME} found a semicolon!*"
|
|
193
|
+
""
|
|
194
|
+
"*${NAME} debugs alongside you*"
|
|
195
|
+
""
|
|
196
|
+
"*${NAME} spots a typo... maybe*"
|
|
197
|
+
""
|
|
198
|
+
"*${NAME} celebrates a clean build*"
|
|
199
|
+
""
|
|
200
|
+
"*${NAME} is impressed by that refactor*"
|
|
201
|
+
""
|
|
202
|
+
"*${NAME} blinks at the test results*"
|
|
203
|
+
""
|
|
204
|
+
"*${NAME} dreams of evolution*"
|
|
205
|
+
""
|
|
206
|
+
"*${NAME} wants to learn new moves*"
|
|
207
|
+
""
|
|
208
|
+
"*${NAME} is proud of your progress*"
|
|
209
|
+
""
|
|
210
|
+
"*${NAME} snoozes between commits*"
|
|
211
|
+
""
|
|
212
|
+
"*${NAME} practices its type moves*"
|
|
213
|
+
""
|
|
214
|
+
"*${NAME} stares at the linter output*"
|
|
215
|
+
""
|
|
216
|
+
"*${NAME} wonders about that TODO*"
|
|
217
|
+
""
|
|
218
|
+
"*${NAME} approves of that commit msg*"
|
|
219
|
+
""
|
|
220
|
+
"*${NAME} is ready for action!*"
|
|
221
|
+
""
|
|
176
222
|
)
|
|
177
223
|
SPEECH="${SPEECHES[$IDX]}"
|
|
178
224
|
fi
|