hedgequantx 2.5.1 → 2.5.3

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.1",
3
+ "version": "2.5.3",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -6,7 +6,7 @@
6
6
  const chalk = require('chalk');
7
7
  const ora = require('ora');
8
8
 
9
- const { getLogoWidth, drawBoxHeader, drawBoxFooter } = require('../ui');
9
+ const { getLogoWidth, drawBoxHeader, drawBoxFooter, displayBanner } = require('../ui');
10
10
  const { prompts } = require('../utils');
11
11
  const aiService = require('../services/ai');
12
12
  const { getCategories, getProvidersByCategory } = require('../services/ai/providers');
@@ -29,6 +29,7 @@ const aiAgentMenu = async () => {
29
29
  };
30
30
 
31
31
  console.clear();
32
+ displayBanner();
32
33
  drawBoxHeader('AI AGENT', boxWidth);
33
34
 
34
35
  // Show current status
@@ -93,6 +94,7 @@ const aiAgentMenu = async () => {
93
94
  const selectCategory = async () => {
94
95
  const boxWidth = getLogoWidth();
95
96
  const W = boxWidth - 2;
97
+ const col1Width = Math.floor(W / 2);
96
98
 
97
99
  const makeLine = (content) => {
98
100
  const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
@@ -100,19 +102,39 @@ const selectCategory = async () => {
100
102
  return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
101
103
  };
102
104
 
105
+ const make2ColRow = (left, right) => {
106
+ const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '').length;
107
+ const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '').length;
108
+ const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain - 1));
109
+ const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain));
110
+ return chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║');
111
+ };
112
+
103
113
  console.clear();
114
+ displayBanner();
104
115
  drawBoxHeader('SELECT PROVIDER TYPE', boxWidth);
105
116
 
106
117
  const categories = getCategories();
107
118
 
108
- categories.forEach((cat, index) => {
109
- const color = cat.id === 'unified' ? chalk.green :
110
- cat.id === 'local' ? chalk.yellow : chalk.cyan;
111
- console.log(makeLine(color(`[${index + 1}] ${cat.name}`)));
112
- console.log(makeLine(chalk.gray(' ' + cat.description)));
113
- console.log(makeLine(''));
114
- });
115
-
119
+ // Display in 2 columns
120
+ console.log(make2ColRow(
121
+ chalk.green('[1] UNIFIED (RECOMMENDED)'),
122
+ chalk.cyan('[2] DIRECT PROVIDERS')
123
+ ));
124
+ console.log(make2ColRow(
125
+ chalk.gray(' 1 API = 100+ models'),
126
+ chalk.gray(' Connect to each provider')
127
+ ));
128
+ console.log(makeLine(''));
129
+ console.log(make2ColRow(
130
+ chalk.yellow('[3] LOCAL (FREE)'),
131
+ chalk.gray('[4] CUSTOM')
132
+ ));
133
+ console.log(make2ColRow(
134
+ chalk.gray(' Run on your machine'),
135
+ chalk.gray(' Self-hosted solutions')
136
+ ));
137
+ console.log(makeLine(''));
116
138
  console.log(makeLine(chalk.gray('[<] BACK')));
117
139
 
118
140
  drawBoxFooter(boxWidth);
@@ -138,6 +160,7 @@ const selectCategory = async () => {
138
160
  const selectProvider = async (categoryId) => {
139
161
  const boxWidth = getLogoWidth();
140
162
  const W = boxWidth - 2;
163
+ const col1Width = Math.floor(W / 2);
141
164
 
142
165
  const makeLine = (content) => {
143
166
  const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
@@ -145,7 +168,16 @@ const selectProvider = async (categoryId) => {
145
168
  return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
146
169
  };
147
170
 
171
+ const make2ColRow = (left, right) => {
172
+ const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '').length;
173
+ const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '').length;
174
+ const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain - 1));
175
+ const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain));
176
+ return chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║');
177
+ };
178
+
148
179
  console.clear();
180
+ displayBanner();
149
181
 
150
182
  const categories = getCategories();
151
183
  const category = categories.find(c => c.id === categoryId);
