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.
- package/dist/server.js +258 -263
- 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 {
|
|
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
|
-
|
|
3861
|
-
var
|
|
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 =
|
|
3865
|
-
|
|
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
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
}
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
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
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
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
|
|
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
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
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
|
|
3998
|
-
|
|
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
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
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
|
|
4016
|
-
return
|
|
3989
|
+
function getPublicClient() {
|
|
3990
|
+
return createPublicClient({
|
|
3991
|
+
chain: tempoChain,
|
|
3992
|
+
transport: http()
|
|
3993
|
+
});
|
|
4017
3994
|
}
|
|
4018
|
-
async function
|
|
4019
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
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 =
|
|
4082
|
-
if (!wallet) {
|
|
4083
|
-
throw new Error("No
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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: ${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4706
|
-
|
|
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
|
|
4690
|
+
const { mkdirSync, writeFileSync } = await import("fs");
|
|
4722
4691
|
const botDir = join3(BOTS_DIR, botName);
|
|
4723
|
-
|
|
4724
|
-
|
|
4692
|
+
mkdirSync(botDir, { recursive: true });
|
|
4693
|
+
writeFileSync(join3(botDir, "bot.env"), [
|
|
4725
4694
|
`BOT_USERNAME=${username}`,
|
|
4726
4695
|
`PASSWORD=${password}`,
|
|
4727
|
-
`SERVER=${
|
|
4696
|
+
`SERVER=${SERVER_URL}`,
|
|
4728
4697
|
`SHOW_CHAT=false`
|
|
4729
4698
|
].join(`
|
|
4730
4699
|
`));
|
|
4731
4700
|
}
|
|
4732
4701
|
} else {
|
|
4733
|
-
const { readdirSync, mkdirSync
|
|
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
|
-
|
|
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
|
-
|
|
4758
|
-
|
|
4726
|
+
mkdirSync(botDir, { recursive: true });
|
|
4727
|
+
writeFileSync(join3(botDir, "bot.env"), [
|
|
4759
4728
|
`BOT_USERNAME=${username}`,
|
|
4760
4729
|
`PASSWORD=${password}`,
|
|
4761
|
-
`SERVER=${
|
|
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
|
-
|
|
4772
|
-
|
|
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=${
|
|
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 =
|
|
4784
|
-
const webBase = isLocal ? `http://localhost:8888` : `https://${
|
|
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 ${
|
|
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 =
|
|
5031
|
-
const webBase = isLocal ? `http://localhost:8888` : `https://${
|
|
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
|
-
|
|
5059
|
-
|
|
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
|
-
|
|
5080
|
-
|
|
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
|
-
|
|
5091
|
+
const setupResult = await ensureTempoSetup();
|
|
5092
|
+
if (setupResult.status === "needs_install") {
|
|
5121
5093
|
try {
|
|
5122
|
-
|
|
5094
|
+
await installTempo();
|
|
5123
5095
|
return {
|
|
5124
5096
|
content: [{
|
|
5125
5097
|
type: "text",
|
|
5126
5098
|
text: [
|
|
5127
|
-
"
|
|
5099
|
+
"Tempo CLI installed successfully.",
|
|
5128
5100
|
"",
|
|
5129
|
-
|
|
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
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
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
|
-
"
|
|
5129
|
+
"Tempo wallet connected!",
|
|
5149
5130
|
"",
|
|
5150
|
-
`
|
|
5151
|
-
`
|
|
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
|
|
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
|
-
|
|
5164
|
-
""
|
|
5165
|
-
` ${pending.authorizeUrl}`,
|
|
5175
|
+
`Address: ${wallet?.walletAddress}`,
|
|
5176
|
+
`Balance: $${wallet?.balanceAvailable || "0"} USDC`,
|
|
5166
5177
|
"",
|
|
5167
|
-
"
|
|
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
|
|
5184
|
+
const w = setupResult.wallet;
|
|
5174
5185
|
return {
|
|
5175
5186
|
content: [{
|
|
5176
5187
|
type: "text",
|
|
5177
5188
|
text: [
|
|
5178
|
-
"
|
|
5179
|
-
"",
|
|
5180
|
-
`Access key: ${accessKeyAddress}`,
|
|
5181
|
-
"",
|
|
5182
|
-
"━━━ OPEN THIS LINK ━━━",
|
|
5183
|
-
"",
|
|
5184
|
-
` ${authorizeUrl}`,
|
|
5189
|
+
"Tempo wallet is set up and ready.",
|
|
5185
5190
|
"",
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
5202
|
-
|
|
5203
|
-
if (
|
|
5204
|
-
|
|
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
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
"",
|
|
5229
|
-
|
|
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: ${
|
|
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...");
|