hedgequantx 2.7.83 → 2.7.85

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.83",
3
+ "version": "2.7.85",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -6,6 +6,7 @@
6
6
 
7
7
  const chalk = require('chalk');
8
8
  const { centerText, visibleLength } = require('../ui');
9
+ const cliproxy = require('../services/cliproxy');
9
10
 
10
11
  /**
11
12
  * Draw a 2-column row with perfect alignment
@@ -94,6 +95,9 @@ const drawProvidersTable = (providers, config, boxWidth) => {
94
95
  const W = boxWidth - 2;
95
96
  const colWidth = Math.floor(W / 2);
96
97
 
98
+ // Get connected providers (have auth files)
99
+ const connected = cliproxy.getConnectedProviders();
100
+
97
101
  // New rectangle (banner is always closed)
98
102
  console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
99
103
  console.log(chalk.cyan('║') + chalk.yellow.bold(centerText('AI AGENTS CONFIGURATION', W)) + chalk.cyan('║'));
@@ -104,9 +108,9 @@ const drawProvidersTable = (providers, config, boxWidth) => {
104
108
  // Find max name length across ALL providers for consistent alignment
105
109
  const maxNameLen = Math.max(...providers.map(p => p.name.length));
106
110
 
107
- // Fixed format: "[N] NAME" where N is always same width, NAME padded to maxNameLen
108
- // Total content width = 3 ([N]) + 1 (space) + maxNameLen + 2 (possible " ●")
109
- const contentWidth = 3 + 1 + maxNameLen + 2;
111
+ // Fixed format: "[N] NAME" where is yellow if connected (has auth file)
112
+ // Total content width = 2 (● ) + 3 ([N]) + 1 (space) + maxNameLen
113
+ const contentWidth = 2 + 3 + 1 + maxNameLen;
110
114
  const leftPad = Math.floor((colWidth - contentWidth) / 2);
111
115
  const rightPad = Math.floor(((W - colWidth) - contentWidth) / 2);
112
116
 
@@ -118,12 +122,13 @@ const drawProvidersTable = (providers, config, boxWidth) => {
118
122
  let leftCol = '';
119
123
  if (leftP) {
120
124
  const num = row + 1;
121
- const status = config.providers[leftP.id]?.active ? chalk.green(' ●') : ' ';
122
- const statusRaw = config.providers[leftP.id]?.active ? ' ●' : ' ';
125
+ // Show yellow dot if provider has auth file (connected via OAuth)
126
+ const isConnected = connected[leftP.id] || config.providers[leftP.id]?.active;
127
+ const status = isConnected ? chalk.yellow('● ') : ' ';
123
128
  const name = leftP.provider ? leftP.provider.name : leftP.name;
124
129
  const namePadded = name.toUpperCase().padEnd(maxNameLen);
125
- const content = chalk.cyan(`[${num}]`) + ' ' + chalk[leftP.color](namePadded) + status;
126
- const contentLen = 3 + 1 + maxNameLen + 2;
130
+ const content = status + chalk.cyan(`[${num}]`) + ' ' + chalk[leftP.color](namePadded);
131
+ const contentLen = 2 + 3 + 1 + maxNameLen;
127
132
  const padR = colWidth - leftPad - contentLen;
128
133
  leftCol = ' '.repeat(leftPad) + content + ' '.repeat(Math.max(0, padR));
129
134
  } else {
@@ -135,11 +140,13 @@ const drawProvidersTable = (providers, config, boxWidth) => {
135
140
  const rightColWidth = W - colWidth;
136
141
  if (rightP) {
137
142
  const num = row + rows + 1;
138
- const status = config.providers[rightP.id]?.active ? chalk.green(' ●') : ' ';
143
+ // Show yellow dot if provider has auth file (connected via OAuth)
144
+ const isConnected = connected[rightP.id] || config.providers[rightP.id]?.active;
145
+ const status = isConnected ? chalk.yellow('● ') : ' ';
139
146
  const name = rightP.provider ? rightP.provider.name : rightP.name;
140
147
  const namePadded = name.toUpperCase().padEnd(maxNameLen);
141
- const content = chalk.cyan(`[${num}]`) + ' ' + chalk[rightP.color](namePadded) + status;
142
- const contentLen = 3 + 1 + maxNameLen + 2;
148
+ const content = status + chalk.cyan(`[${num}]`) + ' ' + chalk[rightP.color](namePadded);
149
+ const contentLen = 2 + 3 + 1 + maxNameLen;
143
150
  const padR2 = rightColWidth - rightPad - contentLen;
144
151
  rightCol = ' '.repeat(rightPad) + content + ' '.repeat(Math.max(0, padR2));
145
152
  } else {
@@ -95,11 +95,8 @@ const selectModel = async (provider, apiKey) => {
95
95
  return selectModelFromList(provider, result.models, boxWidth);
96
96
  };
97
97
 
98
- /** Deactivate all providers and activate one */
98
+ /** Activate a provider (multiple providers can be active at the same time) */
99
99
  const activateProvider = (config, providerId, data) => {
100
- Object.keys(config.providers).forEach(id => {
101
- if (config.providers[id]) config.providers[id].active = false;
102
- });
103
100
  if (!config.providers[providerId]) config.providers[providerId] = {};
104
101
  Object.assign(config.providers[providerId], data, { active: true, configuredAt: new Date().toISOString() });
105
102
  };
