@rvaim/deveco-mobile-mcp 0.1.0-rvaim.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/LICENSE +201 -0
- package/NOTICE +15 -0
- package/README.md +129 -0
- package/lib/android.js +596 -0
- package/lib/harmony.js +805 -0
- package/lib/image-utils.js +155 -0
- package/lib/index.js +60 -0
- package/lib/ios.js +282 -0
- package/lib/iphone-simulator.js +266 -0
- package/lib/jpeg.js +48 -0
- package/lib/logger.js +22 -0
- package/lib/mobile-device.js +159 -0
- package/lib/mobilecli.js +112 -0
- package/lib/png.js +19 -0
- package/lib/robot.js +9 -0
- package/lib/server.js +615 -0
- package/lib/utils.js +80 -0
- package/lib/webdriver-agent.js +399 -0
- package/package.json +86 -0
package/lib/utils.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validatePackageName = validatePackageName;
|
|
7
|
+
exports.validateLocale = validateLocale;
|
|
8
|
+
exports.validateFileExtension = validateFileExtension;
|
|
9
|
+
exports.validateOutputPath = validateOutputPath;
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
12
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
|
+
const robot_1 = require("./robot");
|
|
14
|
+
function validatePackageName(packageName) {
|
|
15
|
+
if (!/^[a-zA-Z0-9._]+$/.test(packageName)) {
|
|
16
|
+
throw new robot_1.ActionableError(`Invalid package name: "${packageName}"`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function validateLocale(locale) {
|
|
20
|
+
if (!/^[a-zA-Z0-9,\- ]+$/.test(locale)) {
|
|
21
|
+
throw new robot_1.ActionableError(`Invalid locale: "${locale}"`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function getAllowedRoots() {
|
|
25
|
+
const roots = [
|
|
26
|
+
node_os_1.default.tmpdir(),
|
|
27
|
+
process.cwd(),
|
|
28
|
+
];
|
|
29
|
+
// macOS /tmp is a symlink to /private/tmp, add both to be safe
|
|
30
|
+
if (process.platform === "darwin") {
|
|
31
|
+
roots.push("/tmp");
|
|
32
|
+
roots.push("/private/tmp");
|
|
33
|
+
}
|
|
34
|
+
return roots.map(r => node_path_1.default.resolve(r));
|
|
35
|
+
}
|
|
36
|
+
function isPathUnderRoot(filePath, root) {
|
|
37
|
+
const relative = node_path_1.default.relative(root, filePath);
|
|
38
|
+
if (relative === "") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (node_path_1.default.isAbsolute(relative)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (relative.startsWith("..")) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
function validateFileExtension(filePath, allowedExtensions, toolName) {
|
|
50
|
+
const ext = node_path_1.default.extname(filePath).toLowerCase();
|
|
51
|
+
if (!allowedExtensions.includes(ext)) {
|
|
52
|
+
throw new robot_1.ActionableError(`${toolName} requires a ${allowedExtensions.join(", ")} file extension, got: "${ext || "(none)"}"`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function resolveWithSymlinks(filePath) {
|
|
56
|
+
const resolved = node_path_1.default.resolve(filePath);
|
|
57
|
+
const dir = node_path_1.default.dirname(resolved);
|
|
58
|
+
const filename = node_path_1.default.basename(resolved);
|
|
59
|
+
try {
|
|
60
|
+
return node_path_1.default.join(node_fs_1.default.realpathSync(dir), filename);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return resolved;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function validateOutputPath(filePath) {
|
|
67
|
+
const resolved = resolveWithSymlinks(filePath);
|
|
68
|
+
const allowedRoots = getAllowedRoots();
|
|
69
|
+
const isWindows = process.platform === "win32";
|
|
70
|
+
const isAllowed = allowedRoots.some(root => {
|
|
71
|
+
if (isWindows) {
|
|
72
|
+
return isPathUnderRoot(resolved.toLowerCase(), root.toLowerCase());
|
|
73
|
+
}
|
|
74
|
+
return isPathUnderRoot(resolved, root);
|
|
75
|
+
});
|
|
76
|
+
if (!isAllowed) {
|
|
77
|
+
const dir = node_path_1.default.dirname(resolved);
|
|
78
|
+
throw new robot_1.ActionableError(`"${dir}" is not in the list of allowed directories. Allowed directories include the current directory and the temp directory on this host.`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebDriverAgent = void 0;
|
|
4
|
+
const robot_1 = require("./robot");
|
|
5
|
+
class WebDriverAgent {
|
|
6
|
+
host;
|
|
7
|
+
port;
|
|
8
|
+
constructor(host, port) {
|
|
9
|
+
this.host = host;
|
|
10
|
+
this.port = port;
|
|
11
|
+
}
|
|
12
|
+
async isRunning() {
|
|
13
|
+
const url = `http://${this.host}:${this.port}/status`;
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetch(url);
|
|
16
|
+
const json = await response.json();
|
|
17
|
+
return response.status === 200 && json.value?.ready === true;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
// console.error(`Failed to connect to WebDriverAgent: ${error}`);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async createSession() {
|
|
25
|
+
const url = `http://${this.host}:${this.port}/session`;
|
|
26
|
+
const response = await fetch(url, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify({ capabilities: { alwaysMatch: { platformName: "iOS" } } }),
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const errorText = await response.text();
|
|
35
|
+
throw new robot_1.ActionableError(`Failed to create WebDriver session: ${response.status} ${errorText}`);
|
|
36
|
+
}
|
|
37
|
+
const json = await response.json();
|
|
38
|
+
if (!json.value || !json.value.sessionId) {
|
|
39
|
+
throw new robot_1.ActionableError(`Invalid session response: ${JSON.stringify(json)}`);
|
|
40
|
+
}
|
|
41
|
+
return json.value.sessionId;
|
|
42
|
+
}
|
|
43
|
+
async deleteSession(sessionId) {
|
|
44
|
+
const url = `http://${this.host}:${this.port}/session/${sessionId}`;
|
|
45
|
+
const response = await fetch(url, { method: "DELETE" });
|
|
46
|
+
return response.json();
|
|
47
|
+
}
|
|
48
|
+
async withinSession(fn) {
|
|
49
|
+
const sessionId = await this.createSession();
|
|
50
|
+
const url = `http://${this.host}:${this.port}/session/${sessionId}`;
|
|
51
|
+
const result = await fn(url);
|
|
52
|
+
await this.deleteSession(sessionId);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
async getScreenSize(sessionUrl) {
|
|
56
|
+
if (sessionUrl) {
|
|
57
|
+
const url = `${sessionUrl}/wda/screen`;
|
|
58
|
+
const response = await fetch(url);
|
|
59
|
+
const json = await response.json();
|
|
60
|
+
return {
|
|
61
|
+
width: json.value.screenSize.width,
|
|
62
|
+
height: json.value.screenSize.height,
|
|
63
|
+
scale: json.value.scale || 1,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
return this.withinSession(async (sessionUrlInner) => {
|
|
68
|
+
const url = `${sessionUrlInner}/wda/screen`;
|
|
69
|
+
const response = await fetch(url);
|
|
70
|
+
const json = await response.json();
|
|
71
|
+
return {
|
|
72
|
+
width: json.value.screenSize.width,
|
|
73
|
+
height: json.value.screenSize.height,
|
|
74
|
+
scale: json.value.scale || 1,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async sendKeys(keys) {
|
|
80
|
+
await this.withinSession(async (sessionUrl) => {
|
|
81
|
+
const url = `${sessionUrl}/wda/keys`;
|
|
82
|
+
await fetch(url, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({ value: [keys] }),
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async pressButton(button) {
|
|
92
|
+
const _map = {
|
|
93
|
+
"HOME": "home",
|
|
94
|
+
"VOLUME_UP": "volumeup",
|
|
95
|
+
"VOLUME_DOWN": "volumedown",
|
|
96
|
+
};
|
|
97
|
+
if (button === "ENTER") {
|
|
98
|
+
await this.sendKeys("\n");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Type assertion to check if button is a key of _map
|
|
102
|
+
if (!(button in _map)) {
|
|
103
|
+
throw new robot_1.ActionableError(`Button "${button}" is not supported`);
|
|
104
|
+
}
|
|
105
|
+
await this.withinSession(async (sessionUrl) => {
|
|
106
|
+
const url = `${sessionUrl}/wda/pressButton`;
|
|
107
|
+
const response = await fetch(url, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
name: button,
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
return response.json();
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async tap(x, y) {
|
|
120
|
+
await this.withinSession(async (sessionUrl) => {
|
|
121
|
+
const url = `${sessionUrl}/actions`;
|
|
122
|
+
await fetch(url, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: {
|
|
125
|
+
"Content-Type": "application/json",
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
actions: [
|
|
129
|
+
{
|
|
130
|
+
type: "pointer",
|
|
131
|
+
id: "finger1",
|
|
132
|
+
parameters: { pointerType: "touch" },
|
|
133
|
+
actions: [
|
|
134
|
+
{ type: "pointerMove", duration: 0, x, y },
|
|
135
|
+
{ type: "pointerDown", button: 0 },
|
|
136
|
+
{ type: "pause", duration: 100 },
|
|
137
|
+
{ type: "pointerUp", button: 0 }
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async doubleTap(x, y) {
|
|
146
|
+
await this.withinSession(async (sessionUrl) => {
|
|
147
|
+
const url = `${sessionUrl}/actions`;
|
|
148
|
+
await fetch(url, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
actions: [
|
|
155
|
+
{
|
|
156
|
+
type: "pointer",
|
|
157
|
+
id: "finger1",
|
|
158
|
+
parameters: { pointerType: "touch" },
|
|
159
|
+
actions: [
|
|
160
|
+
{ type: "pointerMove", duration: 0, x, y },
|
|
161
|
+
{ type: "pointerDown", button: 0 },
|
|
162
|
+
{ type: "pause", duration: 50 },
|
|
163
|
+
{ type: "pointerUp", button: 0 },
|
|
164
|
+
{ type: "pause", duration: 100 },
|
|
165
|
+
{ type: "pointerDown", button: 0 },
|
|
166
|
+
{ type: "pause", duration: 50 },
|
|
167
|
+
{ type: "pointerUp", button: 0 }
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
}),
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async longPress(x, y, duration) {
|
|
176
|
+
await this.withinSession(async (sessionUrl) => {
|
|
177
|
+
const url = `${sessionUrl}/actions`;
|
|
178
|
+
await fetch(url, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: {
|
|
181
|
+
"Content-Type": "application/json",
|
|
182
|
+
},
|
|
183
|
+
body: JSON.stringify({
|
|
184
|
+
actions: [
|
|
185
|
+
{
|
|
186
|
+
type: "pointer",
|
|
187
|
+
id: "finger1",
|
|
188
|
+
parameters: { pointerType: "touch" },
|
|
189
|
+
actions: [
|
|
190
|
+
{ type: "pointerMove", duration: 0, x, y },
|
|
191
|
+
{ type: "pointerDown", button: 0 },
|
|
192
|
+
{ type: "pause", duration },
|
|
193
|
+
{ type: "pointerUp", button: 0 }
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
}),
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
isVisible(rect) {
|
|
202
|
+
return rect.x >= 0 && rect.y >= 0;
|
|
203
|
+
}
|
|
204
|
+
filterSourceElements(source) {
|
|
205
|
+
const output = [];
|
|
206
|
+
const acceptedTypes = ["TextField", "Button", "Switch", "Icon", "SearchField", "StaticText", "Image"];
|
|
207
|
+
if (acceptedTypes.includes(source.type)) {
|
|
208
|
+
if (source.isVisible === "1" && this.isVisible(source.rect)) {
|
|
209
|
+
if (source.label !== null || source.name !== null || source.rawIdentifier !== null) {
|
|
210
|
+
output.push({
|
|
211
|
+
type: source.type,
|
|
212
|
+
label: source.label,
|
|
213
|
+
name: source.name,
|
|
214
|
+
value: source.value,
|
|
215
|
+
identifier: source.rawIdentifier,
|
|
216
|
+
rect: {
|
|
217
|
+
x: source.rect.x,
|
|
218
|
+
y: source.rect.y,
|
|
219
|
+
width: source.rect.width,
|
|
220
|
+
height: source.rect.height,
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (source.children) {
|
|
227
|
+
for (const child of source.children) {
|
|
228
|
+
output.push(...this.filterSourceElements(child));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return output;
|
|
232
|
+
}
|
|
233
|
+
async getPageSource() {
|
|
234
|
+
const url = `http://${this.host}:${this.port}/source/?format=json`;
|
|
235
|
+
const response = await fetch(url);
|
|
236
|
+
const json = await response.json();
|
|
237
|
+
return json;
|
|
238
|
+
}
|
|
239
|
+
async getElementsOnScreen() {
|
|
240
|
+
const source = await this.getPageSource();
|
|
241
|
+
return this.filterSourceElements(source.value);
|
|
242
|
+
}
|
|
243
|
+
async openUrl(url) {
|
|
244
|
+
await this.withinSession(async (sessionUrl) => {
|
|
245
|
+
await fetch(`${sessionUrl}/url`, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
body: JSON.stringify({ url }),
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
async getScreenshot() {
|
|
252
|
+
const url = `http://${this.host}:${this.port}/screenshot`;
|
|
253
|
+
const response = await fetch(url);
|
|
254
|
+
const json = await response.json();
|
|
255
|
+
return Buffer.from(json.value, "base64");
|
|
256
|
+
}
|
|
257
|
+
async swipe(direction) {
|
|
258
|
+
await this.withinSession(async (sessionUrl) => {
|
|
259
|
+
const screenSize = await this.getScreenSize(sessionUrl);
|
|
260
|
+
let x0, y0, x1, y1;
|
|
261
|
+
// Use 60% of the width/height for swipe distance
|
|
262
|
+
const verticalDistance = Math.floor(screenSize.height * 0.6);
|
|
263
|
+
const horizontalDistance = Math.floor(screenSize.width * 0.6);
|
|
264
|
+
const centerX = Math.floor(screenSize.width / 2);
|
|
265
|
+
const centerY = Math.floor(screenSize.height / 2);
|
|
266
|
+
switch (direction) {
|
|
267
|
+
case "up":
|
|
268
|
+
x0 = x1 = centerX;
|
|
269
|
+
y0 = centerY + Math.floor(verticalDistance / 2);
|
|
270
|
+
y1 = centerY - Math.floor(verticalDistance / 2);
|
|
271
|
+
break;
|
|
272
|
+
case "down":
|
|
273
|
+
x0 = x1 = centerX;
|
|
274
|
+
y0 = centerY - Math.floor(verticalDistance / 2);
|
|
275
|
+
y1 = centerY + Math.floor(verticalDistance / 2);
|
|
276
|
+
break;
|
|
277
|
+
case "left":
|
|
278
|
+
y0 = y1 = centerY;
|
|
279
|
+
x0 = centerX + Math.floor(horizontalDistance / 2);
|
|
280
|
+
x1 = centerX - Math.floor(horizontalDistance / 2);
|
|
281
|
+
break;
|
|
282
|
+
case "right":
|
|
283
|
+
y0 = y1 = centerY;
|
|
284
|
+
x0 = centerX - Math.floor(horizontalDistance / 2);
|
|
285
|
+
x1 = centerX + Math.floor(horizontalDistance / 2);
|
|
286
|
+
break;
|
|
287
|
+
default:
|
|
288
|
+
throw new robot_1.ActionableError(`Swipe direction "${direction}" is not supported`);
|
|
289
|
+
}
|
|
290
|
+
const url = `${sessionUrl}/actions`;
|
|
291
|
+
const response = await fetch(url, {
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers: {
|
|
294
|
+
"Content-Type": "application/json",
|
|
295
|
+
},
|
|
296
|
+
body: JSON.stringify({
|
|
297
|
+
actions: [
|
|
298
|
+
{
|
|
299
|
+
type: "pointer",
|
|
300
|
+
id: "finger1",
|
|
301
|
+
parameters: { pointerType: "touch" },
|
|
302
|
+
actions: [
|
|
303
|
+
{ type: "pointerMove", duration: 0, x: x0, y: y0 },
|
|
304
|
+
{ type: "pointerDown", button: 0 },
|
|
305
|
+
{ type: "pointerMove", duration: 1000, x: x1, y: y1 },
|
|
306
|
+
{ type: "pointerUp", button: 0 }
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
}),
|
|
311
|
+
});
|
|
312
|
+
if (!response.ok) {
|
|
313
|
+
const errorText = await response.text();
|
|
314
|
+
throw new robot_1.ActionableError(`WebDriver actions request failed: ${response.status} ${errorText}`);
|
|
315
|
+
}
|
|
316
|
+
// Clear actions to ensure they complete
|
|
317
|
+
await fetch(`${sessionUrl}/actions`, {
|
|
318
|
+
method: "DELETE",
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
async swipeFromCoordinate(x, y, direction, distance = 400) {
|
|
323
|
+
await this.withinSession(async (sessionUrl) => {
|
|
324
|
+
// Use simple coordinates like the working swipe method
|
|
325
|
+
const x0 = x;
|
|
326
|
+
const y0 = y;
|
|
327
|
+
let x1 = x;
|
|
328
|
+
let y1 = y;
|
|
329
|
+
// Calculate target position based on direction and distance
|
|
330
|
+
switch (direction) {
|
|
331
|
+
case "up":
|
|
332
|
+
y1 = y - distance; // Move up by specified distance
|
|
333
|
+
break;
|
|
334
|
+
case "down":
|
|
335
|
+
y1 = y + distance; // Move down by specified distance
|
|
336
|
+
break;
|
|
337
|
+
case "left":
|
|
338
|
+
x1 = x - distance; // Move left by specified distance
|
|
339
|
+
break;
|
|
340
|
+
case "right":
|
|
341
|
+
x1 = x + distance; // Move right by specified distance
|
|
342
|
+
break;
|
|
343
|
+
default:
|
|
344
|
+
throw new robot_1.ActionableError(`Swipe direction "${direction}" is not supported`);
|
|
345
|
+
}
|
|
346
|
+
const url = `${sessionUrl}/actions`;
|
|
347
|
+
const response = await fetch(url, {
|
|
348
|
+
method: "POST",
|
|
349
|
+
headers: {
|
|
350
|
+
"Content-Type": "application/json",
|
|
351
|
+
},
|
|
352
|
+
body: JSON.stringify({
|
|
353
|
+
actions: [
|
|
354
|
+
{
|
|
355
|
+
type: "pointer",
|
|
356
|
+
id: "finger1",
|
|
357
|
+
parameters: { pointerType: "touch" },
|
|
358
|
+
actions: [
|
|
359
|
+
{ type: "pointerMove", duration: 0, x: x0, y: y0 },
|
|
360
|
+
{ type: "pointerDown", button: 0 },
|
|
361
|
+
{ type: "pointerMove", duration: 1000, x: x1, y: y1 },
|
|
362
|
+
{ type: "pointerUp", button: 0 }
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
}),
|
|
367
|
+
});
|
|
368
|
+
if (!response.ok) {
|
|
369
|
+
const errorText = await response.text();
|
|
370
|
+
throw new robot_1.ActionableError(`WebDriver actions request failed: ${response.status} ${errorText}`);
|
|
371
|
+
}
|
|
372
|
+
// Clear actions to ensure they complete
|
|
373
|
+
await fetch(`${sessionUrl}/actions`, {
|
|
374
|
+
method: "DELETE",
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
async setOrientation(orientation) {
|
|
379
|
+
await this.withinSession(async (sessionUrl) => {
|
|
380
|
+
const url = `${sessionUrl}/orientation`;
|
|
381
|
+
await fetch(url, {
|
|
382
|
+
method: "POST",
|
|
383
|
+
headers: { "Content-Type": "application/json" },
|
|
384
|
+
body: JSON.stringify({
|
|
385
|
+
orientation: orientation.toUpperCase()
|
|
386
|
+
})
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
async getOrientation() {
|
|
391
|
+
return this.withinSession(async (sessionUrl) => {
|
|
392
|
+
const url = `${sessionUrl}/orientation`;
|
|
393
|
+
const response = await fetch(url);
|
|
394
|
+
const json = await response.json();
|
|
395
|
+
return json.value.toLowerCase();
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
exports.WebDriverAgent = WebDriverAgent;
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rvaim/deveco-mobile-mcp",
|
|
3
|
+
"version": "0.1.0-rvaim.1",
|
|
4
|
+
"description": "MCP server for HarmonyOS / iOS / Android mobile automation and development. Fork of mobile-mcp with HarmonyOS device support.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/rvaim/deveco-mobile-mcp.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/rvaim/deveco-mobile-mcp#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/rvaim/deveco-mobile-mcp/issues"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc && chmod +x lib/index.js",
|
|
19
|
+
"lint": "eslint .",
|
|
20
|
+
"fixlint": "eslint . --fix",
|
|
21
|
+
"test": "nyc mocha --require ts-node/register test/*.ts",
|
|
22
|
+
"watch": "tsc --watch",
|
|
23
|
+
"clean": "rm -rf lib",
|
|
24
|
+
"prepare": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"lib",
|
|
28
|
+
"NOTICE",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "1.26.0",
|
|
34
|
+
"ajv": "^8.18.0",
|
|
35
|
+
"commander": "14.0.0",
|
|
36
|
+
"express": "5.1.0",
|
|
37
|
+
"fast-xml-parser": "5.5.7",
|
|
38
|
+
"qs": "^6.15.0",
|
|
39
|
+
"zod": "^4.1.13",
|
|
40
|
+
"zod-to-json-schema": "3.25.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
44
|
+
"@eslint/js": "^9.19.0",
|
|
45
|
+
"@stylistic/eslint-plugin": "^3.0.1",
|
|
46
|
+
"@types/commander": "^2.12.0",
|
|
47
|
+
"@types/express": "^5.0.3",
|
|
48
|
+
"@types/mocha": "^10.0.10",
|
|
49
|
+
"@types/node": "^22.13.10",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
|
51
|
+
"@typescript-eslint/parser": "^8.26.1",
|
|
52
|
+
"@typescript-eslint/utils": "^8.26.1",
|
|
53
|
+
"eslint": "^9.19.0",
|
|
54
|
+
"eslint-plugin": "^1.0.1",
|
|
55
|
+
"eslint-plugin-import": "^2.31.0",
|
|
56
|
+
"eslint-plugin-notice": "^1.0.0",
|
|
57
|
+
"husky": "^9.1.7",
|
|
58
|
+
"mocha": "^11.1.0",
|
|
59
|
+
"nyc": "^17.1.0",
|
|
60
|
+
"ts-node": "^10.9.2",
|
|
61
|
+
"typescript": "5.8.2"
|
|
62
|
+
},
|
|
63
|
+
"main": "lib/index.js",
|
|
64
|
+
"bin": {
|
|
65
|
+
"deveco-mobile-mcp": "lib/index.js"
|
|
66
|
+
},
|
|
67
|
+
"directories": {
|
|
68
|
+
"lib": "lib"
|
|
69
|
+
},
|
|
70
|
+
"author": "",
|
|
71
|
+
"publishConfig": {
|
|
72
|
+
"access": "public",
|
|
73
|
+
"registry": "https://registry.npmjs.org"
|
|
74
|
+
},
|
|
75
|
+
"rvaimMirror": {
|
|
76
|
+
"upstreamRepository": "https://gitcode.com/openharmony-mcp/deveco-mobile-mcp.git",
|
|
77
|
+
"upstreamCommit": "b9a1e4ea07dddec102e9e0c14795c079c9f028ac",
|
|
78
|
+
"upstreamVersion": "0.1.0",
|
|
79
|
+
"sourceCodeModified": false,
|
|
80
|
+
"packagingChanges": [
|
|
81
|
+
"调整 npm 包名和仓库元数据",
|
|
82
|
+
"在 npm README 顶部加入非官方镜像说明",
|
|
83
|
+
"确保 NOTICE、LICENSE 和 README.md 进入发布包"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
}
|