@@ -154,30 +186,44 @@ const selectProvider = async (categoryId) => {
154
186
  const providers = getProvidersByCategory(categoryId);
155
187
 
156
188
  if (providers.length === 0) {
157
- console.log(makeLine(chalk.gray('No providers in this category')));
189
+ console.log(makeLine(chalk.gray('NO PROVIDERS IN THIS CATEGORY')));
158
190
  drawBoxFooter(boxWidth);
159
191
  await prompts.waitForEnter();
160
192
  return await selectCategory();
161
193
  }
162
194
 
163
- // Display providers
164
- providers.forEach((provider, index) => {
165
- const isRecommended = provider.id === 'openrouter';
166
- const color = isRecommended ? chalk.green : chalk.cyan;
167
- console.log(makeLine(color(`[${index + 1}] ${provider.name}`)));
168
- console.log(makeLine(chalk.gray(' ' + provider.description)));
169
- if (provider.models && provider.models.length > 0) {
170
- const modelList = provider.models.slice(0, 3).join(', ');
171
- console.log(makeLine(chalk.gray(' Models: ' + modelList + (provider.models.length > 3 ? '...' : ''))));
172
- }
195
+ // Display providers in 2 columns
196
+ for (let i = 0; i < providers.length; i += 2) {
197
+ const left = providers[i];
198
+ const right = providers[i + 1];
199
+
200
+ // Provider names
201
+ const leftName = `[${i + 1}] ${left.name}`;
202
+ const rightName = right ? `[${i + 2}] ${right.name}` : '';
203
+
204
+ console.log(make2ColRow(
205
+ chalk.cyan(leftName.length > col1Width - 3 ? leftName.substring(0, col1Width - 6) + '...' : leftName),
206
+ right ? chalk.cyan(rightName.length > col1Width - 3 ? rightName.substring(0, col1Width - 6) + '...' : rightName) : ''
207
+ ));
208
+
209
+ // Descriptions (truncated)
210
+ const leftDesc = ' ' + left.description;
211
+ const rightDesc = right ? ' ' + right.description : '';
212
+
213
+ console.log(make2ColRow(
214
+ chalk.gray(leftDesc.length > col1Width - 3 ? leftDesc.substring(0, col1Width - 6) + '...' : leftDesc),
215
+ chalk.gray(rightDesc.length > col1Width - 3 ? rightDesc.substring(0, col1Width - 6) + '...' : rightDesc)
216
+ ));
217
+
173
218
  console.log(makeLine(''));
174
- });
219
+ }
175
220
 
176
221
  console.log(makeLine(chalk.gray('[<] BACK')));
177
222
 
178
223
  drawBoxFooter(boxWidth);
179
224
 
180
- const choice = await prompts.textInput(chalk.cyan('SELECT PROVIDER:'));
225
+ const maxNum = providers.length;
226
+ const choice = await prompts.textInput(chalk.cyan(`SELECT (1-${maxNum}):`));
181
227
 
182
228
  if (choice === '<' || choice?.toLowerCase() === 'b') {
183
229
  return await selectCategory();
@@ -198,6 +244,7 @@ const selectProvider = async (categoryId) => {
198
244
  const selectProviderOption = async (provider) => {
199
245
  const boxWidth = getLogoWidth();
200
246
  const W = boxWidth - 2;
247
+ const col1Width = Math.floor(W / 2);
201
248
 
202
249
  const makeLine = (content) => {
203
250
  const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
@@ -205,24 +252,57 @@ const selectProviderOption = async (provider) => {
205
252
  return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
206
253
  };
207
254
 
255
+ const make2ColRow = (left, right) => {
256
+ const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '').length;
257
+ const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '').length;
258
+ const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain - 1));
259
+ const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain));
260
+ return chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║');
261
+ };
262
+
208
263
  // If only one option, skip selection
209
264
  if (provider.options.length === 1) {
210
265
  return await setupConnection(provider, provider.options[0]);
211
266
  }
212
267
 
213
268
  console.clear();
269
+ displayBanner();
214
270
  drawBoxHeader(provider.name, boxWidth);
215
271
 
216
272
  console.log(makeLine(chalk.white('SELECT CONNECTION METHOD:')));
217
273
  console.log(makeLine(''));
218
274
 
