@exagent/agent 0.3.3 → 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-F4TCYYTD.js → chunk-WTECTX2Z.js} +13 -1
- package/dist/cli.js +155 -78
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +122 -3
- package/src/config.ts +18 -1
- package/src/setup.ts +46 -64
- 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";
|
|
@@ -173,98 +174,75 @@ async function setupWallet(config) {
|
|
|
173
174
|
return privateKey;
|
|
174
175
|
}
|
|
175
176
|
var LLM_PROVIDERS = ["openai", "anthropic", "google", "deepseek", "mistral", "groq", "together", "ollama"];
|
|
176
|
-
async function setupLlm(config
|
|
177
|
-
const hasRealLlmConfig = !!(bootstrapPayload.llm?.apiKey || config.llm?.apiKey);
|
|
177
|
+
async function setupLlm(config) {
|
|
178
178
|
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
|
|
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
182
|
if (!provider2) throw new Error("EXAGENT_LLM_PROVIDER required in non-interactive mode");
|
|
183
183
|
if (!model2) throw new Error("EXAGENT_LLM_MODEL required in non-interactive mode");
|
|
184
184
|
if (!apiKey2) throw new Error("EXAGENT_LLM_KEY required in non-interactive mode");
|
|
185
185
|
printDone("LLM configured");
|
|
186
186
|
return { provider: provider2, model: model2, apiKey: apiKey2 };
|
|
187
187
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
message: "LLM model:",
|
|
205
|
-
placeholder: "gpt-4o",
|
|
206
|
-
validate: (val) => {
|
|
207
|
-
if (!val.trim()) return "Model name is required.";
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
if (clack.isCancel(entered)) cancelled();
|
|
211
|
-
model = entered;
|
|
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;
|
|
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.";
|
|
222
204
|
}
|
|
223
|
-
}
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
232
|
-
if (clack.isCancel(entered)) cancelled();
|
|
233
|
-
apiKey = entered;
|
|
234
|
-
}
|
|
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();
|
|
235
213
|
printDone("LLM configured");
|
|
236
214
|
return { provider, model, apiKey };
|
|
237
215
|
}
|
|
238
216
|
async function setupEncryption() {
|
|
239
217
|
if (isNonInteractive()) {
|
|
240
|
-
const
|
|
241
|
-
if (!
|
|
218
|
+
const password4 = process.env.EXAGENT_PASSWORD;
|
|
219
|
+
if (!password4 || password4.length < 12) {
|
|
242
220
|
throw new Error("EXAGENT_PASSWORD must be at least 12 characters in non-interactive mode");
|
|
243
221
|
}
|
|
244
|
-
return
|
|
222
|
+
return password4;
|
|
245
223
|
}
|
|
246
224
|
printInfo(`Secrets encrypted with ${pc.cyan("AES-256-GCM")} (${pc.cyan("scrypt")} KDF)`);
|
|
247
225
|
printInfo("The password never leaves this machine.");
|
|
248
226
|
console.log();
|
|
249
|
-
const
|
|
227
|
+
const password3 = await clack.password({
|
|
250
228
|
message: "Choose a device password (12+ characters):",
|
|
251
229
|
validate: (val) => {
|
|
252
230
|
if (val.length < 12) return "Password must be at least 12 characters.";
|
|
253
231
|
}
|
|
254
232
|
});
|
|
255
|
-
if (clack.isCancel(
|
|
256
|
-
const
|
|
233
|
+
if (clack.isCancel(password3)) cancelled();
|
|
234
|
+
const confirm = await clack.password({
|
|
257
235
|
message: "Confirm password:",
|
|
258
236
|
validate: (val) => {
|
|
259
|
-
if (val !==
|
|
237
|
+
if (val !== password3) return "Passwords do not match.";
|
|
260
238
|
}
|
|
261
239
|
});
|
|
262
|
-
if (clack.isCancel(
|
|
263
|
-
return
|
|
240
|
+
if (clack.isCancel(confirm)) cancelled();
|
|
241
|
+
return password3;
|
|
264
242
|
}
|
|
265
|
-
function writeSecureStore(path, secrets,
|
|
243
|
+
function writeSecureStore(path, secrets, password3) {
|
|
266
244
|
const secureStorePath = expandHomeDir(path);
|
|
267
|
-
const encrypted = encryptSecretPayload(secrets,
|
|
245
|
+
const encrypted = encryptSecretPayload(secrets, password3);
|
|
268
246
|
const dir = dirname(secureStorePath);
|
|
269
247
|
if (!existsSync(dir)) {
|
|
270
248
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
@@ -278,18 +256,26 @@ function writeSecureStore(path, secrets, password2) {
|
|
|
278
256
|
}
|
|
279
257
|
async function promptSecretPassword(question = "Device password:") {
|
|
280
258
|
if (isNonInteractive()) {
|
|
281
|
-
const
|
|
282
|
-
if (!
|
|
283
|
-
return
|
|
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;
|
|
284
262
|
}
|
|
285
|
-
const
|
|
286
|
-
if (clack.isCancel(
|
|
287
|
-
return
|
|
263
|
+
const password3 = await clack.password({ message: question });
|
|
264
|
+
if (clack.isCancel(password3)) cancelled();
|
|
265
|
+
return password3;
|
|
288
266
|
}
|
|
289
267
|
async function ensureLocalSetup(configPath) {
|
|
290
268
|
const config = readConfigFile(configPath);
|
|
291
269
|
const existingSecureStorePath = config.secrets?.secureStorePath ? expandHomeDir(config.secrets.secureStorePath) : null;
|
|
292
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
|
+
]);
|
|
293
279
|
return;
|
|
294
280
|
}
|
|
295
281
|
printBanner();
|
|
@@ -298,18 +284,15 @@ async function ensureLocalSetup(configPath) {
|
|
|
298
284
|
const bootstrapPayload = await consumeBootstrapPackage(config);
|
|
299
285
|
if (config.secrets?.bootstrapToken) {
|
|
300
286
|
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
287
|
} else {
|
|
305
288
|
printInfo("No bootstrap token \u2014 manual configuration");
|
|
306
289
|
}
|
|
307
290
|
printStep(2, 4, "Wallet setup");
|
|
308
291
|
const walletPrivateKey = await setupWallet(config);
|
|
309
292
|
printStep(3, 4, "LLM configuration");
|
|
310
|
-
const llm = await setupLlm(config
|
|
293
|
+
const llm = await setupLlm(config);
|
|
311
294
|
printStep(4, 4, "Device encryption");
|
|
312
|
-
const
|
|
295
|
+
const password3 = await setupEncryption();
|
|
313
296
|
const secrets = {
|
|
314
297
|
apiToken: bootstrapPayload.apiToken || config.apiToken || "",
|
|
315
298
|
walletPrivateKey,
|
|
@@ -343,23 +326,25 @@ async function ensureLocalSetup(configPath) {
|
|
|
343
326
|
const secureStorePath = writeSecureStore(
|
|
344
327
|
nextConfig.secrets?.secureStorePath || getDefaultSecureStorePath(nextConfig.agentId),
|
|
345
328
|
secrets,
|
|
346
|
-
|
|
329
|
+
password3
|
|
347
330
|
);
|
|
348
331
|
nextConfig.secrets = { secureStorePath };
|
|
349
332
|
writeConfigFile(configPath, nextConfig);
|
|
350
333
|
printDone(`Encrypted store: ${pc.dim(secureStorePath)}`);
|
|
351
334
|
clack.outro(pc.green("Setup complete"));
|
|
352
335
|
printSuccess("Ready", [
|
|
353
|
-
`${pc.cyan("npx exagent run")}
|
|
354
|
-
`${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`,
|
|
355
339
|
"",
|
|
356
340
|
`${pc.dim("Dashboard:")} ${pc.cyan("https://exagent.io")}`
|
|
357
341
|
]);
|
|
358
342
|
}
|
|
359
343
|
|
|
360
344
|
// src/cli.ts
|
|
345
|
+
import * as clack2 from "@clack/prompts";
|
|
361
346
|
var program = new Command();
|
|
362
|
-
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");
|
|
363
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) => {
|
|
364
349
|
printBanner();
|
|
365
350
|
writeSampleConfig(opts.agentId, opts.apiUrl, opts.config);
|
|
@@ -435,4 +420,96 @@ program.command("status").description("Check agent status").option("--config <pa
|
|
|
435
420
|
process.exit(1);
|
|
436
421
|
}
|
|
437
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
|
+
});
|
|
438
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';
|
|
@@ -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,
|