@midscene/harmony 1.10.11 → 1.10.12-beta-20260812055946.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/dist/es/bin.mjs CHANGED
@@ -60,6 +60,23 @@ function _define_property(obj, key, value) {
60
60
  }
61
61
  const execFileAsync = promisify(execFile);
62
62
  const debugHdc = getDebug('harmony:hdc');
63
+ const supportedStringKeyEvents = new Set([
64
+ 'Back',
65
+ 'Home',
66
+ 'Power'
67
+ ]);
68
+ const numericKeyEventPattern = /^\d+$/;
69
+ const uiInputErrorPattern = /(?:(?:Invalid parameters|Missing parameter|Too many parameters)\.?|Please confirm that the coordinate values are correct\.?)/i;
70
+ const minUiInputSpeed = 200;
71
+ const maxUiInputSpeed = 40000;
72
+ function roundUiCoordinate(value, name) {
73
+ if (!Number.isFinite(value) || value < 0) throw new Error(`HDC ${name} coordinate must be a non-negative finite number`);
74
+ return Math.round(value);
75
+ }
76
+ function roundUiInputSpeed(value, operation) {
77
+ if (!Number.isFinite(value) || value < minUiInputSpeed || value > maxUiInputSpeed) throw new Error(`HDC ${operation} speed must be a finite number between ${minUiInputSpeed} and ${maxUiInputSpeed}`);
78
+ return Math.round(value);
79
+ }
63
80
  function resolveHdcPath(hdcPath) {
64
81
  if (hdcPath) return hdcPath;
65
82
  if (process.env.HDC_HOME) {
@@ -121,6 +138,26 @@ class HdcClient {
121
138
  async shell(command) {
122
139
  return this.exec('shell', command);
123
140
  }
141
+ assertUiInputSucceeded(operation, output, detail) {
142
+ const errorMatch = output.match(uiInputErrorPattern);
143
+ if (!errorMatch) return;
144
+ const detailText = detail ? ` for ${detail}` : '';
145
+ throw new Error(`HDC uiInput ${operation} failed${detailText}: ${errorMatch[0]}`);
146
+ }
147
+ async runUiInput(operation, args, detail) {
148
+ const output = await this.shell(`uitest uiInput ${operation} ${args}`);
149
+ this.assertUiInputSucceeded(operation, output, detail);
150
+ }
151
+ buildPointerMovementArgs(operation, fromX, fromY, toX, toY, speed) {
152
+ const args = [
153
+ roundUiCoordinate(fromX, 'fromX'),
154
+ roundUiCoordinate(fromY, 'fromY'),
155
+ roundUiCoordinate(toX, 'toX'),
156
+ roundUiCoordinate(toY, 'toY')
157
+ ];
158
+ if (void 0 !== speed) args.push(roundUiInputSpeed(speed, operation));
159
+ return args.join(' ');
160
+ }
124
161
  async fileSend(localPath, remotePath) {
125
162
  await this.exec('file', 'send', localPath, remotePath);
126
163
  }
@@ -138,63 +175,49 @@ class HdcClient {
138
175
  return output.slice(jsonStart);
139
176
  }
140
177
  async click(x, y) {
141
- await this.shell(`uitest uiInput click ${Math.round(x)} ${Math.round(y)}`);
178
+ await this.runUiInput('click', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
142
179
  }
143
180
  async doubleClick(x, y) {
144
- await this.shell(`uitest uiInput doubleClick ${Math.round(x)} ${Math.round(y)}`);
181
+ await this.runUiInput('doubleClick', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
145
182
  }
146
183
  async longClick(x, y) {
147
- await this.shell(`uitest uiInput longClick ${Math.round(x)} ${Math.round(y)}`);
184
+ await this.runUiInput('longClick', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
148
185
  }
149
186
  async swipe(fromX, fromY, toX, toY, speed) {
150
- const args = [
151
- Math.round(fromX),
152
- Math.round(fromY),
153
- Math.round(toX),
154
- Math.round(toY)
155
- ];
156
- if (void 0 !== speed) args.push(Math.round(speed));
157
- await this.shell(`uitest uiInput swipe ${args.join(' ')}`);
187
+ await this.runUiInput('swipe', this.buildPointerMovementArgs('swipe', fromX, fromY, toX, toY, speed));
158
188
  }
159
189
  async fling(fromX, fromY, toX, toY, speed) {
160
- const args = [
161
- Math.round(fromX),
162
- Math.round(fromY),
163
- Math.round(toX),
164
- Math.round(toY)
165
- ];
166
- if (void 0 !== speed) args.push(Math.round(speed));
167
- await this.shell(`uitest uiInput fling ${args.join(' ')}`);
190
+ await this.runUiInput('fling', this.buildPointerMovementArgs('fling', fromX, fromY, toX, toY, speed));
168
191
  }
169
192
  async drag(fromX, fromY, toX, toY, speed) {
170
- const args = [
171
- Math.round(fromX),
172
- Math.round(fromY),
173
- Math.round(toX),
174
- Math.round(toY)
175
- ];
176
- if (void 0 !== speed) args.push(Math.round(speed));
177
- await this.shell(`uitest uiInput drag ${args.join(' ')}`);
193
+ await this.runUiInput('drag', this.buildPointerMovementArgs('drag', fromX, fromY, toX, toY, speed));
178
194
  }
179
195
  async inputText(x, y, text) {
180
196
  const escapedText = text.replace(/'/g, "'\\''");
181
- await this.shell(`uitest uiInput inputText ${Math.round(x)} ${Math.round(y)} '${escapedText}'`);
197
+ await this.runUiInput('inputText', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')} '${escapedText}'`);
182
198
  }
183
199
  async keyEvent(...keys) {
184
- await this.shell(`uitest uiInput keyEvent ${keys.join(' ')}`);
200
+ if (keys.length < 1 || keys.length > 3) throw new Error('HDC keyEvent requires between 1 and 3 keys');
201
+ if (keys.length > 1 && keys.some((key)=>supportedStringKeyEvents.has(key))) throw new Error('HDC system key events cannot be combined');
202
+ for (const key of keys)if (!supportedStringKeyEvents.has(key) && !numericKeyEventPattern.test(key)) throw new Error(`Invalid HDC key event: ${key}`);
203
+ const joinedKeys = keys.join(' ');
204
+ await this.runUiInput('keyEvent', joinedKeys, joinedKeys);
185
205
  }
186
206
  async clearTextField(length = 100) {
187
- if (length <= 0) return;
188
- const MAX_KEYS_PER_CALL = 3;
207
+ if (!Number.isSafeInteger(length) || length < 0) throw new Error('HDC clearTextField length must be a non-negative safe integer');
208
+ if (0 === length) return;
209
+ const maxKeysPerCall = 3;
210
+ const backspaceKeyCode = '2055';
189
211
  const cmds = [];
190
212
  let remaining = length;
191
213
  while(remaining > 0){
192
- const n = Math.min(MAX_KEYS_PER_CALL, remaining);
193
- const codes = Array(n).fill('2055').join(' ');
214
+ const n = Math.min(maxKeysPerCall, remaining);
215
+ const codes = Array(n).fill(backspaceKeyCode).join(' ');
194
216
  cmds.push(`uitest uiInput keyEvent ${codes}`);
195
217
  remaining -= n;
196
218
  }
197
- await this.shell(cmds.join(';'));
219
+ const output = await this.shell(cmds.join(';'));
220
+ this.assertUiInputSucceeded('clearTextField', output);
198
221
  }
199
222
  async startAbility(bundleName, abilityName) {
200
223
  const output = await this.shell(`aa start -a ${abilityName} -b ${bundleName}`);
@@ -258,6 +281,96 @@ class HdcClient {
258
281
  this.timeout = options.timeout ?? 60000;
259
282
  }
260
283
  }
284
+ const namedHarmonyKeyCodeMap = {
285
+ Enter: '2054',
286
+ Backspace: '2055',
287
+ Tab: '2049',
288
+ Escape: '2070',
289
+ Home: 'Home',
290
+ ArrowUp: '2012',
291
+ ArrowDown: '2013',
292
+ ArrowLeft: '2014',
293
+ ArrowRight: '2015',
294
+ Space: '2050',
295
+ Delete: '2071',
296
+ Alt: '2045',
297
+ Shift: '2047',
298
+ Control: '2072',
299
+ Meta: '2076',
300
+ Back: 'Back',
301
+ Power: 'Power'
302
+ };
303
+ const keyNameAliasMap = new Map([
304
+ [
305
+ 'return',
306
+ 'Enter'
307
+ ],
308
+ [
309
+ 'esc',
310
+ 'Escape'
311
+ ],
312
+ [
313
+ 'up',
314
+ 'ArrowUp'
315
+ ],
316
+ [
317
+ 'down',
318
+ 'ArrowDown'
319
+ ],
320
+ [
321
+ 'left',
322
+ 'ArrowLeft'
323
+ ],
324
+ [
325
+ 'right',
326
+ 'ArrowRight'
327
+ ],
328
+ [
329
+ 'ctrl',
330
+ 'Control'
331
+ ]
332
+ ]);
333
+ const caseInsensitiveNamedKeyCodeMap = new Map(Object.entries(namedHarmonyKeyCodeMap).map(([key, code])=>[
334
+ key.toLowerCase(),
335
+ code
336
+ ]));
337
+ function numericKeyCode(value) {
338
+ return String(value);
339
+ }
340
+ function resolveSingleHarmonyKeyCodes(keyName) {
341
+ const trimmedKeyName = keyName.trim();
342
+ if (!trimmedKeyName) {
343
+ if (0 === keyName.length) throw new Error('Unsupported HarmonyOS key: empty key name');
344
+ throw unsupportedKeyboardPressKey(keyName);
345
+ }
346
+ const alias = keyNameAliasMap.get(trimmedKeyName.toLowerCase());
347
+ const normalizedKeyName = alias ?? trimmedKeyName;
348
+ if (/^[0-9]$/.test(normalizedKeyName)) return [
349
+ numericKeyCode(2000 + Number(normalizedKeyName))
350
+ ];
351
+ const letterKeyMatch = normalizedKeyName.match(/^[a-z]$/i);
352
+ if (letterKeyMatch) {
353
+ const alphabetIndex = letterKeyMatch[0].toUpperCase().charCodeAt(0) - 65;
354
+ return [
355
+ numericKeyCode(2017 + alphabetIndex)
356
+ ];
357
+ }
358
+ const namedCode = caseInsensitiveNamedKeyCodeMap.get(normalizedKeyName.toLowerCase());
359
+ if (namedCode) return [
360
+ namedCode
361
+ ];
362
+ throw unsupportedKeyboardPressKey(keyName);
363
+ }
364
+ function unsupportedKeyboardPressKey(keyName) {
365
+ return new Error(`Unsupported HarmonyOS keyboardPress key: ${JSON.stringify(keyName)}. Use typeText() for text input, or pass a supported key name such as "Enter", "A", or "1".`);
366
+ }
367
+ function resolveHarmonyKeyCodes(keyName) {
368
+ const trimmedKeyName = keyName.trim();
369
+ if (!trimmedKeyName && ' ' !== keyName) return resolveSingleHarmonyKeyCodes(keyName);
370
+ if ('+' === trimmedKeyName) return resolveSingleHarmonyKeyCodes(keyName);
371
+ if (trimmedKeyName.includes('+')) throw new Error(`HarmonyOS keyboardPress does not support key combinations: ${JSON.stringify(keyName)}`);
372
+ return resolveSingleHarmonyKeyCodes(keyName);
373
+ }
261
374
  function device_define_property(obj, key, value) {
262
375
  if (key in obj) Object.defineProperty(obj, key, {
263
376
  value: value,
@@ -316,37 +429,6 @@ function pickFieldByPoint(fields, point) {
316
429
  function pickLongestField(fields) {
317
430
  return fields.reduce((a, b)=>b.text.length > a.text.length ? b : a);
318
431
  }
319
- const harmonyKeyCodeMap = {
320
- Enter: '2054',
321
- Backspace: '2055',
322
- Tab: '2049',
323
- Escape: '2070',
324
- Home: 'Home',
325
- ArrowUp: '2012',
326
- ArrowDown: '2013',
327
- ArrowLeft: '2014',
328
- ArrowRight: '2015',
329
- Space: '2050',
330
- Delete: '2071'
331
- };
332
- const keyNameAliasMap = {
333
- enter: 'Enter',
334
- backspace: 'Backspace',
335
- tab: 'Tab',
336
- escape: 'Escape',
337
- esc: 'Escape',
338
- home: 'Home',
339
- space: 'Space',
340
- delete: 'Delete',
341
- arrowup: 'ArrowUp',
342
- arrowdown: 'ArrowDown',
343
- arrowleft: 'ArrowLeft',
344
- arrowright: 'ArrowRight',
345
- up: 'ArrowUp',
346
- down: 'ArrowDown',
347
- left: 'ArrowLeft',
348
- right: 'ArrowRight'
349
- };
350
432
  class device_HarmonyDevice {
351
433
  actionSpace() {
352
434
  const mobileActionContext = {
@@ -590,10 +672,9 @@ class device_HarmonyDevice {
590
672
  }
591
673
  }
592
674
  async pressKey(key) {
593
- const normalizedKey = keyNameAliasMap[key.toLowerCase()] ?? key;
594
- const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
675
+ const keyCodes = resolveHarmonyKeyCodes(key);
595
676
  const hdc = await this.getHdc();
596
- await hdc.keyEvent(harmonyKey);
677
+ await hdc.keyEvent(...keyCodes);
597
678
  }
598
679
  async scroll(deltaX, deltaY, speed) {
599
680
  if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
@@ -748,13 +829,13 @@ class device_HarmonyDevice {
748
829
  }
749
830
  async recentApps() {
750
831
  const hdc = await this.getHdc();
751
- await hdc.keyEvent('RecentApps');
832
+ await hdc.keyEvent('10011');
752
833
  }
753
834
  async hideKeyboard(options) {
754
835
  const hdc = await this.getHdc();
755
836
  const keyboardDismissStrategy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy ?? 'esc-first';
756
- const key = 'back-first' === keyboardDismissStrategy ? 'Back' : harmonyKeyCodeMap.Escape;
757
- await hdc.keyEvent(key);
837
+ if ('back-first' === keyboardDismissStrategy) return void await hdc.keyEvent('Back');
838
+ await hdc.keyEvent(...resolveHarmonyKeyCodes('Escape'));
758
839
  }
759
840
  async getDeviceLocalTimeString(format = 'YYYY-MM-DD HH:mm:ss') {
760
841
  const hdc = await this.getHdc();
package/dist/es/cli.mjs CHANGED
@@ -58,6 +58,23 @@ function _define_property(obj, key, value) {
58
58
  }
59
59
  const execFileAsync = promisify(execFile);
60
60
  const debugHdc = getDebug('harmony:hdc');
61
+ const supportedStringKeyEvents = new Set([
62
+ 'Back',
63
+ 'Home',
64
+ 'Power'
65
+ ]);
66
+ const numericKeyEventPattern = /^\d+$/;
67
+ const uiInputErrorPattern = /(?:(?:Invalid parameters|Missing parameter|Too many parameters)\.?|Please confirm that the coordinate values are correct\.?)/i;
68
+ const minUiInputSpeed = 200;
69
+ const maxUiInputSpeed = 40000;
70
+ function roundUiCoordinate(value, name) {
71
+ if (!Number.isFinite(value) || value < 0) throw new Error(`HDC ${name} coordinate must be a non-negative finite number`);
72
+ return Math.round(value);
73
+ }
74
+ function roundUiInputSpeed(value, operation) {
75
+ if (!Number.isFinite(value) || value < minUiInputSpeed || value > maxUiInputSpeed) throw new Error(`HDC ${operation} speed must be a finite number between ${minUiInputSpeed} and ${maxUiInputSpeed}`);
76
+ return Math.round(value);
77
+ }
61
78
  function resolveHdcPath(hdcPath) {
62
79
  if (hdcPath) return hdcPath;
63
80
  if (process.env.HDC_HOME) {
@@ -119,6 +136,26 @@ class HdcClient {
119
136
  async shell(command) {
120
137
  return this.exec('shell', command);
121
138
  }
139
+ assertUiInputSucceeded(operation, output, detail) {
140
+ const errorMatch = output.match(uiInputErrorPattern);
141
+ if (!errorMatch) return;
142
+ const detailText = detail ? ` for ${detail}` : '';
143
+ throw new Error(`HDC uiInput ${operation} failed${detailText}: ${errorMatch[0]}`);
144
+ }
145
+ async runUiInput(operation, args, detail) {
146
+ const output = await this.shell(`uitest uiInput ${operation} ${args}`);
147
+ this.assertUiInputSucceeded(operation, output, detail);
148
+ }
149
+ buildPointerMovementArgs(operation, fromX, fromY, toX, toY, speed) {
150
+ const args = [
151
+ roundUiCoordinate(fromX, 'fromX'),
152
+ roundUiCoordinate(fromY, 'fromY'),
153
+ roundUiCoordinate(toX, 'toX'),
154
+ roundUiCoordinate(toY, 'toY')
155
+ ];
156
+ if (void 0 !== speed) args.push(roundUiInputSpeed(speed, operation));
157
+ return args.join(' ');
158
+ }
122
159
  async fileSend(localPath, remotePath) {
123
160
  await this.exec('file', 'send', localPath, remotePath);
124
161
  }
@@ -136,63 +173,49 @@ class HdcClient {
136
173
  return output.slice(jsonStart);
137
174
  }
138
175
  async click(x, y) {
139
- await this.shell(`uitest uiInput click ${Math.round(x)} ${Math.round(y)}`);
176
+ await this.runUiInput('click', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
140
177
  }
141
178
  async doubleClick(x, y) {
142
- await this.shell(`uitest uiInput doubleClick ${Math.round(x)} ${Math.round(y)}`);
179
+ await this.runUiInput('doubleClick', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
143
180
  }
144
181
  async longClick(x, y) {
145
- await this.shell(`uitest uiInput longClick ${Math.round(x)} ${Math.round(y)}`);
182
+ await this.runUiInput('longClick', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
146
183
  }
147
184
  async swipe(fromX, fromY, toX, toY, speed) {
148
- const args = [
149
- Math.round(fromX),
150
- Math.round(fromY),
151
- Math.round(toX),
152
- Math.round(toY)
153
- ];
154
- if (void 0 !== speed) args.push(Math.round(speed));
155
- await this.shell(`uitest uiInput swipe ${args.join(' ')}`);
185
+ await this.runUiInput('swipe', this.buildPointerMovementArgs('swipe', fromX, fromY, toX, toY, speed));
156
186
  }
157
187
  async fling(fromX, fromY, toX, toY, speed) {
158
- const args = [
159
- Math.round(fromX),
160
- Math.round(fromY),
161
- Math.round(toX),
162
- Math.round(toY)
163
- ];
164
- if (void 0 !== speed) args.push(Math.round(speed));
165
- await this.shell(`uitest uiInput fling ${args.join(' ')}`);
188
+ await this.runUiInput('fling', this.buildPointerMovementArgs('fling', fromX, fromY, toX, toY, speed));
166
189
  }
167
190
  async drag(fromX, fromY, toX, toY, speed) {
168
- const args = [
169
- Math.round(fromX),
170
- Math.round(fromY),
171
- Math.round(toX),
172
- Math.round(toY)
173
- ];
174
- if (void 0 !== speed) args.push(Math.round(speed));
175
- await this.shell(`uitest uiInput drag ${args.join(' ')}`);
191
+ await this.runUiInput('drag', this.buildPointerMovementArgs('drag', fromX, fromY, toX, toY, speed));
176
192
  }
177
193
  async inputText(x, y, text) {
178
194
  const escapedText = text.replace(/'/g, "'\\''");
179
- await this.shell(`uitest uiInput inputText ${Math.round(x)} ${Math.round(y)} '${escapedText}'`);
195
+ await this.runUiInput('inputText', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')} '${escapedText}'`);
180
196
  }
181
197
  async keyEvent(...keys) {
182
- await this.shell(`uitest uiInput keyEvent ${keys.join(' ')}`);
198
+ if (keys.length < 1 || keys.length > 3) throw new Error('HDC keyEvent requires between 1 and 3 keys');
199
+ if (keys.length > 1 && keys.some((key)=>supportedStringKeyEvents.has(key))) throw new Error('HDC system key events cannot be combined');
200
+ for (const key of keys)if (!supportedStringKeyEvents.has(key) && !numericKeyEventPattern.test(key)) throw new Error(`Invalid HDC key event: ${key}`);
201
+ const joinedKeys = keys.join(' ');
202
+ await this.runUiInput('keyEvent', joinedKeys, joinedKeys);
183
203
  }
184
204
  async clearTextField(length = 100) {
185
- if (length <= 0) return;
186
- const MAX_KEYS_PER_CALL = 3;
205
+ if (!Number.isSafeInteger(length) || length < 0) throw new Error('HDC clearTextField length must be a non-negative safe integer');
206
+ if (0 === length) return;
207
+ const maxKeysPerCall = 3;
208
+ const backspaceKeyCode = '2055';
187
209
  const cmds = [];
188
210
  let remaining = length;
189
211
  while(remaining > 0){
190
- const n = Math.min(MAX_KEYS_PER_CALL, remaining);
191
- const codes = Array(n).fill('2055').join(' ');
212
+ const n = Math.min(maxKeysPerCall, remaining);
213
+ const codes = Array(n).fill(backspaceKeyCode).join(' ');
192
214
  cmds.push(`uitest uiInput keyEvent ${codes}`);
193
215
  remaining -= n;
194
216
  }
195
- await this.shell(cmds.join(';'));
217
+ const output = await this.shell(cmds.join(';'));
218
+ this.assertUiInputSucceeded('clearTextField', output);
196
219
  }
197
220
  async startAbility(bundleName, abilityName) {
198
221
  const output = await this.shell(`aa start -a ${abilityName} -b ${bundleName}`);
@@ -256,6 +279,96 @@ class HdcClient {
256
279
  this.timeout = options.timeout ?? 60000;
257
280
  }
258
281
  }
282
+ const namedHarmonyKeyCodeMap = {
283
+ Enter: '2054',
284
+ Backspace: '2055',
285
+ Tab: '2049',
286
+ Escape: '2070',
287
+ Home: 'Home',
288
+ ArrowUp: '2012',
289
+ ArrowDown: '2013',
290
+ ArrowLeft: '2014',
291
+ ArrowRight: '2015',
292
+ Space: '2050',
293
+ Delete: '2071',
294
+ Alt: '2045',
295
+ Shift: '2047',
296
+ Control: '2072',
297
+ Meta: '2076',
298
+ Back: 'Back',
299
+ Power: 'Power'
300
+ };
301
+ const keyNameAliasMap = new Map([
302
+ [
303
+ 'return',
304
+ 'Enter'
305
+ ],
306
+ [
307
+ 'esc',
308
+ 'Escape'
309
+ ],
310
+ [
311
+ 'up',
312
+ 'ArrowUp'
313
+ ],
314
+ [
315
+ 'down',
316
+ 'ArrowDown'
317
+ ],
318
+ [
319
+ 'left',
320
+ 'ArrowLeft'
321
+ ],
322
+ [
323
+ 'right',
324
+ 'ArrowRight'
325
+ ],
326
+ [
327
+ 'ctrl',
328
+ 'Control'
329
+ ]
330
+ ]);
331
+ const caseInsensitiveNamedKeyCodeMap = new Map(Object.entries(namedHarmonyKeyCodeMap).map(([key, code])=>[
332
+ key.toLowerCase(),
333
+ code
334
+ ]));
335
+ function numericKeyCode(value) {
336
+ return String(value);
337
+ }
338
+ function resolveSingleHarmonyKeyCodes(keyName) {
339
+ const trimmedKeyName = keyName.trim();
340
+ if (!trimmedKeyName) {
341
+ if (0 === keyName.length) throw new Error('Unsupported HarmonyOS key: empty key name');
342
+ throw unsupportedKeyboardPressKey(keyName);
343
+ }
344
+ const alias = keyNameAliasMap.get(trimmedKeyName.toLowerCase());
345
+ const normalizedKeyName = alias ?? trimmedKeyName;
346
+ if (/^[0-9]$/.test(normalizedKeyName)) return [
347
+ numericKeyCode(2000 + Number(normalizedKeyName))
348
+ ];
349
+ const letterKeyMatch = normalizedKeyName.match(/^[a-z]$/i);
350
+ if (letterKeyMatch) {
351
+ const alphabetIndex = letterKeyMatch[0].toUpperCase().charCodeAt(0) - 65;
352
+ return [
353
+ numericKeyCode(2017 + alphabetIndex)
354
+ ];
355
+ }
356
+ const namedCode = caseInsensitiveNamedKeyCodeMap.get(normalizedKeyName.toLowerCase());
357
+ if (namedCode) return [
358
+ namedCode
359
+ ];
360
+ throw unsupportedKeyboardPressKey(keyName);
361
+ }
362
+ function unsupportedKeyboardPressKey(keyName) {
363
+ return new Error(`Unsupported HarmonyOS keyboardPress key: ${JSON.stringify(keyName)}. Use typeText() for text input, or pass a supported key name such as "Enter", "A", or "1".`);
364
+ }
365
+ function resolveHarmonyKeyCodes(keyName) {
366
+ const trimmedKeyName = keyName.trim();
367
+ if (!trimmedKeyName && ' ' !== keyName) return resolveSingleHarmonyKeyCodes(keyName);
368
+ if ('+' === trimmedKeyName) return resolveSingleHarmonyKeyCodes(keyName);
369
+ if (trimmedKeyName.includes('+')) throw new Error(`HarmonyOS keyboardPress does not support key combinations: ${JSON.stringify(keyName)}`);
370
+ return resolveSingleHarmonyKeyCodes(keyName);
371
+ }
259
372
  function device_define_property(obj, key, value) {
260
373
  if (key in obj) Object.defineProperty(obj, key, {
261
374
  value: value,
@@ -314,37 +427,6 @@ function pickFieldByPoint(fields, point) {
314
427
  function pickLongestField(fields) {
315
428
  return fields.reduce((a, b)=>b.text.length > a.text.length ? b : a);
316
429
  }
317
- const harmonyKeyCodeMap = {
318
- Enter: '2054',
319
- Backspace: '2055',
320
- Tab: '2049',
321
- Escape: '2070',
322
- Home: 'Home',
323
- ArrowUp: '2012',
324
- ArrowDown: '2013',
325
- ArrowLeft: '2014',
326
- ArrowRight: '2015',
327
- Space: '2050',
328
- Delete: '2071'
329
- };
330
- const keyNameAliasMap = {
331
- enter: 'Enter',
332
- backspace: 'Backspace',
333
- tab: 'Tab',
334
- escape: 'Escape',
335
- esc: 'Escape',
336
- home: 'Home',
337
- space: 'Space',
338
- delete: 'Delete',
339
- arrowup: 'ArrowUp',
340
- arrowdown: 'ArrowDown',
341
- arrowleft: 'ArrowLeft',
342
- arrowright: 'ArrowRight',
343
- up: 'ArrowUp',
344
- down: 'ArrowDown',
345
- left: 'ArrowLeft',
346
- right: 'ArrowRight'
347
- };
348
430
  class HarmonyDevice {
349
431
  actionSpace() {
350
432
  const mobileActionContext = {
@@ -588,10 +670,9 @@ class HarmonyDevice {
588
670
  }
589
671
  }
590
672
  async pressKey(key) {
591
- const normalizedKey = keyNameAliasMap[key.toLowerCase()] ?? key;
592
- const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
673
+ const keyCodes = resolveHarmonyKeyCodes(key);
593
674
  const hdc = await this.getHdc();
594
- await hdc.keyEvent(harmonyKey);
675
+ await hdc.keyEvent(...keyCodes);
595
676
  }
596
677
  async scroll(deltaX, deltaY, speed) {
597
678
  if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
@@ -746,13 +827,13 @@ class HarmonyDevice {
746
827
  }
747
828
  async recentApps() {
748
829
  const hdc = await this.getHdc();
749
- await hdc.keyEvent('RecentApps');
830
+ await hdc.keyEvent('10011');
750
831
  }
751
832
  async hideKeyboard(options) {
752
833
  const hdc = await this.getHdc();
753
834
  const keyboardDismissStrategy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy ?? 'esc-first';
754
- const key = 'back-first' === keyboardDismissStrategy ? 'Back' : harmonyKeyCodeMap.Escape;
755
- await hdc.keyEvent(key);
835
+ if ('back-first' === keyboardDismissStrategy) return void await hdc.keyEvent('Back');
836
+ await hdc.keyEvent(...resolveHarmonyKeyCodes('Escape'));
756
837
  }
757
838
  async getDeviceLocalTimeString(format = 'YYYY-MM-DD HH:mm:ss') {
758
839
  const hdc = await this.getHdc();
@@ -1093,7 +1174,7 @@ class HarmonyMidsceneTools extends BaseMidsceneTools {
1093
1174
  const tools = new HarmonyMidsceneTools();
1094
1175
  runToolsCLI(tools, 'midscene-harmony', {
1095
1176
  stripPrefix: 'harmony_',
1096
- version: "1.10.11",
1177
+ version: "1.10.12-beta-20260812055946.0",
1097
1178
  extraCommands: createReportCliCommands()
1098
1179
  }).catch((e)=>{
1099
1180
  process.exit(reportCLIError(e));