kill-switch-mcp 1.2.6 → 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 +258 -263
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -3843,7 +3843,8 @@ function formatWorldState(state, stateAgeMs) {
3843
3843
  }
3844
3844
 
3845
3845
  // src/wallet/index.ts
3846
- import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
3846
+ import { execFile } from "child_process";
3847
+ import { readFileSync, existsSync as existsSync2 } from "fs";
3847
3848
  import { join as join2 } from "path";
3848
3849
  import { homedir as homedir2 } from "os";
3849
3850
  import {
@@ -3851,61 +3852,39 @@ import {
3851
3852
  createWalletClient,
3852
3853
  http,
3853
3854
  formatUnits,
3854
- defineChain,
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
- var WALLET_DIR = join2(homedir2(), ".killswitch");
3861
- var WALLET_FILE = join2(WALLET_DIR, "access-key.json");
3862
- var TEMPO_RPC = process.env.TEMPO_RPC_URL || "https://rpc.moderato.tempo.xyz";
3859
+ import { tempoModerato, tempo } from "viem/chains";
3860
+ var TEMPO_BIN = join2(homedir2(), ".tempo", "bin", "tempo");
3863
3861
  var TEMPO_CHAIN_ID = parseInt(process.env.TEMPO_CHAIN_ID || "42431");
3864
- var tempoChain = defineChain({
3865
- id: TEMPO_CHAIN_ID,
3866
- name: TEMPO_CHAIN_ID === 4217 ? "Tempo" : "Tempo Testnet",
3867
- nativeCurrency: { name: "USD", symbol: "USD", decimals: 18 },
3868
- rpcUrls: {
3869
- default: { http: [TEMPO_RPC] }
3870
- }
3871
- });
3862
+ var tempoChain = TEMPO_CHAIN_ID === 4217 ? tempo : tempoModerato;
3863
+ var TEMPO_NETWORK = TEMPO_CHAIN_ID === 4217 ? "mainnet" : "testnet";
3872
3864
  var USDC_ADDRESS = process.env.USDC_ADDRESS || "0x20c0000000000000000000000000000000000000";
3873
- var FACTORY_ADDRESS = process.env.FACTORY_ADDRESS;
3874
- var PENDING_FILE = join2(WALLET_DIR, "pending-wallet.json");
3875
- var SERVER_URL = (() => {
3876
- const idx = process.argv.indexOf("--server");
3877
- if (idx !== -1 && process.argv[idx + 1])
3878
- return process.argv[idx + 1];
3879
- return process.env.KILL_SWITCH_SERVER || "localhost";
3880
- })();
3881
- function getWebBase() {
3882
- const isLocal = SERVER_URL === "localhost" || SERVER_URL === "127.0.0.1";
3883
- return isLocal ? "http://localhost:8888" : `https://${SERVER_URL}`;
3884
- }
3885
- function generateLinkToken() {
3886
- const bytes = new Uint8Array(16);
3887
- crypto.getRandomValues(bytes);
3888
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
3889
- }
3890
- function savePendingWallet(pending) {
3891
- mkdirSync(WALLET_DIR, { recursive: true });
3892
- writeFileSync(PENDING_FILE, JSON.stringify(pending, null, 2), { mode: 384 });
3893
- }
3894
- function loadPendingWallet() {
3895
- try {
3896
- if (existsSync2(PENDING_FILE)) {
3897
- return JSON.parse(readFileSync(PENDING_FILE, "utf-8"));
3898
- }
3899
- } catch {}
3900
- 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
+ });
3901
3876
  }
3902
- function clearPendingWallet() {
3903
- try {
3904
- if (existsSync2(PENDING_FILE)) {
3905
- const fs = __require("fs");
3906
- fs.unlinkSync(PENDING_FILE);
3907
- }
3908
- } 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
+ });
3909
3888
  }
