@exagent/agent 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/dist/{chunk-VDK4XPAC.js → chunk-WTECTX2Z.js} +31 -4
  2. package/dist/cli.js +212 -77
  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 +19 -2
  7. package/src/runtime.ts +18 -2
  8. package/src/setup.ts +118 -67
  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-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-XRHJLL74.js +0 -4893
  138. package/dist/chunk-XXWXEBJQ.js +0 -4885
  139. package/dist/chunk-YC6TH2H3.js +0 -5624
  140. package/dist/chunk-YDH6HCUJ.js +0 -5624
  141. package/dist/chunk-YJD35VKQ.js +0 -4890
  142. package/dist/chunk-ZBIQJBY7.js +0 -5620
  143. package/dist/chunk-ZKTSA2AE.js +0 -5629
  144. package/dist/chunk-ZKZZL3PE.js +0 -3379
  145. package/dist/chunk-ZM5KCPRK.js +0 -4541
  146. package/dist/chunk-ZTYPDSE3.js +0 -3258
package/src/runtime.ts CHANGED
@@ -58,6 +58,21 @@ try { SDK_VERSION = _require('../package.json').version; } catch {}
58
58
  /** Number of consecutive cycle failures before switching to idle */
59
59
  const MAX_CONSECUTIVE_FAILURES = 3;
60
60
 
