react-native-ai-debugger 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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +303 -0
  3. package/build/core/android.d.ts +108 -0
  4. package/build/core/android.d.ts.map +1 -0
  5. package/build/core/android.js +686 -0
  6. package/build/core/android.js.map +1 -0
  7. package/build/core/connection.d.ts +12 -0
  8. package/build/core/connection.d.ts.map +1 -0
  9. package/build/core/connection.js +242 -0
  10. package/build/core/connection.js.map +1 -0
  11. package/build/core/executor.d.ts +6 -0
  12. package/build/core/executor.d.ts.map +1 -0
  13. package/build/core/executor.js +112 -0
  14. package/build/core/executor.js.map +1 -0
  15. package/build/core/index.d.ts +10 -0
  16. package/build/core/index.d.ts.map +1 -0
  17. package/build/core/index.js +21 -0
  18. package/build/core/index.js.map +1 -0
  19. package/build/core/ios.d.ts +54 -0
  20. package/build/core/ios.d.ts.map +1 -0
  21. package/build/core/ios.js +393 -0
  22. package/build/core/ios.js.map +1 -0
  23. package/build/core/logs.d.ts +27 -0
  24. package/build/core/logs.d.ts.map +1 -0
  25. package/build/core/logs.js +102 -0
  26. package/build/core/logs.js.map +1 -0
  27. package/build/core/metro.d.ts +8 -0
  28. package/build/core/metro.d.ts.map +1 -0
  29. package/build/core/metro.js +79 -0
  30. package/build/core/metro.js.map +1 -0
  31. package/build/core/network.d.ts +37 -0
  32. package/build/core/network.d.ts.map +1 -0
  33. package/build/core/network.js +210 -0
  34. package/build/core/network.js.map +1 -0
  35. package/build/core/state.d.ts +9 -0
  36. package/build/core/state.d.ts.map +1 -0
  37. package/build/core/state.js +16 -0
  38. package/build/core/state.js.map +1 -0
  39. package/build/core/types.d.ts +68 -0
  40. package/build/core/types.d.ts.map +1 -0
  41. package/build/core/types.js +2 -0
  42. package/build/core/types.js.map +1 -0
  43. package/build/index.d.ts +3 -0
  44. package/build/index.d.ts.map +1 -0
  45. package/build/index.js +951 -0
  46. package/build/index.js.map +1 -0
  47. package/package.json +50 -0
