shellx-ai 1.0.8 → 1.0.10

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/index.d.ts CHANGED
@@ -30,8 +30,9 @@ export interface PendingTask {
30
30
  /**
31
31
  * Enhanced WebSocket Task Client with protocol-aware task methods
32
32
  */
33
- export declare class WebSocketTaskClient {
33
+ export declare class ConnectionTaskClient {
34
34
  private wsUrl;
35
+ private deviceId;
35
36
  private config;
36
37
  ws: any;
37
38
  private shellxConnected;
@@ -43,8 +44,10 @@ export declare class WebSocketTaskClient {
43
44
  private pingIntervalId;
44
45
  private initializationPromise;
45
46
  private shellx;
46
- constructor(wsUrl: string, config?: Partial<TaskClientConfig>);
47
+ constructor(deviceId: string, config?: Partial<TaskClientConfig>);
47
48
  private init;
49
+ private authenticateDevice;
50
+ getFetch(): (url: string, options?: any) => Promise<any>;
48
51
  /**
49
52
  * 等待WebSocket初始化完成
50
53
  */
@@ -56,7 +59,7 @@ export declare class WebSocketTaskClient {
56
59
  private handleMessage;
57
60
  private processServerMessage;
58
61
  private handleJsonDataResponse;
59
- private sendMessage;
62
+ sendMessage(message: WsClient, taskType?: string, commandType?: string): Promise<any>;
60
63
  /**
61
64
  * Find UI elements on the screen
62
65
  */
@@ -175,6 +178,7 @@ export declare class WebSocketTaskClient {
175
178
  */
176
179
  completeTasksByType(taskType: string, result?: any, success?: boolean): number;
177
180
  }
178
- export default WebSocketTaskClient;
181
+ export default ConnectionTaskClient;
179
182
  export type { UIElement, ElementSelector, ActionSequence, WsClient, WsServer, ScreenShotOptions, FindOptions, WaitOptions, UIHierarchy, WAITElement, ScreenShotResponse, ScreenInfoResponse, AppListResponse } from './protocol';
183
+ export type { Click, Input, Swipe, Key, Wait, Find, Command, AppInfo, Screenshot, ActionResult, Element, FindResult, CommandResult, Action, Actions, } from './types';
180
184
  export { createShellXWithShellMonitoring, ShellX } from './shellx';
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
42
42
  });
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
- exports.ShellX = exports.createShellXWithShellMonitoring = exports.WebSocketTaskClient = exports.DEFAULT_CONFIG = void 0;
45
+ exports.ShellX = exports.createShellXWithShellMonitoring = exports.ConnectionTaskClient = exports.DEFAULT_CONFIG = void 0;
46
46
  const uuid_1 = require("uuid");
47
47
  const utils_1 = require("./utils");
48
48
  // 定义默认配置
@@ -84,8 +84,8 @@ function getWebSocket() {
84
84
  /**
85
85
  * Enhanced WebSocket Task Client with protocol-aware task methods
86
86
  */
87
- class WebSocketTaskClient {
88
- constructor(wsUrl, config = {}) {
87
+ class ConnectionTaskClient {
88
+ constructor(deviceId, config = {}) {
89
89
  this.ws = null; // Use any to avoid WebSocket type issues
90
90
  this.shellxConnected = false; // ShellX.ai 连接状态
91
91
  this.wsConnected = false; // WebSocket 连接状态
@@ -96,7 +96,7 @@ class WebSocketTaskClient {
96
96
  this.pingIntervalId = null;
97
97
  this.initializationPromise = null;
98
98
  this.shellx = null; // 关联的 ShellX 实例
99
- this.wsUrl = wsUrl;
99
+ this.deviceId = deviceId;
100
100
  this.config = Object.assign(Object.assign({}, exports.DEFAULT_CONFIG), config);
101
101
  this.initializationPromise = this.init();
102
102
  }
@@ -104,6 +104,7 @@ class WebSocketTaskClient {
104
104
  return __awaiter(this, void 0, void 0, function* () {
105
105
  var _a, _b;
106
106
  try {
107
+ this.wsUrl = yield this.authenticateDevice(this.deviceId);
107
108
  console.log('Initializing WebSocket client...' + this.wsUrl);
108
109
  // 获取适合当前环境的WebSocket构造函数
109
110
  const WebSocketConstructor = yield getWebSocket();
@@ -124,7 +125,10 @@ class WebSocketTaskClient {
124
125
  };
125
126
  this.ws.onclose = (event) => {
126
127
  var _a, _b;
127
- console.log('❌ [ShellX] WebSocket连接已关闭,正在尝试重新连接...' + event.code + ' ' + this.wsUrl);
128
+ console.log('❌ [ShellX] WebSocket连接已关闭,正在尝试重新连接...' +
129
+ event.code +
130
+ ' ' +
131
+ this.wsUrl);
128
132
  this.wsConnected = false;
129
133
  this.shellxConnected = false;
130
134
  this.authenticated = false;
@@ -144,6 +148,89 @@ class WebSocketTaskClient {
144
148
  }
145
149
  });
146
150
  }
151
+ authenticateDevice(deviceId) {
152
+ return __awaiter(this, void 0, void 0, function* () {
153
+ // 1. 优先检测本地服务
154
+ try {
155
+ // fetch超时实现
156
+ const fetchWithTimeout = (url, options, timeout = 1000) => Promise.race([
157
+ fetch(url, options),
158
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)),
159
+ ]);
160
+ const localResp = (yield fetchWithTimeout('http://127.0.0.1:9091/info', { method: 'GET', headers: { Accept: 'application/json' } }, 1000));
161
+ if (localResp.ok) {
162
+ const info = yield localResp.json();
163
+ if (info && (info.status === 'ok' || info.status === 1) && info.uuid) {
164
+ if (deviceId == info.uuid) {
165
+ const localUuid = info.uuid;
166
+ const fallbackUrl = `ws://127.0.0.1:9091/api/s/${localUuid}`;
167
+ console.log('✅ [Auth] 本地ShellX服务可用,使用本地 /info 返回的 uuid:', localUuid, ',服务地址:', fallbackUrl);
168
+ return fallbackUrl;
169
+ }
170
+ }
171
+ }
172
+ }
173
+ catch (e) {
174
+ // 本地不可用,继续走远程
175
+ }
176
+ const fallbackUrl = `ws://127.0.0.1:9091/api/s/${deviceId}`;
177
+ // 2. 远程认证逻辑
178
+ authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY');
179
+ if (!authKey) {
180
+ throw new Error('SHELLX_AUTH_KEY environment variable is required');
181
+ }
182
+ try {
183
+ console.log('🔑 [Auth] 正在认证设备...');
184
+ const featGlobal = this.getFetch();
185
+ const jsonData = yield featGlobal(`https://shellx.ai/api/device/${deviceId}`, {
186
+ method: 'GET',
187
+ headers: {
188
+ 'Content-Type': 'application/json',
189
+ },
190
+ });
191
+ console.log('ShellX.ai设备认证响应:', jsonData);
192
+ // const jsonData = JSON.parse(data);
193
+ console.log('✅ [Auth] ShellX.ai设备认证成功');
194
+ console.log(`📡 [Auth] 设备ID: ${jsonData.authenticate}`);
195
+ console.log(`📡 [Auth] ShellX.ai服务地址: ${jsonData.machine}`);
196
+ console.log(`📡 [Auth] 注册时间: ${jsonData.registered_at}`);
197
+ console.log(`📡 [Auth] 最后更新: ${jsonData.last_updated}`);
198
+ return jsonData.machine + '/api/s/' + jsonData.authenticate;
199
+ }
200
+ catch (error) {
201
+ const errorMessage = error instanceof Error ? error.message : String(error);
202
+ console.warn('⚠️ [Auth] ShellX.ai在线认证失败,使用本地服务:', JSON.stringify(errorMessage));
203
+ return fallbackUrl;
204
+ }
205
+ });
206
+ }
207
+ getFetch() {
208
+ return (url, options) => __awaiter(this, void 0, void 0, function* () {
209
+ // 尝试使用全局fetch
210
+ if (typeof globalThis.fetch !== 'undefined') {
211
+ console.log(`🌐 [Fetch] 请求: ${(options === null || options === void 0 ? void 0 : options.method) || 'GET'} ${url}`);
212
+ const response = yield globalThis.fetch(url, options);
213
+ console.log(`📡 [Fetch] 响应: ${response.status} ${response.statusText}`);
214
+ if (!response.ok) {
215
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
216
+ }
217
+ return response.json();
218
+ }
219
+ try {
220
+ const { default: fetch } = yield Promise.resolve().then(() => __importStar(require('node-fetch')));
221
+ console.log(`🌐 [Fetch] 请求: ${(options === null || options === void 0 ? void 0 : options.method) || 'GET'} ${url}`);
222
+ const response = yield fetch(url, options);
223
+ console.log(`📡 [Fetch] 响应: ${response.status} ${response.statusText}`);
224
+ if (!response.ok) {
225
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
226
+ }
227
+ return response.json();
228
+ }
229
+ catch (fetchError) {
230
+ throw new Error(`Fetch not available: ${fetchError.message}`);
231
+ }
232
+ });
233
+ }
147
234
  /**
148
235
  * 等待WebSocket初始化完成
149
236
  */
@@ -308,36 +395,45 @@ class WebSocketTaskClient {
308
395
  }
309
396
  }
310
397
  sendMessage(message, taskType, commandType) {
311
- return new Promise((resolve, reject) => {
312
- const taskId = (0, uuid_1.v4)();
313
- if (taskType) {
314
- const timer = setTimeout(() => {
315
- this.pendingTasks.delete(taskId);
316
- reject(new Error(`Task ${taskType} timeout (${this.config.timeout}ms)`));
317
- }, this.config.timeout);
318
- this.pendingTasks.set(taskId, { resolve, reject, timer, type: taskType, commandType });
319
- console.log(`📋 [ShellX] 创建任务: ${taskId}, 类型: ${taskType}}`);
320
- }
321
- if (this.shellxConnected && this.ws) {
322
- try {
323
- console.log('📤 [ShellX] 发送消息:', message);
324
- this.ws.send((0, cbor_1.encode)(message));
325
- if (!taskType)
326
- resolve(undefined); // For fire-and-forget messages
327
- }
328
- catch (error) {
329
- if (taskType) {
398
+ return __awaiter(this, void 0, void 0, function* () {
399
+ return new Promise((resolve, reject) => {
400
+ const taskId = (0, uuid_1.v4)();
401
+ if (taskType) {
402
+ const timer = setTimeout(() => {
330
403
  this.pendingTasks.delete(taskId);
404
+ reject(new Error(`Task ${taskType} timeout (${this.config.timeout}ms)`));
405
+ }, this.config.timeout);
406
+ this.pendingTasks.set(taskId, {
407
+ resolve,
408
+ reject,
409
+ timer,
410
+ type: taskType,
411
+ commandType,
412
+ });
413
+ console.log(`📋 [ShellX] 创建任务: ${taskId}, 类型: ${taskType}}`);
414
+ }
415
+ if (this.shellxConnected && this.ws) {
416
+ try {
417
+ console.log('📤 [ShellX] 发送消息:', JSON.stringify(message));
418
+ this.ws.send((0, cbor_1.encode)(message));
419
+ if (!taskType)
420
+ resolve(undefined); // For fire-and-forget messages
421
+ }
422
+ catch (error) {
423
+ if (taskType) {
424
+ this.pendingTasks.delete(taskId);
425
+ }
426
+ reject(error);
331
427
  }
332
- reject(error);
333
428
  }
334
- }
335
- else {
336
- console.log('⏳ [ShellX] 连接未就绪,消息已加入队列');
337
- this.messageQueue.push(message);
338
- if (!taskType)
339
- resolve(undefined);
340
- }
429
+ else {
430
+ console.log('⏳ [ShellX] 连接未就绪,消息已加入队列');
431
+ this.reconnect();
432
+ this.messageQueue.push(message);
433
+ if (!taskType)
434
+ resolve(undefined);
435
+ }
436
+ });
341
437
  });
342
438
  }
343
439
  // ===== PROTOCOL-AWARE TASK METHODS =====
@@ -349,7 +445,7 @@ class WebSocketTaskClient {
349
445
  const findAction = {
350
446
  type: 'find',
351
447
  selector,
352
- options
448
+ options,
353
449
  };
354
450
  return this.sendMessage({ findElement: findAction }, 'findElement');
355
451
  });
@@ -362,7 +458,7 @@ class WebSocketTaskClient {
362
458
  const waitAction = {
363
459
  type: 'wait',
364
460
  selector,
365
- options
461
+ options,
366
462
  };
367
463
  return this.sendMessage({ waitElement: waitAction }, 'waitElement');
368
464
  });
@@ -372,17 +468,18 @@ class WebSocketTaskClient {
372
468
  */
373
469
  screenShot(options) {
374
470
  return __awaiter(this, void 0, void 0, function* () {
375
- return this.sendMessage({ screenShot: options || {
376
- "format": "png",
377
- "quality": 30,
378
- "scale": 1.0,
379
- "region": {
380
- "left": 0,
381
- "top": 0,
382
- "width": 1080,
383
- "height": 2340
384
- }
385
- }
471
+ return this.sendMessage({
472
+ screenShot: options || {
473
+ format: 'png',
474
+ quality: 30,
475
+ scale: 1.0,
476
+ region: {
477
+ left: 0,
478
+ top: 0,
479
+ width: 1080,
480
+ height: 2340,
481
+ },
482
+ },
386
483
  }, 'screenShot');
387
484
  });
388
485
  }
@@ -432,7 +529,7 @@ class WebSocketTaskClient {
432
529
  screenChange(options) {
433
530
  return __awaiter(this, void 0, void 0, function* () {
434
531
  return this.sendMessage({
435
- screenChange: options || { enable: true }
532
+ screenChange: options || { enable: true },
436
533
  });
437
534
  });
438
535
  }
@@ -499,13 +596,13 @@ class WebSocketTaskClient {
499
596
  resolve: () => { },
500
597
  reject: () => { },
501
598
  timer,
502
- type: taskType
599
+ type: taskType,
503
600
  });
504
601
  console.log(`📋 [ShellX] 创建可手动控制的任务: ${taskId}, 类型: ${taskType}}`);
505
602
  }
506
603
  if (this.shellxConnected && this.ws) {
507
604
  try {
508
- console.log('📤 [ShellX] 发送消息:', message);
605
+ console.log(Date.now() + ' 📤 [ShellX] 发送消息:', JSON.stringify(message));
509
606
  this.ws.send((0, cbor_1.encode)(message));
510
607
  const promise = new Promise((promiseResolve, promiseReject) => {
511
608
  if (taskType) {
@@ -543,7 +640,7 @@ class WebSocketTaskClient {
543
640
  console.log('🏓 [ShellX] 开始心跳检测...');
544
641
  this.pingIntervalId = setInterval(() => {
545
642
  if (this.ws && this.shellxConnected) {
546
- this.ws.send((0, cbor_1.encode)({ ping: BigInt(Date.now()) }));
643
+ this.ws.send((0, cbor_1.encode)({ ping: Date.now() }));
547
644
  }
548
645
  }, this.config.pingInterval);
549
646
  }
@@ -557,8 +654,10 @@ class WebSocketTaskClient {
557
654
  return __awaiter(this, void 0, void 0, function* () {
558
655
  var _a, _b;
559
656
  // TODO: 本地的需要重连
560
- console.log(`🔄 [ShellX] 重连中... (${this.reconnectAttempts}/${this.config.reconnectMaxAttempts})` + this.wsUrl);
657
+ console.log(`🔄 [ShellX] 重连中... (${this.reconnectAttempts}/${this.config.reconnectMaxAttempts})` +
658
+ this.wsUrl);
561
659
  if (this.reconnectAttempts >= this.config.reconnectMaxAttempts) {
660
+ console.error('❌ [ShellX] 重连失败,达到最大重连次数');
562
661
  (_b = (_a = this.config).onReconnectFailed) === null || _b === void 0 ? void 0 : _b.call(_a);
563
662
  return;
564
663
  }
@@ -662,7 +761,7 @@ class WebSocketTaskClient {
662
761
  }
663
762
  return {
664
763
  type: task.type,
665
- commandType: task.commandType
764
+ commandType: task.commandType,
666
765
  };
667
766
  }
668
767
  /**
@@ -685,9 +784,8 @@ class WebSocketTaskClient {
685
784
  return completedCount;
686
785
  }
687
786
  }
688
- exports.WebSocketTaskClient = WebSocketTaskClient;
689
- exports.default = WebSocketTaskClient;
690
- // PromptFlow 模块已移至 examples/ 目录作为示例实现
787
+ exports.ConnectionTaskClient = ConnectionTaskClient;
788
+ exports.default = ConnectionTaskClient;
691
789
  var shellx_1 = require("./shellx");
692
790
  Object.defineProperty(exports, "createShellXWithShellMonitoring", { enumerable: true, get: function () { return shellx_1.createShellXWithShellMonitoring; } });
693
791
  Object.defineProperty(exports, "ShellX", { enumerable: true, get: function () { return shellx_1.ShellX; } });
@@ -43,6 +43,8 @@ export type ScreenShotResponse = {
43
43
  dimensions: ScreenDimensions;
44
44
  success?: boolean;
45
45
  errorMessage?: string;
46
+ isVideo?: boolean;
47
+ videoPath?: string;
46
48
  };
47
49
  export type ScreenInfoResponse = {
48
50
  displayId?: number;
@@ -101,6 +103,7 @@ export interface ActionSequence {
101
103
  }
102
104
  export type JSONData = {
103
105
  findElement?: UIHierarchy;
106
+ findAllElement?: string;
104
107
  waitElement?: WAITElement;
105
108
  screenShot?: ScreenShotResponse;
106
109
  screenInfo?: ScreenInfoResponse;
@@ -158,6 +161,9 @@ export type ScreenShotOptions = {
158
161
  quality?: number;
159
162
  scale?: number;
160
163
  region?: ScreenRegion;
164
+ isRecording?: boolean;
165
+ saveToFile?: boolean;
166
+ duration?: number;
161
167
  };
162
168
  /** Client message type, see the Rust version. */
163
169
  export type WsClient = {
@@ -205,6 +211,17 @@ export interface ShellCommandAction {
205
211
  activity?: string;
206
212
  command: string;
207
213
  }
214
+ export interface ScreenShotAction {
215
+ title?: string;
216
+ type: "screenShot";
217
+ activity?: string;
218
+ format: 'png' | 'jpeg';
219
+ quality?: number;
220
+ scale?: number;
221
+ region?: ScreenRegion;
222
+ isRecording?: boolean;
223
+ duration?: number;
224
+ }
208
225
  export interface getAppInfoAction {
209
226
  title?: string;
210
227
  type: "get_app_info";
package/dist/shellx.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- import type { ElementSelector, ActionSequence, UIElement, ScreenShotOptions, WsServer } from './protocol';
2
- import WebSocketTaskClient from './index';
3
- export type { UIElement, ElementSelector, ActionSequence };
4
- export { ShellX as AutomationHelpers };
5
- export { createShellX as createHelpers };
6
- export { createShellXWithShellMonitoring as createHelpersWithShellMonitoring };
1
+ import type { ElementSelector, ActionSequence as LegacyActionSequence, UIElement, ScreenShotOptions, WsServer } from './protocol';
2
+ import type { ActionResult, Click, Input, Swipe, Key, Wait, Find, Command, AppInfo, Screenshot, Point, Element, FindResult, CommandResult, Action, Actions } from './types';
3
+ import ConnectionTaskClient from './index';
4
+ export type { UIElement, ElementSelector, LegacyActionSequence };
5
+ export type { ActionResult, Click, Input, Swipe, Key, Wait, Find, Command, AppInfo, Screenshot, Point, Element, FindResult, CommandResult, Action, Actions };
7
6
  interface ShellCommandResult {
8
7
  success: boolean;
9
8
  output: string;
@@ -28,11 +27,11 @@ export declare class ShellX {
28
27
  private client;
29
28
  private shellOutputBuffers;
30
29
  private shellCommandPromises;
31
- constructor(client: WebSocketTaskClient);
30
+ constructor(client: ConnectionTaskClient);
32
31
  /**
33
32
  * Get the WebSocket client instance
34
33
  */
35
- getClient(): WebSocketTaskClient;
34
+ getClient(): ConnectionTaskClient;
36
35
  /**
37
36
  * 设置 shell 输出监听器(需要在创建 WebSocketTaskClient 时调用)
38
37
  */
@@ -77,6 +76,58 @@ export declare class ShellX {
77
76
  * 生成命令唯一标识
78
77
  */
79
78
  private generateCommandKey;
79
+ /**
80
+ * 通用重试机制
81
+ */
82
+ private withRetry;
83
+ /**
84
+ * 转换精简选择器为原始选择器
85
+ */
86
+ private convertSelector;
87
+ /**
88
+ * 转换精简元素为原始元素
89
+ */
90
+ private convertElement;
91
+ /**
92
+ * 点击操作 - 支持元素ID、坐标或选择器
93
+ */
94
+ click(clickData: Click): Promise<ActionResult>;
95
+ /**
96
+ * 输入操作 - 支持元素ID、资源ID或选择器
97
+ */
98
+ input(inputData: Input): Promise<ActionResult>;
99
+ /**
100
+ * 滑动操作 - 支持坐标点
101
+ */
102
+ swipe(swipeData: Swipe): Promise<ActionResult>;
103
+ /**
104
+ * 按键操作
105
+ */
106
+ pressKey(keyData: Key): Promise<ActionResult>;
107
+ /**
108
+ * 等待操作 - 等待元素出现或消失
109
+ */
110
+ wait(waitData: Wait): Promise<ActionResult>;
111
+ /**
112
+ * 查找操作 - 查找元素
113
+ */
114
+ find(findData: Find): Promise<FindResult>;
115
+ /**
116
+ * 执行命令 - 兼容现有的 shell 命令执行
117
+ */
118
+ executeCommand(commandData: Command): Promise<CommandResult>;
119
+ /**
120
+ * 获取应用信息
121
+ */
122
+ getAppInfo(appInfoData: AppInfo): Promise<ActionResult>;
123
+ /**
124
+ * 截图操作
125
+ */
126
+ takeScreenshot(screenshotData?: Screenshot): Promise<ActionResult>;
127
+ /**
128
+ * 执行操作序列 - 支持多个操作连续执行
129
+ */
130
+ executeActions(actions: Array<Click | Input | Swipe | Key | Wait | Find | Command | AppInfo | Screenshot>): Promise<ActionResult[]>;
80
131
  /**
81
132
  * Smart element finder with retry logic
82
133
  */
@@ -108,10 +159,6 @@ export declare class ShellX {
108
159
  clear?: boolean;
109
160
  hideKeyboard?: boolean;
110
161
  }): Promise<boolean>;
111
- /**
112
- * Swipe in a direction
113
- */
114
- swipe(direction: 'up' | 'down' | 'left' | 'right', distance?: number, duration?: number): Promise<void>;
115
162
  /**
116
163
  * Take screenshot and save info
117
164
  */
@@ -175,12 +222,9 @@ export declare class ShellX {
175
222
  /**
176
223
  * Create ShellX instance
177
224
  */
178
- export declare function createShellX(client: WebSocketTaskClient): ShellX;
225
+ export declare function createShellX(client: ConnectionTaskClient): ShellX;
179
226
  /**
180
227
  * Create ShellX instance with automatic authentication and shell output monitoring
181
228
  * 自动处理ShellX.ai认证和连接,无需外部提供连接地址
182
229
  */
183
- export declare function createShellXWithShellMonitoring(config?: any): Promise<{
184
- client: WebSocketTaskClient;
185
- shellx: ShellX;
186
- }>;
230
+ export declare function createShellXWithShellMonitoring(config?: any): Promise<ShellX>;