mobile-debug-mcp 0.8.0 → 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.
- package/.eslintignore +5 -0
- package/.eslintrc.cjs +18 -0
- package/.github/workflows/.gitkeep +0 -0
- package/.github/workflows/ci.yml +63 -0
- package/README.md +13 -534
- package/dist/android/interact.js +109 -3
- package/dist/android/observe.js +3 -3
- package/dist/android/utils.js +61 -21
- package/dist/ios/interact.js +89 -22
- package/dist/ios/observe.js +4 -4
- package/dist/ios/utils.js +8 -8
- package/dist/server.js +59 -295
- package/dist/tools/app.js +45 -0
- package/dist/tools/devices.js +5 -0
- package/dist/tools/install.js +47 -0
- package/dist/tools/interact.js +89 -0
- package/dist/tools/logs.js +62 -0
- package/dist/tools/observe.js +126 -0
- package/dist/tools/screenshot.js +17 -0
- package/dist/tools/ui.js +57 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/java.js +76 -0
- package/docs/CHANGELOG.md +24 -0
- package/docs/TOOLS.md +272 -0
- package/eslint.config.cjs +36 -0
- package/eslint.config.js +60 -0
- package/package.json +8 -2
- package/src/android/interact.ts +109 -3
- package/src/android/observe.ts +3 -3
- package/src/android/utils.ts +68 -21
- package/src/ios/interact.ts +91 -26
- package/src/ios/observe.ts +4 -4
- package/src/ios/utils.ts +8 -8
- package/src/server.ts +74 -394
- package/src/tools/interact.ts +84 -0
- package/src/tools/observe.ts +132 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/java.ts +69 -0
- package/test/integration/install.integration.ts +64 -0
- package/test/integration/run-install-android.ts +21 -0
- package/test/integration/run-install-ios.ts +21 -0
- package/test/integration/smoke-test.ts +1 -1
- package/test/integration/test-dist.ts +41 -0
- package/test/integration/test-ui-tree.ts +1 -1
- package/test/integration/wait_for_element_real.ts +1 -1
- package/test/unit/detect-java.test.ts +22 -0
- package/test/unit/index.ts +1 -0
- package/test/unit/install.test.ts +76 -0
package/src/ios/utils.ts
CHANGED
|
@@ -125,7 +125,7 @@ export async function getIOSDeviceMetadata(deviceId: string = "booted"): Promise
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
resolve(fallback)
|
|
128
|
-
} catch
|
|
128
|
+
} catch {
|
|
129
129
|
resolve(fallback)
|
|
130
130
|
}
|
|
131
131
|
})
|
|
@@ -169,7 +169,7 @@ export async function listIOSDevices(appId?: string): Promise<DeviceInfo[]> {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
Promise.all(checks).then(() => resolve(out)).catch(() => resolve(out))
|
|
172
|
-
} catch
|
|
172
|
+
} catch {
|
|
173
173
|
resolve([])
|
|
174
174
|
}
|
|
175
175
|
})
|
|
@@ -192,14 +192,14 @@ export function _clearIOSActiveLogStream(sessionId: string) {
|
|
|
192
192
|
iosActiveLogStreams.delete(sessionId)
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
export async function startIOSLogStream(bundleId: string,
|
|
195
|
+
export async function startIOSLogStream(bundleId: string, deviceId: string = 'booted', sessionId: string = 'default') : Promise<{ success: boolean; stream_started?: boolean; error?: string }> {
|
|
196
196
|
try {
|
|
197
197
|
// Build predicate to filter by process or subsystem
|
|
198
198
|
const predicate = `process == "${bundleId}" or subsystem contains "${bundleId}"`
|
|
199
199
|
|
|
200
200
|
// Prevent multiple streams per session
|
|
201
201
|
if (iosActiveLogStreams.has(sessionId)) {
|
|
202
|
-
try { iosActiveLogStreams.get(sessionId)!.proc.kill() } catch
|
|
202
|
+
try { iosActiveLogStreams.get(sessionId)!.proc.kill() } catch {}
|
|
203
203
|
iosActiveLogStreams.delete(sessionId)
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -231,14 +231,14 @@ export async function startIOSLogStream(bundleId: string, level: 'error' | 'warn
|
|
|
231
231
|
}
|
|
232
232
|
})
|
|
233
233
|
|
|
234
|
-
proc.on('close', (
|
|
234
|
+
proc.on('close', () => {
|
|
235
235
|
stream.end()
|
|
236
236
|
iosActiveLogStreams.delete(sessionId)
|
|
237
237
|
})
|
|
238
238
|
|
|
239
239
|
iosActiveLogStreams.set(sessionId, { proc, file })
|
|
240
240
|
return { success: true, stream_started: true }
|
|
241
|
-
} catch
|
|
241
|
+
} catch {
|
|
242
242
|
return { success: false, error: 'log_stream_start_failed' }
|
|
243
243
|
}
|
|
244
244
|
}
|
|
@@ -246,7 +246,7 @@ export async function startIOSLogStream(bundleId: string, level: 'error' | 'warn
|
|
|
246
246
|
export async function stopIOSLogStream(sessionId: string = 'default'): Promise<{ success: boolean }> {
|
|
247
247
|
const entry = iosActiveLogStreams.get(sessionId)
|
|
248
248
|
if (!entry) return { success: true }
|
|
249
|
-
try { entry.proc.kill() } catch
|
|
249
|
+
try { entry.proc.kill() } catch {}
|
|
250
250
|
iosActiveLogStreams.delete(sessionId)
|
|
251
251
|
return { success: true }
|
|
252
252
|
}
|
|
@@ -284,7 +284,7 @@ export async function readIOSLogStreamLines(sessionId: string = 'default', limit
|
|
|
284
284
|
const crashEntry = entries.find(e => e.crash)
|
|
285
285
|
const crash_summary = crashEntry ? { crash_detected: true, exception: crashEntry.exception, sample: crashEntry.message } : { crash_detected: false }
|
|
286
286
|
return { entries, crash_summary }
|
|
287
|
-
} catch
|
|
287
|
+
} catch {
|
|
288
288
|
return { entries: [], crash_summary: { crash_detected: false } }
|
|
289
289
|
}
|
|
290
290
|
}
|
package/src/server.ts
CHANGED
|
@@ -8,32 +8,19 @@ import {
|
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
StartAppResponse,
|
|
11
|
-
DeviceInfo,
|
|
12
11
|
TerminateAppResponse,
|
|
13
12
|
RestartAppResponse,
|
|
14
13
|
ResetAppDataResponse,
|
|
15
|
-
GetUITreeResponse,
|
|
16
|
-
GetCurrentScreenResponse,
|
|
17
|
-
WaitForElementResponse,
|
|
18
|
-
TapResponse,
|
|
19
|
-
SwipeResponse,
|
|
20
|
-
TypeTextResponse,
|
|
21
|
-
PressBackResponse,
|
|
22
14
|
InstallAppResponse
|
|
23
15
|
} from "./types.js"
|
|
24
16
|
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import { iOSInteract } from
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import { startIOSLogStream, readIOSLogStreamLines, stopIOSLogStream } from "./ios/utils.js"
|
|
17
|
+
import { ToolsInteract } from './tools/interact.js'
|
|
18
|
+
import { ToolsObserve } from './tools/observe.js'
|
|
19
|
+
import { AndroidInteract } from './android/interact.js'
|
|
20
|
+
import { iOSInteract } from './ios/interact.js'
|
|
21
|
+
import { AndroidObserve } from './android/observe.js'
|
|
22
|
+
import { iOSObserve } from './ios/observe.js'
|
|
32
23
|
|
|
33
|
-
const androidObserve = new AndroidObserve()
|
|
34
|
-
const androidInteract = new AndroidInteract()
|
|
35
|
-
const iosObserve = new iOSObserve()
|
|
36
|
-
const iosInteract = new iOSInteract()
|
|
37
24
|
|
|
38
25
|
const server = new Server(
|
|
39
26
|
{
|
|
@@ -148,15 +135,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
148
135
|
},
|
|
149
136
|
{
|
|
150
137
|
name: "install_app",
|
|
151
|
-
description: "Install an app on Android
|
|
138
|
+
description: "Install an app on Android or iOS. Accepts a built binary (apk/.ipa/.app) or a project directory to build then install.",
|
|
152
139
|
inputSchema: {
|
|
153
140
|
type: "object",
|
|
154
141
|
properties: {
|
|
155
|
-
platform: { type: "string", enum: ["android", "ios"] },
|
|
156
|
-
appPath: { type: "string", description: "Path to APK
|
|
142
|
+
platform: { type: "string", enum: ["android", "ios"], description: "Optional. If omitted the server will attempt to detect platform from appPath/project files." },
|
|
143
|
+
appPath: { type: "string", description: "Path to APK, .app, .ipa, or project directory" },
|
|
157
144
|
deviceId: { type: "string", description: "Device UDID (iOS) or Serial (Android). Defaults to booted/connected." }
|
|
158
145
|
},
|
|
159
|
-
required: ["
|
|
146
|
+
required: ["appPath"]
|
|
160
147
|
}
|
|
161
148
|
},
|
|
162
149
|
{
|
|
@@ -409,444 +396,137 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
409
396
|
|
|
410
397
|
try {
|
|
411
398
|
if (name === "start_app") {
|
|
412
|
-
const { platform, appId, deviceId } = args as
|
|
413
|
-
|
|
414
|
-
appId: string
|
|
415
|
-
deviceId?: string
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
let appStarted: boolean
|
|
419
|
-
let launchTimeMs: number
|
|
420
|
-
let deviceInfo: DeviceInfo
|
|
421
|
-
|
|
422
|
-
if (platform === "android") {
|
|
423
|
-
const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId })
|
|
424
|
-
const result = await androidInteract.startApp(appId, resolved.id)
|
|
425
|
-
appStarted = result.appStarted
|
|
426
|
-
launchTimeMs = result.launchTimeMs
|
|
427
|
-
deviceInfo = result.device
|
|
428
|
-
} else {
|
|
429
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId })
|
|
430
|
-
const result = await iosInteract.startApp(appId, resolved.id)
|
|
431
|
-
appStarted = result.appStarted
|
|
432
|
-
launchTimeMs = result.launchTimeMs
|
|
433
|
-
deviceInfo = result.device
|
|
434
|
-
}
|
|
435
|
-
|
|
399
|
+
const { platform, appId, deviceId } = args as any
|
|
400
|
+
const res = await (platform === 'android' ? new AndroidInteract().startApp(appId, deviceId) : new iOSInteract().startApp(appId, deviceId))
|
|
436
401
|
const response: StartAppResponse = {
|
|
437
|
-
device:
|
|
438
|
-
appStarted,
|
|
439
|
-
launchTimeMs
|
|
402
|
+
device: res.device,
|
|
403
|
+
appStarted: res.appStarted,
|
|
404
|
+
launchTimeMs: res.launchTimeMs
|
|
440
405
|
}
|
|
441
|
-
|
|
442
406
|
return wrapResponse(response)
|
|
443
407
|
}
|
|
444
408
|
|
|
445
409
|
if (name === "terminate_app") {
|
|
446
|
-
const { platform, appId, deviceId } = args as
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
deviceId?: string
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
let appTerminated: boolean
|
|
453
|
-
let deviceInfo: DeviceInfo
|
|
454
|
-
|
|
455
|
-
if (platform === "android") {
|
|
456
|
-
const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId })
|
|
457
|
-
const result = await androidInteract.terminateApp(appId, resolved.id)
|
|
458
|
-
appTerminated = result.appTerminated
|
|
459
|
-
deviceInfo = result.device
|
|
460
|
-
} else {
|
|
461
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId })
|
|
462
|
-
const result = await iosInteract.terminateApp(appId, resolved.id)
|
|
463
|
-
appTerminated = result.appTerminated
|
|
464
|
-
deviceInfo = result.device
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const response: TerminateAppResponse = {
|
|
468
|
-
device: deviceInfo,
|
|
469
|
-
appTerminated
|
|
470
|
-
}
|
|
471
|
-
|
|
410
|
+
const { platform, appId, deviceId } = args as any
|
|
411
|
+
const res = await (platform === 'android' ? new AndroidInteract().terminateApp(appId, deviceId) : new iOSInteract().terminateApp(appId, deviceId))
|
|
412
|
+
const response: TerminateAppResponse = { device: res.device, appTerminated: res.appTerminated }
|
|
472
413
|
return wrapResponse(response)
|
|
473
414
|
}
|
|
474
415
|
|
|
475
416
|
if (name === "restart_app") {
|
|
476
|
-
const { platform, appId, deviceId } = args as
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
deviceId?: string
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
let appRestarted: boolean
|
|
483
|
-
let launchTimeMs: number
|
|
484
|
-
let deviceInfo: DeviceInfo
|
|
485
|
-
|
|
486
|
-
if (platform === "android") {
|
|
487
|
-
const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId })
|
|
488
|
-
const result = await androidInteract.restartApp(appId, resolved.id)
|
|
489
|
-
appRestarted = result.appRestarted
|
|
490
|
-
launchTimeMs = result.launchTimeMs
|
|
491
|
-
deviceInfo = result.device
|
|
492
|
-
} else {
|
|
493
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId })
|
|
494
|
-
const result = await iosInteract.restartApp(appId, resolved.id)
|
|
495
|
-
appRestarted = result.appRestarted
|
|
496
|
-
launchTimeMs = result.launchTimeMs
|
|
497
|
-
deviceInfo = result.device
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
const response: RestartAppResponse = {
|
|
501
|
-
device: deviceInfo,
|
|
502
|
-
appRestarted,
|
|
503
|
-
launchTimeMs
|
|
504
|
-
}
|
|
505
|
-
|
|
417
|
+
const { platform, appId, deviceId } = args as any
|
|
418
|
+
const res = await (platform === 'android' ? new AndroidInteract().restartApp(appId, deviceId) : new iOSInteract().restartApp(appId, deviceId))
|
|
419
|
+
const response: RestartAppResponse = { device: res.device, appRestarted: res.appRestarted, launchTimeMs: res.launchTimeMs }
|
|
506
420
|
return wrapResponse(response)
|
|
507
421
|
}
|
|
508
422
|
|
|
509
423
|
if (name === "reset_app_data") {
|
|
510
|
-
const { platform, appId, deviceId } = args as
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
deviceId?: string
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
let dataCleared: boolean
|
|
517
|
-
let deviceInfo: DeviceInfo
|
|
518
|
-
|
|
519
|
-
if (platform === "android") {
|
|
520
|
-
const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId })
|
|
521
|
-
const result = await androidInteract.resetAppData(appId, resolved.id)
|
|
522
|
-
dataCleared = result.dataCleared
|
|
523
|
-
deviceInfo = result.device
|
|
524
|
-
} else {
|
|
525
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId })
|
|
526
|
-
const result = await iosInteract.resetAppData(appId, resolved.id)
|
|
527
|
-
dataCleared = result.dataCleared
|
|
528
|
-
deviceInfo = result.device
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const response: ResetAppDataResponse = {
|
|
532
|
-
device: deviceInfo,
|
|
533
|
-
dataCleared
|
|
534
|
-
}
|
|
535
|
-
|
|
424
|
+
const { platform, appId, deviceId } = args as any
|
|
425
|
+
const res = await (platform === 'android' ? new AndroidInteract().resetAppData(appId, deviceId) : new iOSInteract().resetAppData(appId, deviceId))
|
|
426
|
+
const response: ResetAppDataResponse = { device: res.device, dataCleared: res.dataCleared }
|
|
536
427
|
return wrapResponse(response)
|
|
537
428
|
}
|
|
538
429
|
|
|
539
430
|
if (name === "install_app") {
|
|
540
|
-
|
|
541
|
-
platform
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
let errorMsg: string | undefined
|
|
550
|
-
|
|
551
|
-
if (platform === "android") {
|
|
552
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId })
|
|
553
|
-
const result = await androidInteract.installApp(appPath, resolved.id)
|
|
554
|
-
installed = result.installed
|
|
555
|
-
output = (result as any).output
|
|
556
|
-
deviceInfo = result.device
|
|
557
|
-
errorMsg = (result as any).error
|
|
558
|
-
} else {
|
|
559
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', deviceId })
|
|
560
|
-
const result = await iosInteract.installApp(appPath, resolved.id)
|
|
561
|
-
installed = result.installed
|
|
562
|
-
output = (result as any).output
|
|
563
|
-
deviceInfo = result.device
|
|
564
|
-
errorMsg = (result as any).error
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
const response: InstallAppResponse = {
|
|
568
|
-
device: deviceInfo,
|
|
569
|
-
installed,
|
|
570
|
-
output,
|
|
571
|
-
error: errorMsg
|
|
431
|
+
const { platform, appPath, deviceId } = args as any
|
|
432
|
+
const res = await ToolsInteract.installAppHandler({ platform, appPath, deviceId })
|
|
433
|
+
const response: InstallAppResponse = {
|
|
434
|
+
device: res.device,
|
|
435
|
+
installed: res.installed,
|
|
436
|
+
output: (res as any).output,
|
|
437
|
+
error: (res as any).error
|
|
438
|
+
}
|
|
439
|
+
return wrapResponse(response)
|
|
572
440
|
}
|
|
573
441
|
|
|
574
|
-
return wrapResponse(response)
|
|
575
|
-
}
|
|
576
442
|
|
|
577
443
|
if (name === "get_logs") {
|
|
578
|
-
const { platform, appId, deviceId, lines } = args as
|
|
579
|
-
|
|
580
|
-
appId?: string
|
|
581
|
-
deviceId?: string
|
|
582
|
-
lines?: number
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
let logs: string[]
|
|
586
|
-
let deviceInfo: DeviceInfo
|
|
587
|
-
|
|
588
|
-
if (platform === "android") {
|
|
589
|
-
// Resolve an explicit target device when multiple are attached
|
|
590
|
-
const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId })
|
|
591
|
-
deviceInfo = resolved
|
|
592
|
-
const response = await androidObserve.getLogs(appId, lines ?? 200, resolved.id)
|
|
593
|
-
logs = Array.isArray(response.logs) ? response.logs : []
|
|
594
|
-
} else {
|
|
595
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId })
|
|
596
|
-
deviceInfo = resolved
|
|
597
|
-
const response = await iosObserve.getLogs(appId, resolved.id)
|
|
598
|
-
logs = Array.isArray(response.logs) ? response.logs : []
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// Filter crash lines (e.g. lines containing 'FATAL EXCEPTION') for internal or AI use
|
|
602
|
-
const crashLines = logs.filter(line => line.includes('FATAL EXCEPTION'))
|
|
603
|
-
|
|
604
|
-
// Return device metadata plus logs
|
|
444
|
+
const { platform, appId, deviceId, lines } = args as any
|
|
445
|
+
const res = await ToolsObserve.getLogsHandler({ platform, appId, deviceId, lines })
|
|
605
446
|
return {
|
|
606
447
|
content: [
|
|
607
|
-
{
|
|
608
|
-
|
|
609
|
-
text: JSON.stringify({
|
|
610
|
-
device: deviceInfo,
|
|
611
|
-
result: {
|
|
612
|
-
lines: logs.length,
|
|
613
|
-
crashLines: crashLines.length > 0 ? crashLines : undefined
|
|
614
|
-
}
|
|
615
|
-
}, null, 2)
|
|
616
|
-
},
|
|
617
|
-
{
|
|
618
|
-
type: "text",
|
|
619
|
-
text: logs.join("\n")
|
|
620
|
-
}
|
|
448
|
+
{ type: 'text', text: JSON.stringify({ device: res.device, result: { lines: res.logs.length, crashLines: res.crashLines.length > 0 ? res.crashLines : undefined } }, null, 2) },
|
|
449
|
+
{ type: 'text', text: (res.logs || []).join('\n') }
|
|
621
450
|
]
|
|
622
451
|
}
|
|
623
452
|
}
|
|
624
453
|
|
|
625
454
|
if (name === "list_devices") {
|
|
626
|
-
const { platform, appId } = (args || {}) as
|
|
627
|
-
const
|
|
628
|
-
return wrapResponse(
|
|
455
|
+
const { platform, appId } = (args || {}) as any
|
|
456
|
+
const res = await ToolsObserve.listDevicesHandler({ platform, appId })
|
|
457
|
+
return wrapResponse(res)
|
|
629
458
|
}
|
|
630
459
|
|
|
631
460
|
|
|
632
461
|
if (name === "capture_screenshot") {
|
|
633
|
-
const { platform, deviceId } = args as
|
|
634
|
-
|
|
635
|
-
let screenshot: string
|
|
636
|
-
let resolution: { width: number; height: number }
|
|
637
|
-
let deviceInfo: DeviceInfo
|
|
638
|
-
|
|
639
|
-
if (platform === "android") {
|
|
640
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId })
|
|
641
|
-
deviceInfo = resolved
|
|
642
|
-
const result = await androidObserve.captureScreen(resolved.id)
|
|
643
|
-
screenshot = result.screenshot
|
|
644
|
-
resolution = result.resolution
|
|
645
|
-
} else {
|
|
646
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', deviceId })
|
|
647
|
-
deviceInfo = resolved
|
|
648
|
-
const result = await iosObserve.captureScreenshot(resolved.id)
|
|
649
|
-
screenshot = result.screenshot
|
|
650
|
-
resolution = result.resolution
|
|
651
|
-
}
|
|
652
|
-
|
|
462
|
+
const { platform, deviceId } = args as any
|
|
463
|
+
const res = await ToolsObserve.captureScreenshotHandler({ platform, deviceId })
|
|
653
464
|
return {
|
|
654
465
|
content: [
|
|
655
|
-
{
|
|
656
|
-
|
|
657
|
-
text: JSON.stringify({
|
|
658
|
-
device: deviceInfo,
|
|
659
|
-
result: {
|
|
660
|
-
resolution
|
|
661
|
-
}
|
|
662
|
-
}, null, 2)
|
|
663
|
-
},
|
|
664
|
-
{
|
|
665
|
-
type: "image",
|
|
666
|
-
data: screenshot,
|
|
667
|
-
mimeType: "image/png"
|
|
668
|
-
}
|
|
466
|
+
{ type: 'text', text: JSON.stringify({ device: res.device, result: { resolution: (res as any).resolution } }, null, 2) },
|
|
467
|
+
{ type: 'image', data: (res as any).screenshot, mimeType: 'image/png' }
|
|
669
468
|
]
|
|
670
469
|
}
|
|
671
470
|
}
|
|
672
471
|
|
|
673
472
|
if (name === "get_ui_tree") {
|
|
674
|
-
const { platform, deviceId } = args as
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
if (platform === "android") {
|
|
678
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId })
|
|
679
|
-
result = await androidObserve.getUITree(resolved.id)
|
|
680
|
-
} else if (platform === "ios") {
|
|
681
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', deviceId })
|
|
682
|
-
result = await iosObserve.getUITree(resolved.id)
|
|
683
|
-
} else {
|
|
684
|
-
throw new Error(`Platform ${platform} not supported for get_ui_tree`)
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
return wrapResponse(result)
|
|
473
|
+
const { platform, deviceId } = args as any
|
|
474
|
+
const res = await (platform === 'android' ? new AndroidObserve().getUITree(deviceId) : new iOSObserve().getUITree(deviceId))
|
|
475
|
+
return wrapResponse(res)
|
|
688
476
|
}
|
|
689
477
|
|
|
690
478
|
if (name === "get_current_screen") {
|
|
691
|
-
const { deviceId } = (args || {}) as
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
return wrapResponse(result)
|
|
479
|
+
const { deviceId } = (args || {}) as any
|
|
480
|
+
const res = await new AndroidObserve().getCurrentScreen(deviceId)
|
|
481
|
+
return wrapResponse(res)
|
|
695
482
|
}
|
|
696
483
|
|
|
697
484
|
if (name === "wait_for_element") {
|
|
698
|
-
const { platform, text, timeout, deviceId } = (args || {}) as
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
timeout?: number
|
|
702
|
-
deviceId?: string
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
const effectiveTimeout = timeout ?? 10000;
|
|
706
|
-
|
|
707
|
-
let result: WaitForElementResponse;
|
|
708
|
-
if (platform === "android") {
|
|
709
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId })
|
|
710
|
-
result = await androidInteract.waitForElement(text, effectiveTimeout, resolved.id)
|
|
711
|
-
} else {
|
|
712
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', deviceId })
|
|
713
|
-
result = await iosInteract.waitForElement(text, effectiveTimeout, resolved.id)
|
|
714
|
-
}
|
|
715
|
-
return wrapResponse(result)
|
|
485
|
+
const { platform, text, timeout, deviceId } = (args || {}) as any
|
|
486
|
+
const res = await (platform === 'android' ? new AndroidInteract().waitForElement(text, timeout, deviceId) : new iOSInteract().waitForElement(text, timeout, deviceId))
|
|
487
|
+
return wrapResponse(res)
|
|
716
488
|
}
|
|
717
489
|
|
|
718
490
|
if (name === "tap") {
|
|
719
|
-
const { platform, x, y, deviceId } = (args || {}) as
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
y: number
|
|
723
|
-
deviceId?: string
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
const effectivePlatform = platform || "android";
|
|
727
|
-
|
|
728
|
-
// Basic validation
|
|
729
|
-
if (typeof x !== 'number' || typeof y !== 'number') {
|
|
730
|
-
throw new Error("x and y coordinates are required and must be numbers");
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
let result: TapResponse;
|
|
734
|
-
if (effectivePlatform === "android") {
|
|
735
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId })
|
|
736
|
-
result = await androidInteract.tap(x, y, resolved.id)
|
|
737
|
-
} else {
|
|
738
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', deviceId })
|
|
739
|
-
result = await iosInteract.tap(x, y, resolved.id)
|
|
740
|
-
}
|
|
741
|
-
return wrapResponse(result)
|
|
491
|
+
const { platform, x, y, deviceId } = (args || {}) as any
|
|
492
|
+
const res = await (platform === 'android' ? new AndroidInteract().tap(x, y, deviceId) : new iOSInteract().tap(x, y, deviceId))
|
|
493
|
+
return wrapResponse(res)
|
|
742
494
|
}
|
|
743
495
|
|
|
744
496
|
if (name === "swipe") {
|
|
745
|
-
const {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
y1: number
|
|
749
|
-
x2: number
|
|
750
|
-
y2: number
|
|
751
|
-
duration: number
|
|
752
|
-
deviceId?: string
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
const effectivePlatform = platform || "android";
|
|
756
|
-
|
|
757
|
-
if (typeof x1 !== 'number' || typeof y1 !== 'number' || typeof x2 !== 'number' || typeof y2 !== 'number' || typeof duration !== 'number') {
|
|
758
|
-
throw new Error("x1, y1, x2, y2, and duration are required and must be numbers");
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
let result: SwipeResponse;
|
|
762
|
-
if (effectivePlatform === "android") {
|
|
763
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId })
|
|
764
|
-
result = await androidInteract.swipe(x1, y1, x2, y2, duration, resolved.id)
|
|
765
|
-
} else {
|
|
766
|
-
throw new Error(`Platform ${effectivePlatform} not supported for swipe`)
|
|
767
|
-
}
|
|
768
|
-
return wrapResponse(result)
|
|
497
|
+
const { x1, y1, x2, y2, duration, deviceId } = (args || {}) as any
|
|
498
|
+
const res = await new AndroidInteract().swipe(x1, y1, x2, y2, duration, deviceId)
|
|
499
|
+
return wrapResponse(res)
|
|
769
500
|
}
|
|
770
501
|
|
|
771
502
|
if (name === "type_text") {
|
|
772
|
-
const {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
deviceId?: string
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
const effectivePlatform = platform || "android";
|
|
779
|
-
|
|
780
|
-
if (typeof text !== 'string') {
|
|
781
|
-
throw new Error("text is required and must be a string");
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
let result: TypeTextResponse;
|
|
785
|
-
if (effectivePlatform === "android") {
|
|
786
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId })
|
|
787
|
-
result = await androidInteract.typeText(text, resolved.id)
|
|
788
|
-
} else {
|
|
789
|
-
throw new Error(`Platform ${effectivePlatform} not supported for type_text`)
|
|
790
|
-
}
|
|
791
|
-
return wrapResponse(result)
|
|
503
|
+
const { text, deviceId } = (args || {}) as any
|
|
504
|
+
const res = await new AndroidInteract().typeText(text, deviceId)
|
|
505
|
+
return wrapResponse(res)
|
|
792
506
|
}
|
|
793
507
|
|
|
794
508
|
if (name === "press_back") {
|
|
795
|
-
const {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
const effectivePlatform = platform || "android";
|
|
801
|
-
|
|
802
|
-
if (effectivePlatform !== "android") {
|
|
803
|
-
throw new Error(`Platform ${effectivePlatform} not supported for press_back`)
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId })
|
|
807
|
-
const result = await androidInteract.pressBack(resolved.id)
|
|
808
|
-
return wrapResponse(result)
|
|
509
|
+
const { deviceId } = (args || {}) as any
|
|
510
|
+
const res = await new AndroidInteract().pressBack(deviceId)
|
|
511
|
+
return wrapResponse(res)
|
|
809
512
|
}
|
|
810
513
|
|
|
811
514
|
if (name === 'start_log_stream') {
|
|
812
|
-
const { platform, packageName, level, sessionId
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
if (effectivePlatform === 'android') {
|
|
816
|
-
const resolved = await resolveTargetDevice({ platform: 'android', appId: packageName, deviceId })
|
|
817
|
-
const res = await startAndroidLogStream(packageName, level || 'error', resolved.id, sessionId)
|
|
818
|
-
return wrapResponse(res)
|
|
819
|
-
} else {
|
|
820
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', appId: packageName, deviceId })
|
|
821
|
-
const res = await startIOSLogStream(packageName, level || 'error', resolved.id, sessionId)
|
|
822
|
-
return wrapResponse(res)
|
|
823
|
-
}
|
|
515
|
+
const { platform, packageName, level, sessionId, deviceId } = args as any
|
|
516
|
+
const res = await ToolsObserve.startLogStreamHandler({ platform, packageName, level, sessionId, deviceId })
|
|
517
|
+
return wrapResponse(res)
|
|
824
518
|
}
|
|
825
519
|
|
|
826
520
|
if (name === 'read_log_stream') {
|
|
827
|
-
const { platform, sessionId
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
if (effectivePlatform === 'android') {
|
|
831
|
-
const { entries, crash_summary } = await readLogStreamLines(sid, limit ?? 100, since)
|
|
832
|
-
return wrapResponse({ entries, crash_summary })
|
|
833
|
-
} else {
|
|
834
|
-
const { entries, crash_summary } = await readIOSLogStreamLines(sid, limit ?? 100, since)
|
|
835
|
-
return wrapResponse({ entries, crash_summary })
|
|
836
|
-
}
|
|
521
|
+
const { platform, sessionId, limit, since } = args as any
|
|
522
|
+
const res = await ToolsObserve.readLogStreamHandler({ platform, sessionId, limit, since })
|
|
523
|
+
return wrapResponse(res)
|
|
837
524
|
}
|
|
838
525
|
|
|
839
526
|
if (name === 'stop_log_stream') {
|
|
840
|
-
const { platform, sessionId
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
if (effectivePlatform === 'android') {
|
|
844
|
-
const res = await stopAndroidLogStream(sid)
|
|
845
|
-
return wrapResponse(res)
|
|
846
|
-
} else {
|
|
847
|
-
const res = await stopIOSLogStream(sid)
|
|
848
|
-
return wrapResponse(res)
|
|
849
|
-
}
|
|
527
|
+
const { platform, sessionId } = (args || {}) as any
|
|
528
|
+
const res = await ToolsObserve.stopLogStreamHandler({ platform, sessionId })
|
|
529
|
+
return wrapResponse(res)
|
|
850
530
|
}
|
|
851
531
|
} catch (error) {
|
|
852
532
|
return {
|