hedgequantx 2.5.13 → 2.5.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.5.13",
3
+ "version": "2.5.14",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/app.js CHANGED
@@ -235,13 +235,14 @@ const mainMenu = async () => {
235
235
  console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
236
236
 
237
237
  menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
238
- menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] Exit'));
238
+ menuRow(chalk.cyan('[3] Tradovate'), chalk.magenta('[I] AI Agent'));
239
+ menuRow(chalk.red('[X] Exit'), '');
239
240
 
240
241
  console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
241
242
 
242
- const input = await prompts.textInput(chalk.cyan('Select (1/2/3/X)'));
243
+ const input = await prompts.textInput(chalk.cyan('Select (1/2/3/I/X)'));
243
244
 
244
- const actions = { '1': 'projectx', '2': 'rithmic', '3': 'tradovate', 'x': 'exit' };
245
+ const actions = { '1': 'projectx', '2': 'rithmic', '3': 'tradovate', 'i': 'ai_agent', 'x': 'exit' };
245
246
  return actions[(input || '').toLowerCase()] || 'exit';
246
247
  };
247
248
 
@@ -282,6 +283,11 @@ const run = async () => {
282
283
  process.exit(0);
283
284
  }
284
285
 
286
+ if (choice === 'ai_agent') {
287
+ await aiAgentMenu();
288
+ continue;
289
+ }
290
+
285
291
  let service = null;
286
292
  if (choice === 'projectx') service = await projectXMenu();
287
293
  else if (choice === 'rithmic') service = await rithmicMenu();
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * AI Agent Menu
3
- * Configure AI provider connection
3
+ * Configure multiple AI provider connections
4
4
  */
5
5
 
6
6
  const chalk = require('chalk');
