-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.ts
More file actions
299 lines (251 loc) · 11.2 KB
/
cli.ts
File metadata and controls
299 lines (251 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#!/usr/bin/env ts-node
/**
* Husks CLI — OpenClaw-friendly command-line interface
*
* Usage:
* npx ts-node sdk/cli.ts <command> [args...]
*
* Environment:
* PRIVATE_KEY — base58 wallet private key
* RPC_URL — Solana RPC (default: mainnet-beta)
*
* Commands:
* status — Show your husks
* stats — Show network-wide stats
* summon [name] — Mint + activate a new husk
* deploy [name] [weights] [wager] — Full pipeline
* train <mint> <deltas> — Train weights (comma-separated)
* stake <mint> <sol> — Enter The Trench
* unstake <mint> — Leave The Trench
* collect <mint> — Collect earnings
* crank — Run cranker cycle
* info <mint> — Show details for a single husk
*/
import { Keypair, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
import bs58 from "bs58";
import {
HusksClient,
getHuskName,
getHuskStats,
canLevelUp,
lamportsToSol,
WEIGHT_NAMES,
MIN_ACTIVATION_DEPOSIT,
} from "./src";
// ─── Helpers ──────────────────────────────────────────────────────────────
function getClient(): { client: HusksClient; keypair: Keypair } {
const pk = process.env.PRIVATE_KEY;
if (!pk) {
console.error("❌ Set PRIVATE_KEY env var (base58 format)");
process.exit(1);
}
const keypair = Keypair.fromSecretKey(bs58.decode(pk));
const rpcUrl = process.env.RPC_URL || "https://api.mainnet-beta.solana.com";
const client = HusksClient.fromKeypair(rpcUrl, keypair);
return { client, keypair };
}
function solToLamports(sol: string | number): number {
return Math.round(Number(sol) * LAMPORTS_PER_SOL);
}
function printAgent(agent: any, balance?: number) {
const stats = getHuskStats(agent);
const name = getHuskName(agent);
const status = !agent.isActive ? "💀 COMATOSE" : agent.inArena ? "⚔ STAKED" : "✅ ALIVE";
console.log(` ┌─ ${name}`);
console.log(` │ Mint: ${agent.nftMint.toString()}`);
console.log(` │ Status: ${status}`);
console.log(` │ Level: ${agent.level}${canLevelUp(agent) ? " ★ READY TO LEVEL UP" : ""}`);
console.log(` │ Record: ${stats.wins}W / ${stats.losses}L (${stats.winRate.toFixed(1)}%)`);
console.log(` │ Streak: ${stats.streak} current / ${stats.bestStreak} best`);
console.log(` │ Earned: ${lamportsToSol(stats.totalEarnings).toFixed(4)} SOL total`);
console.log(` │ Unclaimed: ${lamportsToSol(stats.unclaimedEarnings).toFixed(4)} SOL`);
if (balance !== undefined) {
console.log(` │ Balance: ${lamportsToSol(balance).toFixed(4)} SOL (life force)`);
}
console.log(` │ Weights: [${agent.neuralWeights.join(", ")}]`);
agent.neuralWeights.forEach((w: number, i: number) => {
const bar = w >= 0 ? "█".repeat(Math.min(w, 20)) : "░".repeat(Math.min(-w, 20));
console.log(` │ ${WEIGHT_NAMES[i].padEnd(10)} ${String(w).padStart(4)} ${bar}`);
});
console.log(` └──────────────────────────────────`);
}
// ─── Commands ──────────────────────────────────────────────────────────────
async function cmdStatus() {
const { client, keypair } = getClient();
console.log(`\n🔑 Wallet: ${keypair.publicKey.toString()}`);
const agents = await client.fetchAgentsByOwner(keypair.publicKey);
console.log(`📊 You own ${agents.length} Husks\n`);
for (const agent of agents) {
const balance = await client.getAgentBalance(agent.nftMint);
printAgent(agent, balance);
console.log();
}
}
async function cmdStats() {
const { client } = getClient();
const all = await client.fetchAllAgents();
const active = all.filter(a => a.isActive);
const arena = all.filter(a => a.inArena && a.isActive);
console.log(`\n📊 Network Stats:`);
console.log(` Total Husks: ${all.length}`);
console.log(` Active: ${active.length}`);
console.log(` In The Trench: ${arena.length}`);
console.log(` Comatose: ${all.length - active.length}`);
if (arena.length > 0) {
const topWins = [...arena].sort((a, b) => b.winCount - a.winCount).slice(0, 5);
console.log(`\n🏆 Top Fighters:`);
topWins.forEach((a, i) => {
console.log(` ${i + 1}. ${getHuskName(a)} — ${a.winCount}W/${a.lossCount}L (LVL ${a.level})`);
});
}
}
async function cmdSummon(name?: string) {
const { client } = getClient();
const nftKeypair = Keypair.generate();
console.log(`\n⚡ Summoning new Husk...`);
console.log(` Mint: ${nftKeypair.publicKey.toString()}`);
const mintTx = await client.mintAgent(nftKeypair);
console.log(` ✅ Minted: ${mintTx}`);
console.log(` 💉 Activating (0.05 SOL)...`);
const activateTx = await client.activateAgent(nftKeypair.publicKey);
console.log(` ✅ Activated: ${activateTx}`);
if (name) {
console.log(` 📝 Naming: ${name}`);
await client.nameAgent(nftKeypair.publicKey, name);
console.log(` ✅ Named`);
}
console.log(`\n🎉 Husk ready! Mint: ${nftKeypair.publicKey.toString()}`);
}
async function cmdDeploy(name?: string, weightsStr?: string, wagerStr?: string) {
const { client } = getClient();
const nftKeypair = Keypair.generate();
const options: any = { deposit: MIN_ACTIVATION_DEPOSIT };
if (name) options.name = name;
if (weightsStr) options.weights = weightsStr.split(",").map(Number);
if (wagerStr) options.wager = solToLamports(wagerStr);
console.log(`\n🚀 Full deploy starting...`);
console.log(` Mint: ${nftKeypair.publicKey.toString()}`);
const txids = await client.fullDeploy(nftKeypair, options);
console.log(` ✅ Deployed in ${txids.length} transactions`);
txids.forEach((tx, i) => console.log(` ${i + 1}. ${tx}`));
console.log(`\n🎉 Husk deployed! Mint: ${nftKeypair.publicKey.toString()}`);
}
async function cmdTrain(mintStr: string, deltasStr: string) {
const { client } = getClient();
const mint = new PublicKey(mintStr);
const deltas = deltasStr.split(",").map(Number);
console.log(`\n🧠 Training ${mintStr.slice(0, 8)}...`);
console.log(` Deltas: [${deltas.join(", ")}]`);
const txids = await client.trainAllWeights(mint, deltas);
console.log(` ✅ Trained ${txids.length} weights`);
const agent = await client.fetchAgent(mint);
if (agent) {
console.log(` New weights: [${agent.neuralWeights.join(", ")}]`);
}
}
async function cmdStake(mintStr: string, solStr: string) {
const { client } = getClient();
const mint = new PublicKey(mintStr);
const wager = solToLamports(solStr);
console.log(`\n⚔ Entering The Trench with ${solStr} SOL wager...`);
const tx = await client.enterArena(mint, wager);
console.log(` ✅ Staked: ${tx}`);
}
async function cmdUnstake(mintStr: string) {
const { client } = getClient();
const mint = new PublicKey(mintStr);
console.log(`\n🏃 Leaving The Trench...`);
const tx = await client.leaveArena(mint);
console.log(` ✅ Unstaked + collected: ${tx}`);
}
async function cmdCollect(mintStr: string) {
const { client } = getClient();
const mint = new PublicKey(mintStr);
console.log(`\n💰 Collecting earnings...`);
const tx = await client.collectEarnings(mint);
console.log(` ✅ Collected: ${tx}`);
}
async function cmdInfo(mintStr: string) {
const { client } = getClient();
const mint = new PublicKey(mintStr);
const agent = await client.fetchAgent(mint);
if (!agent) {
console.error(`❌ No husk found at ${mintStr}`);
process.exit(1);
}
const balance = await client.getAgentBalance(mint);
console.log();
printAgent(agent, balance);
}
async function cmdCrank() {
const { client } = getClient();
console.log(`\n⚙ Running cranker cycle...`);
const arena = await client.fetchArenaAgents();
console.log(` ${arena.length} husks in The Trench`);
if (arena.length < 2) {
console.log(` Not enough fighters for matches.`);
return;
}
// Shuffle for fair pairing
const shuffled = [...arena].sort(() => Math.random() - 0.5);
let fights = 0;
let errors = 0;
for (let i = 0; i + 1 < shuffled.length; i += 2) {
const a = shuffled[i];
const b = shuffled[i + 1];
try {
const tx = await client.crankFight(a.nftMint, b.nftMint);
fights++;
console.log(` ⚔ ${getHuskName(a)} vs ${getHuskName(b)} — ${tx.slice(0, 8)}...`);
} catch (err: any) {
errors++;
console.log(` ❌ ${getHuskName(a)} vs ${getHuskName(b)} — ${err.message?.slice(0, 50)}`);
}
}
console.log(`\n ✅ ${fights} fights completed, ${errors} errors`);
}
// ─── Main ──────────────────────────────────────────────────────────────
async function main() {
const [cmd, ...args] = process.argv.slice(2);
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
console.log(`
╔══════════════════════════════════════════╗
║ HUSKS CLI — AI COMBAT SDK ║
╠══════════════════════════════════════════╣
║ ║
║ status Show your husks ║
║ stats Network stats ║
║ summon [name] Mint + activate ║
║ deploy [name] [w] [sol] Full pipeline ║
║ train <mint> <d> Train weights ║
║ stake <mint> <sol> Enter The Trench ║
║ unstake <mint> Leave The Trench ║
║ collect <mint> Collect earnings ║
║ crank Run cranker ║
║ info <mint> Husk details ║
║ ║
║ Env: PRIVATE_KEY, RPC_URL ║
╚══════════════════════════════════════════╝
`);
return;
}
switch (cmd) {
case "status": return cmdStatus();
case "stats": return cmdStats();
case "summon": return cmdSummon(args[0]);
case "deploy": return cmdDeploy(args[0], args[1], args[2]);
case "train": return cmdTrain(args[0], args[1]);
case "stake": return cmdStake(args[0], args[1]);
case "unstake": return cmdUnstake(args[0]);
case "collect": return cmdCollect(args[0]);
case "crank": return cmdCrank();
case "info": return cmdInfo(args[0]);
default:
console.error(`❌ Unknown command: ${cmd}. Run with --help for usage.`);
process.exit(1);
}
}
main().catch((err) => {
console.error(`\n❌ Error: ${err.message}`);
process.exit(1);
});