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.
Files changed (105) hide show
  1. package/README.md +7 -6
  2. package/bin/cli.js +13 -7
  3. package/dist/algo/copy-engine.js +3 -0
  4. package/dist/algo/copy-engine.jsc +0 -0
  5. package/dist/algo/engine.js +3 -0
  6. package/dist/algo/engine.jsc +0 -0
  7. package/dist/algo/market-data-rithmic.js +3 -0
  8. package/dist/algo/market-data-rithmic.jsc +0 -0
  9. package/dist/algo/market-data.js +3 -0
  10. package/dist/algo/market-data.jsc +0 -0
  11. package/dist/algo/rithmic/connection.js +3 -0
  12. package/dist/algo/rithmic/connection.jsc +0 -0
  13. package/dist/algo/rithmic/constants.js +3 -0
  14. package/dist/algo/rithmic/constants.jsc +0 -0
  15. package/dist/algo/rithmic/index.js +3 -0
  16. package/dist/algo/rithmic/index.jsc +0 -0
  17. package/dist/algo/rithmic/market-data.js +3 -0
  18. package/dist/algo/rithmic/market-data.jsc +0 -0
  19. package/dist/algo/rithmic/pnl.js +3 -0
  20. package/dist/algo/rithmic/pnl.jsc +0 -0
  21. package/dist/algo/rithmic/pool.js +3 -0
  22. package/dist/algo/rithmic/pool.jsc +0 -0
  23. package/dist/algo/rithmic/trading.js +3 -0
  24. package/dist/algo/rithmic/trading.jsc +0 -0
  25. package/dist/algo/rithmic-decoder.js +3 -0
  26. package/dist/algo/rithmic-decoder.jsc +0 -0
  27. package/dist/algo/strategies/ultra-scalping-v2.js +3 -0
  28. package/dist/algo/strategies/ultra-scalping-v2.jsc +0 -0
  29. package/dist/algo/strategies/ultra-scalping.js +3 -0
  30. package/dist/algo/strategies/ultra-scalping.jsc +0 -0
  31. package/dist/algo/trading-api-rithmic.js +3 -0
  32. package/dist/algo/trading-api-rithmic.jsc +0 -0
  33. package/dist/algo/trading-api.js +3 -0
  34. package/dist/algo/trading-api.jsc +0 -0
  35. package/dist/algo/utils/smart-logger.js +3 -0
  36. package/dist/algo/utils/smart-logger.jsc +0 -0
  37. package/dist/algo/utils/smart-logs.js +3 -0
  38. package/dist/algo/utils/smart-logs.jsc +0 -0
  39. package/package.json +33 -10
  40. package/protos/rithmic/account_pnl_position_update.proto +59 -0
  41. package/protos/rithmic/base.proto +7 -0
  42. package/protos/rithmic/best_bid_offer.proto +39 -0
  43. package/protos/rithmic/exchange_order_notification.proto +140 -0
  44. package/protos/rithmic/instrument_pnl_position_update.proto +50 -0
  45. package/protos/rithmic/last_trade.proto +53 -0
  46. package/protos/rithmic/request_account_list.proto +20 -0
  47. package/protos/rithmic/request_cancel_all_orders.proto +15 -0
  48. package/protos/rithmic/request_front_month_contract.proto +10 -0
  49. package/protos/rithmic/request_heartbeat.proto +13 -0
  50. package/protos/rithmic/request_login.proto +28 -0
  51. package/protos/rithmic/request_login_info.proto +10 -0
  52. package/protos/rithmic/request_logout.proto +10 -0
  53. package/protos/rithmic/request_market_data_update.proto +42 -0
  54. package/protos/rithmic/request_new_order.proto +84 -0
  55. package/protos/rithmic/request_pnl_position_snapshot.proto +14 -0
  56. package/protos/rithmic/request_pnl_position_updates.proto +20 -0
  57. package/protos/rithmic/request_product_codes.proto +9 -0
  58. package/protos/rithmic/request_rithmic_system_info.proto +8 -0
  59. package/protos/rithmic/request_show_order_history.proto +16 -0
  60. package/protos/rithmic/request_show_order_history_dates.proto +10 -0
  61. package/protos/rithmic/request_show_order_history_summary.proto +14 -0
  62. package/protos/rithmic/request_show_orders.proto +14 -0
  63. package/protos/rithmic/request_subscribe_for_order_updates.proto +14 -0
  64. package/protos/rithmic/request_tick_bar_replay.proto +48 -0
  65. package/protos/rithmic/request_trade_routes.proto +11 -0
  66. package/protos/rithmic/response_account_list.proto +18 -0
  67. package/protos/rithmic/response_front_month_contract.proto +13 -0
  68. package/protos/rithmic/response_heartbeat.proto +14 -0
  69. package/protos/rithmic/response_login.proto +18 -0
  70. package/protos/rithmic/response_login_info.proto +24 -0
  71. package/protos/rithmic/response_logout.proto +11 -0
  72. package/protos/rithmic/response_market_data_update.proto +9 -0
  73. package/protos/rithmic/response_new_order.proto +18 -0
  74. package/protos/rithmic/response_pnl_position_snapshot.proto +11 -0
  75. package/protos/rithmic/response_pnl_position_updates.proto +11 -0
  76. package/protos/rithmic/response_product_codes.proto +12 -0
  77. package/protos/rithmic/response_rithmic_system_info.proto +12 -0
  78. package/protos/rithmic/response_show_order_history.proto +11 -0
  79. package/protos/rithmic/response_show_order_history_dates.proto +13 -0
  80. package/protos/rithmic/response_show_order_history_summary.proto +11 -0
  81. package/protos/rithmic/response_show_orders.proto +11 -0
  82. package/protos/rithmic/response_subscribe_for_order_updates.proto +11 -0
  83. package/protos/rithmic/response_tick_bar_replay.proto +40 -0
  84. package/protos/rithmic/response_trade_routes.proto +19 -0
  85. package/protos/rithmic/rithmic_order_notification.proto +124 -0
  86. package/src/app.js +136 -89
  87. package/src/config/index.js +27 -8
  88. package/src/config/settings.js +155 -0
  89. package/src/pages/accounts.js +2 -3
  90. package/src/pages/algo/copy-trading.js +293 -200
  91. package/src/pages/algo/one-account.js +1 -1
  92. package/src/security/encryption.js +81 -46
  93. package/src/security/index.js +12 -8
  94. package/src/security/rateLimit.js +68 -65
  95. package/src/security/validation.js +93 -79
  96. package/src/services/hqx-server.js +538 -206
  97. package/src/services/projectx/index.js +327 -204
  98. package/src/services/rithmic/index.js +288 -285
  99. package/src/services/session.js +184 -114
  100. package/src/services/tradovate/index.js +286 -297
  101. package/src/ui/index.js +53 -1
  102. package/src/utils/http.js +236 -0
  103. package/src/utils/index.js +11 -2
  104. package/src/utils/logger.js +64 -33
  105. 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
