copilot-liku-cli 0.0.1

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 (71) hide show
  1. package/ARCHITECTURE.md +411 -0
  2. package/CONFIGURATION.md +302 -0
  3. package/CONTRIBUTING.md +225 -0
  4. package/ELECTRON_README.md +121 -0
  5. package/INSTALLATION.md +350 -0
  6. package/LICENSE.md +1 -0
  7. package/PROJECT_STATUS.md +229 -0
  8. package/QUICKSTART.md +255 -0
  9. package/README.md +167 -0
  10. package/TESTING.md +274 -0
  11. package/package.json +61 -0
  12. package/scripts/start.js +30 -0
  13. package/src/assets/tray-icon.png +0 -0
  14. package/src/cli/commands/agent.js +327 -0
  15. package/src/cli/commands/click.js +108 -0
  16. package/src/cli/commands/drag.js +85 -0
  17. package/src/cli/commands/find.js +109 -0
  18. package/src/cli/commands/keys.js +132 -0
  19. package/src/cli/commands/mouse.js +79 -0
  20. package/src/cli/commands/repl.js +290 -0
  21. package/src/cli/commands/screenshot.js +72 -0
  22. package/src/cli/commands/scroll.js +74 -0
  23. package/src/cli/commands/start.js +67 -0
  24. package/src/cli/commands/type.js +57 -0
  25. package/src/cli/commands/wait.js +84 -0
  26. package/src/cli/commands/window.js +104 -0
  27. package/src/cli/liku.js +249 -0
  28. package/src/cli/util/output.js +174 -0
  29. package/src/main/agents/base-agent.js +410 -0
  30. package/src/main/agents/builder.js +484 -0
  31. package/src/main/agents/index.js +62 -0
  32. package/src/main/agents/orchestrator.js +362 -0
  33. package/src/main/agents/researcher.js +511 -0
  34. package/src/main/agents/state-manager.js +344 -0
  35. package/src/main/agents/supervisor.js +365 -0
  36. package/src/main/agents/verifier.js +452 -0
  37. package/src/main/ai-service.js +1633 -0
  38. package/src/main/index.js +2208 -0
  39. package/src/main/inspect-service.js +467 -0
  40. package/src/main/system-automation.js +1186 -0
  41. package/src/main/ui-automation/config.js +76 -0
  42. package/src/main/ui-automation/core/helpers.js +41 -0
  43. package/src/main/ui-automation/core/index.js +15 -0
  44. package/src/main/ui-automation/core/powershell.js +82 -0
  45. package/src/main/ui-automation/elements/finder.js +274 -0
  46. package/src/main/ui-automation/elements/index.js +14 -0
  47. package/src/main/ui-automation/elements/wait.js +66 -0
  48. package/src/main/ui-automation/index.js +164 -0
  49. package/src/main/ui-automation/interactions/element-click.js +211 -0
  50. package/src/main/ui-automation/interactions/high-level.js +230 -0
  51. package/src/main/ui-automation/interactions/index.js +47 -0
  52. package/src/main/ui-automation/keyboard/index.js +15 -0
  53. package/src/main/ui-automation/keyboard/input.js +179 -0
  54. package/src/main/ui-automation/mouse/click.js +186 -0
  55. package/src/main/ui-automation/mouse/drag.js +88 -0
  56. package/src/main/ui-automation/mouse/index.js +30 -0
  57. package/src/main/ui-automation/mouse/movement.js +51 -0
  58. package/src/main/ui-automation/mouse/scroll.js +116 -0
  59. package/src/main/ui-automation/screenshot.js +183 -0
  60. package/src/main/ui-automation/window/index.js +23 -0
  61. package/src/main/ui-automation/window/manager.js +305 -0
  62. package/src/main/utils/time.js +62 -0
  63. package/src/main/visual-awareness.js +597 -0
  64. package/src/renderer/chat/chat.js +671 -0
  65. package/src/renderer/chat/index.html +725 -0
  66. package/src/renderer/chat/preload.js +112 -0
  67. package/src/renderer/overlay/index.html +648 -0
  68. package/src/renderer/overlay/overlay.js +782 -0
  69. package/src/renderer/overlay/preload.js +90 -0
  70. package/src/shared/grid-math.js +82 -0
  71. package/src/shared/inspect-types.js +230 -0