219
- provider.options.forEach((option, index) => {
220
- console.log(makeLine(chalk.cyan(`[${index + 1}] ${option.label}`)));
221
- option.description.forEach(desc => {
222
- console.log(makeLine(chalk.gray(' ' + desc)));
223
- });
275
+ // Display options in 2 columns
276
+ for (let i = 0; i < provider.options.length; i += 2) {
277
+ const left = provider.options[i];
278
+ const right = provider.options[i + 1];
279
+
280
+ // Option labels
281
+ console.log(make2ColRow(
282
+ chalk.cyan(`[${i + 1}] ${left.label}`),
283
+ right ? chalk.cyan(`[${i + 2}] ${right.label}`) : ''
284
+ ));
285
+
286
+ // First description line
287
+ const leftDesc1 = left.description[0] ? ' ' + left.description[0] : '';
288
+ const rightDesc1 = right?.description[0] ? ' ' + right.description[0] : '';
289
+ console.log(make2ColRow(
290
+ chalk.gray(leftDesc1.length > col1Width - 2 ? leftDesc1.substring(0, col1Width - 5) + '...' : leftDesc1),
291
+ chalk.gray(rightDesc1.length > col1Width - 2 ? rightDesc1.substring(0, col1Width - 5) + '...' : rightDesc1)
292
+ ));
293
+
294
+ // Second description line if exists
295
+ const leftDesc2 = left.description[1] ? ' ' + left.description[1] : '';
296
+ const rightDesc2 = right?.description[1] ? ' ' + right.description[1] : '';
297
+ if (leftDesc2 || rightDesc2) {
298
+ console.log(make2ColRow(
299
+ chalk.gray(leftDesc2.length > col1Width - 2 ? leftDesc2.substring(0, col1Width - 5) + '...' : leftDesc2),
300
+ chalk.gray(rightDesc2.length > col1Width - 2 ? rightDesc2.substring(0, col1Width - 5) + '...' : rightDesc2)
301
+ ));
302
+ }
303
+
224
304
  console.log(makeLine(''));
225
- });
305
+ }
226
306
 
227
307
  console.log(makeLine(chalk.gray('[<] BACK')));
228
308
 
@@ -257,6 +337,7 @@ const setupConnection = async (provider, option) => {
257
337
  };
258
338
 
259
339
  console.clear();
340
+ displayBanner();
260
341
  drawBoxHeader(`CONNECT TO ${provider.name}`, boxWidth);
261
342
 
262
343
  // Show instructions
@@ -266,6 +347,8 @@ const setupConnection = async (provider, option) => {
266
347
  console.log(makeLine(''));
267
348
  }
268
349
 
350
+ console.log(makeLine(chalk.gray('TYPE < TO GO BACK')));
351
+
269
352
  drawBoxFooter(boxWidth);
270
353
  console.log();
271
354
 