61
+ function getCycleErrorHint(category: string, msg: string): string | null {
62
+ const lower = msg.toLowerCase();
63
+ if (category === 'auth' || lower.includes('401') || lower.includes('unauthorized') || lower.includes('invalid api key') || lower.includes('authentication'))
64
+ return 'Check your LLM API key — update via npx exagent setup';
65
+ if (category === 'network' || lower.includes('econnrefused') || lower.includes('enotfound') || lower.includes('fetch failed') || lower.includes('timeout'))
66
+ return 'Network connectivity issue — check your internet connection';
67
+ if (category === 'venue' || lower.includes('hyperliquid') || lower.includes('polymarket') || lower.includes('venue'))
68
+ return 'Venue API error — the venue may be experiencing issues';
69
+ if (category === 'strategy' || lower.includes('invalid') && lower.includes('signal') || lower.includes('strategy'))
70
+ return 'Strategy returned invalid output — check your strategy file';
71
+ if (lower.includes('rate limit') || lower.includes('429'))
72
+ return 'Rate limited — consider increasing tradingIntervalMs';
73
+ return null;
74
+ }
75
+
61
76
  export class AgentRuntime {
62
77
  private config: RuntimeConfig;
63
78
  private relay: RelayClient;
@@ -809,12 +824,13 @@ export class AgentRuntime {
809
824
  timings.totalMs = Date.now() - cycleStart;
810
825
 
811
826
  const categorized = this.diagnostics.recordError(err as Error);
812
- log.error('cycle', `Cycle error: ${errMsg}`, {
827
+ const hint = getCycleErrorHint(categorized.category, errMsg);
828
+ log.error('cycle', `Cycle error: ${errMsg}${hint ? ` — ${hint}` : ''}`, {
813
829
  cycle: this.cycleCount,
814
830
  totalMs: timings.totalMs,
815
831
  category: categorized.category,
816
832
  });
817
- this.signal.reportError('Cycle Error', errMsg);
833
+ this.signal.reportError('Cycle Error', `${errMsg}${hint ? ` — ${hint}` : ''}`);
818
834
 
819
835
  this.diagnostics.recordCycle({
820
836
  cycleNumber: this.cycleCount,
package/src/setup.ts CHANGED
@@ -33,6 +33,24 @@ function cancelled(): never {
33
33
  process.exit(0);
34
34
  }
35
35
 
36
+ function isNonInteractive(): boolean {
37
+ return process.env.EXAGENT_NONINTERACTIVE === '1' || process.env.EXAGENT_NONINTERACTIVE === 'true';
38
+ }
39
+
40
+ const LLM_KEY_PREFIXES: Record<string, string> = {
41
+ openai: 'sk-',
42
+ anthropic: 'sk-ant-',
43
+ };
44
+
45
+ function validateLlmKeyFormat(provider: string, key: string): string | undefined {
46
+ if (!key.trim()) return 'API key is required.';
47
+ const expectedPrefix = LLM_KEY_PREFIXES[provider];
48
+ if (expectedPrefix && !key.startsWith(expectedPrefix)) {
49
+ return `${provider} API keys typically start with "${expectedPrefix}". Double-check your key.`;
50
+ }
51
+ if (key.length < 10) return 'API key seems too short.';
52
+ }
53
+
36
54
  // ---------------------------------------------------------------------------
37
55
  // Step 1: Bootstrap
38
56
  // ---------------------------------------------------------------------------
@@ -74,6 +92,24 @@ async function setupWallet(config: RuntimeConfigFile): Promise<string> {
74
92
  return config.wallet.privateKey;
75
93
  }
76
94
 
95
+ // Non-interactive: env var wallet
96
+ if (isNonInteractive()) {
97
+ const mode = process.env.EXAGENT_WALLET_MODE || 'generate';
98
+ if (mode === 'import') {
99
+ const key = process.env.EXAGENT_WALLET_KEY;
100
+ if (!key || !/^0x[a-fA-F0-9]{64}$/.test(key)) {
101
+ throw new Error('EXAGENT_WALLET_KEY must be a valid 0x-prefixed 64-char hex private key in non-interactive mode');
102
+ }
103
+ const address = privateKeyToAccount(key as `0x${string}`).address;
104
+ printDone(`Wallet imported: ${pc.dim(address)}`);
105
+ return key;
106
+ }
107
+ const privateKey = generatePrivateKey();
108
+ const address = privateKeyToAccount(privateKey).address;
109
+ printDone(`Wallet created: ${pc.dim(address)}`);
110
+ return privateKey;
111
+ }
112
+
77
113
  const method = await clack.select({
78
114
  message: 'How would you like to set up your wallet?',
79
115
  options: [
@@ -114,62 +150,52 @@ const LLM_PROVIDERS = ['openai', 'anthropic', 'google', 'deepseek', 'mistral', '
114
150
 
115
151
  async function setupLlm(
116
152
  config: RuntimeConfigFile,
117
- bootstrapPayload: BootstrapPayload,
118
153
  ): Promise<{ provider: string; model: string; apiKey: string }> {
119
- // Provider
120
- let provider = config.llm?.provider || bootstrapPayload.llm?.provider;
121
- if (provider) {
122
- printInfo(`Provider: ${pc.cyan(provider)} ${pc.dim('(from dashboard)')}`);
123
- } else {
124
- const selected = await clack.select({
125
- message: 'LLM provider:',
126
- options: LLM_PROVIDERS.map(p => ({ value: p, label: p })),
127
- });
128
- if (clack.isCancel(selected)) cancelled();
129
- provider = selected;
154
+ // LLM config is always entered locally — never pulled from bootstrap.
155
+ // Config file may have provider/model as defaults from the deploy wizard.
156
+
157
+ // Non-interactive mode
158
+ if (isNonInteractive()) {
159
+ const provider = process.env.EXAGENT_LLM_PROVIDER || config.llm?.provider;
160
+ const model = process.env.EXAGENT_LLM_MODEL || config.llm?.model;
161
+ const apiKey = process.env.EXAGENT_LLM_KEY;
162
+ if (!provider) throw new Error('EXAGENT_LLM_PROVIDER required in non-interactive mode');
163
+ if (!model) throw new Error('EXAGENT_LLM_MODEL required in non-interactive mode');
164
+ if (!apiKey) throw new Error('EXAGENT_LLM_KEY required in non-interactive mode');
165
+ printDone('LLM configured');
166
+ return { provider, model, apiKey };
130
167
  }
131
168
 
132
- // Model
133
- let model = config.llm?.model || bootstrapPayload.llm?.model;
134
- if (model) {
135
- printInfo(`Model: ${pc.cyan(model)} ${pc.dim('(from dashboard)')}`);
136
- } else {
137
- const entered = await clack.text({
138
- message: 'LLM model:',
139
- placeholder: 'gpt-4o',
140
- validate: (val) => {
141
- if (!val.trim()) return 'Model name is required.';
142
- },
143
- });
144
- if (clack.isCancel(entered)) cancelled();
145
- model = entered;
146
- }
169
+ // Provider — use config as default selection if available
170
+ const defaultProvider = config.llm?.provider;
171
+ const providerOptions = LLM_PROVIDERS.map(p => ({ value: p, label: p }));
172
+ const selected = await clack.select({
173
+ message: 'LLM provider:',
174
+ options: providerOptions,
175
+ initialValue: defaultProvider || undefined,
176
+ });
177
+ if (clack.isCancel(selected)) cancelled();
178
+ const provider = selected;
179
+
180
+ // Model — use config as default/placeholder if available
181
+ const defaultModel = config.llm?.model;
182
+ const entered = await clack.text({
183
+ message: 'LLM model:',
184
+ placeholder: defaultModel || 'gpt-4o',
185
+ defaultValue: defaultModel || undefined,
186
+ validate: (val) => {
187
+ if (!val.trim()) return 'Model name is required.';
188
+ },
189
+ });
190
+ if (clack.isCancel(entered)) cancelled();
191
+ const model = entered;
147
192
 
148
- // API Key
149
- let apiKey: string | undefined;
150
- if (bootstrapPayload.llm?.apiKey) {
151
- const useBootstrap = await clack.confirm({
152
- message: 'LLM API key received from dashboard. Use it?',
153
- initialValue: true,
154
- });
155
- if (clack.isCancel(useBootstrap)) cancelled();
156
- if (useBootstrap) {
157
- apiKey = bootstrapPayload.llm.apiKey;
158
- }
159
- }
160
- if (!apiKey) {
161
- apiKey = config.llm?.apiKey;
162
- }
163
- if (!apiKey) {
164
- const entered = await clack.password({
165
- message: 'LLM API key:',
166
- validate: (val) => {
167
- if (!val.trim()) return 'API key is required.';
168
- },
169
- });
170
- if (clack.isCancel(entered)) cancelled();
171
- apiKey = entered;
172
- }
193
+ // API Key — always prompt, never from bootstrap
194
+ const apiKey = await clack.password({
195
+ message: 'LLM API key:',
196
+ validate: (val) => validateLlmKeyFormat(provider, val),
197
+ });
198
+ if (clack.isCancel(apiKey)) cancelled();
173
199
 
174
200
  printDone('LLM configured');
175
201
  return { provider, model, apiKey };
@@ -180,6 +206,15 @@ async function setupLlm(
180
206
  // ---------------------------------------------------------------------------
181
207
 
182
208
  async function setupEncryption(): Promise<string> {
209
+ // Non-interactive mode
210
+ if (isNonInteractive()) {
211
+ const password = process.env.EXAGENT_PASSWORD;
212
+ if (!password || password.length < 12) {
213
+ throw new Error('EXAGENT_PASSWORD must be at least 12 characters in non-interactive mode');
214
+ }
215
+ return password;
216
+ }
217
+
183
218
  printInfo(`Secrets encrypted with ${pc.cyan('AES-256-GCM')} (${pc.cyan('scrypt')} KDF)`);
184
219
  printInfo('The password never leaves this machine.');
185
220
  console.log();
@@ -228,6 +263,11 @@ function writeSecureStore(path: string, secrets: LocalSecretPayload, password: s
228
263
  // ---------------------------------------------------------------------------
229
264
 
230
265
  export async function promptSecretPassword(question: string = 'Device password:'): Promise<string> {
266
+ if (isNonInteractive()) {
267
+ const password = process.env.EXAGENT_PASSWORD || process.env.EXAGENT_SECRET_PASSWORD;
268
+ if (!password) throw new Error('EXAGENT_PASSWORD required in non-interactive mode');
269
+ return password;
270
+ }
231
271
  const password = await clack.password({ message: question });
232
272
  if (clack.isCancel(password)) cancelled();
233
273
  return password;
@@ -248,6 +288,14 @@ export async function ensureLocalSetup(configPath: string): Promise<void> {
248
288
  !config.wallet?.privateKey &&
249
289
  !config.llm.apiKey
250
290
  ) {
291
+ printBanner();
292
+ printSuccess('Already set up', [
293
+ `${pc.cyan('npx exagent run')} Start the agent`,
294
+ `${pc.cyan('npx exagent config')} Change LLM API key or model`,
295
+ `${pc.cyan('npx exagent status')} Check agent connection`,
296
+ '',
297
+ `${pc.dim('Dashboard:')} ${pc.cyan('https://exagent.io')}`,
298
+ ]);
251
299
  return;
252
300
  }
253
301
 
@@ -260,9 +308,6 @@ export async function ensureLocalSetup(configPath: string): Promise<void> {
260
308
  const bootstrapPayload = await consumeBootstrapPackage(config);
261
309
  if (config.secrets?.bootstrapToken) {
262
310
  printDone('Bootstrap package consumed');
263
- if (bootstrapPayload.llm?.provider) {
264
- printInfo(`LLM config received: ${pc.cyan(bootstrapPayload.llm.provider)}${bootstrapPayload.llm.model ? ` / ${pc.cyan(bootstrapPayload.llm.model)}` : ''}`);
265
- }
266
311
  } else {
267
312
  printInfo('No bootstrap token — manual configuration');
268
313
  }
@@ -273,7 +318,7 @@ export async function ensureLocalSetup(configPath: string): Promise<void> {
273
318
 
274
319
  // Step 3: LLM
275
320
  printStep(3, 4, 'LLM configuration');
276
- const llm = await setupLlm(config, bootstrapPayload);
321
+ const llm = await setupLlm(config);
277
322
 
278
323
  // Step 4: Encryption
279
324
  printStep(4, 4, 'Device encryption');
@@ -287,15 +332,20 @@ export async function ensureLocalSetup(configPath: string): Promise<void> {
287
332
  };
288
333
 
289
334
  if (!secrets.apiToken) {
290
- // Prompt for relay token if not available from bootstrap or config
291
- const token = await clack.password({
292
- message: 'Agent relay token:',
293
- validate: (val) => {
294
- if (!val.trim()) return 'Relay token is required.';
295
- },
296
- });
297
- if (clack.isCancel(token)) cancelled();
298
- secrets.apiToken = token;
335
+ if (isNonInteractive()) {
336
+ const token = process.env.EXAGENT_API_TOKEN;
337
+ if (!token) throw new Error('EXAGENT_API_TOKEN required in non-interactive mode (no relay token from bootstrap)');
338
+ secrets.apiToken = token;
339
+ } else {
340
+ const token = await clack.password({
341
+ message: 'Agent relay token:',
342
+ validate: (val) => {
343
+ if (!val.trim()) return 'Relay token is required.';
344
+ },
345
+ });
346
+ if (clack.isCancel(token)) cancelled();
347
+ secrets.apiToken = token;
348
+ }
299
349
  }
300
350
 
301
351
  const nextConfig = structuredClone(config);
@@ -324,8 +374,9 @@ export async function ensureLocalSetup(configPath: string): Promise<void> {
324
374
  clack.outro(pc.green('Setup complete'));
325
375
 
326
376
  printSuccess('Ready', [
327
- `${pc.cyan('npx exagent run')} Start trading`,
328
- `${pc.cyan('npx exagent status')} Check connection`,
377
+ `${pc.cyan('npx exagent run')} Start the agent`,
378
+ `${pc.cyan('npx exagent config')} Change LLM API key or model`,
379
+ `${pc.cyan('npx exagent status')} Check agent connection`,
329
380
  '',
330
381
  `${pc.dim('Dashboard:')} ${pc.cyan('https://exagent.io')}`,
331
382
  ]);