@umang-boss/claudemon 1.4.0 → 1.4.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/cli/doctor.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { access, stat, unlink, readdir, readFile } from "node:fs/promises";
9
- import { constants as fsConstants } from "node:fs";
9
+ import { constants as fsConstants, existsSync } from "node:fs";
10
10
  import { resolve, dirname } from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { spawnSync } from "node:child_process";
@@ -32,7 +32,20 @@ const LOCK_MAX_AGE_MS = 5000;
32
32
  const EXPECTED_SPRITE_COUNT = 151;
33
33
  const __filename = fileURLToPath(import.meta.url);
34
34
  const __dirname = dirname(__filename);
35
- const COLORSCRIPT_DIR = resolve(dirname(__dirname), "sprites/colorscripts/small");
35
+ // Sprite dir: check multiple candidate paths (works from source, dist, and npm global)
36
+ function findColorscriptDir(): string | null {
37
+ const candidates = [
38
+ resolve(dirname(__dirname), "sprites", "colorscripts", "small"),
39
+ resolve(dirname(__dirname), "..", "sprites", "colorscripts", "small"),
40
+ resolve(__dirname, "..", "sprites", "colorscripts", "small"),
41
+ resolve(__dirname, "..", "..", "sprites", "colorscripts", "small"),
42
+ ];
43
+ for (const c of candidates) {
44
+ if (existsSync(c)) return c;
45
+ }
46
+ return null;
47
+ }
48
+ const COLORSCRIPT_DIR = findColorscriptDir();
36
49
 
37
50
  // ── Helpers ──────────────────────────────────────────────────
38
51
 
