rl-rockcli 0.0.4 → 0.0.6

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 (74) hide show
  1. package/commands/log/core/constants.js +237 -0
  2. package/commands/log/core/display.js +370 -0
  3. package/commands/log/core/search.js +330 -0
  4. package/commands/log/core/tail.js +216 -0
  5. package/commands/log/core/utils.js +424 -0
  6. package/commands/log.js +298 -0
  7. package/commands/sandbox/core/log-bridge.js +119 -0
  8. package/commands/sandbox/core/replay/analyzer.js +311 -0
  9. package/commands/sandbox/core/replay/batch-orchestrator.js +536 -0
  10. package/commands/sandbox/core/replay/batch-task.js +369 -0
  11. package/commands/sandbox/core/replay/concurrent-display.js +70 -0
  12. package/commands/sandbox/core/replay/concurrent-orchestrator.js +170 -0
  13. package/commands/sandbox/core/replay/data-source.js +86 -0
  14. package/commands/sandbox/core/replay/display.js +231 -0
  15. package/commands/sandbox/core/replay/executor.js +634 -0
  16. package/commands/sandbox/core/replay/history-fetcher.js +124 -0
  17. package/commands/sandbox/core/replay/index.js +338 -0
  18. package/commands/sandbox/core/replay/loghouse-data-source.js +177 -0
  19. package/commands/sandbox/core/replay/pid-mapping.js +26 -0
  20. package/commands/sandbox/core/replay/request.js +109 -0
  21. package/commands/sandbox/core/replay/worker.js +166 -0
  22. package/commands/sandbox/core/session.js +346 -0
  23. package/commands/sandbox/log-bridge.js +2 -0
  24. package/commands/sandbox/ray.js +2 -0
  25. package/commands/sandbox/replay/analyzer.js +311 -0
  26. package/commands/sandbox/replay/batch-orchestrator.js +536 -0
  27. package/commands/sandbox/replay/batch-task.js +369 -0
  28. package/commands/sandbox/replay/concurrent-display.js +70 -0
  29. package/commands/sandbox/replay/concurrent-orchestrator.js +170 -0
  30. package/commands/sandbox/replay/display.js +231 -0
  31. package/commands/sandbox/replay/executor.js +634 -0
  32. package/commands/sandbox/replay/history-fetcher.js +118 -0
  33. package/commands/sandbox/replay/index.js +338 -0
  34. package/commands/sandbox/replay/pid-mapping.js +26 -0
  35. package/commands/sandbox/replay/request.js +109 -0
  36. package/commands/sandbox/replay/worker.js +166 -0
  37. package/commands/sandbox/replay.js +2 -0
  38. package/commands/sandbox/session.js +2 -0
  39. package/commands/sandbox-original.js +1393 -0
  40. package/commands/sandbox.js +499 -0
  41. package/help/help.json +1071 -0
  42. package/help/middleware.js +71 -0
  43. package/help/renderer.js +800 -0
  44. package/index.js +20 -51
  45. package/lib/plugin-context.js +40 -0
  46. package/package.json +1 -1
  47. package/sdks/sandbox/core/client.js +845 -0
  48. package/sdks/sandbox/core/config.js +70 -0
  49. package/sdks/sandbox/core/types.js +74 -0
  50. package/sdks/sandbox/httpLogger.js +251 -0
  51. package/sdks/sandbox/index.js +9 -0
  52. package/utils/asciiArt.js +138 -0
  53. package/utils/bun-compat.js +59 -0
  54. package/utils/ciPipelines.js +138 -0
  55. package/utils/cli.js +17 -0
  56. package/utils/command-router.js +79 -0
  57. package/utils/configManager.js +503 -0
  58. package/utils/dependency-resolver.js +135 -0
  59. package/utils/eagleeye_traceid.js +151 -0
  60. package/utils/envDetector.js +78 -0
  61. package/utils/execution_logger.js +415 -0
  62. package/utils/featureManager.js +68 -0
  63. package/utils/firstTimeTip.js +44 -0
  64. package/utils/hook-manager.js +125 -0
  65. package/utils/http-logger.js +264 -0
  66. package/utils/i18n.js +139 -0
  67. package/utils/image-progress.js +159 -0
  68. package/utils/logger.js +154 -0
  69. package/utils/plugin-loader.js +124 -0
  70. package/utils/plugin-manager.js +348 -0
  71. package/utils/ray_cli_wrapper.js +746 -0
  72. package/utils/sandbox-client.js +419 -0
  73. package/utils/terminal.js +32 -0
  74. package/utils/tips.js +106 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * 首次使用提示工具
