wave-code 0.0.2

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 (131) hide show
  1. package/README.md +120 -0
  2. package/bin/wave-code.js +16 -0
  3. package/dist/cli.d.ts +6 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +62 -0
  6. package/dist/components/App.d.ts +8 -0
  7. package/dist/components/App.d.ts.map +1 -0
  8. package/dist/components/App.js +10 -0
  9. package/dist/components/BashHistorySelector.d.ts +10 -0
  10. package/dist/components/BashHistorySelector.d.ts.map +1 -0
  11. package/dist/components/BashHistorySelector.js +83 -0
  12. package/dist/components/BashShellManager.d.ts +6 -0
  13. package/dist/components/BashShellManager.d.ts.map +1 -0
  14. package/dist/components/BashShellManager.js +116 -0
  15. package/dist/components/ChatInterface.d.ts +3 -0
  16. package/dist/components/ChatInterface.d.ts.map +1 -0
  17. package/dist/components/ChatInterface.js +31 -0
  18. package/dist/components/CommandOutputDisplay.d.ts +9 -0
  19. package/dist/components/CommandOutputDisplay.d.ts.map +1 -0
  20. package/dist/components/CommandOutputDisplay.js +40 -0
  21. package/dist/components/CommandSelector.d.ts +11 -0
  22. package/dist/components/CommandSelector.d.ts.map +1 -0
  23. package/dist/components/CommandSelector.js +60 -0
  24. package/dist/components/CompressDisplay.d.ts +9 -0
  25. package/dist/components/CompressDisplay.d.ts.map +1 -0
  26. package/dist/components/CompressDisplay.js +17 -0
  27. package/dist/components/DiffViewer.d.ts +9 -0
  28. package/dist/components/DiffViewer.d.ts.map +1 -0
  29. package/dist/components/DiffViewer.js +221 -0
  30. package/dist/components/FileSelector.d.ts +13 -0
  31. package/dist/components/FileSelector.d.ts.map +1 -0
  32. package/dist/components/FileSelector.js +48 -0
  33. package/dist/components/InputBox.d.ts +23 -0
  34. package/dist/components/InputBox.d.ts.map +1 -0
  35. package/dist/components/InputBox.js +124 -0
  36. package/dist/components/McpManager.d.ts +10 -0
  37. package/dist/components/McpManager.d.ts.map +1 -0
  38. package/dist/components/McpManager.js +123 -0
  39. package/dist/components/MemoryDisplay.d.ts +8 -0
  40. package/dist/components/MemoryDisplay.d.ts.map +1 -0
  41. package/dist/components/MemoryDisplay.js +25 -0
  42. package/dist/components/MemoryTypeSelector.d.ts +8 -0
  43. package/dist/components/MemoryTypeSelector.d.ts.map +1 -0
  44. package/dist/components/MemoryTypeSelector.js +38 -0
  45. package/dist/components/MessageList.d.ts +12 -0
  46. package/dist/components/MessageList.d.ts.map +1 -0
  47. package/dist/components/MessageList.js +36 -0
  48. package/dist/components/ToolResultDisplay.d.ts +9 -0
  49. package/dist/components/ToolResultDisplay.d.ts.map +1 -0
  50. package/dist/components/ToolResultDisplay.js +52 -0
  51. package/dist/contexts/useAppConfig.d.ts +11 -0
  52. package/dist/contexts/useAppConfig.d.ts.map +1 -0
  53. package/dist/contexts/useAppConfig.js +13 -0
  54. package/dist/contexts/useChat.d.ts +36 -0
  55. package/dist/contexts/useChat.d.ts.map +1 -0
  56. package/dist/contexts/useChat.js +208 -0
  57. package/dist/hooks/useBashHistorySelector.d.ts +15 -0
  58. package/dist/hooks/useBashHistorySelector.d.ts.map +1 -0
  59. package/dist/hooks/useBashHistorySelector.js +61 -0
  60. package/dist/hooks/useCommandSelector.d.ts +24 -0
  61. package/dist/hooks/useCommandSelector.d.ts.map +1 -0
  62. package/dist/hooks/useCommandSelector.js +98 -0
  63. package/dist/hooks/useFileSelector.d.ts +16 -0
  64. package/dist/hooks/useFileSelector.d.ts.map +1 -0
  65. package/dist/hooks/useFileSelector.js +174 -0
  66. package/dist/hooks/useImageManager.d.ts +13 -0
  67. package/dist/hooks/useImageManager.d.ts.map +1 -0
  68. package/dist/hooks/useImageManager.js +46 -0
  69. package/dist/hooks/useInputHistory.d.ts +11 -0
  70. package/dist/hooks/useInputHistory.d.ts.map +1 -0
  71. package/dist/hooks/useInputHistory.js +64 -0
  72. package/dist/hooks/useInputKeyboardHandler.d.ts +83 -0
  73. package/dist/hooks/useInputKeyboardHandler.d.ts.map +1 -0
  74. package/dist/hooks/useInputKeyboardHandler.js +507 -0
  75. package/dist/hooks/useInputState.d.ts +14 -0
  76. package/dist/hooks/useInputState.d.ts.map +1 -0
  77. package/dist/hooks/useInputState.js +57 -0
  78. package/dist/hooks/useMemoryTypeSelector.d.ts +9 -0
  79. package/dist/hooks/useMemoryTypeSelector.d.ts.map +1 -0
  80. package/dist/hooks/useMemoryTypeSelector.js +27 -0
  81. package/dist/hooks/usePagination.d.ts +20 -0
  82. package/dist/hooks/usePagination.d.ts.map +1 -0
  83. package/dist/hooks/usePagination.js +168 -0
  84. package/dist/index.d.ts +5 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +91 -0
  87. package/dist/plain-cli.d.ts +7 -0
  88. package/dist/plain-cli.d.ts.map +1 -0
  89. package/dist/plain-cli.js +49 -0
  90. package/dist/utils/clipboard.d.ts +22 -0
  91. package/dist/utils/clipboard.d.ts.map +1 -0
  92. package/dist/utils/clipboard.js +347 -0
  93. package/dist/utils/constants.d.ts +17 -0
  94. package/dist/utils/constants.d.ts.map +1 -0
  95. package/dist/utils/constants.js +18 -0
  96. package/dist/utils/logger.d.ts +72 -0
  97. package/dist/utils/logger.d.ts.map +1 -0
  98. package/dist/utils/logger.js +245 -0
  99. package/package.json +60 -0
  100. package/src/cli.tsx +82 -0
  101. package/src/components/App.tsx +31 -0
  102. package/src/components/BashHistorySelector.tsx +163 -0
  103. package/src/components/BashShellManager.tsx +306 -0
  104. package/src/components/ChatInterface.tsx +88 -0
  105. package/src/components/CommandOutputDisplay.tsx +81 -0
  106. package/src/components/CommandSelector.tsx +144 -0
  107. package/src/components/CompressDisplay.tsx +58 -0
  108. package/src/components/DiffViewer.tsx +321 -0
  109. package/src/components/FileSelector.tsx +137 -0
  110. package/src/components/InputBox.tsx +310 -0
  111. package/src/components/McpManager.tsx +328 -0
  112. package/src/components/MemoryDisplay.tsx +62 -0
  113. package/src/components/MemoryTypeSelector.tsx +96 -0
  114. package/src/components/MessageList.tsx +215 -0
  115. package/src/components/ToolResultDisplay.tsx +138 -0
  116. package/src/contexts/useAppConfig.tsx +32 -0
  117. package/src/contexts/useChat.tsx +300 -0
  118. package/src/hooks/useBashHistorySelector.ts +77 -0
  119. package/src/hooks/useCommandSelector.ts +131 -0
  120. package/src/hooks/useFileSelector.ts +227 -0
  121. package/src/hooks/useImageManager.ts +64 -0
  122. package/src/hooks/useInputHistory.ts +74 -0
  123. package/src/hooks/useInputKeyboardHandler.ts +778 -0
  124. package/src/hooks/useInputState.ts +66 -0
  125. package/src/hooks/useMemoryTypeSelector.ts +40 -0
  126. package/src/hooks/usePagination.ts +203 -0
  127. package/src/index.ts +108 -0
  128. package/src/plain-cli.ts +66 -0
  129. package/src/utils/clipboard.ts +384 -0
  130. package/src/utils/constants.ts +22 -0
  131. package/src/utils/logger.ts +301 -0
