@aspiresys/visor 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1098 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.visor = exports.Visor = void 0;
4
+ const screen_1 = require("./screen");
5
+ const matcher_1 = require("./matcher");
6
+ const mouse_1 = require("./mouse");
7
+ const text_1 = require("./text");
8
+ const ocr_1 = require("./ocr");
9
+ const config_1 = require("./config");
10
+ const app_1 = require("./app");
11
+ const nut_js_1 = require("@nut-tree-fork/nut-js");
12
+ const path_1 = require("./path");
13
+ class Visor {
14
+ constructor() {
15
+ this.mouse = nut_js_1.mouse;
16
+ this.keyboard = nut_js_1.keyboard;
17
+ this.Key = nut_js_1.Key;
18
+ this.Button = nut_js_1.Button;
19
+ }
20
+ /**
21
+ * Finds an image on screen using OpenCV
22
+ * and performs a left mouse click
23
+ * on the matched region.
24
+ *
25
+ * @param image Optional image filename or path.
26
+ *
27
+ * @param confidence Match confidence threshold.
28
+ *
29
+ * Accepted range:
30
+ * 0.0 to 1.0
31
+ *
32
+ * Default:
33
+ * 0.8
34
+ *
35
+ * Recommended values:
36
+ * - 0.7 for dynamic UI
37
+ * - 0.8 for standard usage
38
+ * - 0.9+ for strict matching
39
+ *
40
+ * Lower confidence increases
41
+ * flexibility but may increase
42
+ * false positives.
43
+ *
44
+ * @example
45
+ * await visor.click("save.png");
46
+ *
47
+ * @example
48
+ * await visor.click("./images/save.png", 0.9);
49
+ */
50
+ async click(image, confidence = 0.8) {
51
+ if (!image) {
52
+ await this.mouse.click(this.Button.LEFT);
53
+ console.log("[MOUSE] Left click completed");
54
+ return;
55
+ }
56
+ const region = await this.find(image, confidence);
57
+ if (!region) {
58
+ throw new Error(`Image not found: ${image}`);
59
+ }
60
+ console.log("[CV] Match found:", region);
61
+ await (0, mouse_1.clickRegion)(region);
62
+ }
63
+ /**
64
+ * Performs a right mouse click.
65
+ *
66
+ * If an image is provided, the framework
67
+ * first finds the image on screen and
68
+ * moves the mouse to the matched region
69
+ * before performing the click.
70
+ *
71
+ * @param image Image filename or path.
72
+ * @param confidence Match confidence threshold.
73
+ *
74
+ * Accepted range:
75
+ * 0.0 to 1.0
76
+ *
77
+ * Default:
78
+ * 0.8
79
+ * @throws Error if image is not found.
80
+ *
81
+ * @example
82
+ * await visor.rightClick("file.png");
83
+ *
84
+ * @example
85
+ * await visor.rightClick();
86
+ */
87
+ async rightClick(image, confidence = 0.8) {
88
+ if (image) {
89
+ const region = await this.find(image, confidence);
90
+ if (!region) {
91
+ throw new Error(`Image not found: ${image}`);
92
+ }
93
+ await (0, mouse_1.moveToRegion)(region);
94
+ }
95
+ await this.mouse.click(this.Button.RIGHT);
96
+ console.log("[MOUSE] Right click completed");
97
+ }
98
+ /**
99
+ * Performs a double left mouse click.
100
+ *
101
+ * If an image is provided, the framework
102
+ * first finds the image on screen and
103
+ * moves the mouse to the matched region
104
+ * before performing the click.
105
+ *
106
+ * @param image Image filename or path.
107
+ * @param confidence Match confidence threshold.
108
+ *
109
+ * Accepted range:
110
+ * 0.0 to 1.0
111
+ *
112
+ * Default:
113
+ * 0.8
114
+ *
115
+ * @throws Error if image is not found.
116
+ *
117
+ * @example
118
+ * await visor.doubleClick("folder.png");
119
+ *
120
+ * @example
121
+ * await visor.doubleClick();
122
+ */
123
+ async doubleClick(image, confidence = 0.8) {
124
+ if (image) {
125
+ const region = await this.find(image, confidence);
126
+ if (!region) {
127
+ throw new Error(`Image not found: ${image}`);
128
+ }
129
+ await (0, mouse_1.moveToRegion)(region);
130
+ }
131
+ await this.mouse.doubleClick(this.Button.LEFT);
132
+ console.log("[MOUSE] Double click completed");
133
+ }
134
+ /**
135
+ * Finds the best matching image on screen
136
+ * using OpenCV template matching.
137
+ *
138
+ * @param image Image filename or path.
139
+ *
140
+ * @param confidence Match confidence threshold.
141
+ *
142
+ * Accepted range:
143
+ * 0.0 to 1.0
144
+ *
145
+ * Default:
146
+ * 0.8
147
+ *
148
+ * Recommended values:
149
+ * - 0.7 for dynamic UI
150
+ * - 0.8 for standard usage
151
+ * - 0.9+ for strict matching
152
+ *
153
+ * Higher confidence improves
154
+ * matching accuracy but may fail
155
+ * on scaled or slightly modified UI.
156
+ *
157
+ * @returns Best matched region or null.
158
+ *
159
+ * @example
160
+ * const region =
161
+ * await visor.find("save.png");
162
+ */
163
+ async find(image, confidence = 0.8) {
164
+ const screen = await (0, screen_1.captureScreen)();
165
+ const template = (0, matcher_1.loadTemplate)((0, path_1.resolveImagePath)(image));
166
+ try {
167
+ return (0, matcher_1.matchTemplate)(screen, template, confidence);
168
+ }
169
+ finally {
170
+ screen.delete();
171
+ template.delete();
172
+ }
173
+ }
174
+ /**
175
+ * Checks whether an image exists
176
+ * on the current screen.
177
+ *
178
+ * @param image Image filename or path.
179
+ * @param confidence Match confidence threshold.
180
+ *
181
+ * Accepted range:
182
+ * 0.0 to 1.0
183
+ *
184
+ * Default:
185
+ * 0.8
186
+ *
187
+ * @returns True if image exists.
188
+ *
189
+ * @example
190
+ * const exists =
191
+ * await visor.exists("login.png");
192
+ */
193
+ async exists(image, confidence = 0.8) {
194
+ return (await this.find(image, confidence)) !== null;
195
+ }
196
+ /**
197
+ * Finds all matching occurrences of an image
198
+ * on the current screen using OpenCV.
199
+ *
200
+ * @param image Image filename or path.
201
+ * @param confidence Match confidence threshold.
202
+ *
203
+ * Accepted range:
204
+ * 0.0 to 1.0
205
+ *
206
+ * Default:
207
+ * 0.8
208
+ *
209
+ * Lower confidence may return
210
+ * more false-positive matches.
211
+ *
212
+ * @returns Array of matched regions.
213
+ *
214
+ * @example
215
+ * const matches =
216
+ * await visor.findAll("icon.png");
217
+ */
218
+ async findAll(image, confidence = 0.8) {
219
+ const screen = await (0, screen_1.captureScreen)();
220
+ const template = (0, matcher_1.loadTemplate)((0, path_1.resolveImagePath)(image));
221
+ try {
222
+ return (0, matcher_1.findAllMatches)(screen, template, confidence);
223
+ }
224
+ finally {
225
+ screen.delete();
226
+ template.delete();
227
+ }
228
+ }
229
+ /**
230
+ * Finds all image matches and prints
231
+ * a formatted match preview including
232
+ * coordinates and confidence values.
233
+ *
234
+ * Useful for debugging and visual
235
+ * automation tuning.
236
+ *
237
+ * @param image Image filename or path.
238
+ * @param confidence Match confidence threshold.
239
+ *
240
+ * Accepted range:
241
+ * 0.0 to 1.0
242
+ *
243
+ * Default:
244
+ * 0.8
245
+ *
246
+ * @returns Array of matched regions.
247
+ *
248
+ * @example
249
+ * await visor.previewMatches("button.png");
250
+ */
251
+ async previewMatches(image, confidence = 0.8) {
252
+ const matches = await this.findAll(image, confidence);
253
+ console.log("\n=== MATCH PREVIEW ===\n");
254
+ matches.forEach((m, i) => {
255
+ console.log(`#${i + 1}
256
+ X:${m.x}
257
+ Y:${m.y}
258
+ W:${m.width}
259
+ H:${m.height}
260
+ CONF:${m.confidence.toFixed(3)}
261
+ `);
262
+ });
263
+ return matches;
264
+ }
265
+ /**
266
+ * Waits until an image appears
267
+ * on the screen.
268
+ *
269
+ * @param image Image filename or path.
270
+ *
271
+ * @param confidence Match confidence threshold.
272
+ *
273
+ * Accepted range:
274
+ * 0.0 to 1.0
275
+ *
276
+ * Default:
277
+ * 0.8
278
+ *
279
+ * Recommended values:
280
+ * - 0.7 for dynamic UI
281
+ * - 0.8 for standard usage
282
+ * - 0.9+ for strict matching
283
+ *
284
+ * @param timeout Timeout duration
285
+ * in milliseconds.
286
+ *
287
+ * Default:
288
+ * 5000ms
289
+ *
290
+ * Recommended range:
291
+ * 1000ms to 30000ms
292
+ *
293
+ * @throws Error if timeout occurs.
294
+ *
295
+ * @example
296
+ * await visor.wait("loading-done.png");
297
+ */
298
+ async wait(image, confidence = 0.8, timeout = 5000) {
299
+ const interval = 1000;
300
+ const attempts = timeout / interval;
301
+ for (let i = 0; i < attempts; i++) {
302
+ if (await this.exists(image, confidence)) {
303
+ return true;
304
+ }
305
+ await this.sleep(interval);
306
+ }
307
+ throw new Error(`Timeout waiting for image: ${image}`);
308
+ }
309
+ /**
310
+ * Waits until an image disappears
311
+ * from the screen.
312
+ *
313
+ * @param image Image filename or path.
314
+ * @param confidence Match confidence threshold.
315
+ *
316
+ * Accepted range:
317
+ * 0.0 to 1.0
318
+ *
319
+ * Default:
320
+ * 0.8
321
+ * @param timeout Timeout duration
322
+ * in milliseconds.
323
+ *
324
+ * Default:
325
+ * 5000ms
326
+ *
327
+ * Recommended range:
328
+ * 1000ms to 30000ms
329
+ * @throws Error if timeout occurs.
330
+ *
331
+ * @example
332
+ * await visor.waitToVanish("spinner.png");
333
+ */
334
+ async waitToVanish(image, confidence = 0.8, timeout = 5000) {
335
+ const start = Date.now();
336
+ while (Date.now() - start < timeout) {
337
+ if (!(await this.exists(image, confidence))) {
338
+ return true;
339
+ }
340
+ await this.sleep(1000);
341
+ }
342
+ throw new Error(`Timeout waiting for image to vanish: ${image}`);
343
+ }
344
+ /**
345
+ * Waits until specific text appears
346
+ * on screen using OCR.
347
+ *
348
+ * Performance Note:
349
+ * OCR operations are computationally
350
+ * expensive and may take 1–5 seconds
351
+ * depending on screen complexity.
352
+ *
353
+ * @param text Text to search for.
354
+ *
355
+ * @param timeout Timeout duration
356
+ * in milliseconds.
357
+ *
358
+ * Default:
359
+ * 5000ms
360
+ *
361
+ * Recommended range:
362
+ * 1000ms to 30000ms
363
+ *
364
+ * @throws Error if timeout occurs.
365
+ *
366
+ * @example
367
+ * await visor.waitText("Success");
368
+ */
369
+ async waitText(text, timeout = 5000) {
370
+ const start = Date.now();
371
+ while (Date.now() - start < timeout) {
372
+ if (await this.existsText(text)) {
373
+ return true;
374
+ }
375
+ await this.sleep(1000);
376
+ }
377
+ throw new Error(`Timeout waiting for text: ${text}`);
378
+ }
379
+ /**
380
+ * Waits until specific text disappears
381
+ * from screen using OCR.
382
+ * Performance Note:
383
+ * OCR operations are computationally
384
+ * expensive and may take 1–5 seconds
385
+ * depending on screen complexity.
386
+ *
387
+ * @param text Text to search for.
388
+ * @param timeout Timeout duration
389
+ * in milliseconds.
390
+ *
391
+ * Default:
392
+ * 5000ms
393
+ *
394
+ * Recommended range:
395
+ * 1000ms to 30000ms
396
+ *
397
+ * @throws Error if timeout occurs.
398
+ *
399
+ * @example
400
+ * await visor.waitTextToVanish("Loading");
401
+ */
402
+ async waitTextToVanish(text, timeout = 5000) {
403
+ const start = Date.now();
404
+ while (Date.now() - start < timeout) {
405
+ if (!(await this.existsText(text))) {
406
+ return true;
407
+ }
408
+ await this.sleep(1000);
409
+ }
410
+ throw new Error(`Timeout waiting for text to vanish: ${text}`);
411
+ }
412
+ /**
413
+ * Finds text on screen using OCR
414
+ * and performs a left mouse click
415
+ * on the matched text region.
416
+ *
417
+ * @param text Text to search for.
418
+ *
419
+ * @throws Error if text is not found.
420
+ *
421
+ * @example
422
+ * await visor.clickText("Login");
423
+ */
424
+ async clickText(text) {
425
+ const region = await (0, text_1.findText)(text);
426
+ if (!region) {
427
+ throw new Error(`Text not found: ${text}`);
428
+ }
429
+ await (0, mouse_1.clickRegion)(region);
430
+ }
431
+ /**
432
+ * Finds text on screen or inside
433
+ * a specific region using OCR.
434
+ *
435
+ * @param text Text to search for.
436
+ * @param region Optional search region.
437
+ *
438
+ * @returns Matched text region or null.
439
+ *
440
+ * @example
441
+ * const region =
442
+ * await visor.findText("Submit");
443
+ */
444
+ async findText(text, region) {
445
+ return await (0, text_1.findText)(text, region);
446
+ }
447
+ /**
448
+ * Checks whether specific text exists
449
+ * on screen using OCR.
450
+ *
451
+ * @param text Text to search for.
452
+ * @param region Optional search region.
453
+ *
454
+ * @returns True if text exists.
455
+ *
456
+ * @example
457
+ * const exists =
458
+ * await visor.existsText("Teams");
459
+ */
460
+ async existsText(text, region) {
461
+ return await (0, text_1.existsText)(text, region);
462
+ }
463
+ /**
464
+ * Performs OCR on a specific
465
+ * screen region.
466
+ *
467
+ * @param region Screen region coordinates.
468
+ *
469
+ * @returns OCR result object.
470
+ *
471
+ * @example
472
+ * const result =
473
+ * await visor.readRegion({
474
+ * x: 100,
475
+ * y: 100,
476
+ * width: 500,
477
+ * height: 300
478
+ * });
479
+ */
480
+ async readRegion(region) {
481
+ return await (0, ocr_1.extractTextFromRegion)(region);
482
+ }
483
+ /**
484
+ * Performs OCR on the entire screen
485
+ * using Tesseract OCR.
486
+ *
487
+ * Captures the current screen,
488
+ * preprocesses the image, and
489
+ * extracts readable text content.
490
+ *
491
+ * Performance Note:
492
+ * OCR is computationally expensive
493
+ * and may take 1–5 seconds depending
494
+ * on:
495
+ * - screen complexity
496
+ * - text density
497
+ * - display resolution
498
+ * - system performance
499
+ *
500
+ * Returns:
501
+ * - extracted text
502
+ * - OCR metadata
503
+ * - TSV layout information
504
+ * - HOCR data
505
+ *
506
+ * @returns OCR result object.
507
+ *
508
+ * @example
509
+ * const result =
510
+ * await visor.readScreen();
511
+ *
512
+ * console.log(result.text);
513
+ */
514
+ async readScreen() {
515
+ return await (0, ocr_1.extractTextFromRegion)();
516
+ }
517
+ /**
518
+ * Captures a screenshot of the
519
+ * current screen and saves it
520
+ * to the specified path.
521
+ *
522
+ * Supported formats depend on
523
+ * the output file extension.
524
+ *
525
+ * @param path Output image path.
526
+ *
527
+ * @example
528
+ * await visor.captureScreenshot(
529
+ * "./screenshots/home.png"
530
+ * );
531
+ */
532
+ async captureScreenshot(path) {
533
+ console.log(`[VISOR] Saving screenshot: ${path}`);
534
+ await (0, screen_1.saveScreenshot)(path);
535
+ console.log("[VISOR] Screenshot saved");
536
+ }
537
+ /**
538
+ * Opens a desktop application,
539
+ * executable, file, folder,
540
+ * or URL using Windows shell.
541
+ *
542
+ * Performance Note:
543
+ * Some Electron-based applications
544
+ * may require additional startup
545
+ * stabilization time.
546
+ *
547
+ * @param command App name, executable,
548
+ * file path, or URL.
549
+ *
550
+ * @example
551
+ * await visor.openApp("notepad");
552
+ *
553
+ * @example
554
+ * await visor.openApp("chrome.exe");
555
+ */
556
+ async openApp(command) {
557
+ console.log(`[VISOR] Opening app: ${command}`);
558
+ await (0, app_1.openApp)(command);
559
+ }
560
+ /**
561
+ * Drags one visual element and
562
+ * drops it onto another using
563
+ * image matching.
564
+ *
565
+ * @param source Source image.
566
+ *
567
+ * @param target Target image.
568
+ *
569
+ * @param confidence Match confidence threshold.
570
+ *
571
+ * Accepted range:
572
+ * 0.0 to 1.0
573
+ *
574
+ * Default:
575
+ * 0.8
576
+ *
577
+ * Recommended values:
578
+ * - 0.7 for dynamic UI
579
+ * - 0.8 for standard usage
580
+ * - 0.9+ for strict matching
581
+ *
582
+ * @throws Error if source or target
583
+ * image is not found.
584
+ *
585
+ * @example
586
+ * await visor.dragDrop(
587
+ * "file.png",
588
+ * "folder.png"
589
+ * );
590
+ */
591
+ async dragDrop(source, target, confidence = 0.8) {
592
+ const src = await this.find(source, confidence);
593
+ const dst = await this.find(target, confidence);
594
+ if (!src || !dst) {
595
+ throw new Error("Source or target not found");
596
+ }
597
+ await (0, mouse_1.moveToRegion)(src);
598
+ await this.mouse.pressButton(this.Button.LEFT);
599
+ await (0, mouse_1.moveToRegion)(dst);
600
+ await this.mouse.releaseButton(this.Button.LEFT);
601
+ console.log("[MOUSE] Drag and drop completed");
602
+ }
603
+ /**
604
+ * Moves the mouse cursor to the
605
+ * matched image location without
606
+ * performing a click.
607
+ *
608
+ * @param image Image filename or path.
609
+ * @param confidence Match confidence threshold.
610
+ *
611
+ * Accepted range:
612
+ * 0.0 to 1.0
613
+ *
614
+ * Default:
615
+ * 0.8
616
+ *
617
+ * @throws Error if image is not found.
618
+ *
619
+ * @example
620
+ * await visor.hover("menu.png");
621
+ */
622
+ async hover(image, confidence = 0.8) {
623
+ const region = await this.find(image, confidence);
624
+ if (!region) {
625
+ throw new Error(`Image not found: ${image}`);
626
+ }
627
+ await (0, mouse_1.moveToRegion)(region);
628
+ console.log("[MOUSE] Hover completed");
629
+ }
630
+ /**
631
+ * Scrolls the mouse wheel downward.
632
+ *
633
+ * @param amount Scroll amount.
634
+ *
635
+ * Default:
636
+ * 500
637
+ *
638
+ * Recommended range:
639
+ * 100 to 3000
640
+ *
641
+ * @example
642
+ * await visor.scrollDown(1000);
643
+ */
644
+ async scrollDown(amount = 500) {
645
+ await this.mouse.scrollDown(amount);
646
+ }
647
+ /**
648
+ * Scrolls the mouse wheel upward.
649
+ *
650
+ * @param amount Scroll amount.
651
+ *
652
+ * Default:
653
+ * 500
654
+ *
655
+ * Recommended range:
656
+ * 100 to 3000
657
+ *
658
+ * @example
659
+ * await visor.scrollUp(1000);
660
+ */
661
+ async scrollUp(amount = 500) {
662
+ await this.mouse.scrollUp(amount);
663
+ }
664
+ /**
665
+ * Types text using keyboard automation.
666
+ *
667
+ * @param text Text to type.
668
+ *
669
+ * @example
670
+ * await visor.type("Hello World");
671
+ */
672
+ async type(text) {
673
+ await this.keyboard.type(text);
674
+ }
675
+ /**
676
+ * Presses one or more keyboard keys.
677
+ *
678
+ * Supports both single key presses
679
+ * and hotkey combinations.
680
+ *
681
+ * @param keys Keyboard keys to press.
682
+ *
683
+ * @example
684
+ * await visor.press(visor.Key.Enter);
685
+ *
686
+ * @example
687
+ * await visor.press(
688
+ * visor.Key.LeftControl,
689
+ * visor.Key.S
690
+ * );
691
+ */
692
+ async press(...keys) {
693
+ await this.keyboard.pressKey(...keys);
694
+ await this.keyboard.releaseKey(...keys);
695
+ }
696
+ /**
697
+ * Pauses execution for a specified
698
+ * duration.
699
+ *
700
+ * Useful for:
701
+ * - UI stabilization
702
+ * - animation completion
703
+ * - Electron rendering delays
704
+ * - OCR synchronization
705
+ * - desktop transition handling
706
+ *
707
+ * Recommended for short stabilization
708
+ * waits rather than long fixed delays.
709
+ *
710
+ * @param ms Delay duration
711
+ * in milliseconds.
712
+ *
713
+ * Recommended range:
714
+ * 200ms to 5000ms
715
+ *
716
+ * Excessive sleep usage may
717
+ * slow automation execution.
718
+ *
719
+ * @example
720
+ * await visor.sleep(2000);
721
+ */
722
+ async sleep(ms) {
723
+ return new Promise(r => setTimeout(r, ms));
724
+ }
725
+ /**
726
+ * Moves the mouse cursor to the
727
+ * specified screen coordinates.
728
+ *
729
+ * Coordinates should match the
730
+ * current display scaling setup.
731
+ *
732
+ * @param x X coordinate.
733
+ * @param y Y coordinate.
734
+ *
735
+ * @example
736
+ * await visor.moveMouse(500, 300);
737
+ */
738
+ async moveMouse(x, y) {
739
+ await this.mouse.move((0, nut_js_1.straightTo)(new nut_js_1.Point(x, y)));
740
+ }
741
+ /**
742
+ * Returns the current mouse cursor
743
+ * position.
744
+ *
745
+ * @returns Current mouse coordinates.
746
+ *
747
+ * @example
748
+ * const pos =
749
+ * await visor.getMousePosition();
750
+ */
751
+ async getMousePosition() {
752
+ return await this.mouse.getPosition();
753
+ }
754
+ /**
755
+ * Sets the display scaling factor
756
+ * used for coordinate normalization
757
+ * and image matching.
758
+ *
759
+ * Required for:
760
+ * - high-DPI displays
761
+ * - Windows display scaling
762
+ * - accurate mouse positioning
763
+ * - reliable template matching
764
+ *
765
+ * Common values:
766
+ * - 1.0 = 100% scaling
767
+ * - 1.25 = 125% scaling
768
+ * - 1.5 = 150% scaling
769
+ * - 2.0 = 200% scaling
770
+ *
771
+ * Incorrect scaling values may cause:
772
+ * - inaccurate clicks
773
+ * - failed image matching
774
+ * - incorrect OCR regions
775
+ * - offset mouse movement
776
+ *
777
+ * @param scale Display scaling factor.
778
+ *
779
+ * Recommended range:
780
+ * 1.0 to 2.0
781
+ *
782
+ * @example
783
+ * visor.setScaleFactor(1.5);
784
+ */
785
+ setScaleFactor(scale) {
786
+ config_1.visorConfig.scaleFactor = scale;
787
+ }
788
+ /**
789
+ * Returns the current display
790
+ * scaling factor.
791
+ *
792
+ * @returns Current scale factor.
793
+ *
794
+ * @example
795
+ * const scale =
796
+ * visor.getScaleFactor();
797
+ */
798
+ getScaleFactor() {
799
+ return config_1.visorConfig.scaleFactor;
800
+ }
801
+ /**
802
+ * Enables or disables debug logging.
803
+ *
804
+ * @param mode Debug mode state.
805
+ *
806
+ * @example
807
+ * visor.setDebug(true);
808
+ */
809
+ setDebug(mode) {
810
+ config_1.visorConfig.debug = mode;
811
+ }
812
+ /**
813
+ * Sets the default image search path
814
+ * used for visual automation APIs.
815
+ *
816
+ * @param path Image directory path.
817
+ *
818
+ * @example
819
+ * visor.setImagePath("./images");
820
+ */
821
+ setImagePath(path) {
822
+ config_1.visorConfig.imagePath = path;
823
+ }
824
+ /**
825
+ * Returns the current default
826
+ * image search path.
827
+ *
828
+ * @returns Image path.
829
+ *
830
+ * @example
831
+ * const path =
832
+ * visor.getImagePath();
833
+ */
834
+ getImagePath() {
835
+ return config_1.visorConfig.imagePath;
836
+ }
837
+ /**
838
+ * Closes a desktop application
839
+ * using Windows taskkill.
840
+ *
841
+ * @param processName Process executable name.
842
+ *
843
+ * @example
844
+ * await visor.closeApp("notepad.exe");
845
+ *
846
+ * @example
847
+ * await visor.closeApp("ms-teams.exe");
848
+ */
849
+ async closeApp(processName) {
850
+ await (0, app_1.closeApp)(processName);
851
+ }
852
+ /**
853
+ * Loads multiple framework settings
854
+ * at once from a configuration object.
855
+ *
856
+ * Useful for centralized framework
857
+ * initialization and project setup.
858
+ *
859
+ * Supported settings:
860
+ * - scaleFactor
861
+ * - imagePath
862
+ * - debug
863
+ *
864
+ * @param config Framework configuration object.
865
+ *
866
+ * @example
867
+ * visor.loadConfig({
868
+ * scaleFactor: 1.5,
869
+ * imagePath: "./images",
870
+ * debug: true
871
+ * });
872
+ */
873
+ loadConfig(config) {
874
+ if (config.scaleFactor !== undefined) {
875
+ this.setScaleFactor(config.scaleFactor);
876
+ }
877
+ if (config.imagePath) {
878
+ this.setImagePath(config.imagePath);
879
+ }
880
+ if (config.debug !== undefined) {
881
+ this.setDebug(config.debug);
882
+ }
883
+ console.log("[VISOR] Config loaded");
884
+ }
885
+ /**
886
+ * Finds the first matching image
887
+ * from a list of possible images.
888
+ *
889
+ * Useful for handling:
890
+ * - light/dark themes
891
+ * - UI variations
892
+ * - multiple visual states
893
+ *
894
+ * @param images Array of image filenames or paths.
895
+ * @param confidence Match confidence threshold.
896
+ *
897
+ * Accepted range:
898
+ * 0.0 to 1.0
899
+ *
900
+ * Default:
901
+ * 0.8
902
+ *
903
+ * @returns Object containing matched image
904
+ * and region, or null if none matched.
905
+ *
906
+ * @example
907
+ * const result =
908
+ * await visor.findAny([
909
+ * "chat-light.png",
910
+ * "chat-dark.png"
911
+ * ]);
912
+ */
913
+ async findAny(images, confidence = 0.8) {
914
+ const screen = await (0, screen_1.captureScreen)();
915
+ try {
916
+ for (const image of images) {
917
+ const template = (0, matcher_1.loadTemplate)((0, path_1.resolveImagePath)(image));
918
+ try {
919
+ const region = (0, matcher_1.matchTemplate)(screen, template, confidence);
920
+ if (region) {
921
+ return {
922
+ image,
923
+ region
924
+ };
925
+ }
926
+ }
927
+ finally {
928
+ template.delete();
929
+ }
930
+ }
931
+ return null;
932
+ }
933
+ finally {
934
+ screen.delete();
935
+ }
936
+ }
937
+ /**
938
+ * Checks whether any image from
939
+ * a list exists on the current screen.
940
+ *
941
+ * Useful for handling:
942
+ * - multiple themes
943
+ * - alternate UI layouts
944
+ * - dynamic visual states
945
+ *
946
+ * @param images Array of image filenames or paths.
947
+ * @param confidence Match confidence threshold.
948
+ *
949
+ * Accepted range:
950
+ * 0.0 to 1.0
951
+ *
952
+ * Default:
953
+ * 0.8
954
+ *
955
+ * @returns True if any image exists.
956
+ *
957
+ * @example
958
+ * const exists =
959
+ * await visor.existsAny([
960
+ * "save-light.png",
961
+ * "save-dark.png"
962
+ * ]);
963
+ */
964
+ async existsAny(images, confidence = 0.8) {
965
+ return (await this.findAny(images, confidence)) !== null;
966
+ }
967
+ /**
968
+ * Finds the first matching image
969
+ * from a list and performs a
970
+ * left mouse click on it.
971
+ *
972
+ * Useful for handling:
973
+ * - light/dark themes
974
+ * - UI variations
975
+ * - dynamic buttons/icons
976
+ *
977
+ * @param images Array of image
978
+ * filenames or paths.
979
+ *
980
+ * @param confidence Match confidence threshold.
981
+ *
982
+ * Accepted range:
983
+ * 0.0 to 1.0
984
+ *
985
+ * Default:
986
+ * 0.8
987
+ *
988
+ * Recommended values:
989
+ * - 0.7 for dynamic UI
990
+ * - 0.8 for standard usage
991
+ * - 0.9+ for strict matching
992
+ *
993
+ * Lower confidence increases
994
+ * flexibility but may increase
995
+ * false positives.
996
+ *
997
+ * @throws Error if none of the
998
+ * images are found.
999
+ *
1000
+ * @example
1001
+ * await visor.clickAny([
1002
+ * "send-light.png",
1003
+ * "send-dark.png"
1004
+ * ]);
1005
+ */
1006
+ async clickAny(images, confidence = 0.8) {
1007
+ const result = await this.findAny(images, confidence);
1008
+ if (!result) {
1009
+ throw new Error(`None of the images were found`);
1010
+ }
1011
+ console.log(`[VISOR] Clicking image: ${result.image}`);
1012
+ await (0, mouse_1.clickRegion)(result.region);
1013
+ }
1014
+ /**
1015
+ * Waits until any image from
1016
+ * a list appears on screen.
1017
+ *
1018
+ * Useful for handling:
1019
+ * - multiple themes
1020
+ * - alternate UI layouts
1021
+ * - dynamic application states
1022
+ *
1023
+ * @param images Array of image
1024
+ * filenames or paths.
1025
+ *
1026
+ * @param confidence Match confidence threshold.
1027
+ *
1028
+ * Accepted range:
1029
+ * 0.0 to 1.0
1030
+ *
1031
+ * Default:
1032
+ * 0.8
1033
+ *
1034
+ * Recommended values:
1035
+ * - 0.7 for dynamic UI
1036
+ * - 0.8 for standard usage
1037
+ * - 0.9+ for strict matching
1038
+ *
1039
+ * Lower confidence increases
1040
+ * flexibility but may increase
1041
+ * false positives.
1042
+ *
1043
+ * @param timeout Timeout duration
1044
+ * in milliseconds.
1045
+ *
1046
+ * Default:
1047
+ * 5000ms
1048
+ *
1049
+ * Recommended range:
1050
+ * 1000ms to 30000ms
1051
+ *
1052
+ * @returns Object containing matched image
1053
+ * and region.
1054
+ *
1055
+ * @throws Error if timeout occurs.
1056
+ *
1057
+ * @example
1058
+ * await visor.waitAny([
1059
+ * "home-light.png",
1060
+ * "home-dark.png"
1061
+ * ]);
1062
+ */
1063
+ async waitAny(images, confidence = 0.8, timeout = 5000) {
1064
+ const interval = 1000;
1065
+ const attempts = timeout / interval;
1066
+ for (let i = 0; i < attempts; i++) {
1067
+ console.log(`[WAITANY] Attempt ${i + 1}`);
1068
+ for (const image of images) {
1069
+ console.log(`[WAITANY] Checking ${image}`);
1070
+ const exists = await this.exists(image, confidence);
1071
+ if (exists) {
1072
+ console.log(`[WAITANY] Found ${image}`);
1073
+ return true;
1074
+ }
1075
+ }
1076
+ await this.sleep(interval);
1077
+ }
1078
+ throw new Error("Timeout waiting for images");
1079
+ }
1080
+ /**
1081
+ * Terminates shared OCR worker.
1082
+ *
1083
+ * Useful for framework cleanup
1084
+ * after long automation sessions.
1085
+ *
1086
+ * Recommended for:
1087
+ * - long-running automation
1088
+ * - memory cleanup
1089
+ * - graceful framework shutdown
1090
+ * @example
1091
+ * await visor.terminateOCR();
1092
+ */
1093
+ async terminateOCR() {
1094
+ await (0, ocr_1.terminateOCR)();
1095
+ }
1096
+ }
1097
+ exports.Visor = Visor;
1098
+ exports.visor = new Visor();