commons-proxy 2.0.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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +757 -0
  3. package/bin/cli.js +146 -0
  4. package/package.json +97 -0
  5. package/public/Complaint Details.pdf +0 -0
  6. package/public/Cyber Crime Portal.pdf +0 -0
  7. package/public/app.js +229 -0
  8. package/public/css/src/input.css +523 -0
  9. package/public/css/style.css +1 -0
  10. package/public/favicon.png +0 -0
  11. package/public/index.html +549 -0
  12. package/public/js/components/account-manager.js +356 -0
  13. package/public/js/components/add-account-modal.js +414 -0
  14. package/public/js/components/claude-config.js +420 -0
  15. package/public/js/components/dashboard/charts.js +605 -0
  16. package/public/js/components/dashboard/filters.js +362 -0
  17. package/public/js/components/dashboard/stats.js +110 -0
  18. package/public/js/components/dashboard.js +236 -0
  19. package/public/js/components/logs-viewer.js +100 -0
  20. package/public/js/components/models.js +36 -0
  21. package/public/js/components/server-config.js +349 -0
  22. package/public/js/config/constants.js +102 -0
  23. package/public/js/data-store.js +375 -0
  24. package/public/js/settings-store.js +58 -0
  25. package/public/js/store.js +99 -0
  26. package/public/js/translations/en.js +367 -0
  27. package/public/js/translations/id.js +412 -0
  28. package/public/js/translations/pt.js +308 -0
  29. package/public/js/translations/tr.js +358 -0
  30. package/public/js/translations/zh.js +373 -0
  31. package/public/js/utils/account-actions.js +189 -0
  32. package/public/js/utils/error-handler.js +96 -0
  33. package/public/js/utils/model-config.js +42 -0
  34. package/public/js/utils/ui-logger.js +143 -0
  35. package/public/js/utils/validators.js +77 -0
  36. package/public/js/utils.js +69 -0
  37. package/public/proxy-server-64.png +0 -0
  38. package/public/views/accounts.html +361 -0
  39. package/public/views/dashboard.html +484 -0
  40. package/public/views/logs.html +97 -0
  41. package/public/views/models.html +331 -0
  42. package/public/views/settings.html +1327 -0
  43. package/src/account-manager/credentials.js +378 -0
  44. package/src/account-manager/index.js +462 -0
  45. package/src/account-manager/onboarding.js +112 -0
  46. package/src/account-manager/rate-limits.js +369 -0
  47. package/src/account-manager/storage.js +160 -0
  48. package/src/account-manager/strategies/base-strategy.js +109 -0
  49. package/src/account-manager/strategies/hybrid-strategy.js +339 -0
  50. package/src/account-manager/strategies/index.js +79 -0
  51. package/src/account-manager/strategies/round-robin-strategy.js +76 -0
  52. package/src/account-manager/strategies/sticky-strategy.js +138 -0
  53. package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
  54. package/src/account-manager/strategies/trackers/index.js +9 -0
  55. package/src/account-manager/strategies/trackers/quota-tracker.js +120 -0
  56. package/src/account-manager/strategies/trackers/token-bucket-tracker.js +155 -0
  57. package/src/auth/database.js +169 -0
  58. package/src/auth/oauth.js +548 -0
  59. package/src/auth/token-extractor.js +117 -0
  60. package/src/cli/accounts.js +648 -0
  61. package/src/cloudcode/index.js +29 -0
  62. package/src/cloudcode/message-handler.js +510 -0
  63. package/src/cloudcode/model-api.js +248 -0
  64. package/src/cloudcode/rate-limit-parser.js +235 -0
  65. package/src/cloudcode/request-builder.js +93 -0
  66. package/src/cloudcode/session-manager.js +47 -0
  67. package/src/cloudcode/sse-parser.js +121 -0
  68. package/src/cloudcode/sse-streamer.js +293 -0
  69. package/src/cloudcode/streaming-handler.js +615 -0
  70. package/src/config.js +125 -0
  71. package/src/constants.js +407 -0
  72. package/src/errors.js +242 -0
  73. package/src/fallback-config.js +29 -0
  74. package/src/format/content-converter.js +193 -0
  75. package/src/format/index.js +20 -0
  76. package/src/format/request-converter.js +255 -0
  77. package/src/format/response-converter.js +120 -0
  78. package/src/format/schema-sanitizer.js +673 -0
  79. package/src/format/signature-cache.js +88 -0
  80. package/src/format/thinking-utils.js +648 -0
  81. package/src/index.js +148 -0
  82. package/src/modules/usage-stats.js +205 -0
  83. package/src/providers/anthropic-provider.js +258 -0
  84. package/src/providers/base-provider.js +157 -0
  85. package/src/providers/cloudcode.js +94 -0
  86. package/src/providers/copilot.js +399 -0
  87. package/src/providers/github-provider.js +287 -0
  88. package/src/providers/google-provider.js +192 -0
  89. package/src/providers/index.js +211 -0
  90. package/src/providers/openai-compatible.js +265 -0
  91. package/src/providers/openai-provider.js +271 -0
  92. package/src/providers/openrouter-provider.js +325 -0
  93. package/src/providers/setup.js +83 -0
  94. package/src/server.js +870 -0
  95. package/src/utils/claude-config.js +245 -0
  96. package/src/utils/helpers.js +51 -0
  97. package/src/utils/logger.js +142 -0
  98. package/src/utils/native-module-helper.js +162 -0
  99. package/src/webui/index.js +1134 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Claude CLI Configuration Utility
