hedgequantx 2.9.19 → 2.9.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.
Files changed (39) hide show
  1. package/package.json +1 -1
  2. package/src/app.js +42 -64
  3. package/src/lib/m/hqx-2b.js +7 -0
  4. package/src/lib/m/index.js +138 -0
  5. package/src/lib/m/ultra-scalping.js +7 -0
  6. package/src/menus/connect.js +14 -17
  7. package/src/menus/dashboard.js +58 -76
  8. package/src/pages/accounts.js +38 -49
  9. package/src/pages/algo/copy-trading.js +546 -178
  10. package/src/pages/algo/index.js +18 -75
  11. package/src/pages/algo/one-account.js +322 -57
  12. package/src/pages/algo/ui.js +15 -15
  13. package/src/pages/orders.js +19 -22
  14. package/src/pages/positions.js +19 -22
  15. package/src/pages/stats/index.js +15 -16
  16. package/src/pages/user.js +7 -11
  17. package/src/services/ai-supervision/health.js +35 -47
  18. package/src/services/index.js +1 -9
  19. package/src/services/rithmic/accounts.js +8 -6
  20. package/src/ui/box.js +9 -5
  21. package/src/ui/index.js +5 -18
  22. package/src/ui/menu.js +4 -4
  23. package/src/pages/ai-agents-ui.js +0 -388
  24. package/src/pages/ai-agents.js +0 -494
  25. package/src/pages/ai-models.js +0 -389
  26. package/src/pages/algo/algo-executor.js +0 -307
  27. package/src/pages/algo/copy-executor.js +0 -331
  28. package/src/pages/algo/custom-strategy.js +0 -313
  29. package/src/services/ai-supervision/consensus.js +0 -284
  30. package/src/services/ai-supervision/context.js +0 -275
  31. package/src/services/ai-supervision/directive.js +0 -167
  32. package/src/services/ai-supervision/index.js +0 -359
  33. package/src/services/ai-supervision/parser.js +0 -278
  34. package/src/services/ai-supervision/symbols.js +0 -259
  35. package/src/services/cliproxy/index.js +0 -256
  36. package/src/services/cliproxy/installer.js +0 -111
  37. package/src/services/cliproxy/manager.js +0 -387
  38. package/src/services/llmproxy/index.js +0 -166
  39. package/src/services/llmproxy/manager.js +0 -411
@@ -7,10 +7,9 @@ const ora = require('ora');
7
7
  const { execSync, spawn } = require('child_process');
8
8
 
9
9
  const { connections } = require('../services');
10
- const { getLogoWidth, centerText, prepareStdin, displayBanner, clearScreen } = require('../ui');
10
+ const { getLogoWidth, centerText, prepareStdin } = require('../ui');
11
11
  const { getCachedStats } = require('../services/stats-cache');
12
12
  const { prompts } = require('../utils');
13
- const { getActiveAgentCount } = require('../pages/ai-agents');
14
13
 
