hedgequantx 2.6.101 → 2.6.103
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
package/src/menus/ai-agent.js
CHANGED
|
@@ -782,7 +782,7 @@ const getOAuthConfig = (providerId) => {
|
|
|
782
782
|
|
|
783
783
|
/**
|
|
784
784
|
* Setup OAuth connection for any provider with OAuth support
|
|
785
|
-
*
|
|
785
|
+
* Uses CLIProxyAPI for proper OAuth handling and API access
|
|
786
786
|
*/
|
|
787
787
|
const setupOAuthConnection = async (provider) => {
|
|
788
788
|
const config = getOAuthConfig(provider.id);
|
|
@@ -792,8 +792,8 @@ const setupOAuthConnection = async (provider) => {
|
|
|
792
792
|
return await selectProviderOption(provider);
|
|
793
793
|
}
|
|
794
794
|
|
|
795
|
-
// Use
|
|
796
|
-
return await
|
|
795
|
+
// Use CLIProxyAPI for OAuth flow - it handles token exchange and API calls
|
|
796
|
+
return await setupProxyOAuth(provider, config);
|
|
797
797
|
};
|
|
798
798
|
|
|
799
799
|
/**
|
|
@@ -948,12 +948,169 @@ const setupRemoteOAuth = async (provider, config) => {
|
|
|
948
948
|
// NOTE: promptForModelName was removed - models MUST come from API (RULES.md)
|
|
949
949
|
|
|
950
950
|
/**
|
|
951
|
-
* Setup OAuth
|
|
952
|
-
*
|
|
951
|
+
* Setup OAuth using CLIProxyAPI
|
|
952
|
+
* CLIProxyAPI handles OAuth flow, token storage, and API calls
|
|
953
|
+
* Models are fetched from CLIProxyAPI /v1/models endpoint
|
|
953
954
|
*/
|
|
954
955
|
const setupProxyOAuth = async (provider, config) => {
|
|
955
|
-
|
|
956
|
-
|
|
956
|
+
const boxWidth = getLogoWidth();
|
|
957
|
+
const W = boxWidth - 2;
|
|
958
|
+
|
|
959
|
+
const makeLine = (content) => {
|
|
960
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
961
|
+
const padding = W - plainLen;
|
|
962
|
+
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// Step 1: Ensure CLIProxyAPI is installed and running
|
|
966
|
+
const spinner = ora({ text: 'Setting up CLIProxyAPI...', color: 'cyan' }).start();
|
|
967
|
+
|
|
968
|
+
try {
|
|
969
|
+
await proxyManager.ensureRunning();
|
|
970
|
+
spinner.succeed('CLIProxyAPI ready');
|
|
971
|
+
} catch (error) {
|
|
972
|
+
spinner.fail(`Failed to start CLIProxyAPI: ${error.message}`);
|
|
973
|
+
console.log();
|
|
974
|
+
console.log(chalk.yellow(' CLIProxyAPI is required for OAuth authentication.'));
|
|
975
|
+
console.log(chalk.gray(' It will be downloaded automatically on first use.'));
|
|
976
|
+
console.log();
|
|
977
|
+
await prompts.waitForEnter();
|
|
978
|
+
return await selectProviderOption(provider);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Step 2: Get OAuth URL from CLIProxyAPI
|
|
982
|
+
spinner.text = 'Getting authorization URL...';
|
|
983
|
+
spinner.start();
|
|
984
|
+
|
|
985
|
+
let authUrl, authState;
|
|
986
|
+
try {
|
|
987
|
+
const authInfo = await proxyManager.getAuthUrl(provider.id);
|
|
988
|
+
authUrl = authInfo.url;
|
|
989
|
+
authState = authInfo.state;
|
|
990
|
+
spinner.succeed('Authorization URL ready');
|
|
991
|
+
} catch (error) {
|
|
992
|
+
spinner.fail(`Failed to get auth URL: ${error.message}`);
|
|
993
|
+
await prompts.waitForEnter();
|
|
994
|
+
return await selectProviderOption(provider);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Step 3: Show instructions to user
|
|
998
|
+
console.clear();
|
|
999
|
+
displayBanner();
|
|
1000
|
+
drawBoxHeaderContinue(`CONNECT ${config.name}`, boxWidth);
|
|
1001
|
+
|
|
1002
|
+
console.log(makeLine(chalk.yellow('CONNECT YOUR ACCOUNT')));
|
|
1003
|
+
console.log(makeLine(''));
|
|
1004
|
+
console.log(makeLine(chalk.white('1. OPEN THE LINK BELOW IN YOUR BROWSER')));
|
|
1005
|
+
console.log(makeLine(''));
|
|
1006
|
+
console.log(makeLine(chalk.white(`2. LOGIN WITH YOUR ${config.accountName.toUpperCase()} ACCOUNT`)));
|
|
1007
|
+
console.log(makeLine(''));
|
|
1008
|
+
console.log(makeLine(chalk.white('3. CLICK "AUTHORIZE"')));
|
|
1009
|
+
console.log(makeLine(''));
|
|
1010
|
+
console.log(makeLine(chalk.green('4. WAIT FOR CONFIRMATION HERE')));
|
|
1011
|
+
console.log(makeLine(chalk.white(' (The page will close automatically)')));
|
|
1012
|
+
console.log(makeLine(''));
|
|
1013
|
+
|
|
1014
|
+
drawBoxFooter(boxWidth);
|
|
1015
|
+
|
|
1016
|
+
// Display URL
|
|
1017
|
+
console.log();
|
|
1018
|
+
console.log(chalk.yellow(' OPEN THIS URL IN YOUR BROWSER:'));
|
|
1019
|
+
console.log();
|
|
1020
|
+
console.log(chalk.cyan(` ${authUrl}`));
|
|
1021
|
+
console.log();
|
|
1022
|
+
|
|
1023
|
+
// Try to open browser automatically
|
|
1024
|
+
const browserOpened = await openBrowser(authUrl);
|
|
1025
|
+
if (browserOpened) {
|
|
1026
|
+
console.log(chalk.gray(' (Browser opened automatically)'));
|
|
1027
|
+
} else {
|
|
1028
|
+
console.log(chalk.gray(' (Copy and paste the URL in your browser)'));
|
|
1029
|
+
}
|
|
1030
|
+
console.log();
|
|
1031
|
+
|
|
1032
|
+
// Step 4: Wait for OAuth callback
|
|
1033
|
+
const waitSpinner = ora({ text: 'Waiting for authorization...', color: 'cyan' }).start();
|
|
1034
|
+
|
|
1035
|
+
try {
|
|
1036
|
+
await proxyManager.waitForAuth(authState, 300000, (status) => {
|
|
1037
|
+
waitSpinner.text = status;
|
|
1038
|
+
});
|
|
1039
|
+
waitSpinner.succeed('Authorization successful!');
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
waitSpinner.fail(`Authorization failed: ${error.message}`);
|
|
1042
|
+
await prompts.waitForEnter();
|
|
1043
|
+
return await selectProviderOption(provider);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Step 5: Fetch models from CLIProxyAPI
|
|
1047
|
+
waitSpinner.text = 'Fetching available models from API...';
|
|
1048
|
+
waitSpinner.start();
|
|
1049
|
+
|
|
1050
|
+
let models = [];
|
|
1051
|
+
try {
|
|
1052
|
+
models = await proxyManager.getModels();
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
waitSpinner.fail(`Failed to fetch models: ${error.message}`);
|
|
1055
|
+
await prompts.waitForEnter();
|
|
1056
|
+
return await selectProviderOption(provider);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// Filter models for this provider
|
|
1060
|
+
const providerPrefixes = {
|
|
1061
|
+
anthropic: ['claude'],
|
|
1062
|
+
openai: ['gpt', 'o1', 'o3', 'o4'],
|
|
1063
|
+
gemini: ['gemini'],
|
|
1064
|
+
qwen: ['qwen'],
|
|
1065
|
+
iflow: ['deepseek', 'kimi', 'glm']
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
const prefixes = providerPrefixes[provider.id] || [];
|
|
1069
|
+
const filteredModels = models.filter(m => {
|
|
1070
|
+
const modelLower = m.toLowerCase();
|
|
1071
|
+
return prefixes.some(p => modelLower.includes(p));
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
// Use filtered models if available, otherwise use all models
|
|
1075
|
+
const availableModels = filteredModels.length > 0 ? filteredModels : models;
|
|
1076
|
+
|
|
1077
|
+
if (!availableModels || availableModels.length === 0) {
|
|
1078
|
+
waitSpinner.fail('No models available');
|
|
1079
|
+
console.log();
|
|
1080
|
+
console.log(chalk.red(' ERROR: No models found for this provider'));
|
|
1081
|
+
console.log(chalk.gray(' Make sure your subscription is active.'));
|
|
1082
|
+
console.log();
|
|
1083
|
+
await prompts.waitForEnter();
|
|
1084
|
+
return await selectProviderOption(provider);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
waitSpinner.succeed(`Found ${availableModels.length} models`);
|
|
1088
|
+
|
|
1089
|
+
// Step 6: Let user select model
|
|
1090
|
+
const selectedModel = await selectModelFromList(availableModels, config.name);
|
|
1091
|
+
if (!selectedModel) {
|
|
1092
|
+
return await selectProviderOption(provider);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Step 7: Save agent config (CLIProxyAPI stores the actual tokens)
|
|
1096
|
+
// We just save a reference to use the proxy
|
|
1097
|
+
const credentials = {
|
|
1098
|
+
useProxy: true,
|
|
1099
|
+
provider: provider.id
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
try {
|
|
1103
|
+
await aiService.addAgent(provider.id, config.optionId, credentials, selectedModel, config.agentName);
|
|
1104
|
+
|
|
1105
|
+
console.log(chalk.green(`\n CONNECTED TO ${config.name}`));
|
|
1106
|
+
console.log(chalk.white(` MODEL: ${selectedModel}`));
|
|
1107
|
+
console.log(chalk.white(' UNLIMITED USAGE WITH YOUR SUBSCRIPTION'));
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
console.log(chalk.red(`\n FAILED TO SAVE: ${error.message}`));
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
await prompts.waitForEnter();
|
|
1113
|
+
return await aiAgentMenu();
|
|
957
1114
|
};
|
|
958
1115
|
|
|
959
1116
|
/**
|
|
@@ -527,13 +527,13 @@ const fetchAnthropicModels = async (apiKey) => {
|
|
|
527
527
|
* Fetch available models from Anthropic API (OAuth auth)
|
|
528
528
|
*
|
|
529
529
|
* @param {string} accessToken - OAuth access token
|
|
530
|
-
* @returns {Promise<
|
|
530
|
+
* @returns {Promise<Object>} { models: Array, error: string|null }
|
|
531
531
|
*
|
|
532
532
|
* Data source: https://api.anthropic.com/v1/models (GET with Bearer token)
|
|
533
533
|
* NO HARDCODED FALLBACK - models must come from API only
|
|
534
534
|
*/
|
|
535
535
|
const fetchAnthropicModelsOAuth = async (accessToken) => {
|
|
536
|
-
if (!accessToken) return null;
|
|
536
|
+
if (!accessToken) return { models: null, error: 'No access token provided' };
|
|
537
537
|
|
|
538
538
|
const modelsUrl = 'https://api.anthropic.com/v1/models';
|
|
539
539
|
|
|
@@ -547,14 +547,11 @@ const fetchAnthropicModelsOAuth = async (accessToken) => {
|
|
|
547
547
|
const response = await makeRequest(modelsUrl, { method: 'GET', headers, timeout: 15000 });
|
|
548
548
|
if (response.data && Array.isArray(response.data)) {
|
|
549
549
|
const models = response.data.map(m => m.id).filter(Boolean);
|
|
550
|
-
if (models.length > 0) return models;
|
|
550
|
+
if (models.length > 0) return { models, error: null };
|
|
551
551
|
}
|
|
552
|
-
return null;
|
|
552
|
+
return { models: null, error: 'API returned empty or invalid response' };
|
|
553
553
|
} catch (error) {
|
|
554
|
-
|
|
555
|
-
console.error('[DEBUG] fetchAnthropicModelsOAuth error:', error.message);
|
|
556
|
-
}
|
|
557
|
-
return null;
|
|
554
|
+
return { models: null, error: error.message };
|
|
558
555
|
}
|
|
559
556
|
};
|
|
560
557
|
|
|
@@ -27,6 +27,7 @@ const PROXY_BIN = path.join(PROXY_DIR, process.platform === 'win32' ? 'cli-proxy
|
|
|
27
27
|
const PROXY_CONFIG = path.join(PROXY_DIR, 'config.yaml');
|
|
28
28
|
const PROXY_AUTH_DIR = path.join(PROXY_DIR, 'auths');
|
|
29
29
|
const API_KEY = 'hqx-local-key';
|
|
30
|
+
const MANAGEMENT_KEY = 'hqx-mgmt-key';
|
|
30
31
|
|
|
31
32
|
// GitHub release URLs
|
|
32
33
|
const getDownloadUrl = () => {
|
|
@@ -202,6 +203,9 @@ const install = async (onProgress = () => {}) => {
|
|
|
202
203
|
auth-dir: "${PROXY_AUTH_DIR}"
|
|
203
204
|
api-keys:
|
|
204
205
|
- "${API_KEY}"
|
|
206
|
+
remote-management:
|
|
207
|
+
secret-key: "${MANAGEMENT_KEY}"
|
|
208
|
+
allow-remote-management: false
|
|
205
209
|
request-retry: 3
|
|
206
210
|
quota-exceeded:
|
|
207
211
|
switch-project: true
|
|
@@ -288,7 +292,7 @@ const ensureRunning = async (onProgress = () => {}) => {
|
|
|
288
292
|
};
|
|
289
293
|
|
|
290
294
|
/**
|
|
291
|
-
* Make request to local proxy
|
|
295
|
+
* Make request to local proxy (API endpoints)
|
|
292
296
|
*/
|
|
293
297
|
const proxyRequest = (method, endpoint, body = null) => {
|
|
294
298
|
return new Promise((resolve, reject) => {
|
|
@@ -329,6 +333,58 @@ const proxyRequest = (method, endpoint, body = null) => {
|
|
|
329
333
|
});
|
|
330
334
|
};
|
|
331
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Make request to local proxy (Management API endpoints)
|
|
338
|
+
* Uses management key for authentication
|
|
339
|
+
*/
|
|
340
|
+
const managementRequest = (method, endpoint, body = null) => {
|
|
341
|
+
return new Promise((resolve, reject) => {
|
|
342
|
+
const options = {
|
|
343
|
+
hostname: '127.0.0.1',
|
|
344
|
+
port: PROXY_PORT,
|
|
345
|
+
path: endpoint,
|
|
346
|
+
method,
|
|
347
|
+
headers: {
|
|
348
|
+
'Authorization': `Bearer ${MANAGEMENT_KEY}`,
|
|
349
|
+
'Content-Type': 'application/json'
|
|
350
|
+
},
|
|
351
|
+
timeout: 30000
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const req = http.request(options, (res) => {
|
|
355
|
+
let data = '';
|
|
356
|
+
res.on('data', chunk => data += chunk);
|
|
357
|
+
res.on('end', () => {
|
|
358
|
+
try {
|
|
359
|
+
const json = JSON.parse(data);
|
|
360
|
+
if (res.statusCode >= 400) {
|
|
361
|
+
reject(new Error(json.error || `HTTP ${res.statusCode}`));
|
|
362
|
+
} else {
|
|
363
|
+
resolve(json);
|
|
364
|
+
}
|
|
365
|
+
} catch (e) {
|
|
366
|
+
if (res.statusCode >= 400) {
|
|
367
|
+
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
368
|
+
} else {
|
|
369
|
+
resolve(data);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
req.on('error', reject);
|
|
376
|
+
req.on('timeout', () => {
|
|
377
|
+
req.destroy();
|
|
378
|
+
reject(new Error('Request timeout'));
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (body) {
|
|
382
|
+
req.write(JSON.stringify(body));
|
|
383
|
+
}
|
|
384
|
+
req.end();
|
|
385
|
+
});
|
|
386
|
+
};
|
|
387
|
+
|
|
332
388
|
/**
|
|
333
389
|
* Get OAuth authorization URL for a provider
|
|
334
390
|
* @param {string} provider - Provider ID (anthropic, openai, gemini, qwen, iflow)
|
|
@@ -350,7 +406,7 @@ const getAuthUrl = async (provider) => {
|
|
|
350
406
|
throw new Error(`Unknown provider: ${provider}`);
|
|
351
407
|
}
|
|
352
408
|
|
|
353
|
-
const response = await
|
|
409
|
+
const response = await managementRequest('GET', endpoint);
|
|
354
410
|
|
|
355
411
|
if (response.status !== 'ok') {
|
|
356
412
|
throw new Error(response.error || 'Failed to get auth URL');
|
|
@@ -368,7 +424,7 @@ const getAuthUrl = async (provider) => {
|
|
|
368
424
|
* @returns {Promise<{status: string, error?: string}>}
|
|
369
425
|
*/
|
|
370
426
|
const pollAuthStatus = async (state) => {
|
|
371
|
-
const response = await
|
|
427
|
+
const response = await managementRequest('GET', `/v0/management/get-auth-status?state=${state}`);
|
|
372
428
|
return response;
|
|
373
429
|
};
|
|
374
430
|
|
|
@@ -422,7 +478,7 @@ const getAuthFiles = async () => {
|
|
|
422
478
|
await ensureRunning();
|
|
423
479
|
|
|
424
480
|
try {
|
|
425
|
-
const response = await
|
|
481
|
+
const response = await managementRequest('GET', '/v0/management/auth-files');
|
|
426
482
|
return response.files || [];
|
|
427
483
|
} catch (e) {
|
|
428
484
|
return [];
|