@@ -150,6 +150,29 @@ const fetchProviderModels = async (providerId) => {
150
150
  };
151
151
  };
152
152
 
153
+ /**
154
+ * Check which providers have auth files (are connected)
155
+ * @returns {Object} { anthropic: true/false, google: true/false, openai: true/false, qwen: true/false }
156
+ */
157
+ const getConnectedProviders = () => {
158
+ const fs = require('fs');
159
+ const connected = { anthropic: false, google: false, openai: false, qwen: false };
160
+
161
+ try {
162
+ if (!fs.existsSync(AUTH_DIR)) return connected;
163
+
164
+ const files = fs.readdirSync(AUTH_DIR);
165
+ for (const file of files) {
166
+ if (file.startsWith('claude-') && file.endsWith('.json')) connected.anthropic = true;
167
+ if (file.startsWith('gemini-') && file.endsWith('.json')) connected.google = true;
168
+ if (file.startsWith('codex-') && file.endsWith('.json')) connected.openai = true;
169
+ if (file.startsWith('qwen-') && file.endsWith('.json')) connected.qwen = true;
170
+ }
171
+ } catch (e) { /* ignore */ }
172
+
173
+ return connected;
174
+ };
175
+
153
176
  /**
154
177
  * Chat completion request
155
178
  * @param {string} model - Model ID
@@ -197,5 +220,6 @@ module.exports = {
197
220
  fetchLocal,
198
221
  fetchModels,
199
222
  fetchProviderModels,
223
+ getConnectedProviders,
200
224
  chatCompletion
201
225
  };
@@ -294,8 +294,14 @@ const getLoginUrl = async (provider) => {
294
294
  if (!flag) return { success: false, url: null, childProcess: null, isHeadless: false, error: 'Provider not supported for OAuth' };
295
295
 
296
296
  const headless = isHeadless();
297
+ const isGemini = (provider === 'google');
298
+
297
299
  return new Promise((resolve) => {
298
- const child = spawn(BINARY_PATH, [flag, '-no-browser'], { cwd: INSTALL_DIR });
300
+ // For Gemini: use 'pipe' stdin so we can send default project selection
301
+ const child = spawn(BINARY_PATH, [flag, '-no-browser'], {
302
+ cwd: INSTALL_DIR,
303
+ stdio: isGemini ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe']
304
+ });
299
305
  let output = '', resolved = false;
300
306
 
301
307
  const checkForUrl = () => {
@@ -303,14 +309,29 @@ const getLoginUrl = async (provider) => {
303
309
  const urlMatch = output.match(/https?:\/\/[^\s]+/);
304
310
  if (urlMatch) {
305
311
  resolved = true;
306
- resolve({ success: true, url: urlMatch[0], childProcess: child, isHeadless: headless, error: null });
312
+ resolve({ success: true, url: urlMatch[0], childProcess: child, isHeadless: headless, isGemini, error: null });
307
313
  }
308
314
  };
309
315
 
310
- child.stdout.on('data', (data) => { output += data.toString(); checkForUrl(); });
311
- child.stderr.on('data', (data) => { output += data.toString(); checkForUrl(); });
312
- child.on('error', (err) => { if (!resolved) { resolved = true; resolve({ success: false, url: null, childProcess: null, isHeadless: headless, error: err.message }); }});
313
- child.on('close', (code) => { if (!resolved) { resolved = true; resolve({ success: false, url: null, childProcess: null, isHeadless: headless, error: `Process exited with code ${code}` }); }});
316
+ // For Gemini: auto-select default project when prompted
317
+ if (isGemini && child.stdout) {
318
+ child.stdout.on('data', (data) => {
319
+ output += data.toString();
320
+ checkForUrl();
321
+ // When Gemini asks for project selection, send Enter (default) or ALL
322
+ if (data.toString().includes('Enter project ID') && child.stdin) {
323
+ child.stdin.write('\n'); // Select default project
324
+ }
325
+ });
326
+ } else if (child.stdout) {
327
+ child.stdout.on('data', (data) => { output += data.toString(); checkForUrl(); });
328
+ }
329
+
330
+ if (child.stderr) {
331
+ child.stderr.on('data', (data) => { output += data.toString(); checkForUrl(); });
332
+ }
333
+ child.on('error', (err) => { if (!resolved) { resolved = true; resolve({ success: false, url: null, childProcess: null, isHeadless: headless, isGemini: false, error: err.message }); }});
334
+ child.on('close', (code) => { if (!resolved) { resolved = true; resolve({ success: false, url: null, childProcess: null, isHeadless: headless, isGemini: false, error: `Process exited with code ${code}` }); }});
314
335
  });
315
336
  };
316
337