hedgequantx 2.6.161 → 2.6.163
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 +1 -1
- package/src/menus/ai-agent-connect.js +181 -0
- package/src/menus/ai-agent-models.js +219 -0
- package/src/menus/ai-agent-oauth.js +292 -0
- package/src/menus/ai-agent-ui.js +141 -0
- package/src/menus/ai-agent.js +88 -1489
- package/src/pages/algo/copy-engine.js +449 -0
- package/src/pages/algo/copy-trading.js +11 -543
- package/src/pages/algo/smart-logs-data.js +218 -0
- package/src/pages/algo/smart-logs.js +9 -214
- package/src/pages/algo/ui-constants.js +144 -0
- package/src/pages/algo/ui-summary.js +184 -0
- package/src/pages/algo/ui.js +42 -526
- package/src/pages/stats-calculations.js +191 -0
- package/src/pages/stats-ui.js +381 -0
- package/src/pages/stats.js +14 -507
- package/src/services/ai/client-analysis.js +194 -0
- package/src/services/ai/client-models.js +333 -0
- package/src/services/ai/client.js +6 -489
- package/src/services/ai/index.js +2 -257
- package/src/services/ai/providers/direct-providers.js +323 -0
- package/src/services/ai/providers/index.js +8 -472
- package/src/services/ai/providers/other-providers.js +104 -0
- package/src/services/ai/proxy-install.js +249 -0
- package/src/services/ai/proxy-manager.js +29 -411
- package/src/services/ai/proxy-remote.js +161 -0
- package/src/services/ai/supervisor-optimize.js +215 -0
- package/src/services/ai/supervisor-sync.js +178 -0
- package/src/services/ai/supervisor.js +50 -515
- package/src/services/ai/validation.js +250 -0
- package/src/services/hqx-server-events.js +110 -0
- package/src/services/hqx-server-handlers.js +217 -0
- package/src/services/hqx-server-latency.js +136 -0
- package/src/services/hqx-server.js +51 -403
- package/src/services/position-constants.js +28 -0
- package/src/services/position-exit-logic.js +174 -0
- package/src/services/position-manager.js +90 -629
- package/src/services/position-momentum.js +206 -0
- package/src/services/projectx/accounts.js +142 -0
- package/src/services/projectx/index.js +40 -289
- package/src/services/projectx/trading.js +180 -0
- package/src/services/rithmic/contracts.js +218 -0
- package/src/services/rithmic/handlers.js +2 -208
- package/src/services/rithmic/index.js +28 -712
- package/src/services/rithmic/latency-tracker.js +182 -0
- package/src/services/rithmic/market-data-decoders.js +229 -0
- package/src/services/rithmic/market-data.js +1 -278
- package/src/services/rithmic/orders-fast.js +246 -0
- package/src/services/rithmic/orders.js +1 -251
- package/src/services/rithmic/proto-decoders.js +403 -0
- package/src/services/rithmic/protobuf.js +7 -443
- package/src/services/rithmic/specs.js +146 -0
- package/src/services/rithmic/trade-history.js +254 -0
- package/src/services/strategy/hft-signal-calc.js +147 -0
- package/src/services/strategy/hft-tick.js +33 -133
- package/src/services/tradovate/index.js +6 -119
- package/src/services/tradovate/orders.js +145 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLIProxyAPI Manager
|
|
3
3
|
*
|
|
4
|
-
* Automatically
|
|
5
|
-
*
|
|
6
|
-
* without needing to understand technical details.
|
|
4
|
+
* Automatically manages CLIProxyAPI for connecting AI subscription accounts
|
|
5
|
+
* (ChatGPT Plus, Claude Pro, etc.) without needing API keys.
|
|
7
6
|
*
|
|
8
7
|
* Flow:
|
|
9
8
|
* 1. User selects "Connect Account" in CLI
|
|
@@ -12,54 +11,32 @@
|
|
|
12
11
|
* 4. Callback to localhost → Token saved → Models available
|
|
13
12
|
*/
|
|
14
13
|
|
|
15
|
-
const https = require('https');
|
|
16
14
|
const http = require('http');
|
|
17
|
-
const fs = require('fs');
|
|
18
|
-
const path = require('path');
|
|
19
|
-
const os = require('os');
|
|
20
15
|
const { spawn, exec } = require('child_process');
|
|
21
16
|
|
|
22
|
-
//
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const arch = process.arch;
|
|
36
|
-
|
|
37
|
-
let osName, archName, ext;
|
|
38
|
-
|
|
39
|
-
if (platform === 'darwin') {
|
|
40
|
-
osName = 'darwin';
|
|
41
|
-
archName = arch === 'arm64' ? 'arm64' : 'amd64';
|
|
42
|
-
ext = 'tar.gz';
|
|
43
|
-
} else if (platform === 'win32') {
|
|
44
|
-
osName = 'windows';
|
|
45
|
-
archName = arch === 'arm64' ? 'arm64' : 'amd64';
|
|
46
|
-
ext = 'zip';
|
|
47
|
-
} else {
|
|
48
|
-
osName = 'linux';
|
|
49
|
-
archName = arch === 'arm64' ? 'arm64' : 'amd64';
|
|
50
|
-
ext = 'tar.gz';
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const version = PROXY_VERSION.replace('v', '');
|
|
54
|
-
return `https://github.com/router-for-me/CLIProxyAPI/releases/download/${PROXY_VERSION}/CLIProxyAPI_${version}_${osName}_${archName}.${ext}`;
|
|
55
|
-
};
|
|
17
|
+
// Import from sub-modules
|
|
18
|
+
const {
|
|
19
|
+
PROXY_PORT,
|
|
20
|
+
PROXY_DIR,
|
|
21
|
+
PROXY_BIN,
|
|
22
|
+
PROXY_CONFIG,
|
|
23
|
+
API_KEY,
|
|
24
|
+
MANAGEMENT_KEY,
|
|
25
|
+
isInstalled,
|
|
26
|
+
install,
|
|
27
|
+
isConfigValid,
|
|
28
|
+
rewriteConfig,
|
|
29
|
+
} = require('./proxy-install');
|
|
56
30
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
31
|
+
const {
|
|
32
|
+
createRemoteSession,
|
|
33
|
+
pollRemoteSession,
|
|
34
|
+
getRemoteTokens,
|
|
35
|
+
waitForRemoteAuth,
|
|
36
|
+
isServerEnvironment,
|
|
37
|
+
canOpenBrowser,
|
|
38
|
+
REMOTE_OAUTH_URL
|
|
39
|
+
} = require('./proxy-remote');
|
|
63
40
|
|
|
64
41
|
/**
|
|
65
42
|
* Check if CLIProxyAPI is running
|
|
@@ -88,140 +65,6 @@ const isRunning = async () => {
|
|
|
88
65
|
});
|
|
89
66
|
};
|
|
90
67
|
|
|
91
|
-
/**
|
|
92
|
-
* Download file from URL
|
|
93
|
-
*/
|
|
94
|
-
const downloadFile = (url, dest) => {
|
|
95
|
-
return new Promise((resolve, reject) => {
|
|
96
|
-
const file = fs.createWriteStream(dest);
|
|
97
|
-
|
|
98
|
-
const request = (url) => {
|
|
99
|
-
https.get(url, (res) => {
|
|
100
|
-
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
101
|
-
// Follow redirect
|
|
102
|
-
request(res.headers.location);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (res.statusCode !== 200) {
|
|
107
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
res.pipe(file);
|
|
112
|
-
file.on('finish', () => {
|
|
113
|
-
file.close();
|
|
114
|
-
resolve();
|
|
115
|
-
});
|
|
116
|
-
}).on('error', (err) => {
|
|
117
|
-
fs.unlink(dest, () => {});
|
|
118
|
-
reject(err);
|
|
119
|
-
});
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
request(url);
|
|
123
|
-
});
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Extract tar.gz archive
|
|
128
|
-
*/
|
|
129
|
-
const extractTarGz = (archive, dest) => {
|
|
130
|
-
return new Promise((resolve, reject) => {
|
|
131
|
-
exec(`tar -xzf "${archive}" -C "${dest}"`, (err) => {
|
|
132
|
-
if (err) reject(err);
|
|
133
|
-
else resolve();
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Extract zip archive (Windows)
|
|
140
|
-
*/
|
|
141
|
-
const extractZip = (archive, dest) => {
|
|
142
|
-
return new Promise((resolve, reject) => {
|
|
143
|
-
exec(`powershell -command "Expand-Archive -Path '${archive}' -DestinationPath '${dest}' -Force"`, (err) => {
|
|
144
|
-
if (err) reject(err);
|
|
145
|
-
else resolve();
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Download and install CLIProxyAPI
|
|
152
|
-
* @param {Function} onProgress - Progress callback (message)
|
|
153
|
-
*/
|
|
154
|
-
const install = async (onProgress = () => {}) => {
|
|
155
|
-
// Create directories
|
|
156
|
-
if (!fs.existsSync(PROXY_DIR)) {
|
|
157
|
-
fs.mkdirSync(PROXY_DIR, { recursive: true });
|
|
158
|
-
}
|
|
159
|
-
if (!fs.existsSync(PROXY_AUTH_DIR)) {
|
|
160
|
-
fs.mkdirSync(PROXY_AUTH_DIR, { recursive: true });
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
onProgress('Downloading CLIProxyAPI...');
|
|
164
|
-
|
|
165
|
-
const downloadUrl = getDownloadUrl();
|
|
166
|
-
const ext = process.platform === 'win32' ? 'zip' : 'tar.gz';
|
|
167
|
-
const archivePath = path.join(PROXY_DIR, `cliproxyapi.${ext}`);
|
|
168
|
-
|
|
169
|
-
// Download
|
|
170
|
-
await downloadFile(downloadUrl, archivePath);
|
|
171
|
-
|
|
172
|
-
onProgress('Extracting...');
|
|
173
|
-
|
|
174
|
-
// Extract
|
|
175
|
-
if (ext === 'zip') {
|
|
176
|
-
await extractZip(archivePath, PROXY_DIR);
|
|
177
|
-
} else {
|
|
178
|
-
await extractTarGz(archivePath, PROXY_DIR);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Find the binary (it might be in a subdirectory)
|
|
182
|
-
const possibleBins = [
|
|
183
|
-
path.join(PROXY_DIR, 'cli-proxy-api'),
|
|
184
|
-
path.join(PROXY_DIR, 'cli-proxy-api.exe'),
|
|
185
|
-
path.join(PROXY_DIR, 'CLIProxyAPI', 'cli-proxy-api'),
|
|
186
|
-
path.join(PROXY_DIR, 'CLIProxyAPI', 'cli-proxy-api.exe')
|
|
187
|
-
];
|
|
188
|
-
|
|
189
|
-
for (const bin of possibleBins) {
|
|
190
|
-
if (fs.existsSync(bin) && bin !== PROXY_BIN) {
|
|
191
|
-
fs.renameSync(bin, PROXY_BIN);
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Make executable (Unix)
|
|
197
|
-
if (process.platform !== 'win32') {
|
|
198
|
-
fs.chmodSync(PROXY_BIN, 0o755);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Create config file
|
|
202
|
-
const config = `port: ${PROXY_PORT}
|
|
203
|
-
auth-dir: "${PROXY_AUTH_DIR}"
|
|
204
|
-
api-keys:
|
|
205
|
-
- "${API_KEY}"
|
|
206
|
-
remote-management:
|
|
207
|
-
secret-key: "${MANAGEMENT_KEY}"
|
|
208
|
-
allow-remote-management: false
|
|
209
|
-
request-retry: 3
|
|
210
|
-
quota-exceeded:
|
|
211
|
-
switch-project: true
|
|
212
|
-
switch-preview-model: true
|
|
213
|
-
`;
|
|
214
|
-
|
|
215
|
-
fs.writeFileSync(PROXY_CONFIG, config);
|
|
216
|
-
|
|
217
|
-
// Cleanup archive
|
|
218
|
-
fs.unlinkSync(archivePath);
|
|
219
|
-
|
|
220
|
-
onProgress('Installation complete');
|
|
221
|
-
|
|
222
|
-
return true;
|
|
223
|
-
};
|
|
224
|
-
|
|
225
68
|
/**
|
|
226
69
|
* Start CLIProxyAPI in background
|
|
227
70
|
*/
|
|
@@ -243,7 +86,6 @@ const start = async () => {
|
|
|
243
86
|
|
|
244
87
|
proc.unref();
|
|
245
88
|
|
|
246
|
-
// Wait for it to start
|
|
247
89
|
let attempts = 0;
|
|
248
90
|
const checkInterval = setInterval(async () => {
|
|
249
91
|
attempts++;
|
|
@@ -271,79 +113,19 @@ const stop = async () => {
|
|
|
271
113
|
});
|
|
272
114
|
};
|
|
273
115
|
|
|
274
|
-
/**
|
|
275
|
-
* Check if config has correct management key (plain text, not hashed)
|
|
276
|
-
* CLIProxyAPI expects plain text key in config, it does bcrypt hashing internally
|
|
277
|
-
* @returns {boolean} true if config is correct, false if needs update
|
|
278
|
-
*/
|
|
279
|
-
const isConfigValid = () => {
|
|
280
|
-
if (!fs.existsSync(PROXY_CONFIG)) return false;
|
|
281
|
-
|
|
282
|
-
try {
|
|
283
|
-
const config = fs.readFileSync(PROXY_CONFIG, 'utf8');
|
|
284
|
-
|
|
285
|
-
// Check if management key section exists
|
|
286
|
-
if (!config.includes('remote-management:') || !config.includes('secret-key:')) {
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Check if key is hashed (bcrypt hashes start with $2a$, $2b$, or $2y$)
|
|
291
|
-
// Plain text key should be 'hqx-mgmt-key', not a bcrypt hash
|
|
292
|
-
if (config.includes('$2a$') || config.includes('$2b$') || config.includes('$2y$')) {
|
|
293
|
-
return false; // Config has hashed key, needs to be plain text
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Check if it has our expected management key
|
|
297
|
-
if (!config.includes(MANAGEMENT_KEY)) {
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return true;
|
|
302
|
-
} catch (e) {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Rewrite config file with correct settings
|
|
309
|
-
*/
|
|
310
|
-
const rewriteConfig = () => {
|
|
311
|
-
const config = `port: ${PROXY_PORT}
|
|
312
|
-
auth-dir: "${PROXY_AUTH_DIR}"
|
|
313
|
-
api-keys:
|
|
314
|
-
- "${API_KEY}"
|
|
315
|
-
remote-management:
|
|
316
|
-
secret-key: "${MANAGEMENT_KEY}"
|
|
317
|
-
allow-remote-management: false
|
|
318
|
-
request-retry: 3
|
|
319
|
-
quota-exceeded:
|
|
320
|
-
switch-project: true
|
|
321
|
-
switch-preview-model: true
|
|
322
|
-
`;
|
|
323
|
-
|
|
324
|
-
// Ensure directory exists
|
|
325
|
-
if (!fs.existsSync(PROXY_DIR)) {
|
|
326
|
-
fs.mkdirSync(PROXY_DIR, { recursive: true });
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
fs.writeFileSync(PROXY_CONFIG, config);
|
|
330
|
-
};
|
|
331
|
-
|
|
332
116
|
/**
|
|
333
117
|
* Ensure CLIProxyAPI is installed and running
|
|
334
118
|
* @param {Function} onProgress - Progress callback
|
|
335
119
|
*/
|
|
336
120
|
const ensureRunning = async (onProgress = () => {}) => {
|
|
337
|
-
// Check if config needs to be fixed (e.g., has hashed key instead of plain text)
|
|
338
121
|
const configValid = isConfigValid();
|
|
339
122
|
|
|
340
123
|
if (await isRunning()) {
|
|
341
124
|
if (!configValid) {
|
|
342
|
-
// Config is invalid but proxy is running - need to restart with correct config
|
|
343
125
|
onProgress('Updating proxy configuration...');
|
|
344
126
|
rewriteConfig();
|
|
345
127
|
await stop();
|
|
346
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
128
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
347
129
|
onProgress('Restarting proxy with updated config...');
|
|
348
130
|
await start();
|
|
349
131
|
}
|
|
@@ -354,7 +136,6 @@ const ensureRunning = async (onProgress = () => {}) => {
|
|
|
354
136
|
onProgress('Installing AI proxy (one-time setup)...');
|
|
355
137
|
await install(onProgress);
|
|
356
138
|
} else if (!configValid) {
|
|
357
|
-
// Binary exists but config is invalid - fix it
|
|
358
139
|
onProgress('Fixing proxy configuration...');
|
|
359
140
|
rewriteConfig();
|
|
360
141
|
}
|
|
@@ -409,7 +190,6 @@ const proxyRequest = (method, endpoint, body = null) => {
|
|
|
409
190
|
|
|
410
191
|
/**
|
|
411
192
|
* Make request to local proxy (Management API endpoints)
|
|
412
|
-
* Uses management key for authentication
|
|
413
193
|
*/
|
|
414
194
|
const managementRequest = (method, endpoint, body = null) => {
|
|
415
195
|
return new Promise((resolve, reject) => {
|
|
@@ -467,8 +247,6 @@ const managementRequest = (method, endpoint, body = null) => {
|
|
|
467
247
|
const getAuthUrl = async (provider) => {
|
|
468
248
|
await ensureRunning();
|
|
469
249
|
|
|
470
|
-
// Use is_webui=true to start the callback server on port 54545
|
|
471
|
-
// Without this, CLIProxyAPI generates the URL but doesn't listen for callbacks
|
|
472
250
|
const endpoints = {
|
|
473
251
|
anthropic: '/v0/management/anthropic-auth-url?is_webui=true',
|
|
474
252
|
openai: '/v0/management/codex-auth-url?is_webui=true',
|
|
@@ -495,12 +273,12 @@ const getAuthUrl = async (provider) => {
|
|
|
495
273
|
};
|
|
496
274
|
|
|
497
275
|
/**
|
|
498
|
-
*
|
|
499
|
-
* @param {string}
|
|
276
|
+
* Submit OAuth callback
|
|
277
|
+
* @param {string} callbackUrl - Full callback URL
|
|
278
|
+
* @param {string} provider - Provider ID
|
|
500
279
|
* @returns {Promise<boolean>}
|
|
501
280
|
*/
|
|
502
281
|
const submitCallback = async (callbackUrl, provider = 'anthropic') => {
|
|
503
|
-
// Parse the callback URL
|
|
504
282
|
let url;
|
|
505
283
|
try {
|
|
506
284
|
url = new URL(callbackUrl);
|
|
@@ -515,9 +293,6 @@ const submitCallback = async (callbackUrl, provider = 'anthropic') => {
|
|
|
515
293
|
throw new Error('Missing code or state in callback URL');
|
|
516
294
|
}
|
|
517
295
|
|
|
518
|
-
// Each provider has its own OAuth callback port and path in CLIProxyAPI
|
|
519
|
-
// We need to submit the callback to the correct port
|
|
520
|
-
// Ports are determined by the redirect_uri in the OAuth URL from CLIProxyAPI
|
|
521
296
|
const providerConfig = {
|
|
522
297
|
anthropic: { port: 54545, path: '/callback' },
|
|
523
298
|
openai: { port: 1455, path: '/auth/callback' },
|
|
@@ -528,9 +303,6 @@ const submitCallback = async (callbackUrl, provider = 'anthropic') => {
|
|
|
528
303
|
|
|
529
304
|
const config = providerConfig[provider] || providerConfig.anthropic;
|
|
530
305
|
|
|
531
|
-
// Submit to the provider's OAuth callback port directly
|
|
532
|
-
// Pass ALL query parameters from the callback URL, not just code and state
|
|
533
|
-
// Some providers (like Gemini) require additional params like scope, authuser, etc.
|
|
534
306
|
return new Promise((resolve, reject) => {
|
|
535
307
|
const callbackPath = `${config.path}?${url.searchParams.toString()}`;
|
|
536
308
|
|
|
@@ -544,7 +316,6 @@ const submitCallback = async (callbackUrl, provider = 'anthropic') => {
|
|
|
544
316
|
let data = '';
|
|
545
317
|
res.on('data', chunk => data += chunk);
|
|
546
318
|
res.on('end', () => {
|
|
547
|
-
// Success if we get a redirect or success page
|
|
548
319
|
if (res.statusCode === 200 || res.statusCode === 302 || res.statusCode === 301) {
|
|
549
320
|
resolve(true);
|
|
550
321
|
} else {
|
|
@@ -604,7 +375,7 @@ const waitForAuth = async (state, timeoutMs = 300000, onStatus = () => {}) => {
|
|
|
604
375
|
|
|
605
376
|
/**
|
|
606
377
|
* Get available models from the proxy
|
|
607
|
-
* @param {string} [provider] - Optional provider to filter by
|
|
378
|
+
* @param {string} [provider] - Optional provider to filter by
|
|
608
379
|
* @returns {Promise<Array<string>>}
|
|
609
380
|
*/
|
|
610
381
|
const getModels = async (provider = null) => {
|
|
@@ -615,9 +386,7 @@ const getModels = async (provider = null) => {
|
|
|
615
386
|
if (response.data && Array.isArray(response.data)) {
|
|
616
387
|
let models = response.data;
|
|
617
388
|
|
|
618
|
-
// Filter by provider if specified (using owned_by field from API)
|
|
619
389
|
if (provider) {
|
|
620
|
-
// Map our provider IDs to the owned_by values from the API
|
|
621
390
|
const ownerMap = {
|
|
622
391
|
anthropic: 'anthropic',
|
|
623
392
|
openai: 'openai',
|
|
@@ -693,157 +462,6 @@ const getProviderFromAuthFile = (filename) => {
|
|
|
693
462
|
return 'unknown';
|
|
694
463
|
};
|
|
695
464
|
|
|
696
|
-
// ============================================
|
|
697
|
-
// REMOTE OAUTH (for VPS/Server users)
|
|
698
|
-
// Uses cli.hedgequantx.com as OAuth relay
|
|
699
|
-
// ============================================
|
|
700
|
-
|
|
701
|
-
const REMOTE_OAUTH_URL = 'https://cli.hedgequantx.com';
|
|
702
|
-
|
|
703
|
-
/**
|
|
704
|
-
* Make HTTPS request
|
|
705
|
-
*/
|
|
706
|
-
const httpsRequest = (url, options, body = null) => {
|
|
707
|
-
return new Promise((resolve, reject) => {
|
|
708
|
-
const parsedUrl = new URL(url);
|
|
709
|
-
const req = https.request({
|
|
710
|
-
hostname: parsedUrl.hostname,
|
|
711
|
-
port: 443,
|
|
712
|
-
path: parsedUrl.pathname + parsedUrl.search,
|
|
713
|
-
method: options.method || 'GET',
|
|
714
|
-
headers: options.headers || {}
|
|
715
|
-
}, (res) => {
|
|
716
|
-
let data = '';
|
|
717
|
-
res.on('data', chunk => data += chunk);
|
|
718
|
-
res.on('end', () => {
|
|
719
|
-
try {
|
|
720
|
-
resolve(JSON.parse(data));
|
|
721
|
-
} catch (e) {
|
|
722
|
-
resolve(data);
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
req.on('error', reject);
|
|
728
|
-
req.on('timeout', () => {
|
|
729
|
-
req.destroy();
|
|
730
|
-
reject(new Error('Request timeout'));
|
|
731
|
-
});
|
|
732
|
-
|
|
733
|
-
if (body) {
|
|
734
|
-
req.write(typeof body === 'string' ? body : JSON.stringify(body));
|
|
735
|
-
}
|
|
736
|
-
req.end();
|
|
737
|
-
});
|
|
738
|
-
};
|
|
739
|
-
|
|
740
|
-
/**
|
|
741
|
-
* Create Remote OAuth session
|
|
742
|
-
* @param {string} provider - Provider ID (anthropic, openai, gemini)
|
|
743
|
-
* @returns {Promise<{sessionId: string, authUrl: string}>}
|
|
744
|
-
*/
|
|
745
|
-
const createRemoteSession = async (provider) => {
|
|
746
|
-
const response = await httpsRequest(
|
|
747
|
-
`${REMOTE_OAUTH_URL}/oauth/session/create`,
|
|
748
|
-
{
|
|
749
|
-
method: 'POST',
|
|
750
|
-
headers: { 'Content-Type': 'application/json' }
|
|
751
|
-
},
|
|
752
|
-
JSON.stringify({ provider })
|
|
753
|
-
);
|
|
754
|
-
|
|
755
|
-
if (response.error) {
|
|
756
|
-
throw new Error(response.error);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
return {
|
|
760
|
-
sessionId: response.sessionId,
|
|
761
|
-
authUrl: response.authUrl
|
|
762
|
-
};
|
|
763
|
-
};
|
|
764
|
-
|
|
765
|
-
/**
|
|
766
|
-
* Poll Remote OAuth session status
|
|
767
|
-
* @param {string} sessionId - Session ID from createRemoteSession
|
|
768
|
-
* @returns {Promise<{status: string, error?: string}>}
|
|
769
|
-
*/
|
|
770
|
-
const pollRemoteSession = async (sessionId) => {
|
|
771
|
-
const response = await httpsRequest(
|
|
772
|
-
`${REMOTE_OAUTH_URL}/oauth/session/${sessionId}/status`,
|
|
773
|
-
{ method: 'GET' }
|
|
774
|
-
);
|
|
775
|
-
return response;
|
|
776
|
-
};
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* Get tokens from Remote OAuth session
|
|
780
|
-
* @param {string} sessionId - Session ID
|
|
781
|
-
* @returns {Promise<{provider: string, tokens: Object}>}
|
|
782
|
-
*/
|
|
783
|
-
const getRemoteTokens = async (sessionId) => {
|
|
784
|
-
const response = await httpsRequest(
|
|
785
|
-
`${REMOTE_OAUTH_URL}/oauth/session/${sessionId}/tokens`,
|
|
786
|
-
{ method: 'GET' }
|
|
787
|
-
);
|
|
788
|
-
|
|
789
|
-
if (response.error) {
|
|
790
|
-
throw new Error(response.error);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
return response;
|
|
794
|
-
};
|
|
795
|
-
|
|
796
|
-
/**
|
|
797
|
-
* Wait for Remote OAuth to complete
|
|
798
|
-
* @param {string} sessionId - Session ID
|
|
799
|
-
* @param {number} timeoutMs - Timeout in milliseconds
|
|
800
|
-
* @param {Function} onStatus - Status callback
|
|
801
|
-
* @returns {Promise<{provider: string, tokens: Object}>}
|
|
802
|
-
*/
|
|
803
|
-
const waitForRemoteAuth = async (sessionId, timeoutMs = 300000, onStatus = () => {}) => {
|
|
804
|
-
const startTime = Date.now();
|
|
805
|
-
|
|
806
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
807
|
-
const status = await pollRemoteSession(sessionId);
|
|
808
|
-
|
|
809
|
-
if (status.status === 'success') {
|
|
810
|
-
return await getRemoteTokens(sessionId);
|
|
811
|
-
} else if (status.status === 'error') {
|
|
812
|
-
throw new Error(status.error || 'Authentication failed');
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
onStatus('Waiting for authorization...');
|
|
816
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
throw new Error('Authentication timeout');
|
|
820
|
-
};
|
|
821
|
-
|
|
822
|
-
/**
|
|
823
|
-
* Detect if we're running on a server (no display/browser)
|
|
824
|
-
* @returns {boolean}
|
|
825
|
-
*/
|
|
826
|
-
const isServerEnvironment = () => {
|
|
827
|
-
// Check for common server indicators
|
|
828
|
-
if (process.env.SSH_CONNECTION || process.env.SSH_CLIENT) return true;
|
|
829
|
-
if (process.env.DISPLAY === undefined && process.platform === 'linux') return true;
|
|
830
|
-
if (process.env.TERM === 'dumb') return true;
|
|
831
|
-
if (process.env.HQX_REMOTE_OAUTH === '1') return true; // Force remote mode
|
|
832
|
-
return false;
|
|
833
|
-
};
|
|
834
|
-
|
|
835
|
-
/**
|
|
836
|
-
* Detect if browser can be opened
|
|
837
|
-
* @returns {boolean}
|
|
838
|
-
*/
|
|
839
|
-
const canOpenBrowser = () => {
|
|
840
|
-
// On macOS/Windows, we can usually open browser
|
|
841
|
-
if (process.platform === 'darwin' || process.platform === 'win32') return true;
|
|
842
|
-
// On Linux, check for display
|
|
843
|
-
if (process.env.DISPLAY) return true;
|
|
844
|
-
return false;
|
|
845
|
-
};
|
|
846
|
-
|
|
847
465
|
module.exports = {
|
|
848
466
|
// Local OAuth (CLIProxyAPI)
|
|
849
467
|
isInstalled,
|