mobile-debug-mcp 0.24.6 → 0.24.7
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/.github/workflows/ci.yml +1 -3
- package/README.md +7 -0
- package/dist/manage/android.js +9 -5
- package/dist/manage/index.js +37 -23
- package/dist/manage/ios.js +12 -15
- package/dist/server/common.js +46 -0
- package/dist/server/tool-handlers.js +120 -33
- package/dist/server-core.js +1 -1
- package/dist/utils/android/utils.js +17 -5
- package/dist/utils/cli/idb/check-idb.js +1 -1
- package/docs/CHANGELOG.md +15 -10
- package/eslint.config.js +2 -47
- package/package.json +7 -6
- package/src/manage/android.ts +22 -11
- package/src/manage/index.ts +37 -16
- package/src/manage/ios.ts +28 -15
- package/src/server/common.ts +50 -0
- package/src/server/tool-handlers.ts +136 -32
- package/src/server-core.ts +1 -1
- package/src/utils/android/utils.ts +18 -7
- package/src/utils/cli/idb/check-idb.ts +1 -1
- package/test/device/automated/observe/capture_screenshot.android.smoke.ts +1 -1
- package/test/device/automated/observe/capture_screenshot.ios.smoke.ts +1 -1
- package/test/device/automated/observe/get_logs.android.smoke.ts +1 -1
- package/test/device/automated/observe/get_logs.ios.smoke.ts +1 -1
- package/test/device/automated/observe/get_ui_tree.android.smoke.ts +1 -1
- package/test/device/automated/observe/get_ui_tree.ios.smoke.ts +1 -1
- package/test/device/manual/interact/app_lifecycle.manual.ts +3 -3
- package/test/device/manual/observe/capture_screenshot.manual.ts +2 -2
- package/test/device/manual/observe/get_logs.manual.ts +2 -2
- package/test/device/manual/observe/get_ui_tree.manual.ts +2 -2
- package/test/device/manual/observe/logstream.manual.ts +1 -1
- package/test/device/manual/observe/screen_fingerprint.manual.ts +2 -2
- package/test/unit/manage/scoped_env.test.ts +137 -0
- package/test/unit/server/capture_screenshot.test.ts +17 -0
- package/test/unit/server/common.test.ts +18 -0
- package/test/unit/server/contract.test.ts +3 -0
- package/test/unit/server/get_logs.test.ts +17 -0
- package/test/unit/server/get_network_activity.test.ts +17 -0
- package/test/unit/server/get_ui_tree.test.ts +17 -0
- package/test/unit/server/response_shapes.test.ts +18 -0
- package/test/unit/server/start_log_stream.test.ts +37 -0
- package/.eslintignore +0 -5
- package/.eslintrc.cjs +0 -18
- package/eslint.config.cjs +0 -36
|
@@ -12,16 +12,35 @@ import { getSystemStatus } from '../system/index.js'
|
|
|
12
12
|
import {
|
|
13
13
|
buildActionExecutionResult,
|
|
14
14
|
captureActionFingerprint,
|
|
15
|
+
getArrayArg,
|
|
16
|
+
getBooleanArg,
|
|
17
|
+
getNumberArg,
|
|
18
|
+
getObjectArg,
|
|
19
|
+
getStringArg,
|
|
15
20
|
inferGenericFailure,
|
|
16
21
|
inferScrollFailure,
|
|
22
|
+
requireBooleanArg,
|
|
23
|
+
requireNumberArg,
|
|
24
|
+
requireObjectArg,
|
|
25
|
+
requireStringArg,
|
|
17
26
|
ToolCallArgs,
|
|
18
27
|
ToolHandler,
|
|
19
28
|
wrapResponse,
|
|
20
29
|
wrapToolError
|
|
21
30
|
} from './common.js'
|
|
22
31
|
|
|
32
|
+
type PlatformArg = 'android' | 'ios'
|
|
33
|
+
type ProjectTypeArg = 'native' | 'kmp' | 'react-native' | 'flutter'
|
|
34
|
+
type ExpectElementSelectorArg = { text?: string, resource_id?: string, accessibility_id?: string, contains?: boolean }
|
|
35
|
+
type WaitForUiMatchArg = { index?: number }
|
|
36
|
+
type WaitForUiRetryArg = { max_attempts?: number, backoff_ms?: number }
|
|
37
|
+
type ScrollSelectorArg = { text?: string, resourceId?: string, contentDesc?: string, className?: string }
|
|
38
|
+
type ClassifyNetworkRequestArg = { endpoint: string, status: 'success' | 'failure' | 'retryable' }
|
|
39
|
+
|
|
23
40
|
async function handleStartApp(args: ToolCallArgs) {
|
|
24
|
-
const
|
|
41
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
42
|
+
const appId = requireStringArg(args, 'appId')
|
|
43
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
25
44
|
const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
|
|
26
45
|
ToolsNetwork.notifyActionStart()
|
|
27
46
|
const res = await (platform === 'android' ? new AndroidManage().startApp(appId, deviceId) : new iOSManage().startApp(appId, deviceId))
|
|
@@ -45,14 +64,18 @@ async function handleStartApp(args: ToolCallArgs) {
|
|
|
45
64
|
}
|
|
46
65
|
|
|
47
66
|
async function handleTerminateApp(args: ToolCallArgs) {
|
|
48
|
-
const
|
|
67
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
68
|
+
const appId = requireStringArg(args, 'appId')
|
|
69
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
49
70
|
const res = await (platform === 'android' ? new AndroidManage().terminateApp(appId, deviceId) : new iOSManage().terminateApp(appId, deviceId))
|
|
50
71
|
const response: TerminateAppResponse = { device: res.device, appTerminated: res.appTerminated }
|
|
51
72
|
return wrapResponse(response)
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
async function handleRestartApp(args: ToolCallArgs) {
|
|
55
|
-
const
|
|
76
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
77
|
+
const appId = requireStringArg(args, 'appId')
|
|
78
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
56
79
|
const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
|
|
57
80
|
ToolsNetwork.notifyActionStart()
|
|
58
81
|
const res = await (platform === 'android' ? new AndroidManage().restartApp(appId, deviceId) : new iOSManage().restartApp(appId, deviceId))
|
|
@@ -77,14 +100,19 @@ async function handleRestartApp(args: ToolCallArgs) {
|
|
|
77
100
|
}
|
|
78
101
|
|
|
79
102
|
async function handleResetAppData(args: ToolCallArgs) {
|
|
80
|
-
const
|
|
103
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
104
|
+
const appId = requireStringArg(args, 'appId')
|
|
105
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
81
106
|
const res = await (platform === 'android' ? new AndroidManage().resetAppData(appId, deviceId) : new iOSManage().resetAppData(appId, deviceId))
|
|
82
107
|
const response: ResetAppDataResponse = { device: res.device, dataCleared: res.dataCleared }
|
|
83
108
|
return wrapResponse(response)
|
|
84
109
|
}
|
|
85
110
|
|
|
86
111
|
async function handleInstallApp(args: ToolCallArgs) {
|
|
87
|
-
const
|
|
112
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
113
|
+
const projectType = requireStringArg(args, 'projectType') as ProjectTypeArg
|
|
114
|
+
const appPath = requireStringArg(args, 'appPath')
|
|
115
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
88
116
|
const res = await ToolsManage.installAppHandler({ platform, appPath, deviceId, projectType })
|
|
89
117
|
const response: InstallAppResponse = {
|
|
90
118
|
device: res.device,
|
|
@@ -96,13 +124,20 @@ async function handleInstallApp(args: ToolCallArgs) {
|
|
|
96
124
|
}
|
|
97
125
|
|
|
98
126
|
async function handleBuildApp(args: ToolCallArgs) {
|
|
99
|
-
const
|
|
127
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
128
|
+
const projectType = requireStringArg(args, 'projectType') as ProjectTypeArg
|
|
129
|
+
const projectPath = requireStringArg(args, 'projectPath')
|
|
130
|
+
const variant = getStringArg(args, 'variant')
|
|
100
131
|
const res = await ToolsManage.buildAppHandler({ platform, projectPath, variant, projectType })
|
|
101
132
|
return wrapResponse(res)
|
|
102
133
|
}
|
|
103
134
|
|
|
104
135
|
async function handleBuildAndInstall(args: ToolCallArgs) {
|
|
105
|
-
const
|
|
136
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
137
|
+
const projectType = requireStringArg(args, 'projectType') as ProjectTypeArg
|
|
138
|
+
const projectPath = requireStringArg(args, 'projectPath')
|
|
139
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
140
|
+
const timeout = getNumberArg(args, 'timeout')
|
|
106
141
|
const res = await ToolsManage.buildAndInstallHandler({ platform, projectPath, deviceId, timeout, projectType })
|
|
107
142
|
return {
|
|
108
143
|
content: [
|
|
@@ -113,7 +148,16 @@ async function handleBuildAndInstall(args: ToolCallArgs) {
|
|
|
113
148
|
}
|
|
114
149
|
|
|
115
150
|
async function handleGetLogs(args: ToolCallArgs) {
|
|
116
|
-
const
|
|
151
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
152
|
+
const appId = getStringArg(args, 'appId')
|
|
153
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
154
|
+
const pid = getNumberArg(args, 'pid')
|
|
155
|
+
const tag = getStringArg(args, 'tag')
|
|
156
|
+
const level = getStringArg(args, 'level')
|
|
157
|
+
const contains = getStringArg(args, 'contains')
|
|
158
|
+
const since_seconds = getNumberArg(args, 'since_seconds')
|
|
159
|
+
const limit = getNumberArg(args, 'limit')
|
|
160
|
+
const lines = getNumberArg(args, 'lines')
|
|
117
161
|
const res = await ToolsObserve.getLogsHandler({ platform, appId, deviceId, pid, tag, level, contains, since_seconds, limit, lines })
|
|
118
162
|
const filtered = !!(pid || tag || level || contains || since_seconds || appId)
|
|
119
163
|
return {
|
|
@@ -125,7 +169,8 @@ async function handleGetLogs(args: ToolCallArgs) {
|
|
|
125
169
|
}
|
|
126
170
|
|
|
127
171
|
async function handleListDevices(args: ToolCallArgs) {
|
|
128
|
-
const
|
|
172
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
173
|
+
const appId = getStringArg(args, 'appId')
|
|
129
174
|
const res = await ToolsManage.listDevicesHandler({ platform, appId })
|
|
130
175
|
return wrapResponse(res)
|
|
131
176
|
}
|
|
@@ -136,7 +181,8 @@ async function handleGetSystemStatus() {
|
|
|
136
181
|
}
|
|
137
182
|
|
|
138
183
|
async function handleCaptureScreenshot(args: ToolCallArgs) {
|
|
139
|
-
const
|
|
184
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
185
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
140
186
|
const res = await ToolsObserve.captureScreenshotHandler({ platform, deviceId })
|
|
141
187
|
const mime = (res as any).screenshot_mime || 'image/png'
|
|
142
188
|
const content: Array<{ type: 'text' | 'image'; text?: string; data?: string; mimeType?: string }> = [
|
|
@@ -151,61 +197,95 @@ async function handleCaptureScreenshot(args: ToolCallArgs) {
|
|
|
151
197
|
}
|
|
152
198
|
|
|
153
199
|
async function handleCaptureDebugSnapshot(args: ToolCallArgs) {
|
|
154
|
-
const
|
|
200
|
+
const reason = getStringArg(args, 'reason')
|
|
201
|
+
const includeLogs = getBooleanArg(args, 'includeLogs')
|
|
202
|
+
const logLines = getNumberArg(args, 'logLines')
|
|
203
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
204
|
+
const appId = getStringArg(args, 'appId')
|
|
205
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
206
|
+
const sessionId = getStringArg(args, 'sessionId')
|
|
155
207
|
const res = await ToolsObserve.captureDebugSnapshotHandler({ reason, includeLogs, logLines, platform, appId, deviceId, sessionId })
|
|
156
208
|
return wrapResponse(res)
|
|
157
209
|
}
|
|
158
210
|
|
|
159
211
|
async function handleGetUITree(args: ToolCallArgs) {
|
|
160
|
-
const
|
|
212
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
213
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
161
214
|
const res = await ToolsObserve.getUITreeHandler({ platform, deviceId })
|
|
162
215
|
return wrapResponse(res)
|
|
163
216
|
}
|
|
164
217
|
|
|
165
218
|
async function handleGetCurrentScreen(args: ToolCallArgs) {
|
|
166
|
-
const
|
|
219
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
167
220
|
const res = await ToolsObserve.getCurrentScreenHandler({ deviceId })
|
|
168
221
|
return wrapResponse(res)
|
|
169
222
|
}
|
|
170
223
|
|
|
171
224
|
async function handleGetScreenFingerprint(args: ToolCallArgs) {
|
|
172
|
-
const
|
|
225
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
226
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
173
227
|
const res = await ToolsObserve.getScreenFingerprintHandler({ platform, deviceId })
|
|
174
228
|
return wrapResponse(res)
|
|
175
229
|
}
|
|
176
230
|
|
|
177
231
|
async function handleWaitForScreenChange(args: ToolCallArgs) {
|
|
178
|
-
const
|
|
232
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
233
|
+
const previousFingerprint = requireStringArg(args, 'previousFingerprint')
|
|
234
|
+
const timeoutMs = getNumberArg(args, 'timeoutMs')
|
|
235
|
+
const pollIntervalMs = getNumberArg(args, 'pollIntervalMs')
|
|
236
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
179
237
|
const res = await ToolsInteract.waitForScreenChangeHandler({ platform, previousFingerprint, timeoutMs, pollIntervalMs, deviceId })
|
|
180
238
|
return wrapResponse(res)
|
|
181
239
|
}
|
|
182
240
|
|
|
183
241
|
async function handleExpectScreen(args: ToolCallArgs) {
|
|
184
|
-
const
|
|
242
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
243
|
+
const fingerprint = getStringArg(args, 'fingerprint')
|
|
244
|
+
const screen = getStringArg(args, 'screen')
|
|
245
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
185
246
|
const res = await ToolsInteract.expectScreenHandler({ platform, fingerprint, screen, deviceId })
|
|
186
247
|
return wrapResponse(res)
|
|
187
248
|
}
|
|
188
249
|
|
|
189
250
|
async function handleExpectElementVisible(args: ToolCallArgs) {
|
|
190
|
-
const
|
|
251
|
+
const selector = requireObjectArg<ExpectElementSelectorArg>(args, 'selector')
|
|
252
|
+
const element_id = getStringArg(args, 'element_id')
|
|
253
|
+
const timeout_ms = getNumberArg(args, 'timeout_ms')
|
|
254
|
+
const poll_interval_ms = getNumberArg(args, 'poll_interval_ms')
|
|
255
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
256
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
191
257
|
const res = await ToolsInteract.expectElementVisibleHandler({ selector, element_id, timeout_ms, poll_interval_ms, platform, deviceId })
|
|
192
258
|
return wrapResponse(res)
|
|
193
259
|
}
|
|
194
260
|
|
|
195
261
|
async function handleWaitForUI(args: ToolCallArgs) {
|
|
196
|
-
const
|
|
262
|
+
const selector = getObjectArg<ExpectElementSelectorArg>(args, 'selector')
|
|
263
|
+
const condition = (getStringArg(args, 'condition') as 'exists' | 'not_exists' | 'visible' | 'clickable' | undefined) ?? 'exists'
|
|
264
|
+
const timeout_ms = getNumberArg(args, 'timeout_ms') ?? 60000
|
|
265
|
+
const poll_interval_ms = getNumberArg(args, 'poll_interval_ms') ?? 300
|
|
266
|
+
const match = getObjectArg<WaitForUiMatchArg>(args, 'match')
|
|
267
|
+
const retry = getObjectArg<WaitForUiRetryArg>(args, 'retry')
|
|
268
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
269
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
197
270
|
const res = await ToolsInteract.waitForUIHandler({ selector, condition, timeout_ms, poll_interval_ms, match, retry, platform, deviceId })
|
|
198
271
|
return wrapResponse(res)
|
|
199
272
|
}
|
|
200
273
|
|
|
201
274
|
async function handleFindElement(args: ToolCallArgs) {
|
|
202
|
-
const
|
|
275
|
+
const query = requireStringArg(args, 'query')
|
|
276
|
+
const exact = getBooleanArg(args, 'exact') ?? false
|
|
277
|
+
const timeoutMs = getNumberArg(args, 'timeoutMs') ?? 3000
|
|
278
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
279
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
203
280
|
const res = await ToolsInteract.findElementHandler({ query, exact, timeoutMs, platform, deviceId })
|
|
204
281
|
return wrapResponse(res)
|
|
205
282
|
}
|
|
206
283
|
|
|
207
284
|
async function handleTap(args: ToolCallArgs) {
|
|
208
|
-
const
|
|
285
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
286
|
+
const x = requireNumberArg(args, 'x')
|
|
287
|
+
const y = requireNumberArg(args, 'y')
|
|
288
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
209
289
|
const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
|
|
210
290
|
ToolsNetwork.notifyActionStart()
|
|
211
291
|
const res = await ToolsInteract.tapHandler({ platform, x, y, deviceId })
|
|
@@ -221,14 +301,20 @@ async function handleTap(args: ToolCallArgs) {
|
|
|
221
301
|
}
|
|
222
302
|
|
|
223
303
|
async function handleTapElement(args: ToolCallArgs) {
|
|
224
|
-
const
|
|
304
|
+
const elementId = requireStringArg(args, 'elementId')
|
|
225
305
|
ToolsNetwork.notifyActionStart()
|
|
226
306
|
const res = await ToolsInteract.tapElementHandler({ elementId })
|
|
227
307
|
return wrapResponse(res)
|
|
228
308
|
}
|
|
229
309
|
|
|
230
310
|
async function handleSwipe(args: ToolCallArgs) {
|
|
231
|
-
const
|
|
311
|
+
const platform = (getStringArg(args, 'platform') as PlatformArg | undefined) ?? 'android'
|
|
312
|
+
const x1 = requireNumberArg(args, 'x1')
|
|
313
|
+
const y1 = requireNumberArg(args, 'y1')
|
|
314
|
+
const x2 = requireNumberArg(args, 'x2')
|
|
315
|
+
const y2 = requireNumberArg(args, 'y2')
|
|
316
|
+
const duration = requireNumberArg(args, 'duration')
|
|
317
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
232
318
|
const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
|
|
233
319
|
ToolsNetwork.notifyActionStart()
|
|
234
320
|
const res = await ToolsInteract.swipeHandler({ platform, x1, y1, x2, y2, duration, deviceId })
|
|
@@ -244,14 +330,19 @@ async function handleSwipe(args: ToolCallArgs) {
|
|
|
244
330
|
}
|
|
245
331
|
|
|
246
332
|
async function handleScrollToElement(args: ToolCallArgs) {
|
|
247
|
-
const
|
|
333
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
334
|
+
const selector = requireObjectArg<ScrollSelectorArg>(args, 'selector')
|
|
335
|
+
const direction = getStringArg(args, 'direction') as 'down' | 'up' | undefined
|
|
336
|
+
const maxScrolls = getNumberArg(args, 'maxScrolls')
|
|
337
|
+
const scrollAmount = getNumberArg(args, 'scrollAmount')
|
|
338
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
248
339
|
const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
|
|
249
340
|
ToolsNetwork.notifyActionStart()
|
|
250
341
|
const res = await ToolsInteract.scrollToElementHandler({ platform, selector, direction, maxScrolls, scrollAmount, deviceId })
|
|
251
342
|
const uiFingerprintAfter = await captureActionFingerprint(platform, deviceId)
|
|
252
343
|
return wrapResponse(buildActionExecutionResult({
|
|
253
344
|
actionType: 'scroll_to_element',
|
|
254
|
-
selector,
|
|
345
|
+
selector: selector ?? null,
|
|
255
346
|
resolved: res?.success && res?.element ? {
|
|
256
347
|
elementId: null,
|
|
257
348
|
text: (res.element as any).text ?? null,
|
|
@@ -269,7 +360,8 @@ async function handleScrollToElement(args: ToolCallArgs) {
|
|
|
269
360
|
}
|
|
270
361
|
|
|
271
362
|
async function handleTypeText(args: ToolCallArgs) {
|
|
272
|
-
const
|
|
363
|
+
const text = requireStringArg(args, 'text')
|
|
364
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
273
365
|
const uiFingerprintBefore = await captureActionFingerprint('android', deviceId)
|
|
274
366
|
ToolsNetwork.notifyActionStart()
|
|
275
367
|
const res = await ToolsInteract.typeTextHandler({ text, deviceId })
|
|
@@ -285,7 +377,7 @@ async function handleTypeText(args: ToolCallArgs) {
|
|
|
285
377
|
}
|
|
286
378
|
|
|
287
379
|
async function handlePressBack(args: ToolCallArgs) {
|
|
288
|
-
const
|
|
380
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
289
381
|
const uiFingerprintBefore = await captureActionFingerprint('android', deviceId)
|
|
290
382
|
ToolsNetwork.notifyActionStart()
|
|
291
383
|
const res = await ToolsInteract.pressBackHandler({ deviceId })
|
|
@@ -301,27 +393,38 @@ async function handlePressBack(args: ToolCallArgs) {
|
|
|
301
393
|
}
|
|
302
394
|
|
|
303
395
|
async function handleStartLogStream(args: ToolCallArgs) {
|
|
304
|
-
const
|
|
396
|
+
const platform = (getStringArg(args, 'platform') as PlatformArg | undefined) ?? 'android'
|
|
397
|
+
const packageName = requireStringArg(args, 'packageName')
|
|
398
|
+
const level = (getStringArg(args, 'level') as 'error' | 'warn' | 'info' | 'debug' | undefined) ?? 'error'
|
|
399
|
+
const sessionId = getStringArg(args, 'sessionId')
|
|
400
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
305
401
|
const res = await ToolsObserve.startLogStreamHandler({ platform, packageName, level, sessionId, deviceId })
|
|
306
402
|
return wrapResponse(res)
|
|
307
403
|
}
|
|
308
404
|
|
|
309
405
|
async function handleReadLogStream(args: ToolCallArgs) {
|
|
310
|
-
const
|
|
406
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
407
|
+
const sessionId = getStringArg(args, 'sessionId')
|
|
408
|
+
const limit = getNumberArg(args, 'limit')
|
|
409
|
+
const since = getStringArg(args, 'since')
|
|
311
410
|
const res = await ToolsObserve.readLogStreamHandler({ platform, sessionId, limit, since })
|
|
312
411
|
return wrapResponse(res)
|
|
313
412
|
}
|
|
314
413
|
|
|
315
414
|
async function handleStopLogStream(args: ToolCallArgs) {
|
|
316
|
-
const
|
|
415
|
+
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
416
|
+
const sessionId = getStringArg(args, 'sessionId')
|
|
317
417
|
const res = await ToolsObserve.stopLogStreamHandler({ platform, sessionId })
|
|
318
418
|
return wrapResponse(res)
|
|
319
419
|
}
|
|
320
420
|
|
|
321
421
|
function handleClassifyActionOutcome(args: ToolCallArgs) {
|
|
322
|
-
const
|
|
422
|
+
const uiChanged = requireBooleanArg(args, 'uiChanged')
|
|
423
|
+
const expectedElementVisible = getBooleanArg(args, 'expectedElementVisible')
|
|
424
|
+
const networkRequests = getArrayArg<ClassifyNetworkRequestArg>(args, 'networkRequests')
|
|
425
|
+
const hasLogErrors = getBooleanArg(args, 'hasLogErrors')
|
|
323
426
|
const result = classifyActionOutcome({
|
|
324
|
-
uiChanged
|
|
427
|
+
uiChanged,
|
|
325
428
|
expectedElementVisible: expectedElementVisible ?? null,
|
|
326
429
|
networkRequests: networkRequests ?? null,
|
|
327
430
|
hasLogErrors: hasLogErrors ?? null
|
|
@@ -330,7 +433,8 @@ function handleClassifyActionOutcome(args: ToolCallArgs) {
|
|
|
330
433
|
}
|
|
331
434
|
|
|
332
435
|
async function handleGetNetworkActivity(args: ToolCallArgs) {
|
|
333
|
-
const
|
|
436
|
+
const platform = requireStringArg(args, 'platform') as PlatformArg
|
|
437
|
+
const deviceId = getStringArg(args, 'deviceId')
|
|
334
438
|
const result = await ToolsNetwork.getNetworkActivity({ platform, deviceId })
|
|
335
439
|
return wrapResponse(result)
|
|
336
440
|
}
|
package/src/server-core.ts
CHANGED
|
@@ -50,26 +50,40 @@ export function ensureAdbAvailable() {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
type GradleEnvOverrides = Record<string, string | undefined>
|
|
54
|
+
|
|
55
|
+
function mergeEnv(overrides?: GradleEnvOverrides) {
|
|
56
|
+
const env: Record<string, string> = {}
|
|
57
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
58
|
+
if (typeof value === 'string') env[key] = value
|
|
59
|
+
}
|
|
60
|
+
for (const [key, value] of Object.entries(overrides || {})) {
|
|
61
|
+
if (typeof value === 'string') env[key] = value
|
|
62
|
+
}
|
|
63
|
+
return env
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
/**
|
|
54
67
|
* Prepare Gradle execution options for building an Android project.
|
|
55
68
|
* Returns execCmd (wrapper or gradle), base gradleArgs array, and spawn options including env.
|
|
56
69
|
*/
|
|
57
|
-
export async function prepareGradle(projectPath: string): Promise<{ execCmd: string, gradleArgs: string[], spawnOpts: any }> {
|
|
70
|
+
export async function prepareGradle(projectPath: string, envOverrides: GradleEnvOverrides = {}): Promise<{ execCmd: string, gradleArgs: string[], spawnOpts: any }> {
|
|
71
|
+
const env = mergeEnv(envOverrides)
|
|
58
72
|
const gradlewPath = path.join(projectPath, 'gradlew')
|
|
59
73
|
const gradleCmd = existsSync(gradlewPath) ? './gradlew' : 'gradle'
|
|
60
74
|
const execCmd = existsSync(gradlewPath) ? gradlewPath : gradleCmd
|
|
61
75
|
|
|
62
76
|
// Start with a default task; callers may append/override via env flags
|
|
63
|
-
const gradleArgs: string[] = [
|
|
77
|
+
const gradleArgs: string[] = [ env.MCP_GRADLE_TASK || 'assembleDebug' ]
|
|
64
78
|
|
|
65
79
|
// Respect generic MCP_BUILD_JOBS and Android-specific MCP_GRADLE_WORKERS
|
|
66
|
-
const workers =
|
|
80
|
+
const workers = env.MCP_GRADLE_WORKERS || env.MCP_BUILD_JOBS
|
|
67
81
|
if (workers) {
|
|
68
82
|
gradleArgs.push(`--max-workers=${workers}`)
|
|
69
83
|
}
|
|
70
84
|
|
|
71
85
|
// Respect gradle cache env: default enabled; set MCP_GRADLE_CACHE=0 to disable
|
|
72
|
-
if (
|
|
86
|
+
if (env.MCP_GRADLE_CACHE === '0') {
|
|
73
87
|
gradleArgs.push('-Dorg.gradle.caching=false')
|
|
74
88
|
}
|
|
75
89
|
|
|
@@ -82,8 +96,6 @@ export async function prepareGradle(projectPath: string): Promise<{ execCmd: str
|
|
|
82
96
|
gradleCheck = { gradleJavaHome: undefined, gradleValid: false, filesChecked: [], issues: [] }
|
|
83
97
|
}
|
|
84
98
|
|
|
85
|
-
const env = Object.assign({}, process.env)
|
|
86
|
-
|
|
87
99
|
// Ensure child processes can find Android platform-tools (adb, etc.) by
|
|
88
100
|
// prepending the platform-tools directory to PATH for spawned processes.
|
|
89
101
|
const adbPath = resolveAdbCmd()
|
|
@@ -488,4 +500,3 @@ export function parseLogLine(line: string) { // Collapse internal newlines so m
|
|
|
488
500
|
}
|
|
489
501
|
|
|
490
502
|
// Legacy readLogStreamLines shim removed. Use AndroidObserve.readLogStream(sessionId, limit, since) instead.
|
|
491
|
-
|
|
@@ -24,7 +24,7 @@ async function runInstaller() {
|
|
|
24
24
|
// prefer invoking the TS script via npx/tsx to ensure environment
|
|
25
25
|
const runner = which('npx') ? 'npx' : which('tsx') ? 'tsx' : null
|
|
26
26
|
if (runner) {
|
|
27
|
-
const args = runner === 'npx' ? ['tsx', './src/cli/idb/install-idb.ts'] : ['./src/cli/idb/install-idb.ts']
|
|
27
|
+
const args = runner === 'npx' ? ['tsx', './src/utils/cli/idb/install-idb.ts'] : ['./src/utils/cli/idb/install-idb.ts']
|
|
28
28
|
const res = spawnSync(runner, args, { stdio: 'inherit' as any })
|
|
29
29
|
return typeof res.status === 'number' ? res.status === 0 : false
|
|
30
30
|
}
|
|
@@ -15,7 +15,7 @@ if (!fs.existsSync(helperScript)) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
const out = execSync(`tsx ${helperScript} --platform android
|
|
18
|
+
const out = execSync(`tsx ${helperScript} --platform android`, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 30000 })
|
|
19
19
|
const parsed = JSON.parse(out)
|
|
20
20
|
|
|
21
21
|
if (!parsed?.resolution || parsed.resolution.width <= 0 || parsed.resolution.height <= 0) throw new Error('Invalid screenshot resolution')
|
|
@@ -15,7 +15,7 @@ if (!fs.existsSync(helperScript)) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
const out = execSync(`tsx ${helperScript} --platform ios
|
|
18
|
+
const out = execSync(`tsx ${helperScript} --platform ios`, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 30000 })
|
|
19
19
|
const parsed = JSON.parse(out)
|
|
20
20
|
|
|
21
21
|
if (!parsed?.resolution || parsed.resolution.width <= 0 || parsed.resolution.height <= 0) throw new Error('Invalid screenshot resolution')
|
|
@@ -17,7 +17,7 @@ if (!fs.existsSync(helperScript)) {
|
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
19
|
// Run the helper smoke script for android
|
|
20
|
-
const cmd = `tsx ${helperScript} --platform android --
|
|
20
|
+
const cmd = `tsx ${helperScript} --platform android --limit 20`
|
|
21
21
|
const out = execSync(cmd, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 30000 })
|
|
22
22
|
const parsed = JSON.parse(out)
|
|
23
23
|
|
|
@@ -15,7 +15,7 @@ if (!fs.existsSync(helperScript)) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
const cmd = `tsx ${helperScript} --platform ios --
|
|
18
|
+
const cmd = `tsx ${helperScript} --platform ios --limit 20`
|
|
19
19
|
const out = execSync(cmd, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 30000 })
|
|
20
20
|
const parsed = JSON.parse(out)
|
|
21
21
|
|
|
@@ -15,7 +15,7 @@ if (!fs.existsSync(helperScript)) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
const out = execSync(`tsx ${helperScript} --platform android
|
|
18
|
+
const out = execSync(`tsx ${helperScript} --platform android`, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 30000 })
|
|
19
19
|
const parsed = JSON.parse(out)
|
|
20
20
|
|
|
21
21
|
if (!parsed?.resolution || parsed.resolution.width <= 0 || parsed.resolution.height <= 0) throw new Error('Invalid UI tree resolution')
|
|
@@ -15,7 +15,7 @@ if (!fs.existsSync(helperScript)) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
const out = execSync(`tsx ${helperScript} --platform ios
|
|
18
|
+
const out = execSync(`tsx ${helperScript} --platform ios`, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 30000 })
|
|
19
19
|
const parsed = JSON.parse(out)
|
|
20
20
|
|
|
21
21
|
if (!parsed?.resolution || parsed.resolution.width <= 0 || parsed.resolution.height <= 0) throw new Error('Invalid UI tree resolution')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AndroidObserve, iOSObserve } from "
|
|
2
|
-
import { AndroidInteract } from "
|
|
3
|
-
import { iOSInteract } from "
|
|
1
|
+
import { AndroidObserve, iOSObserve } from "../../../../src/observe/index.js";
|
|
2
|
+
import { AndroidInteract } from "../../../../src/interact/index.js";
|
|
3
|
+
import { iOSInteract } from "../../../../src/interact/index.js";
|
|
4
4
|
import fs from "fs/promises";
|
|
5
5
|
|
|
6
6
|
const androidObserve = new AndroidObserve();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ToolsObserve } from '
|
|
1
|
+
import { ToolsObserve } from '../../../../src/observe/index.js'
|
|
2
2
|
|
|
3
3
|
function readArg(flag: string): string | undefined {
|
|
4
4
|
const index = process.argv.indexOf(flag)
|
|
@@ -8,7 +8,7 @@ function readArg(flag: string): string | undefined {
|
|
|
8
8
|
|
|
9
9
|
async function main() {
|
|
10
10
|
const platform = (readArg('--platform') || process.argv[2] || 'android') as 'android' | 'ios'
|
|
11
|
-
const deviceId = readArg('--id') || readArg('--deviceId') || process.argv[3]
|
|
11
|
+
const deviceId = readArg('--id') || readArg('--deviceId') || (process.argv[2]?.startsWith('-') ? undefined : process.argv[3])
|
|
12
12
|
|
|
13
13
|
const result = await ToolsObserve.captureScreenshotHandler({ platform, deviceId })
|
|
14
14
|
const screenshot = (result as any).screenshot || ''
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ToolsObserve } from '
|
|
1
|
+
import { ToolsObserve } from '../../../../src/observe/index.js'
|
|
2
2
|
import minimist from 'minimist'
|
|
3
3
|
|
|
4
4
|
async function main() {
|
|
5
5
|
const args = minimist(process.argv.slice(2))
|
|
6
6
|
const platform = args.platform || args.p || 'android'
|
|
7
|
-
const id = args.id || args.device || args.deviceId
|
|
7
|
+
const id = args.id || args.device || args.deviceId
|
|
8
8
|
const limit = typeof args.limit === 'number' ? args.limit : (typeof args.lines === 'number' ? args.lines : 50)
|
|
9
9
|
|
|
10
10
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ToolsObserve } from '
|
|
1
|
+
import { ToolsObserve } from '../../../../src/observe/index.js'
|
|
2
2
|
|
|
3
3
|
function readArg(flag: string): string | undefined {
|
|
4
4
|
const index = process.argv.indexOf(flag)
|
|
@@ -8,7 +8,7 @@ function readArg(flag: string): string | undefined {
|
|
|
8
8
|
|
|
9
9
|
async function main() {
|
|
10
10
|
const platform = (readArg('--platform') || process.argv[2] || 'android') as 'android' | 'ios'
|
|
11
|
-
const deviceId = readArg('--id') || readArg('--deviceId') || process.argv[3]
|
|
11
|
+
const deviceId = readArg('--id') || readArg('--deviceId') || (process.argv[2]?.startsWith('-') ? undefined : process.argv[3])
|
|
12
12
|
|
|
13
13
|
const result = await ToolsObserve.getUITreeHandler({ platform, deviceId })
|
|
14
14
|
if ((result as any).error) throw new Error((result as any).error)
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Usage: RUN_DEVICE_TESTS=true npx tsx test/device/manual/observe/screen_fingerprint.manual.ts [android|ios] [deviceId]
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { AndroidObserve } from '
|
|
8
|
-
import { iOSObserve } from '
|
|
7
|
+
import { AndroidObserve } from '../../../../src/observe/index.js'
|
|
8
|
+
import { iOSObserve } from '../../../../src/observe/index.js'
|
|
9
9
|
|
|
10
10
|
async function main() {
|
|
11
11
|
const args = process.argv.slice(2)
|