@exagent/agent 0.2.1 → 0.3.1
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/.turbo/turbo-build.log +6 -6
- package/dist/chunk-4UVMO6ZM.js +6318 -0
- package/dist/chunk-5NU6FDDE.js +6020 -0
- package/dist/chunk-GPMXUMYH.js +5991 -0
- package/dist/chunk-IJK4EFTJ.js +6043 -0
- package/dist/chunk-J3NG7AGT.js +6047 -0
- package/dist/chunk-QG22GADV.js +6316 -0
- package/dist/chunk-SVFTC5V2.js +6021 -0
- package/dist/chunk-TDACLKD7.js +5867 -0
- package/dist/chunk-UAP5CTHB.js +5985 -0
- package/dist/chunk-VDK4XPAC.js +6318 -0
- package/dist/cli.js +337 -15
- package/dist/index.d.ts +29 -1
- package/dist/index.js +1 -1
- package/package.json +15 -8
- package/src/cli.ts +54 -15
- package/src/config.ts +231 -25
- package/src/runtime.ts +370 -37
- package/src/scrub-secrets.ts +39 -0
- package/src/setup.ts +332 -0
- package/src/strategy/loader.ts +132 -1
- package/src/ui.ts +75 -0
package/dist/cli.js
CHANGED
|
@@ -1,57 +1,379 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
AgentRuntime,
|
|
4
|
+
encryptSecretPayload,
|
|
5
|
+
getDefaultSecureStorePath,
|
|
4
6
|
listTemplates,
|
|
5
7
|
loadConfig,
|
|
8
|
+
readConfigFile,
|
|
9
|
+
writeConfigFile,
|
|
6
10
|
writeSampleConfig
|
|
7
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-VDK4XPAC.js";
|
|
8
12
|
|
|
9
13
|
// src/cli.ts
|
|
10
14
|
import { Command } from "commander";
|
|
15
|
+
|
|
16
|
+
// src/setup.ts
|
|
17
|
+
import { chmodSync, existsSync, mkdirSync, writeFileSync } from "fs";
|
|
18
|
+
import { homedir } from "os";
|
|
19
|
+
import { dirname, resolve } from "path";
|
|
20
|
+
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
21
|
+
import * as clack from "@clack/prompts";
|
|
22
|
+
|
|
23
|
+
// src/ui.ts
|
|
24
|
+
import figlet from "figlet";
|
|
25
|
+
import gradient from "gradient-string";
|
|
26
|
+
import boxen from "boxen";
|
|
27
|
+
import pc from "picocolors";
|
|
28
|
+
import { createRequire } from "module";
|
|
29
|
+
var brandGradient = gradient(["#3B82F6", "#6366F1", "#7C3AED"]);
|
|
30
|
+
var accentGradient = gradient(["#22D3EE", "#3B82F6"]);
|
|
31
|
+
function getVersion() {
|
|
32
|
+
try {
|
|
33
|
+
const require2 = createRequire(import.meta.url);
|
|
34
|
+
const pkg = require2("../package.json");
|
|
35
|
+
return pkg.version || "0.0.0";
|
|
36
|
+
} catch {
|
|
37
|
+
return "0.0.0";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function printBanner() {
|
|
41
|
+
const art = figlet.textSync("EXAGENT", {
|
|
42
|
+
font: "Small",
|
|
43
|
+
horizontalLayout: "default"
|
|
44
|
+
});
|
|
45
|
+
console.log();
|
|
46
|
+
console.log(brandGradient(art));
|
|
47
|
+
console.log(pc.dim(` v${getVersion()}`));
|
|
48
|
+
console.log();
|
|
49
|
+
}
|
|
50
|
+
function printSuccess(title, lines) {
|
|
51
|
+
const body = [
|
|
52
|
+
"",
|
|
53
|
+
pc.bold(pc.white(title)),
|
|
54
|
+
"",
|
|
55
|
+
...lines.map((l) => ` ${l}`),
|
|
56
|
+
""
|
|
57
|
+
].join("\n");
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(boxen(body, {
|
|
60
|
+
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
61
|
+
borderColor: "#3B82F6",
|
|
62
|
+
borderStyle: "round",
|
|
63
|
+
dimBorder: false
|
|
64
|
+
}));
|
|
65
|
+
console.log();
|
|
66
|
+
}
|
|
67
|
+
function printStep(step, total, label) {
|
|
68
|
+
console.log();
|
|
69
|
+
console.log(accentGradient(` Step ${step} of ${total}`) + pc.dim(` \u2014 ${label}`));
|
|
70
|
+
}
|
|
71
|
+
function printDone(message) {
|
|
72
|
+
console.log(` ${pc.green("\u2713")} ${message}`);
|
|
73
|
+
}
|
|
74
|
+
function printInfo(message) {
|
|
75
|
+
console.log(` ${pc.dim("\u2502")} ${message}`);
|
|
76
|
+
}
|
|
77
|
+
function printError(message) {
|
|
78
|
+
console.log(` ${pc.red("\u2717")} ${message}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/setup.ts
|
|
82
|
+
function expandHomeDir(path) {
|
|
83
|
+
if (!path.startsWith("~/")) return path;
|
|
84
|
+
return resolve(homedir(), path.slice(2));
|
|
85
|
+
}
|
|
86
|
+
function cancelled() {
|
|
87
|
+
clack.cancel("Setup cancelled.");
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
async function consumeBootstrapPackage(config) {
|
|
91
|
+
if (!config.secrets?.bootstrapToken) {
|
|
92
|
+
return { apiToken: "" };
|
|
93
|
+
}
|
|
94
|
+
const apiHost = new URL(config.apiUrl).host;
|
|
95
|
+
printInfo(`Connecting to ${pc.cyan(apiHost)}...`);
|
|
96
|
+
const res = await fetch(`${config.apiUrl}/v1/agents/bootstrap/consume`, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: { "Content-Type": "application/json" },
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
agentId: config.agentId,
|
|
101
|
+
token: config.secrets.bootstrapToken
|
|
102
|
+
})
|
|
103
|
+
});
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
const body = await res.text();
|
|
106
|
+
throw new Error(`Failed to consume bootstrap package: ${body}`);
|
|
107
|
+
}
|
|
108
|
+
const data = await res.json();
|
|
109
|
+
return data.payload;
|
|
110
|
+
}
|
|
111
|
+
async function setupWallet(config) {
|
|
112
|
+
if (config.wallet?.privateKey) {
|
|
113
|
+
const account = privateKeyToAccount(config.wallet.privateKey);
|
|
114
|
+
printDone(`Using existing wallet: ${pc.dim(account.address)}`);
|
|
115
|
+
return config.wallet.privateKey;
|
|
116
|
+
}
|
|
117
|
+
const method = await clack.select({
|
|
118
|
+
message: "How would you like to set up your wallet?",
|
|
119
|
+
options: [
|
|
120
|
+
{ value: "generate", label: "Generate new wallet locally", hint: "recommended" },
|
|
121
|
+
{ value: "import", label: "Import existing private key" }
|
|
122
|
+
]
|
|
123
|
+
});
|
|
124
|
+
if (clack.isCancel(method)) cancelled();
|
|
125
|
+
if (method === "generate") {
|
|
126
|
+
const privateKey2 = generatePrivateKey();
|
|
127
|
+
const address2 = privateKeyToAccount(privateKey2).address;
|
|
128
|
+
printDone(`Wallet created: ${pc.dim(address2)}`);
|
|
129
|
+
return privateKey2;
|
|
130
|
+
}
|
|
131
|
+
const privateKey = await clack.password({
|
|
132
|
+
message: "Wallet private key (0x...):",
|
|
133
|
+
validate: (val) => {
|
|
134
|
+
if (!/^0x[a-fA-F0-9]{64}$/.test(val)) {
|
|
135
|
+
return "Invalid private key. Expected a 32-byte hex string prefixed with 0x.";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
if (clack.isCancel(privateKey)) cancelled();
|
|
140
|
+
const address = privateKeyToAccount(privateKey).address;
|
|
141
|
+
printDone(`Wallet imported: ${pc.dim(address)}`);
|
|
142
|
+
return privateKey;
|
|
143
|
+
}
|
|
144
|
+
var LLM_PROVIDERS = ["openai", "anthropic", "google", "deepseek", "mistral", "groq", "together", "ollama"];
|
|
145
|
+
async function setupLlm(config, bootstrapPayload) {
|
|
146
|
+
let provider = config.llm?.provider || bootstrapPayload.llm?.provider;
|
|
147
|
+
if (provider) {
|
|
148
|
+
printInfo(`Provider: ${pc.cyan(provider)} ${pc.dim("(from dashboard)")}`);
|
|
149
|
+
} else {
|
|
150
|
+
const selected = await clack.select({
|
|
151
|
+
message: "LLM provider:",
|
|
152
|
+
options: LLM_PROVIDERS.map((p) => ({ value: p, label: p }))
|
|
153
|
+
});
|
|
154
|
+
if (clack.isCancel(selected)) cancelled();
|
|
155
|
+
provider = selected;
|
|
156
|
+
}
|
|
157
|
+
let model = config.llm?.model || bootstrapPayload.llm?.model;
|
|
158
|
+
if (model) {
|
|
159
|
+
printInfo(`Model: ${pc.cyan(model)} ${pc.dim("(from dashboard)")}`);
|
|
160
|
+
} else {
|
|
161
|
+
const entered = await clack.text({
|
|
162
|
+
message: "LLM model:",
|
|
163
|
+
placeholder: "gpt-4o",
|
|
164
|
+
validate: (val) => {
|
|
165
|
+
if (!val.trim()) return "Model name is required.";
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
if (clack.isCancel(entered)) cancelled();
|
|
169
|
+
model = entered;
|
|
170
|
+
}
|
|
171
|
+
let apiKey;
|
|
172
|
+
if (bootstrapPayload.llm?.apiKey) {
|
|
173
|
+
const useBootstrap = await clack.confirm({
|
|
174
|
+
message: "LLM API key received from dashboard. Use it?",
|
|
175
|
+
initialValue: true
|
|
176
|
+
});
|
|
177
|
+
if (clack.isCancel(useBootstrap)) cancelled();
|
|
178
|
+
if (useBootstrap) {
|
|
179
|
+
apiKey = bootstrapPayload.llm.apiKey;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!apiKey) {
|
|
183
|
+
apiKey = config.llm?.apiKey;
|
|
184
|
+
}
|
|
185
|
+
if (!apiKey) {
|
|
186
|
+
const entered = await clack.password({
|
|
187
|
+
message: "LLM API key:",
|
|
188
|
+
validate: (val) => {
|
|
189
|
+
if (!val.trim()) return "API key is required.";
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
if (clack.isCancel(entered)) cancelled();
|
|
193
|
+
apiKey = entered;
|
|
194
|
+
}
|
|
195
|
+
printDone("LLM configured");
|
|
196
|
+
return { provider, model, apiKey };
|
|
197
|
+
}
|
|
198
|
+
async function setupEncryption() {
|
|
199
|
+
printInfo(`Secrets encrypted with ${pc.cyan("AES-256-GCM")} (${pc.cyan("scrypt")} KDF)`);
|
|
200
|
+
printInfo("The password never leaves this machine.");
|
|
201
|
+
console.log();
|
|
202
|
+
const password2 = await clack.password({
|
|
203
|
+
message: "Choose a device password (12+ characters):",
|
|
204
|
+
validate: (val) => {
|
|
205
|
+
if (val.length < 12) return "Password must be at least 12 characters.";
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
if (clack.isCancel(password2)) cancelled();
|
|
209
|
+
const confirm2 = await clack.password({
|
|
210
|
+
message: "Confirm password:",
|
|
211
|
+
validate: (val) => {
|
|
212
|
+
if (val !== password2) return "Passwords do not match.";
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
if (clack.isCancel(confirm2)) cancelled();
|
|
216
|
+
return password2;
|
|
217
|
+
}
|
|
218
|
+
function writeSecureStore(path, secrets, password2) {
|
|
219
|
+
const secureStorePath = expandHomeDir(path);
|
|
220
|
+
const encrypted = encryptSecretPayload(secrets, password2);
|
|
221
|
+
const dir = dirname(secureStorePath);
|
|
222
|
+
if (!existsSync(dir)) {
|
|
223
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
224
|
+
}
|
|
225
|
+
writeFileSync(secureStorePath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
226
|
+
try {
|
|
227
|
+
chmodSync(secureStorePath, 384);
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
return secureStorePath;
|
|
231
|
+
}
|
|
232
|
+
async function promptSecretPassword(question = "Device password:") {
|
|
233
|
+
const password2 = await clack.password({ message: question });
|
|
234
|
+
if (clack.isCancel(password2)) cancelled();
|
|
235
|
+
return password2;
|
|
236
|
+
}
|
|
237
|
+
async function ensureLocalSetup(configPath) {
|
|
238
|
+
const config = readConfigFile(configPath);
|
|
239
|
+
const existingSecureStorePath = config.secrets?.secureStorePath ? expandHomeDir(config.secrets.secureStorePath) : null;
|
|
240
|
+
if (existingSecureStorePath && !config.secrets?.bootstrapToken && existsSync(existingSecureStorePath) && !config.apiToken && !config.wallet?.privateKey && !config.llm.apiKey) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
printBanner();
|
|
244
|
+
clack.intro(pc.bold("Agent Setup"));
|
|
245
|
+
printStep(1, 4, "Bootstrap package");
|
|
246
|
+
const bootstrapPayload = await consumeBootstrapPackage(config);
|
|
247
|
+
if (config.secrets?.bootstrapToken) {
|
|
248
|
+
printDone("Bootstrap package consumed");
|
|
249
|
+
if (bootstrapPayload.llm?.provider) {
|
|
250
|
+
printInfo(`LLM config received: ${pc.cyan(bootstrapPayload.llm.provider)}${bootstrapPayload.llm.model ? ` / ${pc.cyan(bootstrapPayload.llm.model)}` : ""}`);
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
printInfo("No bootstrap token \u2014 manual configuration");
|
|
254
|
+
}
|
|
255
|
+
printStep(2, 4, "Wallet setup");
|
|
256
|
+
const walletPrivateKey = await setupWallet(config);
|
|
257
|
+
printStep(3, 4, "LLM configuration");
|
|
258
|
+
const llm = await setupLlm(config, bootstrapPayload);
|
|
259
|
+
printStep(4, 4, "Device encryption");
|
|
260
|
+
const password2 = await setupEncryption();
|
|
261
|
+
const secrets = {
|
|
262
|
+
apiToken: bootstrapPayload.apiToken || config.apiToken || "",
|
|
263
|
+
walletPrivateKey,
|
|
264
|
+
llmApiKey: llm.apiKey
|
|
265
|
+
};
|
|
266
|
+
if (!secrets.apiToken) {
|
|
267
|
+
const token = await clack.password({
|
|
268
|
+
message: "Agent relay token:",
|
|
269
|
+
validate: (val) => {
|
|
270
|
+
if (!val.trim()) return "Relay token is required.";
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
if (clack.isCancel(token)) cancelled();
|
|
274
|
+
secrets.apiToken = token;
|
|
275
|
+
}
|
|
276
|
+
const nextConfig = structuredClone(config);
|
|
277
|
+
nextConfig.llm = {
|
|
278
|
+
...nextConfig.llm,
|
|
279
|
+
provider: llm.provider,
|
|
280
|
+
model: llm.model
|
|
281
|
+
};
|
|
282
|
+
delete nextConfig.apiToken;
|
|
283
|
+
delete nextConfig.wallet;
|
|
284
|
+
delete nextConfig.llm.apiKey;
|
|
285
|
+
const secureStorePath = writeSecureStore(
|
|
286
|
+
nextConfig.secrets?.secureStorePath || getDefaultSecureStorePath(nextConfig.agentId),
|
|
287
|
+
secrets,
|
|
288
|
+
password2
|
|
289
|
+
);
|
|
290
|
+
nextConfig.secrets = { secureStorePath };
|
|
291
|
+
writeConfigFile(configPath, nextConfig);
|
|
292
|
+
printDone(`Encrypted store: ${pc.dim(secureStorePath)}`);
|
|
293
|
+
clack.outro(pc.green("Setup complete"));
|
|
294
|
+
printSuccess("Ready", [
|
|
295
|
+
`${pc.cyan("npx exagent run")} Start trading`,
|
|
296
|
+
`${pc.cyan("npx exagent status")} Check connection`,
|
|
297
|
+
"",
|
|
298
|
+
`${pc.dim("Dashboard:")} ${pc.cyan("https://exagent.io")}`
|
|
299
|
+
]);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/cli.ts
|
|
11
303
|
var program = new Command();
|
|
12
|
-
program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.
|
|
304
|
+
program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.3.0");
|
|
13
305
|
program.command("init").description("Create a sample agent configuration file").option("--agent-id <id>", "Agent ID (from dashboard)", "my-agent").option("--api-url <url>", "API server URL", "http://localhost:3002").option("--config <path>", "Config file path", "agent-config.json").action((opts) => {
|
|
306
|
+
printBanner();
|
|
14
307
|
writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
|
|
15
|
-
|
|
16
|
-
console.log(
|
|
17
|
-
console.log(
|
|
308
|
+
printDone(`Created ${pc.cyan(opts.config)}`);
|
|
309
|
+
console.log();
|
|
310
|
+
console.log(` Edit only the public strategy/venue/risk settings.`);
|
|
311
|
+
console.log(` Then run: ${pc.cyan(`exagent setup --config ${opts.config}`)}`);
|
|
312
|
+
console.log();
|
|
313
|
+
});
|
|
314
|
+
program.command("setup").description("Run first-time secure local setup for agent secrets").option("--config <path>", "Config file path", "agent-config.json").action(async (opts) => {
|
|
315
|
+
try {
|
|
316
|
+
await ensureLocalSetup(opts.config);
|
|
317
|
+
} catch (err) {
|
|
318
|
+
printError(err.message);
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
18
321
|
});
|
|
19
322
|
program.command("run").description("Start the agent").option("--config <path>", "Config file path", "agent-config.json").action(async (opts) => {
|
|
20
323
|
try {
|
|
21
|
-
|
|
324
|
+
await ensureLocalSetup(opts.config);
|
|
325
|
+
printBanner();
|
|
326
|
+
const config = await loadConfig(opts.config, {
|
|
327
|
+
getSecretPassword: async () => promptSecretPassword()
|
|
328
|
+
});
|
|
329
|
+
printDone(`Agent ${pc.cyan(config.agentId)} starting...`);
|
|
330
|
+
console.log();
|
|
22
331
|
const runtime = new AgentRuntime(config);
|
|
23
332
|
await runtime.start();
|
|
24
333
|
await new Promise(() => {
|
|
25
334
|
});
|
|
26
335
|
} catch (err) {
|
|
27
|
-
|
|
336
|
+
printError(err.message);
|
|
28
337
|
process.exit(1);
|
|
29
338
|
}
|
|
30
339
|
});
|
|
31
340
|
program.command("templates").description("List available strategy templates").action(() => {
|
|
341
|
+
printBanner();
|
|
32
342
|
const templates = listTemplates();
|
|
33
|
-
console.log("
|
|
343
|
+
console.log(pc.bold(" Strategy Templates"));
|
|
344
|
+
console.log();
|
|
34
345
|
for (const t of templates) {
|
|
35
|
-
console.log(` ${t.id}`);
|
|
36
|
-
console.log(`
|
|
37
|
-
console.log(`
|
|
346
|
+
console.log(` ${pc.cyan(t.id)}`);
|
|
347
|
+
console.log(` ${t.name} \u2014 ${pc.dim(t.description)}`);
|
|
348
|
+
console.log(` ${pc.dim(`Risk: ${t.riskLevel} | Venues: ${t.venues.join(", ") || "any"}`)}`);
|
|
38
349
|
console.log();
|
|
39
350
|
}
|
|
40
351
|
});
|
|
41
352
|
program.command("status").description("Check agent status").option("--config <path>", "Config file path", "agent-config.json").action(async (opts) => {
|
|
42
353
|
try {
|
|
43
|
-
|
|
354
|
+
await ensureLocalSetup(opts.config);
|
|
355
|
+
printBanner();
|
|
356
|
+
const config = await loadConfig(opts.config, {
|
|
357
|
+
getSecretPassword: async () => promptSecretPassword()
|
|
358
|
+
});
|
|
44
359
|
const res = await fetch(`${config.apiUrl}/v1/agents/${config.agentId}`, {
|
|
45
360
|
headers: { Authorization: `Bearer ${config.apiToken}` }
|
|
46
361
|
});
|
|
47
362
|
if (!res.ok) {
|
|
48
|
-
|
|
363
|
+
printError(`API error: ${res.status}`);
|
|
49
364
|
process.exit(1);
|
|
50
365
|
}
|
|
51
366
|
const agent = await res.json();
|
|
52
|
-
|
|
367
|
+
const name = agent.name || config.agentId;
|
|
368
|
+
const status = agent.status || "unknown";
|
|
369
|
+
const statusColor = status === "online" ? pc.green : status === "error" ? pc.red : pc.yellow;
|
|
370
|
+
printSuccess(name, [
|
|
371
|
+
`${pc.dim("Status:")} ${statusColor(status)}`,
|
|
372
|
+
`${pc.dim("Agent:")} ${pc.cyan(config.agentId)}`,
|
|
373
|
+
`${pc.dim("API:")} ${pc.dim(config.apiUrl)}`
|
|
374
|
+
]);
|
|
53
375
|
} catch (err) {
|
|
54
|
-
|
|
376
|
+
printError(err.message);
|
|
55
377
|
process.exit(1);
|
|
56
378
|
}
|
|
57
379
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,14 @@ interface RuntimeConfig {
|
|
|
19
19
|
};
|
|
20
20
|
strategy: {
|
|
21
21
|
file?: string;
|
|
22
|
+
code?: string;
|
|
22
23
|
template?: string;
|
|
24
|
+
venues?: string[];
|
|
25
|
+
prompt?: {
|
|
26
|
+
name?: string;
|
|
27
|
+
systemPrompt: string;
|
|
28
|
+
venues?: string[];
|
|
29
|
+
};
|
|
23
30
|
};
|
|
24
31
|
trading: {
|
|
25
32
|
mode: 'live' | 'paper';
|
|
@@ -78,7 +85,10 @@ interface RuntimeConfig {
|
|
|
78
85
|
json?: boolean;
|
|
79
86
|
};
|
|
80
87
|
}
|
|
81
|
-
|
|
88
|
+
interface LoadConfigOptions {
|
|
89
|
+
getSecretPassword?: () => Promise<string>;
|
|
90
|
+
}
|
|
91
|
+
declare function loadConfig(path?: string, options?: LoadConfigOptions): Promise<RuntimeConfig>;
|
|
82
92
|
declare function generateSampleConfig(agentId: string, apiUrl: string): string;
|
|
83
93
|
declare function writeSampleConfig(agentId: string, apiUrl: string, path?: string): void;
|
|
84
94
|
|
|
@@ -116,6 +126,18 @@ declare class AgentRuntime {
|
|
|
116
126
|
constructor(config: RuntimeConfig);
|
|
117
127
|
start(): Promise<void>;
|
|
118
128
|
stop(): Promise<void>;
|
|
129
|
+
private configureLLMAdapter;
|
|
130
|
+
private teardownVenues;
|
|
131
|
+
private waitForCycleCompletion;
|
|
132
|
+
private getConfiguredSpotVenue;
|
|
133
|
+
private normalizeVenueForExecution;
|
|
134
|
+
private isVenueConfigured;
|
|
135
|
+
private canExecuteVenue;
|
|
136
|
+
private getPreferredExecutionVenues;
|
|
137
|
+
private buildRuntimeVenuesFromSelection;
|
|
138
|
+
private buildStrategyFallbackPrompt;
|
|
139
|
+
private extractStrategyConfigFromAgentConfig;
|
|
140
|
+
private applyExecutionMode;
|
|
119
141
|
private initializeVenues;
|
|
120
142
|
private startTrading;
|
|
121
143
|
private stopTrading;
|
|
@@ -407,7 +429,13 @@ declare function createLLMAdapter(config: LLMConfig): LLMAdapter;
|
|
|
407
429
|
|
|
408
430
|
declare function loadStrategy(config: {
|
|
409
431
|
file?: string;
|
|
432
|
+
code?: string;
|
|
410
433
|
template?: string;
|
|
434
|
+
prompt?: {
|
|
435
|
+
name?: string;
|
|
436
|
+
systemPrompt: string;
|
|
437
|
+
venues?: string[];
|
|
438
|
+
};
|
|
411
439
|
}): Promise<StrategyFunction>;
|
|
412
440
|
|
|
413
441
|
declare function getTemplate(id: string): StrategyTemplate | undefined;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exagent/agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,24 +13,31 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.ts src/cli.ts --format esm --dts",
|
|
18
|
+
"dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch"
|
|
19
|
+
},
|
|
16
20
|
"dependencies": {
|
|
21
|
+
"@clack/prompts": "^1.1.0",
|
|
22
|
+
"@exagent/sdk": "workspace:*",
|
|
17
23
|
"@polymarket/clob-client": "^4.0.0",
|
|
24
|
+
"boxen": "^8.0.1",
|
|
18
25
|
"commander": "^12.0.0",
|
|
19
26
|
"ethers": "^5.7.2",
|
|
27
|
+
"figlet": "^1.10.0",
|
|
28
|
+
"gradient-string": "^3.0.0",
|
|
29
|
+
"picocolors": "^1.1.1",
|
|
20
30
|
"viem": "^2.21.0",
|
|
21
31
|
"ws": "^8.16.0",
|
|
22
|
-
"zod": "^3.22.0"
|
|
23
|
-
"@exagent/sdk": "0.2.1"
|
|
32
|
+
"zod": "^3.22.0"
|
|
24
33
|
},
|
|
25
34
|
"devDependencies": {
|
|
35
|
+
"@types/figlet": "^1.7.0",
|
|
36
|
+
"@types/gradient-string": "^1.1.6",
|
|
26
37
|
"@types/node": "^20.0.0",
|
|
27
38
|
"@types/ws": "^8.5.0",
|
|
28
39
|
"tsup": "^8.0.0",
|
|
29
40
|
"tsx": "^4.0.0",
|
|
30
41
|
"typescript": "^5.6.0"
|
|
31
|
-
},
|
|
32
|
-
"scripts": {
|
|
33
|
-
"build": "tsup src/index.ts src/cli.ts --format esm --dts",
|
|
34
|
-
"dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch"
|
|
35
42
|
}
|
|
36
|
-
}
|
|
43
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -3,13 +3,15 @@ import { Command } from 'commander';
|
|
|
3
3
|
import { loadConfig, writeSampleConfig } from './config.js';
|
|
4
4
|
import { AgentRuntime } from './runtime.js';
|
|
5
5
|
import { listTemplates } from './strategy/index.js';
|
|
6
|
+
import { ensureLocalSetup, promptSecretPassword } from './setup.js';
|
|
7
|
+
import { printBanner, printDone, printError, printSuccess, pc } from './ui.js';
|
|
6
8
|
|
|
7
9
|
const program = new Command();
|
|
8
10
|
|
|
9
11
|
program
|
|
10
12
|
.name('exagent')
|
|
11
13
|
.description('Exagent — LLM trading agent runtime')
|
|
12
|
-
.version('0.
|
|
14
|
+
.version('0.3.0');
|
|
13
15
|
|
|
14
16
|
program
|
|
15
17
|
.command('init')
|
|
@@ -18,10 +20,26 @@ program
|
|
|
18
20
|
.option('--api-url <url>', 'API server URL', 'http://localhost:3002')
|
|
19
21
|
.option('--config <path>', 'Config file path', 'agent-config.json')
|
|
20
22
|
.action((opts) => {
|
|
23
|
+
printBanner();
|
|
21
24
|
writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
|
|
22
|
-
|
|
23
|
-
console.log(
|
|
24
|
-
console.log(
|
|
25
|
+
printDone(`Created ${pc.cyan(opts.config)}`);
|
|
26
|
+
console.log();
|
|
27
|
+
console.log(` Edit only the public strategy/venue/risk settings.`);
|
|
28
|
+
console.log(` Then run: ${pc.cyan(`exagent setup --config ${opts.config}`)}`);
|
|
29
|
+
console.log();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command('setup')
|
|
34
|
+
.description('Run first-time secure local setup for agent secrets')
|
|
35
|
+
.option('--config <path>', 'Config file path', 'agent-config.json')
|
|
36
|
+
.action(async (opts) => {
|
|
37
|
+
try {
|
|
38
|
+
await ensureLocalSetup(opts.config);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
printError((err as Error).message);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
25
43
|
});
|
|
26
44
|
|
|
27
45
|
program
|
|
@@ -30,14 +48,20 @@ program
|
|
|
30
48
|
.option('--config <path>', 'Config file path', 'agent-config.json')
|
|
31
49
|
.action(async (opts) => {
|
|
32
50
|
try {
|
|
33
|
-
|
|
51
|
+
await ensureLocalSetup(opts.config);
|
|
52
|
+
printBanner();
|
|
53
|
+
const config = await loadConfig(opts.config, {
|
|
54
|
+
getSecretPassword: async () => promptSecretPassword(),
|
|
55
|
+
});
|
|
56
|
+
printDone(`Agent ${pc.cyan(config.agentId)} starting...`);
|
|
57
|
+
console.log();
|
|
34
58
|
const runtime = new AgentRuntime(config);
|
|
35
59
|
await runtime.start();
|
|
36
60
|
|
|
37
61
|
// Keep the process running
|
|
38
62
|
await new Promise(() => {});
|
|
39
63
|
} catch (err) {
|
|
40
|
-
|
|
64
|
+
printError((err as Error).message);
|
|
41
65
|
process.exit(1);
|
|
42
66
|
}
|
|
43
67
|
});
|
|
@@ -46,12 +70,14 @@ program
|
|
|
46
70
|
.command('templates')
|
|
47
71
|
.description('List available strategy templates')
|
|
48
72
|
.action(() => {
|
|
73
|
+
printBanner();
|
|
49
74
|
const templates = listTemplates();
|
|
50
|
-
console.log('
|
|
75
|
+
console.log(pc.bold(' Strategy Templates'));
|
|
76
|
+
console.log();
|
|
51
77
|
for (const t of templates) {
|
|
52
|
-
console.log(` ${t.id}`);
|
|
53
|
-
console.log(`
|
|
54
|
-
console.log(`
|
|
78
|
+
console.log(` ${pc.cyan(t.id)}`);
|
|
79
|
+
console.log(` ${t.name} — ${pc.dim(t.description)}`);
|
|
80
|
+
console.log(` ${pc.dim(`Risk: ${t.riskLevel} | Venues: ${t.venues.join(', ') || 'any'}`)}`);
|
|
55
81
|
console.log();
|
|
56
82
|
}
|
|
57
83
|
});
|
|
@@ -62,20 +88,33 @@ program
|
|
|
62
88
|
.option('--config <path>', 'Config file path', 'agent-config.json')
|
|
63
89
|
.action(async (opts) => {
|
|
64
90
|
try {
|
|
65
|
-
|
|
91
|
+
await ensureLocalSetup(opts.config);
|
|
92
|
+
printBanner();
|
|
93
|
+
const config = await loadConfig(opts.config, {
|
|
94
|
+
getSecretPassword: async () => promptSecretPassword(),
|
|
95
|
+
});
|
|
66
96
|
const res = await fetch(`${config.apiUrl}/v1/agents/${config.agentId}`, {
|
|
67
97
|
headers: { Authorization: `Bearer ${config.apiToken}` },
|
|
68
98
|
});
|
|
69
99
|
|
|
70
100
|
if (!res.ok) {
|
|
71
|
-
|
|
101
|
+
printError(`API error: ${res.status}`);
|
|
72
102
|
process.exit(1);
|
|
73
103
|
}
|
|
74
104
|
|
|
75
|
-
const agent = await res.json()
|
|
76
|
-
|
|
105
|
+
const agent = await res.json() as Record<string, unknown>;
|
|
106
|
+
const name = (agent.name as string) || config.agentId;
|
|
107
|
+
const status = (agent.status as string) || 'unknown';
|
|
108
|
+
|
|
109
|
+
const statusColor = status === 'online' ? pc.green : status === 'error' ? pc.red : pc.yellow;
|
|
110
|
+
|
|
111
|
+
printSuccess(name, [
|
|
112
|
+
`${pc.dim('Status:')} ${statusColor(status)}`,
|
|
113
|
+
`${pc.dim('Agent:')} ${pc.cyan(config.agentId)}`,
|
|
114
|
+
`${pc.dim('API:')} ${pc.dim(config.apiUrl)}`,
|
|
115
|
+
]);
|
|
77
116
|
} catch (err) {
|
|
78
|
-
|
|
117
|
+
printError((err as Error).message);
|
|
79
118
|
process.exit(1);
|
|
80
119
|
}
|
|
81
120
|
});
|