3910
3889
  var ERC20_ABI = [
3911
3890
  {
@@ -3924,13 +3903,6 @@ var ERC20_ABI = [
3924
3903
  inputs: [{ name: "account", type: "address" }],
3925
3904
  outputs: [{ name: "", type: "uint256" }],
3926
3905
  stateMutability: "view"
3927
- },
3928
- {
3929
- name: "decimals",
3930
- type: "function",
3931
- inputs: [],
3932
- outputs: [{ name: "", type: "uint8" }],
3933
- stateMutability: "view"
3934
3906
  }
3935
3907
  ];
3936
3908
  var TOURNAMENT_ABI = [
@@ -3948,34 +3920,6 @@ var TOURNAMENT_ABI = [
3948
3920
  outputs: [{ name: "", type: "uint256" }],
3949
3921
  stateMutability: "view"
3950
3922
  },
3951
- {
3952
- name: "state",
3953
- type: "function",
3954
- inputs: [],
3955
- outputs: [{ name: "", type: "uint8" }],
3956
- stateMutability: "view"
3957
- },
3958
- {
3959
- name: "getDepositorCount",
3960
- type: "function",
3961
- inputs: [],
3962
- outputs: [{ name: "", type: "uint256" }],
3963
- stateMutability: "view"
3964
- },
3965
- {
3966
- name: "maxPlayers",
3967
- type: "function",
3968
- inputs: [],
3969
- outputs: [{ name: "", type: "uint8" }],
3970
- stateMutability: "view"
3971
- },
3972
- {
3973
- name: "startTime",
3974
- type: "function",
3975
- inputs: [],
3976
- outputs: [{ name: "", type: "uint256" }],
3977
- stateMutability: "view"
3978
- },
3979
3923
  {
3980
3924
  name: "hasDeposited",
3981
3925
  type: "function",
@@ -3984,25 +3928,55 @@ var TOURNAMENT_ABI = [
3984
3928
  stateMutability: "view"
3985
3929
  }
3986
3930
  ];
3987
- 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;
3988
3949
  try {
3989
- if (existsSync2(WALLET_FILE)) {
3990
- return JSON.parse(readFileSync(WALLET_FILE, "utf-8"));
3991
- }
3992
- } catch (e) {
3993
- 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;
3994
3970
  }
3995
- return null;
3996
3971
  }
3997
- function saveWallet(wallet) {
3998
- mkdirSync(WALLET_DIR, { recursive: true });
3999
- writeFileSync(WALLET_FILE, JSON.stringify(wallet, null, 2), { mode: 384 });
3972
+ async function tempoLogin() {
3973
+ await runTempoInteractive("wallet", "login", "-n", TEMPO_NETWORK);
4000
3974
  }
4001
- function getPublicClient() {
4002
- return createPublicClient({
4003
- chain: tempoChain,
4004
- transport: http()
4005
- });
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);
4006
3980
  }
4007
3981
  function getWalletClient(privateKey, parentAddress) {
4008
3982
  const account = Account.fromSecp256k1(privateKey, { access: parentAddress });
@@ -4012,78 +3986,60 @@ function getWalletClient(privateKey, parentAddress) {
4012
3986
  transport: http()
4013
3987
  });
4014
3988
  }
4015
- function hasGameWallet() {
4016
- return existsSync2(WALLET_FILE);
3989
+ function getPublicClient() {
3990
+ return createPublicClient({
3991
+ chain: tempoChain,
3992
+ transport: http()
3993
+ });
4017
3994
  }
4018
- async function getWalletInfo() {
4019
- 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();
4020
4003
  if (!wallet) {
4021
- 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
+ };
4022
4029
  }
4023
- const pub = getPublicClient();
4024
- const balanceRaw = await pub.readContract({
4025
- address: USDC_ADDRESS,
4026
- abi: ERC20_ABI,
4027
- functionName: "balanceOf",
4028
- args: [wallet.accountAddress]
4029
- });
4030
- const balance = formatUnits(balanceRaw, 6);
4031
- return {
4032
- address: wallet.accountAddress,
4033
- balance,
4034
- balanceRaw
4035
- };
4036
- }
4037
- function initiateWalletSetup() {
4038
- const privateKey = generatePrivateKey();
4039
- const account = privateKeyToAccount(privateKey);
4040
- const linkToken = generateLinkToken();
4041
- const chain = TEMPO_CHAIN_ID === 4217 ? "mainnet" : "testnet";
4042
- const webBase = getWebBase();
4043
- const authorizeUrl = `${webBase}/authorize?key=${account.address}&t=${linkToken}&chain=${chain}`;
4044
- const pending = {
4045
- accessKeyPrivate: privateKey,
4046
- accessKeyAddress: account.address,
4047
- linkToken,
4048
- authorizeUrl,
4049
- createdAt: new Date().toISOString()
4050
- };
4051
- savePendingWallet(pending);
4052
4030
  return {
4053
- accessKeyAddress: account.address,
4054
- linkToken,
4055
- authorizeUrl
4031
+ status: "ready",
4032
+ message: "Tempo wallet is ready.",
4033
+ wallet
4056
4034
  };
4057
4035
  }
