agentgui 1.0.274 → 1.0.275

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 (69) hide show
  1. package/CLAUDE.md +280 -280
  2. package/IPFS_DOWNLOADER.md +277 -277
  3. package/TASK_2C_COMPLETION.md +334 -334
  4. package/bin/gmgui.cjs +54 -54
  5. package/build-portable.js +3 -42
  6. package/database.js +1422 -1406
  7. package/lib/claude-runner.js +1130 -1130
  8. package/lib/ipfs-downloader.js +459 -459
  9. package/lib/speech.js +152 -152
  10. package/package.json +1 -1
  11. package/readme.md +76 -76
  12. package/server.js +3787 -3794
  13. package/setup-npm-token.sh +68 -68
  14. package/static/app.js +773 -773
  15. package/static/event-rendering-showcase.html +708 -708
  16. package/static/index.html +3178 -3180
  17. package/static/js/agent-auth.js +298 -298
  18. package/static/js/audio-recorder-processor.js +18 -18
  19. package/static/js/client.js +2656 -2656
  20. package/static/js/conversations.js +583 -583
  21. package/static/js/dialogs.js +267 -267
  22. package/static/js/event-consolidator.js +101 -101
  23. package/static/js/event-filter.js +311 -311
  24. package/static/js/event-processor.js +452 -452
  25. package/static/js/features.js +413 -413
  26. package/static/js/kalman-filter.js +67 -67
  27. package/static/js/progress-dialog.js +130 -130
  28. package/static/js/script-runner.js +219 -219
  29. package/static/js/streaming-renderer.js +2123 -2120
  30. package/static/js/syntax-highlighter.js +269 -269
  31. package/static/js/tts-websocket-handler.js +152 -152
  32. package/static/js/ui-components.js +431 -431
  33. package/static/js/voice.js +849 -849
  34. package/static/js/websocket-manager.js +596 -596
  35. package/static/templates/INDEX.html +465 -465
  36. package/static/templates/README.md +190 -190
  37. package/static/templates/agent-capabilities.html +56 -56
  38. package/static/templates/agent-metadata-panel.html +44 -44
  39. package/static/templates/agent-status-badge.html +30 -30
  40. package/static/templates/code-annotation-panel.html +155 -155
  41. package/static/templates/code-suggestion-panel.html +184 -184
  42. package/static/templates/command-header.html +77 -77
  43. package/static/templates/command-output-scrollable.html +118 -118
  44. package/static/templates/elapsed-time.html +54 -54
  45. package/static/templates/error-alert.html +106 -106
  46. package/static/templates/error-history-timeline.html +160 -160
  47. package/static/templates/error-recovery-options.html +109 -109
  48. package/static/templates/error-stack-trace.html +95 -95
  49. package/static/templates/error-summary.html +80 -80
  50. package/static/templates/event-counter.html +48 -48
  51. package/static/templates/execution-actions.html +97 -97
  52. package/static/templates/execution-progress-bar.html +80 -80
  53. package/static/templates/execution-stepper.html +120 -120
  54. package/static/templates/file-breadcrumb.html +118 -118
  55. package/static/templates/file-diff-viewer.html +121 -121
  56. package/static/templates/file-metadata.html +133 -133
  57. package/static/templates/file-read-panel.html +66 -66
  58. package/static/templates/file-write-panel.html +120 -120
  59. package/static/templates/git-branch-remote.html +107 -107
  60. package/static/templates/git-diff-list.html +101 -101
  61. package/static/templates/git-log-visualization.html +153 -153
  62. package/static/templates/git-status-panel.html +115 -115
  63. package/static/templates/quality-metrics-display.html +170 -170
  64. package/static/templates/terminal-output-panel.html +87 -87
  65. package/static/templates/test-results-display.html +144 -144
  66. package/static/theme.js +72 -72
  67. package/test-download-progress.js +223 -223
  68. package/test-websocket-broadcast.js +147 -147
  69. package/tests/ipfs-downloader.test.js +370 -370
