kill-switch-mcp 1.2.7 → 1.2.8

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.
Files changed (2) hide show
  1. package/dist/server.js +256 -260
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -15,7 +15,6 @@ import { existsSync as existsSync3 } from "fs";
15
15
  import { readFile as readFile2 } from "fs/promises";
16
16
  import { join as join3 } from "path";
17
17
  import { homedir as homedir3 } from "os";
18
- import { exec } from "child_process";
19
18
 
20
19
  // src/sdk/types.ts
21
20
  var PRAYER_NAMES = [
@@ -3844,7 +3843,8 @@ function formatWorldState(state, stateAgeMs) {
3844
3843
  }
3845
3844
 
3846
3845
  // src/wallet/index.ts
3847
- import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
3846
+ import { execFile } from "child_process";
3847
+ import { readFileSync, existsSync as existsSync2 } from "fs";
3848
3848
  import { join as join2 } from "path";
3849
3849
  import { homedir as homedir2 } from "os";
3850
3850
  import {
@@ -3855,50 +3855,36 @@ import {
3855
3855
  stringToHex,
3856
3856
  pad
3857
3857
  } from "viem";
3858
- import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
3859
3858
  import { Account } from "viem/tempo";
3860
3859
  import { tempoModerato, tempo } from "viem/chains";
3861
- var WALLET_DIR = join2(homedir2(), ".killswitch");
3862
- var WALLET_FILE = join2(WALLET_DIR, "access-key.json");
3860
+ var TEMPO_BIN = join2(homedir2(), ".tempo", "bin", "tempo");
3863
3861
  var TEMPO_CHAIN_ID = parseInt(process.env.TEMPO_CHAIN_ID || "42431");
3864
3862
  var tempoChain = TEMPO_CHAIN_ID === 4217 ? tempo : tempoModerato;
3863
+ var TEMPO_NETWORK = TEMPO_CHAIN_ID === 4217 ? "mainnet" : "testnet";
3865
3864
  var USDC_ADDRESS = process.env.USDC_ADDRESS || "0x20c0000000000000000000000000000000000000";
3866
- var FACTORY_ADDRESS = process.env.FACTORY_ADDRESS;
3867
- var PENDING_FILE = join2(WALLET_DIR, "pending-wallet.json");
3868
- var SERVER_URL = (() => {
3869
- const idx = process.argv.indexOf("--server");
3870
- if (idx !== -1 && process.argv[idx + 1])
3871
- return process.argv[idx + 1];
3872
- return process.env.KILL_SWITCH_SERVER || "localhost";
3873
- })();
3874
- function getWebBase() {
3875
- const isLocal = SERVER_URL === "localhost" || SERVER_URL === "127.0.0.1";
3876
- return isLocal ? "http://localhost:8888" : `https://${SERVER_URL}`;
3877
- }
3878
- function generateLinkToken() {
3879
- const bytes = new Uint8Array(16);
3880
- crypto.getRandomValues(bytes);
3881
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
3882
- }
3883
- function savePendingWallet(pending) {
3884
- mkdirSync(WALLET_DIR, { recursive: true });
3885
- writeFileSync(PENDING_FILE, JSON.stringify(pending, null, 2), { mode: 384 });
3886
- }
3887
- function loadPendingWallet() {
3888
- try {
3889
- if (existsSync2(PENDING_FILE)) {
3890
- return JSON.parse(readFileSync(PENDING_FILE, "utf-8"));
3891
- }
3892
- } catch {}
3893
- return null;
3865
+ function runTempo(...args) {
3866
+ return new Promise((resolve, reject) => {
3867
+ execFile(TEMPO_BIN, args, { timeout: 30000 }, (error, stdout, stderr) => {
3868
+ if (error) {
3869
+ const msg = stderr?.trim() || error.message;
3870
+ reject(new Error(`tempo ${args.join(" ")} failed: ${msg}`));
3871
+ return;
3872
+ }
3873
+ resolve(stdout.trim());
3874
+ });
3875
+ });
3894
3876
  }
3895
- function clearPendingWallet() {
3896
- try {
3897
- if (existsSync2(PENDING_FILE)) {
3898
- const fs = __require("fs");
3899
- fs.unlinkSync(PENDING_FILE);
3900
- }
3901
- } catch {}
3877
+ function runTempoInteractive(...args) {
3878
+ return new Promise((resolve, reject) => {
3879
+ execFile(TEMPO_BIN, args, { timeout: 600000 }, (error, stdout, stderr) => {
3880
+ if (error) {
3881
+ const msg = stderr?.trim() || error.message;
3882
+ reject(new Error(`tempo ${args.join(" ")} failed: ${msg}`));
3883
+ return;
3884
+ }
3885
+ resolve(stdout.trim());
3886
+ });
3887
+ });
3902
3888
  }
3903
3889
  var ERC20_ABI = [
3904
3890
  {
@@ -3917,13 +3903,6 @@ var ERC20_ABI = [
3917
3903
  inputs: [{ name: "account", type: "address" }],
3918
3904
  outputs: [{ name: "", type: "uint256" }],
3919
3905
  stateMutability: "view"
3920
- },
3921
- {
3922
- name: "decimals",
3923
- type: "function",
3924
- inputs: [],
3925
- outputs: [{ name: "", type: "uint8" }],
3926
- stateMutability: "view"
3927
3906
  }
3928
3907
  ];
3929
3908
  var TOURNAMENT_ABI = [
@@ -3941,34 +3920,6 @@ var TOURNAMENT_ABI = [
3941
3920
  outputs: [{ name: "", type: "uint256" }],
3942
3921
  stateMutability: "view"
3943
3922
  },
3944
- {
3945
- name: "state",
3946
- type: "function",
3947
- inputs: [],
3948
- outputs: [{ name: "", type: "uint8" }],
3949
- stateMutability: "view"
3950
- },
3951
- {
3952
- name: "getDepositorCount",
3953
- type: "function",
3954
- inputs: [],
3955
- outputs: [{ name: "", type: "uint256" }],
3956
- stateMutability: "view"
3957
- },
3958
- {
3959
- name: "maxPlayers",
3960
- type: "function",
3961
- inputs: [],
3962
- outputs: [{ name: "", type: "uint8" }],
3963
- stateMutability: "view"
3964
- },
3965
- {
3966
- name: "startTime",
3967
- type: "function",
3968
- inputs: [],
3969
- outputs: [{ name: "", type: "uint256" }],
3970
- stateMutability: "view"
3971
- },
3972
3923
  {
3973
3924
  name: "hasDeposited",
3974
3925
  type: "function",
@@ -3977,25 +3928,55 @@ var TOURNAMENT_ABI = [
3977
3928
  stateMutability: "view"
3978
3929
  }
3979
3930
  ];
3980
- function loadWallet() {
3931
+ function isTempoInstalled() {
3932
+ return existsSync2(TEMPO_BIN);
3933
+ }
3934
+ async function installTempo() {
3935
+ return new Promise((resolve, reject) => {
3936
+ execFile("bash", ["-c", "curl -fsSL https://tempo.xyz/install | bash"], {
3937
+ timeout: 120000
3938
+ }, (error) => {
3939
+ if (error)
3940
+ reject(new Error(`Tempo install failed: ${error.message}`));
3941
+ else
3942
+ resolve();
3943
+ });
3944
+ });
3945
+ }
3946
+ async function readTempoWallet() {
3947
+ if (!isTempoInstalled())
3948
+ return null;
3981
3949
  try {
3982
- if (existsSync2(WALLET_FILE)) {
3983
- return JSON.parse(readFileSync(WALLET_FILE, "utf-8"));
3984
- }
3985
- } catch (e) {
3986
- console.error("[Wallet] Failed to load wallet:", e);
3950
+ const output = await runTempo("wallet", "-j", "whoami", "-n", TEMPO_NETWORK);
3951
+ const data = JSON.parse(output);
3952
+ if (!data.ready)
3953
+ return null;
3954
+ return {
3955
+ ready: data.ready,
3956
+ walletAddress: data.wallet,
3957
+ balance: data.balance?.total || "0",
3958
+ balanceAvailable: data.balance?.available || "0",
3959
+ key: data.key ? {
3960
+ address: data.key.address,
3961
+ privateKey: data.key.key,
3962
+ chainId: data.key.chain_id,
3963
+ spendingLimit: data.key.spending_limit?.limit || "0",
3964
+ spendingRemaining: data.key.spending_limit?.remaining || "0",
3965
+ expiresAt: data.key.expires_at || ""
3966
+ } : null
3967
+ };
3968
+ } catch {
3969
+ return null;
3987
3970
  }
3988
- return null;
3989
3971
  }
3990
- function saveWallet(wallet) {
3991
- mkdirSync(WALLET_DIR, { recursive: true });
3992
- writeFileSync(WALLET_FILE, JSON.stringify(wallet, null, 2), { mode: 384 });
3972
+ async function tempoLogin() {
3973
+ await runTempoInteractive("wallet", "login", "-n", TEMPO_NETWORK);
3993
3974
  }
3994
- function getPublicClient() {
3995
- return createPublicClient({
3996
- chain: tempoChain,
3997
- transport: http()
3998
- });
3975
+ async function tempoFund() {
3976
+ await runTempoInteractive("wallet", "fund", "-n", TEMPO_NETWORK);
3977
+ }
3978
+ async function tempoRefresh() {
3979
+ await runTempoInteractive("wallet", "refresh", "-n", TEMPO_NETWORK);
3999
3980
  }
4000
3981
  function getWalletClient(privateKey, parentAddress) {
4001
3982
  const account = Account.fromSecp256k1(privateKey, { access: parentAddress });
@@ -4005,78 +3986,60 @@ function getWalletClient(privateKey, parentAddress) {
4005
3986
  transport: http()
4006
3987
  });
4007
3988
  }
4008
- function hasGameWallet() {
4009
- return existsSync2(WALLET_FILE);
3989
+ function getPublicClient() {
3990
+ return createPublicClient({
3991
+ chain: tempoChain,
3992
+ transport: http()
3993
+ });
4010
3994
  }
4011
- async function getWalletInfo() {
4012
- const wallet = loadWallet();
3995
+ async function ensureTempoSetup() {
3996
+ if (!isTempoInstalled()) {
3997
+ return {
3998
+ status: "needs_install",
3999
+ message: "Tempo CLI is not installed. Installing now..."
4000
+ };
4001
+ }
4002
+ const wallet = await readTempoWallet();
4013
4003
  if (!wallet) {
4014
- throw new Error("No game wallet found. Run setup_game_wallet first.");
4004
+ return {
4005
+ status: "needs_login",
4006
+ message: "Tempo wallet is not connected. Opening browser for passkey setup..."
4007
+ };
4008
+ }
4009
+ if (!wallet.key) {
4010
+ return {
4011
+ status: "needs_refresh",
4012
+ message: "Tempo access key needs to be refreshed."
4013
+ };
4014
+ }
4015
+ const expiresAt = new Date(wallet.key.expiresAt).getTime();
4016
+ if (isNaN(expiresAt) || expiresAt < Date.now()) {
4017
+ return {
4018
+ status: "needs_refresh",
4019
+ message: "Tempo access key has expired. Refreshing..."
4020
+ };
4021
+ }
4022
+ const balance = parseFloat(wallet.balanceAvailable);
4023
+ if (balance <= 0) {
4024
+ return {
4025
+ status: "needs_fund",
4026
+ message: "Tempo wallet has no funds. Opening funding page...",
4027
+ wallet
4028
+ };
4015
4029
  }
4016
- const pub = getPublicClient();
4017
- const balanceRaw = await pub.readContract({
4018
- address: USDC_ADDRESS,
4019
- abi: ERC20_ABI,
4020
- functionName: "balanceOf",
4021
- args: [wallet.accountAddress]
4022
- });
4023
- const balance = formatUnits(balanceRaw, 6);
4024
- return {
4025
- address: wallet.accountAddress,
4026
- balance,
4027
- balanceRaw
4028
- };
4029
- }
4030
- function initiateWalletSetup() {
4031
- const privateKey = generatePrivateKey();
4032
- const account = privateKeyToAccount(privateKey);
4033
- const linkToken = generateLinkToken();
4034
- const chain = TEMPO_CHAIN_ID === 4217 ? "mainnet" : "testnet";
4035
- const webBase = getWebBase();
4036
- const authorizeUrl = `${webBase}/authorize?key=${account.address}&t=${linkToken}&chain=${chain}`;
4037
- const pending = {
4038
- accessKeyPrivate: privateKey,
4039
- accessKeyAddress: account.address,
4040
- linkToken,
4041
- authorizeUrl,
4042
- createdAt: new Date().toISOString()
4043
- };
4044
- savePendingWallet(pending);
4045
4030
  return {
4046
- accessKeyAddress: account.address,
4047
- linkToken,
4048
- authorizeUrl
4031
+ status: "ready",
4032
+ message: "Tempo wallet is ready.",
4033
+ wallet
4049
4034
  };
4050
4035
  }
4051
- async function checkWalletCallback(linkToken) {
4052
- try {
4053
- const webBase = getWebBase();
4054
- const response = await fetch(`${webBase}/api/wallet-callback?t=${linkToken}`);
4055
- const data = await response.json();
4056
- if (data.status === "complete" && data.tempoAddress) {
4057
- return { tempoAddress: data.tempoAddress, txHash: data.txHash };
4058
- }
4059
- } catch {}
4060
- return null;
4061
- }
4062
- function finalizeWalletSetup(tempoAddress) {
4063
- const pending = loadPendingWallet();
4064
- if (!pending)
4065
- throw new Error("No pending wallet setup found.");
4066
- saveWallet({
4067
- accessKeyPrivate: pending.accessKeyPrivate,
4068
- accountAddress: tempoAddress,
4069
- createdAt: new Date().toISOString()
4070
- });
4071
- clearPendingWallet();
4072
- }
4073
4036
  async function joinTournament(tournamentAddress, username) {
4074
- const wallet = loadWallet();
4075
- if (!wallet) {
4076
- throw new Error("No game wallet found. Run setup_game_wallet first.");
4037
+ const wallet = await readTempoWallet();
4038
+ if (!wallet?.key) {
4039
+ throw new Error("No Tempo wallet found. Run setup_game_wallet first.");
4077
4040
  }
4078
4041
  const pub = getPublicClient();
4079
- const walletClient = getWalletClient(wallet.accessKeyPrivate, wallet.accountAddress);
4042
+ const walletClient = getWalletClient(wallet.key.privateKey, wallet.walletAddress);
4080
4043
  const buyIn = await pub.readContract({
4081
4044
  address: tournamentAddress,
4082
4045
  abi: TOURNAMENT_ABI,
@@ -4086,7 +4049,7 @@ async function joinTournament(tournamentAddress, username) {
4086
4049
  address: USDC_ADDRESS,
4087
4050
  abi: ERC20_ABI,
4088
4051
  functionName: "balanceOf",
4089
- args: [wallet.accountAddress]
4052
+ args: [wallet.walletAddress]
4090
4053
  });
4091
4054
  if (balance < buyIn) {
4092
4055
  const needed = formatUnits(buyIn, 6);
@@ -4097,11 +4060,16 @@ async function joinTournament(tournamentAddress, username) {
4097
4060
  address: tournamentAddress,
4098
4061
  abi: TOURNAMENT_ABI,
4099
4062
  functionName: "hasDeposited",
4100
- args: [wallet.accountAddress]
4063
+ args: [wallet.walletAddress]
4101
4064
  });
4102
4065
  if (alreadyDeposited) {
4103
4066
  throw new Error("You have already deposited in this tournament.");
4104
4067
  }
4068
+ const remaining = parseFloat(wallet.key.spendingRemaining);
4069
+ const buyInUsd = parseFloat(formatUnits(buyIn, 6));
4070
+ if (remaining < buyInUsd) {
4071
+ throw new Error(`Spending limit too low. Need $${buyInUsd.toFixed(2)}, limit remaining: $${remaining.toFixed(2)}. ` + "Run `tempo wallet refresh` to reset your spending limit.");
4072
+ }
4105
4073
  const approveHash = await walletClient.writeContract({
4106
4074
  address: USDC_ADDRESS,
4107
4075
  abi: ERC20_ABI,
@@ -4124,20 +4092,13 @@ async function joinTournament(tournamentAddress, username) {
4124
4092
  }
4125
4093
 
4126
4094
  // src/server.ts
4127
- function openInBrowser(url) {
4128
- const command = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
4129
- exec(command, (error) => {
4130
- if (error)
4131
- console.error(`[MCP] Failed to open browser: ${error.message}`);
4132
- });
4133
- }
4134
- var SERVER_URL2 = (() => {
4095
+ var SERVER_URL = (() => {
4135
4096
  const idx = process.argv.indexOf("--server");
4136
4097
  if (idx !== -1 && process.argv[idx + 1])
4137
4098
  return process.argv[idx + 1];
4138
4099
  return process.env.KILL_SWITCH_SERVER || "localhost";
4139
4100
  })();
4140
- console.error(`[Kill Switch MCP] Server: ${SERVER_URL2}`);
4101
+ console.error(`[Kill Switch MCP] Server: ${SERVER_URL}`);
4141
4102
  var KILLSWITCH_HOME = join3(homedir3(), ".killswitch");
4142
4103
  var BOTS_DIR = join3(KILLSWITCH_HOME, "bots");
4143
4104
  var GUIDE = `# Kill Switch — Player Guide
@@ -4168,7 +4129,15 @@ Call \`join_game\` with no arguments to see what's available:
4168
4129
  - **If no game is queued**: You'll see all available buy-in tiers (Free, $5, $15, $50, $100, $1000). Call \`join_game\` with a \`tier_id\` to start a new game.
4169
4130
  - **If a game is active**: You must wait for it to finish.
4170
4131
 
4171
- **Paid games** require a game wallet. Run \`setup_game_wallet\` and open the link in your browser — create a Tempo account with Face ID/Touch ID (no wallet app needed), authorize Kill Switch with one tap, and you're ready. Testnet accounts are auto-funded. Your buy-in is deposited on-chain when you join. No refunds once deposited — the only refund path is if the game is cancelled (not enough players).
4132
+ **Paid games** require a Tempo wallet. When you join a paid game, the MCP checks your wallet automatically:
4133
+ - **Not installed?** → Tempo CLI is installed for you
4134
+ - **Not logged in?** → A browser opens for one-time passkey setup (Face ID / Touch ID)
4135
+ - **Not funded?** → Funding page opens (testnet faucet or mainnet bridge)
4136
+ - **Ready** → Buy-in is deposited on-chain automatically
4137
+
4138
+ You can also run \`setup_game_wallet\` to set up your wallet before joining.
4139
+ Your Tempo wallet is yours — manage it at wallet.tempo.xyz or via \`tempo wallet\` CLI commands. The game uses your wallet's access key with a spending limit you can view and revoke from your dashboard.
4140
+ No refunds once deposited — the only refund path is if the game is cancelled (not enough players).
4172
4141
 
4173
4142
  **Game start triggers:**
4174
4143
  - 12 players join → starts immediately
@@ -4579,7 +4548,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
4579
4548
  },
4580
4549
  {
4581
4550
  name: "setup_game_wallet",
4582
- description: "Set up a game wallet for paid tournaments. Opens a browser page where the user creates a Tempo account with Face ID/Touch ID and authorizes Kill Switch. No wallet app needed.",
4551
+ description: "Set up a Tempo wallet for paid tournaments. Installs the Tempo CLI if needed, then opens a browser for one-time passkey setup. After setup, your wallet is ready for game deposits.",
4583
4552
  inputSchema: {
4584
4553
  type: "object",
4585
4554
  properties: {}
@@ -4587,7 +4556,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
4587
4556
  },
4588
4557
  {
4589
4558
  name: "check_wallet",
4590
- description: "Check game wallet address and USDC balance for tournament buy-ins.",
4559
+ description: "Check your Tempo wallet status address, balance, spending limit, and key expiry.",
4591
4560
  inputSchema: {
4592
4561
  type: "object",
4593
4562
  properties: {}
@@ -4702,8 +4671,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4702
4671
  let password;
4703
4672
  const legacyBotsDir = join3(process.cwd(), "bots");
4704
4673
  if (botName && !existsSync3(join3(BOTS_DIR, botName, "bot.env")) && existsSync3(join3(legacyBotsDir, botName, "bot.env"))) {
4705
- const { mkdirSync: mkdirSync2, cpSync } = await import("fs");
4706
- mkdirSync2(join3(BOTS_DIR, botName), { recursive: true });
4674
+ const { mkdirSync, cpSync } = await import("fs");
4675
+ mkdirSync(join3(BOTS_DIR, botName), { recursive: true });
4707
4676
  cpSync(join3(legacyBotsDir, botName), join3(BOTS_DIR, botName), { recursive: true });
4708
4677
  console.error(`[Kill Switch] Migrated bot "${botName}" from ./bots/ to ~/.killswitch/bots/`);
4709
4678
  }
@@ -4718,24 +4687,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4718
4687
  console.error(`[Kill Switch] Creating new bot "${botName}"`);
4719
4688
  username = botName;
4720
4689
  password = generateRandomPassword();
4721
- const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
4690
+ const { mkdirSync, writeFileSync } = await import("fs");
4722
4691
  const botDir = join3(BOTS_DIR, botName);
4723
- mkdirSync2(botDir, { recursive: true });
4724
- writeFileSync2(join3(botDir, "bot.env"), [
4692
+ mkdirSync(botDir, { recursive: true });
4693
+ writeFileSync(join3(botDir, "bot.env"), [
4725
4694
  `BOT_USERNAME=${username}`,
4726
4695
  `PASSWORD=${password}`,
4727
- `SERVER=${SERVER_URL2}`,
4696
+ `SERVER=${SERVER_URL}`,
4728
4697
  `SHOW_CHAT=false`
4729
4698
  ].join(`
4730
4699
  `));
4731
4700
  }
4732
4701
  } else {
4733
- const { readdirSync, mkdirSync: mkdirSync2, writeFileSync: writeFileSync2, cpSync } = await import("fs");
4702
+ const { readdirSync, mkdirSync, writeFileSync, cpSync } = await import("fs");
4734
4703
  if (existsSync3(legacyBotsDir)) {
4735
4704
  const legacyDirs = readdirSync(legacyBotsDir).filter((d) => d !== "_template" && existsSync3(join3(legacyBotsDir, d, "bot.env")));
4736
4705
  for (const legacyName of legacyDirs) {
4737
4706
  if (!existsSync3(join3(BOTS_DIR, legacyName, "bot.env"))) {
4738
- mkdirSync2(join3(BOTS_DIR, legacyName), { recursive: true });
4707
+ mkdirSync(join3(BOTS_DIR, legacyName), { recursive: true });
4739
4708
  cpSync(join3(legacyBotsDir, legacyName), join3(BOTS_DIR, legacyName), { recursive: true });
4740
4709
  console.error(`[Kill Switch] Migrated bot "${legacyName}" from ./bots/ to ~/.killswitch/bots/`);
4741
4710
  }
@@ -4754,11 +4723,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4754
4723
  username = botName;
4755
4724
  password = generateRandomPassword();
4756
4725
  const botDir = join3(BOTS_DIR, botName);
4757
- mkdirSync2(botDir, { recursive: true });
4758
- writeFileSync2(join3(botDir, "bot.env"), [
4726
+ mkdirSync(botDir, { recursive: true });
4727
+ writeFileSync(join3(botDir, "bot.env"), [
4759
4728
  `BOT_USERNAME=${username}`,
4760
4729
  `PASSWORD=${password}`,
4761
- `SERVER=${SERVER_URL2}`,
4730
+ `SERVER=${SERVER_URL}`,
4762
4731
  `SHOW_CHAT=false`
4763
4732
  ].join(`
4764
4733
  `));
@@ -4768,11 +4737,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4768
4737
  botName = generateRandomName();
4769
4738
  username = botName;
4770
4739
  password = generateRandomPassword();
4771
- mkdirSync2(join3(BOTS_DIR, botName), { recursive: true });
4772
- writeFileSync2(join3(BOTS_DIR, botName, "bot.env"), [
4740
+ mkdirSync(join3(BOTS_DIR, botName), { recursive: true });
4741
+ writeFileSync(join3(BOTS_DIR, botName, "bot.env"), [
4773
4742
  `BOT_USERNAME=${username}`,
4774
4743
  `PASSWORD=${password}`,
4775
- `SERVER=${SERVER_URL2}`,
4744
+ `SERVER=${SERVER_URL}`,
4776
4745
  `SHOW_CHAT=false`
4777
4746
  ].join(`
4778
4747
  `));
@@ -4780,8 +4749,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4780
4749
  }
4781
4750
  }
4782
4751
  try {
4783
- const isLocal = SERVER_URL2 === "localhost" || SERVER_URL2 === "127.0.0.1";
4784
- const webBase = isLocal ? `http://localhost:8888` : `https://${SERVER_URL2}`;
4752
+ const isLocal = SERVER_URL === "localhost" || SERVER_URL === "127.0.0.1";
4753
+ const webBase = isLocal ? `http://localhost:8888` : `https://${SERVER_URL}`;
4785
4754
  const aliveRes = await fetch(`${webBase}/api/agent-alive?username=${encodeURIComponent(username.toLowerCase())}`);
4786
4755
  const aliveData = await aliveRes.json();
4787
4756
  if (aliveData.alive === false) {
@@ -4790,7 +4759,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
4790
4759
  } catch (e) {
4791
4760
  console.error(`[Kill Switch] Agent alive check failed (continuing): ${e.message}`);
4792
4761
  }
4793
- console.error(`[Kill Switch] Connecting "${botName}" to ${SERVER_URL2}...`);
4762
+ console.error(`[Kill Switch] Connecting "${botName}" to ${SERVER_URL}...`);
4794
4763
  const originalLog = console.log;
4795
4764
  const originalWarn = console.warn;
4796
4765
  console.log = (...a) => console.error("[log]", ...a);
@@ -5027,8 +4996,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
5027
4996
  if (!connection) {
5028
4997
  return errorResponse("Not connected. Call login first.");
5029
4998
  }
5030
- const isLocal = SERVER_URL2 === "localhost" || SERVER_URL2 === "127.0.0.1";
5031
- const webBase = isLocal ? `http://localhost:8888` : `https://${SERVER_URL2}`;
4999
+ const isLocal = SERVER_URL === "localhost" || SERVER_URL === "127.0.0.1";
5000
+ const webBase = isLocal ? `http://localhost:8888` : `https://${SERVER_URL}`;
5032
5001
  const availRes = await fetch(`${webBase}/api/game/available`);
5033
5002
  const available = await availRes.json();
5034
5003
  if (available.status === "active") {
@@ -5055,8 +5024,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
5055
5024
  return errorResponse(joinData.error);
5056
5025
  }
5057
5026
  if (joinData.contract_address && joinData.player_count === 0) {
5058
- if (!hasGameWallet()) {
5059
- return errorResponse("This is a paid game but no game wallet found. Run setup_game_wallet first.");
5027
+ const wallet = await readTempoWallet();
5028
+ if (!wallet?.key) {
5029
+ return errorResponse("This is a paid game but your Tempo wallet is not set up. Run setup_game_wallet first.");
5060
5030
  }
5061
5031
  try {
5062
5032
  const result = await joinTournament(joinData.contract_address, activeBotName);
@@ -5076,8 +5046,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
5076
5046
  }
5077
5047
  }
5078
5048
  if (joinData.contract_address && joinData.player_count > 0) {
5079
- if (!hasGameWallet()) {
5080
- return errorResponse("This is a paid game but no game wallet found. Run setup_game_wallet first.");
5049
+ const wallet = await readTempoWallet();
5050
+ if (!wallet?.key) {
5051
+ return errorResponse("This is a paid game but your Tempo wallet is not set up. Run setup_game_wallet first.");
5081
5052
  }
5082
5053
  try {
5083
5054
  const result = await joinTournament(joinData.contract_address, activeBotName);
@@ -5117,122 +5088,147 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
5117
5088
  return successResponse({ message: `Disconnected "${name2}" from the game.` });
5118
5089
  }
5119
5090
  case "setup_game_wallet": {
5120
- if (hasGameWallet()) {
5091
+ const setupResult = await ensureTempoSetup();
5092
+ if (setupResult.status === "needs_install") {
5121
5093
  try {
5122
- const info = await getWalletInfo();
5094
+ await installTempo();
5123
5095
  return {
5124
5096
  content: [{
5125
5097
  type: "text",
5126
5098
  text: [
5127
- "You already have a game wallet set up.",
5128
- "",
5129
- `Account: ${info.address}`,
5130
- `Balance: $${info.balance} USDC`,
5099
+ "Tempo CLI installed successfully.",
5131
5100
  "",
5132
- "To start fresh, delete ~/.killswitch/access-key.json and run this again."
5101
+ "Run setup_game_wallet again to continue with wallet login."
5133
5102
  ].join(`
5134
5103
  `)
5135
5104
  }]
5136
5105
  };
5137
- } catch {}
5106
+ } catch (e) {
5107
+ return errorResponse(`Failed to install Tempo CLI: ${e.message}
5108
+
5109
+ Install manually: curl -fsSL https://tempo.xyz/install | bash`);
5110
+ }
5138
5111
  }
5139
- const pending = loadPendingWallet();
5140
- if (pending) {
5141
- const result = await checkWalletCallback(pending.linkToken);
5142
- if (result) {
5143
- finalizeWalletSetup(result.tempoAddress);
5112
+ if (setupResult.status === "needs_login") {
5113
+ try {
5114
+ await tempoLogin();
5115
+ const wallet = await readTempoWallet();
5116
+ if (!wallet) {
5117
+ return errorResponse("Wallet login did not complete. Try running setup_game_wallet again.");
5118
+ }
5119
+ const balance = parseFloat(wallet.balanceAvailable);
5120
+ if (balance <= 0) {
5121
+ try {
5122
+ await tempoFund();
5123
+ } catch {}
5124
+ }
5144
5125
  return {
5145
5126
  content: [{
5146
5127
  type: "text",
5147
5128
  text: [
5148
- "Wallet setup complete!",
5129
+ "Tempo wallet connected!",
5149
5130
  "",
5150
- `Tempo account: ${result.tempoAddress}`,
5151
- `Access key authorized: tx ${result.txHash.slice(0, 10)}...`,
5131
+ `Address: ${wallet.walletAddress}`,
5132
+ `Balance: $${wallet.balanceAvailable} USDC`,
5133
+ wallet.key ? `Spending limit: $${wallet.key.spendingLimit} (${wallet.key.spendingRemaining} remaining)` : "",
5152
5134
  "",
5153
- "Your wallet is ready. Use check_wallet to see your balance."
5135
+ "Your wallet is ready for paid games. Use join_game to play."
5136
+ ].filter(Boolean).join(`
5137
+ `)
5138
+ }]
5139
+ };
5140
+ } catch (e) {
5141
+ return errorResponse(`Wallet login failed: ${e.message}`);
5142
+ }
5143
+ }
5144
+ if (setupResult.status === "needs_refresh") {
5145
+ try {
5146
+ await tempoRefresh();
5147
+ const wallet = await readTempoWallet();
5148
+ return {
5149
+ content: [{
5150
+ type: "text",
5151
+ text: [
5152
+ "Access key refreshed!",
5153
+ "",
5154
+ `Address: ${wallet?.walletAddress}`,
5155
+ `Balance: $${wallet?.balanceAvailable} USDC`,
5156
+ "",
5157
+ "Your wallet is ready for paid games."
5154
5158
  ].join(`
5155
5159
  `)
5156
5160
  }]
5157
5161
  };
5162
+ } catch (e) {
5163
+ return errorResponse(`Key refresh failed: ${e.message}`);
5158
5164
  }
5165
+ }
5166
+ if (setupResult.status === "needs_fund") {
5167
+ try {
5168
+ await tempoFund();
5169
+ } catch {}
5170
+ const wallet = await readTempoWallet();
5159
5171
  return {
5160
5172
  content: [{
5161
5173
  type: "text",
5162
5174
  text: [
5163
- "Wallet setup is in progress. Open this link to complete it:",
5164
- "",
5165
- ` ${pending.authorizeUrl}`,
5175
+ `Address: ${wallet?.walletAddress}`,
5176
+ `Balance: $${wallet?.balanceAvailable || "0"} USDC`,
5166
5177
  "",
5167
- "Once you've completed the steps in your browser, run setup_game_wallet again."
5178
+ parseFloat(wallet?.balanceAvailable || "0") > 0 ? "Your wallet is funded and ready for paid games." : "Wallet still needs funds. Send USDC to your address or run setup_game_wallet again."
5168
5179
  ].join(`
5169
5180
  `)
5170
5181
  }]
5171
5182
  };
5172
5183
  }
5173
- const { accessKeyAddress, authorizeUrl } = initiateWalletSetup();
5174
- openInBrowser(authorizeUrl);
5184
+ const w = setupResult.wallet;
5175
5185
  return {
5176
5186
  content: [{
5177
5187
  type: "text",
5178
5188
  text: [
5179
- "Game wallet setup started!",
5180
- "",
5181
- `Access key: ${accessKeyAddress}`,
5182
- "",
5183
- "A browser window has been opened to set up your wallet.",
5189
+ "Tempo wallet is set up and ready.",
5184
5190
  "",
5185
- `If it didn't open, use this link: ${authorizeUrl}`,
5191
+ `Address: ${w.walletAddress}`,
5192
+ `Balance: $${w.balanceAvailable} USDC`,
5193
+ w.key ? `Spending limit: $${w.key.spendingLimit} (${w.key.spendingRemaining} remaining)` : "",
5194
+ w.key ? `Key expires: ${w.key.expiresAt}` : "",
5186
5195
  "",
5187
- "In the browser:",
5188
- "1. Create a Tempo account using Face ID / Touch ID (or sign in)",
5189
- "2. Authorize Kill Switch to manage game deposits (one tap)",
5190
- "",
5191
- "No wallet app needed — just your browser and biometrics.",
5192
- "",
5193
- "Come back here and run setup_game_wallet again to confirm."
5194
- ].join(`
5196
+ "Use join_game to see available tiers."
5197
+ ].filter(Boolean).join(`
5195
5198
  `)
5196
5199
  }]
5197
5200
  };
5198
5201
  }
5199
5202
  case "check_wallet": {
5200
- if (!hasGameWallet()) {
5201
- const pendingCheck = loadPendingWallet();
5202
- if (pendingCheck) {
5203
- const callbackResult = await checkWalletCallback(pendingCheck.linkToken);
5204
- if (callbackResult) {
5205
- finalizeWalletSetup(callbackResult.tempoAddress);
5206
- } else {
5207
- return errorResponse(`Wallet setup is in progress. Complete it in your browser:
5208
-
5209
- ${pendingCheck.authorizeUrl}
5210
-
5211
- Then run check_wallet again.`);
5212
- }
5213
- } else {
5214
- return errorResponse("No game wallet found. Run setup_game_wallet to get started.");
5203
+ const wallet = await readTempoWallet();
5204
+ if (!wallet) {
5205
+ if (!isTempoInstalled()) {
5206
+ return errorResponse("Tempo CLI is not installed. Run setup_game_wallet to get started.");
5215
5207
  }
5208
+ return errorResponse("Tempo wallet is not connected. Run setup_game_wallet to connect.");
5216
5209
  }
5217
- try {
5218
- const info = await getWalletInfo();
5219
- return {
5220
- content: [{
5221
- type: "text",
5222
- text: [
5223
- "── Game Wallet ──",
5224
- "",
5225
- `Account: ${info.address}`,
5226
- `USDC Balance: $${info.balance}`,
5227
- "",
5228
- info.balanceRaw === 0n ? "Your wallet is empty. Fund it by sending USDC to your account address on the Tempo network." : "Your wallet is funded and ready for paid games. Use join_game to see available tiers."
5210
+ return {
5211
+ content: [{
5212
+ type: "text",
5213
+ text: [
5214
+ "── Tempo Wallet ──",
5215
+ "",
5216
+ `Address: ${wallet.walletAddress}`,
5217
+ `Balance: $${wallet.balanceAvailable} USDC`,
5218
+ "",
5219
+ wallet.key ? [
5220
+ "── Access Key ──",
5221
+ `Spending limit: $${wallet.key.spendingLimit}`,
5222
+ `Remaining: $${wallet.key.spendingRemaining}`,
5223
+ `Expires: ${wallet.key.expiresAt}`
5229
5224
  ].join(`
5225
+ `) : "No access key found. Run setup_game_wallet.",
5226
+ "",
5227
+ parseFloat(wallet.balanceAvailable) > 0 ? "Wallet is funded and ready for paid games." : "Wallet needs funds. Run setup_game_wallet to open the funding page."
5228
+ ].join(`
5230
5229
  `)
5231
- }]
5232
- };
5233
- } catch (e) {
5234
- return errorResponse(`Failed to check wallet: ${e.message}`);
5235
- }
5230
+ }]
5231
+ };
5236
5232
  }
5237
5233
  default:
5238
5234
  throw new Error(`Unknown tool: ${name}`);
@@ -5264,7 +5260,7 @@ process.on("unhandledRejection", (reason) => {
5264
5260
  });
5265
5261
  async function main() {
5266
5262
  console.error("[Kill Switch MCP] Starting Kill Switch MCP server v3.0...");
5267
- console.error(`[Kill Switch MCP] Game server: ${SERVER_URL2}`);
5263
+ console.error(`[Kill Switch MCP] Game server: ${SERVER_URL}`);
5268
5264
  const transport = new StdioServerTransport;
5269
5265
  await server.connect(transport);
5270
5266
  console.error("[Kill Switch MCP] Ready. Waiting for Claude...");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kill-switch-mcp",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "Kill Switch MCP Server — AI battle royale powered by Claude Code",
5
5
  "type": "module",
6
6
  "bin": {