4058
- async function checkWalletCallback(linkToken) {
4059
- try {
4060
- const webBase = getWebBase();
4061
- const response = await fetch(`${webBase}/api/wallet-callback?t=${linkToken}`);
4062
- const data = await response.json();
4063
- if (data.status === "complete" && data.tempoAddress) {
4064
- return { tempoAddress: data.tempoAddress, txHash: data.txHash };
4065
- }
4066
- } catch {}
4067
- return null;
4068
- }
4069
- function finalizeWalletSetup(tempoAddress) {
4070
- const pending = loadPendingWallet();
4071
- if (!pending)
4072
- throw new Error("No pending wallet setup found.");
4073
- saveWallet({
4074
- accessKeyPrivate: pending.accessKeyPrivate,
4075
- accountAddress: tempoAddress,
4076
- createdAt: new Date().toISOString()
4077
- });
4078
- clearPendingWallet();
4079
- }
4080
4036
  async function joinTournament(tournamentAddress, username) {
4081
- const wallet = loadWallet();
4082
- if (!wallet) {
4083
- 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.");
4084
4040
  }
4085
4041
  const pub = getPublicClient();
4086
- const walletClient = getWalletClient(wallet.accessKeyPrivate, wallet.accountAddress);
4042
+ const walletClient = getWalletClient(wallet.key.privateKey, wallet.walletAddress);
4087
4043
  const buyIn = await pub.readContract({
4088
4044
  address: tournamentAddress,
4089
4045
  abi: TOURNAMENT_ABI,
@@ -4093,7 +4049,7 @@ async function joinTournament(tournamentAddress, username) {
4093
4049
  address: USDC_ADDRESS,
4094
4050
  abi: ERC20_ABI,
4095
4051
  functionName: "balanceOf",
4096
- args: [wallet.accountAddress]
4052
+ args: [wallet.walletAddress]
4097
4053
  });
4098
4054
  if (balance < buyIn) {
4099
4055
  const needed = formatUnits(buyIn, 6);
@@ -4104,11 +4060,16 @@ async function joinTournament(tournamentAddress, username) {
4104
4060
  address: tournamentAddress,
4105
4061
  abi: TOURNAMENT_ABI,
4106
4062
  functionName: "hasDeposited",
4107
- args: [wallet.accountAddress]
4063
+ args: [wallet.walletAddress]
4108
4064
  });
4109
4065
  if (alreadyDeposited) {
4110
4066
  throw new Error("You have already deposited in this tournament.");
4111
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
+ }
4112
4073
  const approveHash = await walletClient.writeContract({
4113
4074
  address: USDC_ADDRESS,
4114
4075
  abi: ERC20_ABI,
@@ -4131,13 +4092,13 @@ async function joinTournament(tournamentAddress, username) {
4131
4092
  }
4132
4093
 
4133
4094
  // src/server.ts
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,123 +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.",
5099
+ "Tempo CLI installed successfully.",
5128
5100
  "",
5129
- `Account: ${info.address}`,
5130
- `Balance: $${info.balance} USDC`,
5131
- "",
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();
5184
+ const w = setupResult.wallet;
5174
5185
  return {
5175
5186
  content: [{
5176
5187
  type: "text",
5177
5188
  text: [
5178
- "Game wallet setup started!",
5179
- "",
5180
- `Access key: ${accessKeyAddress}`,
5181
- "",
5182
- "━━━ OPEN THIS LINK ━━━",
5183
- "",
5184
- ` ${authorizeUrl}`,
5189
+ "Tempo wallet is set up and ready.",
5185
5190
  "",
5186
- "This will open a page where you:",
5187
- "1. Create a Tempo account using Face ID / Touch ID (or sign in)",
5188
- "2. Authorize Kill Switch to manage game deposits (one tap)",
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}` : "",
5189
5195
  "",
5190
- "No wallet app needed just your browser and biometrics.",
5191
- "",
5192
- "━━━ AFTER YOU'RE DONE ━━━",
5193
- "",
5194
- "Come back here and run setup_game_wallet again to confirm."
5195
- ].join(`
5196
+ "Use join_game to see available tiers."
5197
+ ].filter(Boolean).join(`
5196
5198
  `)
5197
5199
  }]
5198
5200
  };
5199
5201
  }
5200
5202
  case "check_wallet": {
5201
- if (!hasGameWallet()) {
5202
- const pendingCheck = loadPendingWallet();
5203
- if (pendingCheck) {
5204
- const callbackResult = await checkWalletCallback(pendingCheck.linkToken);
5205
- if (callbackResult) {
5206
- finalizeWalletSetup(callbackResult.tempoAddress);
5207
- } else {
5208
- return errorResponse(`Wallet setup is in progress. Complete it in your browser:
5209
-
5210
- ${pendingCheck.authorizeUrl}
5211
-
5212
- Then run check_wallet again.`);
5213
- }
5214
- } else {
5215
- 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.");
5216
5207
  }
5208
+ return errorResponse("Tempo wallet is not connected. Run setup_game_wallet to connect.");
5217
5209
  }
5218
- try {
5219
- const info = await getWalletInfo();
5220
- return {
5221
- content: [{
5222
- type: "text",
5223
- text: [
5224
- "── Game Wallet ──",
5225
- "",
5226
- `Account: ${info.address}`,
5227
- `USDC Balance: $${info.balance}`,
5228
- "",
5229
- 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}`
5230
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(`
5231
5229
  `)
5232
- }]
5233
- };
5234
- } catch (e) {
5235
- return errorResponse(`Failed to check wallet: ${e.message}`);
5236
- }
5230
+ }]
5231
+ };
5237
5232
  }
5238
5233
  default:
5239
5234
  throw new Error(`Unknown tool: ${name}`);
@@ -5265,7 +5260,7 @@ process.on("unhandledRejection", (reason) => {
5265
5260
  });
5266
5261
  async function main() {
5267
5262
  console.error("[Kill Switch MCP] Starting Kill Switch MCP server v3.0...");
5268
- console.error(`[Kill Switch MCP] Game server: ${SERVER_URL2}`);
5263
+ console.error(`[Kill Switch MCP] Game server: ${SERVER_URL}`);
5269
5264
  const transport = new StdioServerTransport;
5270
5265
  await server.connect(transport);
5271
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.6",
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": {