hedgequantx 2.9.18 → 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 -309
  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
@@ -1,387 +0,0 @@
1
- /**
2
- * CLIProxyAPI Manager
3
- *
4
- * Downloads, installs and manages CLIProxyAPI binary for OAuth connections
5
- * to paid AI plans (Claude Pro, ChatGPT Plus, Gemini, etc.)
6
- */
7
-
8
- const os = require('os');
9
- const path = require('path');
10
- const fs = require('fs');
11
- const http = require('http');
12
- const { spawn } = require('child_process');
13
- const { downloadFile, extractTarGz, extractZip } = require('./installer');
14
-
15
- // CLIProxyAPI version and download URLs
16
- const CLIPROXY_VERSION = '6.6.88';
17
- const GITHUB_RELEASE_BASE = 'https://github.com/router-for-me/CLIProxyAPI/releases/download';
18
-
19
- // Installation directory
20
- const INSTALL_DIR = path.join(os.homedir(), '.hqx', 'cliproxy');
21
- const BINARY_NAME = process.platform === 'win32' ? 'cli-proxy-api.exe' : 'cli-proxy-api';
22
- const BINARY_PATH = path.join(INSTALL_DIR, BINARY_NAME);
23
- const PID_FILE = path.join(INSTALL_DIR, 'cliproxy.pid');
24
- // Use default CLIProxyAPI auth directory (where -claude-login saves tokens)
25
- const AUTH_DIR = path.join(os.homedir(), '.cli-proxy-api');
26
-
27
- // Default port
28
- const DEFAULT_PORT = 8317;
29
-
30
- // OAuth callback ports per provider (from CLIProxyAPI)
31
- const CALLBACK_PORTS = {
32
- anthropic: 54545, // Claude: /callback
33
- openai: 1455, // Codex: /auth/callback
34
- google: 8085, // Gemini: /oauth2callback
35
- qwen: null, // Qwen uses polling, no callback
36
- antigravity: 51121, // Antigravity: /oauth-callback
37
- iflow: 11451 // iFlow: /oauth2callback
38
- };
39
-
40
- // OAuth callback paths per provider
41
- const CALLBACK_PATHS = {
42
- anthropic: '/callback',
43
- openai: '/auth/callback',
44
- google: '/oauth2callback',
45
- qwen: null,
46
- antigravity: '/oauth-callback',
47
- iflow: '/oauth2callback'
48
- };
49
-
50
- /** Detect if running in headless/VPS environment (no browser access) */
51
- const isHeadless = () => {
52
- // SSH/Docker/CI = headless
53
- if (process.env.SSH_CLIENT || process.env.SSH_TTY || process.env.SSH_CONNECTION) return true;
54
- if (process.env.DOCKER_CONTAINER || process.env.KUBERNETES_SERVICE_HOST) return true;
55
- if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI) return true;
56
- // Linux without display = headless
57
- if (process.platform === 'linux') {
58
- return !(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
59
- }
60
- // macOS/Windows = local with GUI
61
- return false;
62
- };
63
-
64
- /** Get download URL for current platform */
65
- const getDownloadUrl = () => {
66
- const platform = process.platform, arch = process.arch;
67
- const osMap = { darwin: 'darwin', linux: 'linux', win32: 'windows' };
68
- const extMap = { darwin: 'tar.gz', linux: 'tar.gz', win32: 'zip' };
69
- const archMap = { x64: 'amd64', amd64: 'amd64', arm64: 'arm64' };
70
-
71
- const osName = osMap[platform], ext = extMap[platform], archName = archMap[arch];
72
- if (!osName || !archName) return null;
73
-
74
- const filename = `CLIProxyAPI_${CLIPROXY_VERSION}_${osName}_${archName}.${ext}`;
75
- return { url: `${GITHUB_RELEASE_BASE}/v${CLIPROXY_VERSION}/${filename}`, filename, ext };
76
- };
77
-
78
- /** Check if CLIProxyAPI is installed */
79
- const isInstalled = () => fs.existsSync(BINARY_PATH);
80
-
81
- /** Install CLIProxyAPI */
82
- const install = async (onProgress = null) => {
83
- try {
84
- const download = getDownloadUrl();
85
- if (!download) {
86
- return { success: false, error: 'Unsupported platform' };
87
- }
88
-
89
- // Create install directory
90
- if (!fs.existsSync(INSTALL_DIR)) {
91
- fs.mkdirSync(INSTALL_DIR, { recursive: true });
92
- }
93
- if (!fs.existsSync(AUTH_DIR)) {
94
- fs.mkdirSync(AUTH_DIR, { recursive: true });
95
- }
96
-
97
- const archivePath = path.join(INSTALL_DIR, download.filename);
98
-
99
- // Download
100
- if (onProgress) onProgress('Downloading HQX Connector...', 0);
101
- await downloadFile(download.url, archivePath, (percent) => {
102
- if (onProgress) onProgress('Downloading HQX Connector...', percent);
103
- });
104
-
105
- // Extract
106
- if (onProgress) onProgress('Extracting...', 100);
107
- if (download.ext === 'tar.gz') {
108
- await extractTarGz(archivePath, INSTALL_DIR);
109
- } else {
110
- await extractZip(archivePath, INSTALL_DIR);
111
- }
112
-
113
- // Clean up archive
114
- if (fs.existsSync(archivePath)) {
115
- fs.unlinkSync(archivePath);
116
- }
117
-
118
- // Make executable on Unix
119
- if (process.platform !== 'win32' && fs.existsSync(BINARY_PATH)) {
120
- fs.chmodSync(BINARY_PATH, '755');
121
- }
122
-
123
- if (!fs.existsSync(BINARY_PATH)) {
124
- return { success: false, error: 'Binary not found after extraction' };
125
- }
126
-
127
- return { success: true, error: null };
128
- } catch (error) {
129
- return { success: false, error: error.message };
130
- }
131
- };
132
-
133
- /** Check if CLIProxyAPI is running */
134
- const isRunning = async () => {
135
- if (fs.existsSync(PID_FILE)) {
136
- try {
137
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
138
- process.kill(pid, 0);
139
- return { running: true, pid };
140
- } catch (e) { fs.unlinkSync(PID_FILE); }
141
- }
142
- return new Promise((resolve) => {
143
- const req = http.get(`http://127.0.0.1:${DEFAULT_PORT}/v1/models`, (res) => {
144
- resolve({ running: [200, 401, 403].includes(res.statusCode), pid: null });
145
- });
146
- req.on('error', () => resolve({ running: false, pid: null }));
147
- req.setTimeout(2000, () => { req.destroy(); resolve({ running: false, pid: null }); });
148
- });
149
- };
150
-
151
- const CONFIG_PATH = path.join(INSTALL_DIR, 'config.yaml');
152
-
153
- /** Create or update config file */
154
- const ensureConfig = () => {
155
- fs.writeFileSync(CONFIG_PATH, `# HQX CLIProxyAPI Config
156
- host: "127.0.0.1"
157
- port: ${DEFAULT_PORT}
158
- auth-dir: "${AUTH_DIR}"
159
- debug: false
160
- api-keys:
161
- - "hqx-internal-key"
162
- `);
163
- };
164
-
165
- /** Start CLIProxyAPI */
166
- const start = async () => {
167
- if (!isInstalled()) {
168
- return { success: false, error: 'HQX Connector not installed', pid: null };
169
- }
170
-
171
- const status = await isRunning();
172
- if (status.running) {
173
- return { success: true, error: null, pid: status.pid };
174
- }
175
-
176
- try {
177
- // Ensure config and auth dir exist
178
- if (!fs.existsSync(AUTH_DIR)) fs.mkdirSync(AUTH_DIR, { recursive: true });
179
- ensureConfig();
180
-
181
- const args = ['-config', CONFIG_PATH];
182
-
183
- // Capture stderr for debugging
184
- const logPath = path.join(INSTALL_DIR, 'cliproxy.log');
185
- const logFd = fs.openSync(logPath, 'a');
186
-
187
- const child = spawn(BINARY_PATH, args, {
188
- detached: true,
189
- stdio: ['ignore', logFd, logFd],
190
- cwd: INSTALL_DIR
191
- });
192
-
193
- child.unref();
194
- fs.closeSync(logFd);
195
-
196
- // Save PID
197
- fs.writeFileSync(PID_FILE, String(child.pid));
198
-
199
- // Wait for startup
200
- await new Promise(r => setTimeout(r, 3000));
201
-
202
- const runStatus = await isRunning();
203
- if (runStatus.running) {
204
- return { success: true, error: null, pid: child.pid };
205
- } else {
206
- // Read log for error details
207
- let errorDetail = 'Failed to start HQX Connector';
208
- if (fs.existsSync(logPath)) {
209
- const log = fs.readFileSync(logPath, 'utf8').slice(-500);
210
- if (log) errorDetail += `: ${log.split('\n').pop()}`;
211
- }
212
- return { success: false, error: errorDetail, pid: null };
213
- }
214
- } catch (error) {
215
- return { success: false, error: error.message, pid: null };
216
- }
217
- };
218
-
219
- /** Stop CLIProxyAPI */
220
- const stop = async () => {
221
- const status = await isRunning();
222
- if (!status.running) {
223
- return { success: true, error: null };
224
- }
225
-
226
- try {
227
- if (status.pid) {
228
- process.kill(status.pid, 'SIGTERM');
229
- } else {
230
- // No PID - try to find and kill by port (only cli-proxy-api process)
231
- const { execSync } = require('child_process');
232
- try {
233
- if (process.platform === 'win32') {
234
- // Windows: find PID by port and kill
235
- const result = execSync(`netstat -ano | findstr :${DEFAULT_PORT} | findstr LISTENING`, { encoding: 'utf8' });
236
- const match = result.match(/LISTENING\s+(\d+)/);
237
- if (match) {
238
- const pid = parseInt(match[1]);
239
- if (pid !== process.pid) process.kill(pid, 'SIGTERM');
240
- }
241
- } else {
242
- // Unix: find PID listening on port, filter to only cli-proxy-api
243
- try {
244
- const result = execSync(`lsof -ti:${DEFAULT_PORT} -sTCP:LISTEN 2>/dev/null || true`, { encoding: 'utf8' });
245
- const pids = result.trim().split('\n').filter(p => p && parseInt(p) !== process.pid);
246
- for (const pidStr of pids) {
247
- const pid = parseInt(pidStr);
248
- if (pid && pid !== process.pid) {
249
- try { process.kill(pid, 'SIGTERM'); } catch (e) { /* ignore */ }
250
- }
251
- }
252
- } catch (e) {
253
- // Ignore errors
254
- }
255
- }
256
- } catch (e) {
257
- // Ignore errors - process may already be dead
258
- }
259
- }
260
-
261
- if (fs.existsSync(PID_FILE)) {
262
- fs.unlinkSync(PID_FILE);
263
- }
264
-
265
- // Wait for port to be released
266
- await new Promise(r => setTimeout(r, 1000));
267
-
268
- return { success: true, error: null };
269
- } catch (error) {
270
- return { success: false, error: error.message };
271
- }
272
- };
273
-
274
- /** Ensure CLIProxyAPI is installed and running */
275
- const ensureRunning = async (onProgress = null) => {
276
- if (!isInstalled()) {
277
- if (onProgress) onProgress('Installing HQX Connector...', 0);
278
- const installResult = await install(onProgress);
279
- if (!installResult.success) return installResult;
280
- }
281
- const status = await isRunning();
282
- if (status.running) return { success: true, error: null };
283
- if (onProgress) onProgress('Starting HQX Connector...', 100);
284
- return start();
285
- };
286
-
287
- /** Get OAuth login URL for a provider */
288
- const getLoginUrl = async (provider) => {
289
- const providerFlags = {
290
- anthropic: '-claude-login', openai: '-codex-login', google: '-login',
291
- qwen: '-qwen-login', antigravity: '-antigravity-login', iflow: '-iflow-login'
292
- };
293
- const flag = providerFlags[provider];
294
- if (!flag) return { success: false, url: null, childProcess: null, isHeadless: false, error: 'Provider not supported for OAuth' };
295
-
296
- const headless = isHeadless();
297
- const isGemini = (provider === 'google');
298
-
299
- return new Promise((resolve) => {
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
- });
305
- let output = '', resolved = false;
306
-
307
- const checkForUrl = () => {
308
- if (resolved) return;
309
- const urlMatch = output.match(/https?:\/\/[^\s]+/);
310
- if (urlMatch) {
311
- resolved = true;
312
- resolve({ success: true, url: urlMatch[0], childProcess: child, isHeadless: headless, isGemini, error: null });
313
- }
314
- };
315
-
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}` }); }});
335
- });
336
- };
337
-
338
- /** Get callback port for a provider */
339
- const getCallbackPort = (provider) => CALLBACK_PORTS[provider] || null;
340
-
341
- /** Process OAuth callback URL manually (for VPS/headless) */
342
- const processCallback = (callbackUrl, provider = 'anthropic') => {
343
- return new Promise((resolve) => {
344
- try {
345
- const url = new URL(callbackUrl);
346
- const urlPort = url.port || (url.protocol === 'https:' ? 443 : 80);
347
- const urlPath = url.pathname + url.search;
348
- const expectedPort = CALLBACK_PORTS[provider];
349
-
350
- if (!expectedPort) { resolve({ success: true, error: null }); return; } // Qwen uses polling
351
-
352
- const targetPort = parseInt(urlPort) || expectedPort;
353
- const req = http.get(`http://127.0.0.1:${targetPort}${urlPath}`, (res) => {
354
- let data = '';
355
- res.on('data', chunk => data += chunk);
356
- res.on('end', () => {
357
- resolve(res.statusCode === 200 || res.statusCode === 302
358
- ? { success: true, error: null }
359
- : { success: false, error: `Callback returned ${res.statusCode}: ${data}` });
360
- });
361
- });
362
- req.on('error', (err) => resolve({ success: false, error: `Callback error: ${err.message}` }));
363
- req.setTimeout(10000, () => { req.destroy(); resolve({ success: false, error: 'Callback timeout' }); });
364
- } catch (err) { resolve({ success: false, error: `Invalid URL: ${err.message}` }); }
365
- });
366
- };
367
-
368
- module.exports = {
369
- CLIPROXY_VERSION,
370
- INSTALL_DIR,
371
- BINARY_PATH,
372
- AUTH_DIR,
373
- DEFAULT_PORT,
374
- CALLBACK_PORTS,
375
- CALLBACK_PATHS,
376
- getDownloadUrl,
377
- isInstalled,
378
- isHeadless,
379
- install,
380
- isRunning,
381
- start,
382
- stop,
383
- ensureRunning,
384
- getLoginUrl,
385
- getCallbackPort,
386
- processCallback
387
- };
@@ -1,166 +0,0 @@
1
- /**
2
- * LLM API Proxy Service
3
- *
4
- * Uses LiteLLM (Python) to provide a unified OpenAI-compatible proxy
5
- * for 50+ LLM providers via API keys.
6
- *
7
- * Port: 8318 (different from CLIProxyAPI which uses 8317)
8
- *
9
- * Supported providers (API Key only):
10
- * - MiniMax, DeepSeek, Groq, Mistral, xAI, Perplexity, OpenRouter
11
- * - And 50+ more via LiteLLM
12
- */
13
-
14
- const { LLMProxyManager } = require('./manager');
15
-
16
- // Singleton instance
17
- let proxyManager = null;
18
-
19
- /**
20
- * Get or create proxy manager instance
21
- * @returns {LLMProxyManager}
22
- */
23
- const getManager = () => {
24
- if (!proxyManager) {
25
- proxyManager = new LLMProxyManager();
26
- }
27
- return proxyManager;
28
- };
29
-
30
- /**
31
- * Check if LLM Proxy is installed (Python venv + LiteLLM)
32
- * @returns {boolean}
33
- */
34
- const isInstalled = () => {
35
- return getManager().isInstalled();
36
- };
37
-
38
- /**
39
- * Install LLM Proxy (creates Python venv, installs LiteLLM)
40
- * @param {Function} onProgress - Progress callback (message, percent)
41
- * @returns {Promise<{success: boolean, error?: string}>}
42
- */
43
- const install = async (onProgress = () => {}) => {
44
- return getManager().install(onProgress);
45
- };
46
-
47
- /**
48
- * Check if LLM Proxy is running
49
- * @returns {Promise<{running: boolean, port?: number}>}
50
- */
51
- const isRunning = async () => {
52
- return getManager().isRunning();
53
- };
54
-
55
- /**
56
- * Start LLM Proxy server
57
- * @returns {Promise<{success: boolean, error?: string}>}
58
- */
59
- const start = async () => {
60
- return getManager().start();
61
- };
62
-
63
- /**
64
- * Stop LLM Proxy server
65
- * @returns {Promise<{success: boolean, error?: string}>}
66
- */
67
- const stop = async () => {
68
- return getManager().stop();
69
- };
70
-
71
- /**
72
- * Set API key for a provider
73
- * @param {string} providerId - Provider ID (e.g., 'minimax', 'deepseek')
74
- * @param {string} apiKey - API key
75
- * @returns {Promise<{success: boolean, error?: string}>}
76
- */
77
- const setApiKey = async (providerId, apiKey) => {
78
- return getManager().setApiKey(providerId, apiKey);
79
- };
80
-
81
- /**
82
- * Get API key for a provider
83
- * @param {string} providerId - Provider ID
84
- * @returns {string|null}
85
- */
86
- const getApiKey = (providerId) => {
87
- return getManager().getApiKey(providerId);
88
- };
89
-
90
- /**
91
- * Test connection to a provider
92
- * @param {string} providerId - Provider ID
93
- * @param {string} modelId - Model ID to test
94
- * @returns {Promise<{success: boolean, latency?: number, error?: string}>}
95
- */
96
- const testConnection = async (providerId, modelId) => {
97
- return getManager().testConnection(providerId, modelId);
98
- };
99
-
100
- /**
101
- * Make a chat completion request via LLM Proxy
102
- * @param {string} providerId - Provider ID
103
- * @param {string} modelId - Model ID
104
- * @param {Array} messages - Chat messages
105
- * @param {Object} options - Additional options (temperature, max_tokens, etc.)
106
- * @returns {Promise<{success: boolean, response?: Object, error?: string}>}
107
- */
108
- const chatCompletion = async (providerId, modelId, messages, options = {}) => {
109
- return getManager().chatCompletion(providerId, modelId, messages, options);
110
- };
111
-
112
- /**
113
- * Get LLM Proxy base URL
114
- * @returns {string}
115
- */
116
- const getBaseUrl = () => {
117
- return getManager().getBaseUrl();
118
- };
119
-
120
- /**
121
- * Get port
122
- * @returns {number}
123
- */
124
- const getPort = () => {
125
- return getManager().port;
126
- };
127
-
128
- /**
129
- * Provider mapping for LiteLLM model prefixes
130
- */
131
- const PROVIDER_PREFIXES = {
132
- minimax: 'minimax/',
133
- deepseek: 'deepseek/',
134
- groq: 'groq/',
135
- mistral: 'mistral/',
136
- xai: 'xai/',
137
- perplexity: 'perplexity/',
138
- openrouter: 'openrouter/',
139
- together: 'together_ai/',
140
- anyscale: 'anyscale/',
141
- fireworks: 'fireworks_ai/',
142
- cohere: 'cohere/',
143
- ai21: 'ai21/',
144
- nlp_cloud: 'nlp_cloud/',
145
- replicate: 'replicate/',
146
- bedrock: 'bedrock/',
147
- sagemaker: 'sagemaker/',
148
- vertex: 'vertex_ai/',
149
- palm: 'palm/',
150
- azure: 'azure/',
151
- };
152
-
153
- module.exports = {
154
- isInstalled,
155
- install,
156
- isRunning,
157
- start,
158
- stop,
159
- setApiKey,
160
- getApiKey,
161
- testConnection,
162
- chatCompletion,
163
- getBaseUrl,
164
- getPort,
165
- PROVIDER_PREFIXES,
166
- };