15
14
  /**
16
15
  * Dashboard menu after login
@@ -18,16 +17,6 @@ const { getActiveAgentCount } = require('../pages/ai-agents');
18
17
  const dashboardMenu = async (service) => {
19
18
  prepareStdin();
20
19
 
21
- // Stop any global spinner before clearing
22
- if (global.__hqxSpinner) {
23
- global.__hqxSpinner.stop();
24
- global.__hqxSpinner = null;
25
- }
26
-
27
- // Clear screen and show banner (always closed)
28
- clearScreen();
29
- displayBanner();
30
-
31
20
  const boxWidth = getLogoWidth();
32
21
  const W = boxWidth - 2;
33
22
 
@@ -41,9 +30,9 @@ const dashboardMenu = async (service) => {
41
30
  return chalk.cyan('║') + content + ' '.repeat(Math.max(0, padding)) + chalk.cyan('║');
42
31
  };
43
32
 
44
- // New rectangle (banner is always closed)
45
- console.log(chalk.cyan('' + '═'.repeat(W) + ''));
46
- console.log(makeLine(chalk.yellow.bold('WELCOME, HQX TRADER!'), 'center'));
33
+ // Continue from banner (use not ╔)
34
+ console.log(chalk.cyan('' + '═'.repeat(W) + ''));
35
+ console.log(makeLine(chalk.yellow.bold('Welcome, HQX Trader!'), 'center'));
47
36
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
48
37
 
49
38
  // Show connected propfirms
@@ -62,8 +51,8 @@ const dashboardMenu = async (service) => {
62
51
  const balStr = statsInfo.balance !== null ? `$${statsInfo.balance.toLocaleString()}` : '--';
63
52
  const balColor = statsInfo.balance !== null ? chalk.green : chalk.gray;
64
53
 
65
- // AI Agents status - get fresh count, not from cache
66
- const agentCount = getActiveAgentCount();
54
+ // AI Agents status
55
+ const agentCount = statsInfo.agents || 0;
67
56
  const agentDisplay = agentCount > 0 ? 'ON' : 'OFF';
68
57
  const agentColor = agentCount > 0 ? chalk.green : chalk.red;
69
58
 
@@ -98,9 +87,9 @@ const dashboardMenu = async (service) => {
98
87
 
99
88
  // Find max width for alignment
100
89
  const menuItems = [
101
- { left: '[1] VIEW ACCOUNTS', right: '[2] VIEW STATS' },
102
- { left: '[+] ADD PROP-ACCOUNT', right: '[A] ALGO-TRADING' },
103
- { left: '[I] AI AGENTS', right: '[U] UPDATE HQX' },
90
+ { left: '[1] View Accounts', right: '[2] View Stats' },
91
+ { left: '[+] Add Prop-Account', right: '[A] Algo-Trading' },
92
+ { left: '[U] Update HQX', right: '[X] Disconnect' },
104
93
  ];
105
94
 
106
95
  const maxLeftLen = Math.max(...menuItems.map(m => m.left.length));
@@ -130,25 +119,20 @@ const dashboardMenu = async (service) => {
130
119
  );
131
120
  };
132
121
 
133
- menuRow('[1] VIEW ACCOUNTS', '[2] VIEW STATS', chalk.cyan, chalk.cyan);
134
- menuRow('[+] ADD PROP-ACCOUNT', '[A] ALGO-TRADING', chalk.cyan, chalk.magenta);
135
- menuRow('[I] AI AGENTS', '[U] UPDATE HQX', chalk.green, chalk.yellow);
136
-
137
- // Separator and centered Disconnect button
138
- console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
139
- console.log(makeLine(chalk.red('[X] DISCONNECT'), 'center'));
122
+ menuRow('[1] View Accounts', '[2] View Stats', chalk.cyan, chalk.cyan);
123
+ menuRow('[+] Add Prop-Account', '[A] Algo-Trading', chalk.cyan, chalk.magenta);
124
+ menuRow('[U] Update HQX', '[X] Disconnect', chalk.yellow, chalk.red);
140
125
 
141
126
  console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
142
127
 
143
128
  // Simple input - no duplicate menu
144
- const input = await prompts.textInput(chalk.cyan('SELECT (1/2/+/A/I/U/X):'));
129
+ const input = await prompts.textInput(chalk.cyan('Select (1/2/+/A/U/X)'));
145
130
 
146
131
  const actionMap = {
147
132
  '1': 'accounts',
148
133
  '2': 'stats',
149
134
  '+': 'add_prop_account',
150
135
  'a': 'algotrading',
151
- 'i': 'aiagents',
152
136
  'u': 'update',
153
137
  'x': 'disconnect'
154
138
  };
@@ -160,95 +144,93 @@ const dashboardMenu = async (service) => {
160
144
  * Handle update process
161
145
  */
