@exagent/agent 0.3.3 → 0.3.5
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-F4TCYYTD.js → chunk-WTECTX2Z.js} +13 -1
- package/dist/cli.js +235 -80
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +125 -3
- package/src/config.ts +18 -1
- package/src/llm-providers.ts +100 -0
- package/src/setup.ts +48 -65
- 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-VDK4XPAC.js +0 -6318
- 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-XHYHRJMK.js +0 -6319
- 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-YTZ5MZLP.js +0 -6318
- 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";
|
|
@@ -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,
|
|
@@ -6287,6 +6298,7 @@ export {
|
|
|
6287
6298
|
writeConfigFile,
|
|
6288
6299
|
encryptSecretPayload,
|
|
6289
6300
|
loadConfig,
|
|
6301
|
+
updateSecureStore,
|
|
6290
6302
|
generateSampleConfig,
|
|
6291
6303
|
writeSampleConfig,
|
|
6292
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";
|
|
@@ -78,6 +79,87 @@ function printError(message) {
|
|
|
78
79
|
console.log(` ${pc.red("\u2717")} ${message}`);
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
// src/llm-providers.ts
|
|
83
|
+
var LLM_PROVIDERS = [
|
|
84
|
+
{
|
|
85
|
+
id: "openai",
|
|
86
|
+
label: "OpenAI",
|
|
87
|
+
models: [
|
|
88
|
+
{ id: "gpt-5.2", label: "GPT-5.2" },
|
|
89
|
+
{ id: "gpt-5.2-pro", label: "GPT-5.2 Pro" },
|
|
90
|
+
{ id: "gpt-5-mini", label: "GPT-5 Mini" },
|
|
91
|
+
{ id: "gpt-5-nano", label: "GPT-5 Nano" },
|
|
92
|
+
{ id: "gpt-4o", label: "GPT-4o" },
|
|
93
|
+
{ id: "gpt-4o-mini", label: "GPT-4o Mini" }
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "anthropic",
|
|
98
|
+
label: "Anthropic",
|
|
99
|
+
models: [
|
|
100
|
+
{ id: "claude-opus-4-6", label: "Claude Opus 4.6" },
|
|
101
|
+
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
|
|
102
|
+
{ id: "claude-haiku-4-5", label: "Claude Haiku 4.5" }
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "google",
|
|
107
|
+
label: "Google",
|
|
108
|
+
models: [
|
|
109
|
+
{ id: "gemini-3-pro", label: "Gemini 3 Pro" },
|
|
110
|
+
{ id: "gemini-3-flash", label: "Gemini 3 Flash" },
|
|
111
|
+
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro" },
|
|
112
|
+
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash" },
|
|
113
|
+
{ id: "gemini-2.5-flash-lite", label: "Gemini 2.5 Flash Lite" }
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "deepseek",
|
|
118
|
+
label: "DeepSeek",
|
|
119
|
+
models: [
|
|
120
|
+
{ id: "deepseek-chat", label: "DeepSeek Chat" },
|
|
121
|
+
{ id: "deepseek-reasoner", label: "DeepSeek Reasoner" }
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "mistral",
|
|
126
|
+
label: "Mistral",
|
|
127
|
+
models: [
|
|
128
|
+
{ id: "mistral-large-latest", label: "Mistral Large" },
|
|
129
|
+
{ id: "mistral-small-latest", label: "Mistral Small" }
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "groq",
|
|
134
|
+
label: "Groq",
|
|
135
|
+
models: [
|
|
136
|
+
{ id: "llama-3.3-70b-versatile", label: "Llama 3.3 70B" },
|
|
137
|
+
{ id: "llama-3.1-8b-instant", label: "Llama 3.1 8B" },
|
|
138
|
+
{ id: "mixtral-8x7b-32768", label: "Mixtral 8x7B" }
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "together",
|
|
143
|
+
label: "Together",
|
|
144
|
+
models: [
|
|
145
|
+
{ id: "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo", label: "Llama 3.1 70B" },
|
|
146
|
+
{ id: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", label: "Llama 3.1 8B" }
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "ollama",
|
|
151
|
+
label: "Ollama (local)",
|
|
152
|
+
models: [
|
|
153
|
+
{ id: "llama3.1", label: "Llama 3.1" },
|
|
154
|
+
{ id: "mistral", label: "Mistral" },
|
|
155
|
+
{ id: "custom", label: "Custom (type model name)" }
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
];
|
|
159
|
+
function getProvider(id) {
|
|
160
|
+
return LLM_PROVIDERS.find((p) => p.id === id);
|
|
161
|
+
}
|
|
162
|
+
|
|
81
163
|
// src/setup.ts
|
|
82
164
|
function expandHomeDir(path) {
|
|
83
165
|
if (!path.startsWith("~/")) return path;
|
|
@@ -172,99 +254,74 @@ async function setupWallet(config) {
|
|
|
172
254
|
printDone(`Wallet imported: ${pc.dim(address)}`);
|
|
173
255
|
return privateKey;
|
|
174
256
|
}
|
|
175
|
-
|
|
176
|
-
async function setupLlm(config, bootstrapPayload) {
|
|
177
|
-
const hasRealLlmConfig = !!(bootstrapPayload.llm?.apiKey || config.llm?.apiKey);
|
|
257
|
+
async function setupLlm(config) {
|
|
178
258
|
if (isNonInteractive()) {
|
|
179
|
-
const provider2 = process.env.EXAGENT_LLM_PROVIDER ||
|
|
180
|
-
const model2 = process.env.EXAGENT_LLM_MODEL ||
|
|
181
|
-
const apiKey2 = process.env.EXAGENT_LLM_KEY
|
|
259
|
+
const provider2 = process.env.EXAGENT_LLM_PROVIDER || config.llm?.provider;
|
|
260
|
+
const model2 = process.env.EXAGENT_LLM_MODEL || config.llm?.model;
|
|
261
|
+
const apiKey2 = process.env.EXAGENT_LLM_KEY;
|
|
182
262
|
if (!provider2) throw new Error("EXAGENT_LLM_PROVIDER required in non-interactive mode");
|
|
183
263
|
if (!model2) throw new Error("EXAGENT_LLM_MODEL required in non-interactive mode");
|
|
184
264
|
if (!apiKey2) throw new Error("EXAGENT_LLM_KEY required in non-interactive mode");
|
|
185
265
|
printDone("LLM configured");
|
|
186
266
|
return { provider: provider2, model: model2, apiKey: apiKey2 };
|
|
187
267
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
let apiKey;
|
|
214
|
-
if (bootstrapPayload.llm?.apiKey) {
|
|
215
|
-
const useBootstrap = await clack.confirm({
|
|
216
|
-
message: "LLM API key received from dashboard. Use it?",
|
|
217
|
-
initialValue: true
|
|
218
|
-
});
|
|
219
|
-
if (clack.isCancel(useBootstrap)) cancelled();
|
|
220
|
-
if (useBootstrap) {
|
|
221
|
-
apiKey = bootstrapPayload.llm.apiKey;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
if (!apiKey) {
|
|
225
|
-
apiKey = config.llm?.apiKey;
|
|
226
|
-
}
|
|
227
|
-
if (!apiKey) {
|
|
228
|
-
const entered = await clack.password({
|
|
229
|
-
message: "LLM API key:",
|
|
230
|
-
validate: (val) => validateLlmKeyFormat(provider, val)
|
|
231
|
-
});
|
|
232
|
-
if (clack.isCancel(entered)) cancelled();
|
|
233
|
-
apiKey = entered;
|
|
234
|
-
}
|
|
268
|
+
const defaultProvider = config.llm?.provider;
|
|
269
|
+
const providerOptions = LLM_PROVIDERS.map((p) => ({ value: p.id, label: p.label }));
|
|
270
|
+
const selected = await clack.select({
|
|
271
|
+
message: "LLM provider:",
|
|
272
|
+
options: providerOptions,
|
|
273
|
+
initialValue: defaultProvider || void 0
|
|
274
|
+
});
|
|
275
|
+
if (clack.isCancel(selected)) cancelled();
|
|
276
|
+
const provider = selected;
|
|
277
|
+
const defaultModel = config.llm?.model;
|
|
278
|
+
const providerInfo = getProvider(provider);
|
|
279
|
+
const modelOptions = providerInfo ? providerInfo.models.map((m) => ({ value: m.id, label: m.label })) : [{ value: defaultModel || "gpt-4o", label: defaultModel || "gpt-4o" }];
|
|
280
|
+
const selectedModel = await clack.select({
|
|
281
|
+
message: "LLM model:",
|
|
282
|
+
options: modelOptions,
|
|
283
|
+
initialValue: defaultModel || void 0
|
|
284
|
+
});
|
|
285
|
+
if (clack.isCancel(selectedModel)) cancelled();
|
|
286
|
+
const model = selectedModel;
|
|
287
|
+
const apiKey = await clack.password({
|
|
288
|
+
message: "LLM API key:",
|
|
289
|
+
validate: (val) => validateLlmKeyFormat(provider, val)
|
|
290
|
+
});
|
|
291
|
+
if (clack.isCancel(apiKey)) cancelled();
|
|
235
292
|
printDone("LLM configured");
|
|
236
293
|
return { provider, model, apiKey };
|
|
237
294
|
}
|
|
238
295
|
async function setupEncryption() {
|
|
239
296
|
if (isNonInteractive()) {
|
|
240
|
-
const
|
|
241
|
-
if (!
|
|
297
|
+
const password4 = process.env.EXAGENT_PASSWORD;
|
|
298
|
+
if (!password4 || password4.length < 12) {
|
|
242
299
|
throw new Error("EXAGENT_PASSWORD must be at least 12 characters in non-interactive mode");
|
|
243
300
|
}
|
|
244
|
-
return
|
|
301
|
+
return password4;
|
|
245
302
|
}
|
|
246
303
|
printInfo(`Secrets encrypted with ${pc.cyan("AES-256-GCM")} (${pc.cyan("scrypt")} KDF)`);
|
|
247
304
|
printInfo("The password never leaves this machine.");
|
|
248
305
|
console.log();
|
|
249
|
-
const
|
|
306
|
+
const password3 = await clack.password({
|
|
250
307
|
message: "Choose a device password (12+ characters):",
|
|
251
308
|
validate: (val) => {
|
|
252
309
|
if (val.length < 12) return "Password must be at least 12 characters.";
|
|
253
310
|
}
|
|
254
311
|
});
|
|
255
|
-
if (clack.isCancel(
|
|
256
|
-
const
|
|
312
|
+
if (clack.isCancel(password3)) cancelled();
|
|
313
|
+
const confirm = await clack.password({
|
|
257
314
|
message: "Confirm password:",
|
|
258
315
|
validate: (val) => {
|
|
259
|
-
if (val !==
|
|
316
|
+
if (val !== password3) return "Passwords do not match.";
|
|
260
317
|
}
|
|
261
318
|
});
|
|
262
|
-
if (clack.isCancel(
|
|
263
|
-
return
|
|
319
|
+
if (clack.isCancel(confirm)) cancelled();
|
|
320
|
+
return password3;
|
|
264
321
|
}
|
|
265
|
-
function writeSecureStore(path, secrets,
|
|
322
|
+
function writeSecureStore(path, secrets, password3) {
|
|
266
323
|
const secureStorePath = expandHomeDir(path);
|
|
267
|
-
const encrypted = encryptSecretPayload(secrets,
|
|
324
|
+
const encrypted = encryptSecretPayload(secrets, password3);
|
|
268
325
|
const dir = dirname(secureStorePath);
|
|
269
326
|
if (!existsSync(dir)) {
|
|
270
327
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
@@ -278,18 +335,26 @@ function writeSecureStore(path, secrets, password2) {
|
|
|
278
335
|
}
|
|
279
336
|
async function promptSecretPassword(question = "Device password:") {
|
|
280
337
|
if (isNonInteractive()) {
|
|
281
|
-
const
|
|
282
|
-
if (!
|
|
283
|
-
return
|
|
338
|
+
const password4 = process.env.EXAGENT_PASSWORD || process.env.EXAGENT_SECRET_PASSWORD;
|
|
339
|
+
if (!password4) throw new Error("EXAGENT_PASSWORD required in non-interactive mode");
|
|
340
|
+
return password4;
|
|
284
341
|
}
|
|
285
|
-
const
|
|
286
|
-
if (clack.isCancel(
|
|
287
|
-
return
|
|
342
|
+
const password3 = await clack.password({ message: question });
|
|
343
|
+
if (clack.isCancel(password3)) cancelled();
|
|
344
|
+
return password3;
|
|
288
345
|
}
|
|
289
346
|
async function ensureLocalSetup(configPath) {
|
|
290
347
|
const config = readConfigFile(configPath);
|
|
291
348
|
const existingSecureStorePath = config.secrets?.secureStorePath ? expandHomeDir(config.secrets.secureStorePath) : null;
|
|
292
349
|
if (existingSecureStorePath && !config.secrets?.bootstrapToken && existsSync(existingSecureStorePath) && !config.apiToken && !config.wallet?.privateKey && !config.llm.apiKey) {
|
|
350
|
+
printBanner();
|
|
351
|
+
printSuccess("Already set up", [
|
|
352
|
+
`${pc.cyan("npx exagent run")} Start the agent`,
|
|
353
|
+
`${pc.cyan("npx exagent config")} Change LLM API key or model`,
|
|
354
|
+
`${pc.cyan("npx exagent status")} Check agent connection`,
|
|
355
|
+
"",
|
|
356
|
+
`${pc.dim("Dashboard:")} ${pc.cyan("https://exagent.io")}`
|
|
357
|
+
]);
|
|
293
358
|
return;
|
|
294
359
|
}
|
|
295
360
|
printBanner();
|
|
@@ -298,18 +363,15 @@ async function ensureLocalSetup(configPath) {
|
|
|
298
363
|
const bootstrapPayload = await consumeBootstrapPackage(config);
|
|
299
364
|
if (config.secrets?.bootstrapToken) {
|
|
300
365
|
printDone("Bootstrap package consumed");
|
|
301
|
-
if (bootstrapPayload.llm?.provider) {
|
|
302
|
-
printInfo(`LLM config received: ${pc.cyan(bootstrapPayload.llm.provider)}${bootstrapPayload.llm.model ? ` / ${pc.cyan(bootstrapPayload.llm.model)}` : ""}`);
|
|
303
|
-
}
|
|
304
366
|
} else {
|
|
305
367
|
printInfo("No bootstrap token \u2014 manual configuration");
|
|
306
368
|
}
|
|
307
369
|
printStep(2, 4, "Wallet setup");
|
|
308
370
|
const walletPrivateKey = await setupWallet(config);
|
|
309
371
|
printStep(3, 4, "LLM configuration");
|
|
310
|
-
const llm = await setupLlm(config
|
|
372
|
+
const llm = await setupLlm(config);
|
|
311
373
|
printStep(4, 4, "Device encryption");
|
|
312
|
-
const
|
|
374
|
+
const password3 = await setupEncryption();
|
|
313
375
|
const secrets = {
|
|
314
376
|
apiToken: bootstrapPayload.apiToken || config.apiToken || "",
|
|
315
377
|
walletPrivateKey,
|
|
@@ -343,23 +405,25 @@ async function ensureLocalSetup(configPath) {
|
|
|
343
405
|
const secureStorePath = writeSecureStore(
|
|
344
406
|
nextConfig.secrets?.secureStorePath || getDefaultSecureStorePath(nextConfig.agentId),
|
|
345
407
|
secrets,
|
|
346
|
-
|
|
408
|
+
password3
|
|
347
409
|
);
|
|
348
410
|
nextConfig.secrets = { secureStorePath };
|
|
349
411
|
writeConfigFile(configPath, nextConfig);
|
|
350
412
|
printDone(`Encrypted store: ${pc.dim(secureStorePath)}`);
|
|
351
413
|
clack.outro(pc.green("Setup complete"));
|
|
352
414
|
printSuccess("Ready", [
|
|
353
|
-
`${pc.cyan("npx exagent run")}
|
|
354
|
-
`${pc.cyan("npx exagent
|
|
415
|
+
`${pc.cyan("npx exagent run")} Start the agent`,
|
|
416
|
+
`${pc.cyan("npx exagent config")} Change LLM API key or model`,
|
|
417
|
+
`${pc.cyan("npx exagent status")} Check agent connection`,
|
|
355
418
|
"",
|
|
356
419
|
`${pc.dim("Dashboard:")} ${pc.cyan("https://exagent.io")}`
|
|
357
420
|
]);
|
|
358
421
|
}
|
|
359
422
|
|
|
360
423
|
// src/cli.ts
|
|
424
|
+
import * as clack2 from "@clack/prompts";
|
|
361
425
|
var program = new Command();
|
|
362
|
-
program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.3.
|
|
426
|
+
program.name("exagent").description("Exagent \u2014 LLM trading agent runtime").version("0.3.5");
|
|
363
427
|
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) => {
|
|
364
428
|
printBanner();
|
|
365
429
|
writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
|
|
@@ -435,4 +499,95 @@ program.command("status").description("Check agent status").option("--config <pa
|
|
|
435
499
|
process.exit(1);
|
|
436
500
|
}
|
|
437
501
|
});
|
|
502
|
+
program.command("config").description("Change LLM provider, model, or API key").option("--config <path>", "Config file path", "agent-config.json").action(async (opts) => {
|
|
503
|
+
try {
|
|
504
|
+
printBanner();
|
|
505
|
+
const config = readConfigFile(opts.config);
|
|
506
|
+
const secureStorePath = config.secrets?.secureStorePath;
|
|
507
|
+
if (!secureStorePath) {
|
|
508
|
+
printError("No secure store found. Run setup first: npx exagent setup");
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
clack2.intro(pc.bold("Agent Configuration"));
|
|
512
|
+
const password3 = await promptSecretPassword();
|
|
513
|
+
const currentConfig = await loadConfig(opts.config, {
|
|
514
|
+
getSecretPassword: async () => password3
|
|
515
|
+
});
|
|
516
|
+
const currentProvider = currentConfig.llm.provider;
|
|
517
|
+
const currentModel = currentConfig.llm.model || "not set";
|
|
518
|
+
const currentKey = currentConfig.llm.apiKey;
|
|
519
|
+
const maskedKey = currentKey ? `${currentKey.slice(0, 7)}...${currentKey.slice(-4)}` : "not set";
|
|
520
|
+
printInfo(`Provider: ${pc.cyan(currentProvider)}`);
|
|
521
|
+
printInfo(`Model: ${pc.cyan(currentModel)}`);
|
|
522
|
+
printInfo(`API key: ${pc.dim(maskedKey)}`);
|
|
523
|
+
console.log();
|
|
524
|
+
const action = await clack2.select({
|
|
525
|
+
message: "What would you like to change?",
|
|
526
|
+
options: [
|
|
527
|
+
{ value: "key", label: "LLM API key" },
|
|
528
|
+
{ value: "all", label: "Provider, model, and API key" }
|
|
529
|
+
]
|
|
530
|
+
});
|
|
531
|
+
if (clack2.isCancel(action)) {
|
|
532
|
+
clack2.cancel("Cancelled.");
|
|
533
|
+
process.exit(0);
|
|
534
|
+
}
|
|
535
|
+
let newProvider = currentProvider;
|
|
536
|
+
let newModel = currentModel;
|
|
537
|
+
if (action === "all") {
|
|
538
|
+
const selectedProvider = await clack2.select({
|
|
539
|
+
message: "LLM provider:",
|
|
540
|
+
options: LLM_PROVIDERS.map((p) => ({ value: p.id, label: p.label })),
|
|
541
|
+
initialValue: currentProvider || void 0
|
|
542
|
+
});
|
|
543
|
+
if (clack2.isCancel(selectedProvider)) {
|
|
544
|
+
clack2.cancel("Cancelled.");
|
|
545
|
+
process.exit(0);
|
|
546
|
+
}
|
|
547
|
+
newProvider = selectedProvider;
|
|
548
|
+
const provider = getProvider(newProvider);
|
|
549
|
+
const modelOptions = provider ? provider.models.map((m) => ({ value: m.id, label: m.label })) : [{ value: currentModel, label: currentModel }];
|
|
550
|
+
const selectedModel = await clack2.select({
|
|
551
|
+
message: "LLM model:",
|
|
552
|
+
options: modelOptions,
|
|
553
|
+
initialValue: currentModel || void 0
|
|
554
|
+
});
|
|
555
|
+
if (clack2.isCancel(selectedModel)) {
|
|
556
|
+
clack2.cancel("Cancelled.");
|
|
557
|
+
process.exit(0);
|
|
558
|
+
}
|
|
559
|
+
newModel = selectedModel;
|
|
560
|
+
}
|
|
561
|
+
const newKey = await clack2.password({
|
|
562
|
+
message: "New LLM API key:",
|
|
563
|
+
validate: (val) => {
|
|
564
|
+
if (!val?.trim()) return "API key is required.";
|
|
565
|
+
if (val.length < 10) return "API key seems too short.";
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
if (clack2.isCancel(newKey)) {
|
|
569
|
+
clack2.cancel("Cancelled.");
|
|
570
|
+
process.exit(0);
|
|
571
|
+
}
|
|
572
|
+
updateSecureStore(secureStorePath, password3, { llmApiKey: newKey });
|
|
573
|
+
const updatedConfig = readConfigFile(opts.config);
|
|
574
|
+
updatedConfig.llm = {
|
|
575
|
+
...updatedConfig.llm,
|
|
576
|
+
provider: newProvider,
|
|
577
|
+
model: newModel
|
|
578
|
+
};
|
|
579
|
+
writeConfigFile(opts.config, updatedConfig);
|
|
580
|
+
clack2.outro(pc.green("Configuration updated"));
|
|
581
|
+
printSuccess("Updated", [
|
|
582
|
+
`${pc.dim("Provider:")} ${pc.cyan(newProvider)}`,
|
|
583
|
+
`${pc.dim("Model:")} ${pc.cyan(newModel)}`,
|
|
584
|
+
`${pc.dim("API key:")} ${pc.dim(`${newKey.slice(0, 7)}...${newKey.slice(-4)}`)}`,
|
|
585
|
+
"",
|
|
586
|
+
`Run ${pc.cyan("npx exagent run")} to start with the new configuration.`
|
|
587
|
+
]);
|
|
588
|
+
} catch (err) {
|
|
589
|
+
printError(err.message);
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
438
593
|
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.5');
|
|
15
16
|
|
|
16
17
|
program
|
|
17
18
|
.command('init')
|
|
@@ -119,4 +120,125 @@ program
|
|
|
119
120
|
}
|
|
120
121
|
});
|
|
121
122
|
|
|
123
|
+
import { LLM_PROVIDERS, getProvider } from './llm-providers.js';
|
|
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: string = currentProvider;
|
|
174
|
+
let newModel: string = currentModel;
|
|
175
|
+
|
|
176
|
+
if (action === 'all') {
|
|
177
|
+
const selectedProvider = await clack.select({
|
|
178
|
+
message: 'LLM provider:',
|
|
179
|
+
options: LLM_PROVIDERS.map(p => ({ value: p.id, label: p.label })),
|
|
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 provider = getProvider(newProvider);
|
|
189
|
+
const modelOptions = provider
|
|
190
|
+
? provider.models.map(m => ({ value: m.id, label: m.label }))
|
|
191
|
+
: [{ value: currentModel, label: currentModel }];
|
|
192
|
+
|
|
193
|
+
const selectedModel = await clack.select({
|
|
194
|
+
message: 'LLM model:',
|
|
195
|
+
options: modelOptions,
|
|
196
|
+
initialValue: currentModel || undefined,
|
|
197
|
+
});
|
|
198
|
+
if (clack.isCancel(selectedModel)) {
|
|
199
|
+
clack.cancel('Cancelled.');
|
|
200
|
+
process.exit(0);
|
|
201
|
+
}
|
|
202
|
+
newModel = selectedModel;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const newKey = await clack.password({
|
|
206
|
+
message: 'New LLM API key:',
|
|
207
|
+
validate: (val) => {
|
|
208
|
+
if (!val?.trim()) return 'API key is required.';
|
|
209
|
+
if (val.length < 10) return 'API key seems too short.';
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
if (clack.isCancel(newKey)) {
|
|
213
|
+
clack.cancel('Cancelled.');
|
|
214
|
+
process.exit(0);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Update secure store with new API key
|
|
218
|
+
updateSecureStore(secureStorePath, password, { llmApiKey: newKey });
|
|
219
|
+
|
|
220
|
+
// Update config file with new provider/model
|
|
221
|
+
const updatedConfig = readConfigFile(opts.config);
|
|
222
|
+
updatedConfig.llm = {
|
|
223
|
+
...updatedConfig.llm,
|
|
224
|
+
provider: newProvider as typeof updatedConfig.llm.provider,
|
|
225
|
+
model: newModel,
|
|
226
|
+
};
|
|
227
|
+
writeConfigFile(opts.config, updatedConfig);
|
|
228
|
+
|
|
229
|
+
clack.outro(pc.green('Configuration updated'));
|
|
230
|
+
|
|
231
|
+
printSuccess('Updated', [
|
|
232
|
+
`${pc.dim('Provider:')} ${pc.cyan(newProvider)}`,
|
|
233
|
+
`${pc.dim('Model:')} ${pc.cyan(newModel)}`,
|
|
234
|
+
`${pc.dim('API key:')} ${pc.dim(`${newKey.slice(0, 7)}...${newKey.slice(-4)}`)}`,
|
|
235
|
+
'',
|
|
236
|
+
`Run ${pc.cyan('npx exagent run')} to start with the new configuration.`,
|
|
237
|
+
]);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
printError((err as Error).message);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
122
244
|
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';
|
|
@@ -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,
|