@exagent/agent 0.3.2 → 0.3.4
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/{chunk-VDK4XPAC.js → chunk-WTECTX2Z.js} +31 -4
- package/dist/cli.js +212 -77
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +122 -3
- package/src/config.ts +19 -2
- package/src/runtime.ts +18 -2
- package/src/setup.ts +118 -67
- package/dist/chunk-25J5ZDKX.js +0 -5622
- package/dist/chunk-2ML4XS5X.js +0 -5626
- package/dist/chunk-2OKYNZ3J.js +0 -5622
- package/dist/chunk-2PAASZJN.js +0 -5132
- package/dist/chunk-2WAYVOLU.js +0 -5624
- package/dist/chunk-2XBVVY3I.js +0 -5621
- package/dist/chunk-37HCPVBZ.js +0 -2941
- package/dist/chunk-3DZAZBLF.js +0 -5652
- package/dist/chunk-3IXCKNSV.js +0 -5008
- package/dist/chunk-3MXFTRXB.js +0 -3326
- package/dist/chunk-4MURFLNJ.js +0 -5136
- package/dist/chunk-4UVMO6ZM.js +0 -6318
- package/dist/chunk-56YQROFU.js +0 -5639
- package/dist/chunk-5B3ZEGMD.js +0 -4330
- package/dist/chunk-5NU6FDDE.js +0 -6020
- package/dist/chunk-5WADKPJU.js +0 -1552
- package/dist/chunk-6LOIFEEV.js +0 -4895
- package/dist/chunk-6YIXPNL4.js +0 -5626
- package/dist/chunk-72HK2V74.js +0 -5224
- package/dist/chunk-7Q72QQIV.js +0 -4697
- package/dist/chunk-7V3XTBIF.js +0 -4896
- package/dist/chunk-A4CM4F7Q.js +0 -5633
- package/dist/chunk-ADXXR2MA.js +0 -4816
- package/dist/chunk-AG6CJPIC.js +0 -4895
- package/dist/chunk-AJPXHBTF.js +0 -4459
- package/dist/chunk-AQ6R37XV.js +0 -4809
- package/dist/chunk-AS4UEZMU.js +0 -5001
- package/dist/chunk-ASYMD22Y.js +0 -5624
- package/dist/chunk-AU4MCQCE.js +0 -5624
- package/dist/chunk-B4VHIITU.js +0 -5748
- package/dist/chunk-B6GVDNKQ.js +0 -5624
- package/dist/chunk-BCIAW6ZL.js +0 -5132
- package/dist/chunk-BJZ5PCG3.js +0 -5358
- package/dist/chunk-BS4J5QSM.js +0 -5622
- package/dist/chunk-BV2AUUX6.js +0 -2940
- package/dist/chunk-BWNSH2LK.js +0 -1574
- package/dist/chunk-C3GMBW3R.js +0 -5640
- package/dist/chunk-C6CVQGJ4.js +0 -5624
- package/dist/chunk-CGXKXNUJ.js +0 -5626
- package/dist/chunk-CIEZAYOU.js +0 -4701
- package/dist/chunk-CORZCEAQ.js +0 -5621
- package/dist/chunk-CS5LGZWP.js +0 -5357
- package/dist/chunk-CVT3KC24.js +0 -5624
- package/dist/chunk-D5MJ45R7.js +0 -3258
- package/dist/chunk-DSBRZ5DZ.js +0 -5624
- package/dist/chunk-E2X7JARQ.js +0 -4437
- package/dist/chunk-E3GY36ZP.js +0 -3258
- package/dist/chunk-E6NCIFKB.js +0 -4733
- package/dist/chunk-EOXLKW4D.js +0 -4895
- package/dist/chunk-FCI7LX4Q.js +0 -5624
- package/dist/chunk-FFJSKTOL.js +0 -4539
- package/dist/chunk-FOQYP3IB.js +0 -2950
- package/dist/chunk-GNEYTZDH.js +0 -4686
- package/dist/chunk-GPMXUMYH.js +0 -5991
- package/dist/chunk-GZWPAQPU.js +0 -4593
- package/dist/chunk-H5DXDKMX.js +0 -5619
- package/dist/chunk-HFQRTMS6.js +0 -3377
- package/dist/chunk-HQKRHX6Y.js +0 -5626
- package/dist/chunk-HTF3TNBY.js +0 -4834
- package/dist/chunk-IADSQBBY.js +0 -5523
- package/dist/chunk-IE2SXMZK.js +0 -4890
- package/dist/chunk-IGUQVJCB.js +0 -5622
- package/dist/chunk-IIREL7SL.js +0 -5615
- package/dist/chunk-IJK4EFTJ.js +0 -6043
- package/dist/chunk-J2MQ3Y5O.js +0 -5223
- package/dist/chunk-J3NG7AGT.js +0 -6047
- package/dist/chunk-JIBBZ3NV.js +0 -5132
- package/dist/chunk-JIPSBE6S.js +0 -5622
- package/dist/chunk-JPG755XK.js +0 -4589
- package/dist/chunk-JQBNL5GX.js +0 -5230
- package/dist/chunk-KS3F5WSX.js +0 -4831
- package/dist/chunk-KUYTQ4FR.js +0 -4808
- package/dist/chunk-KVP4CMJ5.js +0 -4711
- package/dist/chunk-LAR2I44B.js +0 -5626
- package/dist/chunk-LBTHSED2.js +0 -1531
- package/dist/chunk-M6OAMYVM.js +0 -5621
- package/dist/chunk-MFN5WWOY.js +0 -5132
- package/dist/chunk-MMTSKXLK.js +0 -5624
- package/dist/chunk-MPUSQLTH.js +0 -5626
- package/dist/chunk-MREXDTWL.js +0 -1555
- package/dist/chunk-MUEDKRFC.js +0 -5624
- package/dist/chunk-NOVPL2JH.js +0 -3327
- package/dist/chunk-NQIP4MHV.js +0 -4334
- package/dist/chunk-NXXKMYLS.js +0 -5624
- package/dist/chunk-OBYNZXNM.js +0 -4756
- package/dist/chunk-OFY4HBOJ.js +0 -5624
- package/dist/chunk-OJNUEZEK.js +0 -5602
- package/dist/chunk-OQCJOMUQ.js +0 -5624
- package/dist/chunk-OZH75GY6.js +0 -5132
- package/dist/chunk-P3IJVDMZ.js +0 -4700
- package/dist/chunk-PMYMYMBH.js +0 -4877
- package/dist/chunk-PRELNRVN.js +0 -5623
- package/dist/chunk-PSQUSNSI.js +0 -4703
- package/dist/chunk-QAIQ5IB6.js +0 -5624
- package/dist/chunk-QG22GADV.js +0 -6316
- package/dist/chunk-QNE2KGGK.js +0 -3315
- package/dist/chunk-RH7ZBSG4.js +0 -5132
- package/dist/chunk-RLD5MUCR.js +0 -5626
- package/dist/chunk-S42VEBNR.js +0 -3268
- package/dist/chunk-SEM6UXU4.js +0 -3324
- package/dist/chunk-SI5WP77M.js +0 -4430
- package/dist/chunk-SID4SQSY.js +0 -4837
- package/dist/chunk-SIELPKWF.js +0 -1558
- package/dist/chunk-SVBLY6QT.js +0 -5742
- package/dist/chunk-SVFTC5V2.js +0 -6021
- package/dist/chunk-SXHTX62B.js +0 -4823
- package/dist/chunk-T2YCEA5U.js +0 -4730
- package/dist/chunk-TARCHIOU.js +0 -4718
- package/dist/chunk-TDACLKD7.js +0 -5867
- package/dist/chunk-TGCBM3NP.js +0 -4890
- package/dist/chunk-TIWG6KAK.js +0 -4769
- package/dist/chunk-TKLKATVM.js +0 -1534
- package/dist/chunk-TSLZ4A5P.js +0 -5222
- package/dist/chunk-TWSDKORW.js +0 -4698
- package/dist/chunk-U5QHYVMJ.js +0 -3341
- package/dist/chunk-UAP5CTHB.js +0 -5985
- package/dist/chunk-UK6SEUWU.js +0 -3210
- package/dist/chunk-UKU5YO65.js +0 -5132
- package/dist/chunk-UOZQXP4Q.js +0 -5144
- package/dist/chunk-UPTN2TSS.js +0 -4727
- package/dist/chunk-UQT2APOE.js +0 -2944
- package/dist/chunk-V32QDZKW.js +0 -5132
- package/dist/chunk-VKY2CDCD.js +0 -5622
- package/dist/chunk-VUCSYMCY.js +0 -3323
- package/dist/chunk-VVLNBD5Y.js +0 -5132
- package/dist/chunk-W3TQ22O6.js +0 -4459
- package/dist/chunk-WA4DSGOM.js +0 -3355
- package/dist/chunk-WI6MIICK.js +0 -4687
- package/dist/chunk-XRHJLL74.js +0 -4893
- package/dist/chunk-XXWXEBJQ.js +0 -4885
- package/dist/chunk-YC6TH2H3.js +0 -5624
- package/dist/chunk-YDH6HCUJ.js +0 -5624
- package/dist/chunk-YJD35VKQ.js +0 -4890
- package/dist/chunk-ZBIQJBY7.js +0 -5620
- package/dist/chunk-ZKTSA2AE.js +0 -5629
- package/dist/chunk-ZKZZL3PE.js +0 -3379
- package/dist/chunk-ZM5KCPRK.js +0 -4541
- package/dist/chunk-ZTYPDSE3.js +0 -3258
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
2
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { chmodSync, existsSync, readFileSync, writeFileSync } from "fs";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { resolve } from "path";
|
|
6
6
|
import { z } from "zod";
|
|
@@ -141,7 +141,7 @@ var secureStoreSchema = z.object({
|
|
|
141
141
|
ciphertext: z.string(),
|
|
142
142
|
authTag: z.string()
|
|
143
143
|
});
|
|
144
|
-
var DEFAULT_SCRYPT_COST =
|
|
144
|
+
var DEFAULT_SCRYPT_COST = 16384;
|
|
145
145
|
var DEFAULT_SCRYPT_BLOCK_SIZE = 8;
|
|
146
146
|
var DEFAULT_SCRYPT_PARALLELIZATION = 1;
|
|
147
147
|
function expandHomeDir(path) {
|
|
@@ -276,6 +276,17 @@ ${issues}`);
|
|
|
276
276
|
}
|
|
277
277
|
return result.data;
|
|
278
278
|
}
|
|
279
|
+
function updateSecureStore(path, password, updates) {
|
|
280
|
+
const secrets = decryptSecretPayload(path, password);
|
|
281
|
+
const updated = { ...secrets, ...updates };
|
|
282
|
+
const encrypted = encryptSecretPayload(updated, password);
|
|
283
|
+
const secureStorePath = expandHomeDir(path);
|
|
284
|
+
writeFileSync(secureStorePath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
285
|
+
try {
|
|
286
|
+
chmodSync(secureStorePath, 384);
|
|
287
|
+
} catch {
|
|
288
|
+
}
|
|
289
|
+
}
|
|
279
290
|
function generateSampleConfig(agentId, apiUrl) {
|
|
280
291
|
const config = {
|
|
281
292
|
agentId,
|
|
@@ -4868,6 +4879,20 @@ try {
|
|
|
4868
4879
|
} catch {
|
|
4869
4880
|
}
|
|
4870
4881
|
var MAX_CONSECUTIVE_FAILURES = 3;
|
|
4882
|
+
function getCycleErrorHint(category, msg) {
|
|
4883
|
+
const lower = msg.toLowerCase();
|
|
4884
|
+
if (category === "auth" || lower.includes("401") || lower.includes("unauthorized") || lower.includes("invalid api key") || lower.includes("authentication"))
|
|
4885
|
+
return "Check your LLM API key \u2014 update via npx exagent setup";
|
|
4886
|
+
if (category === "network" || lower.includes("econnrefused") || lower.includes("enotfound") || lower.includes("fetch failed") || lower.includes("timeout"))
|
|
4887
|
+
return "Network connectivity issue \u2014 check your internet connection";
|
|
4888
|
+
if (category === "venue" || lower.includes("hyperliquid") || lower.includes("polymarket") || lower.includes("venue"))
|
|
4889
|
+
return "Venue API error \u2014 the venue may be experiencing issues";
|
|
4890
|
+
if (category === "strategy" || lower.includes("invalid") && lower.includes("signal") || lower.includes("strategy"))
|
|
4891
|
+
return "Strategy returned invalid output \u2014 check your strategy file";
|
|
4892
|
+
if (lower.includes("rate limit") || lower.includes("429"))
|
|
4893
|
+
return "Rate limited \u2014 consider increasing tradingIntervalMs";
|
|
4894
|
+
return null;
|
|
4895
|
+
}
|
|
4871
4896
|
var AgentRuntime = class _AgentRuntime {
|
|
4872
4897
|
config;
|
|
4873
4898
|
relay;
|
|
@@ -5477,12 +5502,13 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5477
5502
|
const errMsg = err.message;
|
|
5478
5503
|
timings.totalMs = Date.now() - cycleStart;
|
|
5479
5504
|
const categorized = this.diagnostics.recordError(err);
|
|
5480
|
-
|
|
5505
|
+
const hint = getCycleErrorHint(categorized.category, errMsg);
|
|
5506
|
+
log.error("cycle", `Cycle error: ${errMsg}${hint ? ` \u2014 ${hint}` : ""}`, {
|
|
5481
5507
|
cycle: this.cycleCount,
|
|
5482
5508
|
totalMs: timings.totalMs,
|
|
5483
5509
|
category: categorized.category
|
|
5484
5510
|
});
|
|
5485
|
-
this.signal.reportError("Cycle Error", errMsg);
|
|
5511
|
+
this.signal.reportError("Cycle Error", `${errMsg}${hint ? ` \u2014 ${hint}` : ""}`);
|
|
5486
5512
|
this.diagnostics.recordCycle({
|
|
5487
5513
|
cycleNumber: this.cycleCount,
|
|
5488
5514
|
startedAt: cycleStart,
|
|
@@ -6272,6 +6298,7 @@ export {
|
|
|
6272
6298
|
writeConfigFile,
|
|
6273
6299
|
encryptSecretPayload,
|
|
6274
6300
|
loadConfig,
|
|
6301
|
+
updateSecureStore,
|
|
6275
6302
|
generateSampleConfig,
|
|
6276
6303
|
writeSampleConfig,
|
|
6277
6304
|
CHAIN_CONFIGS,
|
package/dist/cli.js
CHANGED
|
@@ -6,9 +6,10 @@ import {
|
|
|
6
6
|
listTemplates,
|
|
7
7
|
loadConfig,
|
|
8
8
|
readConfigFile,
|
|
9
|
+
updateSecureStore,
|
|
9
10
|
writeConfigFile,
|
|
10
11
|
writeSampleConfig
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WTECTX2Z.js";
|
|
12
13
|
|
|
13
14
|
// src/cli.ts
|
|
14
15
|
import { Command } from "commander";
|
|
@@ -87,6 +88,21 @@ function cancelled() {
|
|
|
87
88
|
clack.cancel("Setup cancelled.");
|
|
88
89
|
process.exit(0);
|
|
89
90
|
}
|
|
91
|
+
function isNonInteractive() {
|
|
92
|
+
return process.env.EXAGENT_NONINTERACTIVE === "1" || process.env.EXAGENT_NONINTERACTIVE === "true";
|
|
93
|
+
}
|
|
94
|
+
var LLM_KEY_PREFIXES = {
|
|
95
|
+
openai: "sk-",
|
|
96
|
+
anthropic: "sk-ant-"
|
|
97
|
+
};
|
|
98
|
+
function validateLlmKeyFormat(provider, key) {
|
|
99
|
+
if (!key.trim()) return "API key is required.";
|
|
100
|
+
const expectedPrefix = LLM_KEY_PREFIXES[provider];
|
|
101
|
+
if (expectedPrefix && !key.startsWith(expectedPrefix)) {
|
|
102
|
+
return `${provider} API keys typically start with "${expectedPrefix}". Double-check your key.`;
|
|
103
|
+
}
|
|
104
|
+
if (key.length < 10) return "API key seems too short.";
|
|
105
|
+
}
|
|
90
106
|
async function consumeBootstrapPackage(config) {
|
|
91
107
|
if (!config.secrets?.bootstrapToken) {
|
|
92
108
|
return { apiToken: "" };
|
|
@@ -114,6 +130,22 @@ async function setupWallet(config) {
|
|
|
114
130
|
printDone(`Using existing wallet: ${pc.dim(account.address)}`);
|
|
115
131
|
return config.wallet.privateKey;
|
|
116
132
|
}
|
|
133
|
+
if (isNonInteractive()) {
|
|
134
|
+
const mode = process.env.EXAGENT_WALLET_MODE || "generate";
|
|
135
|
+
if (mode === "import") {
|
|
136
|
+
const key = process.env.EXAGENT_WALLET_KEY;
|
|
137
|
+
if (!key || !/^0x[a-fA-F0-9]{64}$/.test(key)) {
|
|
138
|
+
throw new Error("EXAGENT_WALLET_KEY must be a valid 0x-prefixed 64-char hex private key in non-interactive mode");
|
|
139
|
+
}
|
|
140
|
+
const address3 = privateKeyToAccount(key).address;
|
|
141
|
+
printDone(`Wallet imported: ${pc.dim(address3)}`);
|
|
142
|
+
return key;
|
|
143
|
+
}
|
|
144
|
+
const privateKey2 = generatePrivateKey();
|
|
145
|
+
const address2 = privateKeyToAccount(privateKey2).address;
|
|
146
|
+
printDone(`Wallet created: ${pc.dim(address2)}`);
|
|
147
|
+
return privateKey2;
|
|
148
|
+
}
|
|
117
149
|
const method = await clack.select({
|
|
118
150
|
message: "How would you like to set up your wallet?",
|
|
119
151
|
options: [
|
|
@@ -142,82 +174,75 @@ async function setupWallet(config) {
|
|
|
142
174
|
return privateKey;
|
|
143
175
|
}
|
|
144
176
|
var LLM_PROVIDERS = ["openai", "anthropic", "google", "deepseek", "mistral", "groq", "together", "ollama"];
|
|
145
|
-
async function setupLlm(config
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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;
|
|
177
|
+
async function setupLlm(config) {
|
|
178
|
+
if (isNonInteractive()) {
|
|
179
|
+
const provider2 = process.env.EXAGENT_LLM_PROVIDER || config.llm?.provider;
|
|
180
|
+
const model2 = process.env.EXAGENT_LLM_MODEL || config.llm?.model;
|
|
181
|
+
const apiKey2 = process.env.EXAGENT_LLM_KEY;
|
|
182
|
+
if (!provider2) throw new Error("EXAGENT_LLM_PROVIDER required in non-interactive mode");
|
|
183
|
+
if (!model2) throw new Error("EXAGENT_LLM_MODEL required in non-interactive mode");
|
|
184
|
+
if (!apiKey2) throw new Error("EXAGENT_LLM_KEY required in non-interactive mode");
|
|
185
|
+
printDone("LLM configured");
|
|
186
|
+
return { provider: provider2, model: model2, apiKey: apiKey2 };
|
|
170
187
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
188
|
+
const defaultProvider = config.llm?.provider;
|
|
189
|
+
const providerOptions = LLM_PROVIDERS.map((p) => ({ value: p, label: p }));
|
|
190
|
+
const selected = await clack.select({
|
|
191
|
+
message: "LLM provider:",
|
|
192
|
+
options: providerOptions,
|
|
193
|
+
initialValue: defaultProvider || void 0
|
|
194
|
+
});
|
|
195
|
+
if (clack.isCancel(selected)) cancelled();
|
|
196
|
+
const provider = selected;
|
|
197
|
+
const defaultModel = config.llm?.model;
|
|
198
|
+
const entered = await clack.text({
|
|
199
|
+
message: "LLM model:",
|
|
200
|
+
placeholder: defaultModel || "gpt-4o",
|
|
201
|
+
defaultValue: defaultModel || void 0,
|
|
202
|
+
validate: (val) => {
|
|
203
|
+
if (!val.trim()) return "Model name is required.";
|
|
180
204
|
}
|
|
181
|
-
}
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (!val.trim()) return "API key is required.";
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
if (clack.isCancel(entered)) cancelled();
|
|
193
|
-
apiKey = entered;
|
|
194
|
-
}
|
|
205
|
+
});
|
|
206
|
+
if (clack.isCancel(entered)) cancelled();
|
|
207
|
+
const model = entered;
|
|
208
|
+
const apiKey = await clack.password({
|
|
209
|
+
message: "LLM API key:",
|
|
210
|
+
validate: (val) => validateLlmKeyFormat(provider, val)
|
|
211
|
+
});
|
|
212
|
+
if (clack.isCancel(apiKey)) cancelled();
|
|
195
213
|
printDone("LLM configured");
|
|
196
214
|
return { provider, model, apiKey };
|
|
197
215
|
}
|
|
198
216
|
async function setupEncryption() {
|
|
217
|
+
if (isNonInteractive()) {
|
|
218
|
+
const password4 = process.env.EXAGENT_PASSWORD;
|
|
219
|
+
if (!password4 || password4.length < 12) {
|
|
220
|
+
throw new Error("EXAGENT_PASSWORD must be at least 12 characters in non-interactive mode");
|
|
221
|
+
}
|
|
222
|
+
return password4;
|
|
223
|
+
}
|
|
199
224
|
printInfo(`Secrets encrypted with ${pc.cyan("AES-256-GCM")} (${pc.cyan("scrypt")} KDF)`);
|
|
200
225
|
printInfo("The password never leaves this machine.");
|
|
201
226
|
console.log();
|
|
202
|
-
const
|
|
227
|
+
const password3 = await clack.password({
|
|
203
228
|
message: "Choose a device password (12+ characters):",
|
|
204
229
|
validate: (val) => {
|
|
205
230
|
if (val.length < 12) return "Password must be at least 12 characters.";
|
|
206
231
|
}
|
|
207
232
|
});
|
|
208
|
-
if (clack.isCancel(
|
|
209
|
-
const
|
|
233
|
+
if (clack.isCancel(password3)) cancelled();
|
|
234
|
+
const confirm = await clack.password({
|
|
210
235
|
message: "Confirm password:",
|
|
211
236
|
validate: (val) => {
|
|
212
|
-
if (val !==
|
|
237
|
+
if (val !== password3) return "Passwords do not match.";
|
|
213
238
|
}
|
|
214
239
|
});
|
|
215
|
-
if (clack.isCancel(
|
|
216
|
-
return
|
|
240
|
+
if (clack.isCancel(confirm)) cancelled();
|
|
241
|
+
return password3;
|
|
217
242
|
}
|
|
218
|
-
function writeSecureStore(path, secrets,
|
|
243
|
+
function writeSecureStore(path, secrets, password3) {
|
|
219
244
|
const secureStorePath = expandHomeDir(path);
|
|
220
|
-
const encrypted = encryptSecretPayload(secrets,
|
|
245
|
+
const encrypted = encryptSecretPayload(secrets, password3);
|
|
221
246
|
const dir = dirname(secureStorePath);
|
|
222
247
|
if (!existsSync(dir)) {
|
|
223
248
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
@@ -230,14 +255,27 @@ function writeSecureStore(path, secrets, password2) {
|
|
|
230
255
|
return secureStorePath;
|
|
231
256
|
}
|
|
232
257
|
async function promptSecretPassword(question = "Device password:") {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
258
|
+
if (isNonInteractive()) {
|
|
259
|
+
const password4 = process.env.EXAGENT_PASSWORD || process.env.EXAGENT_SECRET_PASSWORD;
|
|
260
|
+
if (!password4) throw new Error("EXAGENT_PASSWORD required in non-interactive mode");
|
|
261
|
+
return password4;
|
|
262
|
+
}
|
|
263
|
+
const password3 = await clack.password({ message: question });
|
|
264
|
+
if (clack.isCancel(password3)) cancelled();
|
|
265
|
+
return password3;
|
|
236
266
|
}
|
|
237
267
|
async function ensureLocalSetup(configPath) {
|
|
238
268
|
const config = readConfigFile(configPath);
|
|
239
269
|
const existingSecureStorePath = config.secrets?.secureStorePath ? expandHomeDir(config.secrets.secureStorePath) : null;
|
|
240
270
|
if (existingSecureStorePath && !config.secrets?.bootstrapToken && existsSync(existingSecureStorePath) && !config.apiToken && !config.wallet?.privateKey && !config.llm.apiKey) {
|
|
271
|
+
printBanner();
|
|
272
|
+
printSuccess("Already set up", [
|
|
273
|
+
`${pc.cyan("npx exagent run")} Start the agent`,
|
|
274
|
+
`${pc.cyan("npx exagent config")} Change LLM API key or model`,
|
|
275
|
+
`${pc.cyan("npx exagent status")} Check agent connection`,
|
|
276
|
+
"",
|
|
277
|
+
`${pc.dim("Dashboard:")} ${pc.cyan("https://exagent.io")}`
|
|
278
|
+
]);
|
|
241
279
|
return;
|
|
242
280
|
}
|
|
243
281
|
printBanner();
|
|
@@ -246,32 +284,35 @@ async function ensureLocalSetup(configPath) {
|
|
|
246
284
|
const bootstrapPayload = await consumeBootstrapPackage(config);
|
|
247
285
|
if (config.secrets?.bootstrapToken) {
|
|
248
286
|
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
287
|
} else {
|
|
253
288
|
printInfo("No bootstrap token \u2014 manual configuration");
|
|
254
289
|
}
|
|
255
290
|
printStep(2, 4, "Wallet setup");
|
|
256
291
|
const walletPrivateKey = await setupWallet(config);
|
|
257
292
|
printStep(3, 4, "LLM configuration");
|
|
258
|
-
const llm = await setupLlm(config
|
|
293
|
+
const llm = await setupLlm(config);
|
|
259
294
|
printStep(4, 4, "Device encryption");
|
|
260
|
-
const
|
|
295
|
+
const password3 = await setupEncryption();
|
|
261
296
|
const secrets = {
|
|
262
297
|
apiToken: bootstrapPayload.apiToken || config.apiToken || "",
|
|
263
298
|
walletPrivateKey,
|
|
264
299
|
llmApiKey: llm.apiKey
|
|
265
300
|
};
|
|
266
301
|
if (!secrets.apiToken) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
302
|
+
if (isNonInteractive()) {
|
|
303
|
+
const token = process.env.EXAGENT_API_TOKEN;
|
|
304
|
+
if (!token) throw new Error("EXAGENT_API_TOKEN required in non-interactive mode (no relay token from bootstrap)");
|
|
305
|
+
secrets.apiToken = token;
|
|
306
|
+
} else {
|
|
307
|
+
const token = await clack.password({
|
|
308
|
+
message: "Agent relay token:",
|
|
309
|
+
validate: (val) => {
|
|
310
|
+
if (!val.trim()) return "Relay token is required.";
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
if (clack.isCancel(token)) cancelled();
|
|
314
|
+
secrets.apiToken = token;
|
|
315
|
+
}
|
|
275
316
|
}
|
|
276
317
|
const nextConfig = structuredClone(config);
|
|
277
318
|
nextConfig.llm = {
|
|
@@ -285,23 +326,25 @@ async function ensureLocalSetup(configPath) {
|
|
|
285
326
|
const secureStorePath = writeSecureStore(
|
|
286
327
|
nextConfig.secrets?.secureStorePath || getDefaultSecureStorePath(nextConfig.agentId),
|
|
287
328
|
secrets,
|
|
288
|
-
|
|
329
|
+
password3
|
|
289
330
|
);
|
|
290
331
|
nextConfig.secrets = { secureStorePath };
|
|
291
332
|
writeConfigFile(configPath, nextConfig);
|
|
292
333
|
printDone(`Encrypted store: ${pc.dim(secureStorePath)}`);
|
|
293
334
|
clack.outro(pc.green("Setup complete"));
|
|
294
335
|
printSuccess("Ready", [
|
|
295
|
-
`${pc.cyan("npx exagent run")}
|
|
296
|
-
`${pc.cyan("npx exagent
|
|
336
|
+
`${pc.cyan("npx exagent run")} Start the agent`,
|
|
337
|
+
`${pc.cyan("npx exagent config")} Change LLM API key or model`,
|
|
338
|
+
`${pc.cyan("npx exagent status")} Check agent connection`,
|
|
297
339
|
"",
|
|
298
340
|
`${pc.dim("Dashboard:")} ${pc.cyan("https://exagent.io")}`
|
|
299
341
|
]);
|
|
300
342
|
}
|
|
301
343
|
|
|
302
344
|
// src/cli.ts
|
|
345
|
+
import * as clack2 from "@clack/prompts";
|
|
303
346
|
var program = new Command();
|
|
304
|
-
program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.3.
|
|
347
|
+
program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.3.4");
|
|
305
348
|
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
349
|
printBanner();
|
|
307
350
|
writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
|
|
@@ -377,4 +420,96 @@ program.command("status").description("Check agent status").option("--config <pa
|
|
|
377
420
|
process.exit(1);
|
|
378
421
|
}
|
|
379
422
|
});
|
|
423
|
+
var LLM_PROVIDERS2 = ["openai", "anthropic", "google", "deepseek", "mistral", "groq", "together", "ollama"];
|
|
424
|
+
program.command("config").description("Change LLM provider, model, or API key").option("--config <path>", "Config file path", "agent-config.json").action(async (opts) => {
|
|
425
|
+
try {
|
|
426
|
+
printBanner();
|
|
427
|
+
const config = readConfigFile(opts.config);
|
|
428
|
+
const secureStorePath = config.secrets?.secureStorePath;
|
|
429
|
+
if (!secureStorePath) {
|
|
430
|
+
printError("No secure store found. Run setup first: npx exagent setup");
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
clack2.intro(pc.bold("Agent Configuration"));
|
|
434
|
+
const password3 = await promptSecretPassword();
|
|
435
|
+
const currentConfig = await loadConfig(opts.config, {
|
|
436
|
+
getSecretPassword: async () => password3
|
|
437
|
+
});
|
|
438
|
+
const currentProvider = currentConfig.llm.provider;
|
|
439
|
+
const currentModel = currentConfig.llm.model || "not set";
|
|
440
|
+
const currentKey = currentConfig.llm.apiKey;
|
|
441
|
+
const maskedKey = currentKey ? `${currentKey.slice(0, 7)}...${currentKey.slice(-4)}` : "not set";
|
|
442
|
+
printInfo(`Provider: ${pc.cyan(currentProvider)}`);
|
|
443
|
+
printInfo(`Model: ${pc.cyan(currentModel)}`);
|
|
444
|
+
printInfo(`API key: ${pc.dim(maskedKey)}`);
|
|
445
|
+
console.log();
|
|
446
|
+
const action = await clack2.select({
|
|
447
|
+
message: "What would you like to change?",
|
|
448
|
+
options: [
|
|
449
|
+
{ value: "key", label: "LLM API key" },
|
|
450
|
+
{ value: "all", label: "Provider, model, and API key" }
|
|
451
|
+
]
|
|
452
|
+
});
|
|
453
|
+
if (clack2.isCancel(action)) {
|
|
454
|
+
clack2.cancel("Cancelled.");
|
|
455
|
+
process.exit(0);
|
|
456
|
+
}
|
|
457
|
+
let newProvider = currentProvider;
|
|
458
|
+
let newModel = currentModel;
|
|
459
|
+
if (action === "all") {
|
|
460
|
+
const selectedProvider = await clack2.select({
|
|
461
|
+
message: "LLM provider:",
|
|
462
|
+
options: LLM_PROVIDERS2.map((p) => ({ value: p, label: p })),
|
|
463
|
+
initialValue: currentProvider || void 0
|
|
464
|
+
});
|
|
465
|
+
if (clack2.isCancel(selectedProvider)) {
|
|
466
|
+
clack2.cancel("Cancelled.");
|
|
467
|
+
process.exit(0);
|
|
468
|
+
}
|
|
469
|
+
newProvider = selectedProvider;
|
|
470
|
+
const enteredModel = await clack2.text({
|
|
471
|
+
message: "LLM model:",
|
|
472
|
+
defaultValue: currentModel,
|
|
473
|
+
validate: (val) => {
|
|
474
|
+
if (!val?.trim()) return "Model name is required.";
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
if (clack2.isCancel(enteredModel)) {
|
|
478
|
+
clack2.cancel("Cancelled.");
|
|
479
|
+
process.exit(0);
|
|
480
|
+
}
|
|
481
|
+
newModel = enteredModel;
|
|
482
|
+
}
|
|
483
|
+
const newKey = await clack2.password({
|
|
484
|
+
message: "New LLM API key:",
|
|
485
|
+
validate: (val) => {
|
|
486
|
+
if (!val?.trim()) return "API key is required.";
|
|
487
|
+
if (val.length < 10) return "API key seems too short.";
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
if (clack2.isCancel(newKey)) {
|
|
491
|
+
clack2.cancel("Cancelled.");
|
|
492
|
+
process.exit(0);
|
|
493
|
+
}
|
|
494
|
+
updateSecureStore(secureStorePath, password3, { llmApiKey: newKey });
|
|
495
|
+
const updatedConfig = readConfigFile(opts.config);
|
|
496
|
+
updatedConfig.llm = {
|
|
497
|
+
...updatedConfig.llm,
|
|
498
|
+
provider: newProvider,
|
|
499
|
+
model: newModel
|
|
500
|
+
};
|
|
501
|
+
writeConfigFile(opts.config, updatedConfig);
|
|
502
|
+
clack2.outro(pc.green("Configuration updated"));
|
|
503
|
+
printSuccess("Updated", [
|
|
504
|
+
`${pc.dim("Provider:")} ${pc.cyan(newProvider)}`,
|
|
505
|
+
`${pc.dim("Model:")} ${pc.cyan(newModel)}`,
|
|
506
|
+
`${pc.dim("API key:")} ${pc.dim(`${newKey.slice(0, 7)}...${newKey.slice(-4)}`)}`,
|
|
507
|
+
"",
|
|
508
|
+
`Run ${pc.cyan("npx exagent run")} to start with the new configuration.`
|
|
509
|
+
]);
|
|
510
|
+
} catch (err) {
|
|
511
|
+
printError(err.message);
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
380
515
|
program.parse();
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import { loadConfig, writeSampleConfig } from './config.js';
|
|
3
|
+
import { loadConfig, readConfigFile, writeConfigFile, updateSecureStore, writeSampleConfig } from './config.js';
|
|
4
4
|
import { AgentRuntime } from './runtime.js';
|
|
5
5
|
import { listTemplates } from './strategy/index.js';
|
|
6
6
|
import { ensureLocalSetup, promptSecretPassword } from './setup.js';
|
|
7
|
-
import { printBanner, printDone, printError, printSuccess, pc } from './ui.js';
|
|
7
|
+
import { printBanner, printDone, printError, printInfo, printSuccess, pc } from './ui.js';
|
|
8
|
+
import * as clack from '@clack/prompts';
|
|
8
9
|
|
|
9
10
|
const program = new Command();
|
|
10
11
|
|
|
11
12
|
program
|
|
12
13
|
.name('exagent')
|
|
13
14
|
.description('Exagent — LLM trading agent runtime')
|
|
14
|
-
.version('0.3.
|
|
15
|
+
.version('0.3.4');
|
|
15
16
|
|
|
16
17
|
program
|
|
17
18
|
.command('init')
|
|
@@ -119,4 +120,122 @@ program
|
|
|
119
120
|
}
|
|
120
121
|
});
|
|
121
122
|
|
|
123
|
+
const LLM_PROVIDERS = ['openai', 'anthropic', 'google', 'deepseek', 'mistral', 'groq', 'together', 'ollama'] as const;
|
|
124
|
+
|
|
125
|
+
program
|
|
126
|
+
.command('config')
|
|
127
|
+
.description('Change LLM provider, model, or API key')
|
|
128
|
+
.option('--config <path>', 'Config file path', 'agent-config.json')
|
|
129
|
+
.action(async (opts) => {
|
|
130
|
+
try {
|
|
131
|
+
printBanner();
|
|
132
|
+
const config = readConfigFile(opts.config);
|
|
133
|
+
const secureStorePath = config.secrets?.secureStorePath;
|
|
134
|
+
|
|
135
|
+
if (!secureStorePath) {
|
|
136
|
+
printError('No secure store found. Run setup first: npx exagent setup');
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
clack.intro(pc.bold('Agent Configuration'));
|
|
141
|
+
|
|
142
|
+
// Decrypt current secrets
|
|
143
|
+
const password = await promptSecretPassword();
|
|
144
|
+
const currentConfig = await loadConfig(opts.config, {
|
|
145
|
+
getSecretPassword: async () => password,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Show current LLM settings
|
|
149
|
+
const currentProvider = currentConfig.llm.provider;
|
|
150
|
+
const currentModel = currentConfig.llm.model || 'not set';
|
|
151
|
+
const currentKey = currentConfig.llm.apiKey;
|
|
152
|
+
const maskedKey = currentKey
|
|
153
|
+
? `${currentKey.slice(0, 7)}...${currentKey.slice(-4)}`
|
|
154
|
+
: 'not set';
|
|
155
|
+
|
|
156
|
+
printInfo(`Provider: ${pc.cyan(currentProvider)}`);
|
|
157
|
+
printInfo(`Model: ${pc.cyan(currentModel)}`);
|
|
158
|
+
printInfo(`API key: ${pc.dim(maskedKey)}`);
|
|
159
|
+
console.log();
|
|
160
|
+
|
|
161
|
+
const action = await clack.select({
|
|
162
|
+
message: 'What would you like to change?',
|
|
163
|
+
options: [
|
|
164
|
+
{ value: 'key', label: 'LLM API key' },
|
|
165
|
+
{ value: 'all', label: 'Provider, model, and API key' },
|
|
166
|
+
],
|
|
167
|
+
});
|
|
168
|
+
if (clack.isCancel(action)) {
|
|
169
|
+
clack.cancel('Cancelled.');
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let newProvider = currentProvider;
|
|
174
|
+
let newModel = currentModel;
|
|
175
|
+
|
|
176
|
+
if (action === 'all') {
|
|
177
|
+
const selectedProvider = await clack.select({
|
|
178
|
+
message: 'LLM provider:',
|
|
179
|
+
options: LLM_PROVIDERS.map(p => ({ value: p, label: p })),
|
|
180
|
+
initialValue: currentProvider || undefined,
|
|
181
|
+
});
|
|
182
|
+
if (clack.isCancel(selectedProvider)) {
|
|
183
|
+
clack.cancel('Cancelled.');
|
|
184
|
+
process.exit(0);
|
|
185
|
+
}
|
|
186
|
+
newProvider = selectedProvider;
|
|
187
|
+
|
|
188
|
+
const enteredModel = await clack.text({
|
|
189
|
+
message: 'LLM model:',
|
|
190
|
+
defaultValue: currentModel,
|
|
191
|
+
validate: (val) => {
|
|
192
|
+
if (!val?.trim()) return 'Model name is required.';
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
if (clack.isCancel(enteredModel)) {
|
|
196
|
+
clack.cancel('Cancelled.');
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
newModel = enteredModel;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const newKey = await clack.password({
|
|
203
|
+
message: 'New LLM API key:',
|
|
204
|
+
validate: (val) => {
|
|
205
|
+
if (!val?.trim()) return 'API key is required.';
|
|
206
|
+
if (val.length < 10) return 'API key seems too short.';
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
if (clack.isCancel(newKey)) {
|
|
210
|
+
clack.cancel('Cancelled.');
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Update secure store with new API key
|
|
215
|
+
updateSecureStore(secureStorePath, password, { llmApiKey: newKey });
|
|
216
|
+
|
|
217
|
+
// Update config file with new provider/model
|
|
218
|
+
const updatedConfig = readConfigFile(opts.config);
|
|
219
|
+
updatedConfig.llm = {
|
|
220
|
+
...updatedConfig.llm,
|
|
221
|
+
provider: newProvider as typeof updatedConfig.llm.provider,
|
|
222
|
+
model: newModel,
|
|
223
|
+
};
|
|
224
|
+
writeConfigFile(opts.config, updatedConfig);
|
|
225
|
+
|
|
226
|
+
clack.outro(pc.green('Configuration updated'));
|
|
227
|
+
|
|
228
|
+
printSuccess('Updated', [
|
|
229
|
+
`${pc.dim('Provider:')} ${pc.cyan(newProvider)}`,
|
|
230
|
+
`${pc.dim('Model:')} ${pc.cyan(newModel)}`,
|
|
231
|
+
`${pc.dim('API key:')} ${pc.dim(`${newKey.slice(0, 7)}...${newKey.slice(-4)}`)}`,
|
|
232
|
+
'',
|
|
233
|
+
`Run ${pc.cyan('npx exagent run')} to start with the new configuration.`,
|
|
234
|
+
]);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
printError((err as Error).message);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
122
241
|
program.parse();
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { dirname, resolve } from 'node:path';
|
|
5
5
|
import { z } from 'zod';
|
|
@@ -258,7 +258,7 @@ const secureStoreSchema = z.object({
|
|
|
258
258
|
|
|
259
259
|
export type RuntimeConfigFile = z.infer<typeof configFileSchema>;
|
|
260
260
|
|
|
261
|
-
const DEFAULT_SCRYPT_COST =
|
|
261
|
+
const DEFAULT_SCRYPT_COST = 16384;
|
|
262
262
|
const DEFAULT_SCRYPT_BLOCK_SIZE = 8;
|
|
263
263
|
const DEFAULT_SCRYPT_PARALLELIZATION = 1;
|
|
264
264
|
|
|
@@ -415,6 +415,23 @@ export async function loadConfig(path: string = 'agent-config.json', options: Lo
|
|
|
415
415
|
return result.data;
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
+
export function updateSecureStore(
|
|
419
|
+
path: string,
|
|
420
|
+
password: string,
|
|
421
|
+
updates: Partial<LocalSecretPayload>,
|
|
422
|
+
): void {
|
|
423
|
+
const secrets = decryptSecretPayload(path, password);
|
|
424
|
+
const updated = { ...secrets, ...updates };
|
|
425
|
+
const encrypted = encryptSecretPayload(updated, password);
|
|
426
|
+
const secureStorePath = expandHomeDir(path);
|
|
427
|
+
writeFileSync(secureStorePath, JSON.stringify(encrypted, null, 2), { mode: 0o600 });
|
|
428
|
+
try {
|
|
429
|
+
chmodSync(secureStorePath, 0o600);
|
|
430
|
+
} catch {
|
|
431
|
+
// Best effort
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
418
435
|
export function generateSampleConfig(agentId: string, apiUrl: string): string {
|
|
419
436
|
const config: RuntimeConfigFile = {
|
|
420
437
|
agentId,
|