shellx-ai 1.0.12 → 1.1.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +586 -0
  3. package/dist/automation/element-finder.d.ts +189 -0
  4. package/dist/automation/element-finder.js +322 -0
  5. package/dist/automation/element-finder.js.map +1 -0
  6. package/dist/automation/ui-action-handler.d.ts +330 -0
  7. package/dist/automation/ui-action-handler.js +873 -0
  8. package/dist/automation/ui-action-handler.js.map +1 -0
  9. package/dist/cbor-compat.d.ts +27 -0
  10. package/dist/cbor-compat.js +108 -0
  11. package/dist/cbor-compat.js.map +1 -0
  12. package/dist/domain-manager.d.ts +80 -0
  13. package/dist/domain-manager.js +158 -0
  14. package/dist/domain-manager.js.map +1 -0
  15. package/dist/error-handler.d.ts +87 -0
  16. package/dist/error-handler.js +148 -0
  17. package/dist/error-handler.js.map +1 -0
  18. package/dist/errors.d.ts +114 -0
  19. package/dist/errors.js +139 -0
  20. package/dist/errors.js.map +1 -0
  21. package/dist/index.d.ts +163 -54
  22. package/dist/index.js +706 -480
  23. package/dist/index.js.map +1 -0
  24. package/dist/logger.d.ts +81 -0
  25. package/dist/logger.js +128 -0
  26. package/dist/logger.js.map +1 -0
  27. package/dist/protocol.d.ts +147 -31
  28. package/dist/protocol.js +2 -2
  29. package/dist/protocol.js.map +1 -0
  30. package/dist/shell/output-buffer.d.ts +152 -0
  31. package/dist/shell/output-buffer.js +163 -0
  32. package/dist/shell/output-buffer.js.map +1 -0
  33. package/dist/shell/shell-command-executor.d.ts +182 -0
  34. package/dist/shell/shell-command-executor.js +348 -0
  35. package/dist/shell/shell-command-executor.js.map +1 -0
  36. package/dist/shellx.d.ts +681 -178
  37. package/dist/shellx.js +762 -1159
  38. package/dist/shellx.js.map +1 -0
  39. package/dist/types.d.ts +132 -57
  40. package/dist/types.js +4 -4
  41. package/dist/types.js.map +1 -0
  42. package/dist/utils/retry-helper.d.ts +73 -0
  43. package/dist/utils/retry-helper.js +92 -0
  44. package/dist/utils/retry-helper.js.map +1 -0
  45. package/dist/utils.d.ts +3 -3
  46. package/dist/utils.js +17 -23
  47. package/dist/utils.js.map +1 -0
  48. package/package.json +95 -62
package/dist/shellx.js CHANGED
@@ -1,1222 +1,825 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.ShellX = void 0;
16
- exports.createShellX = createShellX;
17
- exports.createShellXWithShellMonitoring = createShellXWithShellMonitoring;
18
- const uuid_1 = require("uuid");
19
- const index_1 = __importDefault(require("./index"));
20
- const COMMAND_PTY_SID = 999;
21
1
  /**
22
- * ShellX automation utilities for common patterns
2
+ * ShellX - High-level automation utilities for Android device control
3
+ *
4
+ * This module provides a comprehensive API for automating Android devices,
5
+ * including UI actions, shell commands, element finding, and device information.
6
+ *
7
+ * @module shellx
23
8
  */
