react-native-ai-debugger 1.0.7 → 1.0.9

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.
@@ -1,9 +1,12 @@
1
1
  import { createServer } from "http";
2
2
  import { logBuffer, networkBuffer, bundleErrorBuffer, connectedApps } from "./state.js";
3
+ import { listAndroidDevices, androidScreenshot, androidGetScreenSize, androidTap } from "./android.js";
4
+ import { listIOSSimulators, iosScreenshot, iosTap } from "./ios.js";
3
5
  const DEFAULT_HTTP_PORT = 3456;
4
6
  const MAX_PORT_ATTEMPTS = 20;
5
7
  // Store the active port for querying via MCP tool
6
8
  let activeDebugServerPort = null;
9
+ let tapVerifierMarkers = [];
7
10
  /**
8
11
  * Get the port the debug HTTP server is running on (if started)
9
12
  */
@@ -178,6 +181,7 @@ function htmlTemplate(title, content, refreshInterval = 3000) {
178
181
  <a href="/logs" ${title === 'Logs' ? 'class="active"' : ''}>Logs</a>
179
182
  <a href="/network" ${title === 'Network' ? 'class="active"' : ''}>Network</a>
180
183
  <a href="/apps" ${title === 'Apps' ? 'class="active"' : ''}>Apps</a>
184
+ <a href="/tap-verifier" ${title === 'Tap Verifier' ? 'class="active"' : ''}>Tap Verifier</a>
181
185
  </nav>
182
186
  <div id="content">${content}</div>
183
187
  <script>
@@ -481,18 +485,535 @@ function renderApps() {
481
485
  ${appsHtml}
482
486
  `);
483
487
  }
488
+ function renderTapVerifier() {
489
+ return `<!DOCTYPE html>
490
+ <html lang="en">
491
+ <head>
492
+ <meta charset="UTF-8">
493
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
494
+ <title>Tap Test Page - RN Debugger</title>
495
+ <style>
496
+ * { box-sizing: border-box; margin: 0; padding: 0; }
497
+ body {
498
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
499
+ background: #1a1a2e;
500
+ color: #eee;
501
+ min-height: 100vh;
502
+ }
503
+ .page-container {
504
+ position: relative;
505
+ width: 100%;
506
+ min-height: 100vh;
507
+ }
508
+
509
+ /* Sample UI Elements for Testing */
510
+ .header {
511
+ background: #16213e;
512
+ padding: 20px;
513
+ text-align: center;
514
+ border-bottom: 2px solid #0f3460;
515
+ }
516
+ .header h1 { font-size: 24px; margin-bottom: 8px; }
517
+ .header p { color: #888; font-size: 14px; }
518
+
519
+ .button-grid {
520
+ display: grid;
521
+ grid-template-columns: repeat(3, 1fr);
522
+ gap: 16px;
523
+ padding: 20px;
524
+ max-width: 400px;
525
+ margin: 0 auto;
526
+ }
527
+ .test-btn {
528
+ padding: 20px;
529
+ border: none;
530
+ border-radius: 12px;
531
+ font-size: 14px;
532
+ font-weight: 600;
533
+ cursor: pointer;
534
+ transition: transform 0.1s;
535
+ }
536
+ .test-btn:active { transform: scale(0.95); }
537
+ .btn-red { background: #e94560; color: white; }
538
+ .btn-blue { background: #0f3460; color: white; }
539
+ .btn-green { background: #1eb980; color: white; }
540
+ .btn-yellow { background: #f39c12; color: #333; }
541
+ .btn-purple { background: #9b59b6; color: white; }
542
+ .btn-cyan { background: #00cec9; color: #333; }
543
+
544
+ /* Grid Overlay */
545
+ .grid-overlay {
546
+ position: fixed;
547
+ top: 0;
548
+ left: 0;
549
+ width: 100%;
550
+ height: 100%;
551
+ pointer-events: none;
552
+ z-index: 9998;
553
+ display: none;
554
+ }
555
+ .grid-overlay.visible { display: block; }
556
+ .grid-line-v {
557
+ position: absolute;
558
+ top: 0;
559
+ bottom: 0;
560
+ width: 1px;
561
+ background: rgba(255, 255, 0, 0.6);
562
+ }
563
+ .grid-line-h {
564
+ position: absolute;
565
+ left: 0;
566
+ right: 0;
567
+ height: 1px;
568
+ background: rgba(255, 255, 0, 0.6);
569
+ }
570
+ .grid-label {
571
+ position: absolute;
572
+ background: rgba(0, 0, 0, 0.8);
573
+ color: #ffff00;
574
+ font-size: 10px;
575
+ font-family: monospace;
576
+ padding: 2px 4px;
577
+ border-radius: 2px;
578
+ }
579
+ .grid-toggle {
580
+ position: fixed;
581
+ top: 10px;
582
+ left: 10px;
583
+ background: #ffff00;
584
+ color: #000;
585
+ border: none;
586
+ padding: 6px 10px;
587
+ border-radius: 4px;
588
+ font-size: 11px;
589
+ font-weight: bold;
590
+ cursor: pointer;
591
+ z-index: 10001;
592
+ opacity: 0.8;
593
+ }
594
+
595
+ .nav-bar {
596
+ display: flex;
597
+ justify-content: space-around;
598
+ background: #16213e;
599
+ padding: 16px;
600
+ position: fixed;
601
+ bottom: 0;
602
+ left: 0;
603
+ right: 0;
604
+ border-top: 2px solid #0f3460;
605
+ }
606
+ .nav-item {
607
+ display: flex;
608
+ flex-direction: column;
609
+ align-items: center;
610
+ color: #888;
611
+ font-size: 12px;
612
+ padding: 8px 16px;
613
+ border-radius: 8px;
614
+ cursor: pointer;
615
+ }
616
+ .nav-item:hover { background: #0f3460; color: #fff; }
617
+ .nav-icon { font-size: 24px; margin-bottom: 4px; }
618
+
619
+ .card {
620
+ background: #16213e;
621
+ margin: 20px;
622
+ padding: 20px;
623
+ border-radius: 12px;
624
+ border: 1px solid #0f3460;
625
+ }
626
+ .card h2 { margin-bottom: 12px; font-size: 18px; }
627
+ .card p { color: #888; line-height: 1.6; }
628
+
629
+ .input-group {
630
+ margin: 20px;
631
+ }
632
+ .input-group label {
633
+ display: block;
634
+ margin-bottom: 8px;
635
+ color: #888;
636
+ }
637
+ .input-group input {
638
+ width: 100%;
639
+ padding: 14px;
640
+ border: 2px solid #0f3460;
641
+ border-radius: 8px;
642
+ background: #16213e;
643
+ color: #fff;
644
+ font-size: 16px;
645
+ }
646
+
647
+ /* Canvas Overlay for Markers */
648
+ #markerCanvas {
649
+ position: fixed;
650
+ top: 0;
651
+ left: 0;
652
+ width: 100%;
653
+ height: 100%;
654
+ pointer-events: auto;
655
+ z-index: 9999;
656
+ cursor: crosshair;
657
+ }
658
+
659
+ /* Marker info panel */
660
+ .marker-panel {
661
+ position: fixed;
662
+ top: 10px;
663
+ right: 10px;
664
+ background: rgba(0,0,0,0.8);
665
+ padding: 12px;
666
+ border-radius: 8px;
667
+ font-size: 12px;
668
+ z-index: 10000;
669
+ max-width: 250px;
670
+ }
671
+ .marker-panel h3 {
672
+ color: #ff6b6b;
673
+ margin-bottom: 8px;
674
+ font-size: 14px;
675
+ }
676
+ .marker-info {
677
+ color: #ccc;
678
+ margin-bottom: 4px;
679
+ }
680
+ .clear-btn {
681
+ margin-top: 8px;
682
+ padding: 6px 12px;
683
+ background: #e94560;
684
+ border: none;
685
+ border-radius: 4px;
686
+ color: white;
687
+ cursor: pointer;
688
+ font-size: 12px;
689
+ }
690
+ </style>
691
+ </head>
692
+ <body>
693
+ <div class="page-container">
694
+ <div class="header">
695
+ <h1>Tap Test Page</h1>
696
+ <p>Agent marks coordinates here to verify accuracy</p>
697
+ </div>
698
+
699
+ <div class="button-grid">
700
+ <button class="test-btn btn-red" onclick="btnClick('Red')">Red</button>
701
+ <button class="test-btn btn-blue" onclick="btnClick('Blue')">Blue</button>
702
+ <button class="test-btn btn-green" onclick="btnClick('Green')">Green</button>
703
+ <button class="test-btn btn-yellow" onclick="btnClick('Yellow')">Yellow</button>
704
+ <button class="test-btn btn-purple" onclick="btnClick('Purple')">Purple</button>
705
+ <button class="test-btn btn-cyan" onclick="btnClick('Cyan')">Cyan</button>
706
+ </div>
707
+
708
+ <div class="card">
709
+ <h2>Test Card</h2>
710
+ <p>This is a sample card element. The agent can try to tap on this text or the card itself to test coordinate accuracy.</p>
711
+ </div>
712
+
713
+ <div class="input-group">
714
+ <label>Test Input Field</label>
715
+ <input type="text" placeholder="Tap here to focus...">
716
+ </div>
717
+
718
+ <div class="card">
719
+ <h2>Instructions</h2>
720
+ <p>1. Agent takes screenshot of this page<br>
721
+ 2. Agent identifies element and calculates coordinates<br>
722
+ 3. Agent calls /api/tap-verifier/mark with coordinates<br>
723
+ 4. Marker appears on canvas overlay<br>
724
+ 5. Verify if marker aligns with intended element</p>
725
+ </div>
726
+
727
+ <div class="nav-bar">
728
+ <div class="nav-item" onclick="navClick('Home')">
729
+ <span class="nav-icon">🏠</span>
730
+ Home
731
+ </div>
732
+ <div class="nav-item" onclick="navClick('Search')">
733
+ <span class="nav-icon">🔍</span>
734
+ Search
735
+ </div>
736
+ <div class="nav-item" onclick="navClick('Profile')">
737
+ <span class="nav-icon">👤</span>
738
+ Profile
739
+ </div>
740
+ <div class="nav-item" onclick="navClick('Settings')">
741
+ <span class="nav-icon">⚙️</span>
742
+ Settings
743
+ </div>
744
+ </div>
745
+ </div>
746
+
747
+ <!-- Grid overlay for coordinate reference -->
748
+ <div class="grid-overlay" id="gridOverlay"></div>
749
+ <button class="grid-toggle" onclick="toggleGrid()">Grid</button>
750
+
751
+ <!-- Transparent canvas overlay for markers -->
752
+ <canvas id="markerCanvas"></canvas>
753
+
754
+ <!-- Marker info panel -->
755
+ <div class="marker-panel" id="markerPanel" style="display: none;">
756
+ <h3>Agent Markers</h3>
757
+ <div id="markerList"></div>
758
+ <button class="clear-btn" onclick="clearMarkers()">Clear All</button>
759
+ </div>
760
+
761
+ <script>
762
+ const canvas = document.getElementById('markerCanvas');
763
+ const ctx = canvas.getContext('2d');
764
+ const markerPanel = document.getElementById('markerPanel');
765
+ const markerList = document.getElementById('markerList');
766
+
767
+ let markers = [];
768
+
769
+ // Resize canvas to match window
770
+ function resizeCanvas() {
771
+ canvas.width = window.innerWidth;
772
+ canvas.height = window.innerHeight;
773
+ drawMarkers();
774
+ }
775
+ window.addEventListener('resize', resizeCanvas);
776
+ resizeCanvas();
777
+
778
+ // Click on canvas to add marker
779
+ canvas.addEventListener('click', async (e) => {
780
+ const x = e.clientX;
781
+ const y = e.clientY;
782
+ try {
783
+ await fetch('/api/tap-verifier/mark', {
784
+ method: 'POST',
785
+ headers: { 'Content-Type': 'application/json' },
786
+ body: JSON.stringify({ x, y, label: 'Click', color: '#00ff88' })
787
+ });
788
+ pollMarkers();
789
+ } catch (err) {
790
+ console.error('Failed to add marker:', err);
791
+ }
792
+ });
793
+
794
+ // Button click feedback
795
+ function btnClick(name) {
796
+ console.log('Button clicked:', name);
797
+ }
798
+ function navClick(name) {
799
+ console.log('Nav clicked:', name);
800
+ }
801
+
802
+ // Draw all markers
803
+ function drawMarkers() {
804
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
805
+
806
+ markers.forEach((marker, i) => {
807
+ const color = marker.color || '#ff6b6b';
808
+ const x = marker.x;
809
+ const y = marker.y;
810
+
811
+ // Draw outer ring (dashed)
812
+ ctx.beginPath();
813
+ ctx.arc(x, y, 30, 0, Math.PI * 2);
814
+ ctx.setLineDash([6, 4]);
815
+ ctx.strokeStyle = color;
816
+ ctx.lineWidth = 3;
817
+ ctx.stroke();
818
+ ctx.setLineDash([]);
819
+
820
+ // Draw inner circle
821
+ ctx.beginPath();
822
+ ctx.arc(x, y, 8, 0, Math.PI * 2);
823
+ ctx.fillStyle = color;
824
+ ctx.fill();
825
+
826
+ // Draw crosshair
827
+ ctx.beginPath();
828
+ ctx.moveTo(x - 20, y);
829
+ ctx.lineTo(x + 20, y);
830
+ ctx.moveTo(x, y - 20);
831
+ ctx.lineTo(x, y + 20);
832
+ ctx.strokeStyle = color;
833
+ ctx.lineWidth = 2;
834
+ ctx.stroke();
835
+
836
+ // Draw label
837
+ const label = marker.label || 'Marker ' + (i + 1);
838
+ const text = label + ' (' + x + ', ' + y + ')';
839
+ ctx.font = 'bold 12px monospace';
840
+ const textWidth = ctx.measureText(text).width;
841
+
842
+ ctx.fillStyle = 'rgba(0,0,0,0.8)';
843
+ ctx.fillRect(x + 35, y - 10, textWidth + 10, 22);
844
+ ctx.strokeStyle = color;
845
+ ctx.lineWidth = 1;
846
+ ctx.strokeRect(x + 35, y - 10, textWidth + 10, 22);
847
+
848
+ ctx.fillStyle = color;
849
+ ctx.fillText(text, x + 40, y + 5);
850
+ });
851
+
852
+ updateMarkerPanel();
853
+ }
854
+
855
+ // Update marker panel
856
+ function updateMarkerPanel() {
857
+ if (markers.length === 0) {
858
+ markerPanel.style.display = 'none';
859
+ return;
860
+ }
861
+
862
+ markerPanel.style.display = 'block';
863
+ markerList.innerHTML = markers.map((m, i) =>
864
+ '<div class="marker-info">' + (m.label || 'Marker ' + (i+1)) + ': (' + m.x + ', ' + m.y + ')</div>'
865
+ ).join('');
866
+ }
867
+
868
+ // Poll for markers from server
869
+ async function pollMarkers() {
870
+ try {
871
+ const res = await fetch('/api/tap-verifier/markers');
872
+ const data = await res.json();
873
+ if (data.markers && JSON.stringify(data.markers) !== JSON.stringify(markers)) {
874
+ markers = data.markers;
875
+ drawMarkers();
876
+ }
877
+ } catch (err) {
878
+ // Ignore
879
+ }
880
+ }
881
+
882
+ // Clear markers
883
+ async function clearMarkers() {
884
+ try {
885
+ await fetch('/api/tap-verifier/clear-markers', { method: 'POST' });
886
+ markers = [];
887
+ drawMarkers();
888
+ } catch (err) {
889
+ console.error('Failed to clear:', err);
890
+ }
891
+ }
892
+
893
+ // Grid overlay functionality
894
+ const gridOverlay = document.getElementById('gridOverlay');
895
+ let gridVisible = false;
896
+
897
+ function toggleGrid() {
898
+ gridVisible = !gridVisible;
899
+ if (gridVisible) {
900
+ gridOverlay.classList.add('visible');
901
+ createGrid();
902
+ } else {
903
+ gridOverlay.classList.remove('visible');
904
+ }
905
+ }
906
+
907
+ function createGrid() {
908
+ gridOverlay.innerHTML = '';
909
+ const w = window.innerWidth;
910
+ const h = window.innerHeight;
911
+
912
+ // Calculate button grid boundaries
913
+ // Grid: max-width 400px, centered, padding 20px, gap 16px, 3 columns
914
+ const gridWidth = Math.min(400, w);
915
+ const gridLeft = (w - gridWidth) / 2;
916
+ const gridRight = gridLeft + gridWidth;
917
+ const padding = 20;
918
+ const gap = 16;
919
+ const colWidth = (gridWidth - 2 * padding - 2 * gap) / 3;
920
+
921
+ // Column positions (centers)
922
+ const col1Center = gridLeft + padding + colWidth / 2;
923
+ const col2Center = gridLeft + padding + colWidth + gap + colWidth / 2;
924
+ const col3Center = gridLeft + padding + 2 * colWidth + 2 * gap + colWidth / 2;
925
+
926
+ // Column boundaries
927
+ const col1Left = gridLeft + padding;
928
+ const col1Right = col1Left + colWidth;
929
+ const col2Left = col1Right + gap;
930
+ const col2Right = col2Left + colWidth;
931
+ const col3Left = col2Right + gap;
932
+ const col3Right = col3Left + colWidth;
933
+
934
+ // Draw vertical lines at column boundaries
935
+ const vLines = [
936
+ { x: gridLeft, label: Math.round(gridLeft) },
937
+ { x: col1Left, label: Math.round(col1Left) },
938
+ { x: col1Center, label: Math.round(col1Center) + ' (C1)', isCenter: true },
939
+ { x: col1Right, label: Math.round(col1Right) },
940
+ { x: col2Left, label: Math.round(col2Left) },
941
+ { x: col2Center, label: Math.round(col2Center) + ' (C2)', isCenter: true },
942
+ { x: col2Right, label: Math.round(col2Right) },
943
+ { x: col3Left, label: Math.round(col3Left) },
944
+ { x: col3Center, label: Math.round(col3Center) + ' (C3)', isCenter: true },
945
+ { x: col3Right, label: Math.round(col3Right) },
946
+ { x: gridRight, label: Math.round(gridRight) }
947
+ ];
948
+
949
+ vLines.forEach((line, i) => {
950
+ const div = document.createElement('div');
951
+ div.className = 'grid-line-v';
952
+ div.style.left = line.x + 'px';
953
+ if (line.isCenter) {
954
+ div.style.background = 'rgba(0, 255, 0, 0.8)';
955
+ div.style.width = '2px';
956
+ }
957
+ gridOverlay.appendChild(div);
958
+
959
+ // Label
960
+ const label = document.createElement('div');
961
+ label.className = 'grid-label';
962
+ label.textContent = line.label;
963
+ label.style.left = (line.x + 3) + 'px';
964
+ label.style.top = (70 + (i % 3) * 14) + 'px';
965
+ if (line.isCenter) label.style.color = '#00ff00';
966
+ gridOverlay.appendChild(label);
967
+ });
968
+
969
+ // Horizontal lines every 50px with labels
970
+ for (let y = 0; y <= h; y += 50) {
971
+ const div = document.createElement('div');
972
+ div.className = 'grid-line-h';
973
+ div.style.top = y + 'px';
974
+ if (y % 100 === 0) {
975
+ div.style.background = 'rgba(255, 255, 0, 0.8)';
976
+ }
977
+ gridOverlay.appendChild(div);
978
+
979
+ if (y % 100 === 0) {
980
+ const label = document.createElement('div');
981
+ label.className = 'grid-label';
982
+ label.textContent = 'y=' + y;
983
+ label.style.left = '3px';
984
+ label.style.top = (y + 2) + 'px';
985
+ gridOverlay.appendChild(label);
986
+ }
987
+ }
988
+ }
989
+
990
+ // Recreate grid on resize
991
+ window.addEventListener('resize', () => {
992
+ if (gridVisible) createGrid();
993
+ });
994
+
995
+ // Start polling
996
+ setInterval(pollMarkers, 500);
997
+ pollMarkers();
998
+ </script>
999
+ </body>
1000
+ </html>`;
1001
+ }
484
1002
  function createRequestHandler() {
485
- return (req, res) => {
1003
+ return async (req, res) => {
486
1004
  // Set CORS headers for browser access
487
1005
  res.setHeader("Access-Control-Allow-Origin", "*");
488
- res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
1006
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
489
1007
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
490
1008
  if (req.method === "OPTIONS") {
491
1009
  res.statusCode = 204;
492
1010
  res.end();
493
1011
  return;
494
1012
  }
495
- const url = (req.url ?? "/").split('?')[0]; // Remove query params
1013
+ const fullUrl = req.url ?? "/";
1014
+ const [urlPath, queryString] = fullUrl.split('?');
1015
+ const url = urlPath; // Path without query params
1016
+ const params = new URLSearchParams(queryString || '');
496
1017
  try {
497
1018
  // HTML endpoints
498
1019
  if (url === "/") {
@@ -515,6 +1036,11 @@ function createRequestHandler() {
515
1036
  res.end(renderApps());
516
1037
  return;
517
1038
  }
1039
+ if (url === "/tap-verifier") {
1040
+ res.setHeader("Content-Type", "text/html");
1041
+ res.end(renderTapVerifier());
1042
+ return;
1043
+ }
518
1044
  // JSON API endpoints
519
1045
  res.setHeader("Content-Type", "application/json");
520
1046
  if (url === "/api/logs" || url === "/api/logs/") {
@@ -549,6 +1075,162 @@ function createRequestHandler() {
549
1075
  };
550
1076
  res.end(JSON.stringify(status, null, 2));
551
1077
  }
1078
+ else if (url === "/api/tap-verifier/devices") {
1079
+ const platform = params.get('platform') || 'android';
1080
+ try {
1081
+ if (platform === 'android') {
1082
+ const result = await listAndroidDevices();
1083
+ if (result.success && result.devices) {
1084
+ const devices = result.devices.map(d => ({
1085
+ id: d.id,
1086
+ name: `${d.model || d.id} (${d.status})`
1087
+ }));
1088
+ res.end(JSON.stringify({ devices }));
1089
+ }
1090
+ else {
1091
+ res.end(JSON.stringify({ devices: [], error: result.error }));
1092
+ }
1093
+ }
1094
+ else {
1095
+ const result = await listIOSSimulators();
1096
+ if (result.success && result.simulators) {
1097
+ const devices = result.simulators
1098
+ .filter(s => s.state === 'Booted')
1099
+ .map(s => ({
1100
+ id: s.udid,
1101
+ name: `${s.name} (${s.runtime})`
1102
+ }));
1103
+ res.end(JSON.stringify({ devices }));
1104
+ }
1105
+ else {
1106
+ res.end(JSON.stringify({ devices: [], error: result.error }));
1107
+ }
1108
+ }
1109
+ }
1110
+ catch (err) {
1111
+ res.end(JSON.stringify({ devices: [], error: String(err) }));
1112
+ }
1113
+ }
1114
+ else if (url === "/api/tap-verifier/screen-size") {
1115
+ const platform = params.get('platform') || 'android';
1116
+ const deviceId = params.get('deviceId') || undefined;
1117
+ try {
1118
+ if (platform === 'android') {
1119
+ const result = await androidGetScreenSize(deviceId);
1120
+ if (result.success && result.width && result.height) {
1121
+ res.end(JSON.stringify({ width: result.width, height: result.height }));
1122
+ }
1123
+ else {
1124
+ res.end(JSON.stringify({ error: result.error || 'Failed to get screen size' }));
1125
+ }
1126
+ }
1127
+ else {
1128
+ // iOS doesn't have a direct screen size function, use common sizes
1129
+ // We'll get the actual size from the screenshot
1130
+ res.end(JSON.stringify({ width: 390, height: 844, note: 'Default iPhone size. Load screenshot for actual dimensions.' }));
1131
+ }
1132
+ }
1133
+ catch (err) {
1134
+ res.end(JSON.stringify({ error: String(err) }));
1135
+ }
1136
+ }
1137
+ else if (url === "/api/tap-verifier/screenshot") {
1138
+ const platform = params.get('platform') || 'android';
1139
+ const deviceId = params.get('deviceId') || undefined;
1140
+ try {
1141
+ if (platform === 'android') {
1142
+ const result = await androidScreenshot(deviceId);
1143
+ if (result.success && result.data) {
1144
+ const base64 = result.data.toString('base64');
1145
+ res.end(JSON.stringify({ success: true, image: base64 }));
1146
+ }
1147
+ else {
1148
+ res.end(JSON.stringify({ success: false, error: result.error || 'Failed to take screenshot' }));
1149
+ }
1150
+ }
1151
+ else {
1152
+ const result = await iosScreenshot(undefined, deviceId);
1153
+ if (result.success && result.data) {
1154
+ const base64 = result.data.toString('base64');
1155
+ res.end(JSON.stringify({ success: true, image: base64 }));
1156
+ }
1157
+ else {
1158
+ res.end(JSON.stringify({ success: false, error: result.error || 'Failed to take screenshot' }));
1159
+ }
1160
+ }
1161
+ }
1162
+ catch (err) {
1163
+ res.end(JSON.stringify({ success: false, error: String(err) }));
1164
+ }
1165
+ }
1166
+ else if (url === "/api/tap-verifier/execute" && req.method === "POST") {
1167
+ // Parse POST body
1168
+ let body = '';
1169
+ req.on('data', chunk => { body += chunk; });
1170
+ req.on('end', async () => {
1171
+ try {
1172
+ const data = JSON.parse(body);
1173
+ const { platform, x, y, deviceId } = data;
1174
+ if (typeof x !== 'number' || typeof y !== 'number') {
1175
+ res.end(JSON.stringify({ success: false, error: 'x and y must be numbers' }));
1176
+ return;
1177
+ }
1178
+ if (platform === 'android') {
1179
+ const result = await androidTap(x, y, deviceId);
1180
+ res.end(JSON.stringify({ success: result.success, error: result.error }));
1181
+ }
1182
+ else {
1183
+ const result = await iosTap(x, y, { udid: deviceId });
1184
+ res.end(JSON.stringify({ success: result.success, error: result.error }));
1185
+ }
1186
+ }
1187
+ catch (err) {
1188
+ res.end(JSON.stringify({ success: false, error: String(err) }));
1189
+ }
1190
+ });
1191
+ return; // Important: return here since we're handling the response asynchronously
1192
+ }
1193
+ else if (url === "/api/tap-verifier/mark" && req.method === "POST") {
1194
+ // Add a marker to the tap verifier (agent can mark calculated coordinates)
1195
+ let body = '';
1196
+ req.on('data', chunk => { body += chunk; });
1197
+ req.on('end', () => {
1198
+ try {
1199
+ const data = JSON.parse(body);
1200
+ const { x, y, label, color } = data;
1201
+ if (typeof x !== 'number' || typeof y !== 'number') {
1202
+ res.end(JSON.stringify({ success: false, error: 'x and y must be numbers' }));
1203
+ return;
1204
+ }
1205
+ const marker = {
1206
+ x,
1207
+ y,
1208
+ label: label || undefined,
1209
+ color: color || undefined,
1210
+ timestamp: Date.now()
1211
+ };
1212
+ tapVerifierMarkers.push(marker);
1213
+ res.end(JSON.stringify({
1214
+ success: true,
1215
+ marker,
1216
+ totalMarkers: tapVerifierMarkers.length
1217
+ }));
1218
+ }
1219
+ catch (err) {
1220
+ res.end(JSON.stringify({ success: false, error: String(err) }));
1221
+ }
1222
+ });
1223
+ return;
1224
+ }
1225
+ else if (url === "/api/tap-verifier/markers") {
1226
+ // Get all markers added by the agent
1227
+ res.end(JSON.stringify({ markers: tapVerifierMarkers }));
1228
+ }
1229
+ else if (url === "/api/tap-verifier/clear-markers" && req.method === "POST") {
1230
+ // Clear all agent-added markers
1231
+ tapVerifierMarkers = [];
1232
+ res.end(JSON.stringify({ success: true, message: 'All markers cleared' }));
1233
+ }
552
1234
  else if (url === "/api" || url === "/api/") {
553
1235
  const endpoints = {
554
1236
  message: "React Native AI Debugger - Debug HTTP Server",
@@ -556,14 +1238,22 @@ function createRequestHandler() {
556
1238
  "/": "Dashboard",
557
1239
  "/logs": "Console logs (colored)",
558
1240
  "/network": "Network requests",
559
- "/apps": "Connected apps"
1241
+ "/apps": "Connected apps",
1242
+ "/tap-verifier": "Tap coordinate verification tool"
560
1243
  },
561
1244
  api: {
562
1245
  "/api/status": "Overall server status and buffer sizes",
563
1246
  "/api/logs": "All captured console logs (JSON)",
564
1247
  "/api/network": "All captured network requests (JSON)",
565
1248
  "/api/bundle-errors": "Metro bundle/compilation errors (JSON)",
566
- "/api/apps": "Connected React Native apps (JSON)"
1249
+ "/api/apps": "Connected React Native apps (JSON)",
1250
+ "/api/tap-verifier/devices": "List available devices (query: platform=android|ios)",
1251
+ "/api/tap-verifier/screen-size": "Get device screen size (query: platform, deviceId)",
1252
+ "/api/tap-verifier/screenshot": "Get device screenshot as base64 (query: platform, deviceId)",
1253
+ "/api/tap-verifier/execute": "Execute tap at coordinates (POST: platform, x, y, deviceId)",
1254
+ "/api/tap-verifier/mark": "Add a marker to visualize agent-calculated coordinates (POST: x, y, label?, color?)",
1255
+ "/api/tap-verifier/markers": "Get all agent-added markers (GET)",
1256
+ "/api/tap-verifier/clear-markers": "Clear all agent-added markers (POST)"
567
1257
  }
568
1258
  };
569
1259
  res.end(JSON.stringify(endpoints, null, 2));