shellx-ai 1.0.3 → 1.0.5
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 +16 -8
- package/dist/index.js +47 -37
- package/dist/protocol.d.ts +23 -9
- package/dist/shellx.d.ts +11 -4
- package/dist/shellx.js +68 -49
- package/dist/utils.d.ts +13 -1
- package/dist/utils.js +64 -20
- package/package.json +7 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
export interface TaskClientConfig {
|
|
2
|
+
timeout: number;
|
|
3
|
+
reconnect: boolean;
|
|
4
|
+
reconnectMaxAttempts: number;
|
|
5
|
+
reconnectInterval: number;
|
|
6
|
+
pingInterval: number;
|
|
7
|
+
onOpen: () => void;
|
|
8
|
+
onClose: (event?: CloseEvent) => void;
|
|
9
|
+
onError: (error?: Event) => void;
|
|
10
|
+
onReconnectFailed: () => void;
|
|
11
|
+
onMessage?: (data: any) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare const DEFAULT_CONFIG: TaskClientConfig;
|
|
2
14
|
import type { WsClient, ActionSequence, ScreenShotOptions, ElementSelector, FindOptions, WaitOptions, UIHierarchy, WAITElement, ScreenShotResponse, ScreenInfoResponse, AppListResponse } from './protocol';
|
|
3
15
|
export interface TaskResponse<T = any> {
|
|
4
16
|
taskId?: string;
|
|
@@ -11,7 +23,7 @@ export interface TaskResponse<T = any> {
|
|
|
11
23
|
export interface PendingTask {
|
|
12
24
|
resolve: (data: any) => void;
|
|
13
25
|
reject: (err: Error) => void;
|
|
14
|
-
timer:
|
|
26
|
+
timer: number;
|
|
15
27
|
type: string;
|
|
16
28
|
commandType?: string;
|
|
17
29
|
}
|
|
@@ -41,10 +53,6 @@ export declare class WebSocketTaskClient {
|
|
|
41
53
|
* 发送认证消息
|
|
42
54
|
*/
|
|
43
55
|
private sendAuthenticationMessage;
|
|
44
|
-
/**
|
|
45
|
-
* 从WebSocket URL中提取认证密钥
|
|
46
|
-
*/
|
|
47
|
-
private extractAuthKeyFromUrl;
|
|
48
56
|
private handleMessage;
|
|
49
57
|
private processServerMessage;
|
|
50
58
|
private handleJsonDataResponse;
|
|
@@ -168,5 +176,5 @@ export declare class WebSocketTaskClient {
|
|
|
168
176
|
completeTasksByType(taskType: string, result?: any, success?: boolean): number;
|
|
169
177
|
}
|
|
170
178
|
export default WebSocketTaskClient;
|
|
171
|
-
export {
|
|
172
|
-
export
|
|
179
|
+
export type { UIElement, ElementSelector, ActionSequence, WsClient, WsServer, ScreenShotOptions, FindOptions, WaitOptions, UIHierarchy, WAITElement, ScreenShotResponse, ScreenInfoResponse, AppListResponse } from './protocol';
|
|
180
|
+
export { createShellXWithShellMonitoring, ShellX } from './shellx';
|
package/dist/index.js
CHANGED
|
@@ -42,11 +42,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.
|
|
45
|
+
exports.ShellX = exports.createShellXWithShellMonitoring = exports.WebSocketTaskClient = exports.DEFAULT_CONFIG = void 0;
|
|
46
46
|
const uuid_1 = require("uuid");
|
|
47
|
-
const constants_1 = require("./constants");
|
|
48
47
|
const utils_1 = require("./utils");
|
|
48
|
+
// 定义默认配置
|
|
49
|
+
exports.DEFAULT_CONFIG = {
|
|
50
|
+
timeout: 5000,
|
|
51
|
+
reconnect: true,
|
|
52
|
+
reconnectMaxAttempts: 5,
|
|
53
|
+
reconnectInterval: 1000,
|
|
54
|
+
pingInterval: 2000,
|
|
55
|
+
onOpen: () => { },
|
|
56
|
+
onClose: () => { },
|
|
57
|
+
onError: () => { },
|
|
58
|
+
onReconnectFailed: () => { },
|
|
59
|
+
};
|
|
49
60
|
const cbor_x_1 = require("cbor-x");
|
|
61
|
+
// 安全地获取环境变量,兼容浏览器和Node.js环境
|
|
62
|
+
let authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY');
|
|
50
63
|
/**
|
|
51
64
|
* 获取适合当前环境的WebSocket构造函数
|
|
52
65
|
*/
|
|
@@ -84,14 +97,14 @@ class WebSocketTaskClient {
|
|
|
84
97
|
this.initializationPromise = null;
|
|
85
98
|
this.shellx = null; // 关联的 ShellX 实例
|
|
86
99
|
this.wsUrl = wsUrl;
|
|
87
|
-
this.config = Object.assign(Object.assign({},
|
|
100
|
+
this.config = Object.assign(Object.assign({}, exports.DEFAULT_CONFIG), config);
|
|
88
101
|
this.initializationPromise = this.init();
|
|
89
102
|
}
|
|
90
103
|
init() {
|
|
91
104
|
return __awaiter(this, void 0, void 0, function* () {
|
|
92
105
|
var _a, _b;
|
|
93
106
|
try {
|
|
94
|
-
console.log('Initializing WebSocket client...');
|
|
107
|
+
console.log('Initializing WebSocket client...' + this.wsUrl);
|
|
95
108
|
// 获取适合当前环境的WebSocket构造函数
|
|
96
109
|
const WebSocketConstructor = yield getWebSocket();
|
|
97
110
|
this.ws = new WebSocketConstructor(this.wsUrl);
|
|
@@ -100,7 +113,7 @@ class WebSocketTaskClient {
|
|
|
100
113
|
this.ws.binaryType = 'arraybuffer';
|
|
101
114
|
}
|
|
102
115
|
this.ws.onopen = () => {
|
|
103
|
-
console.log('🔗 [ShellX] WebSocket连接已建立,发送认证消息...');
|
|
116
|
+
console.log('🔗 [ShellX] WebSocket连接已建立,发送认证消息...' + this.wsUrl);
|
|
104
117
|
this.wsConnected = true;
|
|
105
118
|
this.reconnectAttempts = 0;
|
|
106
119
|
// 连接建立后立即发送认证消息
|
|
@@ -111,6 +124,7 @@ class WebSocketTaskClient {
|
|
|
111
124
|
};
|
|
112
125
|
this.ws.onclose = (event) => {
|
|
113
126
|
var _a, _b;
|
|
127
|
+
console.log('❌ [ShellX] WebSocket连接已关闭,正在尝试重新连接...' + event.code + ' ' + this.wsUrl);
|
|
114
128
|
this.wsConnected = false;
|
|
115
129
|
this.shellxConnected = false;
|
|
116
130
|
this.authenticated = false;
|
|
@@ -146,20 +160,16 @@ class WebSocketTaskClient {
|
|
|
146
160
|
sendAuthenticationMessage() {
|
|
147
161
|
return __awaiter(this, void 0, void 0, function* () {
|
|
148
162
|
try {
|
|
149
|
-
|
|
163
|
+
authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY');
|
|
150
164
|
if (!authKey) {
|
|
151
165
|
console.error('❌ [Auth] SHELLX_AUTH_KEY 环境变量未设置');
|
|
152
166
|
return;
|
|
153
167
|
}
|
|
154
168
|
console.log('🔑 [Auth] 发送认证消息...');
|
|
155
169
|
// 从URL中提取认证密钥(如果URL包含认证信息)
|
|
156
|
-
const urlAuthKey = this.extractAuthKeyFromUrl();
|
|
157
|
-
const finalAuthKey = urlAuthKey || authKey;
|
|
158
|
-
// 创建认证数据(这里使用简单的字符串,实际可能需要更复杂的格式)
|
|
159
|
-
const authData = new TextEncoder().encode(finalAuthKey);
|
|
160
170
|
// 发送认证消息
|
|
161
|
-
const authMessage = { authenticate:
|
|
162
|
-
console.log('📤 [Auth] 发送认证消息:', { authenticate:
|
|
171
|
+
const authMessage = { authenticate: authKey };
|
|
172
|
+
console.log('📤 [Auth] 发送认证消息:', { authenticate: authKey });
|
|
163
173
|
if (this.ws && this.wsConnected) {
|
|
164
174
|
this.ws.send((0, cbor_x_1.encode)(authMessage));
|
|
165
175
|
}
|
|
@@ -172,21 +182,6 @@ class WebSocketTaskClient {
|
|
|
172
182
|
}
|
|
173
183
|
});
|
|
174
184
|
}
|
|
175
|
-
/**
|
|
176
|
-
* 从WebSocket URL中提取认证密钥
|
|
177
|
-
*/
|
|
178
|
-
extractAuthKeyFromUrl() {
|
|
179
|
-
try {
|
|
180
|
-
const url = new URL(this.wsUrl);
|
|
181
|
-
// 尝试从路径中提取认证密钥
|
|
182
|
-
const pathParts = url.pathname.split('/');
|
|
183
|
-
const authKey = pathParts[pathParts.length - 1];
|
|
184
|
-
return authKey && authKey !== 's' ? authKey : null;
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
185
|
handleMessage(event) {
|
|
191
186
|
let serverMessage;
|
|
192
187
|
try {
|
|
@@ -377,7 +372,18 @@ class WebSocketTaskClient {
|
|
|
377
372
|
*/
|
|
378
373
|
screenShot(options) {
|
|
379
374
|
return __awaiter(this, void 0, void 0, function* () {
|
|
380
|
-
return this.sendMessage({ screenShot: options || {
|
|
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
|
+
}
|
|
386
|
+
}, 'screenShot');
|
|
381
387
|
});
|
|
382
388
|
}
|
|
383
389
|
/**
|
|
@@ -484,7 +490,9 @@ class WebSocketTaskClient {
|
|
|
484
490
|
}
|
|
485
491
|
if (taskType) {
|
|
486
492
|
const timer = setTimeout(() => {
|
|
487
|
-
|
|
493
|
+
if (taskId != null) {
|
|
494
|
+
this.pendingTasks.delete(taskId);
|
|
495
|
+
}
|
|
488
496
|
console.log(`⏰ [ShellX] 任务超时: ${taskId}, 类型: ${taskType}`);
|
|
489
497
|
}, this.config.timeout);
|
|
490
498
|
this.pendingTasks.set(taskId, {
|
|
@@ -501,10 +509,12 @@ class WebSocketTaskClient {
|
|
|
501
509
|
this.ws.send((0, cbor_x_1.encode)(message));
|
|
502
510
|
const promise = new Promise((promiseResolve, promiseReject) => {
|
|
503
511
|
if (taskType) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
task
|
|
507
|
-
|
|
512
|
+
if (taskId != null) {
|
|
513
|
+
const task = this.pendingTasks.get(taskId);
|
|
514
|
+
if (task) {
|
|
515
|
+
task.resolve = promiseResolve;
|
|
516
|
+
task.reject = promiseReject;
|
|
517
|
+
}
|
|
508
518
|
}
|
|
509
519
|
}
|
|
510
520
|
else {
|
|
@@ -546,6 +556,8 @@ class WebSocketTaskClient {
|
|
|
546
556
|
reconnect() {
|
|
547
557
|
return __awaiter(this, void 0, void 0, function* () {
|
|
548
558
|
var _a, _b;
|
|
559
|
+
// TODO: 本地的需要重连
|
|
560
|
+
console.log(`🔄 [ShellX] 重连中... (${this.reconnectAttempts}/${this.config.reconnectMaxAttempts})` + this.wsUrl);
|
|
549
561
|
if (this.reconnectAttempts >= this.config.reconnectMaxAttempts) {
|
|
550
562
|
(_b = (_a = this.config).onReconnectFailed) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
551
563
|
return;
|
|
@@ -675,9 +687,7 @@ class WebSocketTaskClient {
|
|
|
675
687
|
}
|
|
676
688
|
exports.WebSocketTaskClient = WebSocketTaskClient;
|
|
677
689
|
exports.default = WebSocketTaskClient;
|
|
678
|
-
//
|
|
690
|
+
// PromptFlow 模块已移至 examples/ 目录作为示例实现
|
|
679
691
|
var shellx_1 = require("./shellx");
|
|
680
|
-
Object.defineProperty(exports, "ShellX", { enumerable: true, get: function () { return shellx_1.ShellX; } });
|
|
681
|
-
Object.defineProperty(exports, "createShellX", { enumerable: true, get: function () { return shellx_1.createShellX; } });
|
|
682
692
|
Object.defineProperty(exports, "createShellXWithShellMonitoring", { enumerable: true, get: function () { return shellx_1.createShellXWithShellMonitoring; } });
|
|
683
|
-
|
|
693
|
+
Object.defineProperty(exports, "ShellX", { enumerable: true, get: function () { return shellx_1.ShellX; } });
|
package/dist/protocol.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type UIElement = {
|
|
|
14
14
|
resourceId: string;
|
|
15
15
|
text: string;
|
|
16
16
|
describe: string;
|
|
17
|
+
hintText: string;
|
|
17
18
|
bounds: UIBounds;
|
|
18
19
|
visible: boolean;
|
|
19
20
|
clickable: boolean;
|
|
@@ -59,6 +60,7 @@ export type ScreenInfoResponse = {
|
|
|
59
60
|
timestamp: bigint;
|
|
60
61
|
success?: boolean;
|
|
61
62
|
errorMessage?: string;
|
|
63
|
+
bashStatus?: number;
|
|
62
64
|
};
|
|
63
65
|
export type App = {
|
|
64
66
|
packageName: string;
|
|
@@ -163,7 +165,7 @@ export type WsClient = {
|
|
|
163
165
|
setName?: string;
|
|
164
166
|
setCursor?: [number, number] | null;
|
|
165
167
|
setFocus?: number | null;
|
|
166
|
-
create?: [number, number];
|
|
168
|
+
create?: [number, number, number, number];
|
|
167
169
|
close?: Sid;
|
|
168
170
|
move?: [Sid, WsWinsize | null];
|
|
169
171
|
data?: [Sid, Uint8Array, bigint];
|
|
@@ -197,19 +199,22 @@ export type ShellXActions = {
|
|
|
197
199
|
thought?: string;
|
|
198
200
|
action: ShellCommandAction | ClickAction | InputAction | SwipeAction | KeyAction | WaitAction | findAction | getAppInfoAction | completeAction;
|
|
199
201
|
};
|
|
200
|
-
interface ShellCommandAction {
|
|
202
|
+
export interface ShellCommandAction {
|
|
201
203
|
title?: string;
|
|
202
204
|
type: "command";
|
|
205
|
+
activity?: string;
|
|
203
206
|
command: string;
|
|
204
207
|
}
|
|
205
|
-
interface getAppInfoAction {
|
|
208
|
+
export interface getAppInfoAction {
|
|
206
209
|
title?: string;
|
|
207
210
|
type: "get_app_info";
|
|
211
|
+
activity?: string;
|
|
208
212
|
packageName: string;
|
|
209
213
|
}
|
|
210
|
-
interface ClickAction {
|
|
214
|
+
export interface ClickAction {
|
|
211
215
|
title?: string;
|
|
212
216
|
type: 'click';
|
|
217
|
+
activity?: string;
|
|
213
218
|
target: {
|
|
214
219
|
type: "elementId";
|
|
215
220
|
value: string;
|
|
@@ -223,15 +228,16 @@ interface ClickAction {
|
|
|
223
228
|
"y": number;
|
|
224
229
|
};
|
|
225
230
|
};
|
|
226
|
-
options
|
|
231
|
+
options: {
|
|
227
232
|
timeoutMs?: number;
|
|
228
233
|
durationMs?: number;
|
|
229
234
|
waitAfterMs?: number;
|
|
230
|
-
clickType?: 'normal' | 'long' | 'double';
|
|
235
|
+
clickType?: 'single' | 'normal' | 'long' | 'double';
|
|
231
236
|
};
|
|
232
237
|
}
|
|
233
|
-
interface InputAction {
|
|
238
|
+
export interface InputAction {
|
|
234
239
|
title?: string;
|
|
240
|
+
activity?: string;
|
|
235
241
|
type: "input";
|
|
236
242
|
text: string;
|
|
237
243
|
target: {
|
|
@@ -247,8 +253,9 @@ interface InputAction {
|
|
|
247
253
|
hideKeyboardAfter?: boolean;
|
|
248
254
|
};
|
|
249
255
|
}
|
|
250
|
-
interface SwipeAction {
|
|
256
|
+
export interface SwipeAction {
|
|
251
257
|
title?: string;
|
|
258
|
+
activity?: string;
|
|
252
259
|
type: 'swipe';
|
|
253
260
|
"from": {
|
|
254
261
|
"x": number;
|
|
@@ -267,8 +274,9 @@ interface SwipeAction {
|
|
|
267
274
|
hideKeyboardAfter?: boolean;
|
|
268
275
|
};
|
|
269
276
|
}
|
|
270
|
-
interface KeyAction {
|
|
277
|
+
export interface KeyAction {
|
|
271
278
|
title?: string;
|
|
279
|
+
activity?: string;
|
|
272
280
|
type: "key";
|
|
273
281
|
keyCode?: string;
|
|
274
282
|
options?: {
|
|
@@ -277,6 +285,7 @@ interface KeyAction {
|
|
|
277
285
|
}
|
|
278
286
|
export interface WaitAction {
|
|
279
287
|
title?: string;
|
|
288
|
+
activity?: string;
|
|
280
289
|
type?: "wait";
|
|
281
290
|
selector: ElementSelector;
|
|
282
291
|
options?: WaitOptions;
|
|
@@ -284,14 +293,17 @@ export interface WaitAction {
|
|
|
284
293
|
}
|
|
285
294
|
export interface findAction {
|
|
286
295
|
title?: string;
|
|
296
|
+
activity?: string;
|
|
287
297
|
type?: "find";
|
|
288
298
|
selector: ElementSelector;
|
|
289
299
|
options?: FindOptions;
|
|
290
300
|
}
|
|
291
301
|
export type completeAction = {
|
|
292
302
|
title?: string;
|
|
303
|
+
activity?: string;
|
|
293
304
|
type: "complete";
|
|
294
305
|
result?: string;
|
|
306
|
+
script?: string;
|
|
295
307
|
};
|
|
296
308
|
export type ElementSelector = {
|
|
297
309
|
elementId?: string;
|
|
@@ -309,6 +321,8 @@ export type WaitOptions = {
|
|
|
309
321
|
failOnTimeout?: boolean;
|
|
310
322
|
};
|
|
311
323
|
export type FindOptions = {
|
|
324
|
+
pressClick?: boolean;
|
|
325
|
+
waitAfterMs?: number;
|
|
312
326
|
timeout?: number;
|
|
313
327
|
visibleOnly?: boolean;
|
|
314
328
|
clickableOnly?: boolean;
|
package/dist/shellx.d.ts
CHANGED
|
@@ -29,6 +29,10 @@ export declare class ShellX {
|
|
|
29
29
|
private shellOutputBuffers;
|
|
30
30
|
private shellCommandPromises;
|
|
31
31
|
constructor(client: WebSocketTaskClient);
|
|
32
|
+
/**
|
|
33
|
+
* Get the WebSocket client instance
|
|
34
|
+
*/
|
|
35
|
+
getClient(): WebSocketTaskClient;
|
|
32
36
|
/**
|
|
33
37
|
* 设置 shell 输出监听器(需要在创建 WebSocketTaskClient 时调用)
|
|
34
38
|
*/
|
|
@@ -121,10 +125,6 @@ export declare class ShellX {
|
|
|
121
125
|
element: UIElement;
|
|
122
126
|
selectorIndex: number;
|
|
123
127
|
} | null>;
|
|
124
|
-
/**
|
|
125
|
-
* Login helper for common login flows
|
|
126
|
-
*/
|
|
127
|
-
performLogin(usernameSelector: ElementSelector, passwordSelector: ElementSelector, loginButtonSelector: ElementSelector, username: string, password: string): Promise<boolean>;
|
|
128
128
|
/**
|
|
129
129
|
* Navigate through app using a series of clicks
|
|
130
130
|
*/
|
|
@@ -164,6 +164,13 @@ export declare class ShellX {
|
|
|
164
164
|
* Device info commands
|
|
165
165
|
*/
|
|
166
166
|
getDeviceInfo(): Promise<ShellCommandResult[]>;
|
|
167
|
+
/**
|
|
168
|
+
* Execute key action (press a key)
|
|
169
|
+
*/
|
|
170
|
+
executeKeyAction(keyCode: string, options?: {
|
|
171
|
+
longPress?: boolean;
|
|
172
|
+
waitAfterMs?: number;
|
|
173
|
+
}): Promise<boolean>;
|
|
167
174
|
}
|
|
168
175
|
/**
|
|
169
176
|
* Create ShellX instance
|
package/dist/shellx.js
CHANGED
|
@@ -50,8 +50,12 @@ exports.createShellX = createShellX;
|
|
|
50
50
|
exports.createHelpers = createShellX;
|
|
51
51
|
exports.createShellXWithShellMonitoring = createShellXWithShellMonitoring;
|
|
52
52
|
exports.createHelpersWithShellMonitoring = createShellXWithShellMonitoring;
|
|
53
|
-
const index_1 = __importDefault(require("./index"));
|
|
54
53
|
const uuid_1 = require("uuid");
|
|
54
|
+
const utils_1 = require("./utils");
|
|
55
|
+
// 导入 WebSocketTaskClient 类
|
|
56
|
+
const index_1 = __importDefault(require("./index"));
|
|
57
|
+
// 安全地获取环境变量,兼容浏览器和Node.js环境
|
|
58
|
+
let authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY');
|
|
55
59
|
/**
|
|
56
60
|
* ShellX automation utilities for common patterns
|
|
57
61
|
*/
|
|
@@ -64,6 +68,12 @@ class ShellX {
|
|
|
64
68
|
// 监听 shell 输出需要在创建 WebSocketTaskClient 时设置 onMessage 回调
|
|
65
69
|
console.log('⚠️ [Shell] 注意:要监听 shell 输出,请在创建 WebSocketTaskClient 时设置 onMessage 回调');
|
|
66
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Get the WebSocket client instance
|
|
73
|
+
*/
|
|
74
|
+
getClient() {
|
|
75
|
+
return this.client;
|
|
76
|
+
}
|
|
67
77
|
/**
|
|
68
78
|
* 设置 shell 输出监听器(需要在创建 WebSocketTaskClient 时调用)
|
|
69
79
|
*/
|
|
@@ -483,42 +493,6 @@ class ShellX {
|
|
|
483
493
|
return null;
|
|
484
494
|
});
|
|
485
495
|
}
|
|
486
|
-
/**
|
|
487
|
-
* Login helper for common login flows
|
|
488
|
-
*/
|
|
489
|
-
performLogin(usernameSelector, passwordSelector, loginButtonSelector, username, password) {
|
|
490
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
491
|
-
try {
|
|
492
|
-
console.log('开始登录流程...');
|
|
493
|
-
// Input username
|
|
494
|
-
yield this.inputText(usernameSelector, username, { clear: true });
|
|
495
|
-
console.log('✅ 用户名已输入');
|
|
496
|
-
// Input password
|
|
497
|
-
yield this.inputText(passwordSelector, password, { clear: true });
|
|
498
|
-
console.log('✅ 密码已输入');
|
|
499
|
-
// Click login button
|
|
500
|
-
const loginElement = yield this.findElementWithRetry(loginButtonSelector);
|
|
501
|
-
if (!loginElement) {
|
|
502
|
-
throw new Error('未找到登录按钮');
|
|
503
|
-
}
|
|
504
|
-
const loginAction = {
|
|
505
|
-
title: "执行登录",
|
|
506
|
-
actions: [{
|
|
507
|
-
type: "click",
|
|
508
|
-
target: { type: "elementId", value: loginElement.elementId },
|
|
509
|
-
options: { waitAfterMs: 2000 }
|
|
510
|
-
}]
|
|
511
|
-
};
|
|
512
|
-
yield this.client.executeAction(loginAction);
|
|
513
|
-
console.log('✅ 登录按钮已点击');
|
|
514
|
-
return true;
|
|
515
|
-
}
|
|
516
|
-
catch (error) {
|
|
517
|
-
console.error('❌ 登录失败:', error);
|
|
518
|
-
return false;
|
|
519
|
-
}
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
496
|
/**
|
|
523
497
|
* Navigate through app using a series of clicks
|
|
524
498
|
*/
|
|
@@ -746,6 +720,37 @@ class ShellX {
|
|
|
746
720
|
}
|
|
747
721
|
});
|
|
748
722
|
}
|
|
723
|
+
/**
|
|
724
|
+
* Execute key action (press a key)
|
|
725
|
+
*/
|
|
726
|
+
executeKeyAction(keyCode_1) {
|
|
727
|
+
return __awaiter(this, arguments, void 0, function* (keyCode, options = {}) {
|
|
728
|
+
try {
|
|
729
|
+
console.log(`🔑 [Key] 执行按键操作: ${keyCode}${options.longPress ? ' (长按)' : ''}`);
|
|
730
|
+
const keyAction = {
|
|
731
|
+
title: `按键: ${keyCode}${options.longPress ? ' (长按)' : ''}`,
|
|
732
|
+
actions: [{
|
|
733
|
+
type: "key",
|
|
734
|
+
keyCode,
|
|
735
|
+
options: {
|
|
736
|
+
longPress: options.longPress
|
|
737
|
+
}
|
|
738
|
+
}]
|
|
739
|
+
};
|
|
740
|
+
yield this.client.executeAction(keyAction);
|
|
741
|
+
// 如果设置了等待时间,则等待
|
|
742
|
+
if (options.waitAfterMs) {
|
|
743
|
+
yield new Promise(resolve => setTimeout(resolve, options.waitAfterMs));
|
|
744
|
+
}
|
|
745
|
+
console.log(`✅ [Key] 按键操作完成: ${keyCode}`);
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
catch (error) {
|
|
749
|
+
console.error(`❌ [Key] 按键操作失败: ${keyCode}`, error);
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
}
|
|
749
754
|
}
|
|
750
755
|
exports.ShellX = ShellX;
|
|
751
756
|
exports.AutomationHelpers = ShellX;
|
|
@@ -826,9 +831,30 @@ function createFallbackFetch() {
|
|
|
826
831
|
/**
|
|
827
832
|
* 从ShellX.ai服务认证并获取WebSocket连接信息
|
|
828
833
|
*/
|
|
829
|
-
function authenticateDevice() {
|
|
834
|
+
function authenticateDevice(deviceId) {
|
|
830
835
|
return __awaiter(this, void 0, void 0, function* () {
|
|
831
|
-
const
|
|
836
|
+
const fallbackUrl = `ws://127.0.0.1:9091/api/s/${deviceId}`;
|
|
837
|
+
// 1. 优先检测本地服务
|
|
838
|
+
try {
|
|
839
|
+
// fetch超时实现
|
|
840
|
+
const fetchWithTimeout = (url, options, timeout = 1000) => Promise.race([
|
|
841
|
+
fetch(url, options),
|
|
842
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
|
|
843
|
+
]);
|
|
844
|
+
const localResp = yield fetchWithTimeout('http://127.0.0.1:9091/info', { method: 'GET', headers: { 'Accept': 'application/json' } }, 1000);
|
|
845
|
+
if (localResp.ok) {
|
|
846
|
+
const info = yield localResp.json();
|
|
847
|
+
if (info && (info.status === 'ok' || info.status === 1)) {
|
|
848
|
+
console.log('✅ [Auth] 本地ShellX服务可用,直接使用本地服务:', fallbackUrl);
|
|
849
|
+
return fallbackUrl;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
catch (e) {
|
|
854
|
+
// 本地不可用,继续走远程
|
|
855
|
+
}
|
|
856
|
+
// 2. 远程认证逻辑
|
|
857
|
+
authKey = (0, utils_1.getEnvVar)('SHELLX_AUTH_KEY');
|
|
832
858
|
if (!authKey) {
|
|
833
859
|
throw new Error('SHELLX_AUTH_KEY environment variable is required');
|
|
834
860
|
}
|
|
@@ -837,7 +863,7 @@ function authenticateDevice() {
|
|
|
837
863
|
// 获取ofetch实例
|
|
838
864
|
const fetchFn = yield getfetch();
|
|
839
865
|
// ofetch自动处理JSON解析和错误处理
|
|
840
|
-
const data = yield fetchFn(`https://shellx.ai/api/device/${
|
|
866
|
+
const data = yield fetchFn(`https://shellx.ai/api/device/${deviceId}`, {
|
|
841
867
|
method: 'GET',
|
|
842
868
|
headers: {
|
|
843
869
|
'Content-Type': 'application/json',
|
|
@@ -849,13 +875,11 @@ function authenticateDevice() {
|
|
|
849
875
|
console.log(`📡 [Auth] ShellX.ai服务地址: ${jsonData.machine}`);
|
|
850
876
|
console.log(`📡 [Auth] 注册时间: ${jsonData.registered_at}`);
|
|
851
877
|
console.log(`📡 [Auth] 最后更新: ${jsonData.last_updated}`);
|
|
852
|
-
return jsonData.machine;
|
|
878
|
+
return jsonData.machine + '/api/s/' + jsonData.authenticate;
|
|
853
879
|
}
|
|
854
880
|
catch (error) {
|
|
855
881
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
856
882
|
console.warn('⚠️ [Auth] ShellX.ai在线认证失败,使用本地服务:', errorMessage);
|
|
857
|
-
// 认证失败时使用默认的本地ShellX服务
|
|
858
|
-
const fallbackUrl = `ws://127.0.0.1:9091/api/s/${authKey}`;
|
|
859
883
|
console.log(`🔄 [Auth] 使用本地ShellX服务: ${fallbackUrl}`);
|
|
860
884
|
return fallbackUrl;
|
|
861
885
|
}
|
|
@@ -868,17 +892,12 @@ function authenticateDevice() {
|
|
|
868
892
|
function createShellXWithShellMonitoring() {
|
|
869
893
|
return __awaiter(this, arguments, void 0, function* (config = {}) {
|
|
870
894
|
try {
|
|
871
|
-
|
|
872
|
-
const wsUrl = yield authenticateDevice();
|
|
873
|
-
// 先创建 shellx 实例,但不绑定客户端
|
|
895
|
+
const wsUrl = yield authenticateDevice(config.deviceId);
|
|
874
896
|
const shellx = new ShellX(null);
|
|
875
|
-
// 创建ShellX客户端并设置消息监听器
|
|
876
897
|
const client = new index_1.default(wsUrl, Object.assign(Object.assign({}, config), { onMessage: (message) => {
|
|
877
|
-
// 处理 chunks 数据(pty 终端输出)
|
|
878
898
|
if (message.chunks) {
|
|
879
899
|
shellx.handleShellOutput(message.chunks);
|
|
880
900
|
}
|
|
881
|
-
// 调用原始的消息处理器
|
|
882
901
|
if (config.onMessage) {
|
|
883
902
|
config.onMessage(message);
|
|
884
903
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
export declare const wait: (ms: number) => Promise<void>;
|
|
2
|
+
/**
|
|
3
|
+
* 安全地获取环境变量,兼容浏览器和Node.js环境
|
|
4
|
+
*/
|
|
5
|
+
export declare function getEnvVar(key: string): string | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* 安全地设置环境变量,兼容浏览器和Node.js环境
|
|
8
|
+
*/
|
|
9
|
+
export declare function setEnvVar(key: string, value: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* 安全地删除环境变量,兼容浏览器和Node.js环境
|
|
12
|
+
*/
|
|
13
|
+
export declare function deleteEnvVar(key: string): void;
|
|
2
14
|
/**
|
|
3
15
|
* 检查并获取 WebSocket URL 环境变量
|
|
4
|
-
*
|
|
16
|
+
* 如果未设置则显示错误并抛出异常
|
|
5
17
|
*/
|
|
6
18
|
export declare function getWebSocketUrl(): string;
|
package/dist/utils.js
CHANGED
|
@@ -1,37 +1,81 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.wait = void 0;
|
|
4
|
+
exports.getEnvVar = getEnvVar;
|
|
5
|
+
exports.setEnvVar = setEnvVar;
|
|
6
|
+
exports.deleteEnvVar = deleteEnvVar;
|
|
4
7
|
exports.getWebSocketUrl = getWebSocketUrl;
|
|
5
8
|
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
6
9
|
exports.wait = wait;
|
|
10
|
+
/**
|
|
11
|
+
* 安全地获取环境变量,兼容浏览器和Node.js环境
|
|
12
|
+
*/
|
|
13
|
+
function getEnvVar(key) {
|
|
14
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
15
|
+
return process.env[key];
|
|
16
|
+
}
|
|
17
|
+
else if (typeof window !== 'undefined' && window.localStorage) {
|
|
18
|
+
const item = window.localStorage.getItem(key);
|
|
19
|
+
if (item) {
|
|
20
|
+
console.log("已从 localStorage 中获取环境变量:", key);
|
|
21
|
+
return item;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 安全地设置环境变量,兼容浏览器和Node.js环境
|
|
28
|
+
*/
|
|
29
|
+
function setEnvVar(key, value) {
|
|
30
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
31
|
+
process.env[key] = value;
|
|
32
|
+
}
|
|
33
|
+
else if (typeof window !== 'undefined' && window.localStorage) {
|
|
34
|
+
window.localStorage.setItem(key, value);
|
|
35
|
+
console.log("已保存环境变量:", key, value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 安全地删除环境变量,兼容浏览器和Node.js环境
|
|
40
|
+
*/
|
|
41
|
+
function deleteEnvVar(key) {
|
|
42
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
43
|
+
delete process.env[key];
|
|
44
|
+
}
|
|
45
|
+
else if (typeof window !== 'undefined' && window.localStorage) {
|
|
46
|
+
window.localStorage.removeItem(key);
|
|
47
|
+
console.log("已删除环境变量:", key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
7
50
|
/**
|
|
8
51
|
* 检查并获取 WebSocket URL 环境变量
|
|
9
|
-
*
|
|
52
|
+
* 如果未设置则显示错误并抛出异常
|
|
10
53
|
*/
|
|
11
54
|
function getWebSocketUrl() {
|
|
12
|
-
|
|
55
|
+
// 安全地获取环境变量,兼容浏览器和Node.js环境
|
|
56
|
+
const wsUrl = getEnvVar('WEBSOCKET_URL');
|
|
13
57
|
if (!wsUrl || wsUrl.trim() === '') {
|
|
14
|
-
|
|
15
|
-
console.error(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
console.error('- 确保 ShellX 服务器正在运行');
|
|
24
|
-
console.error('- 检查端口号是否正确(默认9091)');
|
|
25
|
-
console.error('');
|
|
26
|
-
process.exit(1);
|
|
58
|
+
const errorMessage = '❌ 错误: 未设置 WEBSOCKET_URL 环境变量!\n\n📋 解决方案:\n1. 在项目根目录创建 .env 文件\n2. 在 .env 文件中添加以下内容:\n WEBSOCKET_URL=ws://127.0.0.1:9091/api/s/your-session-id\n\n💡 提示:\n- 请将 your-session-id 替换为实际的会话ID\n- 确保 ShellX 服务器正在运行\n- 检查端口号是否正确(默认9091)';
|
|
59
|
+
console.error(errorMessage);
|
|
60
|
+
// 在Node.js环境中退出,在浏览器环境中抛出异常
|
|
61
|
+
if (typeof process !== 'undefined' && process.exit) {
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
throw new Error(errorMessage);
|
|
66
|
+
}
|
|
27
67
|
}
|
|
28
68
|
// 简单的 URL 格式验证
|
|
29
69
|
if (!wsUrl.startsWith('ws://') && !wsUrl.startsWith('wss://')) {
|
|
30
|
-
|
|
31
|
-
console.error(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
70
|
+
const errorMessage = `❌ 错误: WEBSOCKET_URL 格式不正确!\n当前值: ${wsUrl}\n应该以 ws:// 或 wss:// 开头\n例如: ws://127.0.0.1:9091/api/s/your-session-id`;
|
|
71
|
+
console.error(errorMessage);
|
|
72
|
+
// 在Node.js环境中退出,在浏览器环境中抛出异常
|
|
73
|
+
if (typeof process !== 'undefined' && process.exit) {
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
throw new Error(errorMessage);
|
|
78
|
+
}
|
|
35
79
|
}
|
|
36
80
|
return wsUrl;
|
|
37
81
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shellx-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "shellx is a powerful WebSocket-based client for controlling shell commands and UI automation on remote devices.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "git+https://github.com/10cl/shellx.git",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"cbor-x": "^1.6.0",
|
|
34
34
|
"dotenv": "^16.4.5",
|
|
35
|
+
"ofetch": "^1.4.1",
|
|
35
36
|
"uuid": "^11.1.0"
|
|
36
37
|
},
|
|
37
38
|
"optionalDependencies": {
|
|
@@ -41,14 +42,15 @@
|
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@types/jest": "^30.0.0",
|
|
43
44
|
"@types/node": "^24.0.13",
|
|
45
|
+
"@types/ws": "^8.18.1",
|
|
44
46
|
"jest": "^30.0.4",
|
|
45
|
-
"ts-jest": "^29.4.0",
|
|
46
|
-
"ts-node": "^10.9.2",
|
|
47
|
-
"typescript": "^5.8.3",
|
|
48
47
|
"langchain": "^0.1.34",
|
|
49
48
|
"promptflow-eval": "1.0.0",
|
|
50
49
|
"promptflow-template": "1.0.0",
|
|
51
|
-
"promptflowx": "^0.1.
|
|
50
|
+
"promptflowx": "^0.1.9",
|
|
51
|
+
"ts-jest": "^29.4.0",
|
|
52
|
+
"ts-node": "^10.9.2",
|
|
53
|
+
"typescript": "^5.8.3"
|
|
52
54
|
},
|
|
53
55
|
"bugs": {
|
|
54
56
|
"url": "https://github.com/10cl/shellx/issues"
|