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