shellx-ai 1.0.1 → 1.0.2
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/README.md +98 -10
- package/dist/index.d.ts +66 -4
- package/dist/index.js +332 -47
- package/dist/protocol.d.ts +1 -1
- package/dist/shellx.d.ts +9 -5
- package/dist/shellx.js +142 -196
- package/package.json +11 -3
package/dist/shellx.js
CHANGED
|
@@ -51,6 +51,7 @@ exports.createHelpers = createShellX;
|
|
|
51
51
|
exports.createShellXWithShellMonitoring = createShellXWithShellMonitoring;
|
|
52
52
|
exports.createHelpersWithShellMonitoring = createShellXWithShellMonitoring;
|
|
53
53
|
const index_1 = __importDefault(require("./index"));
|
|
54
|
+
const uuid_1 = require("uuid");
|
|
54
55
|
/**
|
|
55
56
|
* ShellX automation utilities for common patterns
|
|
56
57
|
*/
|
|
@@ -78,7 +79,7 @@ class ShellX {
|
|
|
78
79
|
* 处理 shell 输出数据
|
|
79
80
|
*/
|
|
80
81
|
handleShellOutput(chunks) {
|
|
81
|
-
const [
|
|
82
|
+
const [sessionId, len, dataArrays] = chunks;
|
|
82
83
|
try {
|
|
83
84
|
// 将 Uint8Array 数组转换为字符串
|
|
84
85
|
let output = '';
|
|
@@ -99,9 +100,10 @@ class ShellX {
|
|
|
99
100
|
// 更新总输出
|
|
100
101
|
commandPromise.output = this.combineSessionOutputs(commandPromise.sessionOutputs);
|
|
101
102
|
console.log(`📊 [Shell] 命令 ${commandKey} 累积输出长度: ${commandPromise.output.length}`);
|
|
102
|
-
//
|
|
103
|
+
// 调用输出回调(传递清理后的输出)
|
|
103
104
|
if (commandPromise.options.onOutput) {
|
|
104
|
-
|
|
105
|
+
const cleanOutput = this.cleanCommandOutput(output, commandPromise.command);
|
|
106
|
+
commandPromise.options.onOutput(cleanOutput);
|
|
105
107
|
}
|
|
106
108
|
// 检查是否满足完成条件
|
|
107
109
|
this.checkCommandCompletion(commandKey, commandPromise, output);
|
|
@@ -120,10 +122,6 @@ class ShellX {
|
|
|
120
122
|
if (output.includes(command)) {
|
|
121
123
|
return true;
|
|
122
124
|
}
|
|
123
|
-
// 如果输出包含提示符,说明是命令完成
|
|
124
|
-
if (this.containsPrompt(output)) {
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
125
|
// 如果有多个命令在等待,按时间顺序分配
|
|
128
126
|
const waitingCommands = Array.from(this.shellCommandPromises.keys());
|
|
129
127
|
if (waitingCommands.length === 1) {
|
|
@@ -132,6 +130,41 @@ class ShellX {
|
|
|
132
130
|
// 多个命令时,根据 sessionId 和命令创建时间进行启发式匹配
|
|
133
131
|
return true; // 暂时返回 true,让所有命令都接收输出
|
|
134
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* 清理命令输出,去除输入的命令内容
|
|
135
|
+
*/
|
|
136
|
+
cleanCommandOutput(output, command) {
|
|
137
|
+
if (!output || !command) {
|
|
138
|
+
return output;
|
|
139
|
+
}
|
|
140
|
+
let cleanOutput = output;
|
|
141
|
+
// 去除命令回显(命令本身)
|
|
142
|
+
cleanOutput = cleanOutput.replace(new RegExp(this.escapeRegExp(command), 'g'), '');
|
|
143
|
+
// 去除可能的命令提示符前缀和后缀
|
|
144
|
+
const promptPatterns = [
|
|
145
|
+
/^\s*[^$#>\s]*[#$>]\s*/, // 匹配提示符前缀
|
|
146
|
+
/^\s*[^$#>\s]*@[^$#>\s]*[#$>]\s*/, // 匹配 user@host 格式
|
|
147
|
+
/^\s*[^$#>\s]*:\s*[^$#>\s]*[#$>]\s*/, // 匹配 path: 格式
|
|
148
|
+
/\s*[^$#>\s]*[#$>]\s*$/, // 匹配提示符后缀
|
|
149
|
+
/\s*[^$#>\s]*@[^$#>\s]*[#$>]\s*$/, // 匹配 user@host 后缀
|
|
150
|
+
/\s*[^$#>\s]*:\s*[^$#>\s]*[#$>]\s*$/, // 匹配 path: 后缀
|
|
151
|
+
/\s*\d+\|[^$#>\s]*[#$>]\s*$/, // 匹配 Android 格式后缀
|
|
152
|
+
];
|
|
153
|
+
for (const pattern of promptPatterns) {
|
|
154
|
+
cleanOutput = cleanOutput.replace(pattern, '');
|
|
155
|
+
}
|
|
156
|
+
// 去除多余的空行和空格
|
|
157
|
+
cleanOutput = cleanOutput.replace(/^\s+|\s+$/g, ''); // 去除首尾空白
|
|
158
|
+
cleanOutput = cleanOutput.replace(/\n\s*\n/g, '\n'); // 去除多余空行
|
|
159
|
+
// 如果清理后为空,返回空字符串
|
|
160
|
+
if (cleanOutput.trim().length === 0) {
|
|
161
|
+
return '';
|
|
162
|
+
}
|
|
163
|
+
console.log(`🧹 [Shell] 清理命令输出:`);
|
|
164
|
+
console.log(` 原始: "${output.trim()}"`);
|
|
165
|
+
console.log(` 清理后: "${cleanOutput.trim()}"`);
|
|
166
|
+
return cleanOutput;
|
|
167
|
+
}
|
|
135
168
|
/**
|
|
136
169
|
* 合并多个 session 的输出
|
|
137
170
|
*/
|
|
@@ -145,20 +178,10 @@ class ShellX {
|
|
|
145
178
|
return combined;
|
|
146
179
|
}
|
|
147
180
|
/**
|
|
148
|
-
*
|
|
181
|
+
* 转义正则表达式特殊字符
|
|
149
182
|
*/
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
/\$ $/,
|
|
153
|
-
/# $/,
|
|
154
|
-
/> $/,
|
|
155
|
-
/\w+@\w+.*\$ $/,
|
|
156
|
-
/shell@.*:.*\$ $/,
|
|
157
|
-
/houji:\/.*\$ $/,
|
|
158
|
-
/\d+\|\w+:\/.*\$ $/, // Android pattern like "127|bluejay:/ $"
|
|
159
|
-
/\w+:\/.*\$ $/ // Android pattern like "bluejay:/ $"
|
|
160
|
-
];
|
|
161
|
-
return promptPatterns.some(pattern => pattern.test(output));
|
|
183
|
+
escapeRegExp(string) {
|
|
184
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
162
185
|
}
|
|
163
186
|
/**
|
|
164
187
|
* 检查命令是否完成
|
|
@@ -166,41 +189,6 @@ class ShellX {
|
|
|
166
189
|
checkCommandCompletion(commandKey, commandPromise, newOutput) {
|
|
167
190
|
const { resolve, startTime, options, output, command } = commandPromise;
|
|
168
191
|
console.log(`🔍 [Shell] 检查命令 "${command}" 完成条件,当前输出长度: ${output.length}`);
|
|
169
|
-
// 检查成功模式
|
|
170
|
-
if (options.successPattern) {
|
|
171
|
-
const pattern = options.successPattern;
|
|
172
|
-
const isMatch = typeof pattern === 'string'
|
|
173
|
-
? output.includes(pattern)
|
|
174
|
-
: pattern.test(output);
|
|
175
|
-
if (isMatch) {
|
|
176
|
-
console.log(`✅ [Shell] 命令 "${command}" 成功完成 (匹配成功模式)`);
|
|
177
|
-
this.resolveCommand(commandKey, {
|
|
178
|
-
success: true,
|
|
179
|
-
output: output.trim(),
|
|
180
|
-
duration: Date.now() - startTime
|
|
181
|
-
});
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// 检查错误模式
|
|
186
|
-
if (options.errorPattern) {
|
|
187
|
-
const pattern = options.errorPattern;
|
|
188
|
-
const isMatch = typeof pattern === 'string'
|
|
189
|
-
? output.includes(pattern)
|
|
190
|
-
: pattern.test(output);
|
|
191
|
-
if (isMatch) {
|
|
192
|
-
console.log(`❌ [Shell] 命令 "${command}" 执行失败 (匹配错误模式)`);
|
|
193
|
-
this.resolveCommand(commandKey, {
|
|
194
|
-
success: false,
|
|
195
|
-
output: output.trim(),
|
|
196
|
-
error: 'Command failed with error pattern',
|
|
197
|
-
duration: Date.now() - startTime
|
|
198
|
-
});
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
// 检查通用完成模式(如命令提示符)
|
|
203
|
-
// 这是最主要的完成检测逻辑,包括对没有输出的命令的支持
|
|
204
192
|
if (this.isCommandComplete(output, command)) {
|
|
205
193
|
console.log(`✅ [Shell] 命令 "${command}" 执行完成 (检测到提示符)`);
|
|
206
194
|
this.resolveCommand(commandKey, {
|
|
@@ -210,26 +198,6 @@ class ShellX {
|
|
|
210
198
|
});
|
|
211
199
|
return;
|
|
212
200
|
}
|
|
213
|
-
// 额外的完成检测:仅当有输出时才进行的检查
|
|
214
|
-
if (output.trim().length > 0) {
|
|
215
|
-
// 检查是否有错误指示器
|
|
216
|
-
if (this.hasErrorIndicators(output)) {
|
|
217
|
-
console.log(`❌ [Shell] 命令 "${command}" 可能执行失败 (检测到错误指示器)`);
|
|
218
|
-
// 不立即失败,继续等待完整的提示符
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
// 检查是否包含命令和结果(备用检测方式)
|
|
222
|
-
if (output.includes(command) && this.containsPrompt(output)) {
|
|
223
|
-
console.log(`✅ [Shell] 命令 "${command}" 执行完成 (命令+提示符备用检测)`);
|
|
224
|
-
this.resolveCommand(commandKey, {
|
|
225
|
-
success: true,
|
|
226
|
-
output: output.trim(),
|
|
227
|
-
duration: Date.now() - startTime
|
|
228
|
-
});
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
// 如果没有检测到完成条件,继续等待
|
|
233
201
|
console.log(`⏳ [Shell] 命令 "${command}" 继续等待完成...`);
|
|
234
202
|
}
|
|
235
203
|
/**
|
|
@@ -248,50 +216,12 @@ class ShellX {
|
|
|
248
216
|
return errorPatterns.some(pattern => pattern.test(output));
|
|
249
217
|
}
|
|
250
218
|
/**
|
|
251
|
-
*
|
|
219
|
+
* 判断命令是否完成(基于时间)
|
|
252
220
|
*/
|
|
253
221
|
isCommandComplete(output, command) {
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
/# $/, // Root shell prompt
|
|
258
|
-
/> $/, // Windows command prompt
|
|
259
|
-
/~\$ $/, // Home directory prompt
|
|
260
|
-
/\w+@\w+.*\$ $/, // User@host prompt
|
|
261
|
-
/shell@.*:.*\$ $/, // Android shell prompt
|
|
262
|
-
/houji:\/.*\$ $/, // Specific Android shell prompt
|
|
263
|
-
/\d+\|\w+:\/.*\$ $/, // Android pattern like "127|bluejay:/ $"
|
|
264
|
-
/\w+:\/.*\$ $/, // Android pattern like "bluejay:/ $"
|
|
265
|
-
/\[.*\]\$ $/, // Bracketed prompt
|
|
266
|
-
/\w+:\w+\$ $/, // Simple path prompt
|
|
267
|
-
/\w+> $/, // Simple greater-than prompt
|
|
268
|
-
/.*:\w+\$ $/, // Path-based prompt
|
|
269
|
-
/\w+@\w+:.* $/, // User@host with path
|
|
270
|
-
/\d+\|.*\$ $/, // Numbered prompt with pipe
|
|
271
|
-
/.*#\s*$/, // Hash prompt with optional space
|
|
272
|
-
/.*\$\s*$/ // Dollar prompt with optional space
|
|
273
|
-
];
|
|
274
|
-
// 分割输出为行,检查最后几行
|
|
275
|
-
const lines = output.split(/\r?\n/).filter(line => line.trim().length > 0);
|
|
276
|
-
const lastLine = lines[lines.length - 1] || '';
|
|
277
|
-
// 清理最后一行的控制字符
|
|
278
|
-
const cleanLastLine = lastLine.replace(/\r/g, '').trim();
|
|
279
|
-
console.log(`🔍 [Shell] 检查命令 "${command}" 最后一行提示符: "${cleanLastLine}"`);
|
|
280
|
-
const hasPrompt = promptPatterns.some(pattern => {
|
|
281
|
-
const matches = pattern.test(cleanLastLine);
|
|
282
|
-
if (matches) {
|
|
283
|
-
console.log(`✅ [Shell] 命令 "${command}" 匹配到提示符模式: ${pattern}`);
|
|
284
|
-
}
|
|
285
|
-
return matches;
|
|
286
|
-
});
|
|
287
|
-
// 如果检测到提示符,则认为命令完成
|
|
288
|
-
// 不再要求输出必须包含命令本身,因为有些命令可能没有输出
|
|
289
|
-
if (hasPrompt) {
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
// 如果没有检测到提示符,但输出包含命令本身,可能命令还在执行中
|
|
293
|
-
// 这种情况下返回false,继续等待
|
|
294
|
-
return false;
|
|
222
|
+
// 简化逻辑:只基于时间判断,不依赖提示符检测
|
|
223
|
+
// 命令完成由超时机制控制
|
|
224
|
+
return false; // 让超时机制处理命令完成
|
|
295
225
|
}
|
|
296
226
|
/**
|
|
297
227
|
* 解析命令 Promise
|
|
@@ -300,7 +230,9 @@ class ShellX {
|
|
|
300
230
|
const commandPromise = this.shellCommandPromises.get(commandKey);
|
|
301
231
|
if (commandPromise) {
|
|
302
232
|
console.log(`✅ [Shell] 解析命令 Promise: ${commandKey}`);
|
|
303
|
-
|
|
233
|
+
// 清理最终输出结果
|
|
234
|
+
const cleanResult = Object.assign(Object.assign({}, result), { output: this.cleanCommandOutput(result.output, commandPromise.command) });
|
|
235
|
+
commandPromise.resolve(cleanResult);
|
|
304
236
|
this.shellCommandPromises.delete(commandKey);
|
|
305
237
|
}
|
|
306
238
|
else {
|
|
@@ -643,7 +575,7 @@ class ShellX {
|
|
|
643
575
|
console.log(`🔨 [Shell] ${title}`);
|
|
644
576
|
console.log(`⏱️ 超时时间: ${timeout}ms`);
|
|
645
577
|
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
646
|
-
const commandKey =
|
|
578
|
+
const commandKey = (0, uuid_1.v4)();
|
|
647
579
|
console.log(`🔑 [Shell] 生成命令键: ${commandKey}`);
|
|
648
580
|
// 注册命令 Promise
|
|
649
581
|
this.shellCommandPromises.set(commandKey, {
|
|
@@ -660,21 +592,21 @@ class ShellX {
|
|
|
660
592
|
const timeoutId = setTimeout(() => {
|
|
661
593
|
if (this.shellCommandPromises.has(commandKey)) {
|
|
662
594
|
const commandPromise = this.shellCommandPromises.get(commandKey);
|
|
663
|
-
|
|
664
|
-
// 即使超时,如果有输出也返回输出
|
|
595
|
+
this.shellCommandPromises.delete(commandKey);
|
|
665
596
|
if (commandPromise && commandPromise.output.trim().length > 0) {
|
|
666
|
-
console.log(`📤 [Shell] 超时但有输出,返回部分结果`);
|
|
667
|
-
this.shellCommandPromises.delete(commandKey);
|
|
668
597
|
resolve({
|
|
669
|
-
success:
|
|
598
|
+
success: true,
|
|
670
599
|
output: commandPromise.output.trim(),
|
|
671
|
-
error: `Command timeout after ${timeout}ms, but got partial output`,
|
|
672
600
|
duration: Date.now() - startTime
|
|
673
601
|
});
|
|
674
602
|
}
|
|
675
603
|
else {
|
|
676
|
-
|
|
677
|
-
|
|
604
|
+
resolve({
|
|
605
|
+
success: true,
|
|
606
|
+
output: "",
|
|
607
|
+
error: `Command timeout after ${timeout}ms, but got partial output`,
|
|
608
|
+
duration: Date.now() - startTime
|
|
609
|
+
});
|
|
678
610
|
}
|
|
679
611
|
}
|
|
680
612
|
}, timeout);
|
|
@@ -692,10 +624,8 @@ class ShellX {
|
|
|
692
624
|
}
|
|
693
625
|
};
|
|
694
626
|
// 发送命令
|
|
695
|
-
yield this.client.executeAction(shellAction);
|
|
696
|
-
console.log(`📤 [Shell] 命令已发送: ${
|
|
697
|
-
// 等待命令完成(通过 chunks 数据流)
|
|
698
|
-
// Promise 将由 handleShellOutput 解析
|
|
627
|
+
yield this.client.executeAction(shellAction, commandKey);
|
|
628
|
+
console.log(`📤 [Shell] 命令已发送: ${commandKey}`);
|
|
699
629
|
}
|
|
700
630
|
catch (error) {
|
|
701
631
|
clearTimeout(timeoutId);
|
|
@@ -787,7 +717,6 @@ class ShellX {
|
|
|
787
717
|
onError: options === null || options === void 0 ? void 0 : options.onError,
|
|
788
718
|
expectedOutput: options === null || options === void 0 ? void 0 : options.expectedOutput,
|
|
789
719
|
successPattern: options === null || options === void 0 ? void 0 : options.successPattern,
|
|
790
|
-
errorPattern: options === null || options === void 0 ? void 0 : options.errorPattern
|
|
791
720
|
});
|
|
792
721
|
});
|
|
793
722
|
}
|
|
@@ -827,62 +756,71 @@ function createShellX(client) {
|
|
|
827
756
|
return new ShellX(client);
|
|
828
757
|
}
|
|
829
758
|
/**
|
|
830
|
-
*
|
|
759
|
+
* 获取ofetch实例
|
|
831
760
|
*/
|
|
832
|
-
function
|
|
761
|
+
function getfetch() {
|
|
833
762
|
return __awaiter(this, void 0, void 0, function* () {
|
|
834
|
-
// 检查是否已有全局fetch(Node.js 18+或浏览器环境)
|
|
835
|
-
if (typeof globalThis.fetch !== 'undefined') {
|
|
836
|
-
return globalThis.fetch;
|
|
837
|
-
}
|
|
838
|
-
// Node.js环境下动态导入node-fetch
|
|
839
763
|
try {
|
|
840
|
-
|
|
841
|
-
|
|
764
|
+
// @ts-ignore - Dynamic import may not have types
|
|
765
|
+
const { ofetch } = yield Promise.resolve().then(() => __importStar(require('ofetch')));
|
|
766
|
+
// 创建配置好的ofetch实例
|
|
767
|
+
const fetchInstance = ofetch.create({
|
|
768
|
+
timeout: 10000,
|
|
769
|
+
retry: 1,
|
|
770
|
+
retryDelay: 1000,
|
|
771
|
+
headers: {
|
|
772
|
+
'User-Agent': 'ShellX/1.0.1'
|
|
773
|
+
},
|
|
774
|
+
onRequest({ request, options }) {
|
|
775
|
+
console.log(`🌐 [Fetch] 请求: ${options.method || 'GET'} ${request}`);
|
|
776
|
+
},
|
|
777
|
+
onResponse({ response }) {
|
|
778
|
+
console.log(`📡 [Fetch] 响应: ${response.status} ${response.statusText}`);
|
|
779
|
+
},
|
|
780
|
+
onRequestError({ error }) {
|
|
781
|
+
console.error('❌ [Fetch] 请求错误:', error.message);
|
|
782
|
+
},
|
|
783
|
+
onResponseError({ response }) {
|
|
784
|
+
console.error(`❌ [Fetch] 响应错误: ${response.status} ${response.statusText}`);
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
return fetchInstance;
|
|
842
788
|
}
|
|
843
789
|
catch (error) {
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
return createNodeFetch();
|
|
790
|
+
console.warn('⚠️ [Auth] ofetch不可用,使用降级方案');
|
|
791
|
+
return createFallbackFetch();
|
|
847
792
|
}
|
|
848
793
|
});
|
|
849
794
|
}
|
|
850
795
|
/**
|
|
851
|
-
*
|
|
796
|
+
* 降级fetch实现
|
|
852
797
|
*/
|
|
853
|
-
function
|
|
798
|
+
function createFallbackFetch() {
|
|
854
799
|
return (url, options) => __awaiter(this, void 0, void 0, function* () {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
path: parsedUrl.pathname + parsedUrl.search,
|
|
863
|
-
method: (options === null || options === void 0 ? void 0 : options.method) || 'GET',
|
|
864
|
-
headers: (options === null || options === void 0 ? void 0 : options.headers) || {}
|
|
865
|
-
};
|
|
866
|
-
const req = https.request(requestOptions, (res) => {
|
|
867
|
-
let data = '';
|
|
868
|
-
res.on('data', (chunk) => data += chunk);
|
|
869
|
-
res.on('end', () => {
|
|
870
|
-
const response = {
|
|
871
|
-
ok: res.statusCode ? res.statusCode >= 200 && res.statusCode < 300 : false,
|
|
872
|
-
status: res.statusCode || 0,
|
|
873
|
-
statusText: res.statusMessage || '',
|
|
874
|
-
json: () => __awaiter(this, void 0, void 0, function* () { return JSON.parse(data); }),
|
|
875
|
-
text: () => __awaiter(this, void 0, void 0, function* () { return data; })
|
|
876
|
-
};
|
|
877
|
-
resolve(response);
|
|
878
|
-
});
|
|
879
|
-
});
|
|
880
|
-
req.on('error', reject);
|
|
881
|
-
if (options === null || options === void 0 ? void 0 : options.body) {
|
|
882
|
-
req.write(options.body);
|
|
800
|
+
// 尝试使用全局fetch
|
|
801
|
+
if (typeof globalThis.fetch !== 'undefined') {
|
|
802
|
+
console.log(`🌐 [Fetch] 请求: ${(options === null || options === void 0 ? void 0 : options.method) || 'GET'} ${url}`);
|
|
803
|
+
const response = yield globalThis.fetch(url, options);
|
|
804
|
+
console.log(`📡 [Fetch] 响应: ${response.status} ${response.statusText}`);
|
|
805
|
+
if (!response.ok) {
|
|
806
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
883
807
|
}
|
|
884
|
-
|
|
885
|
-
}
|
|
808
|
+
return response.json();
|
|
809
|
+
}
|
|
810
|
+
// Node.js环境降级
|
|
811
|
+
try {
|
|
812
|
+
const { default: fetch } = yield Promise.resolve().then(() => __importStar(require('node-fetch')));
|
|
813
|
+
console.log(`🌐 [Fetch] 请求: ${(options === null || options === void 0 ? void 0 : options.method) || 'GET'} ${url}`);
|
|
814
|
+
const response = yield fetch(url, options);
|
|
815
|
+
console.log(`📡 [Fetch] 响应: ${response.status} ${response.statusText}`);
|
|
816
|
+
if (!response.ok) {
|
|
817
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
818
|
+
}
|
|
819
|
+
return response.json();
|
|
820
|
+
}
|
|
821
|
+
catch (fetchError) {
|
|
822
|
+
throw new Error(`Fetch not available: ${fetchError.message}`);
|
|
823
|
+
}
|
|
886
824
|
});
|
|
887
825
|
}
|
|
888
826
|
/**
|
|
@@ -896,47 +834,45 @@ function authenticateDevice() {
|
|
|
896
834
|
}
|
|
897
835
|
try {
|
|
898
836
|
console.log('🔑 [Auth] 正在认证设备...');
|
|
899
|
-
//
|
|
900
|
-
const fetchFn = yield
|
|
901
|
-
|
|
837
|
+
// 获取ofetch实例
|
|
838
|
+
const fetchFn = yield getfetch();
|
|
839
|
+
// ofetch自动处理JSON解析和错误处理
|
|
840
|
+
const data = yield fetchFn(`https://shellx.ai/api/device/${authKey}`, {
|
|
902
841
|
method: 'GET',
|
|
903
842
|
headers: {
|
|
904
843
|
'Content-Type': 'application/json',
|
|
905
844
|
}
|
|
906
845
|
});
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
console.log('✅ [Auth] 设备认证成功');
|
|
915
|
-
console.log(`📡 [Auth] 连接信息: ${data.machine}`);
|
|
916
|
-
return data.machine;
|
|
846
|
+
const jsonData = JSON.parse(data);
|
|
847
|
+
console.log('✅ [Auth] ShellX.ai设备认证成功');
|
|
848
|
+
console.log(`📡 [Auth] 设备ID: ${jsonData.authenticate}`);
|
|
849
|
+
console.log(`📡 [Auth] ShellX.ai服务地址: ${jsonData.machine}`);
|
|
850
|
+
console.log(`📡 [Auth] 注册时间: ${jsonData.registered_at}`);
|
|
851
|
+
console.log(`📡 [Auth] 最后更新: ${jsonData.last_updated}`);
|
|
852
|
+
return jsonData.machine;
|
|
917
853
|
}
|
|
918
854
|
catch (error) {
|
|
919
855
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
920
|
-
console.warn('⚠️ [Auth]
|
|
921
|
-
// 认证失败时使用默认的本地
|
|
856
|
+
console.warn('⚠️ [Auth] ShellX.ai在线认证失败,使用本地服务:', errorMessage);
|
|
857
|
+
// 认证失败时使用默认的本地ShellX服务
|
|
922
858
|
const fallbackUrl = `ws://127.0.0.1:9091/api/s/${authKey}`;
|
|
923
|
-
console.log(`🔄 [Auth]
|
|
859
|
+
console.log(`🔄 [Auth] 使用本地ShellX服务: ${fallbackUrl}`);
|
|
924
860
|
return fallbackUrl;
|
|
925
861
|
}
|
|
926
862
|
});
|
|
927
863
|
}
|
|
928
864
|
/**
|
|
929
865
|
* Create ShellX instance with automatic authentication and shell output monitoring
|
|
930
|
-
*
|
|
866
|
+
* 自动处理ShellX.ai认证和连接,无需外部提供连接地址
|
|
931
867
|
*/
|
|
932
868
|
function createShellXWithShellMonitoring() {
|
|
933
869
|
return __awaiter(this, arguments, void 0, function* (config = {}) {
|
|
934
870
|
try {
|
|
935
|
-
// 认证并获取
|
|
871
|
+
// 认证并获取ShellX.ai服务地址
|
|
936
872
|
const wsUrl = yield authenticateDevice();
|
|
937
873
|
// 先创建 shellx 实例,但不绑定客户端
|
|
938
874
|
const shellx = new ShellX(null);
|
|
939
|
-
//
|
|
875
|
+
// 创建ShellX客户端并设置消息监听器
|
|
940
876
|
const client = new index_1.default(wsUrl, Object.assign(Object.assign({}, config), { onMessage: (message) => {
|
|
941
877
|
// 处理 chunks 数据(pty 终端输出)
|
|
942
878
|
if (message.chunks) {
|
|
@@ -946,10 +882,20 @@ function createShellXWithShellMonitoring() {
|
|
|
946
882
|
if (config.onMessage) {
|
|
947
883
|
config.onMessage(message);
|
|
948
884
|
}
|
|
885
|
+
}, onOpen: () => {
|
|
886
|
+
// 调用原始的onOpen处理器
|
|
887
|
+
if (config.onOpen) {
|
|
888
|
+
config.onOpen();
|
|
889
|
+
}
|
|
949
890
|
} }));
|
|
891
|
+
// 等待ShellX.ai服务连接完成
|
|
892
|
+
console.log('⏳ [ShellX] 等待ShellX.ai服务连接...');
|
|
893
|
+
yield client.waitForInitialization();
|
|
950
894
|
// 绑定客户端到 shellx
|
|
951
895
|
shellx.client = client;
|
|
952
|
-
|
|
896
|
+
// 将 shellx 实例关联到客户端
|
|
897
|
+
client.setShellX(shellx);
|
|
898
|
+
console.log('🚀 [ShellX] 初始化完成,等待ShellX.ai服务响应...');
|
|
953
899
|
return { client, shellx };
|
|
954
900
|
}
|
|
955
901
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shellx-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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",
|
|
@@ -24,7 +24,14 @@
|
|
|
24
24
|
"test:shell-commands": "ts-node -r dotenv/config examples/shell-commands-demo.ts",
|
|
25
25
|
"test:no-output": "ts-node -r dotenv/config examples/no-output-command-test.ts",
|
|
26
26
|
"test:find-elements": "ts-node -r dotenv/config examples/find-elements-demo.ts",
|
|
27
|
-
"test:api": "ts-node test-shellx.ts"
|
|
27
|
+
"test:api": "ts-node test-shellx.ts",
|
|
28
|
+
"test:auth": "ts-node test-auth.ts",
|
|
29
|
+
"test:ofetch": "ts-node test-ofetch.ts",
|
|
30
|
+
"test:paytm-connection": "ts-node -r dotenv/config test-paytm-connection.ts",
|
|
31
|
+
"test:prompt-detection": "ts-node -r dotenv/config test-shell-prompt-integration.ts",
|
|
32
|
+
"test:simplified": "ts-node -r dotenv/config test-simplified-shell.ts",
|
|
33
|
+
"install-deps": "node install-deps.js",
|
|
34
|
+
"postinstall": "node install-deps.js"
|
|
28
35
|
},
|
|
29
36
|
"keywords": [
|
|
30
37
|
"websocket",
|
|
@@ -44,7 +51,8 @@
|
|
|
44
51
|
"uuid": "^11.1.0"
|
|
45
52
|
},
|
|
46
53
|
"optionalDependencies": {
|
|
47
|
-
"node-fetch": "^3.3.2"
|
|
54
|
+
"node-fetch": "^3.3.2",
|
|
55
|
+
"ws": "^8.14.2"
|
|
48
56
|
},
|
|
49
57
|
"devDependencies": {
|
|
50
58
|
"@types/jest": "^30.0.0",
|