@@ -0,0 +1,597 @@
1
+ /**
2
+ * Visual Awareness Module
3
+ * Advanced screen analysis, OCR, element detection, and active window tracking
4
+ */
5
+
6
+ const { exec } = require('child_process');
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const os = require('os');
10
+
11
+ // ===== STATE =====
12
+ let previousScreenshot = null;
13
+ let screenDiffHistory = [];
14
+ let activeWindowInfo = null;
15
+ let ocrCache = new Map();
16
+ let elementCache = new Map();
17
+
18
+ const MAX_DIFF_HISTORY = 10;
19
+ const DIFF_THRESHOLD = 0.05; // 5% change threshold
20
+
21
+ // ===== POWERSHELL HELPER =====
22
+ // BLOCKER-2 FIX: Write scripts to temp files instead of inline commands
23
+ // This preserves Here-String syntax which requires newlines
24
+
25
+ /**
26
+ * Execute a PowerShell script by writing to a temp file
27
+ * This fixes the Here-String (@" ... "@) syntax issue
28
+ * @param {string} script - PowerShell script content
29
+ * @param {number} timeout - Execution timeout in ms
30
+ * @returns {Promise<{stdout: string, stderr: string}>}
31
+ */
32
+ function executePowerShellScript(script, timeout = 10000) {
33
+ return new Promise((resolve, reject) => {
34
+ const tempDir = path.join(os.tmpdir(), 'liku-ps');
35
+ if (!fs.existsSync(tempDir)) {
36
+ fs.mkdirSync(tempDir, { recursive: true });
37
+ }
38
+
39
+ const scriptPath = path.join(tempDir, `script-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.ps1`);
40
+
41
+ try {
42
+ fs.writeFileSync(scriptPath, script, 'utf8');
43
+ } catch (err) {
44
+ resolve({ error: `Failed to write temp script: ${err.message}` });
45
+ return;
46
+ }
47
+
48
+ // Execute with -File to avoid quote escaping issues
49
+ exec(`powershell -NoProfile -ExecutionPolicy Bypass -File "${scriptPath}"`,
50
+ { timeout },
51
+ (error, stdout, stderr) => {
52
+ // Clean up temp file
53
+ try { fs.unlinkSync(scriptPath); } catch (e) {}
54
+
55
+ if (error) {
56
+ resolve({ error: error.message, stderr });
57
+ } else {
58
+ resolve({ stdout: stdout.trim(), stderr });
59
+ }
60
+ }
61
+ );
62
+ });
63
+ }
64
+
65
+ // ===== SCREEN DIFFING =====
66
+
67
+ /**
68
+ * Compare two screenshots and detect changes
69
+ * Returns regions that have changed significantly
70
+ */
71
+ function compareScreenshots(current, previous) {
72
+ if (!previous || !current) return null;
73
+
74
+ // Both should be base64 data URLs
75
+ // For actual pixel comparison, we'd use a canvas-based approach
76
+ // Here we provide a simplified version that can be enhanced
77
+
78
+ const currentData = current.dataURL;
79
+ const previousData = previous.dataURL;
80
+
81
+ // Simple comparison: if the base64 differs significantly
82
+ if (currentData === previousData) {
83
+ return { changed: false, changePercent: 0, regions: [] };
84
+ }
85
+
86
+ // Calculate approximate change based on string difference
87
+ // This is a heuristic; real implementation would use pixel comparison
88
+ const lenDiff = Math.abs(currentData.length - previousData.length);
89
+ const avgLen = (currentData.length + previousData.length) / 2;
90
+ const changePercent = lenDiff / avgLen;
91
+
92
+ const changed = changePercent > DIFF_THRESHOLD;
93
+
94
+ return {
95
+ changed,
96
+ changePercent: Math.min(changePercent * 100, 100),
97
+ timestamp: Date.now(),
98
+ regions: changed ? detectChangedRegions(current, previous) : []
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Detect which regions of the screen changed
104
+ * This is a simplified version - real implementation would use image processing
105
+ */
106
+ function detectChangedRegions(current, previous) {
107
+ // Placeholder for region detection
108
+ // In a real implementation, this would:
109
+ // 1. Divide screen into grid
110
+ // 2. Compare each cell
111
+ // 3. Return list of changed regions with coordinates
112
+
113
+ return [{
114
+ x: 0, y: 0,
115
+ width: current.width,
116
+ height: current.height,
117
+ type: 'full-screen-change'
118
+ }];
119
+ }
120
+
121
+ /**
122
+ * Store current screenshot and return diff from previous
123
+ */
124
+ function trackScreenChange(screenshot) {
125
+ const diff = compareScreenshots(screenshot, previousScreenshot);
126
+
127
+ if (diff && diff.changed) {
128
+ screenDiffHistory.push({
129
+ ...diff,
130
+ from: previousScreenshot?.timestamp,
131
+ to: screenshot.timestamp
132
+ });
133
+
134
+ // Trim history
135
+ while (screenDiffHistory.length > MAX_DIFF_HISTORY) {
136
+ screenDiffHistory.shift();
137
+ }
138
+ }
139
+
140
+ previousScreenshot = screenshot;
141
+ return diff;
142
+ }
143
+
144
+ /**
145
+ * Get recent screen changes
146
+ */
147
+ function getScreenDiffHistory() {
148
+ return screenDiffHistory;
149
+ }
150
+
151
+ // ===== ACTIVE WINDOW TRACKING (Windows) =====
152
+
153
+ /**
154
+ * Get information about the currently active window
155
+ * Uses PowerShell on Windows
156
+ */
157
+ async function getActiveWindow() {
158
+ if (process.platform !== 'win32') {
159
+ return { error: 'Active window tracking only supported on Windows currently' };
160
+ }
161
+
162
+ const psScript = `
163
+ Add-Type @"
164
+ using System;
165
+ using System.Runtime.InteropServices;
166
+ using System.Text;
167
+ public class Win32 {
168
+ [DllImport("user32.dll")]
169
+ public static extern IntPtr GetForegroundWindow();
170
+ [DllImport("user32.dll")]
171
+ public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
172
+ [DllImport("user32.dll")]
173
+ public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
174
+ [DllImport("user32.dll")]
175
+ public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
176
+ [StructLayout(LayoutKind.Sequential)]
177
+ public struct RECT { public int Left, Top, Right, Bottom; }
178
+ }
179
+ "@
180
+ $hwnd = [Win32]::GetForegroundWindow()
181
+ $title = New-Object System.Text.StringBuilder 256
182
+ [Win32]::GetWindowText($hwnd, $title, 256) | Out-Null
183
+ $processId = 0
184
+ [Win32]::GetWindowThreadProcessId($hwnd, [ref]$processId) | Out-Null
185
+ $process = Get-Process -Id $processId -ErrorAction SilentlyContinue
186
+ $rect = New-Object Win32+RECT
187
+ [Win32]::GetWindowRect($hwnd, [ref]$rect) | Out-Null
188
+ @{
189
+ Title = $title.ToString()
190
+ ProcessName = $process.ProcessName
191
+ ProcessId = $processId
192
+ Bounds = @{
193
+ X = $rect.Left
194
+ Y = $rect.Top
195
+ Width = $rect.Right - $rect.Left
196
+ Height = $rect.Bottom - $rect.Top
197
+ }
198
+ } | ConvertTo-Json
199
+ `;
200
+
201
+ const result = await executePowerShellScript(psScript, 5000);
202
+
203
+ if (result.error) {
204
+ return { error: result.error };
205
+ }
206
+
207
+ try {
208
+ const info = JSON.parse(result.stdout);
209
+ activeWindowInfo = {
210
+ ...info,
211
+ timestamp: Date.now()
212
+ };
213
+ return activeWindowInfo;
214
+ } catch (e) {
215
+ return { error: 'Failed to parse window info', raw: result.stdout };
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Get cached active window info
221
+ */
222
+ function getCachedActiveWindow() {
223
+ return activeWindowInfo;
224
+ }
225
+
226
+ // ===== OCR INTEGRATION =====
227
+
228
+ /**
229
+ * Extract text from an image using OCR
230
+ * Supports Tesseract (local) or cloud OCR services
231
+ */
232
+ async function extractTextFromImage(imageData, options = {}) {
233
+ const { provider = 'tesseract', language = 'eng' } = options;
234
+
235
+ // Check cache
236
+ const cacheKey = `${imageData.timestamp}-${provider}`;
237
+ if (ocrCache.has(cacheKey)) {
238
+ return ocrCache.get(cacheKey);
239
+ }
240
+
241
+ try {
242
+ let result;
243
+
244
+ switch (provider) {
245
+ case 'tesseract':
246
+ result = await extractWithTesseract(imageData, language);
247
+ break;
248
+ case 'windows-ocr':
249
+ result = await extractWithWindowsOCR(imageData);
250
+ break;
251
+ default:
252
+ result = { error: `Unknown OCR provider: ${provider}` };
253
+ }
254
+
255
+ // Cache result
256
+ ocrCache.set(cacheKey, result);
257
+
258
+ // Limit cache size
259
+ if (ocrCache.size > 50) {
260
+ const firstKey = ocrCache.keys().next().value;
261
+ ocrCache.delete(firstKey);
262
+ }
263
+
264
+ return result;
265
+ } catch (error) {
266
+ return { error: error.message };
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Extract text using Tesseract OCR
272
+ */
273
+ function extractWithTesseract(imageData, language) {
274
+ return new Promise((resolve, reject) => {
275
+ // Save image to temp file
276
+ const tempDir = path.join(os.tmpdir(), 'liku-ocr');
277
+ if (!fs.existsSync(tempDir)) {
278
+ fs.mkdirSync(tempDir, { recursive: true });
279
+ }
280
+
281
+ const tempImagePath = path.join(tempDir, `ocr-${Date.now()}.png`);
282
+ const base64Data = imageData.dataURL.replace(/^data:image\/\w+;base64,/, '');
283
+
284
+ try {
285
+ fs.writeFileSync(tempImagePath, base64Data, 'base64');
286
+ } catch (err) {
287
+ resolve({ error: 'Failed to write temp image: ' + err.message });
288
+ return;
289
+ }
290
+
291
+ // Call Tesseract
292
+ exec(`tesseract "${tempImagePath}" stdout -l ${language}`,
293
+ { timeout: 30000 },
294
+ (error, stdout, stderr) => {
295
+ // Clean up temp file
296
+ try { fs.unlinkSync(tempImagePath); } catch (e) {}
297
+
298
+ if (error) {
299
+ if (error.message.includes('not recognized') || error.message.includes('not found')) {
300
+ resolve({
301
+ error: 'Tesseract not installed. Install from: https://github.com/UB-Mannheim/tesseract/wiki',
302
+ installHint: true
303
+ });
304
+ } else {
305
+ resolve({ error: error.message });
306
+ }
307
+ return;
308
+ }
309
+
310
+ resolve({
311
+ text: stdout.trim(),
312
+ language,
313
+ timestamp: Date.now()
314
+ });
315
+ }
316
+ );
317
+ });
318
+ }
319
+
320
+ /**
321
+ * Extract text using Windows built-in OCR
322
+ */
323
+ async function extractWithWindowsOCR(imageData) {
324
+ if (process.platform !== 'win32') {
325
+ return { error: 'Windows OCR only available on Windows' };
326
+ }
327
+
328
+ // Save image to temp file
329
+ const tempDir = path.join(os.tmpdir(), 'liku-ocr');
330
+ if (!fs.existsSync(tempDir)) {
331
+ fs.mkdirSync(tempDir, { recursive: true });
332
+ }
333
+
334
+ const tempImagePath = path.join(tempDir, `ocr-${Date.now()}.png`);
335
+ const base64Data = imageData.dataURL.replace(/^data:image\/\w+;base64,/, '');
336
+
337
+ try {
338
+ fs.writeFileSync(tempImagePath, base64Data, 'base64');
339
+ } catch (err) {
340
+ return { error: 'Failed to write temp image: ' + err.message };
341
+ }
342
+
343
+ // Use Windows OCR via PowerShell
344
+ const psScript = `
345
+ Add-Type -AssemblyName System.Runtime.WindowsRuntime
346
+ $null = [Windows.Media.Ocr.OcrEngine,Windows.Foundation,ContentType=WindowsRuntime]
347
+ $null = [Windows.Graphics.Imaging.BitmapDecoder,Windows.Foundation,ContentType=WindowsRuntime]
348
+ $null = [Windows.Storage.StorageFile,Windows.Foundation,ContentType=WindowsRuntime]
349
+
350
+ $file = [Windows.Storage.StorageFile]::GetFileFromPathAsync("${tempImagePath.replace(/\\/g, '\\\\')}").GetAwaiter().GetResult()
351
+ $stream = $file.OpenReadAsync().GetAwaiter().GetResult()
352
+ $decoder = [Windows.Graphics.Imaging.BitmapDecoder]::CreateAsync($stream).GetAwaiter().GetResult()
353
+ $bitmap = $decoder.GetSoftwareBitmapAsync().GetAwaiter().GetResult()
354
+
355
+ $engine = [Windows.Media.Ocr.OcrEngine]::TryCreateFromUserProfileLanguages()
356
+ $result = $engine.RecognizeAsync($bitmap).GetAwaiter().GetResult()
357
+ $result.Text
358
+ `;
359
+
360
+ const result = await executePowerShellScript(psScript, 30000);
361
+
362
+ // Clean up temp file
363
+ try { fs.unlinkSync(tempImagePath); } catch (e) {}
364
+
365
+ if (result.error) {
366
+ return { error: 'Windows OCR failed: ' + result.error };
367
+ }
368
+
369
+ return {
370
+ text: result.stdout,
371
+ provider: 'windows-ocr',
372
+ timestamp: Date.now()
373
+ };
374
+ }
375
+
376
+ // ===== UI ELEMENT DETECTION =====
377
+
378
+ /**
379
+ * Detect UI elements from accessibility tree (Windows UI Automation)
380
+ */
381
+ async function detectUIElements(options = {}) {
382
+ if (process.platform !== 'win32') {
383
+ return { error: 'UI Automation only available on Windows' };
384
+ }
385
+
386
+ const { depth = 3 } = options;
387
+
388
+ const psScript = `
389
+ Add-Type -AssemblyName UIAutomationClient
390
+ Add-Type -AssemblyName UIAutomationTypes
391
+
392
+ function Get-UIElements {
393
+ param($element, $depth, $currentDepth = 0)
394
+
395
+ if ($currentDepth -ge $depth) { return @() }
396
+
397
+ $results = @()
398
+ $condition = [System.Windows.Automation.Condition]::TrueCondition
399
+ $children = $element.FindAll([System.Windows.Automation.TreeScope]::Children, $condition)
400
+
401
+ foreach ($child in $children) {
402
+ try {
403
+ $rect = $child.Current.BoundingRectangle
404
+ if ($rect.Width -gt 0 -and $rect.Height -gt 0) {
405
+ $results += @{
406
+ Name = $child.Current.Name
407
+ ControlType = $child.Current.ControlType.ProgrammaticName
408
+ AutomationId = $child.Current.AutomationId
409
+ ClassName = $child.Current.ClassName
410
+ Bounds = @{
411
+ X = [int]$rect.X
412
+ Y = [int]$rect.Y
413
+ Width = [int]$rect.Width
414
+ Height = [int]$rect.Height
415
+ }
416
+ IsEnabled = $child.Current.IsEnabled
417
+ }
418
+ $results += Get-UIElements -element $child -depth $depth -currentDepth ($currentDepth + 1)
419
+ }
420
+ } catch {}
421
+ }
422
+ return $results
423
+ }
424
+
425
+ $root = [System.Windows.Automation.AutomationElement]::FocusedElement
426
+ if ($null -eq $root) {
427
+ $root = [System.Windows.Automation.AutomationElement]::RootElement
428
+ }
429
+
430
+ $elements = Get-UIElements -element $root -depth ${depth}
431
+ $elements | ConvertTo-Json -Depth 10
432
+ `;
433
+
434
+ const result = await executePowerShellScript(psScript, 10000);
435
+
436
+ if (result.error) {
437
+ return { error: 'UI Automation failed: ' + result.error };
438
+ }
439
+
440
+ try {
441
+ let elements = JSON.parse(result.stdout || '[]');
442
+ if (!Array.isArray(elements)) {
443
+ elements = [elements];
444
+ }
445
+
446
+ // Cache results
447
+ elementCache.set(Date.now(), elements);
448
+
449
+ return {
450
+ elements,
451
+ count: elements.length,
452
+ timestamp: Date.now()
453
+ };
454
+ } catch (e) {
455
+ return { elements: [], count: 0, error: 'Parse error' };
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Find UI element at specific coordinates
461
+ */
462
+ async function findElementAtPoint(x, y) {
463
+ if (process.platform !== 'win32') {
464
+ return { error: 'UI Automation only available on Windows' };
465
+ }
466
+
467
+ const psScript = `
468
+ Add-Type -AssemblyName UIAutomationClient
469
+ Add-Type -AssemblyName UIAutomationTypes
470
+
471
+ $point = New-Object System.Windows.Point(${x}, ${y})
472
+ $element = [System.Windows.Automation.AutomationElement]::FromPoint($point)
473
+
474
+ if ($null -ne $element) {
475
+ $rect = $element.Current.BoundingRectangle
476
+ @{
477
+ Name = $element.Current.Name
478
+ ControlType = $element.Current.ControlType.ProgrammaticName
479
+ AutomationId = $element.Current.AutomationId
480
+ ClassName = $element.Current.ClassName
481
+ Value = try { $element.GetCurrentPropertyValue([System.Windows.Automation.AutomationElement]::ValueProperty) } catch { $null }
482
+ Bounds = @{
483
+ X = [int]$rect.X
484
+ Y = [int]$rect.Y
485
+ Width = [int]$rect.Width
486
+ Height = [int]$rect.Height
487
+ }
488
+ IsEnabled = $element.Current.IsEnabled
489
+ HasKeyboardFocus = $element.Current.HasKeyboardFocus
490
+ } | ConvertTo-Json
491
+ } else {
492
+ @{ error = "No element found at point" } | ConvertTo-Json
493
+ }
494
+ `;
495
+
496
+ const result = await executePowerShellScript(psScript, 5000);
497
+
498
+ if (result.error) {
499
+ return { error: 'Element lookup failed: ' + result.error };
500
+ }
501
+
502
+ try {
503
+ const element = JSON.parse(result.stdout);
504
+ return {
505
+ ...element,
506
+ queryPoint: { x, y },
507
+ timestamp: Date.now()
508
+ };
509
+ } catch (e) {
510
+ return { error: 'Parse error' };
511
+ }
512
+ }
513
+
514
+ // ===== COMPREHENSIVE SCREEN ANALYSIS =====
515
+
516
+ /**
517
+ * Perform full screen analysis including:
518
+ * - Active window detection
519
+ * - Screen diff from previous
520
+ * - OCR text extraction
521
+ * - UI element detection
522
+ */
523
+ async function analyzeScreen(screenshot, options = {}) {
524
+ const {
525
+ includeOCR = true,
526
+ includeElements = true,
527
+ ocrProvider = 'tesseract'
528
+ } = options;
529
+
530
+ const results = {
531
+ timestamp: Date.now(),
532
+ screenshot: {
533
+ width: screenshot.width,
534
+ height: screenshot.height,
535
+ timestamp: screenshot.timestamp
536
+ }
537
+ };
538
+
539
+ // Parallel execution of analysis tasks
540
+ const tasks = [];
541
+
542
+ // Active window
543
+ tasks.push(
544
+ getActiveWindow().then(info => {
545
+ results.activeWindow = info;
546
+ })
547
+ );
548
+
549
+ // Screen diff
550
+ const diff = trackScreenChange(screenshot);
551
+ results.screenDiff = diff;
552
+
553
+ // OCR (optional, can be slow)
554
+ if (includeOCR) {
555
+ tasks.push(
556
+ extractTextFromImage(screenshot, { provider: ocrProvider }).then(ocr => {
557
+ results.ocr = ocr;
558
+ })
559
+ );
560
+ }
561
+
562
+ // UI Elements (optional)
563
+ if (includeElements) {
564
+ tasks.push(
565
+ detectUIElements({ depth: 2 }).then(elements => {
566
+ results.uiElements = elements;
567
+ })
568
+ );
569
+ }
570
+
571
+ // Wait for all tasks
572
+ await Promise.allSettled(tasks);
573
+
574
+ return results;
575
+ }
576
+
577
+ // ===== EXPORTS =====
578
+ module.exports = {
579
+ // Screen diffing
580
+ trackScreenChange,
581
+ getScreenDiffHistory,
582
+ compareScreenshots,
583
+
584
+ // Active window
585
+ getActiveWindow,
586
+ getCachedActiveWindow,
587
+
588
+ // OCR
589
+ extractTextFromImage,
590
+
591
+ // UI Elements
592
+ detectUIElements,
593
+ findElementAtPoint,
594
+
595
+ // Comprehensive analysis
596
+ analyzeScreen
597
+ };