24
- class ShellX {
25
- constructor(client) {
26
- this.client = client;
27
- this.shellOutputBuffers = new Map();
28
- this.shellCommandPromises = new Map();
29
- // 注意:由于 WebSocketTaskClient 的 config 是私有的,
30
- // 监听 shell 输出需要在创建 WebSocketTaskClient 时设置 onMessage 回调
31
- console.log('⚠️ [Shell] 注意:要监听 shell 输出,请在创建 WebSocketTaskClient 时设置 onMessage 回调');
32
- }
33
- /**
34
- * Get the WebSocket client instance
9
+ // Import refactored modules
10
+ import { UIActionHandler } from "./automation/ui-action-handler.js";
11
+ import { ElementFinder } from "./automation/element-finder.js";
12
+ import { ShellCommandExecutor } from "./shell/shell-command-executor.js";
13
+ // Import client
14
+ import ConnectionClient from "./index.js";
15
+ import { createLogger } from "./logger.js";
16
+ /**
17
+ * ShellX - High-level Android automation SDK (Recommended API)
18
+ *
19
+ * **This is the recommended API for 95% of users.**
20
+ *
21
+ * ShellX provides a simple, intuitive interface for Android automation with:
22
+ *
23
+ * - ✅ Automatic error handling and retry logic
24
+ * - ✅ Type-safe TypeScript API
25
+ * - ✅ Consistent method signatures
26
+ * - ✅ Built-in timing and performance tracking
27
+ * - ✅ Simplified element selection
28
+ *
29
+ * **Key Features:**
30
+ * - UI interaction: click, input, swipe, press, wait
31
+ * - Element finding with smart selectors
32
+ * - Shell command execution
33
+ * - Screen info and screenshots
34
+ * - Batch operations
35
+ *
36
+ * **Quick Start:**
37
+ * ```typescript
38
+ * import { ShellX } from '@shellx-ai/sdk';
39
+ *
40
+ * // Initialize
41
+ * const shellx = new ShellX({ deviceId: 'your-device-id' });
42
+ *
43
+ * // Connect
44
+ * await shellx.connect();
45
+ *
46
+ * // Automate
47
+ * await shellx.click('Submit');
48
+ * await shellx.input({ text: 'Hello World' });
49
+ * await shellx.swipe({ fromX: 500, fromY: 1000, toX: 500, toY: 500 });
50
+ *
51
+ * // Get info
52
+ * const screen = await shellx.getScreenInfo();
53
+ * console.log(`Screen: ${screen.width}x${screen.height}`);
54
+ * ```
55
+ *
56
+ * **Note:** For low-level WebSocket access, see ConnectionClient (advanced users only).
57
+ *
58
+ * @see API-GUIDE.md for comprehensive documentation
59
+ * @see ConnectionClient for low-level protocol access
60
+ */
61
+ export class ShellX {
62
+ /** Logger instance for ShellX */
63
+ logger;
64
+ /** UI action handler for executing UI operations */
65
+ uiActionHandler;
66
+ /** Element finder for locating UI elements */
67
+ elementFinder;
68
+ /** Shell command executor for running shell commands */
69
+ shellCommandExecutor;
70
+ /** Connection client for device communication */
71
+ client;
72
+ /**
73
+ * Creates a ShellX instance
74
+ *
75
+ * @param options - Configuration options for ShellX instance
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * const shellx = new ShellX({
80
+ * deviceId: 'device-id',
81
+ * onOpen: () => console.log('Connected!'),
82
+ * onMessage: (msg) => console.log(msg)
83
+ * });
84
+ *
85
+ * // Optionally wait for connection
86
+ * await shellx.ready();
87
+ *
88
+ * // Start using ShellX
89
+ * await shellx.click('Settings');
90
+ * ```
91
+ */
92
+ constructor(options) {
93
+ // Initialize logger with provided log level
94
+ this.logger = createLogger("ShellX", options.logLevel);
95
+ // Create ConnectionClient with options and logger
96
+ this.client = new ConnectionClient(options.deviceId, {
97
+ timeout: options.timeout ?? 5000,
98
+ reconnect: options.reconnect ?? true,
99
+ reconnectMaxAttempts: options.reconnectMaxAttempts ?? 5,
100
+ reconnectInterval: options.reconnectInterval ?? 1000,
101
+ pingInterval: options.pingInterval ?? 2000,
102
+ onOpen: () => {
103
+ this.logger.info("✅ WebSocket connection opened");
104
+ options.onOpen?.();
105
+ },
106
+ onClose: () => {
107
+ options.onClose?.();
108
+ },
109
+ onError: (error) => {
110
+ options.onError?.(error);
111
+ },
112
+ onReconnectFailed: () => {
113
+ options.onReconnectFailed?.();
114
+ },
115
+ onMessage: (message) => {
116
+ // Forward shell output to ShellX instance
117
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
118
+ const wsMessage = message;
119
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
120
+ if (wsMessage.chunks) {
121
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
122
+ this.handleShellOutput(wsMessage.chunks);
123
+ }
124
+ // Call user's onMessage callback
125
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
126
+ options.onMessage?.(message);
127
+ },
128
+ }, this.logger);
129
+ // Initialize handlers
130
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
131
+ this.uiActionHandler = new UIActionHandler(this.client);
132
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
133
+ this.elementFinder = new ElementFinder(this.client);
134
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
135
+ this.shellCommandExecutor = new ShellCommandExecutor(this.client);
136
+ // Link client and shellx
137
+ this.client.setShellX(this);
138
+ this.logger.info("⏳ Initializing connection...");
139
+ }
140
+ /**
141
+ * Wait for connection to be ready
142
+ *
143
+ * This method waits for the WebSocket connection to be established.
144
+ * Most operations will automatically wait for connection, but you can
145
+ * call this method explicitly if you need to ensure connection before
146
+ * performing operations.
147
+ *
148
+ * @returns Promise that resolves when connection is ready
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * const shellx = new ShellX({ deviceId: 'device-id' });
153
+ * await shellx.ready();
154
+ * console.log('Connection is ready!');
155
+ * ```
156
+ */
157
+ async ready() {
158
+ await this.client.waitForInitialization();
159
+ this.logger.info("✅ Connection is ready");
160
+ }
161
+ /**
162
+ * Get the underlying ConnectionClient instance
163
+ *
164
+ * @returns The ConnectionClient instance
35
165
  */
36
166
  getClient() {
37
167
  return this.client;
38
168
  }
39
169
  /**
40
- * 设置 shell 输出监听器(需要在创建 WebSocketTaskClient 时调用)
170
+ * Get the current log level
171
+ *
172
+ * @returns Current log level
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * const shellx = new ShellX({ deviceId: 'device-id' });
177
+ * console.log('Current log level:', shellx.getLogLevel());
178
+ * ```
41
179
  */
42
- static createShellOutputHandler(helpers) {
43
- return (message) => {
44
- // 处理 chunks 数据(pty 终端输出)
45
- if (message.chunks) {
46
- helpers.handleShellOutput(message.chunks);
47
- }
48
- };
180
+ getLogLevel() {
181
+ return this.logger.getLogLevel();
49
182
  }
50
183
  /**
51
- * 处理 shell 输出数据
184
+ * Set the log level
185
+ *
186
+ * @param level - Log level to set
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * import { ShellX, LogLevel } from '@shellx-ai/sdk';
191
+ *
192
+ * const shellx = new ShellX({ deviceId: 'device-id' });
193
+ * shellx.setLogLevel(LogLevel.NONE); // Disable all logging
194
+ * shellx.setLogLevel(LogLevel.ERROR); // Only errors
195
+ * shellx.setLogLevel(LogLevel.DEBUG); // All logs
196
+ * ```
52
197
  */
53
- handleShellOutput(chunks) {
54
- const [sessionId, len, dataArrays] = chunks;
55
- if (sessionId === COMMAND_PTY_SID) {
56
- try {
57
- // 将 Uint8Array 数组转换为字符串
58
- let output = '';
59
- for (const data of dataArrays) {
60
- output += new TextDecoder().decode(data);
61
- }
62
- /*console.log(
63
- `📟 [Shell] 收到输出 (Session ${sessionId}): ${output.trim()}`,
64
- );*/
65
- // 为每个等待的命令累积输出
66
- for (const [commandKey, commandPromise,] of this.shellCommandPromises.entries()) {
67
- if (!commandPromise.sessionOutputs.has(sessionId)) {
68
- commandPromise.sessionOutputs.set(sessionId, '');
69
- }
70
- const currentSessionOutput = commandPromise.sessionOutputs.get(sessionId) || '';
71
- commandPromise.sessionOutputs.set(sessionId, currentSessionOutput + output);
72
- commandPromise.output = this.combineSessionOutputs(commandPromise.sessionOutputs);
73
- /*console.log(
74
- `📊 [Shell] 命令 ${commandKey} 累积输出长度: ${commandPromise.output.length}`,
75
- );*/
76
- // 调用输出回调(传递清理后的输出)
77
- if (commandPromise.options.onOutput) {
78
- const cleanOutput = this.cleanCommandOutput(output, commandPromise.command);
79
- commandPromise.options.onOutput(cleanOutput);
80
- }
81
- }
82
- }
83
- catch (error) {
84
- console.error(`❌ [Shell] 处理输出数据失败:`, error);
85
- }
86
- }
198
+ setLogLevel(level) {
199
+ this.logger.setLogLevel(level);
87
200
  }
88
201
  /**
89
- * 清理命令输出,去除输入的命令内容
202
+ * Enable all logging (equivalent to setLogLevel(LogLevel.DEBUG))
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * const shellx = new ShellX({ deviceId: 'device-id' });
207
+ * shellx.enableDebugLogging();
208
+ * ```
90
209
  */
91
- cleanCommandOutput(output, command) {
92
- return output.replace(command, '');
210
+ enableDebugLogging() {
211
+ this.logger.enableDebugLogging();
93
212
  }
94
213
  /**
95
- * 合并多个 session 的输出
214
+ * Disable all logging (equivalent to setLogLevel(LogLevel.NONE))
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const shellx = new ShellX({ deviceId: 'device-id' });
219
+ * shellx.disableLogging();
220
+ * ```
96
221
  */
97
- combineSessionOutputs(sessionOutputs) {
98
- const sessions = Array.from(sessionOutputs.keys()).sort();
99
- let combined = '';
100
- for (const sessionId of sessions) {
101
- const sessionOutput = sessionOutputs.get(sessionId) || '';
102
- combined += sessionOutput;
103
- }
104
- return combined;
222
+ disableLogging() {
223
+ this.logger.disableLogging();
105
224
  }
106
225
  /**
107
- * 通用重试机制 - 失败时返回 undefined 而不是抛出异常
226
+ * Ensure connection is ready before executing device operations
227
+ *
228
+ * @param timeout - Timeout in milliseconds (default: 10000ms)
229
+ * @throws Error if connection timeout
108
230
  */
109
- withRetry(operation_1) {
110
- return __awaiter(this, arguments, void 0, function* (operation, options = {}) {
111
- const { retry = 3, delay = 1000, onRetry } = options;
112
- for (let attempt = 1; attempt <= retry; attempt++) {
113
- try {
114
- return yield operation();
115
- }
116
- catch (error) {
117
- if (attempt === retry) {
118
- console.log(`❌ [Retry] 重试 ${retry} 次后仍然失败,返回 undefined:`, error);
119
- return undefined;
120
- }
121
- if (onRetry) {
122
- onRetry(attempt, error);
123
- }
124
- console.log(`🔄 [Retry] 第 ${attempt} 次尝试失败,${delay}ms 后重试...`);
125
- yield new Promise(resolve => setTimeout(resolve, delay));
126
- }
127
- }
128
- return undefined;
129
- });
231
+ async ensureConnectionReady(timeout = 10000) {
232
+ await this.client.ensureConnected(timeout);
130
233
  }
131
234
  /**
132
- * 转换精简选择器为原始选择器
235
+ * Send chat message through ShellX
236
+ *
237
+ * @param message - The chat message to send
238
+ * @returns Promise that resolves when the message is sent
133
239
  */
134
- convertSelector(selector) {
240
+ async sendChat(message) {
241
+ return this.client.sendChat(message);
242
+ }
243
+ async click(selectorOrData, options) {
244
+ await this.ensureConnectionReady();
245
+ if (typeof selectorOrData === "string") {
246
+ return this.uiActionHandler.click({ text: selectorOrData, ...options });
247
+ }
248
+ return this.uiActionHandler.click(selectorOrData);
249
+ }
250
+ /**
251
+ * Execute an input action
252
+ *
253
+ * @param inputData - Input configuration
254
+ * @returns Promise resolving to InputResult
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * const result = await shellx.input({
259
+ * elementId: 'field123',
260
+ * text: 'Hello World',
261
+ * clear: true
262
+ * });
263
+ * ```
264
+ */
265
+ async input(inputData) {
266
+ await this.ensureConnectionReady();
267
+ return this.uiActionHandler.input(inputData);
268
+ }
269
+ /**
270
+ * Execute a swipe action
271
+ *
272
+ * @param swipeData - Swipe configuration
273
+ * @returns Promise resolving to SwipeResult
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * const result = await shellx.swipe({
278
+ * fromX: 500,
279
+ * fromY: 1000,
280
+ * toX: 500,
281
+ * toY: 500,
282
+ * duration: 800
283
+ * });
284
+ * ```
285
+ */
286
+ async swipe(swipeData) {
287
+ await this.ensureConnectionReady();
288
+ return this.uiActionHandler.swipe(swipeData);
289
+ }
290
+ async press(keyOrData, options) {
291
+ await this.ensureConnectionReady();
292
+ if (typeof keyOrData === "string") {
293
+ return this.uiActionHandler.pressKey({ key: keyOrData, ...options });
294
+ }
295
+ return this.uiActionHandler.pressKey(keyOrData);
296
+ }
297
+ async wait(selectorOrData, options) {
298
+ await this.ensureConnectionReady();
299
+ if (typeof selectorOrData === "string") {
300
+ return this.uiActionHandler.wait({ text: selectorOrData, ...options });
301
+ }
302
+ return this.uiActionHandler.wait(selectorOrData);
303
+ }
304
+ async find(selectorOrData, options) {
305
+ await this.ensureConnectionReady();
306
+ const startTime = Date.now();
307
+ const findData = typeof selectorOrData === "string" ? { text: selectorOrData, ...options } : selectorOrData;
308
+ const selector = this.uiActionHandler["convertSelector"](findData);
309
+ const result = await this.client.findElement(selector, {
310
+ pressClick: findData.pressClick,
311
+ waitAfterMs: findData.waitAfterMs || 5000,
312
+ maxResults: findData.maxResults || 1000,
313
+ visibleOnly: true,
314
+ clickableOnly: false,
315
+ multiple: findData.multiple || false,
316
+ });
317
+ if (!result || result.success === false || !result.elements) {
318
+ return {
319
+ elements: [],
320
+ count: 0,
321
+ success: false,
322
+ found: false,
323
+ duration: 0,
324
+ timestamp: startTime,
325
+ };
326
+ }
135
327
  return {
136
- elementId: selector.targetElementId,
137
- resourceId: selector.targetResourceId,
138
- className: selector.targetClass,
139
- text: selector.targetText,
140
- textContains: undefined,
141
- visible: selector.visible,
142
- clickable: selector.clickable
328
+ elements: result.elements.map((el) => ({
329
+ id: el.elementId,
330
+ text: el.text,
331
+ class: el.className,
332
+ left: el.bounds.left,
333
+ top: el.bounds.top,
334
+ right: el.bounds.right,
335
+ bottom: el.bounds.bottom,
336
+ visible: el.visible,
337
+ clickable: el.clickable,
338
+ })),
339
+ count: result.elements.length,
340
+ success: true,
341
+ found: true,
342
+ duration: 0,
343
+ timestamp: startTime,
143
344
  };
144
345
  }
145
- /**
146
- * 转换精简元素为原始元素
147
- */
148
- convertElement(uiElement) {
346
+ async command(cmdOrData, options) {
347
+ await this.ensureConnectionReady();
348
+ const startTime = Date.now();
349
+ const commandData = typeof cmdOrData === "string" ? { cmd: cmdOrData, ...options } : cmdOrData;
350
+ const result = await this.shellCommandExecutor.executeShellCommand(commandData.cmd, {
351
+ title: `Execute command: ${commandData.cmd}`,
352
+ timeout: commandData.timeout,
353
+ waitAfterMs: commandData.wait,
354
+ });
355
+ this.getClient().appendExecutionLog(`✅ Command executed successfully - Output: ${result.output}`);
149
356
  return {
150
- id: uiElement.elementId,
151
- text: uiElement.text,
152
- class: uiElement.className,
153
- left: uiElement.bounds.left,
154
- top: uiElement.bounds.top,
155
- right: uiElement.bounds.right,
156
- bottom: uiElement.bounds.bottom,
157
- visible: uiElement.visible,
158
- clickable: uiElement.clickable
357
+ success: result.success,
358
+ output: result.output,
359
+ error: result.error,
360
+ exitCode: result.exitCode,
361
+ duration: result.duration,
362
+ cmd: commandData.cmd,
363
+ timestamp: startTime,
159
364
  };
160
365
  }
161
- // ==================== 精简 Action 封装函数 ====================
162
366
  /**
163
- * 点击操作 - 支持元素ID、坐标或选择器
164
- */
165
- click(clickData) {
166
- return __awaiter(this, void 0, void 0, function* () {
167
- const startTime = Date.now();
168
- const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
169
- try {
170
- let target;
171
- if (clickData.targetElementId) {
172
- // 元素ID
173
- target = { type: "elementId", value: clickData.targetElementId };
174
- }
175
- else if (clickData.targetResourceId) {
176
- // 精简元素
177
- target = { type: "resourceId", value: clickData.targetResourceId };
178
- }
179
- else if (clickData.targetX !== undefined && clickData.targetY !== undefined) {
180
- // 坐标
181
- target = { type: "coordinate", value: { x: clickData.targetX, y: clickData.targetY } };
182
- }
183
- else if (clickData.targetText || clickData.targetClass) {
184
- // 选择器
185
- const element = yield this.findElementWithRetry(this.convertSelector(clickData), clickData.retry || 3);
186
- if (!element) {
187
- throw new Error('未找到目标元素');
188
- }
189
- target = { type: "elementId", value: element.elementId };
190
- }
191
- else {
192
- throw new Error('必须指定目标:targetElementId、targetResourceId、坐标(targetX/targetY)或选择器(targetText/targetClass)');
193
- }
194
- const action = {
195
- title: `点击操作: ${clickData.clickType || 'single'}`,
196
- actions: [{
197
- type: "click",
198
- target,
199
- options: {
200
- clickType: clickData.clickType || 'single',
201
- waitAfterMs: clickData.wait || 1000
202
- }
203
- }]
204
- };
205
- yield this.client.executeAction(action);
206
- return {
207
- success: true,
208
- data: {
209
- targetElementId: clickData.targetElementId,
210
- targetResourceId: clickData.targetResourceId,
211
- targetText: clickData.targetText,
212
- targetClass: clickData.targetClass,
213
- targetX: clickData.targetX,
214
- targetY: clickData.targetY,
215
- clickType: clickData.clickType
216
- },
217
- duration: Date.now() - startTime
218
- };
219
- }
220
- catch (error) {
221
- throw new Error(`点击操作失败: ${error instanceof Error ? error.message : String(error)}`);
222
- }
223
- }), { retry: clickData.retry, delay: 500 });
224
- // 如果 withRetry 返回 undefined,返回失败的 ActionResult
225
- if (!result) {
226
- return {
227
- success: false,
228
- error: '点击操作失败',
229
- duration: Date.now() - startTime
230
- };
231
- }
232
- return result;
233
- });
367
+ * Execute a clipboard action
368
+ *
369
+ * @param clipboardData - Clipboard configuration
370
+ * @returns Promise resolving to ClipboardResult
371
+ *
372
+ * @example
373
+ * ```typescript
374
+ * // Get clipboard content
375
+ * const result = await shellx.clipboard({ get: true });
376
+ * console.log(result.text);
377
+ *
378
+ * // Set clipboard content
379
+ * await shellx.clipboard({ text: 'Hello' });
380
+ *
381
+ * // Paste clipboard content
382
+ * await shellx.clipboard({ paste: true });
383
+ * ```
384
+ */
385
+ async clipboard(clipboardData) {
386
+ await this.ensureConnectionReady();
387
+ const result = await this.uiActionHandler.clipboard(clipboardData);
388
+ return {
389
+ ...result,
390
+ text: result.text,
391
+ pasted: clipboardData.paste ? result.success : undefined,
392
+ getCopied: clipboardData.get ? result.success : undefined,
393
+ };
234
394
  }
235
- /**
236
- * 输入操作 - 支持元素ID、资源ID或选择器
237
- */
238
- input(inputData) {
239
- return __awaiter(this, void 0, void 0, function* () {
240
- const startTime = Date.now();
241
- const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
242
- var _a, _b;
243
- try {
244
- let target;
245
- if (inputData.targetElementId) {
246
- target = { type: "elementId", value: inputData.targetElementId };
247
- }
248
- else if (inputData.targetResourceId) {
249
- target = { type: "resourceId", value: inputData.targetResourceId };
250
- }
251
- else if (inputData.targetText || inputData.targetClass) {
252
- const element = yield this.findElementWithRetry(this.convertSelector(inputData), inputData.retry || 3);
253
- if (!element) {
254
- throw new Error('未找到目标元素');
255
- }
256
- target = { type: "elementId", value: element.elementId };
257
- }
258
- else {
259
- throw new Error('必须指定目标:targetElementId、targetResourceId、targetText或targetClass');
260
- }
261
- const action = {
262
- title: `输入文本: ${inputData.text}`,
263
- actions: [{
264
- type: "input",
265
- text: inputData.text,
266
- target,
267
- options: {
268
- replaceExisting: (_a = inputData.clear) !== null && _a !== void 0 ? _a : true,
269
- hideKeyboardAfter: (_b = inputData.hideKeyboard) !== null && _b !== void 0 ? _b : false,
270
- waitAfterMs: inputData.wait || 500
271
- }
272
- }]
273
- };
274
- yield this.client.executeAction(action);
275
- return {
276
- success: true,
277
- data: {
278
- text: inputData.text,
279
- targetElementId: inputData.targetElementId,
280
- targetResourceId: inputData.targetResourceId,
281
- targetText: inputData.targetText,
282
- targetClass: inputData.targetClass
283
- },
284
- duration: Date.now() - startTime
285
- };
286
- }
287
- catch (error) {
288
- throw new Error(`输入操作失败: ${error instanceof Error ? error.message : String(error)}`);
289
- }
290
- }), { retry: inputData.retry, delay: 500 });
291
- if (!result) {
292
- return {
293
- success: false,
294
- error: '输入操作失败',
295
- duration: Date.now() - startTime
296
- };
297
- }
298
- return result;
299
- });
395
+ async getAppInfo(pkgOrData, options) {
396
+ await this.ensureConnectionReady();
397
+ const appInfoData = typeof pkgOrData === "string" ? { package: pkgOrData, ...options } : pkgOrData;
398
+ const result = await this.uiActionHandler.getAppInfo(appInfoData);
399
+ return result;
400
+ }
401
+ /**
402
+ * Get list of installed applications
403
+ *
404
+ * @param options - Options for filtering app list
405
+ * @returns Promise resolving to app list response
406
+ *
407
+ * @example
408
+ * ```typescript
409
+ * // Get all user apps
410
+ * const result = await shellx.getAppList();
411
+ * console.log(`Found ${result.userAppCount} user apps`);
412
+ * result.apps.forEach(app => {
413
+ * console.log(`${app.appName} (${app.packageName})`);
414
+ * });
415
+ *
416
+ * // Get user and system apps
417
+ * const allApps = await shellx.getAppList({
418
+ * includeSystemApps: true,
419
+ * includeDisabledApps: false
420
+ * });
421
+ * ```
422
+ */
423
+ async getAppList(options) {
424
+ await this.ensureConnectionReady();
425
+ return this.uiActionHandler.getAppList(options);
426
+ }
427
+ /**
428
+ * Take a screenshot
429
+ *
430
+ * @param screenshotData - Screenshot configuration
431
+ * @returns Promise resolving to ScreenshotResult
432
+ *
433
+ * @example
434
+ * ```typescript
435
+ * const result = await shellx.takeScreenshot({
436
+ * format: 'png',
437
+ * quality: 100,
438
+ * saveToFile: true
439
+ * });
440
+ *
441
+ * console.log(result.imagePath);
442
+ * ```
443
+ */
444
+ async takeScreenshot(screenshotData = {}) {
445
+ await this.ensureConnectionReady();
446
+ return await this.uiActionHandler.takeScreenshot(screenshotData);
447
+ }
448
+ /**
449
+ * Get screen information
450
+ *
451
+ * @returns Promise resolving to ScreenInfoResult
452
+ *
453
+ * @example
454
+ * ```typescript
455
+ * const screenInfo = await shellx.getScreenInfo();
456
+ * console.log(`Screen: ${screenInfo.width}x${screenInfo.height}`);
457
+ * console.log(`Current app: ${screenInfo.foregroundApp}`);
458
+ * ```
459
+ */
460
+ async getScreenInfo() {
461
+ await this.ensureConnectionReady();
462
+ const startTime = Date.now();
463
+ const info = await this.uiActionHandler.getScreenInfo();
464
+ if (!info || info.success === false) {
465
+ throw new Error(info?.error || "Failed to get screen info");
466
+ }
467
+ return {
468
+ success: true,
469
+ duration: Date.now() - startTime,
470
+ timestamp: startTime,
471
+ width: info.width,
472
+ height: info.height,
473
+ density: info.density,
474
+ screenOn: info.screenOn,
475
+ screenUnlocked: info.screenUnlocked,
476
+ foregroundApp: info.accurateForegroundApp,
477
+ foregroundActivity: info.accurateForegroundActivity,
478
+ model: info.model,
479
+ androidVersion: info.androidVersion,
480
+ manufacturer: info.manufacturer,
481
+ };
300
482
  }
301
483
  /**
302
- * 滑动操作 - 支持坐标点
303
- */
304
- swipe(swipeData) {
305
- return __awaiter(this, void 0, void 0, function* () {
306
- const startTime = Date.now();
307
- const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
308
- try {
309
- const from = { x: swipeData.fromX, y: swipeData.fromY };
310
- const to = { x: swipeData.toX, y: swipeData.toY };
311
- const action = {
312
- title: `滑动操作: ${from.x},${from.y} → ${to.x},${to.y}`,
313
- actions: [{
314
- type: "swipe",
315
- from,
316
- to,
317
- options: {
318
- durationMs: swipeData.duration || 800,
319
- waitAfterMs: swipeData.wait || 500
320
- }
321
- }]
322
- };
323
- yield this.client.executeAction(action);
324
- return {
325
- success: true,
326
- data: { from, to, duration: swipeData.duration },
327
- duration: Date.now() - startTime
328
- };
329
- }
330
- catch (error) {
331
- throw new Error(`滑动操作失败: ${error instanceof Error ? error.message : String(error)}`);
484
+ * Execute multiple actions in sequence
485
+ *
486
+ * @param actions - Array of actions to execute
487
+ * @returns Promise resolving to ExecuteActionsResult
488
+ *
489
+ * @example
490
+ * ```typescript
491
+ * const result = await shellx.executeActions([
492
+ * { text: 'Settings' },
493
+ * { text: 'Accounts' },
494
+ * { cmd: 'ls -la' }
495
+ * ]);
496
+ *
497
+ * console.log(`Success: ${result.successCount}, Failed: ${result.failureCount}`);
498
+ * result.results.forEach((res, index) => {
499
+ * console.log(`Action ${index + 1}: ${res.success ? 'Success' : 'Failed'}`);
500
+ * });
501
+ * ```
502
+ */
503
+ async executeActions(actions) {
504
+ await this.ensureConnectionReady();
505
+ const startTime = Date.now();
506
+ const results = [];
507
+ let successCount = 0;
508
+ let failureCount = 0;
509
+ for (const [index, action] of actions.entries()) {
510
+ try {
511
+ this.logger.info(`🔨 Executing action ${index + 1}/${actions.length}`);
512
+ let result;
513
+ // Check for clipboard first (before click/input which also have text)
514
+ if ("get" in action || "paste" in action) {
515
+ result = await this.clipboard(action);
332
516
  }
333
- }), { retry: swipeData.retry, delay: 500 });
334
- if (!result) {
335
- return {
336
- success: false,
337
- error: '滑动操作失败',
338
- duration: Date.now() - startTime
339
- };
340
- }
341
- return result;
342
- });
343
- }
344
- /**
345
- * 按键操作
346
- */
347
- pressKey(keyData) {
348
- return __awaiter(this, void 0, void 0, function* () {
349
- const startTime = Date.now();
350
- const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
351
- try {
352
- const action = {
353
- title: `按键操作: ${keyData.key}${keyData.longPress ? ' (长按)' : ''}`,
354
- actions: [{
355
- type: "key",
356
- keyCode: keyData.key,
357
- options: {
358
- longPress: keyData.longPress || false
359
- }
360
- }]
361
- };
362
- yield this.client.executeAction(action);
363
- if (keyData.wait) {
364
- yield new Promise(resolve => setTimeout(resolve, keyData.wait));
365
- }
366
- return {
367
- success: true,
368
- data: { key: keyData.key, longPress: keyData.longPress },
369
- duration: Date.now() - startTime
370
- };
517
+ else if ("text" in action && ("elementId" in action || "resourceId" in action)) {
518
+ result = await this.input(action);
371
519
  }
372
- catch (error) {
373
- throw new Error(`按键操作失败: ${error instanceof Error ? error.message : String(error)}`);
520
+ else if ("elementId" in action ||
521
+ "resourceId" in action ||
522
+ "class" in action ||
523
+ "x" in action ||
524
+ "y" in action) {
525
+ result = await this.click(action);
374
526
  }
375
- }), { retry: keyData.retry, delay: 500 });
376
- if (!result) {
377
- return {
378
- success: false,
379
- error: '按键操作失败',
380
- duration: Date.now() - startTime
381
- };
382
- }
383
- return result;
384
- });
385
- }
386
- /**
387
- * 等待操作 - 等待元素出现或消失
388
- */
389
- wait(waitData) {
390
- return __awaiter(this, void 0, void 0, function* () {
391
- const startTime = Date.now();
392
- const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
393
- try {
394
- const selector = this.convertSelector(waitData);
395
- const timeout = waitData.timeout || 10000;
396
- const interval = 500;
397
- const maxAttempts = Math.floor(timeout / interval);
398
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
399
- try {
400
- const result = yield this.client.findElement(selector, {
401
- timeout: interval,
402
- maxResults: 1,
403
- visibleOnly: waitData.condition === 'visible',
404
- clickableOnly: waitData.condition === 'clickable'
405
- });
406
- // 如果服务器返回失败,跳过本次尝试
407
- if (!result || result.success === false) {
408
- yield new Promise(resolve => setTimeout(resolve, interval));
409
- continue;
410
- }
411
- if (result.elements && result.elements.length > 0) {
412
- if (waitData.condition === 'gone') {
413
- // 等待元素消失,继续等待
414
- yield new Promise(resolve => setTimeout(resolve, interval));
415
- continue;
416
- }
417
- else {
418
- // 等待元素出现,已找到
419
- return {
420
- success: true,
421
- data: { element: this.convertElement(result.elements[0]) },
422
- duration: Date.now() - startTime
423
- };
424
- }
425
- }
426
- else if (waitData.condition === 'gone') {
427
- // 等待元素消失,已消失
428
- return {
429
- success: true,
430
- data: { element: null },
431
- duration: Date.now() - startTime
432
- };
433
- }
434
- }
435
- catch (error) {
436
- // 查找失败,继续等待
437
- }
438
- yield new Promise(resolve => setTimeout(resolve, interval));
439
- }
440
- throw new Error(`等待超时: ${waitData.condition || 'visible'}`);
527
+ else if ("fromX" in action && "fromY" in action && "toX" in action && "toY" in action) {
528
+ result = await this.swipe(action);
441
529
  }
442
- catch (error) {
443
- throw new Error(`等待操作失败: ${error instanceof Error ? error.message : String(error)}`);
530
+ else if ("key" in action) {
531
+ result = await this.press(action);
444
532
  }
445
- }), { retry: waitData.retry, delay: 500 });
446
- if (!result) {
447
- return {
448
- success: false,
449
- error: '等待操作失败',
450
- duration: Date.now() - startTime
451
- };
452
- }
453
- return result;
454
- });
455
- }
456
- /**
457
- * 查找操作 - 查找元素
458
- */
459
- find(findData) {
460
- return __awaiter(this, void 0, void 0, function* () {
461
- const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
462
- try {
463
- const selector = this.convertSelector(findData);
464
- const result = yield this.client.findElement(selector, {
465
- pressClick: findData.pressClick,
466
- waitAfterMs: findData.waitAfterMs || 5000,
467
- maxResults: findData.maxResults || 1000,
468
- visibleOnly: true,
469
- clickableOnly: false,
470
- multiple: findData.multiple || false
471
- });
472
- // 检查服务器是否返回了失败响应
473
- if (!result || result.success === false) {
474
- throw new Error((result === null || result === void 0 ? void 0 : result.errorMessage) || '查找元素失败');
475
- }
476
- if (!result.elements) {
477
- throw new Error('未找到元素');
478
- }
479
- const elements = result.elements.map(element => this.convertElement(element));
480
- return {
481
- elements,
482
- count: elements.length,
483
- success: true,
484
- found: result.found,
485
- };
533
+ else if ("condition" in action &&
534
+ ("elementId" in action || "resourceId" in action || "text" in action || "class" in action)) {
535
+ result = await this.wait(action);
486
536
  }
487
- catch (error) {
488
- throw new Error(`查找操作失败: ${error instanceof Error ? error.message : String(error)}`);
537
+ else if ("multiple" in action &&
538
+ ("elementId" in action || "resourceId" in action || "text" in action || "class" in action)) {
539
+ result = await this.find(action);
489
540
  }
490
- }), { retry: findData.retry, delay: 500 });
491
- if (!result) {
492
- return {
493
- elements: [],
494
- count: 0,
495
- success: false,
496
- found: false
497
- };
498
- }
499
- return result;
500
- });
501
- }
502
- /**
503
- * 执行命令 - 兼容现有的 shell 命令执行
504
- */
505
- executeCommand(commandData) {
506
- return __awaiter(this, void 0, void 0, function* () {
507
- const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
508
- try {
509
- const result = yield this.executeShellCommand(commandData.cmd, {
510
- title: `执行命令: ${commandData.cmd}`,
511
- timeout: commandData.timeout,
512
- waitAfterMs: commandData.wait
513
- });
514
- return result;
541
+ else if ("cmd" in action) {
542
+ result = await this.command(action);
515
543
  }
516
- catch (error) {
517
- throw new Error(`命令执行失败: ${error instanceof Error ? error.message : String(error)}`);
544
+ else if ("package" in action) {
545
+ result = await this.getAppInfo(action);
518
546
  }
519
- }), { retry: commandData.retry, delay: 1000 });
520
- if (!result) {
521
- return {
522
- success: false,
523
- output: '',
524
- error: '命令执行失败',
525
- duration: 0
526
- };
527
- }
528
- return result;
529
- });
530
- }
531
- executeCodeEval(context, code, timeout) {
532
- const evalInstance = require('eval');
533
- return evalInstance(code, context, true);
534
- }
535
- executeCode(agentCode, context, timeout) {
536
- return __awaiter(this, void 0, void 0, function* () {
537
- // console.log('executeCode', agentCode);
538
- // Interpreter.global = context;
539
- // const evalFunc = getEvalInstance(context);
540
- // 如果没有指定 timeout,直接执行
541
- if (!timeout) {
542
- return yield this.executeCodeEval(context, agentCode);
543
- }
544
- // 使用 timeout 执行 - 修复:await 应该在 Promise.race 上,而不是在内部的 promise 上
545
- return yield Promise.race([
546
- this.executeCodeEval(context, agentCode),
547
- new Promise((_, reject) => setTimeout(() => reject(new Error(`代码执行超时: ${timeout}ms`)), timeout))
548
- ]);
549
- });
550
- }
551
- /**
552
- * 获取应用信息
553
- */
554
- getAppInfo(appInfoData) {
555
- return __awaiter(this, void 0, void 0, function* () {
556
- const startTime = Date.now();
557
- const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
558
- try {
559
- const action = {
560
- title: `获取应用信息: ${appInfoData.package}`,
561
- actions: [{
562
- type: "get_app_info",
563
- packageName: appInfoData.package
564
- }]
565
- };
566
- yield this.client.executeAction(action);
567
- return {
568
- success: true,
569
- data: { package: appInfoData.package },
570
- duration: Date.now() - startTime
571
- };
547
+ else if ("text" in action) {
548
+ // Text-only action (without elementId/resourceId) is for clipboard
549
+ result = await this.clipboard(action);
572
550
  }
573
- catch (error) {
574
- throw new Error(`获取应用信息失败: ${error instanceof Error ? error.message : String(error)}`);
551
+ else {
552
+ result = await this.takeScreenshot(action);
575
553
  }
576
- }), { retry: appInfoData.retry, delay: 500 });
577
- if (!result) {
578
- return {
579
- success: false,
580
- error: '获取应用信息失败',
581
- duration: Date.now() - startTime
582
- };
583
- }
584
- return result;
585
- });
586
- }
587
- /**
588
- * 截图操作
589
- */
590
- takeScreenshot() {
591
- return __awaiter(this, arguments, void 0, function* (screenshotData = {}) {
592
- const startTime = Date.now();
593
- const result = yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
594
- try {
595
- const options = {
596
- format: screenshotData.format || 'png',
597
- quality: screenshotData.quality,
598
- scale: 0.10,
599
- };
600
- if (screenshotData.regionX !== undefined && screenshotData.regionY !== undefined &&
601
- screenshotData.regionWidth !== undefined && screenshotData.regionHeight !== undefined) {
602
- options.region = {
603
- left: screenshotData.regionX,
604
- top: screenshotData.regionY,
605
- width: screenshotData.regionWidth,
606
- height: screenshotData.regionHeight
607
- };
608
- }
609
- const screenshot = yield this.client.screenShot(options);
610
- // 检查服务器是否返回了失败响应
611
- if (!screenshot || screenshot.success === false) {
612
- throw new Error((screenshot === null || screenshot === void 0 ? void 0 : screenshot.errorMessage) || '截图失败');
613
- }
614
- return {
615
- success: true,
616
- data: screenshot,
617
- duration: Date.now() - startTime
618
- };
554
+ results.push(result);
555
+ if (!result.success) {
556
+ failureCount++;
557
+ this.logger.warn(`⚠️ Action ${index + 1} failed: ${result.error}`);
619
558
  }
620
- catch (error) {
621
- throw new Error(`截图操作失败: ${error instanceof Error ? error.message : String(error)}`);
559
+ else {
560
+ successCount++;
561
+ this.logger.info(`✅ Action ${index + 1} succeeded`);
622
562
  }
623
- }), { retry: screenshotData.retry, delay: 500 });
624
- if (!result) {
625
- return {
563
+ }
564
+ catch (error) {
565
+ const errorResult = {
626
566
  success: false,
627
- error: '截图操作失败',
628
- duration: Date.now() - startTime
567
+ error: error instanceof Error ? error.message : String(error),
568
+ duration: 0,
569
+ timestamp: Date.now(),
629
570
  };
571
+ results.push(errorResult);
572
+ failureCount++;
573
+ this.logger.error(`❌ Action ${index + 1} failed:`, error);
630
574
  }
631
- return result;
632
- });
633
- }
634
- /**
635
- * 执行操作序列 - 支持多个操作连续执行
636
- */
637
- executeActions(actions) {
638
- return __awaiter(this, void 0, void 0, function* () {
639
- const results = [];
640
- for (const [index, action] of actions.entries()) {
641
- try {
642
- console.log(`🔨 [Actions] 执行第 ${index + 1}/${actions.length} 个操作`);
643
- let result;
644
- if ('text' in action && ('targetElementId' in action || 'targetResourceId' in action || 'targetText' in action || 'targetClass' in action)) {
645
- result = yield this.input(action);
646
- }
647
- else if ('targetElementId' in action || 'targetResourceId' in action || 'targetText' in action || 'targetClass' in action || 'targetX' in action || 'targetY' in action) {
648
- result = yield this.click(action);
649
- }
650
- else if ('fromX' in action && 'fromY' in action && 'toX' in action && 'toY' in action) {
651
- result = yield this.swipe(action);
652
- }
653
- else if ('key' in action) {
654
- result = yield this.pressKey(action);
655
- }
656
- else if ('condition' in action && ('targetElementId' in action || 'targetResourceId' in action || 'targetText' in action || 'targetClass' in action)) {
657
- result = yield this.wait(action);
658
- }
659
- else if ('multiple' in action && ('targetElementId' in action || 'targetResourceId' in action || 'targetText' in action || 'targetClass' in action)) {
660
- const findResult = yield this.find(action);
661
- result = {
662
- success: findResult.success,
663
- data: findResult,
664
- duration: 0
665
- };
666
- }
667
- else if ('cmd' in action) {
668
- const cmdResult = yield this.executeCommand(action);
669
- result = {
670
- success: cmdResult.success,
671
- data: cmdResult,
672
- duration: cmdResult.duration
673
- };
674
- }
675
- else if ('package' in action) {
676
- result = yield this.getAppInfo(action);
677
- }
678
- else {
679
- result = yield this.takeScreenshot(action);
680
- }
681
- results.push(result);
682
- // 记录操作结果但不抛出错误,让调用者决定如何处理失败的操作
683
- if (!result.success) {
684
- console.warn(`⚠️ [Actions] 第 ${index + 1} 个操作失败: ${result.error}`);
685
- }
686
- else {
687
- console.log(`✅ [Actions] 第 ${index + 1} 个操作执行成功`);
688
- }
689
- }
690
- catch (error) {
691
- const errorResult = {
692
- success: false,
693
- error: error instanceof Error ? error.message : String(error),
694
- duration: 0
695
- };
696
- results.push(errorResult);
697
- console.error(`❌ [Actions] 第 ${index + 1} 个操作执行失败:`, error);
698
- // 不再抛出错误,继续执行后续操作
699
- }
700
- }
701
- return results;
702
- });
703
- }
704
- /**
705
- * Smart element finder with retry logic
706
- */
707
- findElementWithRetry(selector_1) {
708
- return __awaiter(this, arguments, void 0, function* (selector, maxRetries = 3, retryDelay = 1000) {
709
- for (let i = 0; i < maxRetries; i++) {
710
- try {
711
- const result = yield this.client.findElement(selector, {
712
- timeout: 3000,
713
- visibleOnly: true,
714
- maxResults: 1
715
- });
716
- if (result.elements.length > 0) {
717
- return result.elements[0];
718
- }
719
- }
720
- catch (error) {
721
- console.log(`查找尝试 ${i + 1}/${maxRetries} 失败:`, error);
722
- }
723
- if (i < maxRetries - 1) {
724
- yield new Promise(resolve => setTimeout(resolve, retryDelay));
725
- }
726
- }
727
- return null;
728
- });
729
- }
730
- /**
731
- * Smart multiple elements finder with retry logic
732
- */
733
- findElementsWithRetry(selector_1) {
734
- return __awaiter(this, arguments, void 0, function* (selector, maxRetries = 3, retryDelay = 1000, options) {
735
- var _a, _b, _c;
736
- for (let i = 0; i < maxRetries; i++) {
737
- try {
738
- const result = yield this.client.findElement(selector, {
739
- timeout: 3000,
740
- visibleOnly: (_a = options === null || options === void 0 ? void 0 : options.visibleOnly) !== null && _a !== void 0 ? _a : true,
741
- clickableOnly: (_b = options === null || options === void 0 ? void 0 : options.clickableOnly) !== null && _b !== void 0 ? _b : false,
742
- multiple: true,
743
- maxResults: (_c = options === null || options === void 0 ? void 0 : options.maxResults) !== null && _c !== void 0 ? _c : 10
744
- });
745
- if (result.elements.length > 0) {
746
- console.log(`🔍 [FindElements] 找到 ${result.elements.length} 个元素`);
747
- return result.elements;
748
- }
749
- }
750
- catch (error) {
751
- console.log(`查找尝试 ${i + 1}/${maxRetries} 失败:`, error);
752
- }
753
- if (i < maxRetries - 1) {
754
- yield new Promise(resolve => setTimeout(resolve, retryDelay));
755
- }
756
- }
757
- console.log(`❌ [FindElements] 未找到任何元素`);
758
- return [];
759
- });
760
- }
761
- /**
762
- * 打印元素信息的工具方法
763
- */
764
- printElementInfo(element, index) {
765
- const prefix = index !== undefined ? `📋 元素 ${index + 1}:` : `📋 元素信息:`;
766
- console.log(`\n${prefix}`);
767
- console.log(` - Element ID: ${element.elementId}`);
768
- console.log(` - Class Name: ${element.className}`);
769
- console.log(` - Resource ID: ${element.resourceId}`);
770
- console.log(` - Text: "${element.text}"`);
771
- console.log(` - Describe: "${element.describe}"`);
772
- console.log(` - Visible: ${element.visible}`);
773
- console.log(` - Clickable: ${element.clickable}`);
774
- console.log(` - Bounds: {left: ${element.bounds.left}, top: ${element.bounds.top}, right: ${element.bounds.right}, bottom: ${element.bounds.bottom}}`);
775
- console.log(` - Size: ${element.bounds.right - element.bounds.left} x ${element.bounds.bottom - element.bounds.top}`);
776
- }
777
- /**
778
- * 打印多个元素信息的工具方法
779
- */
780
- printElementsInfo(elements, title) {
781
- if (elements.length === 0) {
782
- console.log('❌ 没有元素可以打印');
783
- return;
784
575
  }
785
- console.log(`\n${title || `✅ 找到 ${elements.length} 个元素`}:`);
786
- /*
787
- elements.forEach((element, index) => {
788
- this.printElementInfo(element, index);
789
- });*/
790
- // 统计信息
791
- const visibleCount = elements.filter(e => e.visible).length;
792
- const clickableCount = elements.filter(e => e.clickable).length;
793
- const withTextCount = elements.filter(e => e.text && e.text.trim().length > 0).length;
794
- console.log(`\n📊 统计信息:`);
795
- console.log(` - 总共元素: ${elements.length}`);
796
- console.log(` - 可见元素: ${visibleCount}`);
797
- console.log(` - 可点击元素: ${clickableCount}`);
798
- console.log(` - 有文本内容元素: ${withTextCount}`);
799
- // 展示不同的文本内容
800
- const uniqueTexts = [...new Set(elements.map(e => e.text).filter(text => text && text.trim().length > 0))];
801
- if (uniqueTexts.length > 0) {
802
- console.log(`\n📝 不同的文本内容:`);
803
- uniqueTexts.forEach((text, index) => {
804
- console.log(` ${index + 1}. "${text}"`);
805
- });
806
- }
807
- }
808
- /**
809
- * Click element by text content
810
- */
811
- clickByText(text_1) {
812
- return __awaiter(this, arguments, void 0, function* (text, exact = false) {
813
- const selector = exact
814
- ? { text, clickable: false, visible: true }
815
- : { textContains: text, clickable: false, visible: true };
816
- const element = yield this.findElementWithRetry(selector);
817
- if (!element) {
818
- return false;
819
- }
820
- const clickAction = {
821
- title: `点击文本: ${text}`,
822
- actions: [{
823
- type: "click",
824
- target: { type: "elementId", value: element.elementId },
825
- options: { waitAfterMs: 2000 }
826
- }]
827
- };
828
- yield this.client.executeAction(clickAction);
829
- return true;
830
- });
831
- }
832
- /**
833
- * Input text into field
834
- */
835
- inputText(selector, text, options) {
836
- return __awaiter(this, void 0, void 0, function* () {
837
- var _a, _b;
838
- const element = yield this.findElementWithRetry(selector);
839
- if (!element) {
840
- return false;
841
- }
842
- const inputAction = {
843
- title: `输入文本: ${text}`,
844
- actions: [{
845
- type: "input",
846
- text,
847
- target: { type: "elementId", value: element.elementId },
848
- options: {
849
- replaceExisting: (_a = options === null || options === void 0 ? void 0 : options.clear) !== null && _a !== void 0 ? _a : true,
850
- hideKeyboardAfter: (_b = options === null || options === void 0 ? void 0 : options.hideKeyboard) !== null && _b !== void 0 ? _b : false
851
- }
852
- }]
853
- };
854
- yield this.client.executeAction(inputAction);
855
- return true;
856
- });
576
+ return {
577
+ results,
578
+ successCount,
579
+ failureCount,
580
+ totalDuration: Date.now() - startTime,
581
+ };
857
582
  }