@@ -31,62 +31,311 @@ const aiAgentMenu = async () => {
31
31
 
32
32
  console.clear();
33
33
  displayBanner();
34
- drawBoxHeaderContinue('AI AGENT', boxWidth);
34
+ drawBoxHeaderContinue('AI AGENTS', boxWidth);
35
35
 
36
- // Show current status
37
- const connection = aiService.getConnection();
36
+ // Get all connected agents
37
+ const agents = aiService.getAgents();
38
+ const agentCount = agents.length;
38
39
 
39
- if (connection) {
40
- console.log(makeLine(chalk.green('STATUS: CONNECTED'), 'left'));
41
- console.log(makeLine(chalk.white(`PROVIDER: ${connection.provider.name}`), 'left'));
42
- console.log(makeLine(chalk.white(`MODEL: ${connection.model}`), 'left'));
40
+ if (agentCount === 0) {
41
+ console.log(makeLine(chalk.gray('STATUS: NO AGENTS CONNECTED'), 'left'));
43
42
  } else {
44
- console.log(makeLine(chalk.gray('STATUS: NOT CONNECTED'), 'left'));
43
+ console.log(makeLine(chalk.green(`STATUS: ${agentCount} AGENT${agentCount > 1 ? 'S' : ''} CONNECTED`), 'left'));
44
+ console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
45
+
46
+ // List all agents
47
+ for (let i = 0; i < agents.length; i++) {
48
+ const agent = agents[i];
49
+ const activeMarker = agent.isActive ? chalk.yellow(' *') : '';
50
+ const providerColor = agent.providerId === 'anthropic' ? chalk.magenta :
51
+ agent.providerId === 'openai' ? chalk.green :
52
+ agent.providerId === 'openrouter' ? chalk.yellow : chalk.cyan;
53
+
54
+ console.log(makeLine(
55
+ chalk.white(`[${i + 1}] `) +
56
+ providerColor(agent.name) +
57
+ activeMarker +
58
+ chalk.gray(` - ${agent.model}`)
59
+ ));
60
+ }
45
61
  }
46
62
 
47
63
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
48
64
 
49
65
  // Menu options
50
- const options = [];
51
-
52
- if (!connection) {
53
- options.push({ label: chalk.green('[1] CONNECT PROVIDER'), value: 'connect' });
54
- } else {
55
- options.push({ label: chalk.cyan('[1] CHANGE PROVIDER'), value: 'connect' });
56
- options.push({ label: chalk.yellow('[2] CHANGE MODEL'), value: 'model' });
57
- options.push({ label: chalk.red('[3] DISCONNECT'), value: 'disconnect' });
66
+ console.log(makeLine(chalk.green('[+] ADD NEW AGENT')));
67
+
68
+ if (agentCount > 0) {
69
+ console.log(makeLine(chalk.cyan('[S] SET ACTIVE AGENT')));
70
+ console.log(makeLine(chalk.yellow('[M] CHANGE MODEL')));
71
+ console.log(makeLine(chalk.red('[R] REMOVE AGENT')));
72
+ if (agentCount > 1) {
73
+ console.log(makeLine(chalk.red('[X] REMOVE ALL')));
74
+ }
58
75
  }
59
- options.push({ label: chalk.gray('[<] BACK'), value: 'back' });
60
76
 
61
- for (const opt of options) {
62
- console.log(makeLine(opt.label, 'left'));
63
- }
77
+ console.log(makeLine(chalk.gray('[<] BACK')));
64
78
 
65
79
  drawBoxFooter(boxWidth);
66
80
 
67
81
  const choice = await prompts.textInput(chalk.cyan('SELECT:'));
82
+ const input = (choice || '').toLowerCase();
68
83
 
69
- switch (choice?.toLowerCase()) {
70
- case '1':
84
+ // Handle number input (select agent for details)
85
+ const num = parseInt(choice);
86
+ if (!isNaN(num) && num >= 1 && num <= agentCount) {
87
+ return await showAgentDetails(agents[num - 1]);
88
+ }
89
+
90
+ switch (input) {
91
+ case '+':
71
92
  return await showExistingTokens();
72
- case '2':
73
- if (connection) {
74
- return await selectModel(connection.provider);
93
+ case 's':
94
+ if (agentCount > 1) {
95
+ return await selectActiveAgent();
75
96
  }
76
- return;
77
- case '3':
78
- if (connection) {
79
- aiService.disconnect();
80
- console.log(chalk.yellow('\n AI AGENT DISCONNECTED'));
97
+ return await aiAgentMenu();
98
+ case 'm':
99
+ if (agentCount > 0) {
100
+ return await selectAgentForModelChange();
101
+ }
102
+ return await aiAgentMenu();
103
+ case 'r':
104
+ if (agentCount > 0) {
105
+ return await selectAgentToRemove();
106
+ }
107
+ return await aiAgentMenu();
108
+ case 'x':
109
+ if (agentCount > 1) {
110
+ aiService.disconnectAll();
111
+ console.log(chalk.yellow('\n ALL AGENTS REMOVED'));
81
112
  await prompts.waitForEnter();
82
113
  }
83
- return;
114
+ return await aiAgentMenu();
84
115
  case '<':
85
116
  case 'b':
86
117
  return;
87
118
  default:
88
- return;
119
+ return await aiAgentMenu();
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Show agent details
125
+ */
126
+ const showAgentDetails = async (agent) => {
127
+ const boxWidth = getLogoWidth();
128
+ const W = boxWidth - 2;
129
+
130
+ const makeLine = (content) => {
131
+ const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
132
+ const padding = W - plainLen;
133
+ return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
134
+ };
135
+
136
+ console.clear();
137
+ displayBanner();
138
+ drawBoxHeaderContinue('AGENT DETAILS', boxWidth);
139
+
140
+ const providerColor = agent.providerId === 'anthropic' ? chalk.magenta :
141
+ agent.providerId === 'openai' ? chalk.green :
142
+ agent.providerId === 'openrouter' ? chalk.yellow : chalk.cyan;
143
+
144
+ console.log(makeLine(chalk.white('NAME: ') + providerColor(agent.name)));
145
+ console.log(makeLine(chalk.white('PROVIDER: ') + chalk.gray(agent.provider?.name || agent.providerId)));
146
+ console.log(makeLine(chalk.white('MODEL: ') + chalk.gray(agent.model)));
147
+ console.log(makeLine(chalk.white('STATUS: ') + (agent.isActive ? chalk.green('ACTIVE') : chalk.gray('STANDBY'))));
148
+
149
+ console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
150
+
151
+ if (!agent.isActive) {
152
+ console.log(makeLine(chalk.cyan('[A] SET AS ACTIVE')));
153
+ }
154
+ console.log(makeLine(chalk.yellow('[M] CHANGE MODEL')));
155
+ console.log(makeLine(chalk.red('[R] REMOVE')));
156
+ console.log(makeLine(chalk.gray('[<] BACK')));
157
+
158
+ drawBoxFooter(boxWidth);
159
+
160
+ const choice = await prompts.textInput(chalk.cyan('SELECT:'));
161
+
162
+ switch ((choice || '').toLowerCase()) {
163
+ case 'a':
164
+ if (!agent.isActive) {
165
+ aiService.setActiveAgent(agent.id);
166
+ console.log(chalk.green(`\n ${agent.name} IS NOW ACTIVE`));
167
+ await prompts.waitForEnter();
168
+ }
169
+ return await aiAgentMenu();
170
+ case 'm':
171
+ return await selectModel(agent);
172
+ case 'r':
173
+ aiService.removeAgent(agent.id);
174
+ console.log(chalk.yellow(`\n ${agent.name} REMOVED`));
175
+ await prompts.waitForEnter();
176
+ return await aiAgentMenu();
177
+ case '<':
178
+ case 'b':
179
+ return await aiAgentMenu();
180
+ default:
181
+ return await aiAgentMenu();
182
+ }
183
+ };
184
+
185
+ /**
186
+ * Select active agent
187
+ */
188
+ const selectActiveAgent = async () => {
189
+ const boxWidth = getLogoWidth();
190
+ const W = boxWidth - 2;
191
+
192
+ const makeLine = (content) => {
193
+ const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
194
+ const padding = W - plainLen;
195
+ return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
196
+ };
197
+
198
+ console.clear();
199
+ displayBanner();
200
+ drawBoxHeaderContinue('SET ACTIVE AGENT', boxWidth);
201
+
202
+ const agents = aiService.getAgents();
203
+
204
+ for (let i = 0; i < agents.length; i++) {
205
+ const agent = agents[i];
206
+ const activeMarker = agent.isActive ? chalk.yellow(' (CURRENT)') : '';
207
+ const providerColor = agent.providerId === 'anthropic' ? chalk.magenta :
208
+ agent.providerId === 'openai' ? chalk.green : chalk.cyan;
209
+
210
+ console.log(makeLine(
211
+ chalk.white(`[${i + 1}] `) + providerColor(agent.name) + activeMarker
212
+ ));
213
+ }
214
+
215
+ console.log(makeLine(''));
216
+ console.log(makeLine(chalk.gray('[<] BACK')));
217
+
218
+ drawBoxFooter(boxWidth);
219
+
220
+ const choice = await prompts.textInput(chalk.cyan('SELECT AGENT:'));
221
+
222
+ if (choice === '<' || choice?.toLowerCase() === 'b') {
223
+ return await aiAgentMenu();
224
+ }
225
+
226
+ const index = parseInt(choice) - 1;
227
+ if (isNaN(index) || index < 0 || index >= agents.length) {
228
+ return await aiAgentMenu();
229
+ }
230
+
231
+ aiService.setActiveAgent(agents[index].id);
232
+ console.log(chalk.green(`\n ${agents[index].name} IS NOW ACTIVE`));
233
+ await prompts.waitForEnter();
234
+ return await aiAgentMenu();
235
+ };
236
+
237
+ /**
238
+ * Select agent to change model
239
+ */
240
+ const selectAgentForModelChange = async () => {
241
+ const agents = aiService.getAgents();
242
+
243
+ if (agents.length === 1) {
244
+ return await selectModel(agents[0]);
245
+ }
246
+
247
+ const boxWidth = getLogoWidth();
248
+ const W = boxWidth - 2;
249
+
250
+ const makeLine = (content) => {
251
+ const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
252
+ const padding = W - plainLen;
253
+ return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
254
+ };
255
+
256
+ console.clear();
257
+ displayBanner();
258
+ drawBoxHeaderContinue('SELECT AGENT TO CHANGE MODEL', boxWidth);
259
+
260
+ for (let i = 0; i < agents.length; i++) {
261
+ const agent = agents[i];
262
+ console.log(makeLine(
263
+ chalk.white(`[${i + 1}] `) + chalk.cyan(agent.name) + chalk.gray(` - ${agent.model}`)
264
+ ));
265
+ }
266
+
267
+ console.log(makeLine(''));
268
+ console.log(makeLine(chalk.gray('[<] BACK')));
269
+
270
+ drawBoxFooter(boxWidth);
271
+
272
+ const choice = await prompts.textInput(chalk.cyan('SELECT AGENT:'));
273
+
274
+ if (choice === '<' || choice?.toLowerCase() === 'b') {
275
+ return await aiAgentMenu();
89
276
  }
277
+
278
+ const index = parseInt(choice) - 1;
279
+ if (isNaN(index) || index < 0 || index >= agents.length) {
280
+ return await aiAgentMenu();
281
+ }
282
+
283
+ return await selectModel(agents[index]);
284
+ };
285
+
286
+ /**
287
+ * Select agent to remove
288
+ */
289
+ const selectAgentToRemove = async () => {
290
+ const agents = aiService.getAgents();
291
+
292
+ if (agents.length === 1) {
293
+ aiService.removeAgent(agents[0].id);
294
+ console.log(chalk.yellow(`\n ${agents[0].name} REMOVED`));
295
+ await prompts.waitForEnter();
296
+ return await aiAgentMenu();
297
+ }
298
+
299
+ const boxWidth = getLogoWidth();
300
+ const W = boxWidth - 2;
301
+
302
+ const makeLine = (content) => {
303
+ const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
304
+ const padding = W - plainLen;
305
+ return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
306
+ };
307
+
308
+ console.clear();
309
+ displayBanner();
310
+ drawBoxHeaderContinue('SELECT AGENT TO REMOVE', boxWidth);
311
+
312
+ for (let i = 0; i < agents.length; i++) {
313
+ const agent = agents[i];
314
+ console.log(makeLine(
315
+ chalk.white(`[${i + 1}] `) + chalk.red(agent.name)
316
+ ));
317
+ }
318
+
319
+ console.log(makeLine(''));
320
+ console.log(makeLine(chalk.gray('[<] BACK')));
321
+
322
+ drawBoxFooter(boxWidth);
323
+
324
+ const choice = await prompts.textInput(chalk.cyan('SELECT AGENT TO REMOVE:'));
325
+
326
+ if (choice === '<' || choice?.toLowerCase() === 'b') {
327
+ return await aiAgentMenu();
328
+ }
329
+
330
+ const index = parseInt(choice) - 1;
331
+ if (isNaN(index) || index < 0 || index >= agents.length) {
332
+ return await aiAgentMenu();
333
+ }
334
+
335
+ aiService.removeAgent(agents[index].id);
336
+ console.log(chalk.yellow(`\n ${agents[index].name} REMOVED`));
337
+ await prompts.waitForEnter();
338
+ return await aiAgentMenu();
90
339
  };
91
340
 
92
341
  // Cache for scanned tokens (avoid multiple Keychain prompts)
@@ -217,11 +466,12 @@ const showExistingTokens = async () => {
217
466
  return await showExistingTokens();
218
467
  }
219
468
 
220
- // Save connection
469
+ // Add as new agent
221
470
  const model = provider.defaultModel;
222
- await aiService.connect(selectedToken.provider, 'api_key', credentials, model);
471
+ const agentName = `${provider.name} (${selectedToken.source})`;
472
+ await aiService.addAgent(selectedToken.provider, 'api_key', credentials, model, agentName);
223
473
 
224
- spinner.succeed(`CONNECTED TO ${provider.name}`);
474
+ spinner.succeed(`AGENT ADDED: ${provider.name}`);
225
475
  console.log(chalk.gray(` SOURCE: ${selectedToken.source}`));
226
476
  console.log(chalk.gray(` MODEL: ${model}`));
227
477
 
@@ -644,11 +894,11 @@ const setupConnection = async (provider, option) => {
644
894
  return await selectProviderOption(provider);
645
895
  }
646
896
 
647
- // Save connection
897
+ // Add as new agent
648
898
  try {
649
899
  const model = credentials.model || provider.defaultModel;
650
- await aiService.connect(provider.id, option.id, credentials, model);
651
- spinner.succeed(`CONNECTED TO ${provider.name}`);
900
+ await aiService.addAgent(provider.id, option.id, credentials, model, provider.name);
901
+ spinner.succeed(`AGENT ADDED: ${provider.name}`);
652
902
 
653
903
  // Show available models for local providers
654
904
  if (validation.models && validation.models.length > 0) {
@@ -665,9 +915,9 @@ const setupConnection = async (provider, option) => {
665
915
  };
666
916
 
667
917
  /**
668
- * Select/change model for current provider
918
+ * Select/change model for an agent
669
919
  */
670
- const selectModel = async (provider) => {
920
+ const selectModel = async (agent) => {
671
921
  const boxWidth = getLogoWidth();
672
922
  const W = boxWidth - 2;
673
923
 
@@ -679,9 +929,9 @@ const selectModel = async (provider) => {
679
929
 
680
930
  console.clear();
681
931
  displayBanner();
682
- drawBoxHeaderContinue('SELECT MODEL', boxWidth);
932
+ drawBoxHeaderContinue(`SELECT MODEL - ${agent.name}`, boxWidth);
683
933
 
684
- const models = provider.models || [];
934
+ const models = agent.provider?.models || [];
685
935
 
686
936
  if (models.length === 0) {
687
937
  console.log(makeLine(chalk.gray('NO PREDEFINED MODELS. ENTER MODEL NAME MANUALLY.')));
@@ -693,9 +943,7 @@ const selectModel = async (provider) => {
693
943
  if (!model || model === '<') {
694
944
  return await aiAgentMenu();
695
945
  }
696
- const settings = aiService.getAISettings();
697
- settings.model = model;
698
- aiService.saveAISettings(settings);
946
+ aiService.updateAgent(agent.id, { model });
699
947
  console.log(chalk.green(`\n MODEL CHANGED TO: ${model}`));
700
948
  await prompts.waitForEnter();
701
949
  return await aiAgentMenu();
@@ -704,7 +952,8 @@ const selectModel = async (provider) => {
704
952
  models.forEach((model, index) => {
705
953
  // Truncate long model names
706
954
  const displayModel = model.length > W - 10 ? model.substring(0, W - 13) + '...' : model;
707
- console.log(makeLine(chalk.cyan(`[${index + 1}] ${displayModel}`)));
955
+ const currentMarker = model === agent.model ? chalk.yellow(' (CURRENT)') : '';
956
+ console.log(makeLine(chalk.cyan(`[${index + 1}] ${displayModel}`) + currentMarker));
708
957
  });
709
958
 
710
959
  console.log(makeLine(''));
@@ -724,9 +973,7 @@ const selectModel = async (provider) => {
724
973
  }
725
974
 
726
975
  const selectedModel = models[index];
727
- const settings = aiService.getAISettings();
728
- settings.model = selectedModel;
729
- aiService.saveAISettings(settings);
976
+ aiService.updateAgent(agent.id, { model: selectedModel });
730
977
 
731
978
  console.log(chalk.green(`\n MODEL CHANGED TO: ${selectedModel}`));
732
979
  await prompts.waitForEnter();
@@ -12,6 +12,7 @@ const { getCachedStats } = require('../services/stats-cache');
12
12
  const { prompts } = require('../utils');
13
13
  const aiService = require('../services/ai');
14
14
 
15
+
15
16
  /**
16
17
  * Dashboard menu after login
17
18
  */
@@ -49,32 +50,27 @@ const dashboardMenu = async (service) => {
49
50
  if (statsInfo) {
50
51
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
51
52
 
52
- const balStr = statsInfo.balance !== null ? `$${statsInfo.balance.toLocaleString()}` : '--';
53
+ const balStr = statsInfo.balance !== null ? `$${statsInfo.balance.toLocaleString()}` : '--';
53
54
  const balColor = statsInfo.balance !== null ? chalk.green : chalk.gray;
54
55
 
55
- // AI status
56
- const aiConnection = aiService.getConnection();
57
- const aiStatus = aiConnection
58
- ? aiConnection.provider.name.split(' ')[0] // Just "CLAUDE" or "OPENAI"
59
- : null;
60
-
61
- // Build plain text for length calculation (unicode ✔ and ○ are 1 char width each)
62
- // Format: "✔ CONNECTIONS: X ✔ ACCOUNTS: X ✔ BALANCE: $X ○ AI: NONE"
63
- const plainText = `* CONNECTIONS: ${statsInfo.connections} * ACCOUNTS: ${statsInfo.accounts} * BALANCE: ${balStr} * AI: ${aiStatus || 'NONE'}`;
56
+ // Build plain text for length calculation
57
+ // Format: "✔ CONNECTIONS: X ✔ ACCOUNTS: X ✔ BALANCE: $X ✔ AI: CONNECTED"
58
+ const aiText = aiConnected ? 'CONNECTED' : 'NONE';
59
+ const plainText = `* CONNECTIONS: ${statsInfo.connections} * ACCOUNTS: ${statsInfo.accounts} * BALANCE: ${balStr} * AI: ${aiText}`;
64
60
  const statsLen = plainText.length;
65
61
  const statsLeftPad = Math.max(0, Math.floor((W - statsLen) / 2));
66
62
  const statsRightPad = Math.max(0, W - statsLen - statsLeftPad);
67
63
 
68
64
  // Build with unicode icons and colors
69
65
  const checkIcon = chalk.yellow('✔ ');
70
- const aiIcon = aiStatus ? chalk.magenta('✔ ') : chalk.gray('○ ');
71
- const aiText = aiStatus ? chalk.magenta(aiStatus) : chalk.gray('NONE');
66
+ const aiIcon = aiConnected ? chalk.magenta('✔ ') : chalk.gray('○ ');
67
+ const aiTextColored = aiConnected ? chalk.magenta('CONNECTED') : chalk.gray('NONE');
72
68
 
73
69
  console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
74
70
  checkIcon + chalk.white(`CONNECTIONS: ${statsInfo.connections}`) + ' ' +
75
71
  checkIcon + chalk.white(`ACCOUNTS: ${statsInfo.accounts}`) + ' ' +
76
72
  checkIcon + chalk.white('BALANCE: ') + balColor(balStr) + ' ' +
77
- aiIcon + chalk.white('AI: ') + aiText +
73
+ aiIcon + chalk.white('AI: ') + aiTextColored +
78
74
  ' '.repeat(statsRightPad) + chalk.cyan('║'));
79
75
  }
80
76