@@ -236,6 +249,9 @@ async function checkStaleLock(): Promise<CheckResult> {
236
249
 
237
250
  async function checkSpriteCount(): Promise<CheckResult> {
238
251
  try {
252
+ if (!COLORSCRIPT_DIR) {
253
+ return { label: "Sprites", passed: false, detail: "colorscripts directory not found" };
254
+ }
239
255
  await access(COLORSCRIPT_DIR, fsConstants.F_OK);
240
256
  const entries = await readdir(COLORSCRIPT_DIR);
241
257
  const spriteFiles = entries.filter((f) => f.endsWith(".txt"));
package/cli/index.ts CHANGED
@@ -1,19 +1,36 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
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
4
+ * Routes to install, uninstall, update, doctor, or --version.
11
5
  */
12
6
 
7
+ import { readFileSync } from "node:fs";
8
+ import { resolve, dirname } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
13
11
  export {};
14
12
  const command = process.argv[2];
15
13
 
16
14
  switch (command) {
15
+ case "--version":
16
+ case "-v":
17
+ case "version": {
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const pkgPaths = [
20
+ resolve(__dirname, "..", "package.json"),
21
+ resolve(__dirname, "..", "..", "package.json"),
22
+ ];
23
+ for (const p of pkgPaths) {
24
+ try {
25
+ const pkg = JSON.parse(readFileSync(p, "utf-8")) as { version: string };
26
+ console.log(`claudemon v${pkg.version}`);
27
+ break;
28
+ } catch {
29
+ continue;
30
+ }
31
+ }
32
+ break;
33
+ }
17
34
  case "install":
18
35
  await import("./install.js");
19
36
  break;
@@ -28,13 +45,14 @@ switch (command) {
28
45
  break;
29
46
  default:
30
47
  console.log(`
31
- Claudemon — Pokemon Gen 1 coding companion for Claude Code
48
+ Claudemon — Pokemon coding companion for Claude Code
32
49
 
33
50
  Usage:
34
51
  claudemon install Set up Claudemon (MCP server, hooks, skill, status line)
35
52
  claudemon uninstall Remove Claudemon from Claude Code
36
53
  claudemon update Re-register everything (preserves save data)
37
54
  claudemon doctor Run diagnostics
55
+ claudemon --version Show version
38
56
 
39
57
  After install, start a new Claude Code session and type /buddy
40
58
  `);
@@ -5,7 +5,7 @@
5
5
  * Usage: bun run cli/doctor.ts
6
6
  */
7
7
  import { access, stat, unlink, readdir, readFile } from "node:fs/promises";
8
- import { constants as fsConstants } from "node:fs";
8
+ import { constants as fsConstants, existsSync } from "node:fs";
9
9
  import { resolve, dirname } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { spawnSync } from "node:child_process";
@@ -19,7 +19,21 @@ const LOCK_MAX_AGE_MS = 5000;
19
19
  const EXPECTED_SPRITE_COUNT = 151;
20
20
  const __filename = fileURLToPath(import.meta.url);
21
21
  const __dirname = dirname(__filename);
22
- const COLORSCRIPT_DIR = resolve(dirname(__dirname), "sprites/colorscripts/small");
22
+ // Sprite dir: check multiple candidate paths (works from source, dist, and npm global)
23
+ function findColorscriptDir() {
24
+ const candidates = [
25
+ resolve(dirname(__dirname), "sprites", "colorscripts", "small"),
26
+ resolve(dirname(__dirname), "..", "sprites", "colorscripts", "small"),
27
+ resolve(__dirname, "..", "sprites", "colorscripts", "small"),
28
+ resolve(__dirname, "..", "..", "sprites", "colorscripts", "small"),
29
+ ];
30
+ for (const c of candidates) {
31
+ if (existsSync(c))
32
+ return c;
33
+ }
34
+ return null;
35
+ }
36
+ const COLORSCRIPT_DIR = findColorscriptDir();
23
37
  function formatCheck(result) {
24
38
  const icon = result.passed ? "\u2713" : "\u2717";
25
39
  return `[${icon}] ${result.label}: ${result.detail}`;
@@ -188,6 +202,9 @@ async function checkStaleLock() {
188
202
  // ── Check 10: Sprite Count ──────────────────────────────────
189
203
  async function checkSpriteCount() {
190
204
  try {
205
+ if (!COLORSCRIPT_DIR) {
206
+ return { label: "Sprites", passed: false, detail: "colorscripts directory not found" };
207
+ }
191
208
  await access(COLORSCRIPT_DIR, fsConstants.F_OK);
192
209
  const entries = await readdir(COLORSCRIPT_DIR);
193
210
  const spriteFiles = entries.filter((f) => f.endsWith(".txt"));
package/dist/cli/index.js CHANGED
@@ -1,16 +1,33 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
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
4
+ * Routes to install, uninstall, update, doctor, or --version.
11
5
  */
6
+ import { readFileSync } from "node:fs";
7
+ import { resolve, dirname } from "node:path";
8
+ import { fileURLToPath } from "node:url";
12
9
  const command = process.argv[2];
13
10
  switch (command) {
11
+ case "--version":
12
+ case "-v":
13
+ case "version": {
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const pkgPaths = [
16
+ resolve(__dirname, "..", "package.json"),
17
+ resolve(__dirname, "..", "..", "package.json"),
18
+ ];
19
+ for (const p of pkgPaths) {
20
+ try {
21
+ const pkg = JSON.parse(readFileSync(p, "utf-8"));
22
+ console.log(`claudemon v${pkg.version}`);
23
+ break;
24
+ }
25
+ catch {
26
+ continue;
27
+ }
28
+ }
29
+ break;
30
+ }
14
31
  case "install":
15
32
  await import("./install.js");
16
33
  break;
@@ -25,16 +42,16 @@ switch (command) {
25
42
  break;
26
43
  default:
27
44
  console.log(`
28
- Claudemon — Pokemon Gen 1 coding companion for Claude Code
45
+ Claudemon — Pokemon coding companion for Claude Code
29
46
 
30
47
  Usage:
31
48
  claudemon install Set up Claudemon (MCP server, hooks, skill, status line)
32
49
  claudemon uninstall Remove Claudemon from Claude Code
33
50
  claudemon update Re-register everything (preserves save data)
34
51
  claudemon doctor Run diagnostics
52
+ claudemon --version Show version
35
53
 
36
54
  After install, start a new Claude Code session and type /buddy
37
55
  `);
38
56
  break;
39
57
  }
40
- export {};
@@ -44,6 +44,21 @@ const levelUp = addXp(pokemon, xpEvent.xp, pokemonData);
44
44
  if (xpEvent.statBoost && xpEvent.boostAmount > 0) {
45
45
  applyStatBoost(pokemon, xpEvent.statBoost, xpEvent.boostAmount);
46
46
  }
47
+ // XP sharing: give inactive party members a percentage
48
+ const sharePercent = state.config.xpSharePercent ?? 0;
49
+ if (sharePercent > 0 && state.party.length > 1) {
50
+ const sharedXp = Math.floor((xpEvent.xp * sharePercent) / 100);
51
+ if (sharedXp > 0) {
52
+ for (const member of state.party) {
53
+ if (member.isActive)
54
+ continue;
55
+ const memberSpecies = POKEMON_BY_ID.get(member.pokemonId);
56
+ if (memberSpecies) {
57
+ addXp(member, sharedXp, memberSpecies);
58
+ }
59
+ }
60
+ }
61
+ }
47
62
  // Track total XP earned by the trainer
48
63
  state.totalXpEarned += xpEvent.xp;
49
64
  // Increment the event counter directly on state (avoids extra save from incrementCounter)
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * buddy_settings tool — Configure Claudemon settings.
3
- * Currently supports: encounter-speed (fast | normal | slow).
3
+ * Supports: encounter-speed, xp-share.
4
4
  */
5
5
  import { z } from "zod";
6
6
  import { StateManager } from "../../state/state-manager.js";
7
7
  import { ENCOUNTER_THRESHOLDS } from "../../engine/constants.js";
8
8
  const VALID_ENCOUNTER_SPEEDS = ["fast", "normal", "slow"];
9
- /** Speed descriptions for user-facing messages. */
10
9
  const SPEED_DESCRIPTIONS = {
11
10
  fast: "Fastest encounters — wild Pokemon appear every ~100 XP",
12
11
  normal: "Default pace — wild Pokemon appear every ~250 XP",
@@ -14,9 +13,9 @@ const SPEED_DESCRIPTIONS = {
14
13
  };
15
14
  /** Registers the buddy_settings tool on the MCP server. */
16
15
  export function registerSettingsTool(server) {
17
- server.tool("buddy_settings", "Configure Claudemon settings (encounter speed, etc.)", {
18
- setting: z.enum(["encounter-speed"]).describe("The setting to configure"),
19
- value: z.string().describe("The value to set (for encounter-speed: fast, normal, or slow)"),
16
+ server.tool("buddy_settings", "Configure Claudemon settings (encounter-speed, xp-share)", {
17
+ setting: z.enum(["encounter-speed", "xp-share"]).describe("The setting to configure"),
18
+ value: z.string().describe("The value to set"),
20
19
  }, async (params) => {
21
20
  const stateManager = StateManager.getInstance();
22
21
  const state = await stateManager.load();
@@ -30,6 +29,7 @@ export function registerSettingsTool(server) {
30
29
  ],
31
30
  };
32
31
  }
32
+ // ── Encounter Speed ──────────────────────────────────
33
33
  if (params.setting === "encounter-speed") {
34
34
  const speed = params.value.toLowerCase();
35
35
  if (!VALID_ENCOUNTER_SPEEDS.includes(speed)) {
@@ -57,7 +57,7 @@ export function registerSettingsTool(server) {
57
57
  {
58
58
  type: "text",
59
59
  text: [
60
- `Encounter speed updated: ${previousSpeed} -> ${validSpeed}`,
60
+ `Encounter speed: ${previousSpeed} ${validSpeed}`,
61
61
  "",
62
62
  SPEED_DESCRIPTIONS[validSpeed],
63
63
  `XP threshold: ${ENCOUNTER_THRESHOLDS[validSpeed]}`,
@@ -66,12 +66,48 @@ export function registerSettingsTool(server) {
66
66
  ],
67
67
  };
68
68
  }
69
- // Unreachable with the current enum, but guards against future additions
69
+ // ── XP Share ─────────────────────────────────────────
70
+ if (params.setting === "xp-share") {
71
+ const percent = parseInt(params.value, 10);
72
+ if (isNaN(percent) || percent < 0 || percent > 100) {
73
+ return {
74
+ content: [
75
+ {
76
+ type: "text",
77
+ text: [
78
+ `Invalid XP share value: "${params.value}"`,
79
+ "",
80
+ "Enter a number 0-100 (percentage of XP shared to inactive party):",
81
+ " 0 — No XP sharing (only active Pokemon earns)",
82
+ " 25 — Default (inactive get 25% of earned XP)",
83
+ " 50 — Half XP shared to inactive party",
84
+ " 100 — Full XP to everyone",
85
+ ].join("\n"),
86
+ },
87
+ ],
88
+ isError: true,
89
+ };
90
+ }
91
+ const previous = state.config.xpSharePercent ?? 25;
92
+ state.config.xpSharePercent = percent;
93
+ await stateManager.save();
94
+ const desc = percent === 0
95
+ ? "Disabled — only active Pokemon earns XP"
96
+ : `Inactive party members receive ${percent}% of earned XP`;
97
+ return {
98
+ content: [
99
+ {
100
+ type: "text",
101
+ text: [`XP share: ${previous}% → ${percent}%`, "", desc].join("\n"),
102
+ },
103
+ ],
104
+ };
105
+ }
70
106
  return {
71
107
  content: [
72
108
  {
73
109
  type: "text",
74
- text: `Unknown setting: "${params.setting}". Available settings: encounter-speed`,
110
+ text: `Unknown setting: "${params.setting}". Available: encounter-speed, xp-share`,
75
111
  },
76
112
  ],
77
113
  isError: true,
@@ -48,6 +48,7 @@ export const BuddyConfigSchema = z.object({
48
48
  statusLineEnabled: z.boolean().default(true),
49
49
  bellEnabled: z.boolean().default(true),
50
50
  encounterSpeed: z.enum(["fast", "normal", "slow"]).default("normal"),
51
+ xpSharePercent: z.number().min(0).max(100).default(25),
51
52
  });
52
53
  // ---- Pokedex ----
53
54
  export const PokedexEntrySchema = z.object({
@@ -31,6 +31,7 @@ function defaultConfig() {
31
31
  statusLineEnabled: true,
32
32
  bellEnabled: true,
33
33
  encounterSpeed: "normal",
34
+ xpSharePercent: 25,
34
35
  };
35
36
  }
36
37
  /** Build empty pokedex */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umang-boss/claudemon",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Pokemon coding companion for Claude Code — Gotta code 'em all!",
5
5
  "type": "module",
6
6
  "main": "dist/src/server/index.js",
@@ -56,6 +56,7 @@ This ensures first-time users get the starter selection flow automatically.
56
56
  | `settings encounter-speed fast` | `buddy_settings` with setting="encounter-speed", value="fast" — fastest encounters (100 XP) |
57
57
  | `settings encounter-speed normal` | `buddy_settings` with setting="encounter-speed", value="normal" — default (250 XP) |
58
58
  | `settings encounter-speed slow` | `buddy_settings` with setting="encounter-speed", value="slow" — less interruptions (500 XP) |
59
+ | `settings xp-share N` | `buddy_settings` with setting="xp-share", value="N" — share N% XP to inactive party (0-100) |
59
60
  | `help` | List all available /buddy commands |
60
61
 
61
62
  Pass $ARGUMENTS to determine which subcommand to route to.
@@ -254,6 +254,7 @@ export interface BuddyConfig {
254
254
  statusLineEnabled: boolean;
255
255
  bellEnabled: boolean; // Terminal bell on level-up/encounters
256
256
  encounterSpeed: "fast" | "normal" | "slow"; // Configurable encounter frequency
257
+ xpSharePercent: number; // 0-100, default 25. Percentage of XP shared to inactive party
257
258
  }
258
259
 
259
260
  // ── XP Events (what triggers XP awards) ────────────────────
@@ -64,6 +64,21 @@ if (xpEvent.statBoost && xpEvent.boostAmount > 0) {
64
64
  applyStatBoost(pokemon, xpEvent.statBoost, xpEvent.boostAmount);
65
65
  }
66
66
 
67
+ // XP sharing: give inactive party members a percentage
68
+ const sharePercent = state.config.xpSharePercent ?? 0;
69
+ if (sharePercent > 0 && state.party.length > 1) {
70
+ const sharedXp = Math.floor((xpEvent.xp * sharePercent) / 100);
71
+ if (sharedXp > 0) {
72
+ for (const member of state.party) {
73
+ if (member.isActive) continue;
74
+ const memberSpecies = POKEMON_BY_ID.get(member.pokemonId);
75
+ if (memberSpecies) {
76
+ addXp(member, sharedXp, memberSpecies);
77
+ }
78
+ }
79
+ }
80
+ }
81
+
67
82
  // Track total XP earned by the trainer
68
83
  state.totalXpEarned += xpEvent.xp;
69
84
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * buddy_settings tool — Configure Claudemon settings.
3
- * Currently supports: encounter-speed (fast | normal | slow).
3
+ * Supports: encounter-speed, xp-share.
4
4
  */
5
5
 
6
6
  import { z } from "zod";
@@ -11,7 +11,6 @@ import { ENCOUNTER_THRESHOLDS } from "../../engine/constants.js";
11
11
 
12
12
  const VALID_ENCOUNTER_SPEEDS: readonly EncounterSpeed[] = ["fast", "normal", "slow"];
13
13
 
14
- /** Speed descriptions for user-facing messages. */
15
14
  const SPEED_DESCRIPTIONS: Readonly<Record<EncounterSpeed, string>> = {
16
15
  fast: "Fastest encounters — wild Pokemon appear every ~100 XP",
17
16
  normal: "Default pace — wild Pokemon appear every ~250 XP",
@@ -22,12 +21,12 @@ const SPEED_DESCRIPTIONS: Readonly<Record<EncounterSpeed, string>> = {
22
21
  export function registerSettingsTool(server: McpServer): void {
23
22
  server.tool(
24
23
  "buddy_settings",
25
- "Configure Claudemon settings (encounter speed, etc.)",
24
+ "Configure Claudemon settings (encounter-speed, xp-share)",
26
25
  {
27
- setting: z.enum(["encounter-speed"]).describe("The setting to configure"),
28
- value: z.string().describe("The value to set (for encounter-speed: fast, normal, or slow)"),
26
+ setting: z.enum(["encounter-speed", "xp-share"]).describe("The setting to configure"),
27
+ value: z.string().describe("The value to set"),
29
28
  },
30
- async (params: { setting: "encounter-speed"; value: string }) => {
29
+ async (params: { setting: "encounter-speed" | "xp-share"; value: string }) => {
31
30
  const stateManager = StateManager.getInstance();
32
31
  const state = await stateManager.load();
33
32
 
@@ -42,6 +41,7 @@ export function registerSettingsTool(server: McpServer): void {
42
41
  };
43
42
  }
44
43
 
44
+ // ── Encounter Speed ──────────────────────────────────
45
45
  if (params.setting === "encounter-speed") {
46
46
  const speed = params.value.toLowerCase();
47
47
 
@@ -68,7 +68,6 @@ export function registerSettingsTool(server: McpServer): void {
68
68
  const validSpeed = speed as EncounterSpeed;
69
69
  const previousSpeed = state.config.encounterSpeed ?? "normal";
70
70
  state.config.encounterSpeed = validSpeed;
71
-
72
71
  await stateManager.save();
73
72
 
74
73
  return {
@@ -76,7 +75,7 @@ export function registerSettingsTool(server: McpServer): void {
76
75
  {
77
76
  type: "text" as const,
78
77
  text: [
79
- `Encounter speed updated: ${previousSpeed} -> ${validSpeed}`,
78
+ `Encounter speed: ${previousSpeed} ${validSpeed}`,
80
79
  "",
81
80
  SPEED_DESCRIPTIONS[validSpeed],
82
81
  `XP threshold: ${ENCOUNTER_THRESHOLDS[validSpeed]}`,
@@ -86,12 +85,54 @@ export function registerSettingsTool(server: McpServer): void {
86
85
  };
87
86
  }
88
87
 
89
- // Unreachable with the current enum, but guards against future additions
88
+ // ── XP Share ─────────────────────────────────────────
89
+ if (params.setting === "xp-share") {
90
+ const percent = parseInt(params.value, 10);
91
+
92
+ if (isNaN(percent) || percent < 0 || percent > 100) {
93
+ return {
94
+ content: [
95
+ {
96
+ type: "text" as const,
97
+ text: [
98
+ `Invalid XP share value: "${params.value}"`,
99
+ "",
100
+ "Enter a number 0-100 (percentage of XP shared to inactive party):",
101
+ " 0 — No XP sharing (only active Pokemon earns)",
102
+ " 25 — Default (inactive get 25% of earned XP)",
103
+ " 50 — Half XP shared to inactive party",
104
+ " 100 — Full XP to everyone",
105
+ ].join("\n"),
106
+ },
107
+ ],
108
+ isError: true,
109
+ };
110
+ }
111
+
112
+ const previous = state.config.xpSharePercent ?? 25;
113
+ state.config.xpSharePercent = percent;
114
+ await stateManager.save();
115
+
116
+ const desc =
117
+ percent === 0
118
+ ? "Disabled — only active Pokemon earns XP"
119
+ : `Inactive party members receive ${percent}% of earned XP`;
120
+
121
+ return {
122
+ content: [
123
+ {
124
+ type: "text" as const,
125
+ text: [`XP share: ${previous}% → ${percent}%`, "", desc].join("\n"),
126
+ },
127
+ ],
128
+ };
129
+ }
130
+
90
131
  return {
91
132
  content: [
92
133
  {
93
134
  type: "text" as const,
94
- text: `Unknown setting: "${params.setting}". Available settings: encounter-speed`,
135
+ text: `Unknown setting: "${params.setting}". Available: encounter-speed, xp-share`,
95
136
  },
96
137
  ],
97
138
  isError: true,
@@ -71,6 +71,7 @@ export const BuddyConfigSchema = z.object({
71
71
  statusLineEnabled: z.boolean().default(true),
72
72
  bellEnabled: z.boolean().default(true),
73
73
  encounterSpeed: z.enum(["fast", "normal", "slow"]).default("normal"),
74
+ xpSharePercent: z.number().min(0).max(100).default(25),
74
75
  });
75
76
 
76
77
  // ---- Pokedex ----
@@ -56,6 +56,7 @@ function defaultConfig(): BuddyConfig {
56
56
  statusLineEnabled: true,
57
57
  bellEnabled: true,
58
58
  encounterSpeed: "normal",
59
+ xpSharePercent: 25,
59
60
  };
60
61
  }
61
62