kill-switch-mcp 1.0.1 → 1.1.2
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/bin/kill-switch-mcp.js +13 -0
- package/package.json +8 -6
- package/src/api/index.ts +188 -0
- package/src/sdk/actions-helpers.ts +450 -0
- package/src/sdk/actions.ts +3208 -0
- package/src/sdk/formatter.ts +263 -0
- package/src/sdk/index.ts +1313 -0
- package/src/sdk/pathfinding.ts +57 -0
- package/src/sdk/types.ts +584 -0
- package/src/server.ts +1088 -0
- package/src/wallet/index.ts +369 -0
- package/dist/server.js +0 -18160
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// formatter.ts - State formatting for CLI and agent consumption
|
|
2
|
+
// Adapted from webclient/src/bot/formatters.ts for SDK use
|
|
3
|
+
|
|
4
|
+
import type { BotWorldState, PrayerName } from './types';
|
|
5
|
+
import { PRAYER_NAMES } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Format a duration in ms to human readable string
|
|
9
|
+
*/
|
|
10
|
+
function formatAge(ms: number): string {
|
|
11
|
+
if (ms < 1000) return 'just now';
|
|
12
|
+
const seconds = Math.floor(ms / 1000);
|
|
13
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
14
|
+
const minutes = Math.floor(seconds / 60);
|
|
15
|
+
if (minutes < 60) return `${minutes}m ${seconds % 60}s ago`;
|
|
16
|
+
const hours = Math.floor(minutes / 60);
|
|
17
|
+
return `${hours}h ${minutes % 60}m ago`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Format world state as readable plaintext/markdown
|
|
22
|
+
*/
|
|
23
|
+
export function formatWorldState(state: BotWorldState, stateAgeMs?: number): string {
|
|
24
|
+
const lines: string[] = [];
|
|
25
|
+
|
|
26
|
+
lines.push('# World State');
|
|
27
|
+
const ageStr = stateAgeMs !== undefined ? ` | Updated: ${formatAge(stateAgeMs)}` : '';
|
|
28
|
+
lines.push(`Tick: ${state.tick} | In Game: ${state.inGame}${ageStr}`);
|
|
29
|
+
|
|
30
|
+
// Player info
|
|
31
|
+
if (state.player) {
|
|
32
|
+
const p = state.player;
|
|
33
|
+
lines.push('');
|
|
34
|
+
lines.push('## Player');
|
|
35
|
+
lines.push(`Name: ${p.name} (Combat ${p.combatLevel})`);
|
|
36
|
+
lines.push(`Position: (${p.worldX}, ${p.worldZ}) Level ${p.level}`);
|
|
37
|
+
|
|
38
|
+
// Combat status
|
|
39
|
+
if (p.combat.inCombat) {
|
|
40
|
+
const target = state.nearbyNpcs.find(n => n.index === p.combat.targetIndex);
|
|
41
|
+
if (target) {
|
|
42
|
+
const hpStr = target.maxHp > 0 ? ` HP: ${target.hp}/${target.maxHp}` : '';
|
|
43
|
+
lines.push(`In Combat: ${target.name}${hpStr}`);
|
|
44
|
+
} else {
|
|
45
|
+
lines.push(`In Combat: target index ${p.combat.targetIndex}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Recent combat events
|
|
50
|
+
if (state.combatEvents && state.combatEvents.length > 0) {
|
|
51
|
+
const recentEvents = state.combatEvents.slice(-5);
|
|
52
|
+
for (const evt of recentEvents) {
|
|
53
|
+
const ticksAgo = state.tick - evt.tick;
|
|
54
|
+
const ago = ticksAgo > 0 ? ` (${ticksAgo} ticks ago)` : '';
|
|
55
|
+
if (evt.type === 'damage_taken') {
|
|
56
|
+
lines.push(` <- Took ${evt.damage} damage${ago}`);
|
|
57
|
+
} else if (evt.type === 'damage_dealt') {
|
|
58
|
+
lines.push(` -> Dealt ${evt.damage} damage${ago}`);
|
|
59
|
+
} else if (evt.type === 'kill') {
|
|
60
|
+
lines.push(` ** Kill${ago}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Modal/dialog state (important for understanding game state)
|
|
67
|
+
if (state.modalOpen) {
|
|
68
|
+
lines.push('');
|
|
69
|
+
lines.push(`## Modal Open (interface: ${state.modalInterface})`);
|
|
70
|
+
if (state.modalInterface === 269) {
|
|
71
|
+
lines.push('(Character design screen - use acceptCharacterDesign to continue)');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (state.dialog.isOpen) {
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push('## Dialog');
|
|
78
|
+
if (state.dialog.isWaiting) {
|
|
79
|
+
lines.push('(Waiting for server response...)');
|
|
80
|
+
} else if (state.dialog.options.length > 0) {
|
|
81
|
+
lines.push('Options:');
|
|
82
|
+
for (const opt of state.dialog.options) {
|
|
83
|
+
lines.push(` ${opt.index}. ${opt.text}`);
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
lines.push('(Click to continue - use optionIndex: 0)');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Interface state (crafting menus, etc.)
|
|
91
|
+
if (state.interface && state.interface.isOpen) {
|
|
92
|
+
lines.push('');
|
|
93
|
+
lines.push(`## Interface (id: ${state.interface.interfaceId})`);
|
|
94
|
+
if (state.interface.options.length > 0) {
|
|
95
|
+
lines.push('Options:');
|
|
96
|
+
for (const opt of state.interface.options) {
|
|
97
|
+
lines.push(` ${opt.index}. ${opt.text}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Shop state
|
|
103
|
+
if (state.shop && state.shop.isOpen) {
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push('## Shop');
|
|
106
|
+
lines.push(`Title: ${state.shop.title}`);
|
|
107
|
+
lines.push('');
|
|
108
|
+
lines.push('**Items for sale:**');
|
|
109
|
+
if (state.shop.shopItems.length === 0) {
|
|
110
|
+
lines.push(' (Empty)');
|
|
111
|
+
} else {
|
|
112
|
+
for (const item of state.shop.shopItems) {
|
|
113
|
+
lines.push(` [${item.slot}] ${item.name} x${item.count} - buy: ${item.buyPrice}gp`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
lines.push('');
|
|
117
|
+
lines.push('**Your items (to sell):**');
|
|
118
|
+
if (state.shop.playerItems.length === 0) {
|
|
119
|
+
lines.push(' (Empty)');
|
|
120
|
+
} else {
|
|
121
|
+
for (const item of state.shop.playerItems) {
|
|
122
|
+
lines.push(` [${item.slot}] ${item.name} x${item.count} - sell: ${item.sellPrice}gp`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Skills
|
|
128
|
+
lines.push('');
|
|
129
|
+
lines.push('## Skills');
|
|
130
|
+
for (const skill of state.skills) {
|
|
131
|
+
const boosted = skill.level !== skill.baseLevel ? `${skill.level}/` : '';
|
|
132
|
+
lines.push(`${skill.name}: ${boosted}${skill.baseLevel} (${skill.experience.toLocaleString()} xp)`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Inventory
|
|
136
|
+
lines.push('');
|
|
137
|
+
const usedSlots = state.inventory.length;
|
|
138
|
+
const maxSlots = 28;
|
|
139
|
+
const emptySlots = maxSlots - usedSlots;
|
|
140
|
+
lines.push(`## Inventory (${emptySlots} empty slots)`);
|
|
141
|
+
if (state.inventory.length === 0) {
|
|
142
|
+
lines.push('(Empty)');
|
|
143
|
+
} else {
|
|
144
|
+
const itemCounts = new Map<string, { count: number; options: string[] }>();
|
|
145
|
+
for (const item of state.inventory) {
|
|
146
|
+
const existing = itemCounts.get(item.name);
|
|
147
|
+
if (existing) {
|
|
148
|
+
existing.count += item.count;
|
|
149
|
+
} else {
|
|
150
|
+
itemCounts.set(item.name, {
|
|
151
|
+
count: item.count,
|
|
152
|
+
options: item.optionsWithIndex?.map(o => o.text) ?? []
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const [name, data] of itemCounts) {
|
|
157
|
+
const opts = data.options.length > 0 ? ` [${data.options.join(', ')}]` : '';
|
|
158
|
+
lines.push(`- ${name} x${data.count}${opts}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Equipment
|
|
163
|
+
if (state.equipment.length > 0) {
|
|
164
|
+
lines.push('');
|
|
165
|
+
lines.push('## Equipment');
|
|
166
|
+
for (const item of state.equipment) {
|
|
167
|
+
lines.push(`- ${item.name}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Combat style
|
|
172
|
+
if (state.combatStyle) {
|
|
173
|
+
lines.push('');
|
|
174
|
+
lines.push('## Combat Style');
|
|
175
|
+
lines.push(`Weapon: ${state.combatStyle.weaponName}`);
|
|
176
|
+
const current = state.combatStyle.styles[state.combatStyle.currentStyle];
|
|
177
|
+
if (current) {
|
|
178
|
+
lines.push(`Style: ${current.name} (${current.type}) - trains ${current.trainedSkill}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Prayers
|
|
183
|
+
if (state.prayers) {
|
|
184
|
+
const activePrayers = state.prayers.activePrayers
|
|
185
|
+
.map((active, i) => active ? PRAYER_NAMES[i] : null)
|
|
186
|
+
.filter((name): name is PrayerName => name !== null);
|
|
187
|
+
if (activePrayers.length > 0) {
|
|
188
|
+
lines.push('');
|
|
189
|
+
lines.push('## Active Prayers');
|
|
190
|
+
lines.push(`Points: ${state.prayers.prayerPoints}/${state.prayers.prayerLevel}`);
|
|
191
|
+
lines.push(`Active: ${activePrayers.join(', ')}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Nearby NPCs
|
|
196
|
+
if (state.nearbyNpcs.length > 0) {
|
|
197
|
+
lines.push('');
|
|
198
|
+
lines.push('## Nearby NPCs');
|
|
199
|
+
for (const npc of state.nearbyNpcs.slice(0, 10)) {
|
|
200
|
+
const lvl = npc.combatLevel > 0 ? ` (Lvl ${npc.combatLevel})` : '';
|
|
201
|
+
const hp = npc.maxHp > 0 ? ` HP: ${npc.hp}/${npc.maxHp}` : '';
|
|
202
|
+
const combat = npc.inCombat ? ' [in combat]' : '';
|
|
203
|
+
const opts = npc.options?.length > 0 ? ` [${npc.options.join(', ')}]` : '';
|
|
204
|
+
lines.push(`- ${npc.name}${lvl}${hp}${combat} - ${npc.distance} tiles (idx: ${npc.index})${opts}`);
|
|
205
|
+
}
|
|
206
|
+
if (state.nearbyNpcs.length > 10) {
|
|
207
|
+
lines.push(` ... and ${state.nearbyNpcs.length - 10} more`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Nearby Players
|
|
212
|
+
if (state.nearbyPlayers.length > 0) {
|
|
213
|
+
lines.push('');
|
|
214
|
+
lines.push('## Nearby Players');
|
|
215
|
+
for (const pl of state.nearbyPlayers.slice(0, 5)) {
|
|
216
|
+
lines.push(`- ${pl.name} (Combat ${pl.combatLevel}) - ${pl.distance} tiles`);
|
|
217
|
+
}
|
|
218
|
+
if (state.nearbyPlayers.length > 5) {
|
|
219
|
+
lines.push(` ... and ${state.nearbyPlayers.length - 5} more`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Nearby Locs
|
|
224
|
+
if (state.nearbyLocs.length > 0) {
|
|
225
|
+
lines.push('');
|
|
226
|
+
lines.push('## Nearby Objects');
|
|
227
|
+
for (const loc of state.nearbyLocs.slice(0, 10)) {
|
|
228
|
+
const opts = loc.options?.length > 0 ? ` [${loc.options.join(', ')}]` : '';
|
|
229
|
+
lines.push(`- ${loc.name} at (${loc.x}, ${loc.z}) - ${loc.distance} tiles${opts}`);
|
|
230
|
+
}
|
|
231
|
+
if (state.nearbyLocs.length > 10) {
|
|
232
|
+
lines.push(` ... and ${state.nearbyLocs.length - 10} more`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Ground Items
|
|
237
|
+
if (state.groundItems.length > 0) {
|
|
238
|
+
lines.push('');
|
|
239
|
+
lines.push('## Ground Items');
|
|
240
|
+
for (const item of state.groundItems.slice(0, 10)) {
|
|
241
|
+
lines.push(`- ${item.name} x${item.count} at (${item.x}, ${item.z}) - ${item.distance} tiles`);
|
|
242
|
+
}
|
|
243
|
+
if (state.groundItems.length > 10) {
|
|
244
|
+
lines.push(` ... and ${state.groundItems.length - 10} more`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Recent messages
|
|
249
|
+
if (state.gameMessages && state.gameMessages.length > 0) {
|
|
250
|
+
lines.push('');
|
|
251
|
+
lines.push('## Recent Messages');
|
|
252
|
+
for (const msg of state.gameMessages.slice(-5)) {
|
|
253
|
+
const cleanText = msg.text.replace(/@\w+@/g, '');
|
|
254
|
+
if (msg.sender) {
|
|
255
|
+
lines.push(`- ${msg.sender}: ${cleanText}`);
|
|
256
|
+
} else {
|
|
257
|
+
lines.push(`- ${cleanText}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return lines.join('\n');
|
|
263
|
+
}
|