@midscene/harmony 1.4.7-beta-20260226072540.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/LICENSE +21 -0
- package/bin/midscene-harmony +2 -0
- package/dist/es/cli.mjs +882 -0
- package/dist/es/index.mjs +821 -0
- package/dist/es/mcp-server.mjs +905 -0
- package/dist/lib/cli.js +911 -0
- package/dist/lib/index.js +878 -0
- package/dist/lib/mcp-server.js +956 -0
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/index.d.ts +139 -0
- package/dist/types/mcp-server.d.ts +157 -0
- package/package.json +58 -0
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
|
|
2
|
+
import node_assert from "node:assert";
|
|
3
|
+
import { getMidsceneLocationSchema, z } from "@midscene/core";
|
|
4
|
+
import { defineAction, defineActionClearInput, defineActionCursorMove, defineActionDoubleClick, defineActionDragAndDrop, defineActionKeyboardPress, defineActionScroll, defineActionSwipe, defineActionTap, normalizeMobileSwipeParam } from "@midscene/core/device";
|
|
5
|
+
import { getTmpFile, sleep } from "@midscene/core/utils";
|
|
6
|
+
import { createImgBase64ByFormat } from "@midscene/shared/img";
|
|
7
|
+
import { getDebug } from "@midscene/shared/logger";
|
|
8
|
+
import { mergeAndNormalizeAppNameMapping, normalizeForComparison, repeat } from "@midscene/shared/utils";
|
|
9
|
+
import { execFile } from "node:child_process";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
import { Agent } from "@midscene/core/agent";
|
|
12
|
+
import { overrideAIConfig } from "@midscene/shared/env";
|
|
13
|
+
var __webpack_modules__ = {
|
|
14
|
+
"node:fs" (module) {
|
|
15
|
+
module.exports = __rspack_external_node_fs_5ea92f0c;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var __webpack_module_cache__ = {};
|
|
19
|
+
function __webpack_require__(moduleId) {
|
|
20
|
+
var cachedModule = __webpack_module_cache__[moduleId];
|
|
21
|
+
if (void 0 !== cachedModule) return cachedModule.exports;
|
|
22
|
+
var module = __webpack_module_cache__[moduleId] = {
|
|
23
|
+
exports: {}
|
|
24
|
+
};
|
|
25
|
+
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
26
|
+
return module.exports;
|
|
27
|
+
}
|
|
28
|
+
var external_node_fs_ = __webpack_require__("node:fs");
|
|
29
|
+
function _define_property(obj, key, value) {
|
|
30
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
31
|
+
value: value,
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
writable: true
|
|
35
|
+
});
|
|
36
|
+
else obj[key] = value;
|
|
37
|
+
return obj;
|
|
38
|
+
}
|
|
39
|
+
const execFileAsync = promisify(execFile);
|
|
40
|
+
const debugHdc = getDebug('harmony:hdc');
|
|
41
|
+
function resolveHdcPath(hdcPath) {
|
|
42
|
+
if (hdcPath) return hdcPath;
|
|
43
|
+
if (process.env.HDC_HOME) {
|
|
44
|
+
const envPath = `${process.env.HDC_HOME}/hdc`;
|
|
45
|
+
debugHdc(`Using HDC from HDC_HOME: ${envPath}`);
|
|
46
|
+
return envPath;
|
|
47
|
+
}
|
|
48
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
49
|
+
const commonPaths = [
|
|
50
|
+
`${homeDir}/Library/HarmonyOS/next/command-line-tools/sdk/default/openharmony/toolchains/hdc`,
|
|
51
|
+
`${homeDir}/Library/HarmonyOS/sdk/hmscore/3.1.0/toolchains/hdc`
|
|
52
|
+
];
|
|
53
|
+
for (const p of commonPaths)try {
|
|
54
|
+
__webpack_require__("node:fs").accessSync(p, __webpack_require__("node:fs").constants.X_OK);
|
|
55
|
+
debugHdc(`Found HDC at: ${p}`);
|
|
56
|
+
return p;
|
|
57
|
+
} catch {}
|
|
58
|
+
return 'hdc';
|
|
59
|
+
}
|
|
60
|
+
class HdcClient {
|
|
61
|
+
buildArgs(args) {
|
|
62
|
+
if (this.deviceId) return [
|
|
63
|
+
'-t',
|
|
64
|
+
this.deviceId,
|
|
65
|
+
...args
|
|
66
|
+
];
|
|
67
|
+
return args;
|
|
68
|
+
}
|
|
69
|
+
async exec(...args) {
|
|
70
|
+
const fullArgs = this.buildArgs(args);
|
|
71
|
+
debugHdc(`hdc ${fullArgs.join(' ')}`);
|
|
72
|
+
try {
|
|
73
|
+
const { stdout, stderr } = await execFileAsync(this.hdcPath, fullArgs, {
|
|
74
|
+
timeout: this.timeout,
|
|
75
|
+
maxBuffer: 52428800
|
|
76
|
+
});
|
|
77
|
+
if (stderr?.trim()) debugHdc(`hdc stderr: ${stderr.trim()}`);
|
|
78
|
+
debugHdc(`hdc ${fullArgs.join(' ')} end`);
|
|
79
|
+
return stdout;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
debugHdc(`hdc error: ${error.message}`);
|
|
82
|
+
throw new Error(`HDC command failed: hdc ${fullArgs.join(' ')}: ${error.message}`, {
|
|
83
|
+
cause: error
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async shell(command) {
|
|
88
|
+
return this.exec('shell', command);
|
|
89
|
+
}
|
|
90
|
+
async fileSend(localPath, remotePath) {
|
|
91
|
+
await this.exec('file', 'send', localPath, remotePath);
|
|
92
|
+
}
|
|
93
|
+
async fileRecv(remotePath, localPath) {
|
|
94
|
+
await this.exec('file', 'recv', remotePath, localPath);
|
|
95
|
+
}
|
|
96
|
+
async screenshot(remotePath) {
|
|
97
|
+
await this.shell(`snapshot_display -f ${remotePath}`);
|
|
98
|
+
}
|
|
99
|
+
async click(x, y) {
|
|
100
|
+
await this.shell(`uitest uiInput click ${Math.round(x)} ${Math.round(y)}`);
|
|
101
|
+
}
|
|
102
|
+
async doubleClick(x, y) {
|
|
103
|
+
await this.shell(`uitest uiInput doubleClick ${Math.round(x)} ${Math.round(y)}`);
|
|
104
|
+
}
|
|
105
|
+
async longClick(x, y) {
|
|
106
|
+
await this.shell(`uitest uiInput longClick ${Math.round(x)} ${Math.round(y)}`);
|
|
107
|
+
}
|
|
108
|
+
async swipe(fromX, fromY, toX, toY, speed) {
|
|
109
|
+
const args = [
|
|
110
|
+
Math.round(fromX),
|
|
111
|
+
Math.round(fromY),
|
|
112
|
+
Math.round(toX),
|
|
113
|
+
Math.round(toY)
|
|
114
|
+
];
|
|
115
|
+
if (void 0 !== speed) args.push(Math.round(speed));
|
|
116
|
+
await this.shell(`uitest uiInput swipe ${args.join(' ')}`);
|
|
117
|
+
}
|
|
118
|
+
async fling(fromX, fromY, toX, toY, speed) {
|
|
119
|
+
const args = [
|
|
120
|
+
Math.round(fromX),
|
|
121
|
+
Math.round(fromY),
|
|
122
|
+
Math.round(toX),
|
|
123
|
+
Math.round(toY)
|
|
124
|
+
];
|
|
125
|
+
if (void 0 !== speed) args.push(Math.round(speed));
|
|
126
|
+
await this.shell(`uitest uiInput fling ${args.join(' ')}`);
|
|
127
|
+
}
|
|
128
|
+
async drag(fromX, fromY, toX, toY, speed) {
|
|
129
|
+
const args = [
|
|
130
|
+
Math.round(fromX),
|
|
131
|
+
Math.round(fromY),
|
|
132
|
+
Math.round(toX),
|
|
133
|
+
Math.round(toY)
|
|
134
|
+
];
|
|
135
|
+
if (void 0 !== speed) args.push(Math.round(speed));
|
|
136
|
+
await this.shell(`uitest uiInput drag ${args.join(' ')}`);
|
|
137
|
+
}
|
|
138
|
+
async inputText(x, y, text) {
|
|
139
|
+
const escapedText = text.replace(/'/g, "'\\''");
|
|
140
|
+
await this.shell(`uitest uiInput inputText ${Math.round(x)} ${Math.round(y)} '${escapedText}'`);
|
|
141
|
+
}
|
|
142
|
+
async keyEvent(...keys) {
|
|
143
|
+
await this.shell(`uitest uiInput keyEvent ${keys.join(' ')}`);
|
|
144
|
+
}
|
|
145
|
+
async startAbility(bundleName, abilityName) {
|
|
146
|
+
await this.shell(`aa start -a ${abilityName} -b ${bundleName}`);
|
|
147
|
+
}
|
|
148
|
+
async forceStop(bundleName) {
|
|
149
|
+
await this.shell(`aa force-stop ${bundleName}`);
|
|
150
|
+
}
|
|
151
|
+
async getScreenInfo() {
|
|
152
|
+
const stdout = await this.shell('hidumper -s RenderService -a screen');
|
|
153
|
+
const renderSizeMatch = stdout.match(/(\d{3,5})\s*x\s*(\d{3,5})/);
|
|
154
|
+
if (renderSizeMatch) return {
|
|
155
|
+
width: Number.parseInt(renderSizeMatch[1], 10),
|
|
156
|
+
height: Number.parseInt(renderSizeMatch[2], 10)
|
|
157
|
+
};
|
|
158
|
+
const displayStdout = await this.shell('hidumper -s DisplayManagerService -a -a');
|
|
159
|
+
const displayMatch = displayStdout.match(/activeModes.*?(\d{3,5}),\s*(\d{3,5})/);
|
|
160
|
+
if (displayMatch) return {
|
|
161
|
+
width: Number.parseInt(displayMatch[1], 10),
|
|
162
|
+
height: Number.parseInt(displayMatch[2], 10)
|
|
163
|
+
};
|
|
164
|
+
throw new Error(`Failed to get screen size from HDC. RenderService output: ${stdout}`);
|
|
165
|
+
}
|
|
166
|
+
async listTargets() {
|
|
167
|
+
const stdout = await this.exec('list', 'targets');
|
|
168
|
+
return stdout.trim().split('\n').map((line)=>line.trim()).filter((line)=>line.length > 0 && !line.startsWith('['));
|
|
169
|
+
}
|
|
170
|
+
constructor(options){
|
|
171
|
+
_define_property(this, "hdcPath", void 0);
|
|
172
|
+
_define_property(this, "deviceId", void 0);
|
|
173
|
+
_define_property(this, "timeout", void 0);
|
|
174
|
+
this.hdcPath = resolveHdcPath(options.hdcPath);
|
|
175
|
+
this.deviceId = options.deviceId || '';
|
|
176
|
+
this.timeout = options.timeout || 60000;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function device_define_property(obj, key, value) {
|
|
180
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
181
|
+
value: value,
|
|
182
|
+
enumerable: true,
|
|
183
|
+
configurable: true,
|
|
184
|
+
writable: true
|
|
185
|
+
});
|
|
186
|
+
else obj[key] = value;
|
|
187
|
+
return obj;
|
|
188
|
+
}
|
|
189
|
+
const defaultScrollUntilTimes = 10;
|
|
190
|
+
const defaultSwipeSpeed = 600;
|
|
191
|
+
const defaultFastSwipeSpeed = 2000;
|
|
192
|
+
const debugDevice = getDebug('harmony:device');
|
|
193
|
+
class HarmonyDevice {
|
|
194
|
+
actionSpace() {
|
|
195
|
+
const defaultActions = [
|
|
196
|
+
defineActionTap(async (param)=>{
|
|
197
|
+
const element = param.locate;
|
|
198
|
+
node_assert(element, 'Element not found, cannot tap');
|
|
199
|
+
await this.tap(element.center[0], element.center[1]);
|
|
200
|
+
}),
|
|
201
|
+
defineActionDoubleClick(async (param)=>{
|
|
202
|
+
const element = param.locate;
|
|
203
|
+
node_assert(element, 'Element not found, cannot double click');
|
|
204
|
+
await this.doubleTap(element.center[0], element.center[1]);
|
|
205
|
+
}),
|
|
206
|
+
defineAction({
|
|
207
|
+
name: 'Input',
|
|
208
|
+
description: 'Input text into the input field',
|
|
209
|
+
interfaceAlias: 'aiInput',
|
|
210
|
+
paramSchema: z.object({
|
|
211
|
+
value: 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.'),
|
|
212
|
+
mode: z.preprocess((val)=>'append' === val ? 'typeOnly' : val, z["enum"]([
|
|
213
|
+
'replace',
|
|
214
|
+
'clear',
|
|
215
|
+
'typeOnly'
|
|
216
|
+
]).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.')),
|
|
217
|
+
locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
|
|
218
|
+
}),
|
|
219
|
+
call: async (param)=>{
|
|
220
|
+
const element = param.locate;
|
|
221
|
+
if ('clear' === param.mode) return void await this.clearInput(element);
|
|
222
|
+
if (!param || !param.value) return;
|
|
223
|
+
const shouldReplace = 'typeOnly' !== param.mode;
|
|
224
|
+
await this.inputText(param.value, element, shouldReplace);
|
|
225
|
+
}
|
|
226
|
+
}),
|
|
227
|
+
defineActionScroll(async (param)=>{
|
|
228
|
+
const element = param.locate;
|
|
229
|
+
const startingPoint = element ? {
|
|
230
|
+
left: element.center[0],
|
|
231
|
+
top: element.center[1]
|
|
232
|
+
} : void 0;
|
|
233
|
+
const scrollToEventName = param?.scrollType;
|
|
234
|
+
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
235
|
+
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
236
|
+
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
237
|
+
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
238
|
+
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
239
|
+
else {
|
|
240
|
+
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
|
|
241
|
+
else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
|
|
242
|
+
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
243
|
+
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
244
|
+
else await this.scrollDown(param?.distance || void 0, startingPoint);
|
|
245
|
+
await sleep(500);
|
|
246
|
+
}
|
|
247
|
+
}),
|
|
248
|
+
defineActionDragAndDrop(async (param)=>{
|
|
249
|
+
const from = param.from;
|
|
250
|
+
const to = param.to;
|
|
251
|
+
node_assert(from, 'missing "from" param for drag and drop');
|
|
252
|
+
node_assert(to, 'missing "to" param for drag and drop');
|
|
253
|
+
const hdc = await this.getHdc();
|
|
254
|
+
await hdc.drag(from.center[0], from.center[1], to.center[0], to.center[1]);
|
|
255
|
+
}),
|
|
256
|
+
defineActionSwipe(async (param)=>{
|
|
257
|
+
const { startPoint, endPoint, duration, repeatCount } = normalizeMobileSwipeParam(param, await this.size());
|
|
258
|
+
const hdc = await this.getHdc();
|
|
259
|
+
for(let i = 0; i < repeatCount; i++)await hdc.swipe(startPoint.x, startPoint.y, endPoint.x, endPoint.y, duration ? Math.round(duration) : void 0);
|
|
260
|
+
}),
|
|
261
|
+
defineActionKeyboardPress(async (param)=>{
|
|
262
|
+
await this.keyboardPress(param.keyName);
|
|
263
|
+
}),
|
|
264
|
+
defineActionCursorMove(async (param)=>{
|
|
265
|
+
const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
|
|
266
|
+
const times = param.times ?? 1;
|
|
267
|
+
for(let i = 0; i < times; i++){
|
|
268
|
+
await this.keyboardPress(arrowKey);
|
|
269
|
+
await sleep(100);
|
|
270
|
+
}
|
|
271
|
+
}),
|
|
272
|
+
defineAction({
|
|
273
|
+
name: 'LongPress',
|
|
274
|
+
description: 'Trigger a long press on the screen at specified element',
|
|
275
|
+
paramSchema: z.object({
|
|
276
|
+
duration: z.number().optional().describe('The duration of the long press in milliseconds'),
|
|
277
|
+
locate: getMidsceneLocationSchema().describe('The element to be long pressed')
|
|
278
|
+
}),
|
|
279
|
+
call: async (param)=>{
|
|
280
|
+
const element = param.locate;
|
|
281
|
+
if (!element) throw new Error('LongPress requires an element to be located');
|
|
282
|
+
await this.longPress(element.center[0], element.center[1]);
|
|
283
|
+
}
|
|
284
|
+
}),
|
|
285
|
+
defineActionClearInput(async (param)=>{
|
|
286
|
+
await this.clearInput(param.locate);
|
|
287
|
+
})
|
|
288
|
+
];
|
|
289
|
+
const platformSpecificActions = Object.values(createPlatformActions(this));
|
|
290
|
+
const customActions = this.customActions || [];
|
|
291
|
+
return [
|
|
292
|
+
...defaultActions,
|
|
293
|
+
...platformSpecificActions,
|
|
294
|
+
...customActions
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
describe() {
|
|
298
|
+
return this.descriptionText || `DeviceId: ${this.deviceId}`;
|
|
299
|
+
}
|
|
300
|
+
async connect() {
|
|
301
|
+
const hdc = await this.getHdc();
|
|
302
|
+
return hdc;
|
|
303
|
+
}
|
|
304
|
+
async getHdc() {
|
|
305
|
+
if (this.destroyed) throw new Error(`HarmonyDevice ${this.deviceId} has been destroyed and cannot execute HDC commands`);
|
|
306
|
+
if (this.hdc) return this.hdc;
|
|
307
|
+
if (this.connecting) return this.connecting;
|
|
308
|
+
this.connecting = (async ()=>{
|
|
309
|
+
let error = null;
|
|
310
|
+
debugDevice(`Initializing HDC with device ID: ${this.deviceId}`);
|
|
311
|
+
try {
|
|
312
|
+
this.hdc = new HdcClient({
|
|
313
|
+
hdcPath: this.options?.hdcPath,
|
|
314
|
+
deviceId: this.deviceId
|
|
315
|
+
});
|
|
316
|
+
const screenInfo = await this.hdc.getScreenInfo();
|
|
317
|
+
this.cachedScreenSize = screenInfo;
|
|
318
|
+
this.descriptionText = `DeviceId: ${this.deviceId}\nScreenSize: ${screenInfo.width}x${screenInfo.height}`;
|
|
319
|
+
debugDevice('HDC initialized successfully', this.descriptionText);
|
|
320
|
+
return this.hdc;
|
|
321
|
+
} catch (e) {
|
|
322
|
+
debugDevice(`Failed to initialize HDC: ${e}`);
|
|
323
|
+
error = new Error(`Unable to connect to device ${this.deviceId}: ${e}`);
|
|
324
|
+
} finally{
|
|
325
|
+
this.connecting = null;
|
|
326
|
+
}
|
|
327
|
+
if (error) throw error;
|
|
328
|
+
throw new Error('HDC initialization failed unexpectedly');
|
|
329
|
+
})();
|
|
330
|
+
return this.connecting;
|
|
331
|
+
}
|
|
332
|
+
setAppNameMapping(mapping) {
|
|
333
|
+
this.appNameMapping = mapping;
|
|
334
|
+
}
|
|
335
|
+
resolvePackageName(appName) {
|
|
336
|
+
const normalizedAppName = normalizeForComparison(appName);
|
|
337
|
+
return this.appNameMapping[normalizedAppName];
|
|
338
|
+
}
|
|
339
|
+
async launch(uri) {
|
|
340
|
+
const hdc = await this.getHdc();
|
|
341
|
+
this.uri = uri;
|
|
342
|
+
try {
|
|
343
|
+
debugDevice(`Launching app: ${uri}`);
|
|
344
|
+
if (uri.startsWith('http://') || uri.startsWith('https://') || uri.includes('://')) await hdc.shell(`aa start -U ${uri}`);
|
|
345
|
+
else if (uri.includes('/')) {
|
|
346
|
+
const [bundleName, abilityName] = uri.split('/');
|
|
347
|
+
await hdc.startAbility(bundleName, abilityName);
|
|
348
|
+
} else {
|
|
349
|
+
const resolvedUri = this.resolvePackageName(uri) ?? uri;
|
|
350
|
+
await hdc.startAbility(resolvedUri, 'EntryAbility');
|
|
351
|
+
}
|
|
352
|
+
debugDevice(`Successfully launched: ${uri}`);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
debugDevice(`Error launching ${uri}: ${error}`);
|
|
355
|
+
throw new Error(`Failed to launch ${uri}: ${error.message}`, {
|
|
356
|
+
cause: error
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
async getScreenSize() {
|
|
362
|
+
if (this.cachedScreenSize) return this.cachedScreenSize;
|
|
363
|
+
const hdc = await this.getHdc();
|
|
364
|
+
const screenInfo = await hdc.getScreenInfo();
|
|
365
|
+
this.cachedScreenSize = screenInfo;
|
|
366
|
+
return screenInfo;
|
|
367
|
+
}
|
|
368
|
+
async size() {
|
|
369
|
+
const screenInfo = await this.getScreenSize();
|
|
370
|
+
const scale = this.options?.screenshotResizeScale ?? 1;
|
|
371
|
+
const logicalWidth = Math.round(screenInfo.width * scale);
|
|
372
|
+
const logicalHeight = Math.round(screenInfo.height * scale);
|
|
373
|
+
return {
|
|
374
|
+
width: logicalWidth,
|
|
375
|
+
height: logicalHeight
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
async screenshotBase64() {
|
|
379
|
+
debugDevice('screenshotBase64 begin');
|
|
380
|
+
const hdc = await this.getHdc();
|
|
381
|
+
const screenshotId = Date.now().toString(36);
|
|
382
|
+
const remoteScreenshotPath = `/data/local/tmp/ms_${screenshotId}.jpeg`;
|
|
383
|
+
const localScreenshotPath = getTmpFile('jpeg');
|
|
384
|
+
try {
|
|
385
|
+
await hdc.screenshot(remoteScreenshotPath);
|
|
386
|
+
await hdc.fileRecv(remoteScreenshotPath, localScreenshotPath);
|
|
387
|
+
const screenshotBuffer = await external_node_fs_["default"].promises.readFile(localScreenshotPath);
|
|
388
|
+
if (!screenshotBuffer || 0 === screenshotBuffer.length) throw new Error('Screenshot buffer is empty');
|
|
389
|
+
debugDevice(`Screenshot captured: ${screenshotBuffer.length} bytes`);
|
|
390
|
+
const result = createImgBase64ByFormat('jpeg', screenshotBuffer.toString('base64'));
|
|
391
|
+
debugDevice('screenshotBase64 end');
|
|
392
|
+
return result;
|
|
393
|
+
} finally{
|
|
394
|
+
Promise.resolve().then(()=>hdc.shell(`rm ${remoteScreenshotPath}`)).catch((error)=>{
|
|
395
|
+
debugDevice(`Failed to delete remote screenshot: ${error}`);
|
|
396
|
+
});
|
|
397
|
+
(0, external_node_fs_.unlink)(localScreenshotPath, (unlinkError)=>{
|
|
398
|
+
if (unlinkError) debugDevice(`Failed to delete local screenshot: ${unlinkError}`);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async tap(x, y) {
|
|
403
|
+
this.lastTapPosition = {
|
|
404
|
+
x,
|
|
405
|
+
y
|
|
406
|
+
};
|
|
407
|
+
const hdc = await this.getHdc();
|
|
408
|
+
await hdc.click(x, y);
|
|
409
|
+
}
|
|
410
|
+
async doubleTap(x, y) {
|
|
411
|
+
const hdc = await this.getHdc();
|
|
412
|
+
await hdc.doubleClick(x, y);
|
|
413
|
+
}
|
|
414
|
+
async longPress(x, y) {
|
|
415
|
+
const hdc = await this.getHdc();
|
|
416
|
+
await hdc.longClick(x, y);
|
|
417
|
+
}
|
|
418
|
+
async inputText(text, element, shouldReplace) {
|
|
419
|
+
if (!text) return;
|
|
420
|
+
const hdc = await this.getHdc();
|
|
421
|
+
let x;
|
|
422
|
+
let y;
|
|
423
|
+
if (element) [x, y] = element.center;
|
|
424
|
+
else if (this.lastTapPosition) {
|
|
425
|
+
x = this.lastTapPosition.x;
|
|
426
|
+
y = this.lastTapPosition.y;
|
|
427
|
+
} else {
|
|
428
|
+
const { width, height } = await this.size();
|
|
429
|
+
x = Math.round(width / 2);
|
|
430
|
+
y = Math.round(height / 2);
|
|
431
|
+
}
|
|
432
|
+
if (shouldReplace) {
|
|
433
|
+
await hdc.inputText(x, y, '.');
|
|
434
|
+
await hdc.keyEvent('2072', '2017');
|
|
435
|
+
}
|
|
436
|
+
await hdc.inputText(x, y, text);
|
|
437
|
+
if (this.options?.autoDismissKeyboard) await this.hideKeyboard();
|
|
438
|
+
}
|
|
439
|
+
async clearInput(_element) {}
|
|
440
|
+
async keyboardPress(key) {
|
|
441
|
+
const keyMap = {
|
|
442
|
+
Enter: '2054',
|
|
443
|
+
Backspace: '2055',
|
|
444
|
+
Tab: '2049',
|
|
445
|
+
Escape: '2070',
|
|
446
|
+
Home: 'Home',
|
|
447
|
+
ArrowUp: '2012',
|
|
448
|
+
ArrowDown: '2013',
|
|
449
|
+
ArrowLeft: '2014',
|
|
450
|
+
ArrowRight: '2015',
|
|
451
|
+
Space: '2050',
|
|
452
|
+
Delete: '2071'
|
|
453
|
+
};
|
|
454
|
+
const normalizedKey = this.normalizeKeyName(key);
|
|
455
|
+
const harmonyKey = keyMap[normalizedKey] || key;
|
|
456
|
+
const hdc = await this.getHdc();
|
|
457
|
+
await hdc.keyEvent(harmonyKey);
|
|
458
|
+
}
|
|
459
|
+
normalizeKeyName(key) {
|
|
460
|
+
const keyMap = {
|
|
461
|
+
enter: 'Enter',
|
|
462
|
+
backspace: 'Backspace',
|
|
463
|
+
tab: 'Tab',
|
|
464
|
+
escape: 'Escape',
|
|
465
|
+
esc: 'Escape',
|
|
466
|
+
home: 'Home',
|
|
467
|
+
space: 'Space',
|
|
468
|
+
delete: 'Delete',
|
|
469
|
+
arrowup: 'ArrowUp',
|
|
470
|
+
arrowdown: 'ArrowDown',
|
|
471
|
+
arrowleft: 'ArrowLeft',
|
|
472
|
+
arrowright: 'ArrowRight',
|
|
473
|
+
up: 'ArrowUp',
|
|
474
|
+
down: 'ArrowDown',
|
|
475
|
+
left: 'ArrowLeft',
|
|
476
|
+
right: 'ArrowRight'
|
|
477
|
+
};
|
|
478
|
+
const lowerKey = key.toLowerCase();
|
|
479
|
+
return keyMap[lowerKey] || key;
|
|
480
|
+
}
|
|
481
|
+
async scroll(deltaX, deltaY, speed) {
|
|
482
|
+
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
483
|
+
const { width, height } = await this.size();
|
|
484
|
+
const n = 4;
|
|
485
|
+
const startX = Math.round(deltaX < 0 ? width / n * (n - 1) : width / n);
|
|
486
|
+
const startY = Math.round(deltaY < 0 ? height / n * (n - 1) : height / n);
|
|
487
|
+
const maxPositiveDeltaX = startX;
|
|
488
|
+
const maxNegativeDeltaX = width - startX;
|
|
489
|
+
const maxPositiveDeltaY = startY;
|
|
490
|
+
const maxNegativeDeltaY = height - startY;
|
|
491
|
+
deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
|
|
492
|
+
deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
|
|
493
|
+
const endX = Math.round(startX - deltaX);
|
|
494
|
+
const endY = Math.round(startY - deltaY);
|
|
495
|
+
const hdc = await this.getHdc();
|
|
496
|
+
await hdc.swipe(startX, startY, endX, endY, speed ?? defaultSwipeSpeed);
|
|
497
|
+
}
|
|
498
|
+
async scrollDown(distance, startPoint) {
|
|
499
|
+
const { height } = await this.size();
|
|
500
|
+
const scrollDistance = Math.round(distance || height);
|
|
501
|
+
if (startPoint) {
|
|
502
|
+
const hdc = await this.getHdc();
|
|
503
|
+
const startX = Math.round(startPoint.left);
|
|
504
|
+
const startY = Math.round(startPoint.top);
|
|
505
|
+
const endY = Math.max(0, startY - scrollDistance);
|
|
506
|
+
await hdc.swipe(startX, startY, startX, endY);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
await this.scroll(0, scrollDistance);
|
|
510
|
+
}
|
|
511
|
+
async scrollUp(distance, startPoint) {
|
|
512
|
+
const { height } = await this.size();
|
|
513
|
+
const scrollDistance = Math.round(distance || height);
|
|
514
|
+
if (startPoint) {
|
|
515
|
+
const hdc = await this.getHdc();
|
|
516
|
+
const startX = Math.round(startPoint.left);
|
|
517
|
+
const startY = Math.round(startPoint.top);
|
|
518
|
+
const endY = Math.min(height, startY + scrollDistance);
|
|
519
|
+
await hdc.swipe(startX, startY, startX, endY);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
await this.scroll(0, -scrollDistance);
|
|
523
|
+
}
|
|
524
|
+
async scrollLeft(distance, startPoint) {
|
|
525
|
+
const { width } = await this.size();
|
|
526
|
+
const scrollDistance = Math.round(distance || width);
|
|
527
|
+
if (startPoint) {
|
|
528
|
+
const hdc = await this.getHdc();
|
|
529
|
+
const startX = Math.round(startPoint.left);
|
|
530
|
+
const startY = Math.round(startPoint.top);
|
|
531
|
+
const endX = Math.min(width, startX + scrollDistance);
|
|
532
|
+
await hdc.swipe(startX, startY, endX, startY);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
await this.scroll(-scrollDistance, 0);
|
|
536
|
+
}
|
|
537
|
+
async scrollRight(distance, startPoint) {
|
|
538
|
+
const { width } = await this.size();
|
|
539
|
+
const scrollDistance = Math.round(distance || width);
|
|
540
|
+
if (startPoint) {
|
|
541
|
+
const hdc = await this.getHdc();
|
|
542
|
+
const startX = Math.round(startPoint.left);
|
|
543
|
+
const startY = Math.round(startPoint.top);
|
|
544
|
+
const endX = Math.max(0, startX - scrollDistance);
|
|
545
|
+
await hdc.swipe(startX, startY, endX, startY);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
await this.scroll(scrollDistance, 0);
|
|
549
|
+
}
|
|
550
|
+
async scrollUntilTop(startPoint) {
|
|
551
|
+
if (startPoint) {
|
|
552
|
+
const { height } = await this.size();
|
|
553
|
+
const hdc = await this.getHdc();
|
|
554
|
+
const startX = Math.round(startPoint.left);
|
|
555
|
+
const startY = Math.round(startPoint.top);
|
|
556
|
+
await repeat(defaultScrollUntilTimes, ()=>hdc.fling(startX, startY, startX, Math.round(height), defaultFastSwipeSpeed));
|
|
557
|
+
await sleep(1000);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
await repeat(defaultScrollUntilTimes, ()=>this.scroll(0, -9999999, defaultFastSwipeSpeed));
|
|
561
|
+
await sleep(1000);
|
|
562
|
+
}
|
|
563
|
+
async scrollUntilBottom(startPoint) {
|
|
564
|
+
if (startPoint) {
|
|
565
|
+
const hdc = await this.getHdc();
|
|
566
|
+
const startX = Math.round(startPoint.left);
|
|
567
|
+
const startY = Math.round(startPoint.top);
|
|
568
|
+
await repeat(defaultScrollUntilTimes, ()=>hdc.fling(startX, startY, startX, 0, defaultFastSwipeSpeed));
|
|
569
|
+
await sleep(1000);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
await repeat(defaultScrollUntilTimes, ()=>this.scroll(0, 9999999, defaultFastSwipeSpeed));
|
|
573
|
+
await sleep(1000);
|
|
574
|
+
}
|
|
575
|
+
async scrollUntilLeft(startPoint) {
|
|
576
|
+
if (startPoint) {
|
|
577
|
+
const { width } = await this.size();
|
|
578
|
+
const hdc = await this.getHdc();
|
|
579
|
+
const startX = Math.round(startPoint.left);
|
|
580
|
+
const startY = Math.round(startPoint.top);
|
|
581
|
+
await repeat(defaultScrollUntilTimes, ()=>hdc.fling(startX, startY, Math.round(width), startY, defaultFastSwipeSpeed));
|
|
582
|
+
await sleep(1000);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
await repeat(defaultScrollUntilTimes, ()=>this.scroll(-9999999, 0, defaultFastSwipeSpeed));
|
|
586
|
+
await sleep(1000);
|
|
587
|
+
}
|
|
588
|
+
async scrollUntilRight(startPoint) {
|
|
589
|
+
if (startPoint) {
|
|
590
|
+
const hdc = await this.getHdc();
|
|
591
|
+
const startX = Math.round(startPoint.left);
|
|
592
|
+
const startY = Math.round(startPoint.top);
|
|
593
|
+
await repeat(defaultScrollUntilTimes, ()=>hdc.fling(startX, startY, 0, startY, defaultFastSwipeSpeed));
|
|
594
|
+
await sleep(1000);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
await repeat(defaultScrollUntilTimes, ()=>this.scroll(9999999, 0, defaultFastSwipeSpeed));
|
|
598
|
+
await sleep(1000);
|
|
599
|
+
}
|
|
600
|
+
async back() {
|
|
601
|
+
const hdc = await this.getHdc();
|
|
602
|
+
await hdc.keyEvent('Back');
|
|
603
|
+
}
|
|
604
|
+
async home() {
|
|
605
|
+
const hdc = await this.getHdc();
|
|
606
|
+
await hdc.keyEvent('Home');
|
|
607
|
+
}
|
|
608
|
+
async recentApps() {
|
|
609
|
+
const hdc = await this.getHdc();
|
|
610
|
+
await hdc.keyEvent('RecentApps');
|
|
611
|
+
}
|
|
612
|
+
async hideKeyboard() {
|
|
613
|
+
const hdc = await this.getHdc();
|
|
614
|
+
await hdc.keyEvent('Back');
|
|
615
|
+
}
|
|
616
|
+
async getTimestamp() {
|
|
617
|
+
const hdc = await this.getHdc();
|
|
618
|
+
try {
|
|
619
|
+
const stdout = await hdc.shell('date +%s%3N');
|
|
620
|
+
const timestamp = Number.parseInt(stdout.trim(), 10);
|
|
621
|
+
if (Number.isNaN(timestamp)) throw new Error(`Invalid timestamp format: ${stdout}`);
|
|
622
|
+
debugDevice(`Got device time: ${timestamp}`);
|
|
623
|
+
return timestamp;
|
|
624
|
+
} catch (error) {
|
|
625
|
+
debugDevice(`Failed to get device time: ${error}`);
|
|
626
|
+
throw new Error(`Failed to get device time: ${error}`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
async destroy() {
|
|
630
|
+
if (this.destroyed) return;
|
|
631
|
+
this.destroyed = true;
|
|
632
|
+
this.cachedScreenSize = null;
|
|
633
|
+
this.hdc = null;
|
|
634
|
+
this.connecting = null;
|
|
635
|
+
}
|
|
636
|
+
constructor(deviceId, options){
|
|
637
|
+
device_define_property(this, "deviceId", void 0);
|
|
638
|
+
device_define_property(this, "hdc", null);
|
|
639
|
+
device_define_property(this, "connecting", null);
|
|
640
|
+
device_define_property(this, "destroyed", false);
|
|
641
|
+
device_define_property(this, "descriptionText", void 0);
|
|
642
|
+
device_define_property(this, "customActions", void 0);
|
|
643
|
+
device_define_property(this, "cachedScreenSize", null);
|
|
644
|
+
device_define_property(this, "appNameMapping", {});
|
|
645
|
+
device_define_property(this, "lastTapPosition", null);
|
|
646
|
+
device_define_property(this, "interfaceType", 'harmony');
|
|
647
|
+
device_define_property(this, "uri", void 0);
|
|
648
|
+
device_define_property(this, "options", void 0);
|
|
649
|
+
node_assert(deviceId, 'deviceId is required for HarmonyDevice');
|
|
650
|
+
this.deviceId = deviceId;
|
|
651
|
+
this.options = options;
|
|
652
|
+
this.customActions = options?.customActions;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const runHdcShellParamSchema = z.object({
|
|
656
|
+
command: z.string().describe('HDC shell command to execute')
|
|
657
|
+
});
|
|
658
|
+
const launchParamSchema = z.object({
|
|
659
|
+
uri: z.string().describe('App name, bundle name, or URL to launch. Prioritize using the exact bundle name or URL the user has provided. If none provided, use the accurate app name.')
|
|
660
|
+
});
|
|
661
|
+
const createPlatformActions = (device)=>({
|
|
662
|
+
RunHdcShell: defineAction({
|
|
663
|
+
name: 'RunHdcShell',
|
|
664
|
+
description: 'Execute HDC shell command on HarmonyOS device',
|
|
665
|
+
interfaceAlias: 'runHdcShell',
|
|
666
|
+
paramSchema: runHdcShellParamSchema,
|
|
667
|
+
call: async (param)=>{
|
|
668
|
+
if (!param.command || '' === param.command.trim()) throw new Error('RunHdcShell requires a non-empty command parameter');
|
|
669
|
+
const hdc = await device.getHdc();
|
|
670
|
+
return await hdc.shell(param.command);
|
|
671
|
+
}
|
|
672
|
+
}),
|
|
673
|
+
Launch: defineAction({
|
|
674
|
+
name: 'Launch',
|
|
675
|
+
description: 'Launch a HarmonyOS app or URL',
|
|
676
|
+
interfaceAlias: 'launch',
|
|
677
|
+
paramSchema: launchParamSchema,
|
|
678
|
+
call: async (param)=>{
|
|
679
|
+
if (!param.uri || '' === param.uri.trim()) throw new Error('Launch requires a non-empty uri parameter');
|
|
680
|
+
await device.launch(param.uri);
|
|
681
|
+
}
|
|
682
|
+
}),
|
|
683
|
+
HarmonyBackButton: defineAction({
|
|
684
|
+
name: 'HarmonyBackButton',
|
|
685
|
+
description: 'Trigger the system "back" operation on HarmonyOS devices',
|
|
686
|
+
call: async ()=>{
|
|
687
|
+
await device.back();
|
|
688
|
+
}
|
|
689
|
+
}),
|
|
690
|
+
HarmonyHomeButton: defineAction({
|
|
691
|
+
name: 'HarmonyHomeButton',
|
|
692
|
+
description: 'Trigger the system "home" operation on HarmonyOS devices',
|
|
693
|
+
call: async ()=>{
|
|
694
|
+
await device.home();
|
|
695
|
+
}
|
|
696
|
+
}),
|
|
697
|
+
HarmonyRecentAppsButton: defineAction({
|
|
698
|
+
name: 'HarmonyRecentAppsButton',
|
|
699
|
+
description: 'Trigger the system "recent apps" operation on HarmonyOS devices',
|
|
700
|
+
call: async ()=>{
|
|
701
|
+
await device.recentApps();
|
|
702
|
+
}
|
|
703
|
+
})
|
|
704
|
+
});
|
|
705
|
+
const defaultAppNameMapping = {
|
|
706
|
+
设置: 'com.huawei.hmos.settings',
|
|
707
|
+
Settings: 'com.huawei.hmos.settings',
|
|
708
|
+
相机: 'com.huawei.hmos.camera',
|
|
709
|
+
Camera: 'com.huawei.hmos.camera',
|
|
710
|
+
图库: 'com.huawei.hmos.photos',
|
|
711
|
+
Gallery: 'com.huawei.hmos.photos',
|
|
712
|
+
日历: 'com.huawei.hmos.calendar',
|
|
713
|
+
Calendar: 'com.huawei.hmos.calendar',
|
|
714
|
+
时钟: 'com.huawei.hmos.clock',
|
|
715
|
+
Clock: 'com.huawei.hmos.clock',
|
|
716
|
+
计算器: 'com.huawei.hmos.calculator',
|
|
717
|
+
Calculator: 'com.huawei.hmos.calculator',
|
|
718
|
+
文件管理: 'com.huawei.hmos.filemanager',
|
|
719
|
+
备忘录: 'com.huawei.hmos.notepad',
|
|
720
|
+
联系人: 'com.huawei.hmos.contacts',
|
|
721
|
+
电话: 'com.huawei.hmos.phone',
|
|
722
|
+
信息: 'com.huawei.hmos.message',
|
|
723
|
+
邮件: 'com.huawei.hmos.email',
|
|
724
|
+
浏览器: 'com.huawei.hmos.browser',
|
|
725
|
+
Browser: 'com.huawei.hmos.browser',
|
|
726
|
+
应用市场: 'com.huawei.appmarket',
|
|
727
|
+
AppGallery: 'com.huawei.appmarket',
|
|
728
|
+
华为音乐: 'com.huawei.hmos.music',
|
|
729
|
+
华为视频: 'com.huawei.hmos.video',
|
|
730
|
+
天气: 'com.huawei.hmos.weather',
|
|
731
|
+
Weather: 'com.huawei.hmos.weather',
|
|
732
|
+
微信: 'com.tencent.mm',
|
|
733
|
+
WeChat: 'com.tencent.mm',
|
|
734
|
+
支付宝: 'com.eg.android.AlipayGphone',
|
|
735
|
+
淘宝: 'com.taobao.taobao',
|
|
736
|
+
京东: 'com.jingdong.app.mall',
|
|
737
|
+
抖音: 'com.ss.hm.ugc.aweme',
|
|
738
|
+
小红书: 'com.xingin.xhs',
|
|
739
|
+
bilibili: 'tv.danmaku.bili',
|
|
740
|
+
QQ: 'com.tencent.mobileqq',
|
|
741
|
+
微博: 'com.sina.weibo',
|
|
742
|
+
高德地图: 'com.autonavi.minimap',
|
|
743
|
+
百度地图: 'com.baidu.BaiduMap',
|
|
744
|
+
美团: 'com.sankuai.meituan',
|
|
745
|
+
饿了么: 'me.ele',
|
|
746
|
+
飞书: 'com.ss.android.lark',
|
|
747
|
+
豆包: 'com.larus.nova',
|
|
748
|
+
网易云音乐: 'com.netease.cloudmusic',
|
|
749
|
+
QQ音乐: 'com.tencent.qqmusic',
|
|
750
|
+
知乎: 'com.zhihu.android',
|
|
751
|
+
今日头条: 'com.ss.android.article.news',
|
|
752
|
+
Keep: 'com.gotokeep.keep'
|
|
753
|
+
};
|
|
754
|
+
const debugUtils = getDebug('harmony:utils');
|
|
755
|
+
async function getConnectedDevices(hdcPath) {
|
|
756
|
+
try {
|
|
757
|
+
const hdc = new HdcClient({
|
|
758
|
+
hdcPath
|
|
759
|
+
});
|
|
760
|
+
const targets = await hdc.listTargets();
|
|
761
|
+
const devices = targets.map((deviceId)=>({
|
|
762
|
+
deviceId
|
|
763
|
+
}));
|
|
764
|
+
debugUtils(`Found ${devices.length} connected devices: `, devices);
|
|
765
|
+
return devices;
|
|
766
|
+
} catch (error) {
|
|
767
|
+
console.error('Failed to get device list:', error);
|
|
768
|
+
throw new Error(`Unable to get connected HarmonyOS device list, please ensure HDC is properly configured: ${error.message}`, {
|
|
769
|
+
cause: error
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function agent_define_property(obj, key, value) {
|
|
774
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
775
|
+
value: value,
|
|
776
|
+
enumerable: true,
|
|
777
|
+
configurable: true,
|
|
778
|
+
writable: true
|
|
779
|
+
});
|
|
780
|
+
else obj[key] = value;
|
|
781
|
+
return obj;
|
|
782
|
+
}
|
|
783
|
+
const debugAgent = getDebug('harmony:agent');
|
|
784
|
+
class HarmonyAgent extends Agent {
|
|
785
|
+
async launch(uri) {
|
|
786
|
+
const action = this.wrapActionInActionSpace('Launch');
|
|
787
|
+
return action({
|
|
788
|
+
uri
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
async runHdcShell(command) {
|
|
792
|
+
const action = this.wrapActionInActionSpace('RunHdcShell');
|
|
793
|
+
return action({
|
|
794
|
+
command
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
createActionWrapper(name) {
|
|
798
|
+
const action = this.wrapActionInActionSpace(name);
|
|
799
|
+
return (...args)=>action(args[0]);
|
|
800
|
+
}
|
|
801
|
+
constructor(device, opts){
|
|
802
|
+
super(device, opts), agent_define_property(this, "back", void 0), agent_define_property(this, "home", void 0), agent_define_property(this, "recentApps", void 0), agent_define_property(this, "appNameMapping", void 0);
|
|
803
|
+
this.appNameMapping = mergeAndNormalizeAppNameMapping(defaultAppNameMapping, opts?.appNameMapping);
|
|
804
|
+
device.setAppNameMapping(this.appNameMapping);
|
|
805
|
+
this.back = this.createActionWrapper('HarmonyBackButton');
|
|
806
|
+
this.home = this.createActionWrapper('HarmonyHomeButton');
|
|
807
|
+
this.recentApps = this.createActionWrapper('HarmonyRecentAppsButton');
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
async function agentFromHdcDevice(deviceId, opts) {
|
|
811
|
+
if (!deviceId) {
|
|
812
|
+
const devices = await getConnectedDevices(opts?.hdcPath);
|
|
813
|
+
if (0 === devices.length) throw new Error('No HarmonyOS devices found. Please connect a HarmonyOS device and ensure HDC is properly configured. Run `hdc list targets` to verify device connection.');
|
|
814
|
+
deviceId = devices[0].deviceId;
|
|
815
|
+
debugAgent('deviceId not specified, will use the first device (id = %s)', deviceId);
|
|
816
|
+
}
|
|
817
|
+
const device = new HarmonyDevice(deviceId, opts || {});
|
|
818
|
+
await device.connect();
|
|
819
|
+
return new HarmonyAgent(device, opts);
|
|
820
|
+
}
|
|
821
|
+
export { HarmonyAgent, HarmonyDevice, agentFromHdcDevice, getConnectedDevices, overrideAIConfig };
|