@wiimdy/openfunderse 0.1.0 → 0.1.2
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/README.md +55 -0
- package/bin/openfunderse.mjs +636 -3
- package/package.json +1 -1
- package/packs/openfunderse/config/setup-manifest.json +8 -3
- package/packs/openfunderse/prompts/core/system.md +25 -0
- package/packs/openfunderse/prompts/participant/system.md +34 -2
- package/packs/openfunderse/prompts/relayer/system.md +13 -2
- package/packs/openfunderse/prompts/strategy/system.md +28 -2
- package/packs/openfunderse/skills/clawbot-core/SKILL.md +157 -0
- package/packs/openfunderse/skills/openfunderse-participant/SKILL.md +176 -0
- package/packs/openfunderse/skills/openfunderse-strategy/SKILL.md +265 -0
- package/packs/openfunderse/skills/relayer/SKILL.md +6 -5
- package/packs/openfunderse-participant/config/setup-manifest.json +12 -0
- package/packs/openfunderse-participant/openfunderse-participant/SKILL.md +176 -0
- package/packs/openfunderse-strategy/config/setup-manifest.json +12 -0
- package/packs/openfunderse-strategy/openfunderse-strategy/SKILL.md +265 -0
- package/packs/openfunderse/skills/participant/SKILL.md +0 -9
- package/packs/openfunderse/skills/strategy/SKILL.md +0 -9
package/bin/openfunderse.mjs
CHANGED
|
@@ -1,26 +1,86 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
3
4
|
import { existsSync } from "node:fs";
|
|
4
|
-
import { cp, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { chmod, cp, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
6
|
import os from "node:os";
|
|
6
7
|
import path from "node:path";
|
|
8
|
+
import { createInterface } from "node:readline/promises";
|
|
7
9
|
import { fileURLToPath } from "node:url";
|
|
8
10
|
|
|
9
11
|
const THIS_FILE = fileURLToPath(import.meta.url);
|
|
10
12
|
const PACKAGE_ROOT = path.resolve(path.dirname(THIS_FILE), "..");
|
|
11
13
|
const PACKS_ROOT = path.join(PACKAGE_ROOT, "packs");
|
|
14
|
+
const DEFAULT_RUNTIME_PACKAGE = "@wiimdy/openfunderse-agents";
|
|
15
|
+
const SUPPORTED_ENV_PROFILES = new Set(["strategy", "participant", "all"]);
|
|
16
|
+
const SUPPORTED_BOT_INIT_ROLES = new Set(["strategy", "participant"]);
|
|
17
|
+
const DEFAULT_MONAD_CHAIN_ID = "10143";
|
|
18
|
+
const TEMP_PRIVATE_KEY = "0x1111111111111111111111111111111111111111111111111111111111111111";
|
|
19
|
+
|
|
20
|
+
const STRATEGY_ENV_TEMPLATE = `# OpenFunderse strategy env scaffold
|
|
21
|
+
# Copy values from your relayer + deployed contracts.
|
|
22
|
+
|
|
23
|
+
RELAYER_URL=https://your-relayer.example.com
|
|
24
|
+
BOT_ID=bot-strategy-1
|
|
25
|
+
BOT_API_KEY=replace_me
|
|
26
|
+
BOT_ADDRESS=0x0000000000000000000000000000000000000000
|
|
27
|
+
CHAIN_ID=10143
|
|
28
|
+
RPC_URL=https://testnet-rpc.monad.xyz
|
|
29
|
+
|
|
30
|
+
# NadFun / protocol addresses
|
|
31
|
+
INTENT_BOOK_ADDRESS=0x0000000000000000000000000000000000000000
|
|
32
|
+
NADFUN_EXECUTION_ADAPTER_ADDRESS=0x0000000000000000000000000000000000000000
|
|
33
|
+
VAULT_ADDRESS=0x0000000000000000000000000000000000000000
|
|
34
|
+
NADFUN_LENS_ADDRESS=0x0000000000000000000000000000000000000000
|
|
35
|
+
NADFUN_BONDING_CURVE_ROUTER=0x0000000000000000000000000000000000000000
|
|
36
|
+
NADFUN_DEX_ROUTER=0x0000000000000000000000000000000000000000
|
|
37
|
+
NADFUN_WMON_ADDRESS=0x0000000000000000000000000000000000000000
|
|
38
|
+
|
|
39
|
+
# Strategy signer (EOA)
|
|
40
|
+
# Temporary bootstrap key (public and unsafe). Replace via bot-init before real usage.
|
|
41
|
+
STRATEGY_PRIVATE_KEY=${TEMP_PRIVATE_KEY}
|
|
42
|
+
# STRATEGY_CREATE_MIN_SIGNER_BALANCE_WEI=10000000000000000
|
|
43
|
+
|
|
44
|
+
# Safety defaults
|
|
45
|
+
STRATEGY_REQUIRE_EXPLICIT_SUBMIT=true
|
|
46
|
+
STRATEGY_AUTO_SUBMIT=false
|
|
47
|
+
# STRATEGY_TRUSTED_RELAYER_HOSTS=openfunderse-relayer.example.com
|
|
48
|
+
# STRATEGY_ALLOW_HTTP_RELAYER=true
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
const PARTICIPANT_ENV_TEMPLATE = `# OpenFunderse participant env scaffold
|
|
52
|
+
RELAYER_URL=https://your-relayer.example.com
|
|
53
|
+
BOT_ID=bot-participant-1
|
|
54
|
+
BOT_API_KEY=replace_me
|
|
55
|
+
BOT_ADDRESS=0x0000000000000000000000000000000000000000
|
|
56
|
+
CHAIN_ID=10143
|
|
57
|
+
# Temporary bootstrap key (public and unsafe). Replace via bot-init before real usage.
|
|
58
|
+
PARTICIPANT_PRIVATE_KEY=${TEMP_PRIVATE_KEY}
|
|
59
|
+
CLAIM_ATTESTATION_VERIFIER_ADDRESS=0x0000000000000000000000000000000000000000
|
|
60
|
+
PARTICIPANT_REQUIRE_EXPLICIT_SUBMIT=true
|
|
61
|
+
PARTICIPANT_AUTO_SUBMIT=false
|
|
62
|
+
# PARTICIPANT_TRUSTED_RELAYER_HOSTS=openfunderse-relayer.example.com
|
|
63
|
+
# PARTICIPANT_ALLOW_HTTP_RELAYER=true
|
|
64
|
+
`;
|
|
12
65
|
|
|
13
66
|
function printUsage() {
|
|
14
67
|
console.log(`openfunderse
|
|
15
68
|
|
|
16
69
|
Usage:
|
|
17
70
|
openfunderse list
|
|
18
|
-
openfunderse
|
|
71
|
+
openfunderse bot-init [--role <strategy|participant>] [--skill-name <name>] [--env-path <path>] [--wallet-dir <dir>] [--wallet-name <name>] [--force] [--yes]
|
|
72
|
+
openfunderse install <pack-name> [--dest <skills-dir>] [--codex-home <dir>] [--force] [--with-runtime]
|
|
73
|
+
[--no-init-env] [--env-path <path>] [--env-profile <strategy|participant|all>]
|
|
74
|
+
[--runtime-package <name>] [--runtime-dir <dir>] [--runtime-manager <npm|pnpm|yarn|bun>]
|
|
19
75
|
|
|
20
76
|
Examples:
|
|
21
77
|
openfunderse list
|
|
22
78
|
openfunderse install openfunderse
|
|
79
|
+
openfunderse install openfunderse --with-runtime
|
|
80
|
+
openfunderse install openfunderse-strategy --with-runtime
|
|
23
81
|
openfunderse install openfunderse --codex-home /tmp/codex-home
|
|
82
|
+
openfunderse bot-init --env-path .env.participant --wallet-name participant-bot --yes
|
|
83
|
+
openfunderse bot-init --skill-name strategy --env-path .env.strategy --force
|
|
24
84
|
`);
|
|
25
85
|
}
|
|
26
86
|
|
|
@@ -30,7 +90,21 @@ function parseArgs(argv) {
|
|
|
30
90
|
const options = {
|
|
31
91
|
force: false,
|
|
32
92
|
dest: "",
|
|
33
|
-
codexHome: ""
|
|
93
|
+
codexHome: "",
|
|
94
|
+
withRuntime: false,
|
|
95
|
+
initEnv: true,
|
|
96
|
+
initEnvExplicit: false,
|
|
97
|
+
envFile: "",
|
|
98
|
+
envProfile: "",
|
|
99
|
+
envProfileExplicit: false,
|
|
100
|
+
runtimePackage: "",
|
|
101
|
+
runtimeDir: "",
|
|
102
|
+
runtimeManager: "",
|
|
103
|
+
role: "",
|
|
104
|
+
skillName: "",
|
|
105
|
+
walletDir: "",
|
|
106
|
+
walletName: "",
|
|
107
|
+
yes: false
|
|
34
108
|
};
|
|
35
109
|
const positionals = [];
|
|
36
110
|
|
|
@@ -44,6 +118,10 @@ function parseArgs(argv) {
|
|
|
44
118
|
options.force = true;
|
|
45
119
|
continue;
|
|
46
120
|
}
|
|
121
|
+
if (token === "--yes") {
|
|
122
|
+
options.yes = true;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
47
125
|
if (token === "--dest") {
|
|
48
126
|
options.dest = args[i + 1] ?? "";
|
|
49
127
|
i += 1;
|
|
@@ -54,6 +132,66 @@ function parseArgs(argv) {
|
|
|
54
132
|
i += 1;
|
|
55
133
|
continue;
|
|
56
134
|
}
|
|
135
|
+
if (token === "--with-runtime") {
|
|
136
|
+
options.withRuntime = true;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (token === "--init-env") {
|
|
140
|
+
options.initEnv = true;
|
|
141
|
+
options.initEnvExplicit = true;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (token === "--no-init-env") {
|
|
145
|
+
options.initEnv = false;
|
|
146
|
+
options.initEnvExplicit = true;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (token === "--env-file" || token === "--env-path") {
|
|
150
|
+
options.envFile = args[i + 1] ?? "";
|
|
151
|
+
i += 1;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (token === "--env-profile") {
|
|
155
|
+
options.envProfile = args[i + 1] ?? "";
|
|
156
|
+
options.envProfileExplicit = true;
|
|
157
|
+
i += 1;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (token === "--runtime-package") {
|
|
161
|
+
options.runtimePackage = args[i + 1] ?? "";
|
|
162
|
+
i += 1;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (token === "--runtime-dir") {
|
|
166
|
+
options.runtimeDir = args[i + 1] ?? "";
|
|
167
|
+
i += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (token === "--runtime-manager") {
|
|
171
|
+
options.runtimeManager = args[i + 1] ?? "";
|
|
172
|
+
i += 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (token === "--role") {
|
|
176
|
+
options.role = args[i + 1] ?? "";
|
|
177
|
+
i += 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (token === "--skill-name") {
|
|
181
|
+
options.skillName = args[i + 1] ?? "";
|
|
182
|
+
i += 1;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (token === "--wallet-dir") {
|
|
186
|
+
options.walletDir = args[i + 1] ?? "";
|
|
187
|
+
i += 1;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (token === "--wallet-name") {
|
|
191
|
+
options.walletName = args[i + 1] ?? "";
|
|
192
|
+
i += 1;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
57
195
|
if (token.startsWith("--")) {
|
|
58
196
|
throw new Error(`unknown option: ${token}`);
|
|
59
197
|
}
|
|
@@ -93,6 +231,403 @@ async function listPacks() {
|
|
|
93
231
|
}
|
|
94
232
|
}
|
|
95
233
|
|
|
234
|
+
function normalizeEnvProfile(rawProfile) {
|
|
235
|
+
const profile = (rawProfile || "").trim().toLowerCase();
|
|
236
|
+
if (SUPPORTED_ENV_PROFILES.has(profile)) {
|
|
237
|
+
return profile;
|
|
238
|
+
}
|
|
239
|
+
throw new Error(
|
|
240
|
+
`invalid --env-profile value: ${rawProfile} (expected strategy|participant|all)`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function defaultEnvProfileForPack(packName) {
|
|
245
|
+
const normalized = String(packName || "").trim().toLowerCase();
|
|
246
|
+
if (normalized.includes("participant")) {
|
|
247
|
+
return "participant";
|
|
248
|
+
}
|
|
249
|
+
if (normalized.includes("strategy")) {
|
|
250
|
+
return "strategy";
|
|
251
|
+
}
|
|
252
|
+
return "all";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function normalizeBotInitRole(rawRole) {
|
|
256
|
+
const role = (rawRole || "").trim().toLowerCase();
|
|
257
|
+
if (SUPPORTED_BOT_INIT_ROLES.has(role)) {
|
|
258
|
+
return role;
|
|
259
|
+
}
|
|
260
|
+
throw new Error(
|
|
261
|
+
`invalid --role value: ${rawRole} (expected strategy|participant)`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function inferRoleFromHint(hint) {
|
|
266
|
+
const normalized = (hint || "").trim().toLowerCase();
|
|
267
|
+
if (!normalized) return "";
|
|
268
|
+
if (normalized.includes("participant")) return "participant";
|
|
269
|
+
if (normalized.includes("strategy")) return "strategy";
|
|
270
|
+
return "";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function resolveBotInitRole(options) {
|
|
274
|
+
if (options.role && options.role.trim().length > 0) {
|
|
275
|
+
return normalizeBotInitRole(options.role);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const envSkillHints = [
|
|
279
|
+
process.env.OPENCLAW_SKILL_KEY,
|
|
280
|
+
process.env.OPENCLAW_ACTIVE_SKILL,
|
|
281
|
+
process.env.SKILL_KEY,
|
|
282
|
+
process.env.SKILL_NAME
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
const hints = [
|
|
286
|
+
options.skillName,
|
|
287
|
+
options.envFile ? path.basename(options.envFile) : "",
|
|
288
|
+
options.walletName,
|
|
289
|
+
...envSkillHints
|
|
290
|
+
].filter((entry) => Boolean(entry && entry.trim().length > 0));
|
|
291
|
+
|
|
292
|
+
const inferredRoles = new Set();
|
|
293
|
+
for (const hint of hints) {
|
|
294
|
+
const inferred = inferRoleFromHint(hint);
|
|
295
|
+
if (inferred) {
|
|
296
|
+
inferredRoles.add(inferred);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (inferredRoles.size === 1) {
|
|
301
|
+
return [...inferredRoles][0];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (inferredRoles.size > 1) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
"conflicting role hints found. pass explicit --role <strategy|participant>."
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
throw new Error(
|
|
311
|
+
"cannot infer bot role. pass --role or include strategy/participant in --skill-name, --env-path, --wallet-name, or OPENCLAW_SKILL_KEY."
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function runtimeEnvExamplePath(runtimeDir, runtimePackage) {
|
|
316
|
+
return path.join(runtimeDir, "node_modules", ...runtimePackage.split("/"), ".env.example");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function buildEnvScaffold(profile, runtimeDir, runtimePackage) {
|
|
320
|
+
if (profile === "strategy") {
|
|
321
|
+
return STRATEGY_ENV_TEMPLATE;
|
|
322
|
+
}
|
|
323
|
+
if (profile === "participant") {
|
|
324
|
+
return PARTICIPANT_ENV_TEMPLATE;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const runtimeTemplate = runtimeEnvExamplePath(runtimeDir, runtimePackage);
|
|
328
|
+
if (existsSync(runtimeTemplate)) {
|
|
329
|
+
return readFile(runtimeTemplate, "utf8");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return `${STRATEGY_ENV_TEMPLATE}\n\n${PARTICIPANT_ENV_TEMPLATE}`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function writeEnvScaffold(options) {
|
|
336
|
+
const runtimeDir = options.runtimeDir ? path.resolve(options.runtimeDir) : process.cwd();
|
|
337
|
+
const runtimePackage = options.runtimePackage || DEFAULT_RUNTIME_PACKAGE;
|
|
338
|
+
const rawProfile =
|
|
339
|
+
typeof options.envProfile === "string" && options.envProfile.trim().length > 0
|
|
340
|
+
? options.envProfile
|
|
341
|
+
: "all";
|
|
342
|
+
const profile = normalizeEnvProfile(rawProfile);
|
|
343
|
+
const envTarget = options.envFile
|
|
344
|
+
? path.resolve(options.envFile)
|
|
345
|
+
: path.join(runtimeDir, ".env.openfunderse");
|
|
346
|
+
|
|
347
|
+
const alreadyExists = existsSync(envTarget);
|
|
348
|
+
if (alreadyExists && !options.force) {
|
|
349
|
+
return {
|
|
350
|
+
written: false,
|
|
351
|
+
envFile: envTarget,
|
|
352
|
+
profile
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (alreadyExists && options.force) {
|
|
357
|
+
await rm(envTarget, { force: true });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const scaffold = await buildEnvScaffold(profile, runtimeDir, runtimePackage);
|
|
361
|
+
await mkdir(path.dirname(envTarget), { recursive: true });
|
|
362
|
+
await writeFile(envTarget, scaffold.endsWith("\n") ? scaffold : `${scaffold}\n`);
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
written: true,
|
|
366
|
+
envFile: envTarget,
|
|
367
|
+
profile
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function defaultEnvPathForRole(role) {
|
|
372
|
+
return path.join(process.cwd(), `.env.${role}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function readAssignedEnvValue(content, key) {
|
|
376
|
+
const lines = content.split(/\r?\n/);
|
|
377
|
+
for (const line of lines) {
|
|
378
|
+
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
379
|
+
if (!match) continue;
|
|
380
|
+
if (match[1] !== key) continue;
|
|
381
|
+
return match[2];
|
|
382
|
+
}
|
|
383
|
+
return "";
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function isPlaceholderEnvValue(value) {
|
|
387
|
+
const normalized = (value || "").trim();
|
|
388
|
+
if (!normalized) return true;
|
|
389
|
+
if (normalized === "replace_me") return true;
|
|
390
|
+
if (normalized.toLowerCase() === TEMP_PRIVATE_KEY.toLowerCase()) return true;
|
|
391
|
+
if (/^0xYOUR_/i.test(normalized)) return true;
|
|
392
|
+
if (/^YOUR_/i.test(normalized)) return true;
|
|
393
|
+
if (/^0x0+$/i.test(normalized)) return true;
|
|
394
|
+
if (normalized === "0x0000000000000000000000000000000000000000") return true;
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function upsertEnvValues(content, updates) {
|
|
399
|
+
const lines = content.split(/\r?\n/);
|
|
400
|
+
const applied = new Set();
|
|
401
|
+
|
|
402
|
+
const patched = lines.map((line) => {
|
|
403
|
+
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
404
|
+
if (!match) return line;
|
|
405
|
+
const key = match[1];
|
|
406
|
+
if (!(key in updates)) return line;
|
|
407
|
+
applied.add(key);
|
|
408
|
+
return `${key}=${updates[key]}`;
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
412
|
+
if (applied.has(key)) continue;
|
|
413
|
+
patched.push(`${key}=${value}`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return `${patched.join("\n").replace(/\n+$/g, "")}\n`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function shellQuote(input) {
|
|
420
|
+
return `'${input.replace(/'/g, `'\\''`)}'`;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function runCommandCapture(cmd, args, cwd = process.cwd()) {
|
|
424
|
+
return await new Promise((resolve, reject) => {
|
|
425
|
+
const child = spawn(cmd, args, {
|
|
426
|
+
cwd,
|
|
427
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const stdout = [];
|
|
431
|
+
const stderr = [];
|
|
432
|
+
|
|
433
|
+
child.stdout.on("data", (chunk) => {
|
|
434
|
+
stdout.push(chunk);
|
|
435
|
+
});
|
|
436
|
+
child.stderr.on("data", (chunk) => {
|
|
437
|
+
stderr.push(chunk);
|
|
438
|
+
});
|
|
439
|
+
child.on("error", (error) => {
|
|
440
|
+
reject(error);
|
|
441
|
+
});
|
|
442
|
+
child.on("exit", (code) => {
|
|
443
|
+
if (code === 0) {
|
|
444
|
+
resolve({
|
|
445
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
446
|
+
stderr: Buffer.concat(stderr).toString("utf8")
|
|
447
|
+
});
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const stderrText = Buffer.concat(stderr).toString("utf8").trim();
|
|
451
|
+
reject(
|
|
452
|
+
new Error(
|
|
453
|
+
`command failed: ${cmd} ${args.join(" ")} (exit ${code})${stderrText ? ` - ${stderrText}` : ""}`
|
|
454
|
+
)
|
|
455
|
+
);
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async function confirmBotInit({ role, envFile, privateKeyKey, isRotation, assumeYes }) {
|
|
461
|
+
const mode = isRotation ? "wallet rotation" : "new wallet bootstrap";
|
|
462
|
+
console.log("WARNING: bot-init will generate a new wallet and update your env private key.");
|
|
463
|
+
console.log(`- Mode: ${mode}`);
|
|
464
|
+
console.log(`- Role: ${role}`);
|
|
465
|
+
console.log(`- Env file: ${envFile}`);
|
|
466
|
+
console.log(`- Key field: ${privateKeyKey}`);
|
|
467
|
+
console.log("- Existing funds tied to old key are not moved automatically.");
|
|
468
|
+
|
|
469
|
+
if (assumeYes) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
474
|
+
throw new Error("bot-init requires confirmation in TTY. Re-run with --yes to confirm non-interactively.");
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const rl = createInterface({
|
|
478
|
+
input: process.stdin,
|
|
479
|
+
output: process.stdout
|
|
480
|
+
});
|
|
481
|
+
try {
|
|
482
|
+
const answer = await rl.question("Type YES to continue: ");
|
|
483
|
+
if (answer.trim().toUpperCase() !== "YES") {
|
|
484
|
+
throw new Error("bot-init cancelled by user.");
|
|
485
|
+
}
|
|
486
|
+
} finally {
|
|
487
|
+
rl.close();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function generateMonadWalletWithCast() {
|
|
492
|
+
let output;
|
|
493
|
+
try {
|
|
494
|
+
output = await runCommandCapture("cast", ["wallet", "new", "--json"]);
|
|
495
|
+
} catch (error) {
|
|
496
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
497
|
+
if (message.includes("ENOENT")) {
|
|
498
|
+
throw new Error(
|
|
499
|
+
"cast is not installed. Install Foundry first (https://book.getfoundry.sh/getting-started/installation)."
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let parsed;
|
|
506
|
+
try {
|
|
507
|
+
parsed = JSON.parse(output.stdout);
|
|
508
|
+
} catch {
|
|
509
|
+
throw new Error(`failed to parse cast output as JSON: ${output.stdout}`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const first = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
513
|
+
const address = String(first?.address ?? "");
|
|
514
|
+
const privateKey = String(first?.private_key ?? first?.privateKey ?? "");
|
|
515
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
516
|
+
throw new Error(`invalid wallet address from cast: ${address}`);
|
|
517
|
+
}
|
|
518
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(privateKey)) {
|
|
519
|
+
throw new Error("invalid private key from cast output");
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
address,
|
|
524
|
+
privateKey
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function roleEnvUpdates(role, wallet) {
|
|
529
|
+
if (role === "strategy") {
|
|
530
|
+
return {
|
|
531
|
+
CHAIN_ID: DEFAULT_MONAD_CHAIN_ID,
|
|
532
|
+
STRATEGY_PRIVATE_KEY: wallet.privateKey,
|
|
533
|
+
BOT_ADDRESS: wallet.address
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
CHAIN_ID: DEFAULT_MONAD_CHAIN_ID,
|
|
538
|
+
PARTICIPANT_PRIVATE_KEY: wallet.privateKey,
|
|
539
|
+
PARTICIPANT_BOT_ADDRESS: wallet.address,
|
|
540
|
+
BOT_ADDRESS: wallet.address
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function persistWallet(role, wallet, options) {
|
|
545
|
+
const walletDir = options.walletDir
|
|
546
|
+
? path.resolve(options.walletDir)
|
|
547
|
+
: path.join(defaultCodexHome(), "openfunderse", "wallets");
|
|
548
|
+
const timestamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+$/, "");
|
|
549
|
+
const rawName = (options.walletName || `${role}-${timestamp}`).trim();
|
|
550
|
+
if (!rawName) {
|
|
551
|
+
throw new Error("--wallet-name must not be empty");
|
|
552
|
+
}
|
|
553
|
+
if (rawName.includes("/") || rawName.includes("\\")) {
|
|
554
|
+
throw new Error("--wallet-name must be a file name, not a path");
|
|
555
|
+
}
|
|
556
|
+
const fileName = rawName.endsWith(".json") ? rawName : `${rawName}.json`;
|
|
557
|
+
const walletPath = path.join(walletDir, fileName);
|
|
558
|
+
if (existsSync(walletPath) && !options.force) {
|
|
559
|
+
throw new Error(`wallet file already exists: ${walletPath} (use --force to overwrite)`);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const payload = {
|
|
563
|
+
createdAt: new Date().toISOString(),
|
|
564
|
+
role,
|
|
565
|
+
chainId: DEFAULT_MONAD_CHAIN_ID,
|
|
566
|
+
address: wallet.address,
|
|
567
|
+
privateKey: wallet.privateKey
|
|
568
|
+
};
|
|
569
|
+
await mkdir(walletDir, { recursive: true, mode: 0o700 });
|
|
570
|
+
await writeFile(walletPath, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
571
|
+
mode: 0o600
|
|
572
|
+
});
|
|
573
|
+
await chmod(walletPath, 0o600);
|
|
574
|
+
return walletPath;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function assertPrivateKeyRotationAllowed(envContent, role, force) {
|
|
578
|
+
const privateKeyKey = role === "strategy" ? "STRATEGY_PRIVATE_KEY" : "PARTICIPANT_PRIVATE_KEY";
|
|
579
|
+
const existing = readAssignedEnvValue(envContent, privateKeyKey);
|
|
580
|
+
if (!existing) return;
|
|
581
|
+
if (isPlaceholderEnvValue(existing)) return;
|
|
582
|
+
if (force) return;
|
|
583
|
+
throw new Error(
|
|
584
|
+
`${privateKeyKey} already has a value. bot-init creates a new wallet and will replace it; rerun with --force to rotate.`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async function runBotInit(options) {
|
|
589
|
+
const role = resolveBotInitRole(options);
|
|
590
|
+
const envFile = options.envFile ? path.resolve(options.envFile) : defaultEnvPathForRole(role);
|
|
591
|
+
const runtimeDir = options.runtimeDir ? path.resolve(options.runtimeDir) : process.cwd();
|
|
592
|
+
const runtimePackage = options.runtimePackage || DEFAULT_RUNTIME_PACKAGE;
|
|
593
|
+
|
|
594
|
+
let envContent;
|
|
595
|
+
if (existsSync(envFile)) {
|
|
596
|
+
envContent = await readFile(envFile, "utf8");
|
|
597
|
+
} else {
|
|
598
|
+
envContent = await buildEnvScaffold(role, runtimeDir, runtimePackage);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const privateKeyKey = role === "strategy" ? "STRATEGY_PRIVATE_KEY" : "PARTICIPANT_PRIVATE_KEY";
|
|
602
|
+
const existingPrivateKey = readAssignedEnvValue(envContent, privateKeyKey);
|
|
603
|
+
const isRotation = Boolean(existingPrivateKey) && !isPlaceholderEnvValue(existingPrivateKey);
|
|
604
|
+
|
|
605
|
+
assertPrivateKeyRotationAllowed(envContent, role, options.force);
|
|
606
|
+
await confirmBotInit({
|
|
607
|
+
role,
|
|
608
|
+
envFile,
|
|
609
|
+
privateKeyKey,
|
|
610
|
+
isRotation,
|
|
611
|
+
assumeYes: options.yes
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
const wallet = await generateMonadWalletWithCast();
|
|
615
|
+
const walletPath = await persistWallet(role, wallet, options);
|
|
616
|
+
const updates = roleEnvUpdates(role, wallet);
|
|
617
|
+
const nextEnvContent = upsertEnvValues(envContent, updates);
|
|
618
|
+
|
|
619
|
+
await mkdir(path.dirname(envFile), { recursive: true });
|
|
620
|
+
await writeFile(envFile, nextEnvContent);
|
|
621
|
+
await chmod(envFile, 0o600);
|
|
622
|
+
const sourceCommand = `set -a; source ${shellQuote(envFile)}; set +a`;
|
|
623
|
+
|
|
624
|
+
console.log(`Initialized ${role} bot wallet for Monad testnet (${DEFAULT_MONAD_CHAIN_ID}).`);
|
|
625
|
+
console.log(`Address: ${wallet.address}`);
|
|
626
|
+
console.log(`Env file updated: ${envFile}`);
|
|
627
|
+
console.log(`Wallet backup (keep secret): ${walletPath}`);
|
|
628
|
+
console.log(`Load env now: ${sourceCommand}`);
|
|
629
|
+
}
|
|
630
|
+
|
|
96
631
|
async function loadManifest(packDir) {
|
|
97
632
|
const candidates = [
|
|
98
633
|
path.join(packDir, "manifest.json"),
|
|
@@ -122,6 +657,65 @@ async function copySkillDir(sourceDir, destinationDir, force) {
|
|
|
122
657
|
await cp(sourceDir, destinationDir, { recursive: true });
|
|
123
658
|
}
|
|
124
659
|
|
|
660
|
+
function detectRuntimeManager() {
|
|
661
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
662
|
+
if (userAgent.startsWith("pnpm/")) return "pnpm";
|
|
663
|
+
if (userAgent.startsWith("yarn/")) return "yarn";
|
|
664
|
+
if (userAgent.startsWith("bun/")) return "bun";
|
|
665
|
+
return "npm";
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function commandForRuntimeInstall(manager, runtimePackage) {
|
|
669
|
+
if (manager === "pnpm") {
|
|
670
|
+
return { cmd: "pnpm", args: ["add", runtimePackage] };
|
|
671
|
+
}
|
|
672
|
+
if (manager === "yarn") {
|
|
673
|
+
return { cmd: "yarn", args: ["add", runtimePackage] };
|
|
674
|
+
}
|
|
675
|
+
if (manager === "bun") {
|
|
676
|
+
return { cmd: "bun", args: ["add", runtimePackage] };
|
|
677
|
+
}
|
|
678
|
+
return { cmd: "npm", args: ["install", runtimePackage] };
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
async function installRuntimePackage(options) {
|
|
682
|
+
const runtimePackage = options.runtimePackage || DEFAULT_RUNTIME_PACKAGE;
|
|
683
|
+
const runtimeDir = options.runtimeDir ? path.resolve(options.runtimeDir) : process.cwd();
|
|
684
|
+
const runtimeManager = options.runtimeManager || detectRuntimeManager();
|
|
685
|
+
const packageJsonPath = path.join(runtimeDir, "package.json");
|
|
686
|
+
|
|
687
|
+
if (!existsSync(packageJsonPath)) {
|
|
688
|
+
throw new Error(
|
|
689
|
+
`runtime install target has no package.json: ${runtimeDir} (use --runtime-dir <project-root>)`
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const { cmd, args } = commandForRuntimeInstall(runtimeManager, runtimePackage);
|
|
694
|
+
await new Promise((resolve, reject) => {
|
|
695
|
+
const child = spawn(cmd, args, {
|
|
696
|
+
cwd: runtimeDir,
|
|
697
|
+
stdio: "inherit"
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
child.on("error", (error) => {
|
|
701
|
+
reject(error);
|
|
702
|
+
});
|
|
703
|
+
child.on("exit", (code) => {
|
|
704
|
+
if (code === 0) {
|
|
705
|
+
resolve(undefined);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
reject(new Error(`runtime install failed with exit code ${code}`));
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
return {
|
|
713
|
+
runtimePackage,
|
|
714
|
+
runtimeDir,
|
|
715
|
+
runtimeManager
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|
|
125
719
|
async function installPack(packName, options) {
|
|
126
720
|
const packDir = path.join(PACKS_ROOT, packName);
|
|
127
721
|
if (!existsSync(packDir)) {
|
|
@@ -180,10 +774,44 @@ async function installPack(packName, options) {
|
|
|
180
774
|
};
|
|
181
775
|
await writeFile(path.join(packMetaRoot, "install.json"), `${JSON.stringify(installedMeta, null, 2)}\n`);
|
|
182
776
|
|
|
777
|
+
let runtimeInstallMeta = null;
|
|
778
|
+
if (options.withRuntime) {
|
|
779
|
+
runtimeInstallMeta = await installRuntimePackage(options);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
let envScaffoldMeta = null;
|
|
783
|
+
if (options.initEnv) {
|
|
784
|
+
const resolvedEnvProfile = options.envProfileExplicit
|
|
785
|
+
? options.envProfile
|
|
786
|
+
: defaultEnvProfileForPack(packName);
|
|
787
|
+
const envOptions = {
|
|
788
|
+
...options,
|
|
789
|
+
envProfile: resolvedEnvProfile,
|
|
790
|
+
runtimeDir: runtimeInstallMeta?.runtimeDir ?? options.runtimeDir,
|
|
791
|
+
runtimePackage: runtimeInstallMeta?.runtimePackage ?? options.runtimePackage
|
|
792
|
+
};
|
|
793
|
+
envScaffoldMeta = await writeEnvScaffold(envOptions);
|
|
794
|
+
}
|
|
795
|
+
|
|
183
796
|
console.log(`Installed pack: ${packName}`);
|
|
184
797
|
console.log(`Skills root: ${skillsRoot}`);
|
|
185
798
|
console.log(`Installed skills: ${installed.join(", ")}`);
|
|
186
799
|
console.log(`Pack metadata: ${packMetaRoot}`);
|
|
800
|
+
if (runtimeInstallMeta) {
|
|
801
|
+
console.log(
|
|
802
|
+
`Installed runtime package: ${runtimeInstallMeta.runtimePackage} (${runtimeInstallMeta.runtimeManager})`
|
|
803
|
+
);
|
|
804
|
+
console.log(`Runtime install dir: ${runtimeInstallMeta.runtimeDir}`);
|
|
805
|
+
}
|
|
806
|
+
if (envScaffoldMeta) {
|
|
807
|
+
if (envScaffoldMeta.written) {
|
|
808
|
+
console.log(`Generated env scaffold (${envScaffoldMeta.profile}): ${envScaffoldMeta.envFile}`);
|
|
809
|
+
} else {
|
|
810
|
+
console.log(
|
|
811
|
+
`Env scaffold already exists (use --force to overwrite): ${envScaffoldMeta.envFile}`
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
187
815
|
console.log("Restart Codex to pick up new skills.");
|
|
188
816
|
}
|
|
189
817
|
|
|
@@ -209,6 +837,11 @@ async function main() {
|
|
|
209
837
|
return;
|
|
210
838
|
}
|
|
211
839
|
|
|
840
|
+
if (command === "bot-init") {
|
|
841
|
+
await runBotInit(options);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
|
|
212
845
|
throw new Error(`unknown command: ${command}`);
|
|
213
846
|
}
|
|
214
847
|
|