@@ -1,452 +1,452 @@
1
- /**
2
- * Event Processor
3
- * Transforms, validates, and enriches streaming events
4
- * Handles ANSI colors, markdown, diffs, and other data transformations
5
- */
6
-
7
- class EventProcessor {
8
- constructor(config = {}) {
9
- this.config = {
10
- enableSyntaxHighlight: config.enableSyntaxHighlight !== false,
11
- enableMarkdown: config.enableMarkdown !== false,
12
- enableANSI: config.enableANSI !== false,
13
- maxContentLength: config.maxContentLength || 100000,
14
- ...config
15
- };
16
-
17
- // ANSI color codes mapping
18
- this.ansiCodes = {
19
- reset: '\x1b[0m',
20
- bold: '\x1b[1m',
21
- dim: '\x1b[2m',
22
- italic: '\x1b[3m',
23
- underline: '\x1b[4m',
24
- blink: '\x1b[5m',
25
- reverse: '\x1b[7m',
26
- hidden: '\x1b[8m',
27
- strikethrough: '\x1b[9m',
28
- // Foreground colors
29
- black: '\x1b[30m',
30
- red: '\x1b[31m',
31
- green: '\x1b[32m',
32
- yellow: '\x1b[33m',
33
- blue: '\x1b[34m',
34
- magenta: '\x1b[35m',
35
- cyan: '\x1b[36m',
36
- white: '\x1b[37m',
37
- // Background colors
38
- bgBlack: '\x1b[40m',
39
- bgRed: '\x1b[41m',
40
- bgGreen: '\x1b[42m',
41
- bgYellow: '\x1b[43m',
42
- bgBlue: '\x1b[44m',
43
- bgMagenta: '\x1b[45m',
44
- bgCyan: '\x1b[46m',
45
- bgWhite: '\x1b[47m'
46
- };
47
-
48
- // CSS color mapping
49
- this.colorMap = {
50
- '30': '#000000', // black
51
- '31': '#ff6b6b', // red
52
- '32': '#51cf66', // green
53
- '33': '#ffd43b', // yellow
54
- '34': '#4dabf7', // blue
55
- '35': '#da77f2', // magenta
56
- '36': '#20c997', // cyan
57
- '37': '#ffffff', // white
58
- '90': '#666666', // bright black
59
- '91': '#ff8787', // bright red
60
- '92': '#69db7c', // bright green
61
- '93': '#ffe066', // bright yellow
62
- '94': '#74c0fc', // bright blue
63
- '95': '#e599f7', // bright magenta
64
- '96': '#38f9d7', // bright cyan
65
- '97': '#f8f9fa' // bright white
66
- };
67
-
68
- // Statistics
69
- this.stats = {
70
- totalEvents: 0,
71
- processedEvents: 0,
72
- validatedEvents: 0,
73
- transformedEvents: 0,
74
- errorCount: 0,
75
- avgProcessTime: 0
76
- };
77
- }
78
-
79
- /**
80
- * Process and enrich event
81
- */
82
- processEvent(event) {
83
- if (!event || typeof event !== 'object') {
84
- return null;
85
- }
86
-
87
- const startTime = performance.now();
88
- this.stats.totalEvents++;
89
-
90
- try {
91
- // Validate event structure
92
- if (!this.validateEvent(event)) {
93
- this.stats.errorCount++;
94
- return null;
95
- }
96
-
97
- this.stats.validatedEvents++;
98
-
99
- // Add processing metadata
100
- const processed = {
101
- ...event,
102
- processedAt: Date.now(),
103
- processTime: 0
104
- };
105
-
106
- // Transform event based on type
107
- if (event.type === 'text_block' || event.type === 'code_block') {
108
- processed.content = this.transformContent(event.content || '', event.type);
109
- this.stats.transformedEvents++;
110
- }
111
-
112
- if (event.type === 'command_execute' && event.output) {
113
- processed.output = this.transformANSI(event.output);
114
- this.stats.transformedEvents++;
115
- }
116
-
117
- if (event.type === 'file_diff' || event.type === 'git_diff') {
118
- processed.diff = this.transformDiff(event.diff || event.content || '');
119
- this.stats.transformedEvents++;
120
- }
121
-
122
- processed.processTime = performance.now() - startTime;
123
- this.stats.processedEvents++;
124
-
125
- // Update average processing time
126
- this.stats.avgProcessTime = (this.stats.avgProcessTime * (this.stats.processedEvents - 1) + processed.processTime) / this.stats.processedEvents;
127
-
128
- return processed;
129
- } catch (error) {
130
- console.error('Event processing error:', error);
131
- this.stats.errorCount++;
132
- return null;
133
- }
134
- }
135
-
136
- /**
137
- * Validate event structure
138
- */
139
- validateEvent(event) {
140
- if (!event.type) {
141
- return false;
142
- }
143
-
144
- // Required fields by type
145
- const typeRequirements = {
146
- streaming_start: ['sessionId', 'conversationId'],
147
- streaming_complete: ['sessionId'],
148
- file_read: ['path'],
149
- file_write: ['path'],
150
- command_execute: ['command'],
151
- git_status: [],
152
- error: ['message']
153
- };
154
-
155
- const requirements = typeRequirements[event.type];
156
- if (requirements) {
157
- for (const field of requirements) {
158
- if (!event[field]) {
159
- return false;
160
- }
161
- }
162
- }
163
-
164
- return true;
165
- }
166
-
167
- /**
168
- * Transform content based on type
169
- */
170
- transformContent(content, type) {
171
- if (typeof content !== 'string') {
172
- return content;
173
- }
174
-
175
- if (content.length > this.config.maxContentLength) {
176
- return content.substring(0, this.config.maxContentLength) + '\n... (truncated)';
177
- }
178
-
179
- return content;
180
- }
181
-
182
- /**
183
- * Transform ANSI escape codes to HTML/CSS
184
- */
185
- transformANSI(text) {
186
- if (!this.config.enableANSI || typeof text !== 'string') {
187
- return text;
188
- }
189
-
190
- let result = '';
191
- let currentStyle = { fg: null, bg: null, bold: false };
192
- const stack = [];
193
-
194
- // Pattern for ANSI escape sequences
195
- const pattern = /\x1b\[([0-9;]*?)m/g;
196
- let lastIndex = 0;
197
- let match;
198
-
199
- while ((match = pattern.exec(text)) !== null) {
200
- // Add text before this escape sequence
201
- if (match.index > lastIndex) {
202
- const plainText = text.substring(lastIndex, match.index);
203
- result += this.escapeHtml(plainText);
204
- }
205
-
206
- // Parse ANSI code
207
- const codes = match[1].split(';').map(c => parseInt(c, 10));
208
- for (const code of codes) {
209
- if (code === 0) {
210
- // Reset
211
- currentStyle = { fg: null, bg: null, bold: false };
212
- } else if (code === 1) {
213
- currentStyle.bold = true;
214
- } else if (code >= 30 && code <= 37) {
215
- currentStyle.fg = this.colorMap[code];
216
- } else if (code >= 40 && code <= 47) {
217
- currentStyle.bg = this.colorMap[String(code - 10)];
218
- } else if (code >= 90 && code <= 97) {
219
- currentStyle.fg = this.colorMap[code];
220
- }
221
- }
222
-
223
- lastIndex = pattern.lastIndex;
224
- }
225
-
226
- // Add remaining text
227
- if (lastIndex < text.length) {
228
- result += this.escapeHtml(text.substring(lastIndex));
229
- }
230
-
231
- return result;
232
- }
233
-
234
- /**
235
- * Transform unified diff format
236
- */
237
- transformDiff(diffText) {
238
- if (typeof diffText !== 'string') {
239
- return diffText;
240
- }
241
-
242
- const lines = diffText.split('\n');
243
- const parsed = {
244
- headers: [],
245
- hunks: []
246
- };
247
-
248
- let currentHunk = null;
249
-
250
- for (const line of lines) {
251
- if (line.startsWith('---') || line.startsWith('+++')) {
252
- parsed.headers.push(line);
253
- } else if (line.startsWith('@@')) {
254
- if (currentHunk) {
255
- parsed.hunks.push(currentHunk);
256
- }
257
- currentHunk = {
258
- header: line,
259
- changes: []
260
- };
261
- } else if (currentHunk) {
262
- if (line.startsWith('-')) {
263
- currentHunk.changes.push({ type: 'deleted', line: line.substring(1) });
264
- } else if (line.startsWith('+')) {
265
- currentHunk.changes.push({ type: 'added', line: line.substring(1) });
266
- } else if (line.startsWith(' ')) {
267
- currentHunk.changes.push({ type: 'context', line: line.substring(1) });
268
- }
269
- }
270
- }
271
-
272
- if (currentHunk) {
273
- parsed.hunks.push(currentHunk);
274
- }
275
-
276
- return parsed;
277
- }
278
-
279
- /**
280
- * Convert markdown to HTML (simple implementation)
281
- */
282
- transformMarkdown(markdown) {
283
- if (!this.config.enableMarkdown || typeof markdown !== 'string') {
284
- return markdown;
285
- }
286
-
287
- let html = this.escapeHtml(markdown);
288
-
289
- // Bold
290
- html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
291
-
292
- // Italic
293
- html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
294
-
295
- // Code
296
- html = html.replace(/`(.+?)`/g, '<code>$1</code>');
297
-
298
- // Links
299
- html = html.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank">$1</a>');
300
-
301
- // Headings
302
- html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
303
- html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
304
- html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
305
-
306
- // Line breaks
307
- html = html.replace(/\n/g, '<br>');
308
-
309
- return html;
310
- }
311
-
312
- /**
313
- * Detect language from content or hint
314
- */
315
- detectLanguage(content, hint = null) {
316
- if (hint) {
317
- return hint.toLowerCase();
318
- }
319
-
320
- // Simple language detection based on shebang or content patterns
321
- if (content.startsWith('#!/')) {
322
- if (content.includes('python')) return 'python';
323
- if (content.includes('node') || content.includes('javascript')) return 'javascript';
324
- if (content.includes('bash') || content.includes('sh')) return 'bash';
325
- if (content.includes('ruby')) return 'ruby';
326
- }
327
-
328
- // Pattern detection
329
- if (content.includes('def ') && content.includes(':')) return 'python';
330
- if (content.includes('function') || content.includes('=>')) return 'javascript';
331
- if (content.includes('fn ') && content.includes('->')) return 'rust';
332
- if (content.includes('public static void') || content.includes('class ')) return 'java';
333
-
334
- return 'plaintext';
335
- }
336
-
337
- /**
338
- * Parse JSON safely
339
- */
340
- parseJSON(jsonStr) {
341
- try {
342
- return JSON.parse(jsonStr);
343
- } catch (e) {
344
- console.error('JSON parse error:', e);
345
- return null;
346
- }
347
- }
348
-
349
- /**
350
- * Format JSON for display
351
- */
352
- formatJSON(obj, indent = 2) {
353
- try {
354
- return JSON.stringify(obj, null, indent);
355
- } catch (e) {
356
- return String(obj);
357
- }
358
- }
359
-
360
- /**
361
- * Extract file extension
362
- */
363
- getFileExtension(filePath) {
364
- const match = filePath.match(/\.([^.]+)$/);
365
- return match ? match[1].toLowerCase() : null;
366
- }
367
-
368
- /**
369
- * Determine syntax highlighter language from file extension
370
- */
371
- getLanguageFromExtension(ext) {
372
- const extMap = {
373
- 'js': 'javascript',
374
- 'jsx': 'jsx',
375
- 'ts': 'typescript',
376
- 'tsx': 'typescript',
377
- 'py': 'python',
378
- 'java': 'java',
379
- 'cpp': 'cpp',
380
- 'c': 'c',
381
- 'cs': 'csharp',
382
- 'php': 'php',
383
- 'rb': 'ruby',
384
- 'go': 'go',
385
- 'rs': 'rust',
386
- 'json': 'json',
387
- 'xml': 'xml',
388
- 'html': 'html',
389
- 'css': 'css',
390
- 'scss': 'scss',
391
- 'yaml': 'yaml',
392
- 'yml': 'yaml',
393
- 'sql': 'sql',
394
- 'sh': 'bash',
395
- 'bash': 'bash',
396
- 'zsh': 'bash'
397
- };
398
-
399
- return extMap[ext?.toLowerCase()] || 'plaintext';
400
- }
401
-
402
- /**
403
- * Truncate text with ellipsis
404
- */
405
- truncateText(text, maxLength = 200) {
406
- if (text.length <= maxLength) {
407
- return text;
408
- }
409
- return text.substring(0, maxLength) + '...';
410
- }
411
-
412
- /**
413
- * HTML escape utility
414
- */
415
- escapeHtml(text) {
416
- return window._escHtml(text);
417
- }
418
-
419
- /**
420
- * Format timestamp
421
- */
422
- formatTimestamp(timestamp) {
423
- const date = new Date(timestamp);
424
- return date.toLocaleTimeString();
425
- }
426
-
427
- /**
428
- * Get statistics
429
- */
430
- getStats() {
431
- return { ...this.stats };
432
- }
433
-
434
- /**
435
- * Reset statistics
436
- */
437
- resetStats() {
438
- this.stats = {
439
- totalEvents: 0,
440
- processedEvents: 0,
441
- validatedEvents: 0,
442
- transformedEvents: 0,
443
- errorCount: 0,
444
- avgProcessTime: 0
445
- };
446
- }
447
- }
448
-
449
- // Export for use in browser
450
- if (typeof module !== 'undefined' && module.exports) {
451
- module.exports = EventProcessor;
452
- }
1
+ /**
2
+ * Event Processor
3
+ * Transforms, validates, and enriches streaming events
4
+ * Handles ANSI colors, markdown, diffs, and other data transformations
5
+ */
6
+
7
+ class EventProcessor {
8
+ constructor(config = {}) {
9
+ this.config = {
10
+ enableSyntaxHighlight: config.enableSyntaxHighlight !== false,
11
+ enableMarkdown: config.enableMarkdown !== false,
12
+ enableANSI: config.enableANSI !== false,
13
+ maxContentLength: config.maxContentLength || 100000,
14
+ ...config
15
+ };
16
+
17
+ // ANSI color codes mapping
18
+ this.ansiCodes = {
19
+ reset: '\x1b[0m',
20
+ bold: '\x1b[1m',
21
+ dim: '\x1b[2m',
22
+ italic: '\x1b[3m',
23
+ underline: '\x1b[4m',
24
+ blink: '\x1b[5m',
25
+ reverse: '\x1b[7m',
26
+ hidden: '\x1b[8m',
27
+ strikethrough: '\x1b[9m',
28
+ // Foreground colors
29
+ black: '\x1b[30m',
30
+ red: '\x1b[31m',
31
+ green: '\x1b[32m',
32
+ yellow: '\x1b[33m',
33
+ blue: '\x1b[34m',
34
+ magenta: '\x1b[35m',
35
+ cyan: '\x1b[36m',
36
+ white: '\x1b[37m',
37
+ // Background colors
38
+ bgBlack: '\x1b[40m',
39
+ bgRed: '\x1b[41m',
40
+ bgGreen: '\x1b[42m',
41
+ bgYellow: '\x1b[43m',
42
+ bgBlue: '\x1b[44m',
43
+ bgMagenta: '\x1b[45m',
44
+ bgCyan: '\x1b[46m',
45
+ bgWhite: '\x1b[47m'
46
+ };
47
+
48
+ // CSS color mapping
49
+ this.colorMap = {
50
+ '30': '#000000', // black
51
+ '31': '#ff6b6b', // red
52
+ '32': '#51cf66', // green
53
+ '33': '#ffd43b', // yellow
54
+ '34': '#4dabf7', // blue
55
+ '35': '#da77f2', // magenta
56
+ '36': '#20c997', // cyan
57
+ '37': '#ffffff', // white
58
+ '90': '#666666', // bright black
59
+ '91': '#ff8787', // bright red
60
+ '92': '#69db7c', // bright green
61
+ '93': '#ffe066', // bright yellow
62
+ '94': '#74c0fc', // bright blue
63
+ '95': '#e599f7', // bright magenta
64
+ '96': '#38f9d7', // bright cyan
65
+ '97': '#f8f9fa' // bright white
66
+ };
67
+
68
+ // Statistics
69
+ this.stats = {
70
+ totalEvents: 0,
71
+ processedEvents: 0,
72
+ validatedEvents: 0,
73
+ transformedEvents: 0,
74
+ errorCount: 0,
75
+ avgProcessTime: 0
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Process and enrich event
81
+ */
82
+ processEvent(event) {
83
+ if (!event || typeof event !== 'object') {
84
+ return null;
85
+ }
86
+
87
+ const startTime = performance.now();
88
+ this.stats.totalEvents++;
89
+
90
+ try {
91
+ // Validate event structure
92
+ if (!this.validateEvent(event)) {
93
+ this.stats.errorCount++;
94
+ return null;
95
+ }
96
+
97
+ this.stats.validatedEvents++;
98
+
99
+ // Add processing metadata
100
+ const processed = {
101
+ ...event,
102
+ processedAt: Date.now(),
103
+ processTime: 0
104
+ };
105
+
106
+ // Transform event based on type
107
+ if (event.type === 'text_block' || event.type === 'code_block') {
108
+ processed.content = this.transformContent(event.content || '', event.type);
109
+ this.stats.transformedEvents++;
110
+ }
111
+
112
+ if (event.type === 'command_execute' && event.output) {
113
+ processed.output = this.transformANSI(event.output);
114
+ this.stats.transformedEvents++;
115
+ }
116
+
117
+ if (event.type === 'file_diff' || event.type === 'git_diff') {
118
+ processed.diff = this.transformDiff(event.diff || event.content || '');
119
+ this.stats.transformedEvents++;
120
+ }
121
+
122
+ processed.processTime = performance.now() - startTime;
123
+ this.stats.processedEvents++;
124
+
125
+ // Update average processing time
126
+ this.stats.avgProcessTime = (this.stats.avgProcessTime * (this.stats.processedEvents - 1) + processed.processTime) / this.stats.processedEvents;
127
+
128
+ return processed;
129
+ } catch (error) {
130
+ console.error('Event processing error:', error);
131
+ this.stats.errorCount++;
132
+ return null;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Validate event structure
138
+ */
139
+ validateEvent(event) {
140
+ if (!event.type) {
141
+ return false;
142
+ }
143
+
144
+ // Required fields by type
145
+ const typeRequirements = {
146
+ streaming_start: ['sessionId', 'conversationId'],
147
+ streaming_complete: ['sessionId'],
148
+ file_read: ['path'],
149
+ file_write: ['path'],
150
+ command_execute: ['command'],
151
+ git_status: [],
152
+ error: ['message']
153
+ };
154
+
155
+ const requirements = typeRequirements[event.type];
156
+ if (requirements) {
157
+ for (const field of requirements) {
158
+ if (!event[field]) {
159
+ return false;
160
+ }
161
+ }
162
+ }
163
+
164
+ return true;
165
+ }
166
+
167
+ /**
168
+ * Transform content based on type
169
+ */
170
+ transformContent(content, type) {
171
+ if (typeof content !== 'string') {
172
+ return content;
173
+ }
174
+
175
+ if (content.length > this.config.maxContentLength) {
176
+ return content.substring(0, this.config.maxContentLength) + '\n... (truncated)';
177
+ }
178
+
179
+ return content;
180
+ }
181
+
182
+ /**
183
+ * Transform ANSI escape codes to HTML/CSS
184
+ */
185
+ transformANSI(text) {
186
+ if (!this.config.enableANSI || typeof text !== 'string') {
187
+ return text;
188
+ }
189
+
190
+ let result = '';
191
+ let currentStyle = { fg: null, bg: null, bold: false };
192
+ const stack = [];
193
+
194
+ // Pattern for ANSI escape sequences
195
+ const pattern = /\x1b\[([0-9;]*?)m/g;
196
+ let lastIndex = 0;
197
+ let match;
198
+
199
+ while ((match = pattern.exec(text)) !== null) {
200
+ // Add text before this escape sequence
201
+ if (match.index > lastIndex) {
202
+ const plainText = text.substring(lastIndex, match.index);
203
+ result += this.escapeHtml(plainText);
204
+ }
205
+
206
+ // Parse ANSI code
207
+ const codes = match[1].split(';').map(c => parseInt(c, 10));
208
+ for (const code of codes) {
209
+ if (code === 0) {
210
+ // Reset
211
+ currentStyle = { fg: null, bg: null, bold: false };
212
+ } else if (code === 1) {
213
+ currentStyle.bold = true;
214
+ } else if (code >= 30 && code <= 37) {
215
+ currentStyle.fg = this.colorMap[code];
216
+ } else if (code >= 40 && code <= 47) {
217
+ currentStyle.bg = this.colorMap[String(code - 10)];
218
+ } else if (code >= 90 && code <= 97) {
219
+ currentStyle.fg = this.colorMap[code];
220
+ }
221
+ }
222
+
223
+ lastIndex = pattern.lastIndex;
224
+ }
225
+
226
+ // Add remaining text
227
+ if (lastIndex < text.length) {
228
+ result += this.escapeHtml(text.substring(lastIndex));
229
+ }
230
+
231
+ return result;
232
+ }
233
+
234
+ /**
235
+ * Transform unified diff format
236
+ */
237
+ transformDiff(diffText) {
238
+ if (typeof diffText !== 'string') {
239
+ return diffText;
240
+ }
241
+
242
+ const lines = diffText.split('\n');
243
+ const parsed = {
244
+ headers: [],
245
+ hunks: []
246
+ };
247
+
248
+ let currentHunk = null;
249
+
250
+ for (const line of lines) {
251
+ if (line.startsWith('---') || line.startsWith('+++')) {
252
+ parsed.headers.push(line);
253
+ } else if (line.startsWith('@@')) {
254
+ if (currentHunk) {
255
+ parsed.hunks.push(currentHunk);
256
+ }
257
+ currentHunk = {
258
+ header: line,
259
+ changes: []
260
+ };
261
+ } else if (currentHunk) {
262
+ if (line.startsWith('-')) {
263
+ currentHunk.changes.push({ type: 'deleted', line: line.substring(1) });
264
+ } else if (line.startsWith('+')) {
265
+ currentHunk.changes.push({ type: 'added', line: line.substring(1) });
266
+ } else if (line.startsWith(' ')) {
267
+ currentHunk.changes.push({ type: 'context', line: line.substring(1) });
268
+ }
269
+ }
270
+ }
271
+
272
+ if (currentHunk) {
273
+ parsed.hunks.push(currentHunk);
274
+ }
275
+
276
+ return parsed;
277
+ }
278
+
279
+ /**
280
+ * Convert markdown to HTML (simple implementation)
281
+ */
282
+ transformMarkdown(markdown) {
283
+ if (!this.config.enableMarkdown || typeof markdown !== 'string') {
284
+ return markdown;
285
+ }
286
+
287
+ let html = this.escapeHtml(markdown);
288
+
289
+ // Bold
290
+ html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
291
+
292
+ // Italic
293
+ html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
294
+
295
+ // Code
296
+ html = html.replace(/`(.+?)`/g, '<code>$1</code>');
297
+
298
+ // Links
299
+ html = html.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank">$1</a>');
300
+
301
+ // Headings
302
+ html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
303
+ html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
304
+ html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
305
+
306
+ // Line breaks
307
+ html = html.replace(/\n/g, '<br>');
308
+
309
+ return html;
310
+ }
311
+
312
+ /**
313
+ * Detect language from content or hint
314
+ */
315
+ detectLanguage(content, hint = null) {
316
+ if (hint) {
317
+ return hint.toLowerCase();
318
+ }
319
+
320
+ // Simple language detection based on shebang or content patterns
321
+ if (content.startsWith('#!/')) {
322
+ if (content.includes('python')) return 'python';
323
+ if (content.includes('node') || content.includes('javascript')) return 'javascript';
324
+ if (content.includes('bash') || content.includes('sh')) return 'bash';
325
+ if (content.includes('ruby')) return 'ruby';
326
+ }
327
+
328
+ // Pattern detection
329
+ if (content.includes('def ') && content.includes(':')) return 'python';
330
+ if (content.includes('function') || content.includes('=>')) return 'javascript';
331
+ if (content.includes('fn ') && content.includes('->')) return 'rust';
332
+ if (content.includes('public static void') || content.includes('class ')) return 'java';
333
+
334
+ return 'plaintext';
335
+ }
336
+
337
+ /**
338
+ * Parse JSON safely
339
+ */
340
+ parseJSON(jsonStr) {
341
+ try {
342
+ return JSON.parse(jsonStr);
343
+ } catch (e) {
344
+ console.error('JSON parse error:', e);
345
+ return null;
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Format JSON for display
351
+ */
352
+ formatJSON(obj, indent = 2) {
353
+ try {
354
+ return JSON.stringify(obj, null, indent);
355
+ } catch (e) {
356
+ return String(obj);
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Extract file extension
362
+ */
363
+ getFileExtension(filePath) {
364
+ const match = filePath.match(/\.([^.]+)$/);
365
+ return match ? match[1].toLowerCase() : null;
366
+ }
367
+
368
+ /**
369
+ * Determine syntax highlighter language from file extension
370
+ */
371
+ getLanguageFromExtension(ext) {
372
+ const extMap = {
373
+ 'js': 'javascript',
374
+ 'jsx': 'jsx',
375
+ 'ts': 'typescript',
376
+ 'tsx': 'typescript',
377
+ 'py': 'python',
378
+ 'java': 'java',
379
+ 'cpp': 'cpp',
380
+ 'c': 'c',
381
+ 'cs': 'csharp',
382
+ 'php': 'php',
383
+ 'rb': 'ruby',
384
+ 'go': 'go',
385
+ 'rs': 'rust',
386
+ 'json': 'json',
387
+ 'xml': 'xml',
388
+ 'html': 'html',
389
+ 'css': 'css',
390
+ 'scss': 'scss',
391
+ 'yaml': 'yaml',
392
+ 'yml': 'yaml',
393
+ 'sql': 'sql',
394
+ 'sh': 'bash',
395
+ 'bash': 'bash',
396
+ 'zsh': 'bash'
397
+ };
398
+
399
+ return extMap[ext?.toLowerCase()] || 'plaintext';
400
+ }
401
+
402
+ /**
403
+ * Truncate text with ellipsis
404
+ */
405
+ truncateText(text, maxLength = 200) {
406
+ if (text.length <= maxLength) {
407
+ return text;
408
+ }
409
+ return text.substring(0, maxLength) + '...';
410
+ }
411
+
412
+ /**
413
+ * HTML escape utility
414
+ */
415
+ escapeHtml(text) {
416
+ return window._escHtml(text);
417
+ }
418
+
419
+ /**
420
+ * Format timestamp
421
+ */
422
+ formatTimestamp(timestamp) {
423
+ const date = new Date(timestamp);
424
+ return date.toLocaleTimeString();
425
+ }
426
+
427
+ /**
428
+ * Get statistics
429
+ */
430
+ getStats() {
431
+ return { ...this.stats };
432
+ }
433
+
434
+ /**
435
+ * Reset statistics
436
+ */
437
+ resetStats() {
438
+ this.stats = {
439
+ totalEvents: 0,
440
+ processedEvents: 0,
441
+ validatedEvents: 0,
442
+ transformedEvents: 0,
443
+ errorCount: 0,
444
+ avgProcessTime: 0
445
+ };
446
+ }
447
+ }
448
+
449
+ // Export for use in browser
450
+ if (typeof module !== 'undefined' && module.exports) {
451
+ module.exports = EventProcessor;
452
+ }