@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.
Files changed (148) hide show
  1. package/dist/{chunk-F4TCYYTD.js → chunk-WTECTX2Z.js} +13 -1
  2. package/dist/cli.js +155 -78
  3. package/dist/index.js +1 -1
  4. package/package.json +1 -1
  5. package/src/cli.ts +122 -3
  6. package/src/config.ts +18 -1
  7. package/src/setup.ts +46 -64
  8. package/dist/chunk-25J5ZDKX.js +0 -5622
  9. package/dist/chunk-2ML4XS5X.js +0 -5626
  10. package/dist/chunk-2OKYNZ3J.js +0 -5622
  11. package/dist/chunk-2PAASZJN.js +0 -5132
  12. package/dist/chunk-2WAYVOLU.js +0 -5624
  13. package/dist/chunk-2XBVVY3I.js +0 -5621
  14. package/dist/chunk-37HCPVBZ.js +0 -2941
  15. package/dist/chunk-3DZAZBLF.js +0 -5652
  16. package/dist/chunk-3IXCKNSV.js +0 -5008
  17. package/dist/chunk-3MXFTRXB.js +0 -3326
  18. package/dist/chunk-4MURFLNJ.js +0 -5136
  19. package/dist/chunk-4UVMO6ZM.js +0 -6318
  20. package/dist/chunk-56YQROFU.js +0 -5639
  21. package/dist/chunk-5B3ZEGMD.js +0 -4330
  22. package/dist/chunk-5NU6FDDE.js +0 -6020
  23. package/dist/chunk-5WADKPJU.js +0 -1552
  24. package/dist/chunk-6LOIFEEV.js +0 -4895
  25. package/dist/chunk-6YIXPNL4.js +0 -5626
  26. package/dist/chunk-72HK2V74.js +0 -5224
  27. package/dist/chunk-7Q72QQIV.js +0 -4697
  28. package/dist/chunk-7V3XTBIF.js +0 -4896
  29. package/dist/chunk-A4CM4F7Q.js +0 -5633
  30. package/dist/chunk-ADXXR2MA.js +0 -4816
  31. package/dist/chunk-AG6CJPIC.js +0 -4895
  32. package/dist/chunk-AJPXHBTF.js +0 -4459
  33. package/dist/chunk-AQ6R37XV.js +0 -4809
  34. package/dist/chunk-AS4UEZMU.js +0 -5001
  35. package/dist/chunk-ASYMD22Y.js +0 -5624
  36. package/dist/chunk-AU4MCQCE.js +0 -5624
  37. package/dist/chunk-B4VHIITU.js +0 -5748
  38. package/dist/chunk-B6GVDNKQ.js +0 -5624
  39. package/dist/chunk-BCIAW6ZL.js +0 -5132
  40. package/dist/chunk-BJZ5PCG3.js +0 -5358
  41. package/dist/chunk-BS4J5QSM.js +0 -5622
  42. package/dist/chunk-BV2AUUX6.js +0 -2940
  43. package/dist/chunk-BWNSH2LK.js +0 -1574
  44. package/dist/chunk-C3GMBW3R.js +0 -5640
  45. package/dist/chunk-C6CVQGJ4.js +0 -5624
  46. package/dist/chunk-CGXKXNUJ.js +0 -5626
  47. package/dist/chunk-CIEZAYOU.js +0 -4701
  48. package/dist/chunk-CORZCEAQ.js +0 -5621
  49. package/dist/chunk-CS5LGZWP.js +0 -5357
  50. package/dist/chunk-CVT3KC24.js +0 -5624
  51. package/dist/chunk-D5MJ45R7.js +0 -3258
  52. package/dist/chunk-DSBRZ5DZ.js +0 -5624
  53. package/dist/chunk-E2X7JARQ.js +0 -4437
  54. package/dist/chunk-E3GY36ZP.js +0 -3258
  55. package/dist/chunk-E6NCIFKB.js +0 -4733
  56. package/dist/chunk-EOXLKW4D.js +0 -4895
  57. package/dist/chunk-FCI7LX4Q.js +0 -5624
  58. package/dist/chunk-FFJSKTOL.js +0 -4539
  59. package/dist/chunk-FOQYP3IB.js +0 -2950
  60. package/dist/chunk-GNEYTZDH.js +0 -4686
  61. package/dist/chunk-GPMXUMYH.js +0 -5991
  62. package/dist/chunk-GZWPAQPU.js +0 -4593
  63. package/dist/chunk-H5DXDKMX.js +0 -5619
  64. package/dist/chunk-HFQRTMS6.js +0 -3377
  65. package/dist/chunk-HQKRHX6Y.js +0 -5626
  66. package/dist/chunk-HTF3TNBY.js +0 -4834
  67. package/dist/chunk-IADSQBBY.js +0 -5523
  68. package/dist/chunk-IE2SXMZK.js +0 -4890
  69. package/dist/chunk-IGUQVJCB.js +0 -5622
  70. package/dist/chunk-IIREL7SL.js +0 -5615
  71. package/dist/chunk-IJK4EFTJ.js +0 -6043
  72. package/dist/chunk-J2MQ3Y5O.js +0 -5223
  73. package/dist/chunk-J3NG7AGT.js +0 -6047
  74. package/dist/chunk-JIBBZ3NV.js +0 -5132
  75. package/dist/chunk-JIPSBE6S.js +0 -5622
  76. package/dist/chunk-JPG755XK.js +0 -4589
  77. package/dist/chunk-JQBNL5GX.js +0 -5230
  78. package/dist/chunk-KS3F5WSX.js +0 -4831
  79. package/dist/chunk-KUYTQ4FR.js +0 -4808
  80. package/dist/chunk-KVP4CMJ5.js +0 -4711
  81. package/dist/chunk-LAR2I44B.js +0 -5626
  82. package/dist/chunk-LBTHSED2.js +0 -1531
  83. package/dist/chunk-M6OAMYVM.js +0 -5621
  84. package/dist/chunk-MFN5WWOY.js +0 -5132
  85. package/dist/chunk-MMTSKXLK.js +0 -5624
  86. package/dist/chunk-MPUSQLTH.js +0 -5626
  87. package/dist/chunk-MREXDTWL.js +0 -1555
  88. package/dist/chunk-MUEDKRFC.js +0 -5624
  89. package/dist/chunk-NOVPL2JH.js +0 -3327
  90. package/dist/chunk-NQIP4MHV.js +0 -4334
  91. package/dist/chunk-NXXKMYLS.js +0 -5624
  92. package/dist/chunk-OBYNZXNM.js +0 -4756
  93. package/dist/chunk-OFY4HBOJ.js +0 -5624
  94. package/dist/chunk-OJNUEZEK.js +0 -5602
  95. package/dist/chunk-OQCJOMUQ.js +0 -5624
  96. package/dist/chunk-OZH75GY6.js +0 -5132
  97. package/dist/chunk-P3IJVDMZ.js +0 -4700
  98. package/dist/chunk-PMYMYMBH.js +0 -4877
  99. package/dist/chunk-PRELNRVN.js +0 -5623
  100. package/dist/chunk-PSQUSNSI.js +0 -4703
  101. package/dist/chunk-QAIQ5IB6.js +0 -5624
  102. package/dist/chunk-QG22GADV.js +0 -6316
  103. package/dist/chunk-QNE2KGGK.js +0 -3315
  104. package/dist/chunk-RH7ZBSG4.js +0 -5132
  105. package/dist/chunk-RLD5MUCR.js +0 -5626
  106. package/dist/chunk-S42VEBNR.js +0 -3268
  107. package/dist/chunk-SEM6UXU4.js +0 -3324
  108. package/dist/chunk-SI5WP77M.js +0 -4430
  109. package/dist/chunk-SID4SQSY.js +0 -4837
  110. package/dist/chunk-SIELPKWF.js +0 -1558
  111. package/dist/chunk-SVBLY6QT.js +0 -5742
  112. package/dist/chunk-SVFTC5V2.js +0 -6021
  113. package/dist/chunk-SXHTX62B.js +0 -4823
  114. package/dist/chunk-T2YCEA5U.js +0 -4730
  115. package/dist/chunk-TARCHIOU.js +0 -4718
  116. package/dist/chunk-TDACLKD7.js +0 -5867
  117. package/dist/chunk-TGCBM3NP.js +0 -4890
  118. package/dist/chunk-TIWG6KAK.js +0 -4769
  119. package/dist/chunk-TKLKATVM.js +0 -1534
  120. package/dist/chunk-TSLZ4A5P.js +0 -5222
  121. package/dist/chunk-TWSDKORW.js +0 -4698
  122. package/dist/chunk-U5QHYVMJ.js +0 -3341
  123. package/dist/chunk-UAP5CTHB.js +0 -5985
  124. package/dist/chunk-UK6SEUWU.js +0 -3210
  125. package/dist/chunk-UKU5YO65.js +0 -5132
  126. package/dist/chunk-UOZQXP4Q.js +0 -5144
  127. package/dist/chunk-UPTN2TSS.js +0 -4727
  128. package/dist/chunk-UQT2APOE.js +0 -2944
  129. package/dist/chunk-V32QDZKW.js +0 -5132
  130. package/dist/chunk-VDK4XPAC.js +0 -6318
  131. package/dist/chunk-VKY2CDCD.js +0 -5622
  132. package/dist/chunk-VUCSYMCY.js +0 -3323
  133. package/dist/chunk-VVLNBD5Y.js +0 -5132
  134. package/dist/chunk-W3TQ22O6.js +0 -4459
  135. package/dist/chunk-WA4DSGOM.js +0 -3355
  136. package/dist/chunk-WI6MIICK.js +0 -4687
  137. package/dist/chunk-XHYHRJMK.js +0 -6319
  138. package/dist/chunk-XRHJLL74.js +0 -4893
  139. package/dist/chunk-XXWXEBJQ.js +0 -4885
  140. package/dist/chunk-YC6TH2H3.js +0 -5624
  141. package/dist/chunk-YDH6HCUJ.js +0 -5624
  142. package/dist/chunk-YJD35VKQ.js +0 -4890
  143. package/dist/chunk-YTZ5MZLP.js +0 -6318
  144. package/dist/chunk-ZBIQJBY7.js +0 -5620
  145. package/dist/chunk-ZKTSA2AE.js +0 -5629
  146. package/dist/chunk-ZKZZL3PE.js +0 -3379
  147. package/dist/chunk-ZM5KCPRK.js +0 -4541
  148. 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-F4TCYYTD.js";
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, bootstrapPayload) {
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 || bootstrapPayload.llm?.provider || (hasRealLlmConfig ? config.llm?.provider : void 0);
180
- const model2 = process.env.EXAGENT_LLM_MODEL || bootstrapPayload.llm?.model || (hasRealLlmConfig ? config.llm?.model : void 0);
181
- const apiKey2 = process.env.EXAGENT_LLM_KEY || bootstrapPayload.llm?.apiKey || config.llm?.apiKey;
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
- let provider = bootstrapPayload.llm?.provider || (hasRealLlmConfig ? config.llm?.provider : void 0);
189
- if (provider) {
190
- printInfo(`Provider: ${pc.cyan(provider)} ${pc.dim("(from dashboard)")}`);
191
- } else {
192
- const selected = await clack.select({
193
- message: "LLM provider:",
194
- options: LLM_PROVIDERS.map((p) => ({ value: p, label: p }))
195
- });
196
- if (clack.isCancel(selected)) cancelled();
197
- provider = selected;
198
- }
199
- let model = bootstrapPayload.llm?.model || (hasRealLlmConfig ? config.llm?.model : void 0);
200
- if (model) {
201
- printInfo(`Model: ${pc.cyan(model)} ${pc.dim("(from dashboard)")}`);
202
- } else {
203
- const entered = await clack.text({
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 (!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
- }
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 password3 = process.env.EXAGENT_PASSWORD;
241
- if (!password3 || password3.length < 12) {
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 password3;
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 password2 = await clack.password({
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(password2)) cancelled();
256
- const confirm2 = await clack.password({
233
+ if (clack.isCancel(password3)) cancelled();
234
+ const confirm = await clack.password({
257
235
  message: "Confirm password:",
258
236
  validate: (val) => {
259
- if (val !== password2) return "Passwords do not match.";
237
+ if (val !== password3) return "Passwords do not match.";
260
238
  }
261
239
  });
262
- if (clack.isCancel(confirm2)) cancelled();
263
- return password2;
240
+ if (clack.isCancel(confirm)) cancelled();
241
+ return password3;
264
242
  }
265
- function writeSecureStore(path, secrets, password2) {
243
+ function writeSecureStore(path, secrets, password3) {
266
244
  const secureStorePath = expandHomeDir(path);
267
- const encrypted = encryptSecretPayload(secrets, password2);
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 password3 = process.env.EXAGENT_PASSWORD || process.env.EXAGENT_SECRET_PASSWORD;
282
- if (!password3) throw new Error("EXAGENT_PASSWORD required in non-interactive mode");
283
- return password3;
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 password2 = await clack.password({ message: question });
286
- if (clack.isCancel(password2)) cancelled();
287
- return password2;
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, bootstrapPayload);
293
+ const llm = await setupLlm(config);
311
294
  printStep(4, 4, "Device encryption");
312
- const password2 = await setupEncryption();
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
- password2
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")} Start trading`,
354
- `${pc.cyan("npx exagent status")} Check connection`,
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.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
@@ -43,7 +43,7 @@ import {
43
43
  loadConfig,
44
44
  loadStrategy,
45
45
  writeSampleConfig
46
- } from "./chunk-F4TCYYTD.js";
46
+ } from "./chunk-WTECTX2Z.js";
47
47
  export {
48
48
  AcrossAdapter,
49
49
  AerodromeAdapter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exagent/agent",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
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.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,