@@ -277,33 +360,34 @@ const setupConnection = async (provider, option) => {
277
360
 
278
361
  switch (field) {
279
362
  case 'apiKey':
280
- value = await prompts.passwordInput('ENTER API KEY:');
281
- if (!value) return await selectProviderOption(provider);
363
+ value = await prompts.textInput('ENTER API KEY (OR < TO GO BACK):');
364
+ if (!value || value === '<') return await selectProviderOption(provider);
282
365
  credentials.apiKey = value;
283
366
  break;
284
367
 
285
368
  case 'sessionKey':
286
- value = await prompts.passwordInput('ENTER SESSION KEY:');
287
- if (!value) return await selectProviderOption(provider);
369
+ value = await prompts.textInput('ENTER SESSION KEY (OR < TO GO BACK):');
370
+ if (!value || value === '<') return await selectProviderOption(provider);
288
371
  credentials.sessionKey = value;
289
372
  break;
290
373
 
291
374
  case 'accessToken':
292
- value = await prompts.passwordInput('ENTER ACCESS TOKEN:');
293
- if (!value) return await selectProviderOption(provider);
375
+ value = await prompts.textInput('ENTER ACCESS TOKEN (OR < TO GO BACK):');
376
+ if (!value || value === '<') return await selectProviderOption(provider);
294
377
  credentials.accessToken = value;
295
378
  break;
296
379
 
297
380
  case 'endpoint':
298
381
  const defaultEndpoint = option.defaultEndpoint || '';
299
- value = await prompts.textInput(`ENDPOINT [${defaultEndpoint || 'required'}]:`);
382
+ value = await prompts.textInput(`ENDPOINT [${defaultEndpoint || 'required'}] (OR < TO GO BACK):`);
383
+ if (value === '<') return await selectProviderOption(provider);
300
384
  credentials.endpoint = value || defaultEndpoint;
301
385
  if (!credentials.endpoint) return await selectProviderOption(provider);
302
386
  break;
303
387
 
304
388
  case 'model':
305
- value = await prompts.textInput('MODEL NAME:');
306
- if (!value) return await selectProviderOption(provider);
389
+ value = await prompts.textInput('MODEL NAME (OR < TO GO BACK):');
390
+ if (!value || value === '<') return await selectProviderOption(provider);
307
391
  credentials.model = value;
308
392
  break;
309
393
  }
@@ -354,27 +438,33 @@ const selectModel = async (provider) => {
354
438
  };
355
439
 
356
440
  console.clear();
441
+ displayBanner();
357
442
  drawBoxHeader('SELECT MODEL', boxWidth);
358
443
 
359
444
  const models = provider.models || [];
360
445
 
361
446
  if (models.length === 0) {
362
- console.log(makeLine(chalk.gray('No predefined models. Enter model name manually.')));
447
+ console.log(makeLine(chalk.gray('NO PREDEFINED MODELS. ENTER MODEL NAME MANUALLY.')));
448
+ console.log(makeLine(''));
449
+ console.log(makeLine(chalk.gray('[<] BACK')));
363
450
  drawBoxFooter(boxWidth);
364
451
 
365
- const model = await prompts.textInput('ENTER MODEL NAME:');
366
- if (model) {
367
- const settings = aiService.getAISettings();
368
- settings.model = model;
369
- aiService.saveAISettings(settings);
370
- console.log(chalk.green(`\n MODEL CHANGED TO: ${model}`));
452
+ const model = await prompts.textInput('ENTER MODEL NAME (OR < TO GO BACK):');
453
+ if (!model || model === '<') {
454
+ return await aiAgentMenu();
371
455
  }
456
+ const settings = aiService.getAISettings();
457
+ settings.model = model;
458
+ aiService.saveAISettings(settings);
459
+ console.log(chalk.green(`\n MODEL CHANGED TO: ${model}`));
372
460
  await prompts.waitForEnter();
373
461
  return await aiAgentMenu();
374
462
  }
375
463
 
376
464
  models.forEach((model, index) => {
377
- console.log(makeLine(chalk.cyan(`[${index + 1}] ${model}`)));
465
+ // Truncate long model names
466
+ const displayModel = model.length > W - 10 ? model.substring(0, W - 13) + '...' : model;
467
+ console.log(makeLine(chalk.cyan(`[${index + 1}] ${displayModel}`)));
378
468
  });
379
469
 
380
470
  console.log(makeLine(''));
package/src/ui/box.js CHANGED
@@ -11,22 +11,19 @@ let logoWidth = null;
11
11
  /**
12
12
  * Get logo width for consistent box sizing
13
13
  * Adapts to terminal width for mobile devices
14
+ * Returns 98 for desktop to match the full HEDGEQUANTX logo width
14
15
  */
15
16
  const getLogoWidth = () => {
16
- const termWidth = process.stdout.columns || 80;
17
+ const termWidth = process.stdout.columns || 100;
17
18
 
18
19
  // Mobile: use terminal width
19
20
  if (termWidth < 60) {
20
21
  return Math.max(termWidth - 2, 40);
21
22
  }
22
23
 
23
- // Desktop: use logo width
24
- if (!logoWidth) {
25
- const logoText = figlet.textSync('HEDGEQUANTX', { font: 'ANSI Shadow' });
26
- const lines = logoText.split('\n').filter(line => line.trim().length > 0);
27
- logoWidth = Math.max(...lines.map(line => line.length)) + 4;
28
- }
29
- return Math.min(logoWidth, termWidth - 2);
24
+ // Desktop: fixed width of 98 to match banner
25
+ // Logo line = 86 chars (HEDGEQUANT) + 8 chars (X) + 2 borders = 96, round to 98
26
+ return Math.min(98, termWidth - 2);
30
27
  };
31
28
 
32
29
  /**