kill-switch-mcp 1.1.10 → 1.2.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/dist/server.js +208 -218
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { existsSync as existsSync3 } from "fs";
|
|
15
15
|
import { readFile as readFile2 } from "fs/promises";
|
|
16
16
|
import { join as join3 } from "path";
|
|
17
|
+
import { homedir as homedir3 } from "os";
|
|
17
18
|
|
|
18
19
|
// src/sdk/types.ts
|
|
19
20
|
var PRAYER_NAMES = [
|
|
@@ -3452,6 +3453,7 @@ class BotActions {
|
|
|
3452
3453
|
import { readFile } from "fs/promises";
|
|
3453
3454
|
import { join } from "path";
|
|
3454
3455
|
import { existsSync } from "fs";
|
|
3456
|
+
import { homedir } from "os";
|
|
3455
3457
|
|
|
3456
3458
|
class BotManager {
|
|
3457
3459
|
connections = new Map;
|
|
@@ -3471,9 +3473,9 @@ class BotManager {
|
|
|
3471
3473
|
let gateway = gatewayUrl || this.defaultGatewayUrl;
|
|
3472
3474
|
let showChat = false;
|
|
3473
3475
|
if (!password) {
|
|
3474
|
-
const envPath = join(
|
|
3476
|
+
const envPath = join(homedir(), ".killswitch", "bots", name, "bot.env");
|
|
3475
3477
|
if (!existsSync(envPath)) {
|
|
3476
|
-
throw new Error(`Bot "${name}" not found. No bots/${name}/bot.env file exists. Call login with an agent_name to create one.`);
|
|
3478
|
+
throw new Error(`Bot "${name}" not found. No ~/.killswitch/bots/${name}/bot.env file exists. Call login with an agent_name to create one.`);
|
|
3477
3479
|
}
|
|
3478
3480
|
const envContent = await readFile(envPath, "utf-8");
|
|
3479
3481
|
const env = this.parseEnv(envContent);
|
|
@@ -3801,7 +3803,7 @@ function formatWorldState(state, stateAgeMs) {
|
|
|
3801
3803
|
// src/wallet/index.ts
|
|
3802
3804
|
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3803
3805
|
import { join as join2 } from "path";
|
|
3804
|
-
import { homedir } from "os";
|
|
3806
|
+
import { homedir as homedir2 } from "os";
|
|
3805
3807
|
import {
|
|
3806
3808
|
createPublicClient,
|
|
3807
3809
|
createWalletClient,
|
|
@@ -3812,7 +3814,7 @@ import {
|
|
|
3812
3814
|
pad
|
|
3813
3815
|
} from "viem";
|
|
3814
3816
|
import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
|
|
3815
|
-
var WALLET_DIR = join2(
|
|
3817
|
+
var WALLET_DIR = join2(homedir2(), ".killswitch");
|
|
3816
3818
|
var WALLET_FILE = join2(WALLET_DIR, "access-key.json");
|
|
3817
3819
|
var TEMPO_RPC = process.env.TEMPO_RPC_URL || "https://rpc.moderato.tempo.xyz";
|
|
3818
3820
|
var TEMPO_CHAIN_ID = parseInt(process.env.TEMPO_CHAIN_ID || "42431");
|
|
@@ -4067,81 +4069,150 @@ var SERVER_URL2 = (() => {
|
|
|
4067
4069
|
return process.env.KILL_SWITCH_SERVER || "localhost";
|
|
4068
4070
|
})();
|
|
4069
4071
|
console.error(`[Kill Switch MCP] Server: ${SERVER_URL2}`);
|
|
4070
|
-
var
|
|
4072
|
+
var KILLSWITCH_HOME = join3(homedir3(), ".killswitch");
|
|
4073
|
+
var BOTS_DIR = join3(KILLSWITCH_HOME, "bots");
|
|
4074
|
+
var GUIDE = `# Kill Switch — Player Guide
|
|
4075
|
+
|
|
4076
|
+
You are playing Kill Switch — a Hunger Games-style battle royale where AI agents fight to the death.
|
|
4077
|
+
|
|
4078
|
+
YOU are the brain of your bot. Your human talks strategy, you execute it in the game.
|
|
4079
|
+
|
|
4080
|
+
**PERMADEATH**: If your agent dies, it's dead forever. No respawns. Only the last one standing wins.
|
|
4081
|
+
|
|
4082
|
+
---
|
|
4071
4083
|
|
|
4072
|
-
|
|
4073
|
-
execute it by sending code to the game via execute_code.
|
|
4084
|
+
## Quick Start
|
|
4074
4085
|
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4086
|
+
1. \`login\` — connects to the game (creates account if needed)
|
|
4087
|
+
2. \`join_game\` with mode \`"shorty"\` — queues you up
|
|
4088
|
+
3. \`wait_for_game_start\` — blocks until teleported to the arena
|
|
4089
|
+
4. \`execute_code\` — fight! Use \`bot\`, \`sdk\`, and \`actions\` globals.
|
|
4090
|
+
|
|
4091
|
+
Before writing any execute_code, read \`killswitch://sdk-reference\` for the complete API.
|
|
4092
|
+
|
|
4093
|
+
---
|
|
4082
4094
|
|
|
4083
4095
|
## Game Modes
|
|
4084
|
-
|
|
4085
|
-
|
|
4096
|
+
|
|
4097
|
+
### Shorty (Quick Battle Royale)
|
|
4098
|
+
- **Arena**: Draynor Manor fenced grounds
|
|
4099
|
+
- **Stats**: 50 Attack, 50 Strength, 50 Defence, 99 Hitpoints
|
|
4100
|
+
- **Start**: Empty inventory — everything comes from ground loot
|
|
4101
|
+
- **Win condition**: Last agent alive
|
|
4102
|
+
|
|
4103
|
+
### Longy (Coming Soon)
|
|
4104
|
+
- Large map: Falador to Port Sarim
|
|
4105
|
+
- Level 1 everything — skill up, craft gear, fish food, outlast everyone
|
|
4106
|
+
|
|
4107
|
+
---
|
|
4086
4108
|
|
|
4087
4109
|
## The Arena (Shorty)
|
|
4088
|
-
- You spawn inside Draynor Manor's fenced arena with nothing
|
|
4089
|
-
- Items are scattered on the ground in a cornucopia pattern:
|
|
4090
|
-
- Outer ring: bronze/iron weapons, leather armor, bread/meat
|
|
4091
|
-
- Mid ring: mithril/adamant weapons, chainmail, trout/salmon
|
|
4092
|
-
- Center: rune weapons, rune/adamant armor, lobster/swordfish
|
|
4093
|
-
- Better items are closer to the center — but so is everyone else!
|
|
4094
4110
|
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
-
|
|
4111
|
+
You spawn on the perimeter of Draynor Manor with **nothing**. Items are scattered in a cornucopia pattern:
|
|
4112
|
+
|
|
4113
|
+
- **Outer ring** (safe, near spawn): Bronze/iron weapons, leather armor, bread/meat
|
|
4114
|
+
- **Mid ring**: Mithril/adamant weapons, chainmail, trout/salmon
|
|
4115
|
+
- **Center** (dangerous, high traffic): Rune weapons, rune/adamant armor, lobster/swordfish
|
|
4098
4116
|
|
|
4099
|
-
|
|
4117
|
+
**Better loot = more risk.** Everyone converges on the center.
|
|
4100
4118
|
|
|
4101
|
-
###
|
|
4102
|
-
- **bot
|
|
4103
|
-
- **
|
|
4104
|
-
- **actions
|
|
4119
|
+
### Arena Combat Tips
|
|
4120
|
+
- **DO NOT use \`bot.walkTo()\` or \`bot.pickupItem()\` in the arena.** Server pathfinding fails on the arena terrain. Use \`sdk.sendWalk(x, z)\` + \`sdk.waitForTicks()\` for movement, and \`sdk.sendPickup(x, z, itemId)\` to grab items. See the SDK reference for the manual loot pattern.
|
|
4121
|
+
- **Stay inside the fenced area.** If you wander outside the arena bounds, you cannot attack other players and may be eliminated.
|
|
4122
|
+
- **Use \`actions.lootAndEquip()\` and \`actions.fightLoop()\`** — they already use the correct low-level methods internally.
|
|
4105
4123
|
|
|
4106
|
-
|
|
4107
|
-
- bot.pickupItem(/item name/i) — walk to and pick up a ground item
|
|
4108
|
-
- bot.equipItem(/item name/i) — equip from inventory
|
|
4109
|
-
- bot.eatFood(/item name/i) — eat food to heal
|
|
4110
|
-
- bot.attackPlayer("name" or /pattern/i) — attack a player
|
|
4111
|
-
- bot.attackNpc("name" or /pattern/i) — attack an NPC
|
|
4112
|
-
- bot.walkTo(x, z) — pathfind and walk (complex, uses server pathfinding)
|
|
4124
|
+
---
|
|
4113
4125
|
|
|
4114
|
-
|
|
4115
|
-
State (sync): sdk.getState(), sdk.getInventory(), sdk.getEquipment(), sdk.getNearbyPlayers(), sdk.getGroundItems()
|
|
4116
|
-
Search (sync): sdk.findInventoryItem(/pat/i), sdk.findGroundItem(/pat/i), sdk.findNearbyPlayer(/pat/i)
|
|
4117
|
-
Commands (async): sdk.sendWalk(x, z), sdk.sendPickup(x, z, itemId), sdk.sendInteractLoc(x, z, locId, option)
|
|
4118
|
-
Timing (async): await sdk.waitForTicks(n) — each tick ~0.6s
|
|
4126
|
+
## How to Play
|
|
4119
4127
|
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
-
|
|
4123
|
-
-
|
|
4128
|
+
### Interactive Mode (default)
|
|
4129
|
+
Your human gives you strategy between rounds:
|
|
4130
|
+
- "Rush the center, grab the best gear"
|
|
4131
|
+
- "Play safe, loot the outer ring and wait"
|
|
4132
|
+
- "Focus the weakest player"
|
|
4124
4133
|
|
|
4125
|
-
|
|
4126
|
-
Actions are loaded from your bot's actions/ directory at login. Default actions:
|
|
4127
|
-
- actions.lootAndEquip({ maxItems, radius, foodOnly }) — scan ground, pick up best items, equip gear
|
|
4128
|
-
- actions.fightLoop({ eatAt, duration, target, fleeAt }) — attack nearest/weakest, auto-eat, run loop
|
|
4129
|
-
- actions.kite({ safeHp, duration, direction }) — retreat from enemies while eating
|
|
4134
|
+
You turn their words into execute_code calls. Keep each call to **15-30 seconds**, then report back what happened so they can adjust.
|
|
4130
4135
|
|
|
4131
|
-
###
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4136
|
+
### Autonomous Mode (\`claude -p\`)
|
|
4137
|
+
Run a full game with no human input:
|
|
4138
|
+
\`\`\`
|
|
4139
|
+
claude -p 'Play Kill Switch. Create agent named Fury. Join shorty, play aggressively. Exit when done.'
|
|
4140
|
+
\`\`\`
|
|
4141
|
+
Claude makes all decisions. Same tools, same flow — just no human between rounds.
|
|
4142
|
+
|
|
4143
|
+
---
|
|
4139
4144
|
|
|
4140
|
-
##
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
+
## Pre-Built Actions
|
|
4146
|
+
|
|
4147
|
+
Your bot ships with action functions in the \`actions\` object. **Use these first** — they handle the common patterns:
|
|
4148
|
+
|
|
4149
|
+
\`\`\`typescript
|
|
4150
|
+
// Grab nearby items and equip best gear
|
|
4151
|
+
await actions.lootAndEquip({ maxItems: 5 });
|
|
4152
|
+
|
|
4153
|
+
// Fight nearest player with auto-eat (15 seconds)
|
|
4154
|
+
await actions.fightLoop({ eatAt: 25, duration: 15000 });
|
|
4155
|
+
|
|
4156
|
+
// Fight a specific player
|
|
4157
|
+
await actions.fightLoop({ target: /vex/i, eatAt: 20 });
|
|
4158
|
+
|
|
4159
|
+
// Retreat while eating to recover HP
|
|
4160
|
+
await actions.kite({ safeHp: 40 });
|
|
4161
|
+
\`\`\`
|
|
4162
|
+
|
|
4163
|
+
Combine them for a full turn:
|
|
4164
|
+
\`\`\`typescript
|
|
4165
|
+
const loot = await actions.lootAndEquip({ maxItems: 3 });
|
|
4166
|
+
const result = await actions.fightLoop({ eatAt: 25, duration: 15000 });
|
|
4167
|
+
if (result.hp < 15 && result.foodLeft > 0) {
|
|
4168
|
+
await actions.kite({ safeHp: 35 });
|
|
4169
|
+
}
|
|
4170
|
+
return result;
|
|
4171
|
+
\`\`\`
|
|
4172
|
+
|
|
4173
|
+
### Custom Actions
|
|
4174
|
+
Create custom actions in \`~/.killswitch/bots/<name>/actions/\`. Each \`.ts\` file exports a default function that becomes \`actions.functionName()\`. Re-login to pick up new actions.
|
|
4175
|
+
|
|
4176
|
+
---
|
|
4177
|
+
|
|
4178
|
+
## Key Rules
|
|
4179
|
+
|
|
4180
|
+
1. **Use MCP tools only** — never bash commands or scripts for gameplay
|
|
4181
|
+
2. **Read killswitch://sdk-reference** before writing execute_code
|
|
4182
|
+
3. **Pick up items first** — you start empty, looting is survival
|
|
4183
|
+
4. **Equip before fighting** — weapon first, then armor
|
|
4184
|
+
5. **Eat when HP < 25** — any food is better than no food
|
|
4185
|
+
6. **Keep execute_code calls short** (15-30s) — return state, evaluate, adapt
|
|
4186
|
+
7. **You cannot see other players' HP or inventory** — only name, combat level, position, distance
|
|
4187
|
+
8. **bot methods throw on failure** — use try/catch if you want to handle errors
|
|
4188
|
+
9. **sdk.sendWalk(x, z) for movement** — bot.walkTo() uses server pathfinding (slower, can fail)
|
|
4189
|
+
10. **PERMADEATH** — play smart!
|
|
4190
|
+
|
|
4191
|
+
---
|
|
4192
|
+
|
|
4193
|
+
## Bot Data
|
|
4194
|
+
|
|
4195
|
+
Bot credentials and custom actions are stored in \`~/.killswitch/bots/<name>/\`:
|
|
4196
|
+
\`\`\`
|
|
4197
|
+
~/.killswitch/
|
|
4198
|
+
bots/
|
|
4199
|
+
<name>/
|
|
4200
|
+
bot.env # BOT_USERNAME, PASSWORD, SERVER
|
|
4201
|
+
actions/ # Custom action files (.ts)
|
|
4202
|
+
\`\`\`
|
|
4203
|
+
|
|
4204
|
+
The \`login\` tool creates this automatically. You don't need to set up files manually.
|
|
4205
|
+
|
|
4206
|
+
---
|
|
4207
|
+
|
|
4208
|
+
## Troubleshooting
|
|
4209
|
+
|
|
4210
|
+
- **"Not connected" / MCP disconnected** — Call \`login\` again. It reconnects automatically. Do NOT tell the user to restart the server — just call \`login\`.
|
|
4211
|
+
- **"No game state"** — The browser client needs a moment to load. Wait a few seconds, then call \`get_status\`.
|
|
4212
|
+
- **Agent is dead** — That agent is gone forever (permadeath). Create a new one with a different name.
|
|
4213
|
+
- **\`bot.pickupItem\` or \`bot.walkTo\` fails** — These use server pathfinding which fails on arena terrain. Use \`sdk.sendWalk(x, z)\` + \`sdk.sendPickup(x, z, itemId)\` instead.
|
|
4214
|
+
- **"Can't attack" / attacks not working** — Make sure you are inside the arena PvP zone. If you wandered outside the fence, walk back in.
|
|
4215
|
+
`;
|
|
4145
4216
|
var server = new Server({ name: "kill-switch", version: "3.0.0" }, { capabilities: { tools: {}, resources: {} } });
|
|
4146
4217
|
var SDK_REFERENCE = `# Kill Switch SDK Reference
|
|
4147
4218
|
|
|
@@ -4313,10 +4384,16 @@ For combat/arena play, use \`sdk.sendWalk(x, z)\` + \`await sdk.waitForTicks(n)\
|
|
|
4313
4384
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
4314
4385
|
return {
|
|
4315
4386
|
resources: [
|
|
4387
|
+
{
|
|
4388
|
+
uri: "killswitch://guide",
|
|
4389
|
+
name: "Kill Switch Player Guide",
|
|
4390
|
+
description: "START HERE. Game overview, how to play, common patterns, and key rules. Read this before doing anything.",
|
|
4391
|
+
mimeType: "text/markdown"
|
|
4392
|
+
},
|
|
4316
4393
|
{
|
|
4317
4394
|
uri: "killswitch://sdk-reference",
|
|
4318
4395
|
name: "Kill Switch SDK Reference",
|
|
4319
|
-
description: "Complete API reference for bot, sdk, and actions objects used in execute_code.
|
|
4396
|
+
description: "Complete API reference for bot, sdk, and actions objects used in execute_code. Read before writing game code.",
|
|
4320
4397
|
mimeType: "text/markdown"
|
|
4321
4398
|
}
|
|
4322
4399
|
]
|
|
@@ -4324,6 +4401,17 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
4324
4401
|
});
|
|
4325
4402
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
4326
4403
|
const { uri } = request.params;
|
|
4404
|
+
if (uri === "killswitch://guide") {
|
|
4405
|
+
return {
|
|
4406
|
+
contents: [
|
|
4407
|
+
{
|
|
4408
|
+
uri: "killswitch://guide",
|
|
4409
|
+
mimeType: "text/markdown",
|
|
4410
|
+
text: GUIDE
|
|
4411
|
+
}
|
|
4412
|
+
]
|
|
4413
|
+
};
|
|
4414
|
+
}
|
|
4327
4415
|
if (uri === "killswitch://sdk-reference") {
|
|
4328
4416
|
return {
|
|
4329
4417
|
contents: [
|
|
@@ -4342,44 +4430,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
4342
4430
|
tools: [
|
|
4343
4431
|
{
|
|
4344
4432
|
name: "login",
|
|
4345
|
-
description:
|
|
4346
|
-
|
|
4347
|
-
Call this when the user says anything like "let's play", "join the game", "log in", "connect", etc.
|
|
4348
|
-
|
|
4349
|
-
If the user already has a bot, this will reconnect. If not, it creates a new one. Idempotent — safe to call multiple times.
|
|
4350
|
-
|
|
4351
|
-
${GAME_DESCRIPTION}`,
|
|
4433
|
+
description: "Log in to Kill Switch. Creates account if needed, connects to game server, opens browser client. Idempotent — safe to call again. Read killswitch://guide first for game overview.",
|
|
4352
4434
|
inputSchema: {
|
|
4353
4435
|
type: "object",
|
|
4354
4436
|
properties: {
|
|
4355
4437
|
agent_name: {
|
|
4356
4438
|
type: "string",
|
|
4357
|
-
description: "Name for the bot (max 12 chars, alphanumeric). If not provided,
|
|
4439
|
+
description: "Name for the bot (max 12 chars, alphanumeric). If not provided, reuses existing bot or generates random name."
|
|
4358
4440
|
}
|
|
4359
4441
|
}
|
|
4360
4442
|
}
|
|
4361
4443
|
},
|
|
4362
4444
|
{
|
|
4363
4445
|
name: "execute_code",
|
|
4364
|
-
description:
|
|
4365
|
-
|
|
4366
|
-
IMPORTANT: Read the "Kill Switch SDK Reference" resource (killswitch://sdk-reference) for the complete API.
|
|
4367
|
-
Do NOT guess method names or property paths — only use what is documented in the SDK reference.
|
|
4368
|
-
|
|
4369
|
-
Quick reminders:
|
|
4370
|
-
- bot methods are async and THROW on failure
|
|
4371
|
-
- sdk.getState().player.worldX / .worldZ / .hp / .maxHp
|
|
4372
|
-
- Ground items use .x / .z (NOT .worldX)
|
|
4373
|
-
- sdk.sendWalk(x, z) for fast movement, bot.walkTo(x, z) for pathfinding
|
|
4374
|
-
- Blocking UI is auto-dismissed — do NOT call bot.dismissBlockingUI()
|
|
4375
|
-
|
|
4376
|
-
You MUST call login before using this tool.`,
|
|
4446
|
+
description: "Execute TypeScript code on your bot. Globals: bot, sdk, actions. Read killswitch://sdk-reference for API. Bot methods throw on failure. Blocking UI is auto-dismissed.",
|
|
4377
4447
|
inputSchema: {
|
|
4378
4448
|
type: "object",
|
|
4379
4449
|
properties: {
|
|
4380
4450
|
code: {
|
|
4381
4451
|
type: "string",
|
|
4382
|
-
description: "TypeScript code to execute. Has access to bot (BotActions)
|
|
4452
|
+
description: "TypeScript code to execute. Has access to bot (BotActions), sdk (BotSDK), and actions."
|
|
4383
4453
|
},
|
|
4384
4454
|
timeout: {
|
|
4385
4455
|
type: "number",
|
|
@@ -4391,7 +4461,7 @@ You MUST call login before using this tool.`,
|
|
|
4391
4461
|
},
|
|
4392
4462
|
{
|
|
4393
4463
|
name: "get_status",
|
|
4394
|
-
description: "Check
|
|
4464
|
+
description: "Check bot state (position, HP, inventory, nearby players) without executing code.",
|
|
4395
4465
|
inputSchema: {
|
|
4396
4466
|
type: "object",
|
|
4397
4467
|
properties: {}
|
|
@@ -4399,16 +4469,7 @@ You MUST call login before using this tool.`,
|
|
|
4399
4469
|
},
|
|
4400
4470
|
{
|
|
4401
4471
|
name: "wait_for_game_start",
|
|
4402
|
-
description:
|
|
4403
|
-
|
|
4404
|
-
This tool blocks until the game starts and your bot is teleported to the arena. When it returns, the fight is ON — immediately start your combat loop!
|
|
4405
|
-
|
|
4406
|
-
Typical flow:
|
|
4407
|
-
1. login → connect to game
|
|
4408
|
-
2. join_game → pick a mode
|
|
4409
|
-
3. Chat strategy with human
|
|
4410
|
-
4. wait_for_game_start → blocks until game starts
|
|
4411
|
-
5. execute_code → start fighting!`,
|
|
4472
|
+
description: "Block until the match starts and you are teleported to the arena. Call after join_game.",
|
|
4412
4473
|
inputSchema: {
|
|
4413
4474
|
type: "object",
|
|
4414
4475
|
properties: {
|
|
@@ -4421,20 +4482,14 @@ Typical flow:
|
|
|
4421
4482
|
},
|
|
4422
4483
|
{
|
|
4423
4484
|
name: "join_game",
|
|
4424
|
-
description:
|
|
4425
|
-
|
|
4426
|
-
Available modes:
|
|
4427
|
-
- "shorty": Quick battle royale at Draynor Manor. 50 atk/str/def, 99 HP. Grab loot, fight, last one standing wins.
|
|
4428
|
-
- "longy": (Coming soon) Large survival map with skill progression.
|
|
4429
|
-
|
|
4430
|
-
After joining, call wait_for_game_start to wait for the game to begin.`,
|
|
4485
|
+
description: "Join a game mode queue. Call after login. After joining, call wait_for_game_start.",
|
|
4431
4486
|
inputSchema: {
|
|
4432
4487
|
type: "object",
|
|
4433
4488
|
properties: {
|
|
4434
4489
|
mode: {
|
|
4435
4490
|
type: "string",
|
|
4436
4491
|
enum: ["shorty", "longy"],
|
|
4437
|
-
description:
|
|
4492
|
+
description: 'Game mode: "shorty" (quick battle royale) or "longy" (coming soon)'
|
|
4438
4493
|
}
|
|
4439
4494
|
},
|
|
4440
4495
|
required: ["mode"]
|
|
@@ -4442,7 +4497,7 @@ After joining, call wait_for_game_start to wait for the game to begin.`,
|
|
|
4442
4497
|
},
|
|
4443
4498
|
{
|
|
4444
4499
|
name: "disconnect_bot",
|
|
4445
|
-
description: "Disconnect from the game.
|
|
4500
|
+
description: "Disconnect from the game.",
|
|
4446
4501
|
inputSchema: {
|
|
4447
4502
|
type: "object",
|
|
4448
4503
|
properties: {}
|
|
@@ -4450,23 +4505,13 @@ After joining, call wait_for_game_start to wait for the game to begin.`,
|
|
|
4450
4505
|
},
|
|
4451
4506
|
{
|
|
4452
4507
|
name: "setup_game_wallet",
|
|
4453
|
-
description:
|
|
4454
|
-
|
|
4455
|
-
IMPORTANT: This tool handles real (or testnet) money. Explain to the user what's happening at each step.
|
|
4456
|
-
|
|
4457
|
-
Prerequisites:
|
|
4458
|
-
- The user must have the Tempo CLI installed (curl -fsSL https://tempo.xyz/install | bash)
|
|
4459
|
-
- The user must have run "tempo wallet login" to create their Tempo account
|
|
4460
|
-
|
|
4461
|
-
This tool generates a local access key. The user then needs to authorize it on their Tempo account (requires biometric/passkey confirmation). After that, the key is stored locally in ~/.killswitch/ and used for all tournament deposits.
|
|
4462
|
-
|
|
4463
|
-
Learn more about Tempo access keys: https://docs.tempo.xyz/protocol/tips/tip-1011`,
|
|
4508
|
+
description: 'Set up a Tempo wallet access key for paid tournaments. Requires Tempo CLI installed and "tempo wallet login" completed. Handles real money — explain each step to user.',
|
|
4464
4509
|
inputSchema: {
|
|
4465
4510
|
type: "object",
|
|
4466
4511
|
properties: {
|
|
4467
4512
|
tempo_account_address: {
|
|
4468
4513
|
type: "string",
|
|
4469
|
-
description: `The user's Tempo account address (0x...). Get
|
|
4514
|
+
description: `The user's Tempo account address (0x...). Get via "tempo wallet whoami".`
|
|
4470
4515
|
}
|
|
4471
4516
|
},
|
|
4472
4517
|
required: ["tempo_account_address"]
|
|
@@ -4474,9 +4519,7 @@ Learn more about Tempo access keys: https://docs.tempo.xyz/protocol/tips/tip-101
|
|
|
4474
4519
|
},
|
|
4475
4520
|
{
|
|
4476
4521
|
name: "check_wallet",
|
|
4477
|
-
description:
|
|
4478
|
-
|
|
4479
|
-
If no game wallet is set up, this will tell you to run setup_game_wallet first.`,
|
|
4522
|
+
description: "Check game wallet address and USDC balance for tournament buy-ins.",
|
|
4480
4523
|
inputSchema: {
|
|
4481
4524
|
type: "object",
|
|
4482
4525
|
properties: {}
|
|
@@ -4484,9 +4527,7 @@ If no game wallet is set up, this will tell you to run setup_game_wallet first.`
|
|
|
4484
4527
|
},
|
|
4485
4528
|
{
|
|
4486
4529
|
name: "tournament_schedule",
|
|
4487
|
-
description:
|
|
4488
|
-
|
|
4489
|
-
This is a read-only tool — it doesn't cost anything to check the schedule.`,
|
|
4530
|
+
description: "View upcoming paid tournaments — start times, buy-ins, player counts. Read-only.",
|
|
4490
4531
|
inputSchema: {
|
|
4491
4532
|
type: "object",
|
|
4492
4533
|
properties: {}
|
|
@@ -4494,26 +4535,13 @@ This is a read-only tool — it doesn't cost anything to check the schedule.`,
|
|
|
4494
4535
|
},
|
|
4495
4536
|
{
|
|
4496
4537
|
name: "join_tournament",
|
|
4497
|
-
description:
|
|
4498
|
-
|
|
4499
|
-
THIS TOOL SPENDS REAL MONEY (or testnet money). Before calling this tool:
|
|
4500
|
-
1. Tell the user exactly which tournament they're joining (time, buy-in amount)
|
|
4501
|
-
2. Show their current wallet balance
|
|
4502
|
-
3. Ask them to confirm they want to proceed
|
|
4503
|
-
4. Only then call this tool
|
|
4504
|
-
|
|
4505
|
-
The deposit is sent on-chain to the tournament's escrow contract. The user's buy-in is held in escrow until:
|
|
4506
|
-
- They win → they receive 90% of the pot
|
|
4507
|
-
- The match is cancelled → they get a full refund
|
|
4508
|
-
- The server goes down → they can claim a refund after 1 hour
|
|
4509
|
-
|
|
4510
|
-
The user must be logged in (call login first) so we know their in-game username.`,
|
|
4538
|
+
description: "Join a paid tournament by depositing buy-in from game wallet. SPENDS REAL MONEY. Confirm with user before calling. Must be logged in.",
|
|
4511
4539
|
inputSchema: {
|
|
4512
4540
|
type: "object",
|
|
4513
4541
|
properties: {
|
|
4514
4542
|
tournament_address: {
|
|
4515
4543
|
type: "string",
|
|
4516
|
-
description: "
|
|
4544
|
+
description: "Tournament contract address (from tournament_schedule)"
|
|
4517
4545
|
}
|
|
4518
4546
|
},
|
|
4519
4547
|
required: ["tournament_address"]
|
|
@@ -4529,8 +4557,7 @@ async function loadActions(botName, bot, sdk) {
|
|
|
4529
4557
|
const { existsSync: existsSync4, readdirSync, cpSync, readFileSync: readFileSync2 } = await import("fs");
|
|
4530
4558
|
const { join: join4, basename } = await import("path");
|
|
4531
4559
|
const { pathToFileURL } = await import("url");
|
|
4532
|
-
const
|
|
4533
|
-
const actionsDir = join4(botsDir, botName, "actions");
|
|
4560
|
+
const actionsDir = join4(BOTS_DIR, botName, "actions");
|
|
4534
4561
|
const defaultsDir = join4(new URL(".", import.meta.url).pathname, "..", "defaults", "actions");
|
|
4535
4562
|
console.error(`[Kill Switch] Loading actions from ${actionsDir}`);
|
|
4536
4563
|
if (!existsSync4(actionsDir) && existsSync4(defaultsDir)) {
|
|
@@ -4619,20 +4646,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
4619
4646
|
const parts = [`Already connected as "${activeBotName}".`];
|
|
4620
4647
|
if (state) {
|
|
4621
4648
|
parts.push("");
|
|
4622
|
-
parts.push("── Current State ──");
|
|
4623
4649
|
parts.push(formatWorldState(state, existing.sdk.getStateAge()));
|
|
4624
4650
|
}
|
|
4625
|
-
parts.push("");
|
|
4626
|
-
parts.push("Your bot is in the game. Talk strategy with your human while waiting for the tournament to start!");
|
|
4627
4651
|
return { content: [{ type: "text", text: parts.join(`
|
|
4628
4652
|
`) }] };
|
|
4629
4653
|
}
|
|
4630
4654
|
}
|
|
4631
|
-
const botsDir = join3(process.cwd(), "bots");
|
|
4632
4655
|
let username;
|
|
4633
4656
|
let password;
|
|
4657
|
+
const legacyBotsDir = join3(process.cwd(), "bots");
|
|
4658
|
+
if (botName && !existsSync3(join3(BOTS_DIR, botName, "bot.env")) && existsSync3(join3(legacyBotsDir, botName, "bot.env"))) {
|
|
4659
|
+
const { mkdirSync: mkdirSync2, cpSync } = await import("fs");
|
|
4660
|
+
mkdirSync2(join3(BOTS_DIR, botName), { recursive: true });
|
|
4661
|
+
cpSync(join3(legacyBotsDir, botName), join3(BOTS_DIR, botName), { recursive: true });
|
|
4662
|
+
console.error(`[Kill Switch] Migrated bot "${botName}" from ./bots/ to ~/.killswitch/bots/`);
|
|
4663
|
+
}
|
|
4634
4664
|
if (botName) {
|
|
4635
|
-
const envPath = join3(
|
|
4665
|
+
const envPath = join3(BOTS_DIR, botName, "bot.env");
|
|
4636
4666
|
if (existsSync3(envPath)) {
|
|
4637
4667
|
console.error(`[Kill Switch] Found existing bot "${botName}"`);
|
|
4638
4668
|
const env = parseEnv(await readFile2(envPath, "utf-8"));
|
|
@@ -4643,7 +4673,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
4643
4673
|
username = botName;
|
|
4644
4674
|
password = generateRandomPassword();
|
|
4645
4675
|
const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
|
|
4646
|
-
const botDir = join3(
|
|
4676
|
+
const botDir = join3(BOTS_DIR, botName);
|
|
4647
4677
|
mkdirSync2(botDir, { recursive: true });
|
|
4648
4678
|
writeFileSync2(join3(botDir, "bot.env"), [
|
|
4649
4679
|
`BOT_USERNAME=${username}`,
|
|
@@ -4654,21 +4684,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
4654
4684
|
`));
|
|
4655
4685
|
}
|
|
4656
4686
|
} else {
|
|
4657
|
-
const { readdirSync } = await import("fs");
|
|
4658
|
-
if (existsSync3(
|
|
4659
|
-
const
|
|
4687
|
+
const { readdirSync, mkdirSync: mkdirSync2, writeFileSync: writeFileSync2, cpSync } = await import("fs");
|
|
4688
|
+
if (existsSync3(legacyBotsDir)) {
|
|
4689
|
+
const legacyDirs = readdirSync(legacyBotsDir).filter((d) => d !== "_template" && existsSync3(join3(legacyBotsDir, d, "bot.env")));
|
|
4690
|
+
for (const legacyName of legacyDirs) {
|
|
4691
|
+
if (!existsSync3(join3(BOTS_DIR, legacyName, "bot.env"))) {
|
|
4692
|
+
mkdirSync2(join3(BOTS_DIR, legacyName), { recursive: true });
|
|
4693
|
+
cpSync(join3(legacyBotsDir, legacyName), join3(BOTS_DIR, legacyName), { recursive: true });
|
|
4694
|
+
console.error(`[Kill Switch] Migrated bot "${legacyName}" from ./bots/ to ~/.killswitch/bots/`);
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4698
|
+
if (existsSync3(BOTS_DIR)) {
|
|
4699
|
+
const dirs = readdirSync(BOTS_DIR).filter((d) => d !== "_template" && existsSync3(join3(BOTS_DIR, d, "bot.env")));
|
|
4660
4700
|
if (dirs.length > 0) {
|
|
4661
4701
|
botName = dirs[0];
|
|
4662
4702
|
console.error(`[Kill Switch] Reusing existing bot "${botName}"`);
|
|
4663
|
-
const env = parseEnv(await readFile2(join3(
|
|
4703
|
+
const env = parseEnv(await readFile2(join3(BOTS_DIR, botName, "bot.env"), "utf-8"));
|
|
4664
4704
|
username = env.BOT_USERNAME || botName;
|
|
4665
4705
|
password = env.PASSWORD || "";
|
|
4666
4706
|
} else {
|
|
4667
4707
|
botName = generateRandomName();
|
|
4668
4708
|
username = botName;
|
|
4669
4709
|
password = generateRandomPassword();
|
|
4670
|
-
const
|
|
4671
|
-
const botDir = join3(botsDir, botName);
|
|
4710
|
+
const botDir = join3(BOTS_DIR, botName);
|
|
4672
4711
|
mkdirSync2(botDir, { recursive: true });
|
|
4673
4712
|
writeFileSync2(join3(botDir, "bot.env"), [
|
|
4674
4713
|
`BOT_USERNAME=${username}`,
|
|
@@ -4683,10 +4722,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
4683
4722
|
botName = generateRandomName();
|
|
4684
4723
|
username = botName;
|
|
4685
4724
|
password = generateRandomPassword();
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
mkdirSync2(botDir, { recursive: true });
|
|
4689
|
-
writeFileSync2(join3(botDir, "bot.env"), [
|
|
4725
|
+
mkdirSync2(join3(BOTS_DIR, botName), { recursive: true });
|
|
4726
|
+
writeFileSync2(join3(BOTS_DIR, botName, "bot.env"), [
|
|
4690
4727
|
`BOT_USERNAME=${username}`,
|
|
4691
4728
|
`PASSWORD=${password}`,
|
|
4692
4729
|
`SERVER=${SERVER_URL2}`,
|
|
@@ -4753,37 +4790,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
4753
4790
|
activeActionDescriptions = descriptions;
|
|
4754
4791
|
const state = connection.sdk.getState();
|
|
4755
4792
|
const parts = [
|
|
4756
|
-
`Connected as "${username}"
|
|
4757
|
-
|
|
4758
|
-
"Your bot is in the game. A browser window should have opened showing the game view.",
|
|
4759
|
-
"",
|
|
4760
|
-
"You're in the tournament lobby (Barbarian Village) with:",
|
|
4761
|
-
" - EMPTY inventory and no equipment",
|
|
4762
|
-
" - Combat stats: 50 Attack, 50 Strength, 50 Defence, 99 Hitpoints",
|
|
4763
|
-
"",
|
|
4764
|
-
'NEXT STEP: Call join_game with mode "shorty" (or "longy" when available) to queue up.',
|
|
4765
|
-
"",
|
|
4766
|
-
"Game modes:",
|
|
4767
|
-
" - SHORTY: Quick battle royale at Draynor Manor. Loot, fight, last one standing.",
|
|
4768
|
-
" - LONGY: (Coming soon) Large survival map with skill progression.",
|
|
4769
|
-
"",
|
|
4770
|
-
"After joining a game, call wait_for_game_start to wait for the game to begin."
|
|
4793
|
+
`Connected as "${username}".`,
|
|
4794
|
+
`Bot data: ~/.killswitch/bots/${botName}/`
|
|
4771
4795
|
];
|
|
4772
|
-
parts.push("");
|
|
4773
|
-
parts.push("── IMPORTANT: Read the SDK Reference ──");
|
|
4774
|
-
parts.push('Before writing any execute_code, read the "Kill Switch SDK Reference" resource (killswitch://sdk-reference).');
|
|
4775
|
-
parts.push("It contains the complete API for bot, sdk, and actions. Do NOT guess method names — use only what is documented.");
|
|
4776
4796
|
if (activeActionDescriptions.length > 0) {
|
|
4777
4797
|
parts.push("");
|
|
4778
|
-
parts.push("
|
|
4779
|
-
parts.push("Use these in execute_code via the `actions` object:");
|
|
4798
|
+
parts.push("Loaded actions:");
|
|
4780
4799
|
parts.push(...activeActionDescriptions);
|
|
4781
4800
|
}
|
|
4782
4801
|
if (state) {
|
|
4783
4802
|
parts.push("");
|
|
4784
|
-
parts.push("── Current State ──");
|
|
4785
4803
|
parts.push(formatWorldState(state, connection.sdk.getStateAge()));
|
|
4786
4804
|
}
|
|
4805
|
+
parts.push("");
|
|
4806
|
+
parts.push('Next: call join_game with mode "shorty" to queue up.');
|
|
4787
4807
|
return { content: [{ type: "text", text: parts.join(`
|
|
4788
4808
|
`) }] };
|
|
4789
4809
|
} finally {
|
|
@@ -4907,7 +4927,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
4907
4927
|
if (isLongCode) {
|
|
4908
4928
|
parts.push("");
|
|
4909
4929
|
parts.push("── Tip ──");
|
|
4910
|
-
parts.push(`Long script detected. Consider writing to a .ts file
|
|
4930
|
+
parts.push(`Long script detected. Consider writing to a .ts file in ~/.killswitch/bots/${botName}/`);
|
|
4911
4931
|
}
|
|
4912
4932
|
const output = parts.length > 0 ? parts.join(`
|
|
4913
4933
|
`) : "(no output)";
|
|
@@ -4940,33 +4960,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
4940
4960
|
console.error(`[Kill Switch] Tournament started! (detected after ${waitTime}s)`);
|
|
4941
4961
|
const players = connection.sdk.getNearbyPlayers?.() || [];
|
|
4942
4962
|
const parts = [
|
|
4943
|
-
"
|
|
4963
|
+
"GAME STARTED! You are in the arena.",
|
|
4944
4964
|
"",
|
|
4945
|
-
`
|
|
4946
|
-
`Position: ${state.player.worldX}, ${state.player.worldZ}`,
|
|
4965
|
+
`Position: (${state.player.worldX}, ${state.player.worldZ})`,
|
|
4947
4966
|
`HP: ${state.player.hp}/${state.player.maxHp}`,
|
|
4967
|
+
`Nearby players: ${players.length > 0 ? players.map((p) => `${p.name} (dist ${p.distance})`).join(", ") : "scanning..."}`,
|
|
4948
4968
|
"",
|
|
4949
|
-
|
|
4950
|
-
"",
|
|
4951
|
-
"REMEMBER: You die = your agent dies FOREVER. No second chances.",
|
|
4952
|
-
"",
|
|
4953
|
-
"IMMEDIATELY use execute_code to start your strategy:",
|
|
4954
|
-
"1. Pick up weapons and food from the ground",
|
|
4955
|
-
"2. Equip best weapon found, then start fighting",
|
|
4956
|
-
"3. Eat when HP gets low — you have 99 HP but no food yet!",
|
|
4957
|
-
"",
|
|
4958
|
-
"QUICK START: await actions.lootAndEquip({ maxItems: 5 }); await actions.fightLoop({ eatAt: 25, duration: 30000 });",
|
|
4959
|
-
"",
|
|
4960
|
-
"API REMINDERS:",
|
|
4961
|
-
"- sdk.sendWalk(x, z) for quick movement (NOT bot.walkTo for combat)",
|
|
4962
|
-
"- sdk.getState().player.worldX / .worldZ / .hp",
|
|
4963
|
-
"- Blocking UI is auto-dismissed — do NOT call bot.dismissBlockingUI()"
|
|
4969
|
+
"Act now — use execute_code to loot and fight."
|
|
4964
4970
|
];
|
|
4965
|
-
if (activeActionDescriptions.length > 0) {
|
|
4966
|
-
parts.push("");
|
|
4967
|
-
parts.push("AVAILABLE ACTIONS:");
|
|
4968
|
-
parts.push(...activeActionDescriptions);
|
|
4969
|
-
}
|
|
4970
4971
|
return { content: [{ type: "text", text: parts.join(`
|
|
4971
4972
|
`) }] };
|
|
4972
4973
|
}
|
|
@@ -4994,22 +4995,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
4994
4995
|
return errorResponse(data.error);
|
|
4995
4996
|
}
|
|
4996
4997
|
const parts = [
|
|
4997
|
-
`Joined ${mode.toUpperCase()} queue
|
|
4998
|
-
"",
|
|
4999
|
-
`Queue position: ${data.queuePosition}`,
|
|
4998
|
+
`Joined ${mode.toUpperCase()} queue.`,
|
|
5000
4999
|
`Players in queue: ${data.queuePlayers?.join(", ") || "just you"}`,
|
|
5001
|
-
""
|
|
5000
|
+
"",
|
|
5001
|
+
"Next: call wait_for_game_start to wait for the match."
|
|
5002
5002
|
];
|
|
5003
|
-
if (mode === "shorty") {
|
|
5004
|
-
parts.push("SHORTY: Quick battle royale at Draynor Manor.");
|
|
5005
|
-
parts.push("Stats: 50 Attack, 50 Strength, 50 Defence, 99 Hitpoints");
|
|
5006
|
-
parts.push("You start with NOTHING — grab weapons and food from the ground!");
|
|
5007
|
-
} else {
|
|
5008
|
-
parts.push("LONGY: Large survival map (coming soon).");
|
|
5009
|
-
}
|
|
5010
|
-
parts.push("");
|
|
5011
|
-
parts.push("Now call wait_for_game_start to wait for the game to begin.");
|
|
5012
|
-
parts.push("Chat strategy with your human while you wait!");
|
|
5013
5003
|
return { content: [{ type: "text", text: parts.join(`
|
|
5014
5004
|
`) }] };
|
|
5015
5005
|
} catch (e) {
|