hedgequantx 2.7.19 → 2.7.20

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.7.19",
3
+ "version": "2.7.20",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -12,6 +12,7 @@ const fs = require('fs');
12
12
 
13
13
  const { getLogoWidth, centerText, visibleLength } = require('../ui');
14
14
  const { prompts } = require('../utils');
15
+ const { getModelsForProvider, getModelById } = require('./ai-models');
15
16
 
16
17
  // Config file path
17
18
  const CONFIG_DIR = path.join(os.homedir(), '.hqx');
@@ -63,7 +64,7 @@ const saveConfig = (config) => {
63
64
  };
64
65
 
65
66
  /**
66
- * Mask API key for display (show first 8 and last 4 chars)
67
+ * Mask API key for display
67
68
  * @param {string} key - API key
68
69
  * @returns {string} Masked key
69
70
  */
@@ -73,71 +74,98 @@ const maskKey = (key) => {
73
74
  };
74
75
 
75
76
  /**
76
- * Draw the main providers selection table (2 columns)
77
- * @param {Object} config - Current config
78
- * @param {number} boxWidth - Box width
77
+ * Draw a 2-column row
79
78
  */
80
- const drawProvidersTable = (config, boxWidth) => {
81
- const W = boxWidth - 2;
79
+ const draw2ColRow = (leftText, rightText, W) => {
82
80
  const col1Width = Math.floor(W / 2);
83
81
  const col2Width = W - col1Width;
84
-
85
- // Header
82
+ const leftLen = visibleLength(leftText);
83
+ const leftPad = col1Width - leftLen;
84
+ const leftPadL = Math.floor(leftPad / 2);
85
+ const rightLen = visibleLength(rightText || '');
86
+ const rightPad = col2Width - rightLen;
87
+ const rightPadL = Math.floor(rightPad / 2);
88
+ console.log(
89
+ chalk.cyan('║') +
90
+ ' '.repeat(leftPadL) + leftText + ' '.repeat(leftPad - leftPadL) +
91
+ ' '.repeat(rightPadL) + (rightText || '') + ' '.repeat(rightPad - rightPadL) +
92
+ chalk.cyan('║')
93
+ );
94
+ };
95
+
96
+ /**
97
+ * Draw 2-column table
98
+ */
99
+ const draw2ColTable = (title, titleColor, items, backText, W) => {
86
100
  console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
87
- console.log(chalk.cyan('║') + chalk.yellow.bold(centerText('AI AGENTS CONFIGURATION', W)) + chalk.cyan('║'));
101
+ console.log(chalk.cyan('║') + titleColor(centerText(title, W)) + chalk.cyan('║'));
88
102
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
89
103
 
90
- // Calculate max name length for alignment
91
- const maxNameLen = Math.max(...AI_PROVIDERS.map(p => p.name.length));
92
-
93
- // Provider rows (2 columns)
94
- const rows = Math.ceil(AI_PROVIDERS.length / 2);
104
+ const rows = Math.ceil(items.length / 2);
95
105
  for (let row = 0; row < rows; row++) {
96
- const leftIdx = row;
97
- const rightIdx = row + rows;
106
+ const left = items[row];
107
+ const right = items[row + rows];
108
+ draw2ColRow(left || '', right || '', W);
109
+ }
110
+
111
+ console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
112
+ console.log(chalk.cyan('║') + chalk.red(centerText(backText, W)) + chalk.cyan('║'));
113
+ console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
114
+ };
115
+
116
+ /**
117
+ * Draw providers table
118
+ */
119
+ const drawProvidersTable = (config, boxWidth) => {
120
+ const W = boxWidth - 2;
121
+ const items = AI_PROVIDERS.map((p, i) => {
122
+ const status = config.providers[p.id]?.active ? chalk.green(' ●') : '';
123
+ return chalk.cyan(`[${i + 1}]`) + ' ' + chalk[p.color](p.name) + status;
124
+ });
125
+ draw2ColTable('AI AGENTS CONFIGURATION', chalk.yellow.bold, items, '[B] Back to Menu', W);
126
+ };
127
+
128
+ /**
129
+ * Draw models table
130
+ */
131
+ const drawModelsTable = (provider, models, boxWidth) => {
132
+ const W = boxWidth - 2;
133
+ const items = models.map((m, i) => chalk.cyan(`[${i + 1}]`) + ' ' + chalk.white(m.name));
134
+ draw2ColTable(`${provider.name.toUpperCase()} - MODELS`, chalk[provider.color].bold, items, '[B] Back', W);
135
+ };
136
+
137
+ /**
138
+ * Select a model for a provider
139
+ * @param {Object} provider - Provider object
140
+ * @returns {Object|null} Selected model or null if cancelled
141
+ */
142
+ const selectModel = async (provider) => {
143
+ const boxWidth = getLogoWidth();
144
+ const models = getModelsForProvider(provider.id);
145
+
146
+ if (models.length === 0) {
147
+ return null;
148
+ }
149
+
150
+ while (true) {
151
+ console.clear();
152
+ drawModelsTable(provider, models, boxWidth);
98
153
 
99
- const leftProvider = AI_PROVIDERS[leftIdx];
100
- const rightProvider = AI_PROVIDERS[rightIdx];
154
+ const input = await prompts.textInput(chalk.cyan('Select model: '));
155
+ const choice = (input || '').toLowerCase().trim();
101
156
 
102
- // Left column
103
- const leftNum = `[${leftIdx + 1}]`;
104
- const leftName = leftProvider.name;
105
- const leftConfig = config.providers[leftProvider.id] || {};
106
- const leftStatus = leftConfig.active ? chalk.green('●') : '';
107
- const leftText = chalk.cyan(leftNum) + ' ' + chalk[leftProvider.color](leftName) + ' ' + leftStatus;
108
- const leftLen = visibleLength(leftText);
109
- const leftPadTotal = col1Width - leftLen;
110
- const leftPadL = Math.floor(leftPadTotal / 2);
111
- const leftPadR = leftPadTotal - leftPadL;
157
+ if (choice === 'b' || choice === '') {
158
+ return null;
159
+ }
112
160
 
113
- // Right column
114
- let rightText = '';
115
- let rightPadL = 0;
116
- let rightPadR = col2Width;
117
- if (rightProvider) {
118
- const rightNum = `[${rightIdx + 1}]`;
119
- const rightName = rightProvider.name;
120
- const rightConfig = config.providers[rightProvider.id] || {};
121
- const rightStatus = rightConfig.active ? chalk.green('●') : '';
122
- rightText = chalk.cyan(rightNum) + ' ' + chalk[rightProvider.color](rightName) + ' ' + rightStatus;
123
- const rightLen = visibleLength(rightText);
124
- const rightPadTotal = col2Width - rightLen;
125
- rightPadL = Math.floor(rightPadTotal / 2);
126
- rightPadR = rightPadTotal - rightPadL;
161
+ const num = parseInt(choice);
162
+ if (!isNaN(num) && num >= 1 && num <= models.length) {
163
+ return models[num - 1];
127
164
  }
128
165
 
129
- console.log(
130
- chalk.cyan('║') +
131
- ' '.repeat(leftPadL) + leftText + ' '.repeat(leftPadR) +
132
- ' '.repeat(rightPadL) + rightText + ' '.repeat(rightPadR) +
133
- chalk.cyan('║')
134
- );
166
+ console.log(chalk.red(' Invalid option.'));
167
+ await new Promise(r => setTimeout(r, 1000));
135
168
  }
136
-
137
- // Footer
138
- console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
139
- console.log(chalk.cyan('║') + chalk.red(centerText('[B] Back to Menu', W)) + chalk.cyan('║'));
140
- console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
141
169
  };
142
170
 
143
171
  /**
@@ -213,11 +241,8 @@ const drawProviderWindow = (provider, config, boxWidth) => {
213
241
  let statusText = '';
214
242
  if (providerConfig.active) {
215
243
  const connType = providerConfig.connectionType === 'cliproxy' ? 'CLIProxy' : 'API Key';
216
- const keyDisplay = providerConfig.apiKey ? maskKey(providerConfig.apiKey) : 'N/A';
217
- statusText = chalk.green('● ACTIVE') + chalk.gray(' via ') + chalk.cyan(connType);
218
- if (providerConfig.connectionType === 'apikey' && providerConfig.apiKey) {
219
- statusText += chalk.gray(' Key: ') + chalk.cyan(keyDisplay);
220
- }
244
+ const modelName = providerConfig.modelName || 'N/A';
245
+ statusText = chalk.green('● ACTIVE') + chalk.gray(' Model: ') + chalk.yellow(modelName) + chalk.gray(' via ') + chalk.cyan(connType);
221
246
  } else if (providerConfig.apiKey || providerConfig.connectionType) {
222
247
  statusText = chalk.yellow('● CONFIGURED') + chalk.gray(' (not active)');
223
248
  } else {
@@ -269,11 +294,9 @@ const handleProviderConfig = async (provider, config) => {
269
294
  }
270
295
 
271
296
  if (choice === '1') {
272
- // CLIProxy connection
273
- console.log();
274
- console.log(chalk.cyan(' Connecting via CLIProxy...'));
275
- console.log(chalk.gray(' This uses your paid plan (Claude Pro, ChatGPT Plus, etc.)'));
276
- console.log();
297
+ // CLIProxy connection - select model first
298
+ const selectedModel = await selectModel(provider);
299
+ if (!selectedModel) continue;
277
300
 
278
301
  // Deactivate all other providers
279
302
  Object.keys(config.providers).forEach(id => {
@@ -282,22 +305,28 @@ const handleProviderConfig = async (provider, config) => {
282
305
 
283
306
  if (!config.providers[provider.id]) config.providers[provider.id] = {};
284
307
  config.providers[provider.id].connectionType = 'cliproxy';
308
+ config.providers[provider.id].modelId = selectedModel.id;
309
+ config.providers[provider.id].modelName = selectedModel.name;
285
310
  config.providers[provider.id].active = true;
286
311
  config.providers[provider.id].configuredAt = new Date().toISOString();
287
312
 
288
313
  if (saveConfig(config)) {
289
- console.log(chalk.green(` ✓ ${provider.name} connected via CLIProxy.`));
314
+ console.log(chalk.green(`\n ✓ ${provider.name} connected via CLIProxy.`));
315
+ console.log(chalk.cyan(` Model: ${selectedModel.name}`));
290
316
  } else {
291
- console.log(chalk.red(' Failed to save config.'));
317
+ console.log(chalk.red('\n Failed to save config.'));
292
318
  }
293
319
  await prompts.waitForEnter();
294
320
  continue;
295
321
  }
296
322
 
297
323
  if (choice === '2') {
298
- // API Key connection
299
- console.log();
300
- console.log(chalk.yellow(` Enter your ${provider.name} API key:`));
324
+ // API Key connection - select model first
325
+ const selectedModel = await selectModel(provider);
326
+ if (!selectedModel) continue;
327
+
328
+ console.clear();
329
+ console.log(chalk.yellow(`\n Enter your ${provider.name} API key:`));
301
330
  console.log(chalk.gray(' (Press Enter to cancel)'));
302
331
  console.log();
303
332
 
@@ -323,13 +352,16 @@ const handleProviderConfig = async (provider, config) => {
323
352
  if (!config.providers[provider.id]) config.providers[provider.id] = {};
324
353
  config.providers[provider.id].connectionType = 'apikey';
325
354
  config.providers[provider.id].apiKey = apiKey.trim();
355
+ config.providers[provider.id].modelId = selectedModel.id;
356
+ config.providers[provider.id].modelName = selectedModel.name;
326
357
  config.providers[provider.id].active = true;
327
358
  config.providers[provider.id].configuredAt = new Date().toISOString();
328
359
 
329
360
  if (saveConfig(config)) {
330
- console.log(chalk.green(` ✓ ${provider.name} connected via API Key.`));
361
+ console.log(chalk.green(`\n ✓ ${provider.name} connected via API Key.`));
362
+ console.log(chalk.cyan(` Model: ${selectedModel.name}`));
331
363
  } else {
332
- console.log(chalk.red(' Failed to save config.'));
364
+ console.log(chalk.red('\n Failed to save config.'));
333
365
  }
334
366
  await prompts.waitForEnter();
335
367
  continue;
@@ -352,7 +384,9 @@ const getActiveProvider = () => {
352
384
  id: provider.id,
353
385
  name: provider.name,
354
386
  connectionType: providerConfig.connectionType,
355
- apiKey: providerConfig.apiKey || null
387
+ apiKey: providerConfig.apiKey || null,
388
+ modelId: providerConfig.modelId || null,
389
+ modelName: providerConfig.modelName || null
356
390
  };
357
391
  }
358
392
  }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * AI Models Configuration
3
+ *
4
+ * Lists available models for each AI provider.
5
+ * These are technical configuration values, not trading data.
6
+ */
7
+
8
+ // Models by provider ID
9
+ const PROVIDER_MODELS = {
10
+ anthropic: [
11
+ { id: 'claude-opus-4-20250514', name: 'Claude Opus 4', tier: 'flagship' },
12
+ { id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', tier: 'balanced' },
13
+ { id: 'claude-3-5-sonnet-20241022', name: 'Claude Sonnet 3.5', tier: 'balanced' },
14
+ { id: 'claude-3-5-haiku-20241022', name: 'Claude Haiku 3.5', tier: 'fast' },
15
+ ],
16
+ openai: [
17
+ { id: 'gpt-4o', name: 'GPT-4o', tier: 'flagship' },
18
+ { id: 'gpt-4o-mini', name: 'GPT-4o Mini', tier: 'fast' },
19
+ { id: 'gpt-4-turbo', name: 'GPT-4 Turbo', tier: 'balanced' },
20
+ { id: 'o1', name: 'o1', tier: 'reasoning' },
21
+ { id: 'o1-mini', name: 'o1-mini', tier: 'reasoning' },
22
+ { id: 'o3-mini', name: 'o3-mini', tier: 'reasoning' },
23
+ ],
24
+ google: [
25
+ { id: 'gemini-2.0-flash', name: 'Gemini 2.0 Flash', tier: 'flagship' },
26
+ { id: 'gemini-1.5-pro', name: 'Gemini 1.5 Pro', tier: 'balanced' },
27
+ { id: 'gemini-1.5-flash', name: 'Gemini 1.5 Flash', tier: 'fast' },
28
+ { id: 'gemini-1.0-pro', name: 'Gemini 1.0 Pro', tier: 'legacy' },
29
+ ],
30
+ mistral: [
31
+ { id: 'mistral-large-latest', name: 'Mistral Large', tier: 'flagship' },
32
+ { id: 'mistral-medium-latest', name: 'Mistral Medium', tier: 'balanced' },
33
+ { id: 'mistral-small-latest', name: 'Mistral Small', tier: 'fast' },
34
+ { id: 'codestral-latest', name: 'Codestral', tier: 'code' },
35
+ ],
36
+ groq: [
37
+ { id: 'llama-3.3-70b-versatile', name: 'Llama 3.3 70B', tier: 'flagship' },
38
+ { id: 'llama-3.1-8b-instant', name: 'Llama 3.1 8B', tier: 'fast' },
39
+ { id: 'mixtral-8x7b-32768', name: 'Mixtral 8x7B', tier: 'balanced' },
40
+ { id: 'gemma2-9b-it', name: 'Gemma 2 9B', tier: 'fast' },
41
+ ],
42
+ xai: [
43
+ { id: 'grok-2', name: 'Grok 2', tier: 'flagship' },
44
+ { id: 'grok-2-mini', name: 'Grok 2 Mini', tier: 'fast' },
45
+ { id: 'grok-beta', name: 'Grok Beta', tier: 'beta' },
46
+ ],
47
+ perplexity: [
48
+ { id: 'sonar-pro', name: 'Sonar Pro', tier: 'flagship' },
49
+ { id: 'sonar', name: 'Sonar', tier: 'balanced' },
50
+ { id: 'sonar-reasoning', name: 'Sonar Reasoning', tier: 'reasoning' },
51
+ ],
52
+ openrouter: [
53
+ { id: 'anthropic/claude-opus-4', name: 'Claude Opus 4', tier: 'flagship' },
54
+ { id: 'openai/gpt-4o', name: 'GPT-4o', tier: 'flagship' },
55
+ { id: 'google/gemini-2.0-flash', name: 'Gemini 2.0 Flash', tier: 'flagship' },
56
+ { id: 'meta-llama/llama-3.3-70b', name: 'Llama 3.3 70B', tier: 'open' },
57
+ ],
58
+ };
59
+
60
+ /**
61
+ * Get models for a provider
62
+ * @param {string} providerId - Provider ID
63
+ * @returns {Array} List of models
64
+ */
65
+ const getModelsForProvider = (providerId) => {
66
+ return PROVIDER_MODELS[providerId] || [];
67
+ };
68
+
69
+ /**
70
+ * Get model by ID
71
+ * @param {string} providerId - Provider ID
72
+ * @param {string} modelId - Model ID
73
+ * @returns {Object|null} Model object or null
74
+ */
75
+ const getModelById = (providerId, modelId) => {
76
+ const models = PROVIDER_MODELS[providerId] || [];
77
+ return models.find(m => m.id === modelId) || null;
78
+ };
79
+
80
+ module.exports = {
81
+ PROVIDER_MODELS,
82
+ getModelsForProvider,
83
+ getModelById
84
+ };