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.
- package/dist/server.js +256 -260
- 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 {
|
|
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
|
|
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
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
}
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
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
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
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
|
|
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
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
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
|
|
3991
|
-
|
|
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
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
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
|
|
4009
|
-
return
|
|
3989
|
+
function getPublicClient() {
|
|
3990
|
+
return createPublicClient({
|
|
3991
|
+
chain: tempoChain,
|
|
3992
|
+
transport: http()
|
|
3993
|
+
});
|
|
4010
3994
|
}
|
|
4011
|
-
async function
|
|
4012
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
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 =
|
|
4075
|
-
if (!wallet) {
|
|
4076
|
-
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.");
|
|
4077
4040
|
}
|
|
4078
4041
|
const pub = getPublicClient();
|
|
4079
|
-
const walletClient = getWalletClient(wallet.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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: ${
|
|
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,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
|
-
|
|
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
|
-
"
|
|
5128
|
-
"",
|
|
5129
|
-
`Account: ${info.address}`,
|
|
5130
|
-
`Balance: $${info.balance} USDC`,
|
|
5099
|
+
"Tempo CLI installed successfully.",
|
|
5131
5100
|
"",
|
|
5132
|
-
"
|
|
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
|
|
5174
|
-
openInBrowser(authorizeUrl);
|
|
5184
|
+
const w = setupResult.wallet;
|
|
5175
5185
|
return {
|
|
5176
5186
|
content: [{
|
|
5177
5187
|
type: "text",
|
|
5178
5188
|
text: [
|
|
5179
|
-
"
|
|
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
|
-
`
|
|
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
|
-
"
|
|
5188
|
-
|
|
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
|
-
|
|
5201
|
-
|
|
5202
|
-
if (
|
|
5203
|
-
|
|
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
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
"",
|
|
5228
|
-
|
|
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: ${
|
|
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...");
|