3
+ *
4
+ * Handles reading and writing to the global Claude CLI settings file.
5
+ * Location: ~/.claude/settings.json (Windows: %USERPROFILE%\.claude\settings.json)
6
+ */
7
+
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import os from 'os';
11
+ import { logger } from './logger.js';
12
+ import { DEFAULT_PRESETS } from '../constants.js';
13
+
14
+ /**
15
+ * Get the path to the global Claude CLI settings file
16
+ * @returns {string} Absolute path to settings.json
17
+ */
18
+ export function getClaudeConfigPath() {
19
+ return path.join(os.homedir(), '.claude', 'settings.json');
20
+ }
21
+
22
+ /**
23
+ * Read the global Claude CLI configuration
24
+ * @returns {Promise<Object>} The configuration object or empty object if file missing
25
+ */
26
+ export async function readClaudeConfig() {
27
+ const configPath = getClaudeConfigPath();
28
+ try {
29
+ const content = await fs.readFile(configPath, 'utf8');
30
+ if (!content.trim()) return { env: {} };
31
+ return JSON.parse(content);
32
+ } catch (error) {
33
+ if (error.code === 'ENOENT') {
34
+ logger.warn(`[ClaudeConfig] Config file not found at ${configPath}, returning empty default`);
35
+ return { env: {} };
36
+ }
37
+ if (error instanceof SyntaxError) {
38
+ logger.error(`[ClaudeConfig] Invalid JSON in config at ${configPath}. Returning safe default.`);
39
+ return { env: {} };
40
+ }
41
+ logger.error(`[ClaudeConfig] Failed to read config at ${configPath}:`, error.message);
42
+ throw error;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Update the global Claude CLI configuration
48
+ * Performs a deep merge with existing configuration to avoid losing other settings.
49
+ *
50
+ * @param {Object} updates - The partial configuration to merge in
51
+ * @returns {Promise<Object>} The updated full configuration
52
+ */
53
+ export async function updateClaudeConfig(updates) {
54
+ const configPath = getClaudeConfigPath();
55
+ let currentConfig = {};
56
+
57
+ // 1. Read existing config
58
+ try {
59
+ currentConfig = await readClaudeConfig();
60
+ } catch (error) {
61
+ // Ignore ENOENT, otherwise rethrow
62
+ if (error.code !== 'ENOENT') throw error;
63
+ }
64
+
65
+ // 2. Deep merge updates
66
+ const newConfig = deepMerge(currentConfig, updates);
67
+
68
+ // 3. Ensure .claude directory exists
69
+ const configDir = path.dirname(configPath);
70
+ try {
71
+ await fs.mkdir(configDir, { recursive: true });
72
+ } catch (error) {
73
+ // Ignore if exists
74
+ }
75
+
76
+ // 4. Write back to file
77
+ try {
78
+ await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2), 'utf8');
79
+ logger.info(`[ClaudeConfig] Updated config at ${configPath}`);
80
+ return newConfig;
81
+ } catch (error) {
82
+ logger.error(`[ClaudeConfig] Failed to write config:`, error.message);
83
+ throw error;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Replace the global Claude CLI configuration entirely
89
+ * Unlike updateClaudeConfig, this replaces the config instead of merging.
90
+ *
91
+ * @param {Object} config - The new configuration to write
92
+ * @returns {Promise<Object>} The written configuration
93
+ */
94
+ export async function replaceClaudeConfig(config) {
95
+ const configPath = getClaudeConfigPath();
96
+
97
+ // 1. Ensure .claude directory exists
98
+ const configDir = path.dirname(configPath);
99
+ try {
100
+ await fs.mkdir(configDir, { recursive: true });
101
+ } catch (error) {
102
+ // Ignore if exists
103
+ }
104
+
105
+ // 2. Write config directly (no merge)
106
+ try {
107
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8');
108
+ logger.info(`[ClaudeConfig] Replaced config at ${configPath}`);
109
+ return config;
110
+ } catch (error) {
111
+ logger.error(`[ClaudeConfig] Failed to write config:`, error.message);
112
+ throw error;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Simple deep merge for objects
118
+ */
119
+ function deepMerge(target, source) {
120
+ const output = { ...target };
121
+
122
+ if (isObject(target) && isObject(source)) {
123
+ Object.keys(source).forEach(key => {
124
+ if (isObject(source[key])) {
125
+ if (!(key in target)) {
126
+ Object.assign(output, { [key]: source[key] });
127
+ } else {
128
+ output[key] = deepMerge(target[key], source[key]);
129
+ }
130
+ } else {
131
+ Object.assign(output, { [key]: source[key] });
132
+ }
133
+ });
134
+ }
135
+
136
+ return output;
137
+ }
138
+
139
+ function isObject(item) {
140
+ return (item && typeof item === 'object' && !Array.isArray(item));
141
+ }
142
+
143
+ // ==========================================
144
+ // Claude CLI Presets
145
+ // ==========================================
146
+
147
+ /**
148
+ * Get the path to the presets file
149
+ * @returns {string} Absolute path to claude-presets.json
150
+ */
151
+ export function getPresetsPath() {
152
+ return path.join(os.homedir(), '.config', 'commons-proxy', 'claude-presets.json');
153
+ }
154
+
155
+ /**
156
+ * Read all Claude CLI presets
157
+ * Creates the file with default presets if it doesn't exist.
158
+ * @returns {Promise<Array>} Array of preset objects
159
+ */
160
+ export async function readPresets() {
161
+ const presetsPath = getPresetsPath();
162
+ try {
163
+ const content = await fs.readFile(presetsPath, 'utf8');
164
+ if (!content.trim()) return DEFAULT_PRESETS;
165
+ return JSON.parse(content);
166
+ } catch (error) {
167
+ if (error.code === 'ENOENT') {
168
+ // Create with defaults
169
+ try {
170
+ await fs.mkdir(path.dirname(presetsPath), { recursive: true });
171
+ await fs.writeFile(presetsPath, JSON.stringify(DEFAULT_PRESETS, null, 2), 'utf8');
172
+ logger.info(`[ClaudePresets] Created presets file with defaults at ${presetsPath}`);
173
+ } catch (writeError) {
174
+ logger.warn(`[ClaudePresets] Could not create presets file: ${writeError.message}`);
175
+ }
176
+ return DEFAULT_PRESETS;
177
+ }
178
+ if (error instanceof SyntaxError) {
179
+ logger.error(`[ClaudePresets] Invalid JSON in presets at ${presetsPath}. Returning defaults.`);
180
+ return DEFAULT_PRESETS;
181
+ }
182
+ logger.error(`[ClaudePresets] Failed to read presets at ${presetsPath}:`, error.message);
183
+ throw error;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Save a preset (add or update)
189
+ * @param {string} name - Preset name
190
+ * @param {Object} config - Environment variables to save
191
+ * @returns {Promise<Array>} Updated array of presets
192
+ */
193
+ export async function savePreset(name, config) {
194
+ const presetsPath = getPresetsPath();
195
+ let presets = await readPresets();
196
+
197
+ const existingIndex = presets.findIndex(p => p.name === name);
198
+ const newPreset = { name, config: { ...config } };
199
+
200
+ if (existingIndex >= 0) {
201
+ presets[existingIndex] = newPreset;
202
+ logger.info(`[ClaudePresets] Updated preset: ${name}`);
203
+ } else {
204
+ presets.push(newPreset);
205
+ logger.info(`[ClaudePresets] Created preset: ${name}`);
206
+ }
207
+
208
+ try {
209
+ await fs.mkdir(path.dirname(presetsPath), { recursive: true });
210
+ await fs.writeFile(presetsPath, JSON.stringify(presets, null, 2), 'utf8');
211
+ } catch (error) {
212
+ logger.error(`[ClaudePresets] Failed to save preset:`, error.message);
213
+ throw error;
214
+ }
215
+
216
+ return presets;
217
+ }
218
+
219
+ /**
220
+ * Delete a preset by name
221
+ * @param {string} name - Preset name to delete
222
+ * @returns {Promise<Array>} Updated array of presets
223
+ */
224
+ export async function deletePreset(name) {
225
+ const presetsPath = getPresetsPath();
226
+ let presets = await readPresets();
227
+
228
+ const originalLength = presets.length;
229
+ presets = presets.filter(p => p.name !== name);
230
+
231
+ if (presets.length === originalLength) {
232
+ logger.warn(`[ClaudePresets] Preset not found: ${name}`);
233
+ return presets;
234
+ }
235
+
236
+ try {
237
+ await fs.writeFile(presetsPath, JSON.stringify(presets, null, 2), 'utf8');
238
+ logger.info(`[ClaudePresets] Deleted preset: ${name}`);
239
+ } catch (error) {
240
+ logger.error(`[ClaudePresets] Failed to delete preset:`, error.message);
241
+ throw error;
242
+ }
243
+
244
+ return presets;
245
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Shared Utility Functions
3
+ *
4
+ * General-purpose helper functions used across multiple modules.
5
+ */
6
+
7
+ /**
8
+ * Format duration in milliseconds to human-readable string
9
+ * @param {number} ms - Duration in milliseconds
10
+ * @returns {string} Human-readable duration (e.g., "1h23m45s")
11
+ */
12
+ export function formatDuration(ms) {
13
+ const seconds = Math.floor(ms / 1000);
14
+ const hours = Math.floor(seconds / 3600);
15
+ const minutes = Math.floor((seconds % 3600) / 60);
16
+ const secs = seconds % 60;
17
+
18
+ if (hours > 0) {
19
+ return `${hours}h${minutes}m${secs}s`;
20
+ } else if (minutes > 0) {
21
+ return `${minutes}m${secs}s`;
22
+ }
23
+ return `${secs}s`;
24
+ }
25
+
26
+
27
+ /**
28
+ * Sleep for specified milliseconds
29
+ * @param {number} ms - Duration to sleep in milliseconds
30
+ * @returns {Promise<void>} Resolves after the specified duration
31
+ */
32
+ export function sleep(ms) {
33
+ return new Promise(resolve => setTimeout(resolve, ms));
34
+ }
35
+
36
+ /**
37
+ * Check if an error is a network error (transient)
38
+ * @param {Error} error - The error to check
39
+ * @returns {boolean} True if it is a network error
40
+ */
41
+ export function isNetworkError(error) {
42
+ const msg = error.message.toLowerCase();
43
+ return (
44
+ msg.includes('fetch failed') ||
45
+ msg.includes('network error') ||
46
+ msg.includes('econnreset') ||
47
+ msg.includes('etimedout') ||
48
+ msg.includes('socket hang up') ||
49
+ msg.includes('timeout')
50
+ );
51
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Logger Utility
3
+ *
4
+ * Provides structured logging with colors and debug support.
5
+ * Simple ANSI codes used to avoid dependencies.
6
+ */
7
+
8
+ import { EventEmitter } from 'events';
9
+ import util from 'util';
10
+
11
+ const COLORS = {
12
+ RESET: '\x1b[0m',
13
+ BRIGHT: '\x1b[1m',
14
+ DIM: '\x1b[2m',
15
+
16
+ RED: '\x1b[31m',
17
+ GREEN: '\x1b[32m',
18
+ YELLOW: '\x1b[33m',
19
+ BLUE: '\x1b[34m',
20
+ MAGENTA: '\x1b[35m',
21
+ CYAN: '\x1b[36m',
22
+ WHITE: '\x1b[37m',
23
+ GRAY: '\x1b[90m'
24
+ };
25
+
26
+ class Logger extends EventEmitter {
27
+ constructor() {
28
+ super();
29
+ this.isDebugEnabled = false;
30
+ this.history = [];
31
+ this.maxHistory = 1000;
32
+ }
33
+
34
+ /**
35
+ * Set debug mode
36
+ * @param {boolean} enabled
37
+ */
38
+ setDebug(enabled) {
39
+ this.isDebugEnabled = !!enabled;
40
+ }
41
+
42
+ /**
43
+ * Get current timestamp string
44
+ */
45
+ getTimestamp() {
46
+ return new Date().toISOString();
47
+ }
48
+
49
+ /**
50
+ * Get log history
51
+ */
52
+ getHistory() {
53
+ return this.history;
54
+ }
55
+
56
+ /**
57
+ * Format and print a log message
58
+ * @param {string} level
59
+ * @param {string} color
60
+ * @param {string} message
61
+ * @param {...any} args
62
+ */
63
+ print(level, color, message, ...args) {
64
+ // Format: [TIMESTAMP] [LEVEL] Message
65
+ const timestampStr = this.getTimestamp();
66
+ const timestamp = `${COLORS.GRAY}[${timestampStr}]${COLORS.RESET}`;
67
+ const levelTag = `${color}[${level}]${COLORS.RESET}`;
68
+
69
+ // Format the message with args similar to console.log
70
+ const formattedMessage = util.format(message, ...args);
71
+
72
+ console.log(`${timestamp} ${levelTag} ${formattedMessage}`);
73
+
74
+ // Store structured log
75
+ const logEntry = {
76
+ timestamp: timestampStr,
77
+ level,
78
+ message: formattedMessage
79
+ };
80
+
81
+ this.history.push(logEntry);
82
+ if (this.history.length > this.maxHistory) {
83
+ this.history.shift();
84
+ }
85
+
86
+ this.emit('log', logEntry);
87
+ }
88
+
89
+ /**
90
+ * Standard info log
91
+ */
92
+ info(message, ...args) {
93
+ this.print('INFO', COLORS.BLUE, message, ...args);
94
+ }
95
+
96
+ /**
97
+ * Success log
98
+ */
99
+ success(message, ...args) {
100
+ this.print('SUCCESS', COLORS.GREEN, message, ...args);
101
+ }
102
+
103
+ /**
104
+ * Warning log
105
+ */
106
+ warn(message, ...args) {
107
+ this.print('WARN', COLORS.YELLOW, message, ...args);
108
+ }
109
+
110
+ /**
111
+ * Error log
112
+ */
113
+ error(message, ...args) {
114
+ this.print('ERROR', COLORS.RED, message, ...args);
115
+ }
116
+
117
+ /**
118
+ * Debug log - only prints if debug mode is enabled
119
+ */
120
+ debug(message, ...args) {
121
+ if (this.isDebugEnabled) {
122
+ this.print('DEBUG', COLORS.MAGENTA, message, ...args);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Direct log (for raw output usually) - proxied to console.log but can be enhanced
128
+ */
129
+ log(message, ...args) {
130
+ console.log(message, ...args);
131
+ }
132
+
133
+ /**
134
+ * Print a section header
135
+ */
136
+ header(title) {
137
+ console.log(`\n${COLORS.BRIGHT}${COLORS.CYAN}=== ${title} ===${COLORS.RESET}\n`);
138
+ }
139
+ }
140
+
141
+ // Export a singleton instance
142
+ export const logger = new Logger();
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Native Module Helper
3
+ * Detects and auto-rebuilds native Node.js modules when they become
4
+ * incompatible after a Node.js version update.
5
+ */
6
+
7
+ import { execSync } from 'child_process';
8
+ import { dirname, join } from 'path';
9
+ import { existsSync } from 'fs';
10
+ import { logger } from './logger.js';
11
+
12
+ /**
13
+ * Check if an error is a NODE_MODULE_VERSION mismatch error
14
+ * @param {Error} error - The error to check
15
+ * @returns {boolean} True if it's a version mismatch error
16
+ */
17
+ export function isModuleVersionError(error) {
18
+ const message = error?.message || '';
19
+ return message.includes('NODE_MODULE_VERSION') &&
20
+ message.includes('was compiled against a different Node.js version');
21
+ }
22
+
23
+ /**
24
+ * Extract the module path from a NODE_MODULE_VERSION error message
25
+ * @param {Error} error - The error containing the module path
26
+ * @returns {string|null} The path to the .node file, or null if not found
27
+ */
28
+ export function extractModulePath(error) {
29
+ const message = error?.message || '';
30
+ // Match pattern like: "The module '/path/to/module.node'"
31
+ const match = message.match(/The module '([^']+\.node)'/);
32
+ return match ? match[1] : null;
33
+ }
34
+
35
+ /**
36
+ * Find the package root directory from a .node file path
37
+ * @param {string} nodeFilePath - Path to the .node file
38
+ * @returns {string|null} Path to the package root, or null if not found
39
+ */
40
+ export function findPackageRoot(nodeFilePath) {
41
+ // Walk up from the .node file to find package.json
42
+ let dir = dirname(nodeFilePath);
43
+ while (dir) {
44
+ const packageJsonPath = join(dir, 'package.json');
45
+ if (existsSync(packageJsonPath)) {
46
+ return dir;
47
+ }
48
+ const parentDir = dirname(dir);
49
+ // Stop when we've reached the filesystem root (dirname returns same path)
50
+ if (parentDir === dir) {
51
+ break;
52
+ }
53
+ dir = parentDir;
54
+ }
55
+ return null;
56
+ }
57
+
58
+ /**
59
+ * Attempt to rebuild a native module
60
+ * @param {string} packagePath - Path to the package root directory
61
+ * @returns {boolean} True if rebuild succeeded, false otherwise
62
+ */
63
+ export function rebuildModule(packagePath) {
64
+ try {
65
+ logger.info(`[NativeModule] Rebuilding native module at: ${packagePath}`);
66
+
67
+ // Run npm rebuild in the package directory
68
+ const output = execSync('npm rebuild', {
69
+ cwd: packagePath,
70
+ stdio: 'pipe', // Capture output instead of printing
71
+ timeout: 120000 // 2 minute timeout
72
+ });
73
+
74
+ // Log rebuild output for debugging
75
+ const outputStr = output?.toString().trim();
76
+ if (outputStr) {
77
+ logger.debug(`[NativeModule] Rebuild output:\n${outputStr}`);
78
+ }
79
+
80
+ logger.success('[NativeModule] Rebuild completed successfully');
81
+ return true;
82
+ } catch (error) {
83
+ // Include stdout/stderr from the failed command for troubleshooting
84
+ const stdout = error.stdout?.toString().trim();
85
+ const stderr = error.stderr?.toString().trim();
86
+ let errorDetails = `[NativeModule] Rebuild failed: ${error.message}`;
87
+ if (stdout) {
88
+ errorDetails += `\n[NativeModule] stdout: ${stdout}`;
89
+ }
90
+ if (stderr) {
91
+ errorDetails += `\n[NativeModule] stderr: ${stderr}`;
92
+ }
93
+ logger.error(errorDetails);
94
+ return false;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Attempt to auto-rebuild a native module from an error
100
+ * @param {Error} error - The NODE_MODULE_VERSION error
101
+ * @returns {boolean} True if rebuild succeeded, false otherwise
102
+ */
103
+ export function attemptAutoRebuild(error) {
104
+ const nodePath = extractModulePath(error);
105
+ if (!nodePath) {
106
+ logger.error('[NativeModule] Could not extract module path from error');
107
+ return false;
108
+ }
109
+
110
+ const packagePath = findPackageRoot(nodePath);
111
+ if (!packagePath) {
112
+ logger.error('[NativeModule] Could not find package root');
113
+ return false;
114
+ }
115
+
116
+ logger.warn('[NativeModule] Native module version mismatch detected');
117
+ logger.info('[NativeModule] Attempting automatic rebuild...');
118
+
119
+ return rebuildModule(packagePath);
120
+ }
121
+
122
+ /**
123
+ * Recursively clear a module and its dependencies from the require cache
124
+ * This is needed after rebuilding a native module to force re-import
125
+ * @param {string} modulePath - Resolved path to the module
126
+ * @param {object} cache - The require.cache object
127
+ * @param {Set} [visited] - Set of already-visited paths to prevent cycles
128
+ */
129
+ export function clearRequireCache(modulePath, cache, visited = new Set()) {
130
+ if (visited.has(modulePath)) return;
131
+ visited.add(modulePath);
132
+
133
+ const mod = cache[modulePath];
134
+ if (!mod) return;
135
+
136
+ // Recursively clear children first
137
+ if (mod.children) {
138
+ for (const child of mod.children) {
139
+ clearRequireCache(child.id, cache, visited);
140
+ }
141
+ }
142
+
143
+ // Remove from parent's children array
144
+ if (mod.parent && mod.parent.children) {
145
+ const idx = mod.parent.children.indexOf(mod);
146
+ if (idx !== -1) {
147
+ mod.parent.children.splice(idx, 1);
148
+ }
149
+ }
150
+
151
+ // Delete from cache
152
+ delete cache[modulePath];
153
+ }
154
+
155
+ export default {
156
+ isModuleVersionError,
157
+ extractModulePath,
158
+ findPackageRoot,
159
+ rebuildModule,
160
+ attemptAutoRebuild,
161
+ clearRequireCache
162
+ };