@projectservan8n/cnapse 0.8.2 → 0.10.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.
@@ -551,6 +551,225 @@ export async function getMousePosition(): Promise<ToolResult> {
551
551
  }
552
552
  }
553
553
 
554
+ // =====================================================
555
+ // HUMAN-LIKE ACTIONS - More natural, realistic behavior
556
+ // =====================================================
557
+
558
+ const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
559
+
560
+ /**
561
+ * Move mouse smoothly with a curve (like a human would)
562
+ */
563
+ export async function moveMouseSmooth(
564
+ targetX: number,
565
+ targetY: number,
566
+ durationMs: number = 300
567
+ ): Promise<ToolResult> {
568
+ try {
569
+ // Get current position
570
+ const posResult = await getMousePosition();
571
+ const match = posResult.output.match(/(\d+)[,\s]+(\d+)/);
572
+
573
+ let startX = 0, startY = 0;
574
+ if (match) {
575
+ startX = parseInt(match[1]);
576
+ startY = parseInt(match[2]);
577
+ }
578
+
579
+ // Calculate steps (aim for ~60fps)
580
+ const steps = Math.max(10, Math.ceil(durationMs / 16));
581
+ const stepDelay = durationMs / steps;
582
+
583
+ // Move in steps with slight curve (quadratic easing)
584
+ for (let i = 1; i <= steps; i++) {
585
+ const t = i / steps;
586
+ // Ease-out quadratic for natural deceleration
587
+ const easeT = 1 - Math.pow(1 - t, 2);
588
+
589
+ const x = Math.round(startX + (targetX - startX) * easeT);
590
+ const y = Math.round(startY + (targetY - startY) * easeT);
591
+
592
+ await moveMouse(x, y);
593
+ await sleep(stepDelay);
594
+ }
595
+
596
+ return ok(`Smoothly moved to (${targetX}, ${targetY})`);
597
+ } catch (error) {
598
+ return err(`Failed to move smoothly: ${error instanceof Error ? error.message : 'Unknown error'}`);
599
+ }
600
+ }
601
+
602
+ /**
603
+ * Type text with human-like timing (variable speed, occasional pauses)
604
+ */
605
+ export async function typeTextHuman(
606
+ text: string,
607
+ wpm: number = 60
608
+ ): Promise<ToolResult> {
609
+ try {
610
+ // Average 5 characters per word
611
+ const baseDelayMs = 60000 / (wpm * 5);
612
+
613
+ for (let i = 0; i < text.length; i++) {
614
+ const char = text[i];
615
+
616
+ // Type the character
617
+ await typeText(char);
618
+
619
+ // Variable delay (80-120% of base)
620
+ const delay = baseDelayMs * (0.8 + Math.random() * 0.4);
621
+ await sleep(delay);
622
+
623
+ // Occasional longer pause (5% chance) - simulates thinking
624
+ if (Math.random() < 0.05) {
625
+ await sleep(200 + Math.random() * 400);
626
+ }
627
+
628
+ // Extra pause after punctuation (10% chance)
629
+ if (['.', ',', '!', '?', ';'].includes(char) && Math.random() < 0.3) {
630
+ await sleep(100 + Math.random() * 200);
631
+ }
632
+ }
633
+
634
+ return ok(`Typed (human-like): ${text.slice(0, 50)}${text.length > 50 ? '...' : ''}`);
635
+ } catch (error) {
636
+ return err(`Failed to type: ${error instanceof Error ? error.message : 'Unknown error'}`);
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Click at coordinates with smooth mouse movement
642
+ */
643
+ export async function clickAtSmooth(
644
+ x: number,
645
+ y: number,
646
+ button: 'left' | 'right' = 'left'
647
+ ): Promise<ToolResult> {
648
+ try {
649
+ await moveMouseSmooth(x, y, 200 + Math.random() * 100);
650
+ await sleep(50 + Math.random() * 50); // Small pause before click
651
+ await clickMouse(button);
652
+ return ok(`Clicked at (${x}, ${y})`);
653
+ } catch (error) {
654
+ return err(`Failed to click: ${error instanceof Error ? error.message : 'Unknown error'}`);
655
+ }
656
+ }
657
+
658
+ /**
659
+ * Find an element on screen by description and click it
660
+ * Uses vision AI to locate the element
661
+ */
662
+ export async function findAndClick(description: string): Promise<ToolResult> {
663
+ try {
664
+ // Import vision dynamically to avoid circular deps
665
+ const { captureScreenshot, findElementCoordinates } = await import('../lib/vision.js');
666
+
667
+ const screenshot = await captureScreenshot();
668
+ if (!screenshot) {
669
+ return err('Failed to capture screenshot');
670
+ }
671
+
672
+ const coords = await findElementCoordinates(screenshot, description);
673
+ if (!coords) {
674
+ return err(`Could not find element: ${description}`);
675
+ }
676
+
677
+ // Move smoothly and click
678
+ await moveMouseSmooth(coords.x, coords.y, 250);
679
+ await sleep(100);
680
+ await clickMouse('left');
681
+
682
+ return ok(`Found and clicked: ${description} at (${coords.x}, ${coords.y})`);
683
+ } catch (error) {
684
+ return err(`Failed to find and click: ${error instanceof Error ? error.message : 'Unknown error'}`);
685
+ }
686
+ }
687
+
688
+ /**
689
+ * Wait for screen to change (useful after actions)
690
+ */
691
+ export async function waitForScreenChange(
692
+ timeoutMs: number = 5000,
693
+ checkIntervalMs: number = 200
694
+ ): Promise<ToolResult> {
695
+ try {
696
+ const { captureScreenshot, getScreenHash } = await import('../lib/vision.js');
697
+
698
+ const initialScreen = await captureScreenshot();
699
+ if (!initialScreen) {
700
+ return err('Failed to capture initial screenshot');
701
+ }
702
+
703
+ const initialHash = getScreenHash(initialScreen);
704
+ const startTime = Date.now();
705
+
706
+ while (Date.now() - startTime < timeoutMs) {
707
+ await sleep(checkIntervalMs);
708
+
709
+ const currentScreen = await captureScreenshot();
710
+ if (currentScreen) {
711
+ const currentHash = getScreenHash(currentScreen);
712
+ if (currentHash !== initialHash) {
713
+ return ok(`Screen changed after ${Date.now() - startTime}ms`);
714
+ }
715
+ }
716
+ }
717
+
718
+ return ok('Screen did not change within timeout');
719
+ } catch (error) {
720
+ return err(`Wait failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
721
+ }
722
+ }
723
+
724
+ /**
725
+ * Perform a sequence of actions with human-like timing
726
+ */
727
+ export async function performSequence(
728
+ actions: Array<{ type: string; value?: string | number }>
729
+ ): Promise<ToolResult> {
730
+ const results: string[] = [];
731
+
732
+ for (const action of actions) {
733
+ // Random delay between actions (300-800ms)
734
+ await sleep(300 + Math.random() * 500);
735
+
736
+ try {
737
+ switch (action.type) {
738
+ case 'click':
739
+ await clickMouse('left');
740
+ results.push('clicked');
741
+ break;
742
+ case 'type':
743
+ if (typeof action.value === 'string') {
744
+ await typeTextHuman(action.value);
745
+ results.push(`typed: ${action.value.slice(0, 20)}`);
746
+ }
747
+ break;
748
+ case 'press':
749
+ if (typeof action.value === 'string') {
750
+ await pressKey(action.value);
751
+ results.push(`pressed: ${action.value}`);
752
+ }
753
+ break;
754
+ case 'wait':
755
+ const waitMs = typeof action.value === 'number' ? action.value : 1000;
756
+ await sleep(waitMs);
757
+ results.push(`waited: ${waitMs}ms`);
758
+ break;
759
+ case 'scroll':
760
+ const amount = typeof action.value === 'number' ? action.value : -3;
761
+ await scrollMouse(amount);
762
+ results.push(`scrolled: ${amount}`);
763
+ break;
764
+ }
765
+ } catch (error) {
766
+ results.push(`failed: ${action.type}`);
767
+ }
768
+ }
769
+
770
+ return ok(`Performed sequence: ${results.join(' → ')}`);
771
+ }
772
+
554
773
  /**
555
774
  * Get all computer control tools
556
775
  */
@@ -572,6 +791,13 @@ export function getComputerTools() {
572
791
  scrollMouse,
573
792
  dragMouse,
574
793
  getMousePosition,
794
+ // Human-like actions
795
+ moveMouseSmooth,
796
+ typeTextHuman,
797
+ clickAtSmooth,
798
+ findAndClick,
799
+ waitForScreenChange,
800
+ performSequence,
575
801
  };
576
802
  }
577
803
 
@@ -1,6 +0,0 @@
1
- import {
2
- ProviderSelector
3
- } from "./chunk-OPX7FFL6.js";
4
- export {
5
- ProviderSelector
6
- };