pyre-agent-kit 1.0.0

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/cli.js ADDED
@@ -0,0 +1,475 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * pyre-agent-kit CLI
5
+ *
6
+ * Interactive launcher for non-technical users.
7
+ * npx pyre-agent-kit
8
+ *
9
+ * Config is saved to ~/.pyre-agent.json so you only set up once.
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ const readline = __importStar(require("readline"));
46
+ const fs = __importStar(require("fs"));
47
+ const path = __importStar(require("path"));
48
+ const web3_js_1 = require("@solana/web3.js");
49
+ const index_1 = require("./index");
50
+ // ─── Constants ────────────────────────────────────────────────────
51
+ const CONFIG_PATH = path.join(process.env.HOME ?? '.', '.pyre-agent.json');
52
+ const BANNER = `
53
+ ██████╗ ██╗ ██╗██████╗ ███████╗
54
+ ██╔══██╗╚██╗ ██╔╝██╔══██╗██╔════╝
55
+ ██████╔╝ ╚████╔╝ ██████╔╝█████╗
56
+ ██╔═══╝ ╚██╔╝ ██╔══██╗██╔══╝
57
+ ██║ ██║ ██║ ██║███████╗
58
+ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝
59
+ A G E N T K I T
60
+ `;
61
+ const RPC_DEFAULTS = {
62
+ devnet: 'https://api.devnet.solana.com',
63
+ mainnet: 'https://api.mainnet-beta.solana.com',
64
+ };
65
+ const PERSONALITIES = ['loyalist', 'mercenary', 'provocateur', 'scout', 'whale'];
66
+ const PERSONALITY_DISPLAY = {
67
+ loyalist: 'Loyalist — ride-or-die, holds positions long',
68
+ mercenary: 'Mercenary — plays every angle, frequent trades',
69
+ provocateur: 'Provocateur — chaos agent, heavy on comms and FUD',
70
+ scout: 'Scout — intel-focused, small positions',
71
+ whale: 'Whale — big positions, market mover',
72
+ };
73
+ function loadConfig() {
74
+ try {
75
+ if (fs.existsSync(CONFIG_PATH)) {
76
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
77
+ }
78
+ }
79
+ catch { /* ignore */ }
80
+ return null;
81
+ }
82
+ function saveConfig(config) {
83
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
84
+ }
85
+ // ─── Prompts ──────────────────────────────────────────────────────
86
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
87
+ function ask(question, fallback) {
88
+ const suffix = fallback ? ` (${fallback})` : '';
89
+ return new Promise(resolve => {
90
+ rl.question(` ${question}${suffix}: `, answer => {
91
+ resolve(answer.trim() || fallback || '');
92
+ });
93
+ });
94
+ }
95
+ function choose(question, options, defaultIdx = 0) {
96
+ return new Promise(resolve => {
97
+ console.log(`\n ${question}`);
98
+ options.forEach((opt, i) => {
99
+ const marker = i === defaultIdx ? '>' : ' ';
100
+ console.log(` ${marker} ${i + 1}. ${opt}`);
101
+ });
102
+ rl.question(` Choice [${defaultIdx + 1}]: `, answer => {
103
+ const n = parseInt(answer.trim());
104
+ if (n >= 1 && n <= options.length)
105
+ resolve(n - 1);
106
+ else
107
+ resolve(defaultIdx);
108
+ });
109
+ });
110
+ }
111
+ // ─── LLM Factory ─────────────────────────────────────────────────
112
+ function createLLM(config) {
113
+ if (config.llmProvider === 'none')
114
+ return undefined;
115
+ if (config.llmProvider === 'openai') {
116
+ const apiKey = config.llmApiKey;
117
+ const model = config.llmModel || 'gpt-4o';
118
+ return {
119
+ generate: async (prompt) => {
120
+ const res = await fetch('https://api.openai.com/v1/chat/completions', {
121
+ method: 'POST',
122
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
123
+ body: JSON.stringify({
124
+ model,
125
+ messages: [{ role: 'user', content: prompt }],
126
+ max_tokens: 120,
127
+ temperature: 0.9,
128
+ }),
129
+ });
130
+ if (!res.ok) {
131
+ console.error(` [LLM] OpenAI error: ${res.status}`);
132
+ return null;
133
+ }
134
+ const data = await res.json();
135
+ return data.choices?.[0]?.message?.content ?? null;
136
+ },
137
+ };
138
+ }
139
+ if (config.llmProvider === 'anthropic') {
140
+ const apiKey = config.llmApiKey;
141
+ const model = config.llmModel || 'claude-sonnet-4-20250514';
142
+ return {
143
+ generate: async (prompt) => {
144
+ const res = await fetch('https://api.anthropic.com/v1/messages', {
145
+ method: 'POST',
146
+ headers: {
147
+ 'Content-Type': 'application/json',
148
+ 'x-api-key': apiKey,
149
+ 'anthropic-version': '2023-06-01',
150
+ },
151
+ body: JSON.stringify({
152
+ model,
153
+ max_tokens: 120,
154
+ messages: [{ role: 'user', content: prompt }],
155
+ }),
156
+ });
157
+ if (!res.ok) {
158
+ console.error(` [LLM] Anthropic error: ${res.status}`);
159
+ return null;
160
+ }
161
+ const data = await res.json();
162
+ const block = data.content?.[0];
163
+ return block?.type === 'text' ? block.text : null;
164
+ },
165
+ };
166
+ }
167
+ if (config.llmProvider === 'ollama') {
168
+ const url = config.llmUrl || 'http://localhost:11434';
169
+ const model = config.llmModel || 'llama3';
170
+ return {
171
+ generate: async (prompt) => {
172
+ try {
173
+ const res = await fetch(`${url}/api/generate`, {
174
+ method: 'POST',
175
+ headers: { 'Content-Type': 'application/json' },
176
+ body: JSON.stringify({ model, prompt, stream: false }),
177
+ });
178
+ if (!res.ok) {
179
+ console.error(` [LLM] Ollama error: ${res.status}`);
180
+ return null;
181
+ }
182
+ const data = await res.json();
183
+ return data.response ?? null;
184
+ }
185
+ catch (e) {
186
+ console.error(` [LLM] Ollama unreachable: ${e.message}`);
187
+ return null;
188
+ }
189
+ },
190
+ };
191
+ }
192
+ return undefined;
193
+ }
194
+ // ─── Setup Wizard ─────────────────────────────────────────────────
195
+ async function runSetup() {
196
+ console.log('\n First-time setup. Config will be saved to ~/.pyre-agent.json\n');
197
+ // Network
198
+ const netIdx = await choose('Network:', ['Devnet (testing)', 'Mainnet (real SOL)'], 0);
199
+ const network = netIdx === 0 ? 'devnet' : 'mainnet';
200
+ // RPC
201
+ const rpcDefault = RPC_DEFAULTS[network];
202
+ const rpcUrl = await ask('RPC URL', rpcDefault);
203
+ // Keypair
204
+ console.log('\n Wallet setup:');
205
+ const walletIdx = await choose('Keypair:', [
206
+ 'Generate new wallet',
207
+ 'Import from secret key (JSON array)',
208
+ 'Import from file path',
209
+ ], 0);
210
+ let keypair;
211
+ if (walletIdx === 0) {
212
+ keypair = web3_js_1.Keypair.generate();
213
+ console.log(`\n New wallet generated: ${keypair.publicKey.toBase58()}`);
214
+ console.log(` Fund it with SOL before starting the agent.`);
215
+ if (network === 'devnet') {
216
+ console.log(` Devnet faucet: https://faucet.solana.com`);
217
+ }
218
+ }
219
+ else if (walletIdx === 1) {
220
+ const raw = await ask('Paste secret key JSON array');
221
+ keypair = web3_js_1.Keypair.fromSecretKey(Uint8Array.from(JSON.parse(raw)));
222
+ console.log(` Imported: ${keypair.publicKey.toBase58()}`);
223
+ }
224
+ else {
225
+ const filePath = await ask('Path to keypair JSON file');
226
+ const resolved = path.resolve(filePath.replace(/^~/, process.env.HOME ?? '.'));
227
+ const raw = JSON.parse(fs.readFileSync(resolved, 'utf-8'));
228
+ keypair = web3_js_1.Keypair.fromSecretKey(Uint8Array.from(raw));
229
+ console.log(` Imported: ${keypair.publicKey.toBase58()}`);
230
+ }
231
+ // Personality
232
+ const persIdx = await choose('Personality:', [
233
+ ...PERSONALITIES.map(p => PERSONALITY_DISPLAY[p]),
234
+ 'Random (auto-assign)',
235
+ ], 5);
236
+ const personality = persIdx < PERSONALITIES.length ? PERSONALITIES[persIdx] : PERSONALITIES[Math.floor(Math.random() * PERSONALITIES.length)];
237
+ // LLM
238
+ const llmIdx = await choose('LLM Provider:', [
239
+ 'OpenAI (GPT-4o, etc.)',
240
+ 'Anthropic (Claude)',
241
+ 'Ollama (local)',
242
+ 'None (random actions only)',
243
+ ], 0);
244
+ const llmProviders = ['openai', 'anthropic', 'ollama', 'none'];
245
+ const llmProvider = llmProviders[llmIdx];
246
+ let llmApiKey;
247
+ let llmModel;
248
+ let llmUrl;
249
+ if (llmProvider === 'openai') {
250
+ llmApiKey = await ask('OpenAI API key');
251
+ llmModel = await ask('Model', 'gpt-4o');
252
+ }
253
+ else if (llmProvider === 'anthropic') {
254
+ llmApiKey = await ask('Anthropic API key');
255
+ llmModel = await ask('Model', 'claude-sonnet-4-20250514');
256
+ }
257
+ else if (llmProvider === 'ollama') {
258
+ llmUrl = await ask('Ollama URL', 'http://localhost:11434');
259
+ llmModel = await ask('Model (e.g. llama3, gemma3:4b, mistral)', 'llama3');
260
+ }
261
+ // Tick interval
262
+ const intervalStr = await ask('Seconds between actions', '30');
263
+ const tickIntervalMs = Math.max(5, parseInt(intervalStr) || 30) * 1000;
264
+ // Vault funding
265
+ const fundStr = await ask('Stronghold vault funding (SOL)', '35');
266
+ const strongholdFundSol = Math.max(0.1, parseFloat(fundStr) || 35);
267
+ const config = {
268
+ network,
269
+ rpcUrl,
270
+ secretKey: Array.from(keypair.secretKey),
271
+ personality,
272
+ llmProvider,
273
+ llmModel,
274
+ llmApiKey,
275
+ llmUrl,
276
+ strongholdFundSol,
277
+ tickIntervalMs,
278
+ };
279
+ saveConfig(config);
280
+ console.log(`\n Config saved to ${CONFIG_PATH}`);
281
+ return config;
282
+ }
283
+ // ─── Agent Loop ───────────────────────────────────────────────────
284
+ function ts() {
285
+ return new Date().toISOString().substr(11, 8);
286
+ }
287
+ async function runAgent(config) {
288
+ const connection = new web3_js_1.Connection(config.rpcUrl, 'confirmed');
289
+ const keypair = web3_js_1.Keypair.fromSecretKey(Uint8Array.from(config.secretKey));
290
+ const llm = createLLM(config);
291
+ console.log(`\n Network: ${config.network}`);
292
+ console.log(` Wallet: ${keypair.publicKey.toBase58()}`);
293
+ console.log(` Personality: ${config.personality}`);
294
+ console.log(` LLM: ${config.llmProvider}${config.llmModel ? ` (${config.llmModel})` : ''}`);
295
+ console.log(` Tick: every ${config.tickIntervalMs / 1000}s`);
296
+ // Check balance
297
+ const balance = await connection.getBalance(keypair.publicKey);
298
+ const solBalance = balance / web3_js_1.LAMPORTS_PER_SOL;
299
+ console.log(` Balance: ${solBalance.toFixed(4)} SOL`);
300
+ if (solBalance < 0.01) {
301
+ console.error(`\n Wallet has insufficient SOL. Fund it and try again.`);
302
+ console.error(` Address: ${keypair.publicKey.toBase58()}`);
303
+ if (config.network === 'devnet') {
304
+ console.error(` Devnet faucet: https://faucet.solana.com`);
305
+ }
306
+ process.exit(1);
307
+ }
308
+ // Load saved state if it exists
309
+ const statePath = path.join(process.env.HOME ?? '.', `.pyre-agent-state-${keypair.publicKey.toBase58().slice(0, 8)}.json`);
310
+ let state;
311
+ try {
312
+ if (fs.existsSync(statePath)) {
313
+ state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
314
+ console.log(` State: restored from ${path.basename(statePath)}`);
315
+ }
316
+ }
317
+ catch { /* fresh start */ }
318
+ console.log(`\n Starting agent...\n`);
319
+ const agent = await (0, index_1.createPyreAgent)({
320
+ connection,
321
+ keypair,
322
+ network: config.network,
323
+ llm,
324
+ personality: config.personality,
325
+ strongholdFundSol: config.strongholdFundSol,
326
+ solRange: config.solRange,
327
+ state,
328
+ logger: (msg) => console.log(` [${ts()}] ${msg}`),
329
+ });
330
+ console.log(` Agent online: ${agent.publicKey.slice(0, 8)}... (${agent.personality})\n`);
331
+ // Graceful shutdown
332
+ let running = true;
333
+ const shutdown = () => {
334
+ if (!running)
335
+ return;
336
+ running = false;
337
+ console.log(`\n Shutting down...`);
338
+ const saved = agent.serialize();
339
+ fs.writeFileSync(statePath, JSON.stringify(saved, null, 2));
340
+ console.log(` State saved to ${path.basename(statePath)}`);
341
+ console.log(` ${saved.actionCount} total actions performed.`);
342
+ process.exit(0);
343
+ };
344
+ process.on('SIGINT', shutdown);
345
+ process.on('SIGTERM', shutdown);
346
+ // Auto-save every 10 ticks
347
+ let tickCount = 0;
348
+ while (running) {
349
+ try {
350
+ const result = await agent.tick();
351
+ tickCount++;
352
+ const status = result.success ? 'OK' : `FAIL: ${result.error}`;
353
+ const msg = result.message ? ` "${result.message}"` : '';
354
+ const brain = result.usedLLM ? 'LLM' : 'RNG';
355
+ console.log(` [${ts()}] #${tickCount} [${brain}] ${result.action.toUpperCase()} ${result.faction ?? ''}${msg} — ${status}`);
356
+ // Auto-save state every 10 ticks
357
+ if (tickCount % 10 === 0) {
358
+ const saved = agent.serialize();
359
+ fs.writeFileSync(statePath, JSON.stringify(saved, null, 2));
360
+ }
361
+ }
362
+ catch (e) {
363
+ console.error(` [${ts()}] Tick error: ${e.message}`);
364
+ }
365
+ // Wait for next tick
366
+ await new Promise(resolve => setTimeout(resolve, config.tickIntervalMs));
367
+ }
368
+ }
369
+ // ─── Main ─────────────────────────────────────────────────────────
370
+ async function main() {
371
+ console.log(BANNER);
372
+ const args = process.argv.slice(2);
373
+ if (args.includes('--help') || args.includes('-h')) {
374
+ console.log(' Usage: npx pyre-agent-kit [options]');
375
+ console.log('');
376
+ console.log(' Options:');
377
+ console.log(' --setup Re-run full setup wizard');
378
+ console.log(' --model Change LLM provider/model only');
379
+ console.log(' --personality Change personality only');
380
+ console.log(' --reset Delete saved config and start fresh');
381
+ console.log(' --status Show current config and wallet balance');
382
+ console.log(' --help Show this help message');
383
+ console.log('');
384
+ console.log(` Config: ${CONFIG_PATH}`);
385
+ console.log('');
386
+ process.exit(0);
387
+ }
388
+ if (args.includes('--reset')) {
389
+ if (fs.existsSync(CONFIG_PATH)) {
390
+ fs.unlinkSync(CONFIG_PATH);
391
+ console.log(` Config deleted: ${CONFIG_PATH}`);
392
+ }
393
+ else {
394
+ console.log(' No config to delete.');
395
+ }
396
+ process.exit(0);
397
+ }
398
+ let config = loadConfig();
399
+ if (args.includes('--model') && config) {
400
+ console.log(` Current: ${config.llmProvider}${config.llmModel ? ` (${config.llmModel})` : ''}\n`);
401
+ const llmIdx = await choose('LLM Provider:', [
402
+ 'OpenAI (GPT-4o, etc.)',
403
+ 'Anthropic (Claude)',
404
+ 'Ollama (local)',
405
+ 'None (random actions only)',
406
+ ], ['openai', 'anthropic', 'ollama', 'none'].indexOf(config.llmProvider));
407
+ const llmProviders = ['openai', 'anthropic', 'ollama', 'none'];
408
+ config.llmProvider = llmProviders[llmIdx];
409
+ config.llmApiKey = undefined;
410
+ config.llmModel = undefined;
411
+ config.llmUrl = undefined;
412
+ if (config.llmProvider === 'openai') {
413
+ config.llmApiKey = await ask('OpenAI API key');
414
+ config.llmModel = await ask('Model', 'gpt-4o');
415
+ }
416
+ else if (config.llmProvider === 'anthropic') {
417
+ config.llmApiKey = await ask('Anthropic API key');
418
+ config.llmModel = await ask('Model', 'claude-sonnet-4-20250514');
419
+ }
420
+ else if (config.llmProvider === 'ollama') {
421
+ config.llmUrl = await ask('Ollama URL', config.llmUrl || 'http://localhost:11434');
422
+ config.llmModel = await ask('Model (e.g. llama3, gemma3:4b, mistral)', 'llama3');
423
+ }
424
+ saveConfig(config);
425
+ console.log(`\n Updated: ${config.llmProvider}${config.llmModel ? ` (${config.llmModel})` : ''}`);
426
+ console.log(` Saved to ${CONFIG_PATH}\n`);
427
+ rl.close();
428
+ process.exit(0);
429
+ }
430
+ if (args.includes('--personality') && config) {
431
+ console.log(` Current: ${config.personality}\n`);
432
+ const persIdx = await choose('Personality:', [
433
+ ...PERSONALITIES.map(p => PERSONALITY_DISPLAY[p]),
434
+ 'Random (auto-assign)',
435
+ ], PERSONALITIES.indexOf(config.personality));
436
+ config.personality = persIdx < PERSONALITIES.length
437
+ ? PERSONALITIES[persIdx]
438
+ : PERSONALITIES[Math.floor(Math.random() * PERSONALITIES.length)];
439
+ saveConfig(config);
440
+ console.log(`\n Updated: ${config.personality}`);
441
+ console.log(` Saved to ${CONFIG_PATH}\n`);
442
+ rl.close();
443
+ process.exit(0);
444
+ }
445
+ if (args.includes('--setup') || !config) {
446
+ config = await runSetup();
447
+ }
448
+ if (args.includes('--status')) {
449
+ console.log(` Config: ${CONFIG_PATH}`);
450
+ console.log(` Network: ${config.network}`);
451
+ console.log(` RPC: ${config.rpcUrl}`);
452
+ const kp = web3_js_1.Keypair.fromSecretKey(Uint8Array.from(config.secretKey));
453
+ console.log(` Wallet: ${kp.publicKey.toBase58()}`);
454
+ console.log(` Personality: ${config.personality}`);
455
+ console.log(` LLM: ${config.llmProvider}${config.llmModel ? ` (${config.llmModel})` : ''}`);
456
+ console.log(` Tick: every ${config.tickIntervalMs / 1000}s`);
457
+ try {
458
+ const connection = new web3_js_1.Connection(config.rpcUrl, 'confirmed');
459
+ const balance = await connection.getBalance(kp.publicKey);
460
+ console.log(` Balance: ${(balance / web3_js_1.LAMPORTS_PER_SOL).toFixed(4)} SOL`);
461
+ }
462
+ catch {
463
+ console.log(` Balance: (could not reach RPC)`);
464
+ }
465
+ console.log('');
466
+ rl.close();
467
+ process.exit(0);
468
+ }
469
+ rl.close();
470
+ await runAgent(config);
471
+ }
472
+ main().catch(e => {
473
+ console.error(`\n Fatal: ${e.message}`);
474
+ process.exit(1);
475
+ });
@@ -0,0 +1,10 @@
1
+ import { Personality } from './types';
2
+ export declare const PERSONALITY_WEIGHTS: Record<Personality, number[]>;
3
+ export declare const PERSONALITY_SOL: Record<Personality, [number, number]>;
4
+ export declare const personalityDesc: Record<Personality, string>;
5
+ export declare const VOICE_NUDGES: string[];
6
+ export declare const ACTION_MAP: Record<string, string>;
7
+ export declare const STRONGHOLD_FUND_SOL = 35;
8
+ export declare const STRONGHOLD_TOPUP_THRESHOLD_SOL = 5;
9
+ export declare const STRONGHOLD_TOPUP_RESERVE_SOL = 5;
10
+ export declare const assignPersonality: () => Personality;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assignPersonality = exports.STRONGHOLD_TOPUP_RESERVE_SOL = exports.STRONGHOLD_TOPUP_THRESHOLD_SOL = exports.STRONGHOLD_FUND_SOL = exports.ACTION_MAP = exports.VOICE_NUDGES = exports.personalityDesc = exports.PERSONALITY_SOL = exports.PERSONALITY_WEIGHTS = void 0;
4
+ // ─── Personality Weights ────────────────────────────────────────────
5
+ // [join, defect, rally, launch, message, stronghold, war_loan, repay_loan, siege, ascend, raze, tithe, infiltrate, fud]
6
+ exports.PERSONALITY_WEIGHTS = {
7
+ loyalist: [0.28, 0.06, 0.14, 0.02, 0.12, 0.06, 0.04, 0.04, 0.02, 0.05, 0.02, 0.10, 0.02, 0.03],
8
+ mercenary: [0.16, 0.18, 0.04, 0.02, 0.08, 0.04, 0.08, 0.04, 0.06, 0.03, 0.04, 0.03, 0.12, 0.08],
9
+ provocateur: [0.12, 0.08, 0.04, 0.06, 0.18, 0.05, 0.04, 0.03, 0.04, 0.03, 0.05, 0.04, 0.12, 0.12],
10
+ scout: [0.18, 0.10, 0.08, 0.02, 0.16, 0.04, 0.04, 0.03, 0.06, 0.04, 0.05, 0.04, 0.08, 0.08],
11
+ whale: [0.24, 0.14, 0.06, 0.02, 0.06, 0.06, 0.06, 0.04, 0.02, 0.04, 0.04, 0.04, 0.12, 0.06],
12
+ };
13
+ // SOL spend ranges per personality
14
+ exports.PERSONALITY_SOL = {
15
+ loyalist: [0.02, 0.1],
16
+ mercenary: [0.01, 0.08],
17
+ provocateur: [0.005, 0.05],
18
+ scout: [0.005, 0.03],
19
+ whale: [0.1, 0.5],
20
+ };
21
+ exports.personalityDesc = {
22
+ loyalist: 'You\'re ride or die for your factions. Talk trash about rival factions unprompted. Hype your crew loudly and call out anyone who defects — by address. Propose quests and challenges for your allies. You take it personally when someone dumps your faction.',
23
+ mercenary: 'You\'re a lone wolf who plays every angle. Trash talk factions you\'re about to leave. Drop alpha only when it benefits you. Challenge others to prove their conviction. You don\'t need allies — everyone else is just a trade.',
24
+ provocateur: 'You live for chaos and hot takes. Call out the biggest holder in any faction. Start beef between factions. Make bold predictions and dare people to bet against you. Your trash talk is creative and specific — reference actual agents, numbers, and moves.',
25
+ scout: 'You\'re the intel operative who sees everything. Drop suspicious observations about other agents\' moves. Question why someone just bought or sold. Share data that makes people nervous. You\'re helpful to allies but plant doubt in everyone else.',
26
+ whale: 'You move markets and everyone knows it. Flex your position size. Trash talk small holders. Challenge other whales publicly. When you speak, people listen — and you know it. Back your words with big moves.',
27
+ };
28
+ exports.VOICE_NUDGES = [
29
+ 'JOIN a faction you\'ve been eyeing. Put your money where your mouth is.',
30
+ 'DEFECT from a faction that\'s underperforming. Cut your losses.',
31
+ 'INFILTRATE a rival faction. Sneak in before they notice.',
32
+ 'FUD a faction you hold — spread doubt and buy the dip later.',
33
+ 'Take a WAR_LOAN against your strongest position. Leverage up.',
34
+ 'JOIN the top faction on the leaderboard. Ride the momentum.',
35
+ 'DEFECT dramatically. Trash talk on the way out.',
36
+ 'INFILTRATE the weakest faction. Easy target.',
37
+ 'FUD whoever is in first place. Knock them down a peg.',
38
+ 'RALLY a faction you believe in. Show support.',
39
+ 'JOIN a small faction early. Get in before the crowd.',
40
+ 'Double down. REINFORCE your best position.',
41
+ 'Call out a specific agent by address. What are they up to?',
42
+ 'Trash talk a rival faction. Be specific about why they\'re weak.',
43
+ 'Flex on your position. You\'re winning and everyone should know.',
44
+ 'Be suspicious. Something doesn\'t add up. Who\'s dumping?',
45
+ 'Challenge another agent directly. Dare them to make a move.',
46
+ 'Drop a hot take that will start an argument.',
47
+ 'Hype your faction aggressively. Why is everyone else sleeping on it?',
48
+ 'Sound like you know something others don\'t. Be cryptic.',
49
+ 'React to a recent trade or move. Call it smart or stupid.',
50
+ 'Ask a loaded question. You already know the answer.',
51
+ 'Be disappointed in someone. They let the faction down.',
52
+ 'Make a bold prediction. Put your reputation on it.',
53
+ 'Sound paranoid. Someone is coordinating against you.',
54
+ 'Be sarcastic about a faction that\'s underperforming.',
55
+ 'Propose a quest or challenge — but make it competitive.',
56
+ 'Reference a specific number — holder count, percentage, or trend.',
57
+ 'Write a one-liner. Punchy. Sarcastic. No explanation needed.',
58
+ 'Sound like you\'re warning an ally about something you saw.',
59
+ ];
60
+ exports.ACTION_MAP = {
61
+ 'JOIN': 'JOIN', 'DEFECT': 'DEFECT', 'RALLY': 'RALLY', 'LAUNCH': 'LAUNCH',
62
+ 'MESSAGE': 'MESSAGE', 'STRONGHOLD': 'STRONGHOLD', 'WAR_LOAN': 'WAR_LOAN',
63
+ 'REPAY_LOAN': 'REPAY_LOAN', 'SIEGE': 'SIEGE', 'ASCEND': 'ASCEND',
64
+ 'RAZE': 'RAZE', 'TITHE': 'TITHE', 'INFILTRATE': 'INFILTRATE', 'FUD': 'FUD',
65
+ // Aliases
66
+ 'BUY': 'JOIN', 'INVEST': 'JOIN', 'ENTER': 'JOIN', 'JOINING': 'JOIN', 'BUYING': 'JOIN', 'INVESTING': 'JOIN',
67
+ 'REINFORCE': 'JOIN', 'INCREASE': 'JOIN', 'GATHER': 'JOIN',
68
+ 'SELL': 'DEFECT', 'DUMP': 'DEFECT', 'EXIT': 'DEFECT', 'LEAVE': 'DEFECT', 'DEFECTING': 'DEFECT', 'DEFECTION': 'DEFECT', 'SELLING': 'DEFECT', 'DUMPING': 'DEFECT',
69
+ 'WARN': 'FUD', 'ATTACK': 'SIEGE', 'LIQUIDATE': 'SIEGE',
70
+ 'BORROW': 'WAR_LOAN', 'LOAN': 'WAR_LOAN',
71
+ 'REPAY': 'REPAY_LOAN', 'STAR': 'RALLY', 'VOTE': 'RALLY', 'SUPPORT': 'RALLY',
72
+ 'SEND': 'MESSAGE', 'SAY': 'MESSAGE', 'CHAT': 'MESSAGE', 'MSG': 'MESSAGE', 'MESSAGING': 'MESSAGE',
73
+ 'CREATE': 'LAUNCH', 'FOUND': 'LAUNCH', 'HARVEST': 'TITHE',
74
+ 'MIGRATE': 'ASCEND', 'RECLAIM': 'RAZE', 'SPY': 'INFILTRATE',
75
+ 'INVESTIGATION': 'INFILTRATE', 'INVESTIGATE': 'INFILTRATE', 'SCOUT': 'INFILTRATE', 'RECON': 'INFILTRATE',
76
+ 'PLEDGE': 'JOIN', 'ALLY': 'JOIN', 'BACK': 'JOIN', 'FUND': 'JOIN',
77
+ 'WITHDRAW': 'DEFECT', 'RETREAT': 'DEFECT', 'ABANDON': 'DEFECT', 'BAIL': 'DEFECT',
78
+ 'ANNOUNCE': 'MESSAGE', 'BROADCAST': 'MESSAGE', 'COMM': 'MESSAGE', 'COMMS': 'MESSAGE', 'REPORT': 'MESSAGE',
79
+ 'SMEAR': 'FUD', 'SLANDER': 'FUD', 'DISCREDIT': 'FUD', 'SABOTAGE': 'FUD', 'UNDERMINE': 'FUD', 'ARGUE': 'FUD', 'TRASH': 'FUD', 'CRITICIZE': 'FUD', 'MOCK': 'FUD', 'FUDS': 'FUD',
80
+ 'ENDORSE': 'RALLY', 'PROMOTE': 'RALLY', 'BOOST': 'RALLY',
81
+ // Common misspellings
82
+ 'DEFLECT': 'DEFECT', 'DEFEKT': 'DEFECT', 'DEFCT': 'DEFECT', 'DEFFECT': 'DEFECT',
83
+ 'JION': 'JOIN', 'JOING': 'JOIN', 'JOIIN': 'JOIN',
84
+ 'RALEY': 'RALLY', 'RALY': 'RALLY', 'RALLLY': 'RALLY',
85
+ 'LANCH': 'LAUNCH', 'LAUCH': 'LAUNCH',
86
+ 'MESAGE': 'MESSAGE', 'MESSGE': 'MESSAGE', 'MASSGE': 'MESSAGE', 'MESS': 'MESSAGE', 'MESSENGER': 'MESSAGE', 'MESSAGES': 'MESSAGE',
87
+ 'SEIGE': 'SIEGE', 'SEIG': 'SIEGE',
88
+ 'INFLTRATE': 'INFILTRATE', 'INFILTRTE': 'INFILTRATE', 'INFILTRATING': 'INFILTRATE', 'INFIL': 'INFILTRATE', 'INFILTRAT': 'INFILTRATE',
89
+ 'ALERT': 'FUD', 'EXPOSE': 'FUD',
90
+ 'QUESTION': 'MESSAGE', 'ASK': 'MESSAGE', 'TAUNT': 'FUD', 'RALLYING': 'RALLY',
91
+ 'TICKER': 'MESSAGE', 'ACTION': 'MESSAGE', // LLM copies placeholder words from prompt
92
+ };
93
+ // Stronghold defaults
94
+ exports.STRONGHOLD_FUND_SOL = 35;
95
+ exports.STRONGHOLD_TOPUP_THRESHOLD_SOL = 5;
96
+ exports.STRONGHOLD_TOPUP_RESERVE_SOL = 5;
97
+ const assignPersonality = () => {
98
+ const personalities = ['loyalist', 'mercenary', 'provocateur', 'scout', 'whale'];
99
+ const weights = [0.30, 0.25, 0.15, 0.20, 0.10];
100
+ const roll = Math.random();
101
+ let cumulative = 0;
102
+ for (let i = 0; i < weights.length; i++) {
103
+ cumulative += weights[i];
104
+ if (roll < cumulative)
105
+ return personalities[i];
106
+ }
107
+ return 'loyalist';
108
+ };
109
+ exports.assignPersonality = assignPersonality;
@@ -0,0 +1,6 @@
1
+ export declare const PROGRAM_ERRORS: Record<number, string>;
2
+ export declare const SKIP_ERRORS: Set<number>;
3
+ export declare const parseCustomError: (err: any) => {
4
+ code: number;
5
+ name: string;
6
+ } | null;
package/dist/error.js ADDED
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCustomError = exports.SKIP_ERRORS = exports.PROGRAM_ERRORS = void 0;
4
+ // ─── Program Error Codes ────────────────────────────────────────────
5
+ exports.PROGRAM_ERRORS = {
6
+ 6000: 'MathOverflow',
7
+ 6001: 'SlippageExceeded',
8
+ 6002: 'MaxWalletExceeded (2% cap)',
9
+ 6003: 'InsufficientTokens',
10
+ 6004: 'InsufficientSol',
11
+ 6005: 'InsufficientUserBalance',
12
+ 6006: 'BondingComplete',
13
+ 6007: 'BondingNotComplete',
14
+ 6008: 'AlreadyVoted',
15
+ 6009: 'NoTokensToVote',
16
+ 6010: 'AlreadyMigrated',
17
+ 6011: 'InvalidAuthority',
18
+ 6012: 'AmountTooSmall',
19
+ 6013: 'ProtocolPaused',
20
+ 6014: 'ZeroAmount',
21
+ 6022: 'VoteRequired',
22
+ 6030: 'NotMigrated',
23
+ 6044: 'LendingNotEnabled',
24
+ 6045: 'LendingRequiresMigration',
25
+ 6046: 'LtvExceeded',
26
+ 6047: 'LendingCapExceeded',
27
+ 6048: 'UserBorrowCapExceeded',
28
+ 6049: 'BorrowTooSmall (min 0.1 SOL)',
29
+ 6050: 'NoActiveLoan',
30
+ 6051: 'NotLiquidatable',
31
+ 6052: 'EmptyBorrowRequest',
32
+ 6053: 'RepayExceedsDebt',
33
+ 6054: 'InvalidPoolAccount',
34
+ 6055: 'InsufficientVaultBalance',
35
+ 6056: 'VaultUnauthorized',
36
+ 6057: 'WalletNotLinked',
37
+ // Token-2022 / SPL errors
38
+ 2505: 'InsufficientFunds (token balance too low)',
39
+ };
40
+ // Errors that mean "don't retry this action on this faction right now"
41
+ exports.SKIP_ERRORS = new Set([
42
+ 6002, // MaxWalletExceeded — already at 2% cap
43
+ 6006, // BondingComplete — use DEX instead
44
+ 6007, // BondingNotComplete — can't migrate yet
45
+ 6010, // AlreadyMigrated
46
+ 6044, // LendingNotEnabled
47
+ 6045, // LendingRequiresMigration
48
+ 6047, // LendingCapExceeded
49
+ 6051, // NotLiquidatable
50
+ ]);
51
+ const parseCustomError = (err) => {
52
+ const msg = err?.message || String(err);
53
+ // Match "custom program error: 0x1772" or "Custom: 6002"
54
+ const hexMatch = msg.match(/custom program error:\s*0x([0-9a-fA-F]+)/i);
55
+ if (hexMatch) {
56
+ const code = parseInt(hexMatch[1], 16);
57
+ return { code, name: exports.PROGRAM_ERRORS[code] || `Unknown(${code})` };
58
+ }
59
+ const decMatch = msg.match(/Custom:\s*(\d+)/);
60
+ if (decMatch) {
61
+ const code = parseInt(decMatch[1], 10);
62
+ return { code, name: exports.PROGRAM_ERRORS[code] || `Unknown(${code})` };
63
+ }
64
+ return null;
65
+ };
66
+ exports.parseCustomError = parseCustomError;