@@ -0,0 +1,686 @@
1
+ import { exec } from "child_process";
2
+ import { promisify } from "util";
3
+ import { existsSync } from "fs";
4
+ import path from "path";
5
+ import os from "os";
6
+ import sharp from "sharp";
7
+ const execAsync = promisify(exec);
8
+ // ADB command timeout in milliseconds
9
+ const ADB_TIMEOUT = 30000;
10
+ /**
11
+ * Check if ADB is available in PATH
12
+ */
13
+ export async function isAdbAvailable() {
14
+ try {
15
+ await execAsync("adb version", { timeout: 5000 });
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ /**
23
+ * List connected Android devices
24
+ */
25
+ export async function listAndroidDevices() {
26
+ try {
27
+ const adbAvailable = await isAdbAvailable();
28
+ if (!adbAvailable) {
29
+ return {
30
+ success: false,
31
+ error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
32
+ };
33
+ }
34
+ const { stdout } = await execAsync("adb devices -l", { timeout: ADB_TIMEOUT });
35
+ const lines = stdout.trim().split("\n");
36
+ // Skip the "List of devices attached" header
37
+ const deviceLines = lines.slice(1).filter((line) => line.trim().length > 0);
38
+ if (deviceLines.length === 0) {
39
+ return {
40
+ success: true,
41
+ result: "No Android devices connected."
42
+ };
43
+ }
44
+ const devices = deviceLines.map((line) => {
45
+ const parts = line.trim().split(/\s+/);
46
+ const id = parts[0];
47
+ const status = parts[1];
48
+ const device = { id, status };
49
+ // Parse additional info like product:xxx model:xxx device:xxx transport_id:xxx
50
+ for (let i = 2; i < parts.length; i++) {
51
+ const [key, value] = parts[i].split(":");
52
+ if (key === "product")
53
+ device.product = value;
54
+ else if (key === "model")
55
+ device.model = value;
56
+ else if (key === "device")
57
+ device.device = value;
58
+ else if (key === "transport_id")
59
+ device.transportId = value;
60
+ }
61
+ return device;
62
+ });
63
+ const formatted = devices
64
+ .map((d) => {
65
+ let info = `${d.id} (${d.status})`;
66
+ if (d.model)
67
+ info += ` - ${d.model.replace(/_/g, " ")}`;
68
+ if (d.product)
69
+ info += ` [${d.product}]`;
70
+ return info;
71
+ })
72
+ .join("\n");
73
+ return {
74
+ success: true,
75
+ result: `Connected Android devices:\n${formatted}`
76
+ };
77
+ }
78
+ catch (error) {
79
+ return {
80
+ success: false,
81
+ error: `Failed to list devices: ${error instanceof Error ? error.message : String(error)}`
82
+ };
83
+ }
84
+ }
85
+ /**
86
+ * Get the first connected Android device ID
87
+ */
88
+ export async function getDefaultAndroidDevice() {
89
+ try {
90
+ const { stdout } = await execAsync("adb devices", { timeout: ADB_TIMEOUT });
91
+ const lines = stdout.trim().split("\n");
92
+ const deviceLines = lines.slice(1).filter((line) => line.trim().length > 0);
93
+ for (const line of deviceLines) {
94
+ const [id, status] = line.trim().split(/\s+/);
95
+ if (status === "device") {
96
+ return id;
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ /**
106
+ * Build device selector for ADB command
107
+ */
108
+ function buildDeviceArg(deviceId) {
109
+ return deviceId ? `-s ${deviceId}` : "";
110
+ }
111
+ /**
112
+ * Take a screenshot from an Android device
113
+ */
114
+ export async function androidScreenshot(outputPath, deviceId) {
115
+ try {
116
+ const adbAvailable = await isAdbAvailable();
117
+ if (!adbAvailable) {
118
+ return {
119
+ success: false,
120
+ error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
121
+ };
122
+ }
123
+ const deviceArg = buildDeviceArg(deviceId);
124
+ const device = deviceId || (await getDefaultAndroidDevice());
125
+ if (!device) {
126
+ return {
127
+ success: false,
128
+ error: "No Android device connected. Connect a device or start an emulator."
129
+ };
130
+ }
131
+ // Generate output path if not provided
132
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
133
+ const finalOutputPath = outputPath || path.join(os.tmpdir(), `android-screenshot-${timestamp}.png`);
134
+ // Capture screenshot on device
135
+ const remotePath = "/sdcard/screenshot-temp.png";
136
+ await execAsync(`adb ${deviceArg} shell screencap -p ${remotePath}`, {
137
+ timeout: ADB_TIMEOUT
138
+ });
139
+ // Pull screenshot to local machine
140
+ await execAsync(`adb ${deviceArg} pull ${remotePath} "${finalOutputPath}"`, {
141
+ timeout: ADB_TIMEOUT
142
+ });
143
+ // Clean up remote file
144
+ await execAsync(`adb ${deviceArg} shell rm ${remotePath}`, {
145
+ timeout: ADB_TIMEOUT
146
+ }).catch(() => {
147
+ // Ignore cleanup errors
148
+ });
149
+ // Resize image if needed (API limit: 2000px max for multi-image requests)
150
+ // Return scale factor so AI can convert image coords to device coords
151
+ const MAX_DIMENSION = 2000;
152
+ const image = sharp(finalOutputPath);
153
+ const metadata = await image.metadata();
154
+ const originalWidth = metadata.width || 0;
155
+ const originalHeight = metadata.height || 0;
156
+ let imageData;
157
+ let scaleFactor = 1;
158
+ if (originalWidth > MAX_DIMENSION || originalHeight > MAX_DIMENSION) {
159
+ // Calculate scale to fit within MAX_DIMENSION
160
+ scaleFactor = Math.max(originalWidth, originalHeight) / MAX_DIMENSION;
161
+ imageData = await image
162
+ .resize(MAX_DIMENSION, MAX_DIMENSION, {
163
+ fit: "inside",
164
+ withoutEnlargement: true
165
+ })
166
+ .png({ compressionLevel: 9 })
167
+ .toBuffer();
168
+ }
169
+ else {
170
+ imageData = await image
171
+ .png({ compressionLevel: 9 })
172
+ .toBuffer();
173
+ }
174
+ return {
175
+ success: true,
176
+ result: finalOutputPath,
177
+ data: imageData,
178
+ scaleFactor,
179
+ originalWidth,
180
+ originalHeight
181
+ };
182
+ }
183
+ catch (error) {
184
+ return {
185
+ success: false,
186
+ error: `Failed to capture screenshot: ${error instanceof Error ? error.message : String(error)}`
187
+ };
188
+ }
189
+ }
190
+ /**
191
+ * Install an APK on an Android device
192
+ */
193
+ export async function androidInstallApp(apkPath, deviceId, options) {
194
+ try {
195
+ const adbAvailable = await isAdbAvailable();
196
+ if (!adbAvailable) {
197
+ return {
198
+ success: false,
199
+ error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
200
+ };
201
+ }
202
+ // Verify APK exists
203
+ if (!existsSync(apkPath)) {
204
+ return {
205
+ success: false,
206
+ error: `APK file not found: ${apkPath}`
207
+ };
208
+ }
209
+ const deviceArg = buildDeviceArg(deviceId);
210
+ const device = deviceId || (await getDefaultAndroidDevice());
211
+ if (!device) {
212
+ return {
213
+ success: false,
214
+ error: "No Android device connected. Connect a device or start an emulator."
215
+ };
216
+ }
217
+ // Build install flags
218
+ const flags = [];
219
+ if (options?.replace)
220
+ flags.push("-r");
221
+ if (options?.grantPermissions)
222
+ flags.push("-g");
223
+ const flagsStr = flags.length > 0 ? flags.join(" ") + " " : "";
224
+ const { stdout, stderr } = await execAsync(`adb ${deviceArg} install ${flagsStr}"${apkPath}"`, { timeout: 120000 } // 2 minute timeout for install
225
+ );
226
+ const output = stdout + stderr;
227
+ if (output.includes("Success")) {
228
+ return {
229
+ success: true,
230
+ result: `Successfully installed ${path.basename(apkPath)}`
231
+ };
232
+ }
233
+ else {
234
+ return {
235
+ success: false,
236
+ error: output.trim() || "Installation failed with unknown error"
237
+ };
238
+ }
239
+ }
240
+ catch (error) {
241
+ return {
242
+ success: false,
243
+ error: `Failed to install app: ${error instanceof Error ? error.message : String(error)}`
244
+ };
245
+ }
246
+ }
247
+ /**
248
+ * Launch an app on an Android device
249
+ */
250
+ export async function androidLaunchApp(packageName, activityName, deviceId) {
251
+ try {
252
+ const adbAvailable = await isAdbAvailable();
253
+ if (!adbAvailable) {
254
+ return {
255
+ success: false,
256
+ error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
257
+ };
258
+ }
259
+ const deviceArg = buildDeviceArg(deviceId);
260
+ const device = deviceId || (await getDefaultAndroidDevice());
261
+ if (!device) {
262
+ return {
263
+ success: false,
264
+ error: "No Android device connected. Connect a device or start an emulator."
265
+ };
266
+ }
267
+ let command;
268
+ if (activityName) {
269
+ // Launch specific activity
270
+ command = `adb ${deviceArg} shell am start -n ${packageName}/${activityName}`;
271
+ }
272
+ else {
273
+ // Launch main/launcher activity
274
+ command = `adb ${deviceArg} shell monkey -p ${packageName} -c android.intent.category.LAUNCHER 1`;
275
+ }
276
+ const { stdout, stderr } = await execAsync(command, { timeout: ADB_TIMEOUT });
277
+ const output = stdout + stderr;
278
+ // Check for errors
279
+ if (output.includes("Error") || output.includes("Exception")) {
280
+ return {
281
+ success: false,
282
+ error: output.trim()
283
+ };
284
+ }
285
+ return {
286
+ success: true,
287
+ result: `Launched ${packageName}${activityName ? `/${activityName}` : ""}`
288
+ };
289
+ }
290
+ catch (error) {
291
+ return {
292
+ success: false,
293
+ error: `Failed to launch app: ${error instanceof Error ? error.message : String(error)}`
294
+ };
295
+ }
296
+ }
297
+ /**
298
+ * Get list of installed packages on the device
299
+ */
300
+ export async function androidListPackages(deviceId, filter) {
301
+ try {
302
+ const adbAvailable = await isAdbAvailable();
303
+ if (!adbAvailable) {
304
+ return {
305
+ success: false,
306
+ error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
307
+ };
308
+ }
309
+ const deviceArg = buildDeviceArg(deviceId);
310
+ const device = deviceId || (await getDefaultAndroidDevice());
311
+ if (!device) {
312
+ return {
313
+ success: false,
314
+ error: "No Android device connected. Connect a device or start an emulator."
315
+ };
316
+ }
317
+ const { stdout } = await execAsync(`adb ${deviceArg} shell pm list packages`, {
318
+ timeout: ADB_TIMEOUT
319
+ });
320
+ let packages = stdout
321
+ .trim()
322
+ .split("\n")
323
+ .map((line) => line.replace("package:", "").trim())
324
+ .filter((pkg) => pkg.length > 0);
325
+ if (filter) {
326
+ const filterLower = filter.toLowerCase();
327
+ packages = packages.filter((pkg) => pkg.toLowerCase().includes(filterLower));
328
+ }
329
+ if (packages.length === 0) {
330
+ return {
331
+ success: true,
332
+ result: filter ? `No packages found matching "${filter}"` : "No packages found"
333
+ };
334
+ }
335
+ return {
336
+ success: true,
337
+ result: `Installed packages${filter ? ` matching "${filter}"` : ""}:\n${packages.join("\n")}`
338
+ };
339
+ }
340
+ catch (error) {
341
+ return {
342
+ success: false,
343
+ error: `Failed to list packages: ${error instanceof Error ? error.message : String(error)}`
344
+ };
345
+ }
346
+ }
347
+ // ============================================================================
348
+ // UI Input Functions (Phase 2)
349
+ // ============================================================================
350
+ /**
351
+ * Common key event codes for Android
352
+ */
353
+ export const ANDROID_KEY_EVENTS = {
354
+ HOME: 3,
355
+ BACK: 4,
356
+ CALL: 5,
357
+ END_CALL: 6,
358
+ VOLUME_UP: 24,
359
+ VOLUME_DOWN: 25,
360
+ POWER: 26,
361
+ CAMERA: 27,
362
+ CLEAR: 28,
363
+ TAB: 61,
364
+ ENTER: 66,
365
+ DEL: 67,
366
+ MENU: 82,
367
+ SEARCH: 84,
368
+ MEDIA_PLAY_PAUSE: 85,
369
+ MEDIA_STOP: 86,
370
+ MEDIA_NEXT: 87,
371
+ MEDIA_PREVIOUS: 88,
372
+ MOVE_HOME: 122,
373
+ MOVE_END: 123,
374
+ APP_SWITCH: 187,
375
+ ESCAPE: 111
376
+ };
377
+ /**
378
+ * Tap at coordinates on an Android device
379
+ */
380
+ export async function androidTap(x, y, deviceId) {
381
+ try {
382
+ const adbAvailable = await isAdbAvailable();
383
+ if (!adbAvailable) {
384
+ return {
385
+ success: false,
386
+ error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
387
+ };
388
+ }
389
+ const deviceArg = buildDeviceArg(deviceId);
390
+ const device = deviceId || (await getDefaultAndroidDevice());
391
+ if (!device) {
392
+ return {
393
+ success: false,
394
+ error: "No Android device connected. Connect a device or start an emulator."
395
+ };
396
+ }
397
+ await execAsync(`adb ${deviceArg} shell input tap ${Math.round(x)} ${Math.round(y)}`, {
398
+ timeout: ADB_TIMEOUT
399
+ });
400
+ return {
401
+ success: true,
402
+ result: `Tapped at (${Math.round(x)}, ${Math.round(y)})`
403
+ };
404
+ }
405
+ catch (error) {
406
+ return {
407
+ success: false,
408
+ error: `Failed to tap: ${error instanceof Error ? error.message : String(error)}`
409
+ };
410
+ }
411
+ }
412
+ /**
413
+ * Long press at coordinates on an Android device
414
+ */
415
+ export async function androidLongPress(x, y, durationMs = 1000, deviceId) {
416
+ try {
417
+ const adbAvailable = await isAdbAvailable();
418
+ if (!adbAvailable) {
419
+ return {
420
+ success: false,
421
+ error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
422
+ };
423
+ }
424
+ const deviceArg = buildDeviceArg(deviceId);
425
+ const device = deviceId || (await getDefaultAndroidDevice());
426
+ if (!device) {
427
+ return {
428
+ success: false,
429
+ error: "No Android device connected. Connect a device or start an emulator."
430
+ };
431
+ }
432
+ // Long press is implemented as a swipe from the same point to the same point
433
+ const xRounded = Math.round(x);
434
+ const yRounded = Math.round(y);
435
+ await execAsync(`adb ${deviceArg} shell input swipe ${xRounded} ${yRounded} ${xRounded} ${yRounded} ${durationMs}`, { timeout: ADB_TIMEOUT + durationMs });
436
+ return {
437
+ success: true,
438
+ result: `Long pressed at (${xRounded}, ${yRounded}) for ${durationMs}ms`
439
+ };
440
+ }
441
+ catch (error) {
442
+ return {
443
+ success: false,
444
+ error: `Failed to long press: ${error instanceof Error ? error.message : String(error)}`
445
+ };
446
+ }
447
+ }
448
+ /**
449
+ * Swipe on an Android device
450
+ */
451
+ export async function androidSwipe(startX, startY, endX, endY, durationMs = 300, deviceId) {
452
+ try {
453
+ const adbAvailable = await isAdbAvailable();
454
+ if (!adbAvailable) {
455
+ return {
456
+ success: false,
457
+ error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
458
+ };
459
+ }
460
+ const deviceArg = buildDeviceArg(deviceId);
461
+ const device = deviceId || (await getDefaultAndroidDevice());
462
+ if (!device) {
463
+ return {
464
+ success: false,
465
+ error: "No Android device connected. Connect a device or start an emulator."
466
+ };
467
+ }
468
+ const x1 = Math.round(startX);
469
+ const y1 = Math.round(startY);
470
+ const x2 = Math.round(endX);
471
+ const y2 = Math.round(endY);
472
+ await execAsync(`adb ${deviceArg} shell input swipe ${x1} ${y1} ${x2} ${y2} ${durationMs}`, { timeout: ADB_TIMEOUT + durationMs });
473
+ return {
474
+ success: true,
475
+ result: `Swiped from (${x1}, ${y1}) to (${x2}, ${y2}) in ${durationMs}ms`
476
+ };
477
+ }
478
+ catch (error) {
479
+ return {
480
+ success: false,
481
+ error: `Failed to swipe: ${error instanceof Error ? error.message : String(error)}`
482
+ };
483
+ }
484
+ }
485
+ /**
486
+ * Input text on an Android device
487
+ *
488
+ * ADB input text has limitations with special characters.
489
+ * This function handles escaping properly for URLs, emails, and special strings.
490
+ */
491
+ export async function androidInputText(text, deviceId) {
492
+ try {
493
+ const adbAvailable = await isAdbAvailable();
494
+ if (!adbAvailable) {
495
+ return {
496
+ success: false,
497
+ error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
498
+ };
499
+ }
500
+ const deviceArg = buildDeviceArg(deviceId);
501
+ const device = deviceId || (await getDefaultAndroidDevice());
502
+ if (!device) {
503
+ return {
504
+ success: false,
505
+ error: "No Android device connected. Connect a device or start an emulator."
506
+ };
507
+ }
508
+ // For complex strings with special characters, type character by character
509
+ // using key events for reliability
510
+ const hasComplexChars = /[/:?=&#@%+]/.test(text);
511
+ if (hasComplexChars) {
512
+ // Use character-by-character input for strings with special chars
513
+ // This is slower but more reliable for URLs, emails, etc.
514
+ for (const char of text) {
515
+ let keyCmd;
516
+ // Map special characters to their escaped form or use direct input
517
+ switch (char) {
518
+ case " ":
519
+ keyCmd = `adb ${deviceArg} shell input text "%s"`;
520
+ break;
521
+ case "'":
522
+ // Single quote needs special handling
523
+ keyCmd = `adb ${deviceArg} shell input text "\\'"`;
524
+ break;
525
+ case '"':
526
+ keyCmd = `adb ${deviceArg} shell input text '\\"'`;
527
+ break;
528
+ case "\\":
529
+ keyCmd = `adb ${deviceArg} shell input text "\\\\"`;
530
+ break;
531
+ case "&":
532
+ keyCmd = `adb ${deviceArg} shell input text "\\&"`;
533
+ break;
534
+ case "|":
535
+ keyCmd = `adb ${deviceArg} shell input text "\\|"`;
536
+ break;
537
+ case ";":
538
+ keyCmd = `adb ${deviceArg} shell input text "\\;"`;
539
+ break;
540
+ case "<":
541
+ keyCmd = `adb ${deviceArg} shell input text "\\<"`;
542
+ break;
543
+ case ">":
544
+ keyCmd = `adb ${deviceArg} shell input text "\\>"`;
545
+ break;
546
+ case "(":
547
+ keyCmd = `adb ${deviceArg} shell input text "\\("`;
548
+ break;
549
+ case ")":
550
+ keyCmd = `adb ${deviceArg} shell input text "\\)"`;
551
+ break;
552
+ case "$":
553
+ keyCmd = `adb ${deviceArg} shell input text "\\$"`;
554
+ break;
555
+ case "`":
556
+ keyCmd = `adb ${deviceArg} shell input text "\\\`"`;
557
+ break;
558
+ default:
559
+ // For most characters, wrap in single quotes to prevent shell interpretation
560
+ // Single quotes preserve literal meaning of all characters except single quote itself
561
+ keyCmd = `adb ${deviceArg} shell input text '${char}'`;
562
+ }
563
+ await execAsync(keyCmd, { timeout: 5000 });
564
+ }
565
+ return {
566
+ success: true,
567
+ result: `Typed: "${text}"`
568
+ };
569
+ }
570
+ // For simple alphanumeric strings, use the faster bulk input
571
+ // Escape basic special characters
572
+ const escapedText = text
573
+ .replace(/\\/g, "\\\\")
574
+ .replace(/"/g, '\\"')
575
+ .replace(/'/g, "\\'")
576
+ .replace(/`/g, "\\`")
577
+ .replace(/\$/g, "\\$")
578
+ .replace(/ /g, "%s");
579
+ await execAsync(`adb ${deviceArg} shell input text "${escapedText}"`, {
580
+ timeout: ADB_TIMEOUT
581
+ });
582
+ return {
583
+ success: true,
584
+ result: `Typed: "${text}"`
585
+ };
586
+ }
587
+ catch (error) {
588
+ return {
589
+ success: false,
590
+ error: `Failed to input text: ${error instanceof Error ? error.message : String(error)}`
591
+ };
592
+ }
593
+ }
594
+ /**
595
+ * Send a key event to an Android device
596
+ */
597
+ export async function androidKeyEvent(keyCode, deviceId) {
598
+ try {
599
+ const adbAvailable = await isAdbAvailable();
600
+ if (!adbAvailable) {
601
+ return {
602
+ success: false,
603
+ error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
604
+ };
605
+ }
606
+ const deviceArg = buildDeviceArg(deviceId);
607
+ const device = deviceId || (await getDefaultAndroidDevice());
608
+ if (!device) {
609
+ return {
610
+ success: false,
611
+ error: "No Android device connected. Connect a device or start an emulator."
612
+ };
613
+ }
614
+ // Resolve key code from name if needed
615
+ const resolvedKeyCode = typeof keyCode === "string" ? ANDROID_KEY_EVENTS[keyCode] : keyCode;
616
+ if (resolvedKeyCode === undefined) {
617
+ return {
618
+ success: false,
619
+ error: `Invalid key code: ${keyCode}`
620
+ };
621
+ }
622
+ await execAsync(`adb ${deviceArg} shell input keyevent ${resolvedKeyCode}`, {
623
+ timeout: ADB_TIMEOUT
624
+ });
625
+ // Get key name for display
626
+ const keyName = typeof keyCode === "string"
627
+ ? keyCode
628
+ : Object.entries(ANDROID_KEY_EVENTS).find(([_, v]) => v === keyCode)?.[0] ||
629
+ `keycode ${keyCode}`;
630
+ return {
631
+ success: true,
632
+ result: `Sent key event: ${keyName}`
633
+ };
634
+ }
635
+ catch (error) {
636
+ return {
637
+ success: false,
638
+ error: `Failed to send key event: ${error instanceof Error ? error.message : String(error)}`
639
+ };
640
+ }
641
+ }
642
+ /**
643
+ * Get device screen size
644
+ */
645
+ export async function androidGetScreenSize(deviceId) {
646
+ try {
647
+ const adbAvailable = await isAdbAvailable();
648
+ if (!adbAvailable) {
649
+ return {
650
+ success: false,
651
+ error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
652
+ };
653
+ }
654
+ const deviceArg = buildDeviceArg(deviceId);
655
+ const device = deviceId || (await getDefaultAndroidDevice());
656
+ if (!device) {
657
+ return {
658
+ success: false,
659
+ error: "No Android device connected. Connect a device or start an emulator."
660
+ };
661
+ }
662
+ const { stdout } = await execAsync(`adb ${deviceArg} shell wm size`, {
663
+ timeout: ADB_TIMEOUT
664
+ });
665
+ // Parse output like "Physical size: 1080x1920"
666
+ const match = stdout.match(/(\d+)x(\d+)/);
667
+ if (match) {
668
+ return {
669
+ success: true,
670
+ width: parseInt(match[1], 10),
671
+ height: parseInt(match[2], 10)
672
+ };
673
+ }
674
+ return {
675
+ success: false,
676
+ error: `Could not parse screen size from: ${stdout.trim()}`
677
+ };
678
+ }
679
+ catch (error) {
680
+ return {
681
+ success: false,
682
+ error: `Failed to get screen size: ${error instanceof Error ? error.message : String(error)}`
683
+ };
684
+ }
685
+ }
686
+ //# sourceMappingURL=android.js.map