3
+ * 用于在用户首次使用时显示 rc 别名的提示信息
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+ const { showAsciiArt } = require('./asciiArt');
9
+
10
+ /**
11
+ * 获取配置目录和标记文件路径
12
+ */
13
+ function getPaths() {
14
+ const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.rock');
15
+ const FLAG_FILE = path.join(CONFIG_DIR, 'first_time_tip_shown');
16
+ return { CONFIG_DIR, FLAG_FILE };
17
+ }
18
+
19
+ /**
20
+ * 检查并显示首次使用提示
21
+ * @returns {string|null} 如果是首次使用返回提示信息,否则返回null
22
+ */
23
+ function checkAndShowFirstTimeTip() {
24
+ const { CONFIG_DIR, FLAG_FILE } = getPaths();
25
+
26
+ // 如果标记文件已存在,说明不是首次使用
27
+ if (fs.existsSync(FLAG_FILE)) {
28
+ return null;
29
+ }
30
+
31
+ // 确保配置目录存在
32
+ fs.ensureDirSync(CONFIG_DIR);
33
+
34
+ // 创建标记文件
35
+ fs.writeFileSync(FLAG_FILE, new Date().toISOString());
36
+
37
+ // 返回提示信息 + ASCII 艺术字
38
+ return showAsciiArt() + '\n💡 提示:您可以使用 rc 命令代替 rockcli 命令,更加简洁高效!\n';
39
+ }
40
+
41
+ module.exports = {
42
+ checkAndShowFirstTimeTip,
43
+ getPaths
44
+ };
@@ -0,0 +1,125 @@
1
+ const logger = require('./logger');
2
+ const { createPluginContext } = require('../lib/plugin-context');
3
+
4
+ /**
5
+ * 钩子管理器
6
+ * 用于注册、排序和执行钩子
7
+ */
8
+ class HookManager {
9
+ constructor() {
10
+ this._hooks = new Map(); // Map<hookName, Array<hookDef>>
11
+ }
12
+
13
+ /**
14
+ * 注册钩子
15
+ * @param {string} hookName - 钩子名称
16
+ * @param {Object} hookDef - 钩子定义
17
+ * @param {number} priority - 优先级
18
+ * @param {string} pluginName - 插件名称
19
+ */
20
+ registerHook(hookName, hookDef, priority = 0, pluginName = 'unknown') {
21
+ if (!this._hooks.has(hookName)) {
22
+ this._hooks.set(hookName, []);
23
+ }
24
+
25
+ this._hooks.get(hookName).push({
26
+ ...hookDef,
27
+ priority,
28
+ pluginName,
29
+ _loadOrder: this._hooks.get(hookName).length // 用于保持加载顺序
30
+ });
31
+ }
32
+
33
+ /**
34
+ * 获取指定钩子的所有处理函数
35
+ * @param {string} hookName - 钩子名称
36
+ * @returns {Array} - 排序后的钩子数组
37
+ */
38
+ getHooks(hookName) {
39
+ const hooks = this._hooks.get(hookName) || [];
40
+
41
+ // 按优先级排序,相同优先级按加载顺序排序
42
+ return [...hooks].sort((a, b) => {
43
+ if (a.priority !== b.priority) {
44
+ return a.priority - b.priority; // 优先级升序
45
+ }
46
+ return a._loadOrder - b._loadOrder; // 加载顺序
47
+ });
48
+ }
49
+
50
+ /**
51
+ * 执行钩子
52
+ * @param {string} hookName - 钩子名称
53
+ * @param {Object} baseContext - 基础上下文
54
+ * @returns {Promise<{prevented: boolean, message?: string}>}
55
+ */
56
+ async executeHooks(hookName, baseContext = {}) {
57
+ const hooks = this.getHooks(hookName);
58
+
59
+ if (hooks.length === 0) {
60
+ return { prevented: false };
61
+ }
62
+
63
+ // 创建 context 对象
64
+ const context = createPluginContext(baseContext);
65
+
66
+ for (const hook of hooks) {
67
+ try {
68
+ await hook.handler(context);
69
+
70
+ // 检查是否阻止
71
+ if (context.isPrevented) {
72
+ return {
73
+ prevented: true,
74
+ message: context.preventMessage
75
+ };
76
+ }
77
+ } catch (error) {
78
+ logger.error(`Hook execution failed: ${hookName} (plugin: ${hook.pluginName})`);
79
+ logger.error(error);
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ return { prevented: false };
85
+ }
86
+
87
+ /**
88
+ * 获取所有钩子信息
89
+ * @returns {Object} - 钩子信息对象
90
+ */
91
+ getHooksInfo() {
92
+ const info = {};
93
+ for (const [hookName, hooks] of this._hooks.entries()) {
94
+ info[hookName] = hooks.map(h => ({
95
+ priority: h.priority,
96
+ pluginName: h.pluginName
97
+ }));
98
+ }
99
+ return info;
100
+ }
101
+ }
102
+
103
+ // 单例实例
104
+ HookManager._instance = null;
105
+
106
+ /**
107
+ * 获取单例实例
108
+ * @static
109
+ */
110
+ HookManager.getInstance = function() {
111
+ if (!HookManager._instance) {
112
+ HookManager._instance = new HookManager();
113
+ }
114
+ return HookManager._instance;
115
+ };
116
+
117
+ /**
118
+ * 设置单例实例
119
+ * @static
120
+ */
121
+ HookManager.setInstance = function(instance) {
122
+ HookManager._instance = instance;
123
+ };
124
+
125
+ module.exports = HookManager;
@@ -0,0 +1,264 @@
1
+ /**
2
+ * HTTP Logger - Event emitter for HTTP request/response logging
3
+ *
4
+ * Log format: [HH:mm:ss.SSS] [LEVEL] [ACTION] [traceId] msg
5
+ *
6
+ * This module provides a centralized way to log HTTP requests and responses.
7
+ * InkREPL's console can subscribe to these events to display them.
8
+ */
9
+
10
+ const EventEmitter = require('events');
11
+
12
+ // Action types
13
+ const ACTION = {
14
+ SESSION: 'SESSION',
15
+ HTTP_REQ: 'HTTP_REQ',
16
+ HTTP_RES: 'HTTP_RES',
17
+ HTTP_ERR: 'HTTP_ERR',
18
+ CMD: 'CMD',
19
+ };
20
+
21
+ // Log levels
22
+ const LEVEL = {
23
+ DEBUG: 'DEBUG',
24
+ INFO: 'INFO',
25
+ WARN: 'WARN',
26
+ ERROR: 'ERROR',
27
+ };
28
+
29
+ class HttpLogger extends EventEmitter {
30
+ constructor() {
31
+ super();
32
+ this.enabled = false;
33
+ }
34
+
35
+ /**
36
+ * Enable HTTP logging
37
+ */
38
+ enable() {
39
+ this.enabled = true;
40
+ }
41
+
42
+ /**
43
+ * Disable HTTP logging
44
+ */
45
+ disable() {
46
+ this.enabled = false;
47
+ }
48
+
49
+ /**
50
+ * Format timestamp with milliseconds
51
+ */
52
+ _formatTime(date) {
53
+ const pad = (n, len = 2) => String(n).padStart(len, '0');
54
+ return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`;
55
+ }
56
+
57
+ /**
58
+ * Format log message
59
+ * Format: [HH:mm:ss.SSS] [LEVEL] [ACTION] [traceId] msg
60
+ */
61
+ _formatMessage(level, action, traceId, msg) {
62
+ const time = this._formatTime(new Date());
63
+ const tid = traceId || '-';
64
+ return `[${time}] [${level.padEnd(5)}] [${action.padEnd(8)}] [${tid}] ${msg}`;
65
+ }
66
+
67
+ /**
68
+ * Truncate and format JSON for logging
69
+ */
70
+ _formatBody(data, maxLen = 200) {
71
+ if (!data) return null;
72
+
73
+ try {
74
+ let json = JSON.stringify(data);
75
+ if (json.length > maxLen) {
76
+ json = json.slice(0, maxLen) + '...';
77
+ }
78
+ return json;
79
+ } catch {
80
+ return String(data).slice(0, maxLen);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Log an HTTP request
86
+ * @param {string} method - HTTP method (GET, POST, etc.)
87
+ * @param {string} url - Request URL
88
+ * @param {Object} data - Request body (optional)
89
+ */
90
+ logRequest(method, url, data = null) {
91
+ if (!this.enabled) return;
92
+
93
+ const urlPath = this._extractPath(url);
94
+ const msg = `${method} ${urlPath}`;
95
+ const message = this._formatMessage(LEVEL.DEBUG, ACTION.HTTP_REQ, null, msg);
96
+
97
+ const event = {
98
+ type: 'request',
99
+ level: LEVEL.DEBUG,
100
+ action: ACTION.HTTP_REQ,
101
+ method,
102
+ url: urlPath,
103
+ data,
104
+ message,
105
+ timestamp: new Date(),
106
+ };
107
+
108
+ this.emit('request', event);
109
+
110
+ // Log request body as separate debug entry
111
+ if (data) {
112
+ const bodyJson = this._formatBody(data);
113
+ const fullBody = this._formatBody(data, Infinity); // Full body for copying
114
+ if (bodyJson) {
115
+ const bodyMsg = this._formatMessage(LEVEL.DEBUG, ACTION.HTTP_REQ, null, `body: ${bodyJson}`);
116
+ this.emit('request', {
117
+ ...event,
118
+ message: bodyMsg,
119
+ fullMessage: `body: ${fullBody}`, // Store full message for copying
120
+ isBody: true,
121
+ });
122
+ }
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Log an HTTP response
128
+ * @param {string} method - HTTP method
129
+ * @param {string} url - Request URL
130
+ * @param {number} status - HTTP status code
131
+ * @param {number} duration - Request duration in ms
132
+ * @param {Object} data - Response data (optional)
133
+ * @param {string} traceId - Trace ID from response headers (optional)
134
+ */
135
+ logResponse(method, url, status, duration, data = null, traceId = null) {
136
+ if (!this.enabled) return;
137
+
138
+ const urlPath = this._extractPath(url);
139
+ const statusText = status >= 200 && status < 300 ? 'OK' : 'FAIL';
140
+ const level = status >= 400 ? LEVEL.WARN : LEVEL.INFO;
141
+
142
+ const msg = `${method} ${urlPath} ${status} ${statusText} ${duration}ms`;
143
+ const message = this._formatMessage(level, ACTION.HTTP_RES, traceId, msg);
144
+
145
+ const event = {
146
+ type: 'response',
147
+ level,
148
+ action: ACTION.HTTP_RES,
149
+ method,
150
+ url: urlPath,
151
+ status,
152
+ duration,
153
+ data,
154
+ traceId,
155
+ message,
156
+ timestamp: new Date(),
157
+ };
158
+
159
+ this.emit('response', event);
160
+
161
+ // Log response body as separate debug entry
162
+ if (data) {
163
+ const bodyJson = this._formatBody(data);
164
+ const fullBody = this._formatBody(data, Infinity); // Full body for copying
165
+ if (bodyJson) {
166
+ const bodyMsg = this._formatMessage(LEVEL.DEBUG, ACTION.HTTP_RES, traceId, `body: ${bodyJson}`);
167
+ this.emit('response', {
168
+ ...event,
169
+ level: LEVEL.DEBUG,
170
+ message: bodyMsg,
171
+ fullMessage: `body: ${fullBody}`, // Store full message for copying
172
+ isBody: true,
173
+ });
174
+ }
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Log an HTTP error
180
+ * @param {string} method - HTTP method
181
+ * @param {string} url - Request URL
182
+ * @param {Error} error - Error object
183
+ * @param {number} duration - Request duration in ms
184
+ * @param {string} traceId - Trace ID from response headers (optional)
185
+ */
186
+ logError(method, url, error, duration, traceId = null) {
187
+ if (!this.enabled) return;
188
+
189
+ const urlPath = this._extractPath(url);
190
+ // Safely extract error message - handle non-Error objects
191
+ let errorMessage;
192
+ if (error === null) {
193
+ errorMessage = 'null';
194
+ } else if (error === undefined) {
195
+ errorMessage = 'undefined';
196
+ } else if (error instanceof Error) {
197
+ errorMessage = error.message;
198
+ } else if (typeof error === 'object' && error.message) {
199
+ errorMessage = error.message;
200
+ } else {
201
+ errorMessage = String(error);
202
+ }
203
+ const msg = `${method} ${urlPath} ERROR: ${errorMessage} ${duration}ms`;
204
+ const message = this._formatMessage(LEVEL.ERROR, ACTION.HTTP_ERR, traceId, msg);
205
+
206
+ this.emit('error', {
207
+ type: 'error',
208
+ level: LEVEL.ERROR,
209
+ action: ACTION.HTTP_ERR,
210
+ method,
211
+ url: urlPath,
212
+ error: errorMessage,
213
+ duration,
214
+ traceId,
215
+ message,
216
+ timestamp: new Date(),
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Log a generic message (for session, cmd, etc.)
222
+ * @param {string} level - Log level
223
+ * @param {string} action - Action type
224
+ * @param {string} msg - Message
225
+ * @param {string} traceId - Trace ID (optional)
226
+ */
227
+ log(level, action, msg, traceId = null) {
228
+ if (!this.enabled) return;
229
+
230
+ const message = this._formatMessage(level, action, traceId, msg);
231
+
232
+ this.emit('log', {
233
+ type: 'log',
234
+ level,
235
+ action,
236
+ traceId,
237
+ message,
238
+ timestamp: new Date(),
239
+ });
240
+ }
241
+
242
+ /**
243
+ * Extract path from full URL
244
+ */
245
+ _extractPath(url) {
246
+ try {
247
+ const urlObj = new URL(url);
248
+ return urlObj.pathname;
249
+ } catch {
250
+ // If URL parsing fails, try to extract path manually
251
+ const match = url.match(/\/apis\/.*$/);
252
+ return match ? match[0] : url;
253
+ }
254
+ }
255
+ }
256
+
257
+ // Export constants
258
+ HttpLogger.ACTION = ACTION;
259
+ HttpLogger.LEVEL = LEVEL;
260
+
261
+ // Singleton instance
262
+ const httpLogger = new HttpLogger();
263
+
264
+ module.exports = { httpLogger, HttpLogger };
package/utils/i18n.js ADDED
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Internationalization (i18n) utilities
3
+ */
4
+
5
+ /**
6
+ * Detect system language
7
+ * @returns {string} Language code ('zh' for Chinese, 'en' for English)
8
+ */
9
+ function detectLanguage() {
10
+ const lang = (
11
+ process.env.LC_ALL ||
12
+ process.env.LANG ||
13
+ process.env.LC_MESSAGES ||
14
+ 'en'
15
+ ).toLowerCase();
16
+
17
+ // Check for Chinese locale
18
+ if (lang.startsWith('zh')) {
19
+ return 'zh';
20
+ }
21
+
22
+ // Default to English
23
+ return 'en';
24
+ }
25
+
26
+ /**
27
+ * Translations
28
+ */
29
+ const translations = {
30
+ zh: {
31
+ // WelcomeBanner TIPS
32
+ 'welcome.tip.commands': '使用 / 查看内置命令',
33
+ 'welcome.tip.execute': '输入 shell 指令即可在远端执行',
34
+ 'welcome.tip.console': '按 F12 打开调试控制台',
35
+ 'welcome.tip.exit': '双按 Ctrl+C 退出',
36
+
37
+ // ConnectingScreen
38
+ 'connecting.message': '🔄 正在连接沙箱...',
39
+ 'connecting.attempt': '尝试 ${attempt}/${maxAttempts} 次...',
40
+
41
+ // replState welcome output
42
+ 'welcome.connected': '已连接。准备就绪。',
43
+
44
+ // repl.js exit banner
45
+ 'exit.session.duration': '✷ 会话持续时间',
46
+ 'exit.new.session': '新建会话:',
47
+ 'exit.resume.session': '恢复会话:',
48
+
49
+ // image mirror exit summary
50
+ 'image.task.completed': '任务完成!',
51
+ 'image.task.interrupted': '任务已中断。',
52
+ 'image.task.exited': '任务已退出。',
53
+ 'image.task.ended': '任务已结束。',
54
+ 'image.task.error': '任务出错:',
55
+ 'image.task.summary': '共处理 ${total} 个镜像,成功 ${completed} 个,跳过 ${skipped} 个,失败 ${failed} 个。',
56
+ 'image.task.partial': '已完成 ${completed}/${total} 个镜像',
57
+ 'image.task.failedCount': '(${failed} 个失败)',
58
+ 'image.task.skippedCount': '(${skipped} 个跳过)',
59
+ 'image.resume.command': '恢复命令:',
60
+ 'image.log.saved': '日志已保存到: ${logDir}',
61
+ 'image.password.placeholder': '<请填写密码>',
62
+ },
63
+ en: {
64
+ // WelcomeBanner TIPS
65
+ 'welcome.tip.commands': 'Type / for available commands',
66
+ 'welcome.tip.execute': 'Run shell commands remotely',
67
+ 'welcome.tip.console': 'Press F12 to open Console',
68
+ 'welcome.tip.exit': 'Double Ctrl+C to exit',
69
+
70
+ // ConnectingScreen
71
+ 'connecting.message': '🔄 Connecting to sandbox...',
72
+ 'connecting.attempt': 'Attempt ${attempt}/${maxAttempts}...',
73
+
74
+ // replState welcome output
75
+ 'welcome.connected': 'Connected. Ready.',
76
+
77
+ // repl.js exit banner
78
+ 'exit.session.duration': '✷ Session lasted',
79
+ 'exit.new.session': 'New session:',
80
+ 'exit.resume.session': 'Resume session:',
81
+
82
+ // image mirror exit summary
83
+ 'image.task.completed': 'Task completed!',
84
+ 'image.task.interrupted': 'Task interrupted.',
85
+ 'image.task.exited': 'Task exited.',
86
+ 'image.task.ended': 'Task ended.',
87
+ 'image.task.error': 'Task error:',
88
+ 'image.task.summary': 'Processed ${total} images: ${completed} succeeded, ${skipped} skipped, ${failed} failed.',
89
+ 'image.task.partial': 'Completed ${completed}/${total} images',
90
+ 'image.task.failedCount': ' (${failed} failed)',
91
+ 'image.task.skippedCount': ' (${skipped} skipped)',
92
+ 'image.resume.command': 'Resume command:',
93
+ 'image.log.saved': 'Logs saved to: ${logDir}',
94
+ 'image.password.placeholder': '<enter password>',
95
+ },
96
+ };
97
+
98
+ /**
99
+ * Get translation for a key
100
+ * @param {string} key - Translation key
101
+ * @param {string} [language] - Language code (optional, auto-detected if not provided)
102
+ * @param {Object} [vars] - Variables for interpolation (e.g., { total: 10, completed: 5 })
103
+ * @returns {string} Translated string with variables replaced
104
+ */
105
+ function t(key, language = null, vars = null) {
106
+ const lang = language || detectLanguage();
107
+ const langTranslations = translations[lang] || translations.en;
108
+ let text = langTranslations[key] || translations.en[key] || key;
109
+
110
+ // Replace variables like ${varName}
111
+ if (vars && typeof vars === 'object') {
112
+ for (const [varName, value] of Object.entries(vars)) {
113
+ text = text.replace(new RegExp(`\\$\\{${varName}\\}`, 'g'), String(value));
114
+ }
115
+ }
116
+
117
+ return text;
118
+ }
119
+
120
+ /**
121
+ * Get current language
122
+ * @returns {string} Current language code
123
+ */
124
+ function getCurrentLanguage() {
125
+ return detectLanguage();
126
+ }
127
+
128
+ // Export for both CommonJS and ES Module compatibility
129
+ const i18nExports = {
130
+ detectLanguage,
131
+ t,
132
+ getCurrentLanguage,
133
+ };
134
+
135
+ // CommonJS export
136
+ module.exports = i18nExports;
137
+
138
+ // ES Module default export (for Ink components)
139
+ module.exports.default = i18nExports;