+ };
@@ -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 = { logger, LEVELS, prompts };
10
+ module.exports = {
11
+ logger,
12
+ LEVELS,
13
+ prompts,
14
+ request,
15
+ createClient,
16
+ withRetry,
17
+ };
@@ -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
- // Log levels
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
- // Colors for console output
23
+ /** ANSI colors */
23
24
  const COLORS = {
24
- ERROR: '\x1b[31m', // Red
25
- WARN: '\x1b[33m', // Yellow
26
- INFO: '\x1b[36m', // Cyan
27
- DEBUG: '\x1b[90m', // Gray
28
- TRACE: '\x1b[90m', // Gray
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 = process.env.HQX_DEBUG === '1';
38
+ this.consoleEnabled = DEBUG.enabled;
35
39
  this.level = LEVELS.DEBUG;
36
- this.logFile = path.join(os.homedir(), '.hedgequantx', 'debug.log');
37
-
38
- // Always write to file (logs are always saved)
39
- const dir = path.dirname(this.logFile);
40
- if (!fs.existsSync(dir)) {
41
- fs.mkdirSync(dir, { recursive: true });
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().substr(11, 12); // HH:MM:SS.mmm
51
- const dataStr = data !== undefined ? ' ' + JSON.stringify(data) : '';
52
- return `[${timestamp}] [${level}] [${module}]${dataStr ? ' ' + message + dataStr : ' ' + message}`;
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
- // Always write to file (survives crashes)
86
+
87
+ // Write to file (survives crashes)
61
88
  try {
62
89
  fs.appendFileSync(this.logFile, formatted + '\n');
63
- } catch (e) {
64
- // Ignore file write errors
90
+ } catch {
91
+ // Ignore file errors
65
92
  }
66
-
67
- // Console output only if HQX_DEBUG=1
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
- // Create a scoped logger for a specific module
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
- // Singleton instance
137
+ /** Singleton instance */
107
138
  const logger = new Logger();
108
139
 
109
140
  module.exports = { logger, LEVELS };