@midscene/android 1.8.0 → 1.8.1
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/dist/es/cli.mjs +132 -166
- package/dist/es/index.mjs +131 -165
- package/dist/es/mcp-server.mjs +132 -166
- package/dist/lib/cli.js +131 -165
- package/dist/lib/index.js +130 -164
- package/dist/lib/mcp-server.js +131 -165
- package/dist/types/index.d.ts +10 -12
- package/dist/types/mcp-server.d.ts +10 -12
- package/package.json +4 -4
package/dist/lib/mcp-server.js
CHANGED
|
@@ -714,103 +714,30 @@ var __webpack_exports__ = {};
|
|
|
714
714
|
}
|
|
715
715
|
class AndroidDevice {
|
|
716
716
|
actionSpace() {
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
await
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
(0, device_namespaceObject.defineAction)({
|
|
729
|
-
name: 'Input',
|
|
730
|
-
description: 'Input text into the input field',
|
|
731
|
-
interfaceAlias: 'aiInput',
|
|
732
|
-
paramSchema: core_namespaceObject.z.object({
|
|
733
|
-
value: core_namespaceObject.z.string().describe('The text to input. Provide the final content for replace/append modes, or an empty string when using clear mode to remove existing text.'),
|
|
734
|
-
autoDismissKeyboard: core_namespaceObject.z.boolean().optional().describe('If true, the keyboard will be dismissed after the input is completed. Do not set it unless the user asks you to do so.'),
|
|
735
|
-
mode: core_namespaceObject.z.preprocess((val)=>'append' === val ? 'typeOnly' : val, core_namespaceObject.z["enum"]([
|
|
736
|
-
'replace',
|
|
737
|
-
'clear',
|
|
738
|
-
'typeOnly'
|
|
739
|
-
]).default('replace').optional().describe('Input mode: "replace" (default) - clear the field and input the value; "typeOnly" - type the value directly without clearing the field first; "clear" - clear the field without inputting new text.')),
|
|
740
|
-
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
|
|
741
|
-
}),
|
|
742
|
-
sample: {
|
|
743
|
-
value: 'test@example.com',
|
|
744
|
-
locate: {
|
|
745
|
-
prompt: 'the email input field'
|
|
746
|
-
}
|
|
717
|
+
const mobileActionContext = {
|
|
718
|
+
input: this.inputPrimitives,
|
|
719
|
+
size: ()=>this.size(),
|
|
720
|
+
sleep: async (timeMs)=>{
|
|
721
|
+
await (0, core_utils_namespaceObject.sleep)(timeMs);
|
|
722
|
+
},
|
|
723
|
+
getDefaultAutoDismissKeyboard: ()=>this.options?.autoDismissKeyboard,
|
|
724
|
+
systemActions: {
|
|
725
|
+
backButton: {
|
|
726
|
+
name: 'AndroidBackButton',
|
|
727
|
+
description: 'Trigger the system "back" operation on Android devices'
|
|
747
728
|
},
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
autoDismissKeyboard
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
}),
|
|
759
|
-
(0, device_namespaceObject.defineActionScroll)(async (param)=>{
|
|
760
|
-
const element = param.locate;
|
|
761
|
-
const startingPoint = element ? {
|
|
762
|
-
left: element.center[0],
|
|
763
|
-
top: element.center[1]
|
|
764
|
-
} : void 0;
|
|
765
|
-
const scrollToEventName = param?.scrollType;
|
|
766
|
-
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
767
|
-
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
768
|
-
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
769
|
-
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
770
|
-
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
771
|
-
else {
|
|
772
|
-
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
|
|
773
|
-
else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
|
|
774
|
-
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
775
|
-
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
776
|
-
else await this.scrollDown(param?.distance || void 0, startingPoint);
|
|
777
|
-
await (0, core_utils_namespaceObject.sleep)(500);
|
|
778
|
-
}
|
|
779
|
-
}),
|
|
780
|
-
(0, device_namespaceObject.defineActionDragAndDrop)(async (param)=>{
|
|
781
|
-
const from = param.from;
|
|
782
|
-
const to = param.to;
|
|
783
|
-
external_node_assert_default()(from, 'missing "from" param for drag and drop');
|
|
784
|
-
external_node_assert_default()(to, 'missing "to" param for drag and drop');
|
|
785
|
-
await this.mouseDrag({
|
|
786
|
-
x: from.center[0],
|
|
787
|
-
y: from.center[1]
|
|
788
|
-
}, {
|
|
789
|
-
x: to.center[0],
|
|
790
|
-
y: to.center[1]
|
|
791
|
-
});
|
|
792
|
-
}),
|
|
793
|
-
(0, device_namespaceObject.defineActionSwipe)(async (param)=>{
|
|
794
|
-
const { startPoint, endPoint, duration, repeatCount } = (0, device_namespaceObject.normalizeMobileSwipeParam)(param, await this.size());
|
|
795
|
-
for(let i = 0; i < repeatCount; i++)await this.mouseDrag(startPoint, endPoint, duration);
|
|
796
|
-
}),
|
|
797
|
-
(0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
|
|
798
|
-
await this.keyboardPress(param.keyName);
|
|
799
|
-
}),
|
|
800
|
-
(0, device_namespaceObject.defineActionCursorMove)(async (param)=>{
|
|
801
|
-
const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
|
|
802
|
-
const times = param.times ?? 1;
|
|
803
|
-
for(let i = 0; i < times; i++){
|
|
804
|
-
await this.keyboardPress(arrowKey);
|
|
805
|
-
await (0, core_utils_namespaceObject.sleep)(100);
|
|
729
|
+
homeButton: {
|
|
730
|
+
name: 'AndroidHomeButton',
|
|
731
|
+
description: 'Trigger the system "home" operation on Android devices'
|
|
732
|
+
},
|
|
733
|
+
recentAppsButton: {
|
|
734
|
+
name: 'AndroidRecentAppsButton',
|
|
735
|
+
description: 'Trigger the system "recent apps" operation on Android devices'
|
|
806
736
|
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
const [x, y] = element.center;
|
|
812
|
-
await this.longPress(x, y, param?.duration);
|
|
813
|
-
}),
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
const defaultActions = [
|
|
740
|
+
...(0, device_namespaceObject.createDefaultMobileActions)(mobileActionContext),
|
|
814
741
|
(0, device_namespaceObject.defineAction)({
|
|
815
742
|
name: 'PullGesture',
|
|
816
743
|
description: 'Trigger pull down to refresh or pull up actions',
|
|
@@ -840,19 +767,6 @@ var __webpack_exports__ = {};
|
|
|
840
767
|
else if ('up' === param.direction) await this.pullUp(startPoint, param.distance, param.duration);
|
|
841
768
|
else throw new Error(`Unknown pull direction: ${param.direction}`);
|
|
842
769
|
}
|
|
843
|
-
}),
|
|
844
|
-
(0, device_namespaceObject.defineActionPinch)(async (param)=>{
|
|
845
|
-
const { centerX, centerY, startDistance, endDistance, duration } = (0, device_namespaceObject.normalizePinchParam)(param, await this.size());
|
|
846
|
-
const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(centerX, centerY);
|
|
847
|
-
const ratio = 0 !== adjCenterX && 0 !== centerX ? adjCenterX / centerX : 1;
|
|
848
|
-
const adjStartDist = Math.round(startDistance * ratio);
|
|
849
|
-
const adjEndDist = Math.round(endDistance * ratio);
|
|
850
|
-
await this.ensureYadb();
|
|
851
|
-
const adb = await this.getAdb();
|
|
852
|
-
await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -pinch ${adjCenterX} ${adjCenterY} ${adjStartDist} ${adjEndDist} ${duration}`);
|
|
853
|
-
}),
|
|
854
|
-
(0, device_namespaceObject.defineActionClearInput)(async (param)=>{
|
|
855
|
-
await this.clearInput(param.locate);
|
|
856
770
|
})
|
|
857
771
|
];
|
|
858
772
|
const platformSpecificActions = Object.values(createPlatformActions(this));
|
|
@@ -863,6 +777,27 @@ var __webpack_exports__ = {};
|
|
|
863
777
|
...customActions
|
|
864
778
|
];
|
|
865
779
|
}
|
|
780
|
+
async performActionScroll(param) {
|
|
781
|
+
const element = param.locate;
|
|
782
|
+
const startingPoint = element ? {
|
|
783
|
+
left: element.center[0],
|
|
784
|
+
top: element.center[1]
|
|
785
|
+
} : void 0;
|
|
786
|
+
const scrollToEventName = param?.scrollType;
|
|
787
|
+
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
788
|
+
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
789
|
+
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
790
|
+
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
791
|
+
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
792
|
+
else {
|
|
793
|
+
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
|
|
794
|
+
else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
|
|
795
|
+
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
796
|
+
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
797
|
+
else await this.scrollDown(param?.distance || void 0, startingPoint);
|
|
798
|
+
await (0, core_utils_namespaceObject.sleep)(500);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
866
801
|
describe() {
|
|
867
802
|
return this.description || `DeviceId: ${this.deviceId}`;
|
|
868
803
|
}
|
|
@@ -1350,14 +1285,20 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1350
1285
|
return result;
|
|
1351
1286
|
}
|
|
1352
1287
|
async clearInput(element) {
|
|
1353
|
-
if (element) await this.
|
|
1288
|
+
if (element) await this.tapPoint({
|
|
1289
|
+
x: element.center[0],
|
|
1290
|
+
y: element.center[1]
|
|
1291
|
+
});
|
|
1354
1292
|
await this.ensureYadb();
|
|
1355
1293
|
const adb = await this.getAdb();
|
|
1356
1294
|
const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
1357
1295
|
if (IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII) await adb.clearTextField(100);
|
|
1358
1296
|
else await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboardClear`);
|
|
1359
1297
|
if (await adb.isSoftKeyboardPresent()) return;
|
|
1360
|
-
if (element) await this.
|
|
1298
|
+
if (element) await this.tapPoint({
|
|
1299
|
+
x: element.center[0],
|
|
1300
|
+
y: element.center[1]
|
|
1301
|
+
});
|
|
1361
1302
|
}
|
|
1362
1303
|
async forceScreenshot(path) {
|
|
1363
1304
|
await this.ensureYadb();
|
|
@@ -1378,7 +1319,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1378
1319
|
x: start.x,
|
|
1379
1320
|
y: Math.round(height)
|
|
1380
1321
|
};
|
|
1381
|
-
await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.
|
|
1322
|
+
await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1382
1323
|
await (0, core_utils_namespaceObject.sleep)(1000);
|
|
1383
1324
|
return;
|
|
1384
1325
|
}
|
|
@@ -1395,7 +1336,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1395
1336
|
x: start.x,
|
|
1396
1337
|
y: 0
|
|
1397
1338
|
};
|
|
1398
|
-
await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.
|
|
1339
|
+
await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1399
1340
|
await (0, core_utils_namespaceObject.sleep)(1000);
|
|
1400
1341
|
return;
|
|
1401
1342
|
}
|
|
@@ -1413,7 +1354,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1413
1354
|
x: Math.round(width),
|
|
1414
1355
|
y: start.y
|
|
1415
1356
|
};
|
|
1416
|
-
await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.
|
|
1357
|
+
await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1417
1358
|
await (0, core_utils_namespaceObject.sleep)(1000);
|
|
1418
1359
|
return;
|
|
1419
1360
|
}
|
|
@@ -1430,7 +1371,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1430
1371
|
x: 0,
|
|
1431
1372
|
y: start.y
|
|
1432
1373
|
};
|
|
1433
|
-
await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.
|
|
1374
|
+
await (0, utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1434
1375
|
await (0, core_utils_namespaceObject.sleep)(1000);
|
|
1435
1376
|
return;
|
|
1436
1377
|
}
|
|
@@ -1448,7 +1389,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1448
1389
|
};
|
|
1449
1390
|
const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
|
|
1450
1391
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('up', scrollDistance, Math.abs(end.y - start.y));
|
|
1451
|
-
await this.
|
|
1392
|
+
await this.dragPoint(start, end);
|
|
1452
1393
|
return;
|
|
1453
1394
|
}
|
|
1454
1395
|
await this.scroll(0, -scrollDistance, void 0, hasExplicitDistance, 'up');
|
|
@@ -1464,7 +1405,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1464
1405
|
};
|
|
1465
1406
|
const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
|
|
1466
1407
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('down', scrollDistance, Math.abs(end.y - start.y));
|
|
1467
|
-
await this.
|
|
1408
|
+
await this.dragPoint(start, end);
|
|
1468
1409
|
return;
|
|
1469
1410
|
}
|
|
1470
1411
|
await this.scroll(0, scrollDistance, void 0, hasExplicitDistance, 'down');
|
|
@@ -1480,7 +1421,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1480
1421
|
};
|
|
1481
1422
|
const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
|
|
1482
1423
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('left', scrollDistance, Math.abs(end.x - start.x));
|
|
1483
|
-
await this.
|
|
1424
|
+
await this.dragPoint(start, end);
|
|
1484
1425
|
return;
|
|
1485
1426
|
}
|
|
1486
1427
|
await this.scroll(-scrollDistance, 0, void 0, hasExplicitDistance, 'left');
|
|
@@ -1496,7 +1437,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1496
1437
|
};
|
|
1497
1438
|
const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
|
|
1498
1439
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('right', scrollDistance, Math.abs(end.x - start.x));
|
|
1499
|
-
await this.
|
|
1440
|
+
await this.dragPoint(start, end);
|
|
1500
1441
|
return;
|
|
1501
1442
|
}
|
|
1502
1443
|
await this.scroll(scrollDistance, 0, void 0, hasExplicitDistance, 'right');
|
|
@@ -1517,7 +1458,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1517
1458
|
const hasBothQuotes = text.includes('"') && text.includes("'");
|
|
1518
1459
|
return hasNonAscii || hasFormatSpecifiers || hasShellSpecialChars || hasBothQuotes;
|
|
1519
1460
|
}
|
|
1520
|
-
async
|
|
1461
|
+
async typeText(text, options) {
|
|
1521
1462
|
if (!text) return;
|
|
1522
1463
|
const adb = await this.getAdb();
|
|
1523
1464
|
const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
@@ -1554,7 +1495,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1554
1495
|
const lowerKey = key.toLowerCase();
|
|
1555
1496
|
return keyMap[lowerKey] || key;
|
|
1556
1497
|
}
|
|
1557
|
-
async
|
|
1498
|
+
async pressKey(key) {
|
|
1558
1499
|
const keyCodeMap = {
|
|
1559
1500
|
Enter: 66,
|
|
1560
1501
|
Backspace: 67,
|
|
@@ -1576,14 +1517,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1576
1517
|
if (asciiCode >= 65 && asciiCode <= 90) await adb.keyevent(asciiCode - 36);
|
|
1577
1518
|
}
|
|
1578
1519
|
}
|
|
1579
|
-
async
|
|
1520
|
+
async tapPoint(point) {
|
|
1580
1521
|
const adb = await this.getAdb();
|
|
1581
|
-
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1522
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
|
|
1582
1523
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
|
|
1583
1524
|
}
|
|
1584
|
-
async
|
|
1525
|
+
async doubleTapPoint(point) {
|
|
1585
1526
|
const adb = await this.getAdb();
|
|
1586
|
-
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1527
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
|
|
1587
1528
|
const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
|
|
1588
1529
|
await adb.shell(tapCommand);
|
|
1589
1530
|
await (0, core_utils_namespaceObject.sleep)(50);
|
|
@@ -1592,12 +1533,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1592
1533
|
async mouseMove() {
|
|
1593
1534
|
return Promise.resolve();
|
|
1594
1535
|
}
|
|
1595
|
-
async
|
|
1536
|
+
async dragPoint(from, to, duration) {
|
|
1537
|
+
await this.swipePoint(from, to, duration ?? defaultNormalScrollDuration);
|
|
1538
|
+
}
|
|
1539
|
+
async swipePoint(from, to, duration) {
|
|
1596
1540
|
const adb = await this.getAdb();
|
|
1597
1541
|
const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
|
|
1598
1542
|
const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
|
|
1599
|
-
|
|
1600
|
-
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1543
|
+
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
|
|
1601
1544
|
}
|
|
1602
1545
|
async scroll(deltaX, deltaY, duration, warnOnClamp = false, direction) {
|
|
1603
1546
|
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
@@ -1620,11 +1563,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1620
1563
|
}
|
|
1621
1564
|
const endX = Math.round(startX - deltaX);
|
|
1622
1565
|
const endY = Math.round(startY - deltaY);
|
|
1623
|
-
const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
|
|
1624
|
-
const { x: adjustedEndX, y: adjustedEndY } = await this.adjustCoordinates(endX, endY);
|
|
1625
|
-
const adb = await this.getAdb();
|
|
1626
1566
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1627
|
-
await
|
|
1567
|
+
await this.swipePoint({
|
|
1568
|
+
x: startX,
|
|
1569
|
+
y: startY
|
|
1570
|
+
}, {
|
|
1571
|
+
x: endX,
|
|
1572
|
+
y: endY
|
|
1573
|
+
}, swipeDuration);
|
|
1628
1574
|
}
|
|
1629
1575
|
async destroy() {
|
|
1630
1576
|
if (this.destroyed) return;
|
|
@@ -1672,9 +1618,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1672
1618
|
const adb = await this.getAdb();
|
|
1673
1619
|
await adb.shell(`input${this.getDisplayArg()} keyevent 187`);
|
|
1674
1620
|
}
|
|
1675
|
-
async
|
|
1621
|
+
async longPressPoint(point, duration = 2000) {
|
|
1676
1622
|
const adb = await this.getAdb();
|
|
1677
|
-
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1623
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
|
|
1678
1624
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
|
|
1679
1625
|
}
|
|
1680
1626
|
async pullDown(startPoint, distance, duration = 800) {
|
|
@@ -1695,10 +1641,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1695
1641
|
await (0, core_utils_namespaceObject.sleep)(200);
|
|
1696
1642
|
}
|
|
1697
1643
|
async pullDrag(from, to, duration) {
|
|
1698
|
-
|
|
1699
|
-
const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
|
|
1700
|
-
const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
|
|
1701
|
-
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
|
|
1644
|
+
await this.swipePoint(from, to, duration);
|
|
1702
1645
|
}
|
|
1703
1646
|
async pullUp(startPoint, distance, duration = 600) {
|
|
1704
1647
|
const { width, height } = await this.size();
|
|
@@ -1799,6 +1742,56 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1799
1742
|
device_define_property(this, "interfaceType", 'android');
|
|
1800
1743
|
device_define_property(this, "uri", void 0);
|
|
1801
1744
|
device_define_property(this, "options", void 0);
|
|
1745
|
+
device_define_property(this, "inputPrimitives", {
|
|
1746
|
+
pointer: {
|
|
1747
|
+
tap: (point)=>this.tapPoint(point),
|
|
1748
|
+
doubleClick: (point)=>this.doubleTapPoint(point),
|
|
1749
|
+
longPress: (point, opts)=>this.longPressPoint(point, opts?.duration),
|
|
1750
|
+
dragAndDrop: (from, to)=>this.dragPoint(from, to)
|
|
1751
|
+
},
|
|
1752
|
+
keyboard: {
|
|
1753
|
+
keyboardPress: (keyName)=>this.pressKey(keyName),
|
|
1754
|
+
typeText: async (value, opts)=>{
|
|
1755
|
+
const target = opts?.target;
|
|
1756
|
+
if (target && opts?.replace !== false) await this.clearInput(target);
|
|
1757
|
+
else if (target) await this.tapPoint({
|
|
1758
|
+
x: target.center[0],
|
|
1759
|
+
y: target.center[1]
|
|
1760
|
+
});
|
|
1761
|
+
if (opts?.focusOnly) return;
|
|
1762
|
+
await this.typeText(value, opts);
|
|
1763
|
+
},
|
|
1764
|
+
clearInput: (target)=>this.clearInput(target),
|
|
1765
|
+
cursorMove: async (direction, times = 1)=>{
|
|
1766
|
+
const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
|
|
1767
|
+
for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
|
|
1768
|
+
}
|
|
1769
|
+
},
|
|
1770
|
+
touch: {
|
|
1771
|
+
swipe: async (start, end, opts)=>{
|
|
1772
|
+
const duration = opts?.duration ?? 300;
|
|
1773
|
+
const repeatCount = opts?.repeat ?? 1;
|
|
1774
|
+
for(let i = 0; i < repeatCount; i++)await this.dragPoint(start, end, duration);
|
|
1775
|
+
},
|
|
1776
|
+
pinch: async (center, opts)=>{
|
|
1777
|
+
const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(Math.round(center.x), Math.round(center.y));
|
|
1778
|
+
const ratio = 0 !== adjCenterX && 0 !== center.x ? adjCenterX / center.x : 1;
|
|
1779
|
+
const adjStartDist = Math.round(opts.startDistance * ratio);
|
|
1780
|
+
const adjEndDist = Math.round(opts.endDistance * ratio);
|
|
1781
|
+
await this.ensureYadb();
|
|
1782
|
+
const adb = await this.getAdb();
|
|
1783
|
+
await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -pinch ${adjCenterX} ${adjCenterY} ${adjStartDist} ${adjEndDist} ${opts.duration}`);
|
|
1784
|
+
}
|
|
1785
|
+
},
|
|
1786
|
+
scroll: {
|
|
1787
|
+
scroll: (param)=>this.performActionScroll(param)
|
|
1788
|
+
},
|
|
1789
|
+
system: {
|
|
1790
|
+
backButton: ()=>this.back(),
|
|
1791
|
+
homeButton: ()=>this.home(),
|
|
1792
|
+
recentAppsButton: ()=>this.recentApps()
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1802
1795
|
external_node_assert_default()(deviceId, 'deviceId is required for AndroidDevice');
|
|
1803
1796
|
this.deviceId = deviceId;
|
|
1804
1797
|
this.options = options;
|
|
@@ -1849,37 +1842,10 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1849
1842
|
description: 'Terminate (force-stop) an Android app by package name',
|
|
1850
1843
|
interfaceAlias: 'terminate',
|
|
1851
1844
|
paramSchema: terminateParamSchema,
|
|
1852
|
-
delayBeforeRunner: 0,
|
|
1853
|
-
delayAfterRunner: 0,
|
|
1854
1845
|
call: async (param)=>{
|
|
1855
1846
|
if (!param.uri || '' === param.uri.trim()) throw new Error('Terminate requires a non-empty uri parameter');
|
|
1856
1847
|
await device.terminate(param.uri);
|
|
1857
1848
|
}
|
|
1858
|
-
}),
|
|
1859
|
-
AndroidBackButton: (0, device_namespaceObject.defineAction)({
|
|
1860
|
-
name: 'AndroidBackButton',
|
|
1861
|
-
description: 'Trigger the system "back" operation on Android devices',
|
|
1862
|
-
delayBeforeRunner: 0,
|
|
1863
|
-
delayAfterRunner: 0,
|
|
1864
|
-
call: async ()=>{
|
|
1865
|
-
await device.back();
|
|
1866
|
-
}
|
|
1867
|
-
}),
|
|
1868
|
-
AndroidHomeButton: (0, device_namespaceObject.defineAction)({
|
|
1869
|
-
name: 'AndroidHomeButton',
|
|
1870
|
-
description: 'Trigger the system "home" operation on Android devices',
|
|
1871
|
-
delayBeforeRunner: 0,
|
|
1872
|
-
delayAfterRunner: 0,
|
|
1873
|
-
call: async ()=>{
|
|
1874
|
-
await device.home();
|
|
1875
|
-
}
|
|
1876
|
-
}),
|
|
1877
|
-
AndroidRecentAppsButton: (0, device_namespaceObject.defineAction)({
|
|
1878
|
-
name: 'AndroidRecentAppsButton',
|
|
1879
|
-
description: 'Trigger the system "recent apps" operation on Android devices',
|
|
1880
|
-
call: async ()=>{
|
|
1881
|
-
await device.recentApps();
|
|
1882
|
-
}
|
|
1883
1849
|
})
|
|
1884
1850
|
});
|
|
1885
1851
|
const debugUtils = (0, logger_.getDebug)('android:utils');
|
|
@@ -2051,7 +2017,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
2051
2017
|
constructor(toolsManager){
|
|
2052
2018
|
super({
|
|
2053
2019
|
name: '@midscene/android-mcp',
|
|
2054
|
-
version: "1.8.
|
|
2020
|
+
version: "1.8.1",
|
|
2055
2021
|
description: 'Control the Android device using natural language commands'
|
|
2056
2022
|
}, toolsManager);
|
|
2057
2023
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { DeviceAction } from '@midscene/core';
|
|
|
13
13
|
import type { ElementInfo } from '@midscene/shared/extractor';
|
|
14
14
|
import { InitArgSpec } from '@midscene/shared/mcp/base-tools';
|
|
15
15
|
import { InterfaceType } from '@midscene/core';
|
|
16
|
+
import { MobileInputPrimitives } from '@midscene/core/device';
|
|
16
17
|
import { overrideAIConfig } from '@midscene/shared/env';
|
|
17
18
|
import { Point } from '@midscene/core';
|
|
18
19
|
import { Size } from '@midscene/core';
|
|
@@ -95,7 +96,9 @@ export declare class AndroidDevice implements AbstractInterface {
|
|
|
95
96
|
interfaceType: InterfaceType;
|
|
96
97
|
uri: string | undefined;
|
|
97
98
|
options?: AndroidDeviceOpt;
|
|
99
|
+
readonly inputPrimitives: MobileInputPrimitives;
|
|
98
100
|
actionSpace(): DeviceAction<any>[];
|
|
101
|
+
private performActionScroll;
|
|
99
102
|
constructor(deviceId: string, options?: AndroidDeviceOpt);
|
|
100
103
|
describe(): string;
|
|
101
104
|
connect(): Promise<ADB>;
|
|
@@ -196,19 +199,14 @@ export declare class AndroidDevice implements AbstractInterface {
|
|
|
196
199
|
* via escapeForShell + double-quoted shell context.
|
|
197
200
|
*/
|
|
198
201
|
private shouldUseYadbForText;
|
|
199
|
-
|
|
202
|
+
private typeText;
|
|
200
203
|
private normalizeKeyName;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
private pressKey;
|
|
205
|
+
private tapPoint;
|
|
206
|
+
private doubleTapPoint;
|
|
204
207
|
mouseMove(): Promise<void>;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
y: number;
|
|
208
|
-
}, to: {
|
|
209
|
-
x: number;
|
|
210
|
-
y: number;
|
|
211
|
-
}, duration?: number): Promise<void>;
|
|
208
|
+
private dragPoint;
|
|
209
|
+
private swipePoint;
|
|
212
210
|
scroll(deltaX: number, deltaY: number, duration?: number, warnOnClamp?: boolean, direction?: ScrollDirection): Promise<void>;
|
|
213
211
|
destroy(): Promise<void>;
|
|
214
212
|
/**
|
|
@@ -220,7 +218,7 @@ export declare class AndroidDevice implements AbstractInterface {
|
|
|
220
218
|
back(): Promise<void>;
|
|
221
219
|
home(): Promise<void>;
|
|
222
220
|
recentApps(): Promise<void>;
|
|
223
|
-
|
|
221
|
+
private longPressPoint;
|
|
224
222
|
pullDown(startPoint?: Point, distance?: number, duration?: number): Promise<void>;
|
|
225
223
|
pullDrag(from: {
|
|
226
224
|
x: number;
|
|
@@ -14,6 +14,7 @@ import { InitArgSpec } from '@midscene/shared/mcp/base-tools';
|
|
|
14
14
|
import { InterfaceType } from '@midscene/core';
|
|
15
15
|
import { LaunchMCPServerOptions } from '@midscene/shared/mcp';
|
|
16
16
|
import { LaunchMCPServerResult } from '@midscene/shared/mcp';
|
|
17
|
+
import { MobileInputPrimitives } from '@midscene/core/device';
|
|
17
18
|
import { Point } from '@midscene/core';
|
|
18
19
|
import { Size } from '@midscene/core';
|
|
19
20
|
import { Tool } from '@midscene/shared/mcp';
|
|
@@ -87,7 +88,9 @@ declare class AndroidDevice implements AbstractInterface {
|
|
|
87
88
|
interfaceType: InterfaceType;
|
|
88
89
|
uri: string | undefined;
|
|
89
90
|
options?: AndroidDeviceOpt;
|
|
91
|
+
readonly inputPrimitives: MobileInputPrimitives;
|
|
90
92
|
actionSpace(): DeviceAction<any>[];
|
|
93
|
+
private performActionScroll;
|
|
91
94
|
constructor(deviceId: string, options?: AndroidDeviceOpt);
|
|
92
95
|
describe(): string;
|
|
93
96
|
connect(): Promise<ADB>;
|
|
@@ -188,19 +191,14 @@ declare class AndroidDevice implements AbstractInterface {
|
|
|
188
191
|
* via escapeForShell + double-quoted shell context.
|
|
189
192
|
*/
|
|
190
193
|
private shouldUseYadbForText;
|
|
191
|
-
|
|
194
|
+
private typeText;
|
|
192
195
|
private normalizeKeyName;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
+
private pressKey;
|
|
197
|
+
private tapPoint;
|
|
198
|
+
private doubleTapPoint;
|
|
196
199
|
mouseMove(): Promise<void>;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
y: number;
|
|
200
|
-
}, to: {
|
|
201
|
-
x: number;
|
|
202
|
-
y: number;
|
|
203
|
-
}, duration?: number): Promise<void>;
|
|
200
|
+
private dragPoint;
|
|
201
|
+
private swipePoint;
|
|
204
202
|
scroll(deltaX: number, deltaY: number, duration?: number, warnOnClamp?: boolean, direction?: ScrollDirection): Promise<void>;
|
|
205
203
|
destroy(): Promise<void>;
|
|
206
204
|
/**
|
|
@@ -212,7 +210,7 @@ declare class AndroidDevice implements AbstractInterface {
|
|
|
212
210
|
back(): Promise<void>;
|
|
213
211
|
home(): Promise<void>;
|
|
214
212
|
recentApps(): Promise<void>;
|
|
215
|
-
|
|
213
|
+
private longPressPoint;
|
|
216
214
|
pullDown(startPoint?: Point, distance?: number, duration?: number): Promise<void>;
|
|
217
215
|
pullDrag(from: {
|
|
218
216
|
x: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midscene/android",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Android automation library for Midscene",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Android UI automation",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"@yume-chan/stream-extra": "2.1.0",
|
|
42
42
|
"appium-adb": "12.12.1",
|
|
43
43
|
"sharp": "^0.34.3",
|
|
44
|
-
"@midscene/
|
|
45
|
-
"@midscene/
|
|
44
|
+
"@midscene/shared": "1.8.1",
|
|
45
|
+
"@midscene/core": "1.8.1"
|
|
46
46
|
},
|
|
47
47
|
"optionalDependencies": {
|
|
48
48
|
"@ffmpeg-installer/ffmpeg": "^1.1.0"
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"undici": "^6.0.0",
|
|
57
57
|
"vitest": "3.0.5",
|
|
58
58
|
"zod": "^3.25.1",
|
|
59
|
-
"@midscene/playground": "1.8.
|
|
59
|
+
"@midscene/playground": "1.8.1"
|
|
60
60
|
},
|
|
61
61
|
"license": "MIT",
|
|
62
62
|
"scripts": {
|