hedgequantx 1.8.48 → 2.3.0
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/README.md +7 -6
- package/bin/cli.js +13 -7
- package/dist/algo/copy-engine.js +3 -0
- package/dist/algo/copy-engine.jsc +0 -0
- package/dist/algo/engine.js +3 -0
- package/dist/algo/engine.jsc +0 -0
- package/dist/algo/market-data-rithmic.js +3 -0
- package/dist/algo/market-data-rithmic.jsc +0 -0
- package/dist/algo/market-data.js +3 -0
- package/dist/algo/market-data.jsc +0 -0
- package/dist/algo/rithmic/connection.js +3 -0
- package/dist/algo/rithmic/connection.jsc +0 -0
- package/dist/algo/rithmic/constants.js +3 -0
- package/dist/algo/rithmic/constants.jsc +0 -0
- package/dist/algo/rithmic/index.js +3 -0
- package/dist/algo/rithmic/index.jsc +0 -0
- package/dist/algo/rithmic/market-data.js +3 -0
- package/dist/algo/rithmic/market-data.jsc +0 -0
- package/dist/algo/rithmic/pnl.js +3 -0
- package/dist/algo/rithmic/pnl.jsc +0 -0
- package/dist/algo/rithmic/pool.js +3 -0
- package/dist/algo/rithmic/pool.jsc +0 -0
- package/dist/algo/rithmic/trading.js +3 -0
- package/dist/algo/rithmic/trading.jsc +0 -0
- package/dist/algo/rithmic-decoder.js +3 -0
- package/dist/algo/rithmic-decoder.jsc +0 -0
- package/dist/algo/strategies/ultra-scalping-v2.js +3 -0
- package/dist/algo/strategies/ultra-scalping-v2.jsc +0 -0
- package/dist/algo/strategies/ultra-scalping.js +3 -0
- package/dist/algo/strategies/ultra-scalping.jsc +0 -0
- package/dist/algo/trading-api-rithmic.js +3 -0
- package/dist/algo/trading-api-rithmic.jsc +0 -0
- package/dist/algo/trading-api.js +3 -0
- package/dist/algo/trading-api.jsc +0 -0
- package/dist/algo/utils/smart-logger.js +3 -0
- package/dist/algo/utils/smart-logger.jsc +0 -0
- package/dist/algo/utils/smart-logs.js +3 -0
- package/dist/algo/utils/smart-logs.jsc +0 -0
- package/package.json +33 -10
- package/protos/rithmic/account_pnl_position_update.proto +59 -0
- package/protos/rithmic/base.proto +7 -0
- package/protos/rithmic/best_bid_offer.proto +39 -0
- package/protos/rithmic/exchange_order_notification.proto +140 -0
- package/protos/rithmic/instrument_pnl_position_update.proto +50 -0
- package/protos/rithmic/last_trade.proto +53 -0
- package/protos/rithmic/request_account_list.proto +20 -0
- package/protos/rithmic/request_cancel_all_orders.proto +15 -0
- package/protos/rithmic/request_front_month_contract.proto +10 -0
- package/protos/rithmic/request_heartbeat.proto +13 -0
- package/protos/rithmic/request_login.proto +28 -0
- package/protos/rithmic/request_login_info.proto +10 -0
- package/protos/rithmic/request_logout.proto +10 -0
- package/protos/rithmic/request_market_data_update.proto +42 -0
- package/protos/rithmic/request_new_order.proto +84 -0
- package/protos/rithmic/request_pnl_position_snapshot.proto +14 -0
- package/protos/rithmic/request_pnl_position_updates.proto +20 -0
- package/protos/rithmic/request_product_codes.proto +9 -0
- package/protos/rithmic/request_rithmic_system_info.proto +8 -0
- package/protos/rithmic/request_show_order_history.proto +16 -0
- package/protos/rithmic/request_show_order_history_dates.proto +10 -0
- package/protos/rithmic/request_show_order_history_summary.proto +14 -0
- package/protos/rithmic/request_show_orders.proto +14 -0
- package/protos/rithmic/request_subscribe_for_order_updates.proto +14 -0
- package/protos/rithmic/request_tick_bar_replay.proto +48 -0
- package/protos/rithmic/request_trade_routes.proto +11 -0
- package/protos/rithmic/response_account_list.proto +18 -0
- package/protos/rithmic/response_front_month_contract.proto +13 -0
- package/protos/rithmic/response_heartbeat.proto +14 -0
- package/protos/rithmic/response_login.proto +18 -0
- package/protos/rithmic/response_login_info.proto +24 -0
- package/protos/rithmic/response_logout.proto +11 -0
- package/protos/rithmic/response_market_data_update.proto +9 -0
- package/protos/rithmic/response_new_order.proto +18 -0
- package/protos/rithmic/response_pnl_position_snapshot.proto +11 -0
- package/protos/rithmic/response_pnl_position_updates.proto +11 -0
- package/protos/rithmic/response_product_codes.proto +12 -0
- package/protos/rithmic/response_rithmic_system_info.proto +12 -0
- package/protos/rithmic/response_show_order_history.proto +11 -0
- package/protos/rithmic/response_show_order_history_dates.proto +13 -0
- package/protos/rithmic/response_show_order_history_summary.proto +11 -0
- package/protos/rithmic/response_show_orders.proto +11 -0
- package/protos/rithmic/response_subscribe_for_order_updates.proto +11 -0
- package/protos/rithmic/response_tick_bar_replay.proto +40 -0
- package/protos/rithmic/response_trade_routes.proto +19 -0
- package/protos/rithmic/rithmic_order_notification.proto +124 -0
- package/src/app.js +136 -89
- package/src/config/index.js +27 -8
- package/src/config/settings.js +155 -0
- package/src/pages/accounts.js +2 -3
- package/src/pages/algo/copy-trading.js +293 -200
- package/src/pages/algo/one-account.js +1 -1
- package/src/security/encryption.js +81 -46
- package/src/security/index.js +12 -8
- package/src/security/rateLimit.js +68 -65
- package/src/security/validation.js +93 -79
- package/src/services/hqx-server.js +538 -206
- package/src/services/projectx/index.js +327 -204
- package/src/services/rithmic/index.js +288 -285
- package/src/services/session.js +184 -114
- package/src/services/tradovate/index.js +286 -297
- package/src/ui/index.js +53 -1
- package/src/utils/http.js +236 -0
- package/src/utils/index.js +11 -2
- package/src/utils/logger.js +64 -33
- package/src/utils/prompts.js +79 -71
package/src/ui/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* UI Module Exports
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const chalk = require('chalk');
|
|
5
6
|
const { detectDevice, getDevice, getSeparator } = require('./device');
|
|
6
7
|
const {
|
|
7
8
|
getLogoWidth,
|
|
@@ -24,6 +25,55 @@ const {
|
|
|
24
25
|
} = require('./table');
|
|
25
26
|
const { createBoxMenu } = require('./menu');
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Display HQX Banner (without closing border)
|
|
30
|
+
*/
|
|
31
|
+
const displayBanner = () => {
|
|
32
|
+
console.clear();
|
|
33
|
+
const termWidth = process.stdout.columns || 100;
|
|
34
|
+
const isMobile = termWidth < 60;
|
|
35
|
+
const boxWidth = isMobile ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
|
|
36
|
+
const innerWidth = boxWidth - 2;
|
|
37
|
+
|
|
38
|
+
let version = '1.0.0';
|
|
39
|
+
try { version = require('../../package.json').version; } catch (e) {}
|
|
40
|
+
|
|
41
|
+
console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
|
|
42
|
+
|
|
43
|
+
if (isMobile) {
|
|
44
|
+
const logoHQ = ['██╗ ██╗ ██████╗ ','██║ ██║██╔═══██╗','███████║██║ ██║','██╔══██║██║▄▄ ██║','██║ ██║╚██████╔╝','╚═╝ ╚═╝ ╚══▀▀═╝ '];
|
|
45
|
+
const logoX = ['██╗ ██╗','╚██╗██╔╝',' ╚███╔╝ ',' ██╔██╗ ','██╔╝ ██╗','╚═╝ ╚═╝'];
|
|
46
|
+
logoHQ.forEach((line, i) => {
|
|
47
|
+
const fullLine = chalk.cyan(line) + chalk.yellow(logoX[i]);
|
|
48
|
+
const totalLen = line.length + logoX[i].length;
|
|
49
|
+
const padding = innerWidth - totalLen;
|
|
50
|
+
const leftPad = Math.floor(padding / 2);
|
|
51
|
+
console.log(chalk.cyan('║') + ' '.repeat(leftPad) + fullLine + ' '.repeat(padding - leftPad) + chalk.cyan('║'));
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
const logo = [
|
|
55
|
+
'██╗ ██╗███████╗██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗',
|
|
56
|
+
'██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝',
|
|
57
|
+
'███████║█████╗ ██║ ██║██║ ███╗█████╗ ██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ',
|
|
58
|
+
'██╔══██║██╔══╝ ██║ ██║██║ ██║██╔══╝ ██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ',
|
|
59
|
+
'██║ ██║███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ',
|
|
60
|
+
'╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ '
|
|
61
|
+
];
|
|
62
|
+
const logoX = ['██╗ ██╗','╚██╗██╔╝',' ╚███╔╝ ',' ██╔██╗ ','██╔╝ ██╗','╚═╝ ╚═╝'];
|
|
63
|
+
logo.forEach((line, i) => {
|
|
64
|
+
const fullLine = chalk.cyan(line) + chalk.yellow(logoX[i]);
|
|
65
|
+
const totalLen = line.length + logoX[i].length;
|
|
66
|
+
const padding = innerWidth - totalLen;
|
|
67
|
+
const leftPad = Math.floor(padding / 2);
|
|
68
|
+
console.log(chalk.cyan('║') + ' '.repeat(leftPad) + fullLine + ' '.repeat(padding - leftPad) + chalk.cyan('║'));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
73
|
+
const tagline = isMobile ? `HQX v${version}` : `Prop Futures Algo Trading v${version}`;
|
|
74
|
+
console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
75
|
+
};
|
|
76
|
+
|
|
27
77
|
/**
|
|
28
78
|
* Ensure stdin is ready for inquirer prompts
|
|
29
79
|
* This fixes input leaking to bash after session restore or algo trading
|
|
@@ -69,5 +119,7 @@ module.exports = {
|
|
|
69
119
|
// Menu
|
|
70
120
|
createBoxMenu,
|
|
71
121
|
// Stdin
|
|
72
|
-
prepareStdin
|
|
122
|
+
prepareStdin,
|
|
123
|
+
// Banner
|
|
124
|
+
displayBanner
|
|
73
125
|
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared HTTP client for all services
|
|
3
|
+
* @module utils/http
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const http = require('http');
|
|
8
|
+
const { TIMEOUTS } = require('../config/settings');
|
|
9
|
+
const { logger } = require('./logger');
|
|
10
|
+
|
|
11
|
+
const log = logger.scope('HTTP');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} HttpResponse
|
|
15
|
+
* @property {number} statusCode - HTTP status code
|
|
16
|
+
* @property {Object|string} data - Response body
|
|
17
|
+
* @property {Object} headers - Response headers
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} HttpOptions
|
|
22
|
+
* @property {string} [method='GET'] - HTTP method
|
|
23
|
+
* @property {Object} [headers] - Request headers
|
|
24
|
+
* @property {Object|string} [body] - Request body
|
|
25
|
+
* @property {number} [timeout] - Request timeout in ms
|
|
26
|
+
* @property {string} [token] - Bearer token for Authorization
|
|
27
|
+
* @property {string} [apiKey] - API key header
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Performs an HTTP/HTTPS request
|
|
32
|
+
* @param {string} url - Full URL to request
|
|
33
|
+
* @param {HttpOptions} [options={}] - Request options
|
|
34
|
+
* @returns {Promise<HttpResponse>}
|
|
35
|
+
*/
|
|
36
|
+
const request = (url, options = {}) => {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const {
|
|
39
|
+
method = 'GET',
|
|
40
|
+
headers = {},
|
|
41
|
+
body = null,
|
|
42
|
+
timeout = TIMEOUTS.API_REQUEST,
|
|
43
|
+
token = null,
|
|
44
|
+
apiKey = null,
|
|
45
|
+
} = options;
|
|
46
|
+
|
|
47
|
+
const parsedUrl = new URL(url);
|
|
48
|
+
const isHttps = parsedUrl.protocol === 'https:';
|
|
49
|
+
const client = isHttps ? https : http;
|
|
50
|
+
|
|
51
|
+
const postData = body ? (typeof body === 'string' ? body : JSON.stringify(body)) : null;
|
|
52
|
+
|
|
53
|
+
const reqOptions = {
|
|
54
|
+
hostname: parsedUrl.hostname,
|
|
55
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
56
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
57
|
+
method,
|
|
58
|
+
headers: {
|
|
59
|
+
'Content-Type': 'application/json',
|
|
60
|
+
'Accept': 'application/json',
|
|
61
|
+
'User-Agent': 'HQX-CLI/2.0.0',
|
|
62
|
+
...headers,
|
|
63
|
+
},
|
|
64
|
+
timeout,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (postData) {
|
|
68
|
+
reqOptions.headers['Content-Length'] = Buffer.byteLength(postData);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (token) {
|
|
72
|
+
reqOptions.headers['Authorization'] = `Bearer ${token}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (apiKey) {
|
|
76
|
+
reqOptions.headers['X-API-Key'] = apiKey;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
log.debug(`${method} ${parsedUrl.pathname}`);
|
|
80
|
+
|
|
81
|
+
const req = client.request(reqOptions, (res) => {
|
|
82
|
+
let data = '';
|
|
83
|
+
|
|
84
|
+
res.on('data', chunk => { data += chunk; });
|
|
85
|
+
|
|
86
|
+
res.on('end', () => {
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(data);
|
|
90
|
+
} catch {
|
|
91
|
+
parsed = data;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
log.debug(`Response ${res.statusCode}`, {
|
|
95
|
+
path: parsedUrl.pathname,
|
|
96
|
+
status: res.statusCode
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
resolve({
|
|
100
|
+
statusCode: res.statusCode,
|
|
101
|
+
data: parsed,
|
|
102
|
+
headers: res.headers,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
req.on('error', (err) => {
|
|
108
|
+
log.error(`Request failed: ${err.message}`, { path: parsedUrl.pathname });
|
|
109
|
+
reject(err);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
req.on('timeout', () => {
|
|
113
|
+
req.destroy();
|
|
114
|
+
const err = new Error(`Request timeout after ${timeout}ms`);
|
|
115
|
+
log.error(err.message, { path: parsedUrl.pathname });
|
|
116
|
+
reject(err);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (postData) {
|
|
120
|
+
req.write(postData);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
req.end();
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Simplified request helper for common patterns
|
|
129
|
+
* @param {string} baseUrl - Base URL (e.g., 'https://api.example.com')
|
|
130
|
+
* @param {string} [basePath=''] - Base path prefix
|
|
131
|
+
* @returns {Object} Request methods bound to the base URL
|
|
132
|
+
*/
|
|
133
|
+
const createClient = (baseUrl, basePath = '') => {
|
|
134
|
+
const buildUrl = (path) => `${baseUrl}${basePath}${path}`;
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
/**
|
|
138
|
+
* GET request
|
|
139
|
+
* @param {string} path - Request path
|
|
140
|
+
* @param {HttpOptions} [options={}] - Options
|
|
141
|
+
*/
|
|
142
|
+
get: (path, options = {}) => request(buildUrl(path), { ...options, method: 'GET' }),
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* POST request
|
|
146
|
+
* @param {string} path - Request path
|
|
147
|
+
* @param {Object} [body] - Request body
|
|
148
|
+
* @param {HttpOptions} [options={}] - Options
|
|
149
|
+
*/
|
|
150
|
+
post: (path, body, options = {}) => request(buildUrl(path), { ...options, method: 'POST', body }),
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* PUT request
|
|
154
|
+
* @param {string} path - Request path
|
|
155
|
+
* @param {Object} [body] - Request body
|
|
156
|
+
* @param {HttpOptions} [options={}] - Options
|
|
157
|
+
*/
|
|
158
|
+
put: (path, body, options = {}) => request(buildUrl(path), { ...options, method: 'PUT', body }),
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* DELETE request
|
|
162
|
+
* @param {string} path - Request path
|
|
163
|
+
* @param {HttpOptions} [options={}] - Options
|
|
164
|
+
*/
|
|
165
|
+
delete: (path, options = {}) => request(buildUrl(path), { ...options, method: 'DELETE' }),
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Sets authorization token for all subsequent requests
|
|
169
|
+
* @param {string} token - Bearer token
|
|
170
|
+
* @returns {Object} Client with token set
|
|
171
|
+
*/
|
|
172
|
+
withToken: (token) => {
|
|
173
|
+
const client = createClient(baseUrl, basePath);
|
|
174
|
+
const wrapWithToken = (fn) => (path, bodyOrOpts, opts) => {
|
|
175
|
+
const options = opts || (typeof bodyOrOpts === 'object' && !bodyOrOpts?.method ? {} : bodyOrOpts) || {};
|
|
176
|
+
const body = opts ? bodyOrOpts : undefined;
|
|
177
|
+
return fn(path, body, { ...options, token });
|
|
178
|
+
};
|
|
179
|
+
client.get = (path, opts = {}) => request(buildUrl(path), { ...opts, method: 'GET', token });
|
|
180
|
+
client.post = (path, body, opts = {}) => request(buildUrl(path), { ...opts, method: 'POST', body, token });
|
|
181
|
+
client.put = (path, body, opts = {}) => request(buildUrl(path), { ...opts, method: 'PUT', body, token });
|
|
182
|
+
client.delete = (path, opts = {}) => request(buildUrl(path), { ...opts, method: 'DELETE', token });
|
|
183
|
+
return client;
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Retries a request with exponential backoff
|
|
190
|
+
* @param {() => Promise<HttpResponse>} fn - Request function
|
|
191
|
+
* @param {Object} [options={}] - Retry options
|
|
192
|
+
* @param {number} [options.maxRetries=3] - Maximum retry attempts
|
|
193
|
+
* @param {number} [options.baseDelay=1000] - Base delay in ms
|
|
194
|
+
* @param {number[]} [options.retryOn=[502, 503, 504]] - Status codes to retry on
|
|
195
|
+
* @returns {Promise<HttpResponse>}
|
|
196
|
+
*/
|
|
197
|
+
const withRetry = async (fn, options = {}) => {
|
|
198
|
+
const {
|
|
199
|
+
maxRetries = 3,
|
|
200
|
+
baseDelay = 1000,
|
|
201
|
+
retryOn = [502, 503, 504],
|
|
202
|
+
} = options;
|
|
203
|
+
|
|
204
|
+
let lastError;
|
|
205
|
+
|
|
206
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
207
|
+
try {
|
|
208
|
+
const result = await fn();
|
|
209
|
+
|
|
210
|
+
if (retryOn.includes(result.statusCode) && attempt < maxRetries) {
|
|
211
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
212
|
+
log.debug(`Retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`);
|
|
213
|
+
await new Promise(r => setTimeout(r, delay));
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return result;
|
|
218
|
+
} catch (err) {
|
|
219
|
+
lastError = err;
|
|
220
|
+
|
|
221
|
+
if (attempt < maxRetries) {
|
|
222
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
223
|
+
log.debug(`Retrying after error in ${delay}ms`, { error: err.message });
|
|
224
|
+
await new Promise(r => setTimeout(r, delay));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
throw lastError;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
request,
|
|
234
|
+
createClient,
|
|
235
|
+
withRetry,
|
|
236
|
+
};
|
package/src/utils/index.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Utils module exports
|
|
2
|
+
* @fileoverview Utils module exports
|
|
3
|
+
* @module utils
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
const { logger, LEVELS } = require('./logger');
|
|
6
7
|
const prompts = require('./prompts');
|
|
8
|
+
const { request, createClient, withRetry } = require('./http');
|
|
7
9
|
|
|
8
|
-
module.exports = {
|
|
10
|
+
module.exports = {
|
|
11
|
+
logger,
|
|
12
|
+
LEVELS,
|
|
13
|
+
prompts,
|
|
14
|
+
request,
|
|
15
|
+
createClient,
|
|
16
|
+
withRetry,
|
|
17
|
+
};
|
package/src/utils/logger.js
CHANGED
|
@@ -1,70 +1,97 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HQX Logger - Centralized logging for debugging
|
|
2
|
+
* @fileoverview HQX Logger - Centralized logging for debugging
|
|
3
|
+
* @module utils/logger
|
|
3
4
|
*
|
|
4
5
|
* Usage:
|
|
5
6
|
* HQX_DEBUG=1 hedgequantx - Enable all debug logs
|
|
6
|
-
* HQX_LOG_FILE=1 hedgequantx - Also write to ~/.hedgequantx/debug.log
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const os = require('os');
|
|
12
|
+
const { SECURITY, DEBUG } = require('../config/settings');
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
/** Log levels */
|
|
14
15
|
const LEVELS = {
|
|
15
16
|
ERROR: 0,
|
|
16
17
|
WARN: 1,
|
|
17
18
|
INFO: 2,
|
|
18
19
|
DEBUG: 3,
|
|
19
|
-
TRACE: 4
|
|
20
|
+
TRACE: 4,
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
/** ANSI colors */
|
|
23
24
|
const COLORS = {
|
|
24
|
-
ERROR: '\x1b[31m',
|
|
25
|
-
WARN: '\x1b[33m',
|
|
26
|
-
INFO: '\x1b[36m',
|
|
27
|
-
DEBUG: '\x1b[90m',
|
|
28
|
-
TRACE: '\x1b[90m',
|
|
29
|
-
RESET: '\x1b[0m'
|
|
25
|
+
ERROR: '\x1b[31m',
|
|
26
|
+
WARN: '\x1b[33m',
|
|
27
|
+
INFO: '\x1b[36m',
|
|
28
|
+
DEBUG: '\x1b[90m',
|
|
29
|
+
TRACE: '\x1b[90m',
|
|
30
|
+
RESET: '\x1b[0m',
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Logger class with file and console output
|
|
35
|
+
*/
|
|
32
36
|
class Logger {
|
|
33
37
|
constructor() {
|
|
34
|
-
this.consoleEnabled =
|
|
38
|
+
this.consoleEnabled = DEBUG.enabled;
|
|
35
39
|
this.level = LEVELS.DEBUG;
|
|
36
|
-
this.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
this.logDir = path.join(os.homedir(), SECURITY.SESSION_DIR);
|
|
41
|
+
this.logFile = path.join(this.logDir, DEBUG.LOG_FILE);
|
|
42
|
+
this._initLogFile();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initialize log file
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
_initLogFile() {
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.existsSync(this.logDir)) {
|
|
52
|
+
fs.mkdirSync(this.logDir, { recursive: true, mode: SECURITY.DIR_PERMISSIONS });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const header = [
|
|
56
|
+
`=== HQX Log Started ${new Date().toISOString()} ===`,
|
|
57
|
+
`Platform: ${process.platform}, Node: ${process.version}`,
|
|
58
|
+
`CWD: ${process.cwd()}`,
|
|
59
|
+
'',
|
|
60
|
+
].join('\n');
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(this.logFile, header, { mode: SECURITY.FILE_PERMISSIONS });
|
|
63
|
+
} catch {
|
|
64
|
+
// Ignore init errors - logging is optional
|
|
42
65
|
}
|
|
43
|
-
// Clear log file on start
|
|
44
|
-
fs.writeFileSync(this.logFile, `=== HQX Log Started ${new Date().toISOString()} ===\n`);
|
|
45
|
-
fs.appendFileSync(this.logFile, `Platform: ${process.platform}, Node: ${process.version}\n`);
|
|
46
|
-
fs.appendFileSync(this.logFile, `CWD: ${process.cwd()}\n\n`);
|
|
47
66
|
}
|
|
48
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Format log message
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
49
72
|
_format(level, module, message, data) {
|
|
50
|
-
const timestamp = new Date().toISOString().
|
|
51
|
-
const dataStr = data !== undefined ?
|
|
52
|
-
return `[${timestamp}] [${level}] [${module}]${
|
|
73
|
+
const timestamp = new Date().toISOString().slice(11, 23);
|
|
74
|
+
const dataStr = data !== undefined ? ` ${JSON.stringify(data)}` : '';
|
|
75
|
+
return `[${timestamp}] [${level}] [${module}] ${message}${dataStr}`;
|
|
53
76
|
}
|
|
54
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Write log entry
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
55
82
|
_log(level, levelName, module, message, data) {
|
|
56
83
|
if (level > this.level) return;
|
|
57
84
|
|
|
58
85
|
const formatted = this._format(levelName, module, message, data);
|
|
59
|
-
|
|
60
|
-
//
|
|
86
|
+
|
|
87
|
+
// Write to file (survives crashes)
|
|
61
88
|
try {
|
|
62
89
|
fs.appendFileSync(this.logFile, formatted + '\n');
|
|
63
|
-
} catch
|
|
64
|
-
// Ignore file
|
|
90
|
+
} catch {
|
|
91
|
+
// Ignore file errors
|
|
65
92
|
}
|
|
66
|
-
|
|
67
|
-
// Console output only if
|
|
93
|
+
|
|
94
|
+
// Console output only if enabled
|
|
68
95
|
if (this.consoleEnabled) {
|
|
69
96
|
const color = COLORS[levelName] || COLORS.RESET;
|
|
70
97
|
console.error(`${color}${formatted}${COLORS.RESET}`);
|
|
@@ -91,7 +118,11 @@ class Logger {
|
|
|
91
118
|
this._log(LEVELS.TRACE, 'TRACE', module, message, data);
|
|
92
119
|
}
|
|
93
120
|
|
|
94
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Create a scoped logger for a specific module
|
|
123
|
+
* @param {string} moduleName - Module name
|
|
124
|
+
* @returns {Object} Scoped logger methods
|
|
125
|
+
*/
|
|
95
126
|
scope(moduleName) {
|
|
96
127
|
return {
|
|
97
128
|
error: (msg, data) => this.error(moduleName, msg, data),
|
|
@@ -103,7 +134,7 @@ class Logger {
|
|
|
103
134
|
}
|
|
104
135
|
}
|
|
105
136
|
|
|
106
|
-
|
|
137
|
+
/** Singleton instance */
|
|
107
138
|
const logger = new Logger();
|
|
108
139
|
|
|
109
140
|
module.exports = { logger, LEVELS };
|