858
- /**
859
- * Take screenshot and save info
860
- */
861
- captureScreen(options) {
862
- return __awaiter(this, void 0, void 0, function* () {
863
- const screenshot = yield this.client.screenShot(options);
864
- if (options === null || options === void 0 ? void 0 : options.saveInfo) {
865
- console.log('截图信息:', {
866
- 格式: screenshot.format,
867
- 尺寸: screenshot.dimensions,
868
- 大小: `${Math.round(screenshot.imageData.length / 1024)}KB`,
869
- 时间: new Date(Number(screenshot.timestamp)).toLocaleString()
870
- });
871
- }
872
- return screenshot;
583
+ // ==================== Element Finder Methods ====================
584
+ /**
585
+ * Find a single element with retry logic
586
+ *
587
+ * @param selector - Element selector
588
+ * @param maxRetries - Maximum retry attempts (default: 3)
589
+ * @param retryDelay - Delay between retries in milliseconds (default: 1000)
590
+ * @returns Promise resolving to UIElement or null
591
+ *
592
+ * @example
593
+ * ```typescript
594
+ * const element = await shellx.findElement(
595
+ * { text: 'Submit', visible: true },
596
+ * 3,
597
+ * 1000
598
+ * );
599
+ * ```
600
+ */
601
+ async findElementWithRetry(selector, maxRetries = 3, retryDelay = 1000) {
602
+ return this.elementFinder.findElement(selector, { maxRetries, retryDelay });
603
+ }
604
+ /**
605
+ * Find multiple elements with retry logic
606
+ *
607
+ * @param selector - Element selector
608
+ * @param maxRetries - Maximum retry attempts (default: 3)
609
+ * @param retryDelay - Delay between retries in milliseconds (default: 1000)
610
+ * @param options - Additional find options
611
+ * @returns Promise resolving to array of UIElements
612
+ *
613
+ * @example
614
+ * ```typescript
615
+ * const elements = await shellx.findElementsWithRetry(
616
+ * { className: 'Button', visible: true },
617
+ * 3,
618
+ * 1000,
619
+ * { maxResults: 10 }
620
+ * );
621
+ * ```
622
+ */
623
+ async findElementsWithRetry(selector, maxRetries = 3, retryDelay = 1000, options) {
624
+ return this.elementFinder.findElements(selector, {
625
+ maxRetries,
626
+ retryDelay,
627
+ ...options,
873
628
  });
874
629
  }
875
630
  /**
876
631
  * Wait for any of multiple elements to appear
632
+ * @deprecated Use waitAnyElement() instead for consistency
877
633
  */
878
- waitForAnyElement(selectors_1) {
879
- return __awaiter(this, arguments, void 0, function* (selectors, timeout = 10000) {
880
- const startTime = Date.now();
881
- while (Date.now() - startTime < timeout) {
882
- for (let i = 0; i < selectors.length; i++) {
883
- try {
884
- const result = yield this.client.findElement(selectors[i], {
885
- timeout: 1000,
886
- maxResults: 1,
887
- visibleOnly: true
888
- });
889
- if (result.elements.length > 0) {
890
- return { element: result.elements[0], selectorIndex: i };
891
- }
892
- }
893
- catch (error) {
894
- // Continue to next selector
895
- }
896
- }
897
- yield new Promise(resolve => setTimeout(resolve, 500));
898
- }
899
- return null;
900
- });
901
- }
902
- /**
903
- * Navigate through app using a series of clicks
904
- */
905
- navigateByPath(textPath) {
906
- return __awaiter(this, void 0, void 0, function* () {
907
- try {
908
- console.log('开始导航路径:', textPath.join(' → '));
909
- for (const [index, text] of textPath.entries()) {
910
- console.log(`导航步骤 ${index + 1}/${textPath.length}: 点击 "${text}"`);
911
- yield this.clickByText(text);
912
- // Wait a bit between clicks
913
- yield new Promise(resolve => setTimeout(resolve, 1000));
914
- }
915
- console.log('✅ 导航完成');
916
- return true;
917
- }
918
- catch (error) {
919
- console.error('❌ 导航失败:', error);
920
- return false;
921
- }
922
- });
923
- }
924
- /**
925
- * Scroll to find element
926
- */
927
- scrollToFindElement(selector_1) {
928
- return __awaiter(this, arguments, void 0, function* (selector, maxScrolls = 5, direction = 'down') {
929
- // First try to find without scrolling
930
- let element = yield this.findElementWithRetry(selector, 1, 0);
931
- if (element)
932
- return element;
933
- // Try scrolling to find
934
- for (let i = 0; i < maxScrolls; i++) {
935
- console.log(`滚动查找第 ${i + 1} 次...`);
936
- // 获取屏幕信息来计算坐标
937
- const screenInfo = yield this.client.getScreenInfo();
938
- const centerX = (screenInfo.width || 1080) / 2;
939
- const centerY = (screenInfo.height || 1920) / 2;
940
- let from, to;
941
- const distance = 400;
942
- if (direction === 'up') {
943
- from = { x: centerX, y: centerY + distance / 2 };
944
- to = { x: centerX, y: centerY - distance / 2 };
945
- }
946
- else if (direction === 'down') {
947
- from = { x: centerX, y: centerY - distance / 2 };
948
- to = { x: centerX, y: centerY + distance / 2 };
949
- }
950
- else if (direction === 'left') {
951
- from = { x: centerX + distance / 2, y: centerY };
952
- to = { x: centerX - distance / 2, y: centerY };
953
- }
954
- else {
955
- from = { x: centerX - distance / 2, y: centerY };
956
- to = { x: centerX + distance / 2, y: centerY };
957
- }
958
- yield this.swipe({
959
- fromX: from.x,
960
- fromY: from.y,
961
- toX: to.x,
962
- toY: to.y,
963
- duration: 800
964
- });
965
- element = yield this.findElementWithRetry(selector, 1, 0);
966
- if (element) {
967
- console.log('✅ 滚动后找到元素');
968
- return element;
969
- }
970
- }
971
- console.log('❌ 滚动后仍未找到元素');
972
- return null;
973
- });
974
- }
975
- /**
976
- * Execute shell command action with output monitoring
977
- */
978
- executeShellCommand(command_1) {
979
- return __awaiter(this, arguments, void 0, function* (command, options = {}) {
980
- const startTime = Date.now();
981
- const title = options.title || `执行命令: ${command}`;
982
- const timeout = options.timeout || 20000; // 增加默认超时时间到20秒
983
- console.log(`🔨 [Shell] ${title}`);
984
- console.log(`⏱️ 超时时间: ${timeout}ms`);
985
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
986
- const commandKey = (0, uuid_1.v4)();
987
- console.log(`🔑 [Shell] 生成命令键: ${commandKey}`);
988
- // 注册命令 Promise
989
- this.shellCommandPromises.set(commandKey, {
990
- resolve,
991
- reject,
992
- startTime,
993
- options,
994
- output: '',
995
- sessionOutputs: new Map(),
996
- command
997
- });
998
- console.log(`📋 [Shell] 当前待处理命令数: ${this.shellCommandPromises.size}`);
999
- // 设置超时
1000
- const timeoutId = setTimeout(() => {
1001
- if (this.shellCommandPromises.has(commandKey)) {
1002
- console.log(`✅ [Shell] 命令 ${command} 执行完成`);
1003
- const commandPromise = this.shellCommandPromises.get(commandKey);
1004
- this.shellCommandPromises.delete(commandKey);
1005
- resolve({
1006
- success: true,
1007
- output: commandPromise ? commandPromise.output.trim() : "",
1008
- duration: Date.now() - startTime
1009
- });
1010
- }
1011
- }, timeout);
1012
- try {
1013
- const shellAction = {
1014
- title,
1015
- actions: [{
1016
- type: "command",
1017
- command,
1018
- title: options.title
1019
- }],
1020
- options: {
1021
- timeoutMs: timeout
1022
- }
1023
- };
1024
- // 发送命令
1025
- yield this.client.sendMessageWithTaskId({ actions: shellAction }, 'command', commandKey, timeout);
1026
- console.log(`📤 [Shell] 命令已发送: ${commandKey}`);
1027
- }
1028
- catch (error) {
1029
- clearTimeout(timeoutId);
1030
- this.shellCommandPromises.delete(commandKey);
1031
- console.error(`❌ [Shell] 命令发送失败: ${command}`, error);
1032
- reject(error);
1033
- }
1034
- }));
1035
- });
634
+ async waitForAnyElement(selectors, timeout = 10000) {
635
+ return this.waitAnyElement(selectors, timeout);
1036
636
  }
1037
637
  /**
1038
- * Execute shell command with simple output (for backward compatibility)
1039
- */
1040
- executeSimpleShellCommand(command, options) {
1041
- return __awaiter(this, void 0, void 0, function* () {
1042
- try {
1043
- const result = yield this.executeShellCommand(command, {
1044
- title: options === null || options === void 0 ? void 0 : options.title,
1045
- timeout: options === null || options === void 0 ? void 0 : options.timeout
1046
- });
1047
- // 如果设置了等待时间,则等待
1048
- if (options === null || options === void 0 ? void 0 : options.waitAfterMs) {
1049
- yield new Promise(resolve => setTimeout(resolve, options.waitAfterMs));
1050
- }
1051
- console.log(`✅ [Shell] 命令执行完成: ${command}`);
1052
- return result;
1053
- }
1054
- catch (error) {
1055
- console.error(`❌ [Shell] 命令执行失败: ${command}`, error);
1056
- throw error;
1057
- }
1058
- });
1059
- }
1060
- /**
1061
- * Execute multiple shell commands in sequence
1062
- */
1063
- executeShellCommands(commands, options) {
1064
- return __awaiter(this, void 0, void 0, function* () {
1065
- const results = [];
1066
- try {
1067
- console.log(`🔨 [Shell] 开始执行 ${commands.length} 个命令`);
1068
- for (const [index, cmd] of commands.entries()) {
1069
- try {
1070
- const title = cmd.title || `命令 ${index + 1}/${commands.length}: ${cmd.command}`;
1071
- console.log(`🔨 [Shell] ${title}`);
1072
- const result = yield this.executeShellCommand(cmd.command, {
1073
- title,
1074
- timeout: options === null || options === void 0 ? void 0 : options.timeout,
1075
- waitAfterMs: cmd.waitAfterMs
1076
- });
1077
- results.push(result);
1078
- }
1079
- catch (error) {
1080
- console.error(`❌ [Shell] 命令 ${index + 1} 执行失败:`, error);
1081
- if (options === null || options === void 0 ? void 0 : options.continueOnError) {
1082
- results.push({
1083
- success: false,
1084
- output: '',
1085
- error: error instanceof Error ? error.message : String(error),
1086
- duration: Date.now() - Date.now() // 简单的时间戳
1087
- });
1088
- continue;
1089
- }
1090
- else {
1091
- throw error;
1092
- }
1093
- }
1094
- }
1095
- console.log(`✅ [Shell] 所有命令执行完成`);
1096
- return results;
1097
- }
1098
- catch (error) {
1099
- console.error(`❌ [Shell] 批量命令执行失败:`, error);
1100
- throw error;
1101
- }
1102
- });
1103
- }
1104
- /**
1105
- * Common ADB commands helper
1106
- */
1107
- adbCommand(command, options) {
1108
- return __awaiter(this, void 0, void 0, function* () {
1109
- const adbCmd = command;
1110
- return this.executeShellCommand(adbCmd, {
1111
- title: (options === null || options === void 0 ? void 0 : options.title) || `ADB命令: ${command}`,
1112
- timeout: options === null || options === void 0 ? void 0 : options.timeout,
1113
- waitAfterMs: options === null || options === void 0 ? void 0 : options.waitAfterMs,
1114
- onOutput: options === null || options === void 0 ? void 0 : options.onOutput,
1115
- onError: options === null || options === void 0 ? void 0 : options.onError,
1116
- expectedOutput: options === null || options === void 0 ? void 0 : options.expectedOutput,
1117
- successPattern: options === null || options === void 0 ? void 0 : options.successPattern,
638
+ * Wait for any of multiple elements to appear
639
+ *
640
+ * @param selectors - Array of element selectors
641
+ * @param timeout - Maximum wait time in milliseconds (default: 10000)
642
+ * @returns Promise resolving to the first found element and its index, or null
643
+ *
644
+ * @example
645
+ * ```typescript
646
+ * const result = await shellx.waitAnyElement(
647
+ * [
648
+ * { text: 'Submit' },
649
+ * { text: 'OK' },
650
+ * { text: 'Confirm' }
651
+ * ],
652
+ * 10000
653
+ * );
654
+ * ```
655
+ */
656
+ async waitAnyElement(selectors, timeout = 10000) {
657
+ return this.elementFinder.waitAnyElement(selectors, timeout);
658
+ }
659
+ /**
660
+ * Scroll to find an element
661
+ *
662
+ * @param selector - Element selector
663
+ * @param maxScrolls - Maximum scroll attempts (default: 5)
664
+ * @param direction - Scroll direction (default: 'down')
665
+ * @returns Promise resolving to UIElement or null
666
+ *
667
+ * @example
668
+ * ```typescript
669
+ * const element = await shellx.scrollToFindElement(
670
+ * { text: 'Target' },
671
+ * 5,
672
+ * 'down'
673
+ * );
674
+ * ```
675
+ */
676
+ async scrollToFindElement(selector, maxScrolls = 5, direction = "down") {
677
+ return this.elementFinder.scrollToFindElement(selector, async (from, to) => {
678
+ await this.swipe({
679
+ fromX: from.x,
680
+ fromY: from.y,
681
+ toX: to.x,
682
+ toY: to.y,
683
+ duration: 800,
1118
684
  });
1119
- });
1120
- }
1121
- /**
1122
- * Device info commands
685
+ }, { maxScrolls, direction });
686
+ }
687
+ // ==================== Shell Command Methods ====================
688
+ /**
689
+ * Handle shell output from WebSocket messages
690
+ *
691
+ * This method should be called in the WebSocket message handler.
692
+ *
693
+ * @param chunks - Shell output chunks
694
+ *
695
+ * @example
696
+ * ```typescript
697
+ * const client = new ConnectionClient(deviceId, {
698
+ * onMessage: (message) => {
699
+ * if (message.chunks) {
700
+ * shellx.handleShellOutput(message.chunks);
701
+ * }
702
+ * }
703
+ * });
704
+ * ```
1123
705
  */
1124
- getDeviceInfo() {
1125
- return __awaiter(this, void 0, void 0, function* () {
1126
- const commands = [
1127
- { command: 'getprop ro.product.model', title: '获取设备型号' },
1128
- { command: 'getprop ro.build.version.release', title: '获取Android版本' },
1129
- { command: 'wm size', title: '获取屏幕尺寸' },
1130
- { command: 'dumpsys battery', title: '获取电池信息' }
1131
- ];
1132
- console.log('📱 [Device] 开始获取设备信息...');
1133
- try {
1134
- const results = yield this.executeShellCommands(commands, {
1135
- continueOnError: true,
1136
- timeout: 5000
1137
- });
1138
- console.log('📱 [Device] 设备信息获取完成');
1139
- return results;
1140
- }
1141
- catch (error) {
1142
- console.error('❌ [Device] 获取设备信息失败:', error);
1143
- throw error;
1144
- }
1145
- });
1146
- }
1147
- /**
1148
- * Execute key action (press a key)
1149
- */
1150
- executeKeyAction(keyCode_1) {
1151
- return __awaiter(this, arguments, void 0, function* (keyCode, options = {}) {
1152
- try {
1153
- console.log(`🔑 [Key] 执行按键操作: ${keyCode}${options.longPress ? ' (长按)' : ''}`);
1154
- const keyAction = {
1155
- title: `按键: ${keyCode}${options.longPress ? ' (长按)' : ''}`,
1156
- actions: [{
1157
- type: "key",
1158
- keyCode,
1159
- options: {
1160
- longPress: options.longPress
1161
- }
1162
- }]
1163
- };
1164
- yield this.client.executeAction(keyAction);
1165
- // 如果设置了等待时间,则等待
1166
- if (options.waitAfterMs) {
1167
- yield new Promise(resolve => setTimeout(resolve, options.waitAfterMs));
1168
- }
1169
- console.log(`✅ [Key] 按键操作完成: ${keyCode}`);
1170
- return true;
1171
- }
1172
- catch (error) {
1173
- console.error(`❌ [Key] 按键操作失败: ${keyCode}`, error);
1174
- return false;
706
+ handleShellOutput(chunks) {
707
+ this.shellCommandExecutor.handleShellOutput(chunks);
708
+ }
709
+ /**
710
+ * Send raw WebSocket message (for advanced use cases)
711
+ *
712
+ * This method allows you to send raw WebSocket protocol messages directly,
713
+ * bypassing the high-level ShellX API. This is useful for:
714
+ * - Custom protocol operations not exposed by ShellX
715
+ * - Testing and debugging
716
+ * - Advanced integrations requiring low-level control
717
+ *
718
+ * @param message - Raw WebSocket client message (WsClient type)
719
+ * @returns Promise resolving to the server response
720
+ *
721
+ * @example
722
+ * ```typescript
723
+ * // Send a custom find element request
724
+ * await shellx.sendRawMessage({
725
+ * findElement: {
726
+ * type: 'find',
727
+ * selector: { text: 'Submit' },
728
+ * options: { maxResults: 10 }
729
+ * }
730
+ * });
731
+ *
732
+ * // Send a custom action sequence
733
+ * await shellx.sendRawMessage({
734
+ * actions: [
735
+ * { action: 'Launch', app: 'com.example.app' },
736
+ * { action: 'Tap', element: [500, 1000] }
737
+ * ]
738
+ * });
739
+ *
740
+ * // Send screen info request
741
+ * await shellx.sendRawMessage({
742
+ * screenInfo: { keepScreenOn: true, wakeApp: true }
743
+ * });
744
+ * ```
745
+ */
746
+ async sendRawMessage(message) {
747
+ await this.ensureConnectionReady();
748
+ return this.client.sendRawMessage(message);
749
+ }
750
+ /**
751
+ * Start ASR (Automatic Speech Recognition)
752
+ * @returns Promise that resolves when speech recognition has started
753
+ *
754
+ * @example
755
+ * ```typescript
756
+ * await shellx.startSpeechRecognition();
757
+ * console.log('Speech recognition started');
758
+ * ```
759
+ */
760
+ async startSpeechRecognition() {
761
+ await this.ensureConnectionReady();
762
+ return this.client.startAsr();
763
+ }
764
+ /**
765
+ * Stop ASR (Automatic Speech Recognition)
766
+ * @returns Promise that resolves when speech recognition has stopped
767
+ *
768
+ * @example
769
+ * ```typescript
770
+ * await shellx.stopSpeechRecognition();
771
+ * console.log('Speech recognition stopped');
772
+ * ```
773
+ */
774
+ async stopSpeechRecognition() {
775
+ await this.ensureConnectionReady();
776
+ return this.client.stopAsr();
777
+ }
778
+ /**
779
+ * Speak text aloud using TTS (Text-to-Speech)
780
+ * @param text - The text to speak
781
+ * @returns Promise that resolves when TTS has started speaking
782
+ *
783
+ * @example
784
+ * ```typescript
785
+ * await shellx.speak('Hello, world!');
786
+ * ```
787
+ */
788
+ async speak(text) {
789
+ await this.ensureConnectionReady();
790
+ return this.client.speak(text);
791
+ }
792
+ /**
793
+ * Stop TTS (Text-to-Speech)
794
+ * @returns Promise that resolves when TTS has stopped speaking
795
+ *
796
+ * @example
797
+ * ```typescript
798
+ * await shellx.stopSpeaking();
799
+ * ```
800
+ */
801
+ async stopSpeaking() {
802
+ await this.ensureConnectionReady();
803
+ return this.client.stopTts();
804
+ }
805
+ /**
806
+ * Static factory method to create shell output handler
807
+ * @deprecated This method is kept for backward compatibility but is no longer needed
808
+ * @param shellx - The ShellX instance
809
+ * @returns Message handler function
810
+ *
811
+ * @example
812
+ * ```typescript
813
+ * const shellx = new ShellX({ deviceId: 'device-id' });
814
+ * const handler = ShellX.createShellOutputHandler(shellx);
815
+ * ```
816
+ */
817
+ static createShellOutputHandler(shellx) {
818
+ return (message) => {
819
+ if (message.chunks) {
820
+ shellx.handleShellOutput(message.chunks);
1175
821
  }
1176
- });
822
+ };
1177
823
  }
1178
824
  }
1179
- exports.ShellX = ShellX;
1180
- /**
1181
- * Create ShellX instance
1182
- */
1183
- function createShellX(client) {
1184
- return new ShellX(client);
1185
- }
1186
- /**
1187
- * Create ShellX instance with automatic authentication and shell output monitoring
1188
- * 自动处理ShellX.ai认证和连接,无需外部提供连接地址
1189
- */
1190
- function createShellXWithShellMonitoring() {
1191
- return __awaiter(this, arguments, void 0, function* (config = {}) {
1192
- try {
1193
- const shellx = new ShellX(null);
1194
- const client = new index_1.default(config.deviceId, Object.assign(Object.assign({}, config), { onMessage: (message) => {
1195
- if (message.chunks) {
1196
- shellx.handleShellOutput(message.chunks);
1197
- }
1198
- if (config.onMessage) {
1199
- config.onMessage(message);
1200
- }
1201
- }, onOpen: () => {
1202
- // 调用原始的onOpen处理器
1203
- if (config.onOpen) {
1204
- config.onOpen();
1205
- }
1206
- } }));
1207
- // 等待ShellX.ai服务连接完成
1208
- console.log('⏳ [ShellX] 等待ShellX.ai服务连接...');
1209
- yield client.waitForInitialization();
1210
- // 绑定客户端到 shellx
1211
- shellx.client = client;
1212
- // 将 shellx 实例关联到客户端
1213
- client.setShellX(shellx);
1214
- console.log('🚀 [ShellX] 初始化完成,等待ShellX.ai服务响应...');
1215
- return shellx;
1216
- }
1217
- catch (error) {
1218
- console.error('❌ [ShellX] 初始化失败:', error);
1219
- throw error;
1220
- }
1221
- });
1222
- }
825
+ //# sourceMappingURL=shellx.js.map