@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 +18 -2
- package/cli/index.ts +26 -8
- package/dist/cli/doctor.js +19 -2
- package/dist/cli/index.js +26 -9
- package/dist/src/hooks/award-xp.js +15 -0
- package/dist/src/server/tools/settings.js +44 -8
- package/dist/src/state/schemas.js +1 -0
- package/dist/src/state/state-manager.js +1 -0
- package/package.json +1 -1
- package/skills/buddy/SKILL.md +1 -0
- package/src/engine/types.ts +1 -0
- package/src/hooks/award-xp.ts +15 -0
- package/src/server/tools/settings.ts +51 -10
- package/src/state/schemas.ts +1 -0
- package/src/state/state-manager.ts +1 -0
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
|
-
|
|
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,
|
|
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
|
|
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
|
`);
|
package/dist/cli/doctor.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
|
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
|
-
*
|
|
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
|
|
18
|
-
setting: z.enum(["encounter-speed"]).describe("The setting to configure"),
|
|
19
|
-
value: z.string().describe("The value to set
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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({
|
package/package.json
CHANGED
package/skills/buddy/SKILL.md
CHANGED
|
@@ -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.
|
package/src/engine/types.ts
CHANGED
|
@@ -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) ────────────────────
|
package/src/hooks/award-xp.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
135
|
+
text: `Unknown setting: "${params.setting}". Available: encounter-speed, xp-share`,
|
|
95
136
|
},
|
|
96
137
|
],
|
|
97
138
|
isError: true,
|
package/src/state/schemas.ts
CHANGED
|
@@ -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 ----
|