@@ -0,0 +1,347 @@
1
+ import { unlinkSync, existsSync } from "fs";
2
+ import { join } from "path";
3
+ import { tmpdir } from "os";
4
+ /**
5
+ * Read image from clipboard
6
+ * @returns Promise<ClipboardImageResult> Result containing image path or error information
7
+ */
8
+ export async function readClipboardImage() {
9
+ try {
10
+ const platform = process.platform;
11
+ if (platform === "darwin") {
12
+ return await readClipboardImageMac();
13
+ }
14
+ else if (platform === "win32") {
15
+ return await readClipboardImageWindows();
16
+ }
17
+ else if (platform === "linux") {
18
+ return await readClipboardImageLinux();
19
+ }
20
+ else {
21
+ return {
22
+ success: false,
23
+ error: `Clipboard image reading is not supported on platform: ${platform}`,
24
+ };
25
+ }
26
+ }
27
+ catch (error) {
28
+ return {
29
+ success: false,
30
+ error: `Failed to read clipboard image: ${error instanceof Error ? error.message : String(error)}`,
31
+ };
32
+ }
33
+ }
34
+ /**
35
+ * Read clipboard image on macOS
36
+ */
37
+ async function readClipboardImageMac() {
38
+ const { exec } = await import("child_process");
39
+ const { promisify } = await import("util");
40
+ const execAsync = promisify(exec);
41
+ try {
42
+ // Try to read image data directly to check if image exists
43
+ const testScript = `
44
+ tell application "System Events"
45
+ try
46
+ set imageData to the clipboard as «class PNGf»
47
+ return true
48
+ on error
49
+ return false
50
+ end try
51
+ end tell
52
+ `;
53
+ let hasImage = false;
54
+ try {
55
+ const { stdout: testResult } = await execAsync(`osascript -e '${testScript}'`);
56
+ hasImage = testResult.trim() === "true";
57
+ }
58
+ catch {
59
+ hasImage = false;
60
+ }
61
+ if (!hasImage) {
62
+ return {
63
+ success: false,
64
+ error: "No image found in clipboard",
65
+ };
66
+ }
67
+ // Generate temporary file path
68
+ const tempFilePath = join(tmpdir(), `clipboard-image-${Date.now()}.png`);
69
+ // Use osascript to save clipboard image as file
70
+ const saveScript = `
71
+ tell application "System Events"
72
+ try
73
+ set imageData to the clipboard as «class PNGf»
74
+ set fileRef to open for access POSIX file "${tempFilePath}" with write permission
75
+ write imageData to fileRef
76
+ close access fileRef
77
+ return true
78
+ on error errMsg
79
+ try
80
+ close access fileRef
81
+ end try
82
+ error errMsg
83
+ end try
84
+ end tell
85
+ `;
86
+ await execAsync(`osascript -e '${saveScript}'`);
87
+ // Verify if file was created successfully
88
+ if (!existsSync(tempFilePath)) {
89
+ return {
90
+ success: false,
91
+ error: "Failed to save clipboard image to temporary file",
92
+ };
93
+ }
94
+ return {
95
+ success: true,
96
+ imagePath: tempFilePath,
97
+ mimeType: "image/png",
98
+ };
99
+ }
100
+ catch (error) {
101
+ return {
102
+ success: false,
103
+ error: `Failed to read clipboard image on macOS: ${error instanceof Error ? error.message : String(error)}`,
104
+ };
105
+ }
106
+ }
107
+ /**
108
+ * Read clipboard image on Windows
109
+ */
110
+ async function readClipboardImageWindows() {
111
+ try {
112
+ const { exec } = await import("child_process");
113
+ const { promisify } = await import("util");
114
+ const execAsync = promisify(exec);
115
+ // Use PowerShell to check if clipboard contains image
116
+ const checkScript = `
117
+ Add-Type -AssemblyName System.Windows.Forms
118
+ if ([System.Windows.Forms.Clipboard]::ContainsImage()) {
119
+ Write-Output "true"
120
+ } else {
121
+ Write-Output "false"
122
+ }
123
+ `;
124
+ try {
125
+ const { stdout } = await execAsync(`powershell -Command "${checkScript}"`);
126
+ const hasImage = stdout.trim() === "true";
127
+ if (!hasImage) {
128
+ return {
129
+ success: false,
130
+ error: "No image found in clipboard",
131
+ };
132
+ }
133
+ // Generate temporary file path
134
+ const tempFilePath = join(tmpdir(), `clipboard-image-${Date.now()}.png`);
135
+ // Use PowerShell to save clipboard image
136
+ const saveScript = `
137
+ Add-Type -AssemblyName System.Windows.Forms
138
+ Add-Type -AssemblyName System.Drawing
139
+ $image = [System.Windows.Forms.Clipboard]::GetImage()
140
+ if ($image -ne $null) {
141
+ $image.Save("${tempFilePath.replace(/\\/g, "\\\\")}", [System.Drawing.Imaging.ImageFormat]::Png)
142
+ Write-Output "true"
143
+ } else {
144
+ Write-Output "false"
145
+ }
146
+ `;
147
+ const { stdout: saveResult } = await execAsync(`powershell -Command "${saveScript}"`);
148
+ if (saveResult.trim() !== "true" || !existsSync(tempFilePath)) {
149
+ return {
150
+ success: false,
151
+ error: "Failed to save clipboard image to temporary file",
152
+ };
153
+ }
154
+ return {
155
+ success: true,
156
+ imagePath: tempFilePath,
157
+ mimeType: "image/png",
158
+ };
159
+ }
160
+ catch (err) {
161
+ return {
162
+ success: false,
163
+ error: `Failed to access clipboard on Windows: ${err instanceof Error ? err.message : String(err)}`,
164
+ };
165
+ }
166
+ }
167
+ catch (err) {
168
+ return {
169
+ success: false,
170
+ error: `Failed to read clipboard image on Windows: ${err instanceof Error ? err.message : String(err)}`,
171
+ };
172
+ }
173
+ }
174
+ /**
175
+ * Read clipboard image on Linux
176
+ */
177
+ async function readClipboardImageLinux() {
178
+ try {
179
+ // Linux can use tools like xclip or wl-clipboard
180
+ const { exec } = await import("child_process");
181
+ const { promisify } = await import("util");
182
+ const execAsync = promisify(exec);
183
+ // Check if xclip is available
184
+ try {
185
+ await execAsync("which xclip");
186
+ }
187
+ catch {
188
+ return {
189
+ success: false,
190
+ error: "xclip is required for clipboard image operations on Linux. Please install it: sudo apt-get install xclip",
191
+ };
192
+ }
193
+ // Check if clipboard contains image
194
+ try {
195
+ await execAsync("xclip -selection clipboard -t image/png -o > /dev/null 2>&1");
196
+ }
197
+ catch {
198
+ return {
199
+ success: false,
200
+ error: "No image found in clipboard",
201
+ };
202
+ }
203
+ // Generate temporary file path
204
+ const tempFilePath = join(tmpdir(), `clipboard-image-${Date.now()}.png`);
205
+ // Use xclip to save clipboard image
206
+ try {
207
+ await execAsync(`xclip -selection clipboard -t image/png -o > "${tempFilePath}"`);
208
+ if (!existsSync(tempFilePath)) {
209
+ return {
210
+ success: false,
211
+ error: "Failed to save clipboard image to temporary file",
212
+ };
213
+ }
214
+ return {
215
+ success: true,
216
+ imagePath: tempFilePath,
217
+ mimeType: "image/png",
218
+ };
219
+ }
220
+ catch (err) {
221
+ return {
222
+ success: false,
223
+ error: `Failed to save clipboard image: ${err instanceof Error ? err.message : String(err)}`,
224
+ };
225
+ }
226
+ }
227
+ catch (err) {
228
+ return {
229
+ success: false,
230
+ error: `Failed to read clipboard image on Linux: ${err instanceof Error ? err.message : String(err)}`,
231
+ };
232
+ }
233
+ }
234
+ /**
235
+ * Clean up temporary image file
236
+ * @param imagePath Path to the image file to clean up
237
+ */
238
+ export function cleanupTempImage(imagePath) {
239
+ try {
240
+ if (existsSync(imagePath)) {
241
+ unlinkSync(imagePath);
242
+ }
243
+ }
244
+ catch (error) {
245
+ console.warn(`Failed to cleanup temporary image file: ${imagePath}`, error);
246
+ }
247
+ }
248
+ /**
249
+ * Check if clipboard contains image (quick check, does not save file)
250
+ * @returns Promise<boolean> Whether it contains image
251
+ */
252
+ export async function hasClipboardImage() {
253
+ try {
254
+ const platform = process.platform;
255
+ if (platform === "darwin") {
256
+ return await hasClipboardImageMac();
257
+ }
258
+ else if (platform === "win32") {
259
+ return await hasClipboardImageWindows();
260
+ }
261
+ else if (platform === "linux") {
262
+ return await hasClipboardImageLinux();
263
+ }
264
+ else {
265
+ return false;
266
+ }
267
+ }
268
+ catch {
269
+ return false;
270
+ }
271
+ }
272
+ /**
273
+ * Check if clipboard contains image on macOS
274
+ */
275
+ async function hasClipboardImageMac() {
276
+ try {
277
+ const { exec } = await import("child_process");
278
+ const { promisify } = await import("util");
279
+ const execAsync = promisify(exec);
280
+ const checkScript = `
281
+ tell application "System Events"
282
+ try
283
+ set imageData to the clipboard as «class PNGf»
284
+ return true
285
+ on error
286
+ return false
287
+ end try
288
+ end tell
289
+ `;
290
+ const { stdout } = await execAsync(`osascript -e '${checkScript}'`);
291
+ return stdout.trim() === "true";
292
+ }
293
+ catch {
294
+ return false;
295
+ }
296
+ }
297
+ /**
298
+ * Check if clipboard contains image on Windows
299
+ */
300
+ async function hasClipboardImageWindows() {
301
+ try {
302
+ const { exec } = await import("child_process");
303
+ const { promisify } = await import("util");
304
+ const execAsync = promisify(exec);
305
+ const checkScript = `
306
+ Add-Type -AssemblyName System.Windows.Forms
307
+ if ([System.Windows.Forms.Clipboard]::ContainsImage()) {
308
+ Write-Output "true"
309
+ } else {
310
+ Write-Output "false"
311
+ }
312
+ `;
313
+ const { stdout } = await execAsync(`powershell -Command "${checkScript}"`);
314
+ return stdout.trim() === "true";
315
+ }
316
+ catch {
317
+ return false;
318
+ }
319
+ }
320
+ /**
321
+ * Check if clipboard contains image on Linux
322
+ */
323
+ async function hasClipboardImageLinux() {
324
+ try {
325
+ const { exec } = await import("child_process");
326
+ const { promisify } = await import("util");
327
+ const execAsync = promisify(exec);
328
+ // Check if xclip is available
329
+ try {
330
+ await execAsync("which xclip");
331
+ }
332
+ catch {
333
+ return false;
334
+ }
335
+ // Check if clipboard contains image
336
+ try {
337
+ await execAsync("xclip -selection clipboard -t image/png -o > /dev/null 2>&1");
338
+ return true;
339
+ }
340
+ catch {
341
+ return false;
342
+ }
343
+ }
344
+ catch {
345
+ return false;
346
+ }
347
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Application constants definition
3
+ */
4
+ /**
5
+ * Application data storage directory
6
+ * Used to store debug logs, command history and other data
7
+ */
8
+ export declare const DATA_DIRECTORY: string;
9
+ /**
10
+ * Application log file path
11
+ */
12
+ export declare const LOG_FILE: string;
13
+ /**
14
+ * Pagination related constants
15
+ */
16
+ export declare const MESSAGES_PER_PAGE = 20;
17
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/utils/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH;;;GAGG;AACH,eAAO,MAAM,cAAc,QAAmC,CAAC;AAE/D;;GAEG;AACH,eAAO,MAAM,QAAQ,QAAuC,CAAC;AAE7D;;GAEG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Application constants definition
3
+ */
4
+ import path from "path";
5
+ import os from "os";
6
+ /**
7
+ * Application data storage directory
8
+ * Used to store debug logs, command history and other data
9
+ */
10
+ export const DATA_DIRECTORY = path.join(os.homedir(), ".wave");
11
+ /**
12
+ * Application log file path
13
+ */
14
+ export const LOG_FILE = path.join(DATA_DIRECTORY, "app.log");
15
+ /**
16
+ * Pagination related constants
17
+ */
18
+ export const MESSAGES_PER_PAGE = 20; // Number of messages displayed per page
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Logger utility module
3
+ * Supports filtering by log level and keywords
4
+ * Logs are written to files instead of terminal to avoid being cleared by Ink app
5
+ *
6
+ * Performance optimization:
7
+ * - In test environment, you can disable all file and console I/O operations by setting environment variable DISABLE_LOGGER_IO=true
8
+ * - This can significantly improve test execution performance by avoiding unnecessary disk writes
9
+ */
10
+ /**
11
+ * Log level enumeration
12
+ */
13
+ export declare enum LogLevel {
14
+ DEBUG = 0,
15
+ INFO = 1,
16
+ WARN = 2,
17
+ ERROR = 3
18
+ }
19
+ /**
20
+ * Log configuration interface
21
+ */
22
+ interface LogConfig {
23
+ readonly level: LogLevel;
24
+ readonly keywords: string[];
25
+ }
26
+ /**
27
+ * Logger object
28
+ */
29
+ export declare const logger: {
30
+ /**
31
+ * Debug log output function
32
+ */
33
+ debug: (...args: unknown[]) => void;
34
+ /**
35
+ * Info log output function
36
+ */
37
+ info: (...args: unknown[]) => void;
38
+ /**
39
+ * Warning log output function
40
+ */
41
+ warn: (...args: unknown[]) => void;
42
+ /**
43
+ * Error log output function
44
+ */
45
+ error: (...args: unknown[]) => void;
46
+ };
47
+ /**
48
+ * Get current log configuration
49
+ */
50
+ export declare const getLogConfig: () => LogConfig;
51
+ /**
52
+ * Get log file path
53
+ */
54
+ export declare const getLogFile: () => string;
55
+ /**
56
+ * Log cleanup configuration
57
+ */
58
+ interface LogCleanupConfig {
59
+ /** Maximum size of current log file (bytes), default 10MB */
60
+ maxFileSize: number;
61
+ /** Number of lines to keep when truncating, default 1000 lines */
62
+ keepLines: number;
63
+ }
64
+ /**
65
+ * Execute log cleanup
66
+ * Truncate current log file (if needed)
67
+ *
68
+ * @param customConfig Custom cleanup configuration, if not provided uses default configuration
69
+ */
70
+ export declare const cleanupLogs: (customConfig?: Partial<LogCleanupConfig>) => Promise<void>;
71
+ export {};
72
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH;;GAEG;AACH,oBAAY,QAAQ;IAClB,KAAK,IAAI;IACT,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;CACV;AAYD;;GAEG;AACH,UAAU,SAAS;IACjB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;CAC7B;AA8HD;;GAEG;AACH,eAAO,MAAM,MAAM;IACjB;;OAEG;qBACc,OAAO,EAAE,KAAG,IAAI;IAIjC;;OAEG;oBACa,OAAO,EAAE,KAAG,IAAI;IAIhC;;OAEG;oBACa,OAAO,EAAE,KAAG,IAAI;IAIhC;;OAEG;qBACc,OAAO,EAAE,KAAG,IAAI;CAGlC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,QAAO,SAE/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,QAAO,MAE7B,CAAC;AAEF;;GAEG;AACH,UAAU,gBAAgB;IACxB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;CACnB;AAqDD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GACtB,eAAe,OAAO,CAAC,gBAAgB,CAAC,KACvC,OAAO,CAAC,IAAI,CAiBd,CAAC"}
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Logger utility module
3
+ * Supports filtering by log level and keywords
4
+ * Logs are written to files instead of terminal to avoid being cleared by Ink app
5
+ *
6
+ * Performance optimization:
7
+ * - In test environment, you can disable all file and console I/O operations by setting environment variable DISABLE_LOGGER_IO=true
8
+ * - This can significantly improve test execution performance by avoiding unnecessary disk writes
9
+ */
10
+ import * as fs from "fs";
11
+ import { LOG_FILE, DATA_DIRECTORY } from "./constants.js";
12
+ const logFile = process.env.LOG_FILE || LOG_FILE;
13
+ /**
14
+ * Log level enumeration
15
+ */
16
+ export var LogLevel;
17
+ (function (LogLevel) {
18
+ LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
19
+ LogLevel[LogLevel["INFO"] = 1] = "INFO";
20
+ LogLevel[LogLevel["WARN"] = 2] = "WARN";
21
+ LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
22
+ })(LogLevel || (LogLevel = {}));
23
+ /**
24
+ * Log level name mapping
25
+ */
26
+ const LOG_LEVEL_NAMES = {
27
+ [LogLevel.DEBUG]: "DEBUG",
28
+ [LogLevel.INFO]: "INFO",
29
+ [LogLevel.WARN]: "WARN",
30
+ [LogLevel.ERROR]: "ERROR",
31
+ };
32
+ /**
33
+ * Parse log level from environment variable
34
+ */
35
+ const parseLogLevel = (levelStr) => {
36
+ if (!levelStr)
37
+ return LogLevel.INFO;
38
+ const upperLevel = levelStr.toUpperCase();
39
+ switch (upperLevel) {
40
+ case "DEBUG":
41
+ return LogLevel.DEBUG;
42
+ case "INFO":
43
+ return LogLevel.INFO;
44
+ case "WARN":
45
+ return LogLevel.WARN;
46
+ case "ERROR":
47
+ return LogLevel.ERROR;
48
+ default:
49
+ return LogLevel.INFO;
50
+ }
51
+ };
52
+ /**
53
+ * Parse keyword filter from environment variable
54
+ */
55
+ const parseKeywords = (keywordsStr) => {
56
+ if (!keywordsStr)
57
+ return [];
58
+ return keywordsStr
59
+ .split(",")
60
+ .map((k) => k.trim().toLowerCase())
61
+ .filter((k) => k.length > 0);
62
+ };
63
+ /**
64
+ * Load log configuration from environment variables
65
+ *
66
+ * Supported environment variables:
67
+ * - LOG_LEVEL: Log level (DEBUG, INFO, WARN, ERROR), default INFO
68
+ * - LOG_KEYWORDS: Keyword filter, comma-separated, default no filter
69
+ */
70
+ const logConfig = {
71
+ level: parseLogLevel(process.env.LOG_LEVEL),
72
+ keywords: parseKeywords(process.env.LOG_KEYWORDS),
73
+ };
74
+ /**
75
+ * Check if log should be recorded
76
+ */
77
+ const shouldLog = (level, message) => {
78
+ // Check log level
79
+ if (level < logConfig.level) {
80
+ return false;
81
+ }
82
+ // If no keyword filter is set, record all logs that meet the level requirement
83
+ if (logConfig.keywords.length === 0) {
84
+ return true;
85
+ }
86
+ // Check keyword filter
87
+ const lowerMessage = message.toLowerCase();
88
+ return logConfig.keywords.some((keyword) => lowerMessage.includes(keyword));
89
+ };
90
+ /**
91
+ * Format log arguments
92
+ */
93
+ const formatArg = (arg) => {
94
+ if (arg === null)
95
+ return "null";
96
+ if (arg === undefined)
97
+ return "undefined";
98
+ if (arg instanceof Error) {
99
+ // Special handling for Error objects, display stack or message
100
+ return arg.stack || arg.message || String(arg);
101
+ }
102
+ if (typeof arg === "object") {
103
+ try {
104
+ return JSON.stringify(arg, null, 2);
105
+ }
106
+ catch {
107
+ // If JSON.stringify fails, fallback to String()
108
+ return String(arg);
109
+ }
110
+ }
111
+ return String(arg);
112
+ };
113
+ /**
114
+ * Generic log output function
115
+ */
116
+ const logMessage = (level, ...args) => {
117
+ const messageText = args.map(formatArg).join(" ");
118
+ // Check if this log should be recorded
119
+ if (!shouldLog(level, messageText)) {
120
+ return;
121
+ }
122
+ // If logger I/O operations are disabled, return directly to save performance
123
+ if (process.env.DISABLE_LOGGER_IO === "true") {
124
+ return;
125
+ }
126
+ const levelName = LOG_LEVEL_NAMES[level];
127
+ const timestamp = new Date().toISOString();
128
+ const formattedMessage = `[${timestamp}] [${levelName}] ${messageText}\n`;
129
+ try {
130
+ // Ensure directory exists
131
+ if (!fs.existsSync(DATA_DIRECTORY)) {
132
+ fs.mkdirSync(DATA_DIRECTORY, { recursive: true });
133
+ }
134
+ // Write log to file
135
+ fs.appendFileSync(logFile, formattedMessage);
136
+ }
137
+ catch (error) {
138
+ // If file write fails, fallback to stderr
139
+ process.stderr.write(`[${levelName}] Failed to write to log file: ${error}\n`);
140
+ process.stderr.write(formattedMessage);
141
+ }
142
+ };
143
+ /**
144
+ * Logger object
145
+ */
146
+ export const logger = {
147
+ /**
148
+ * Debug log output function
149
+ */
150
+ debug: (...args) => {
151
+ logMessage(LogLevel.DEBUG, ...args);
152
+ },
153
+ /**
154
+ * Info log output function
155
+ */
156
+ info: (...args) => {
157
+ logMessage(LogLevel.INFO, ...args);
158
+ },
159
+ /**
160
+ * Warning log output function
161
+ */
162
+ warn: (...args) => {
163
+ logMessage(LogLevel.WARN, ...args);
164
+ },
165
+ /**
166
+ * Error log output function
167
+ */
168
+ error: (...args) => {
169
+ logMessage(LogLevel.ERROR, ...args);
170
+ },
171
+ };
172
+ /**
173
+ * Get current log configuration
174
+ */
175
+ export const getLogConfig = () => {
176
+ return logConfig;
177
+ };
178
+ /**
179
+ * Get log file path
180
+ */
181
+ export const getLogFile = () => {
182
+ return logFile;
183
+ };
184
+ /**
185
+ * Get log cleanup configuration
186
+ * Can override default configuration with environment variables
187
+ */
188
+ const getCleanupConfig = () => {
189
+ return {
190
+ maxFileSize: parseInt(process.env.LOG_MAX_FILE_SIZE || "10485760", 10), // 10MB
191
+ keepLines: parseInt(process.env.LOG_KEEP_LINES || "1000", 10),
192
+ };
193
+ };
194
+ /**
195
+ * Truncate current log file if too large
196
+ * Keep the last specified number of lines
197
+ */
198
+ const truncateLogFileIfNeeded = (config) => {
199
+ // If logger I/O operations are disabled, return directly to save performance
200
+ if (process.env.DISABLE_LOGGER_IO === "true") {
201
+ return;
202
+ }
203
+ try {
204
+ if (!fs.existsSync(logFile)) {
205
+ return;
206
+ }
207
+ const stats = fs.statSync(logFile);
208
+ // If file size exceeds limit, truncate file
209
+ if (stats.size > config.maxFileSize) {
210
+ const content = fs.readFileSync(logFile, "utf8");
211
+ const lines = content.split("\n");
212
+ // Keep the last specified number of lines
213
+ const keepLines = Math.min(config.keepLines, lines.length);
214
+ const truncatedContent = lines.slice(-keepLines).join("\n");
215
+ // Write truncated content
216
+ fs.writeFileSync(logFile, truncatedContent);
217
+ // Record truncation operation
218
+ const removedLines = lines.length - keepLines;
219
+ logger.info(`Log file truncated: removed ${removedLines} lines, kept last ${keepLines} lines`);
220
+ }
221
+ }
222
+ catch (error) {
223
+ logger.warn("Failed to truncate log file:", error);
224
+ }
225
+ };
226
+ /**
227
+ * Execute log cleanup
228
+ * Truncate current log file (if needed)
229
+ *
230
+ * @param customConfig Custom cleanup configuration, if not provided uses default configuration
231
+ */
232
+ export const cleanupLogs = async (customConfig) => {
233
+ // If logger I/O operations are disabled, return directly to save performance
234
+ if (process.env.DISABLE_LOGGER_IO === "true") {
235
+ return;
236
+ }
237
+ const config = { ...getCleanupConfig(), ...customConfig };
238
+ logger.info("Starting log cleanup...", {
239
+ maxFileSize: config.maxFileSize,
240
+ keepLines: config.keepLines,
241
+ });
242
+ // Truncate current log file (if needed)
243
+ truncateLogFileIfNeeded(config);
244
+ logger.info("Log cleanup completed");
245
+ };