162
146
  const handleUpdate = async () => {
163
- clearScreen();
164
- displayBanner();
165
147
  prepareStdin();
166
148
 
167
- const boxWidth = getLogoWidth();
168
- const W = boxWidth - 2;
169
-
170
- console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
171
- console.log(chalk.cyan('║') + chalk.yellow.bold(centerText('UPDATE HQX', W)) + chalk.cyan('║'));
172
- console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
173
-
174
149
  let spinner = null;
175
150
  let currentVersion = 'unknown';
176
151
 
177
152
  try {
178
- // Get current version
179
153
  try {
180
154
  currentVersion = require('../../package.json').version || 'unknown';
181
155
  } catch (e) {}
182
156
 
183
- console.log(chalk.cyan(`\n CURRENT VERSION: V${currentVersion.toUpperCase()}`));
184
- spinner = ora({ text: 'CHECKING FOR UPDATES...', color: 'yellow' }).start();
157
+ console.log(chalk.cyan(`\n Current version: v${currentVersion}`));
158
+ spinner = ora({ text: 'Checking for updates...', color: 'yellow' }).start();
185
159
 
186
- // Check latest version from npm
187
160
  let latestVersion;
188
161
  try {
189
- latestVersion = execSync('npm view hedgequantx version 2>/dev/null', {
162
+ latestVersion = execSync('npm view hedgequantx version', {
190
163
  stdio: ['pipe', 'pipe', 'pipe'],
191
164
  timeout: 30000,
192
165
  encoding: 'utf8'
193
166
  }).trim();
194
167
 
195
168
  if (!latestVersion || !/^\d+\.\d+\.\d+/.test(latestVersion)) {
196
- throw new Error('INVALID VERSION FORMAT');
169
+ throw new Error('Invalid version format');
197
170
  }
198
171
  } catch (e) {
199
- spinner.fail('CANNOT REACH NPM REGISTRY');
200
- console.log(chalk.yellow('\n TRY MANUALLY: npm update -g hedgequantx'));
172
+ spinner.fail('Cannot reach npm registry');
173
+ console.log(chalk.gray(` Error: ${e.message}`));
174
+ console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
201
175
  await prompts.waitForEnter();
202
176
  return;
203
177
  }
204
178
 
205
- spinner.succeed(`LATEST VERSION: V${latestVersion.toUpperCase()}`);
179
+ spinner.succeed(`Latest version: v${latestVersion}`);
206
180
 
207
- // Already up to date
208
181
  if (currentVersion === latestVersion) {
209
- console.log(chalk.green('\n ALREADY UP TO DATE!'));
182
+ console.log(chalk.green(' Already up to date!'));
210
183
  await prompts.waitForEnter();
211
184
  return;
212
185
  }
213
186
 
214
- // Update available
215
- console.log(chalk.yellow(`\n UPDATE AVAILABLE: V${currentVersion} V${latestVersion}`));
216
- spinner = ora({ text: 'INSTALLING UPDATE...', color: 'yellow' }).start();
187
+ console.log(chalk.yellow(` Update available: v${currentVersion} → v${latestVersion}`));
188
+ spinner = ora({ text: 'Installing update...', color: 'yellow' }).start();
217
189
 
218
- // Try to install update
219
190
  try {
220
- execSync('npm update -g hedgequantx 2>/dev/null', {
191
+ // Try with sudo first on Unix systems
192
+ const isWindows = process.platform === 'win32';
193
+ const cmd = isWindows
194
+ ? 'npm install -g hedgequantx@latest'
195
+ : 'npm install -g hedgequantx@latest';
196
+
197
+ execSync(cmd, {
221
198
  stdio: ['pipe', 'pipe', 'pipe'],
222
199
  timeout: 180000,
223
200
  encoding: 'utf8'
224
201
  });
225
202
  } catch (e) {
226
- // Try without redirecting stderr
227
- try {
228
- execSync('npm update -g hedgequantx', {
229
- stdio: ['pipe', 'pipe', 'pipe'],
230
- timeout: 180000,
231
- encoding: 'utf8'
232
- });
233
- } catch (e2) {
234
- spinner.fail('UPDATE FAILED');
235
- console.log(chalk.yellow('\n TRY MANUALLY:'));
236
- console.log(chalk.white(' npm update -g hedgequantx'));
237
- console.log(chalk.gray(' OR WITH SUDO:'));
238
- console.log(chalk.white(' sudo npm update -g hedgequantx'));
239
- await prompts.waitForEnter();
240
- return;
241
- }
203
+ spinner.fail('Update failed - permission denied?');
204
+ console.log(chalk.gray(` Error: ${e.message}`));
205
+ console.log(chalk.yellow(' Try manually with sudo:'));
206
+ console.log(chalk.white(' sudo npm install -g hedgequantx@latest'));
207
+ await prompts.waitForEnter();
208
+ return;
242
209
  }
243
210
 
244
- spinner.succeed(`UPDATED TO V${latestVersion}!`);
245
- console.log(chalk.green('\n UPDATE SUCCESSFUL!'));
246
- console.log(chalk.yellow('\n Run ') + chalk.cyan('hqx') + chalk.yellow(' to start the new version.\n'));
247
- process.exit(0);
211
+ spinner.succeed(`Updated to v${latestVersion}!`);
212
+ console.log(chalk.cyan(' Restarting HQX...'));
213
+
214
+ await new Promise(r => setTimeout(r, 1500));
215
+
216
+ try {
217
+ const child = spawn('hqx', [], {
218
+ stdio: 'inherit',
219
+ detached: true,
220
+ shell: true
221
+ });
222
+ child.unref();
223
+ process.exit(0);
224
+ } catch (e) {
225
+ console.log(chalk.yellow('\n Please restart HQX manually:'));
226
+ console.log(chalk.white(' hqx'));
227
+ await prompts.waitForEnter();
228
+ }
248
229
 
249
230
  } catch (error) {
250
- if (spinner) spinner.fail('UPDATE ERROR');
251
- console.log(chalk.yellow('\n TRY MANUALLY: npm update -g hedgequantx'));
231
+ if (spinner) spinner.fail('Update error');
232
+ console.log(chalk.gray(` Error: ${error.message}`));
233
+ console.log(chalk.yellow(' Try manually: npm install -g hedgequantx@latest'));
252
234
  await prompts.waitForEnter();
253
235
  }
254
236
  };
@@ -7,17 +7,13 @@ const ora = require('ora');
7
7
 
8
8
  const { connections } = require('../services');
9
9
  const { ACCOUNT_STATUS, ACCOUNT_TYPE } = require('../config');
10
- const { getLogoWidth, getColWidths, drawBoxHeader, drawBoxFooter, draw2ColHeader, visibleLength, displayBanner, clearScreen } = require('../ui');
10
+ const { getLogoWidth, getColWidths, drawBoxHeader, drawBoxFooter, draw2ColHeader, visibleLength, displayBanner } = require('../ui');
11
11
  const { prompts } = require('../utils');
12
12
 
13
13
  /**
14
14
  * Show all accounts
15
15
  */
16
16
  const showAccounts = async (service) => {
17
- // Clear screen and show banner
18
- clearScreen();
19
- displayBanner();
20
-
21
17
  const boxWidth = getLogoWidth();
22
18
  const { col1, col2 } = getColWidths(boxWidth);
23
19
 
@@ -32,12 +28,13 @@ const showAccounts = async (service) => {
32
28
  let spinner;
33
29
 
34
30
  try {
35
- spinner = ora({ text: 'LOADING ACCOUNTS...', color: 'yellow' }).start();
31
+ // Single spinner for loading (appears below the dashboard header)
32
+ spinner = ora({ text: 'Loading accounts...', color: 'yellow' }).start();
36
33
 
37
34
  const allConns = connections.count() > 0 ? connections.getAll() : (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
38
35
 
39
36
  if (allConns.length === 0) {
40
- spinner.fail('NO CONNECTIONS FOUND');
37
+ spinner.fail('No connections found');
41
38
  await prompts.waitForEnter();
42
39
  return;
43
40
  }
@@ -63,7 +60,7 @@ const showAccounts = async (service) => {
63
60
  }
64
61
 
65
62
  if (allAccounts.length === 0) {
66
- spinner.fail('NO ACCOUNTS FOUND');
63
+ spinner.fail('No accounts found');
67
64
  await prompts.waitForEnter();
68
65
  return;
69
66
  }
@@ -81,11 +78,8 @@ const showAccounts = async (service) => {
81
78
  } catch (e) {}
82
79
  }
83
80
 
84
- spinner.stop();
85
-
86
- // Clear and show banner again before displaying accounts
87
- clearScreen();
88
- displayBanner();
81
+ spinner.succeed('Accounts loaded');
82
+ console.log();
89
83
 
90
84
  // Display accounts
91
85
  drawBoxHeader('TRADING ACCOUNTS', boxWidth);
@@ -97,20 +91,12 @@ const showAccounts = async (service) => {
97
91
  const name1 = String(acc1.accountName || acc1.rithmicAccountId || acc1.accountId || `Account #${i + 1}`);
98
92
  const name2 = acc2 ? String(acc2.accountName || acc2.rithmicAccountId || acc2.accountId || `Account #${i + 2}`) : '';
99
93
 
100
- // For single account, use full width; for pairs, use 2-column layout
101
- const sep = acc2 ? '│' : '║';
102
- const rightCol = acc2 ? col2 : col2;
103
-
104
- // Header row with account name(s)
105
- const h1 = centerText(name1.substring(0, col1 - 4), col1);
106
- const h2 = acc2 ? centerText(name2.substring(0, col2 - 4), col2) : ' '.repeat(col2);
107
- console.log(chalk.cyan('║') + chalk.cyan.bold(h1) + chalk.cyan(sep) + chalk.cyan.bold(h2) + chalk.cyan('║'));
108
- console.log(chalk.cyan('╠') + chalk.cyan('─'.repeat(col1)) + chalk.cyan(acc2 ? '┼' : '┼') + chalk.cyan('─'.repeat(col2)) + chalk.cyan('╣'));
94
+ draw2ColHeader(name1.substring(0, col1 - 4), name2 ? name2.substring(0, col2 - 4) : '', boxWidth);
109
95
 
110
96
  // PropFirm
111
97
  const pf1 = chalk.magenta(acc1.propfirm || 'Unknown');
112
98
  const pf2 = acc2 ? chalk.magenta(acc2.propfirm || 'Unknown') : '';
113
- console.log(chalk.cyan('║') + fmtRow('PropFirm:', pf1, col1) + chalk.cyan(sep) + (acc2 ? fmtRow('PropFirm:', pf2, col2) : ' '.repeat(col2)) + chalk.cyan('║'));
99
+ console.log(chalk.cyan('║') + fmtRow('PropFirm:', pf1, col1) + chalk.cyan('│') + (acc2 ? fmtRow('PropFirm:', pf2, col2) : ' '.repeat(col2)) + chalk.cyan('║'));
114
100
 
115
101
  // Balance
116
102
  const bal1 = acc1.balance;
@@ -119,7 +105,7 @@ const showAccounts = async (service) => {
119
105
  const balStr2 = bal2 !== null && bal2 !== undefined ? '$' + Number(bal2).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '--';
120
106
  const balColor1 = bal1 === null || bal1 === undefined ? chalk.gray : (bal1 >= 0 ? chalk.green : chalk.red);
121
107
  const balColor2 = bal2 === null || bal2 === undefined ? chalk.gray : (bal2 >= 0 ? chalk.green : chalk.red);
122
- console.log(chalk.cyan('║') + fmtRow('Balance:', balColor1(balStr1), col1) + chalk.cyan(sep) + (acc2 ? fmtRow('Balance:', balColor2(balStr2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
108
+ console.log(chalk.cyan('║') + fmtRow('Balance:', balColor1(balStr1), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Balance:', balColor2(balStr2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
123
109
 
124
110
  // P&L
125
111
  const pnl1 = acc1.profitAndLoss;
@@ -128,41 +114,44 @@ const showAccounts = async (service) => {
128
114
  const pnlStr2 = pnl2 !== null && pnl2 !== undefined ? (pnl2 >= 0 ? '+' : '') + '$' + Number(pnl2).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '--';
129
115
  const pnlColor1 = pnl1 === null || pnl1 === undefined ? chalk.gray : (pnl1 >= 0 ? chalk.green : chalk.red);
130
116
  const pnlColor2 = pnl2 === null || pnl2 === undefined ? chalk.gray : (pnl2 >= 0 ? chalk.green : chalk.red);
131
- console.log(chalk.cyan('║') + fmtRow('P&L:', pnlColor1(pnlStr1), col1) + chalk.cyan(sep) + (acc2 ? fmtRow('P&L:', pnlColor2(pnlStr2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
117
+ console.log(chalk.cyan('║') + fmtRow('P&L:', pnlColor1(pnlStr1), col1) + chalk.cyan('│') + (acc2 ? fmtRow('P&L:', pnlColor2(pnlStr2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
132
118
 
133
- // Status - from Rithmic RMS API (field 154003), N/A if not available
134
- const getStatusDisplay = (acc) => {
135
- const status = acc.status;
136
- if (status === null || status === undefined) return { text: 'N/A', color: 'gray' };
119
+ // Status - handle both string from API and numeric lookup
120
+ const getStatusDisplay = (status) => {
121
+ if (!status && status !== 0) return { text: '--', color: 'gray' };
137
122
  if (typeof status === 'string') {
123
+ // Direct string from Rithmic API (e.g., "Active", "Disabled")
138
124
  const lowerStatus = status.toLowerCase();
139
125
  if (lowerStatus.includes('active') || lowerStatus.includes('open')) return { text: status, color: 'green' };
140
126
  if (lowerStatus.includes('disabled') || lowerStatus.includes('closed')) return { text: status, color: 'red' };
141
127
  if (lowerStatus.includes('halt')) return { text: status, color: 'red' };
142
128
  return { text: status, color: 'yellow' };
143
129
  }
144
- return { text: String(status), color: 'yellow' };
130
+ return ACCOUNT_STATUS[status] || { text: 'Unknown', color: 'gray' };
145
131
  };
146
- const status1 = getStatusDisplay(acc1);
147
- const status2 = acc2 ? getStatusDisplay(acc2) : null;
148
- console.log(chalk.cyan('║') + fmtRow('Status:', chalk[status1.color](status1.text), col1) + chalk.cyan(sep) + (acc2 ? fmtRow('Status:', chalk[status2.color](status2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
149
-
150
- // Algorithm - from Rithmic RMS API (field 150142), N/A if not available
151
- const getAlgorithmDisplay = (acc) => {
152
- const algo = acc.algorithm;
153
- if (algo === null || algo === undefined) return { text: 'N/A', color: 'gray' };
154
- if (typeof algo === 'string') {
155
- const lowerAlgo = algo.toLowerCase();
156
- if (lowerAlgo.includes('eval')) return { text: algo, color: 'yellow' };
157
- if (lowerAlgo.includes('live') || lowerAlgo.includes('funded')) return { text: algo, color: 'green' };
158
- if (lowerAlgo.includes('sim') || lowerAlgo.includes('demo')) return { text: algo, color: 'gray' };
159
- return { text: algo, color: 'cyan' };
132
+ const status1 = getStatusDisplay(acc1.status);
133
+ const status2 = acc2 ? getStatusDisplay(acc2.status) : null;
134
+ console.log(chalk.cyan('║') + fmtRow('Status:', chalk[status1.color](status1.text), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Status:', chalk[status2.color](status2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
135
+
136
+ // Type/Algorithm - handle both string from API and numeric lookup
137
+ const getTypeDisplay = (type, algorithm) => {
138
+ // Prefer algorithm from RMS info if available
139
+ const value = algorithm || type;
140
+ if (!value && value !== 0) return { text: '--', color: 'gray' };
141
+ if (typeof value === 'string') {
142
+ // Direct string from Rithmic API
143
+ const lowerValue = value.toLowerCase();
144
+ if (lowerValue.includes('eval')) return { text: value, color: 'yellow' };
145
+ if (lowerValue.includes('live') || lowerValue.includes('funded')) return { text: value, color: 'green' };
146
+ if (lowerValue.includes('sim') || lowerValue.includes('demo')) return { text: value, color: 'gray' };
147
+ if (lowerValue.includes('express')) return { text: value, color: 'magenta' };
148
+ return { text: value, color: 'cyan' };
160
149
  }
161
- return { text: String(algo), color: 'cyan' };
150
+ return ACCOUNT_TYPE[value] || { text: 'Unknown', color: 'white' };
162
151
  };
163
- const algo1 = getAlgorithmDisplay(acc1);
164
- const algo2 = acc2 ? getAlgorithmDisplay(acc2) : null;
165
- console.log(chalk.cyan('║') + fmtRow('Algorithm:', chalk[algo1.color](algo1.text), col1) + chalk.cyan(sep) + (acc2 ? fmtRow('Algorithm:', chalk[algo2.color](algo2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
152
+ const type1 = getTypeDisplay(acc1.type, acc1.algorithm);
153
+ const type2 = acc2 ? getTypeDisplay(acc2.type, acc2.algorithm) : null;
154
+ console.log(chalk.cyan('║') + fmtRow('Type:', chalk[type1.color](type1.text), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Type:', chalk[type2.color](type2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
166
155
 
167
156
  if (i + 2 < allAccounts.length) {
168
157
  console.log(chalk.cyan('╠') + chalk.cyan('═'.repeat(col1)) + chalk.cyan('╪') + chalk.cyan('═'.repeat(col2)) + chalk.cyan('╣'));
@@ -173,7 +162,7 @@ const showAccounts = async (service) => {
173
162
  console.log();
174
163
 
175
164
  } catch (error) {
176
- if (spinner) spinner.fail('ERROR LOADING ACCOUNTS: ' + error.message.toUpperCase());
165
+ if (spinner) spinner.fail('Error loading accounts: ' + error.message);
177
166
  }
178
167
 
179
168
  await prompts.waitForEnter();