@xiaoqiong0v0/opencode-ssh-tool 0.2.4

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.
@@ -0,0 +1,410 @@
1
+ // SshSession:ssh2 长驻连接 + PTY shell 管理,提供哨兵法命令执行与状态查询
2
+ import { Client } from "ssh2";
3
+ import createLogger from "@xiaoqiong0v0/opencode-plugin-logger";
4
+ import { ANIMATION_WINDOW_MS, EXEC_TIMEOUT_MS, MAX_OUTPUT_LEN, PTY_COLS, PTY_ROWS, QUIET_WINDOW_MS, READY_TIMEOUT_MS, } from "./constants.js";
5
+ import { resolveAuth } from "./ssh-auth.js";
6
+ import { SessionHistory } from "./history.js";
7
+ import { cleanAnsi, collapseCarriage, genSentinel, stripEcho, stripPrompt } from "./utils.js";
8
+ const log = createLogger("opencode-ssh-tool", { enabled: true });
9
+ /** 交互触发词(sudo 密码 / 分页器 / 确认提示),含中英文 */
10
+ const INTERACTIVE_RE = /(\[sudo\] password for|password for \S+:|Password:|密码:|--More--|\(END\)|\[y\/N\]|\[Y\/n\]|yes\/no|是\/否)/i;
11
+ /** buffer 最大长度(未消费输出超限时截断头部,防内存膨胀) */
12
+ const MAX_BUFFER_LEN = 2 * 1024 * 1024;
13
+ /** 后台哨兵监听最长存活时间(防泄漏) */
14
+ const MAX_WATCH_LEN = 10 * 60_000;
15
+ export class SshSession {
16
+ sessionID;
17
+ name;
18
+ _client = null;
19
+ _stream = null;
20
+ _connected = false;
21
+ _remoteBusy = false;
22
+ _buffer = "";
23
+ _host = "";
24
+ _user = "";
25
+ _port = 22;
26
+ _connectedAt = 0;
27
+ _lastActive = 0;
28
+ _runningStartPos = null;
29
+ _runningSentinel = "";
30
+ _runningCommand = "";
31
+ _watchTimer = null;
32
+ _closed = false;
33
+ _history;
34
+ constructor(sessionID, history, name = "default") {
35
+ this.sessionID = sessionID;
36
+ this.name = name;
37
+ this._history = history;
38
+ }
39
+ /**
40
+ * 建立 SSH 连接并打开 PTY shell
41
+ * @param opts 连接参数
42
+ * @returns 连接结果
43
+ */
44
+ async connect(opts) {
45
+ const port = opts.port ?? 22;
46
+ const auth = resolveAuth(opts.host);
47
+ const config = {
48
+ host: opts.host,
49
+ port,
50
+ username: opts.user,
51
+ readyTimeout: READY_TIMEOUT_MS,
52
+ debug: (msg) => log.info(`[ssh2] ${msg}`),
53
+ ...auth,
54
+ };
55
+ const client = new Client();
56
+ let settled = false;
57
+ return new Promise((resolve) => {
58
+ const fail = (msg) => {
59
+ if (settled)
60
+ return;
61
+ settled = true;
62
+ log.error(`连接失败 ${opts.host}`, msg);
63
+ resolve({ ok: false, host: opts.host, user: opts.user, port, error: msg });
64
+ };
65
+ client.once("error", (err) => fail(err.message));
66
+ client.once("ready", () => {
67
+ client.shell({ rows: PTY_ROWS, cols: PTY_COLS, term: "xterm-256color" }, (err, stream) => {
68
+ if (err || !stream) {
69
+ client.end();
70
+ fail(err?.message ?? "shell open failed");
71
+ return;
72
+ }
73
+ settled = true;
74
+ this._client = client;
75
+ this._stream = stream;
76
+ this._connected = true;
77
+ this._host = opts.host;
78
+ this._user = opts.user;
79
+ this._port = port;
80
+ this._connectedAt = Date.now();
81
+ this._lastActive = Date.now();
82
+ stream.on("data", (chunk) => {
83
+ const text = chunk.toString();
84
+ this._appendBuffer(text);
85
+ });
86
+ stream.on("close", () => {
87
+ this._connected = false;
88
+ this._remoteBusy = false;
89
+ });
90
+ stream.on("error", (e) => {
91
+ log.error("PTY stream 错误", e);
92
+ this._connected = false;
93
+ });
94
+ (async () => {
95
+ await new Promise((r) => setTimeout(r, 400));
96
+ this._buffer = "";
97
+ this._runningStartPos = null;
98
+ this._runningSentinel = "";
99
+ log.info(`连接成功 ${opts.user}@${opts.host}:${port} (session ${this.sessionID})`);
100
+ resolve({ ok: true, host: opts.host, user: opts.user, port });
101
+ })();
102
+ });
103
+ });
104
+ client.connect(config);
105
+ });
106
+ }
107
+ /**
108
+ * 异步提交命令:立即返回,命令后台执行,输出由后台监听收集进 history
109
+ * @param command 命令
110
+ * @returns 提交结果(立即返回,不等待命令完成)
111
+ */
112
+ async submit(command) {
113
+ const startTs = Date.now();
114
+ if (!this._connected || !this._stream) {
115
+ return { ok: false, output: "", error: "Not connected" };
116
+ }
117
+ if (this._remoteBusy) {
118
+ return { ok: false, output: "", error: "Previous command still running, poll with ssh_status first" };
119
+ }
120
+ const sentinel = genSentinel();
121
+ const combo = `${command}; echo ${sentinel}`;
122
+ const startPos = this._buffer.length;
123
+ this._runningStartPos = startPos;
124
+ this._runningSentinel = sentinel;
125
+ this._runningCommand = command;
126
+ this._remoteBusy = true;
127
+ this._lastActive = startTs;
128
+ this._stream.write(combo + "\r");
129
+ this._startBackgroundWatch(sentinel, startPos, command);
130
+ return {
131
+ ok: true,
132
+ output: "",
133
+ submitted: true,
134
+ host: this._host,
135
+ command,
136
+ duration: 0,
137
+ };
138
+ }
139
+ /**
140
+ * 在当前会话执行命令(哨兵法,同步等待),保留 cwd/环境
141
+ * @param command 命令
142
+ * @param timeout 超时毫秒,默认 30s
143
+ * @returns 执行结果
144
+ */
145
+ async exec(command, timeout = EXEC_TIMEOUT_MS) {
146
+ const startTs = Date.now();
147
+ if (!this._connected || !this._stream) {
148
+ return { ok: false, output: "", error: "Not connected" };
149
+ }
150
+ if (this._remoteBusy) {
151
+ return { ok: false, output: "", error: "Previous command still running, poll with ssh_status first" };
152
+ }
153
+ const sentinel = genSentinel();
154
+ const combo = `${command}; echo ${sentinel}`;
155
+ const startPos = this._buffer.length;
156
+ this._remoteBusy = true;
157
+ this._lastActive = startTs;
158
+ this._stream.write(combo + "\r");
159
+ const outcome = await this._waitSentinel(sentinel, startPos, timeout, startTs);
160
+ switch (outcome.kind) {
161
+ case "done": {
162
+ this._remoteBusy = false;
163
+ const out = cleanAnsi(stripEcho(collapseCarriage(this._buffer.slice(startPos, outcome.idx)), combo));
164
+ this._buffer = this._buffer.slice(outcome.afterNewline);
165
+ this._history.append(command, out);
166
+ return {
167
+ ok: true,
168
+ output: this._truncate(out),
169
+ host: this._host,
170
+ command,
171
+ duration: Date.now() - startTs,
172
+ };
173
+ }
174
+ case "interactive": {
175
+ this._remoteBusy = false;
176
+ const out = cleanAnsi(collapseCarriage(this._buffer.slice(startPos)));
177
+ this._history.append(command, out);
178
+ return {
179
+ ok: true,
180
+ output: this._truncate(out),
181
+ interactive: true,
182
+ host: this._host,
183
+ command,
184
+ duration: Date.now() - startTs,
185
+ };
186
+ }
187
+ case "running":
188
+ case "timeout": {
189
+ this._runningStartPos = startPos;
190
+ this._runningSentinel = sentinel;
191
+ this._runningCommand = command;
192
+ this._remoteBusy = true;
193
+ this._startBackgroundWatch(sentinel, startPos, command);
194
+ const out = cleanAnsi(stripEcho(collapseCarriage(this._buffer.slice(startPos)), combo));
195
+ this._history.append(command, out);
196
+ return {
197
+ ok: true,
198
+ output: this._truncate(out),
199
+ running: outcome.kind === "running",
200
+ timeout: outcome.kind === "timeout",
201
+ host: this._host,
202
+ command,
203
+ duration: Date.now() - startTs,
204
+ };
205
+ }
206
+ }
207
+ }
208
+ /**
209
+ * 读取并清空未消费缓冲(交互/轮询场景)
210
+ * @returns 未消费输出
211
+ */
212
+ async readBuffer() {
213
+ if (!this._connected) {
214
+ return { ok: false, output: "", error: "Not connected" };
215
+ }
216
+ let out;
217
+ if (this._runningStartPos !== null) {
218
+ // 有后台运行上下文:输出从运行起点取到哨兵为止
219
+ const idx = this._findSentinel(this._runningSentinel, this._runningStartPos);
220
+ if (idx >= 0) {
221
+ out = this._buffer.slice(this._runningStartPos, idx);
222
+ this._buffer = this._buffer.slice(idx);
223
+ this._clearRunningContext();
224
+ }
225
+ else {
226
+ out = this._buffer.slice(this._runningStartPos);
227
+ }
228
+ }
229
+ else {
230
+ out = this._buffer;
231
+ this._buffer = "";
232
+ }
233
+ return { ok: true, output: this._truncate(stripPrompt(cleanAnsi(collapseCarriage(out)))) };
234
+ }
235
+ /**
236
+ * 获取会话状态(供 ssh_status)
237
+ * @returns 状态对象
238
+ */
239
+ getStatus() {
240
+ return {
241
+ connected: this._connected,
242
+ busy: this._connected && this._remoteBusy,
243
+ pending: this._buffer.length,
244
+ name: this.name,
245
+ host: this._host,
246
+ user: this._user,
247
+ port: this._port,
248
+ lastActive: this._lastActive,
249
+ connectedAt: this._connectedAt,
250
+ };
251
+ }
252
+ /**
253
+ * 获取会话消息对记录(供 ssh_terminal / HTTP 页面渲染)
254
+ * @returns 消息对存储
255
+ */
256
+ getHistory() {
257
+ return this._history;
258
+ }
259
+ /**
260
+ * 获取当前运行中命令的实时缓冲(合并覆盖后,不消费 buffer)
261
+ * @returns 运行中命令的当前进度文本;无运行命令则返回空串
262
+ */
263
+ getRunningOutput() {
264
+ if (this._runningStartPos === null || !this._connected || !this._runningCommand)
265
+ return "";
266
+ const combo = `${this._runningCommand}; echo ${this._runningSentinel}`;
267
+ const raw = this._buffer.slice(this._runningStartPos);
268
+ return cleanAnsi(stripEcho(collapseCarriage(raw), combo)).trim();
269
+ }
270
+ /**
271
+ * 关闭连接,清理会话态(幂等)
272
+ */
273
+ close() {
274
+ if (this._closed)
275
+ return;
276
+ this._closed = true;
277
+ if (this._watchTimer)
278
+ clearInterval(this._watchTimer);
279
+ if (this._stream) {
280
+ try {
281
+ this._stream.end();
282
+ }
283
+ catch {
284
+ /* 忽略 */
285
+ }
286
+ }
287
+ if (this._client) {
288
+ try {
289
+ this._client.end();
290
+ }
291
+ catch {
292
+ /* 忽略 */
293
+ }
294
+ }
295
+ this._connected = false;
296
+ this._remoteBusy = false;
297
+ this._history.dispose();
298
+ log.hook("ssh_disconnect", `关闭会话 ${this._host}`);
299
+ }
300
+ /**
301
+ * 追加输出到未消费 buffer,超限时截断头部(保留尾部)并修正运行索引
302
+ * @param text 新增输出文本
303
+ */
304
+ _appendBuffer(text) {
305
+ this._buffer += text;
306
+ if (this._buffer.length > MAX_BUFFER_LEN) {
307
+ const trimmed = this._buffer.length - MAX_BUFFER_LEN;
308
+ this._buffer = this._buffer.slice(trimmed);
309
+ // 同步修正后台运行上下文索引(若存在)
310
+ if (this._runningStartPos !== null) {
311
+ this._runningStartPos = Math.max(0, this._runningStartPos - trimmed);
312
+ }
313
+ log.info(`buffer 超限截断 ${trimmed} 字符,当前 ${this._buffer.length}`);
314
+ }
315
+ }
316
+ _waitSentinel(sentinel, startPos, timeout, startTs) {
317
+ return new Promise((resolve) => {
318
+ let lastLen = this._buffer.length;
319
+ let lastChange = Date.now();
320
+ const timer = setInterval(() => {
321
+ const idx = this._findSentinel(sentinel, startPos);
322
+ if (idx >= 0) {
323
+ clearInterval(timer);
324
+ const nl = this._buffer.indexOf("\n", idx);
325
+ resolve({
326
+ kind: "done",
327
+ idx,
328
+ afterNewline: nl >= 0 ? nl + 1 : idx + sentinel.length,
329
+ });
330
+ return;
331
+ }
332
+ if (INTERACTIVE_RE.test(this._buffer.slice(startPos))) {
333
+ clearInterval(timer);
334
+ resolve({ kind: "interactive" });
335
+ return;
336
+ }
337
+ const now = Date.now();
338
+ const curLen = this._buffer.length;
339
+ if (curLen !== lastLen) {
340
+ lastLen = curLen;
341
+ lastChange = now;
342
+ }
343
+ // 动画检测:持续有新输出且已超过动画阈值 → 判定仍在运行
344
+ if (now - lastChange < QUIET_WINDOW_MS && now - startTs >= ANIMATION_WINDOW_MS) {
345
+ clearInterval(timer);
346
+ resolve({ kind: "running" });
347
+ return;
348
+ }
349
+ if (now - startTs >= timeout) {
350
+ clearInterval(timer);
351
+ resolve({ kind: "timeout" });
352
+ return;
353
+ }
354
+ }, 50);
355
+ });
356
+ }
357
+ /** 后台哨兵监听:轮询到哨兵 → 收集输出进 history + busy=false,供 ssh_status/ssh_terminal 读取 */
358
+ _startBackgroundWatch(sentinel, startPos, command) {
359
+ if (this._watchTimer)
360
+ clearInterval(this._watchTimer);
361
+ const born = Date.now();
362
+ this._watchTimer = setInterval(() => {
363
+ if (!this._connected || Date.now() - born > MAX_WATCH_LEN) {
364
+ this._remoteBusy = false;
365
+ this._clearRunningContext();
366
+ if (this._watchTimer)
367
+ clearInterval(this._watchTimer);
368
+ return;
369
+ }
370
+ const idx = this._findSentinel(sentinel, startPos);
371
+ if (idx >= 0) {
372
+ // 命令完成:收集输出(哨兵前内容,剔除回显)进 history
373
+ const combo = `${command}; echo ${sentinel}`;
374
+ const out = cleanAnsi(stripEcho(collapseCarriage(this._buffer.slice(startPos, idx)), combo));
375
+ this._history.append(command, out);
376
+ const nl = this._buffer.indexOf("\n", idx);
377
+ this._buffer = this._buffer.slice(nl >= 0 ? nl + 1 : idx + sentinel.length);
378
+ this._remoteBusy = false;
379
+ this._clearRunningContext();
380
+ if (this._watchTimer)
381
+ clearInterval(this._watchTimer);
382
+ }
383
+ }, 200);
384
+ }
385
+ /** 在缓冲中定位哨兵行起始位置(哨兵串开头,容忍行前 ANSI bracketed-paste 序列) */
386
+ _findSentinel(sentinel, fromPos) {
387
+ const esc = sentinel.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
388
+ // 行开始(\r|\n|串首) + 可选 ANSI 序列(如 ESC[?2004l) + 哨兵串
389
+ const re = new RegExp(`(?:^|[\\r\\n])(?:\\x1b\\[[0-9;?]*[a-zA-Z])*${esc}`, "g");
390
+ re.lastIndex = fromPos;
391
+ const m = re.exec(this._buffer);
392
+ if (!m)
393
+ return -1;
394
+ // m[0] = 行开始 + ANSI + sentinel,哨兵串开头 = m.index + m[0].length - sentinel.length
395
+ return m.index + m[0].length - sentinel.length;
396
+ }
397
+ /** 清理后台运行上下文 */
398
+ _clearRunningContext() {
399
+ this._runningStartPos = null;
400
+ this._runningSentinel = "";
401
+ this._runningCommand = "";
402
+ }
403
+ /** 输出截断保护 */
404
+ _truncate(s) {
405
+ if (s.length <= MAX_OUTPUT_LEN)
406
+ return s;
407
+ return `${s.slice(0, MAX_OUTPUT_LEN)}\n... [output truncated, ${s.length} chars total]`;
408
+ }
409
+ }
410
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /** 认证信息(互斥,按优先级取其一),字段名对齐 ssh2 ConnectConfig */
2
+ export interface AuthInfo {
3
+ privateKey?: string;
4
+ agent?: string;
5
+ password?: string;
6
+ }
7
+ /**
8
+ * 解析指定主机的认证信息,优先级:key > agent > 环境变量密码
9
+ * @param host 目标主机
10
+ * @returns 认证信息(任一字段可选)
11
+ */
12
+ export declare function resolveAuth(host: string): AuthInfo;
13
+ //# sourceMappingURL=ssh-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-auth.d.ts","sourceRoot":"","sources":["../src/ssh-auth.ts"],"names":[],"mappings":"AASA,gDAAgD;AAChD,MAAM,WAAW,QAAQ;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAoBD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CA0BlD"}
@@ -0,0 +1,50 @@
1
+ // 认证信息读取:key / agent / 环境变量密码,优先级依次降低,不硬编码
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import createLogger from "@xiaoqiong0v0/opencode-plugin-logger";
6
+ const log = createLogger("opencode-ssh-tool", { enabled: true });
7
+ /** 用户默认 SSH 私钥文件名(按优先级) */
8
+ const DEFAULT_KEYS = ["id_ed25519", "id_rsa"];
9
+ /** SSH agent socket 环境变量 */
10
+ const AGENT_ENV = "SSH_AUTH_SOCK";
11
+ /** 通用密码环境变量 */
12
+ const PASSWORD_ENV = "SSH_PASSWORD";
13
+ /**
14
+ * 计算用户 .ssh 目录:Windows 优先 USERPROFILE,其次 HOME,最后 os.homedir()
15
+ * @returns .ssh 目录绝对路径
16
+ */
17
+ function sshDir() {
18
+ const home = process.env.USERPROFILE || process.env.HOME || homedir();
19
+ return join(home, ".ssh");
20
+ }
21
+ /**
22
+ * 解析指定主机的认证信息,优先级:key > agent > 环境变量密码
23
+ * @param host 目标主机
24
+ * @returns 认证信息(任一字段可选)
25
+ */
26
+ export function resolveAuth(host) {
27
+ // 1. 用户默认 SSH 私钥(存在才用)
28
+ const dir = sshDir();
29
+ for (const name of DEFAULT_KEYS) {
30
+ const p = join(dir, name);
31
+ if (existsSync(p)) {
32
+ log.info(`使用私钥认证 ${p}`);
33
+ return { privateKey: readFileSync(p, "utf8") };
34
+ }
35
+ }
36
+ log.info(`未找到私钥(dir=${dir},${DEFAULT_KEYS.join("/")})`);
37
+ // 2. SSH agent socket(Unix)—— Windows 走 OpenSSH agent 时由 ssh2 自动处理
38
+ const agent = process.env[AGENT_ENV];
39
+ if (agent) {
40
+ return { agent };
41
+ }
42
+ // 3. 环境变量密码:SSH_PASS_<HOST大写> 优先,回退 SSH_PASSWORD
43
+ const hostKey = `SSH_PASS_${host.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
44
+ const password = process.env[hostKey] ?? process.env[PASSWORD_ENV];
45
+ if (password) {
46
+ return { password };
47
+ }
48
+ return {};
49
+ }
50
+ //# sourceMappingURL=ssh-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-auth.js","sourceRoot":"","sources":["../src/ssh-auth.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAE3C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,YAAY,MAAM,sCAAsC,CAAA;AAE/D,MAAM,GAAG,GAAG,YAAY,CAAC,mBAAmB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;AAShE,2BAA2B;AAC3B,MAAM,YAAY,GAAG,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAA;AAE7C,4BAA4B;AAC5B,MAAM,SAAS,GAAG,eAAe,CAAA;AAEjC,eAAe;AACf,MAAM,YAAY,GAAG,cAAc,CAAA;AAEnC;;;GAGG;AACH,SAAS,MAAM;IACb,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,CAAA;IACrE,OAAO,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AAC3B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,uBAAuB;IACvB,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACzB,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;YAClB,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;YACvB,OAAO,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,CAAA;QAChD,CAAC;IACH,CAAC;IACD,GAAG,CAAC,IAAI,CAAC,aAAa,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAEvD,mEAAmE;IACnE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACpC,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,EAAE,KAAK,EAAE,CAAA;IAClB,CAAC;IAED,iDAAiD;IACjD,MAAM,OAAO,GAAG,YAAY,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,EAAE,CAAA;IAC3E,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IAClE,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,EAAE,QAAQ,EAAE,CAAA;IACrB,CAAC;IAED,OAAO,EAAE,CAAA;AACX,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 生成命令完成哨兵标记(随机后缀防撞词)
3
+ * @returns 哨兵字符串
4
+ */
5
+ export declare function genSentinel(): string;
6
+ /**
7
+ * 剔除 PTY 回显的命令行(含提示符前缀,如 "user@host:~$ cmd"),并清理残留 CR
8
+ * @param raw 原始输出
9
+ * @param cmd 已发送命令(组合串)
10
+ * @returns 剔除回显后的输出
11
+ */
12
+ export declare function stripEcho(raw: string, cmd: string): string;
13
+ /**
14
+ * 剥离 ANSI 转义序列(颜色、光标控制等)
15
+ * @param s 原始字符串
16
+ * @returns 清洗后的纯文本
17
+ */
18
+ export declare function cleanAnsi(s: string): string;
19
+ /**
20
+ * 合并同一行内的 \r / ANSI 清行覆盖(进度条/旋转动画):保留每行最后一次刷新状态,
21
+ * 避免 "下载 1%\r下载 2%\r...\r下载 100%" 累积成巨量碎片输出
22
+ * 识别 \r 与 ANSI 清行(ESC[2K / ESC[K)+ 光标归位(ESC[G)作为行覆盖分隔符
23
+ * @param s 原始输出(含 ANSI 或已清洗均可)
24
+ * @returns 合并覆盖后的文本(\r\n 正常换行保留)
25
+ */
26
+ export declare function collapseCarriage(s: string): string;
27
+ /**
28
+ * 剥离尾部 shell 提示符残留(如 "5cb08b77db01:~$ "、"user@host:~/dir$ "、"root@server:/etc#")
29
+ * @param s 清洗后文本
30
+ * @returns 剥离尾部提示符后的文本
31
+ */
32
+ export declare function stripPrompt(s: string): string;
33
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3C;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAYlD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAI7C"}
package/dist/utils.js ADDED
@@ -0,0 +1,60 @@
1
+ // 工具函数:哨兵生成、回显剔除、ANSI 清洗、CR 覆盖合并、提示符剥离
2
+ import { randomBytes } from "node:crypto";
3
+ /**
4
+ * 生成命令完成哨兵标记(随机后缀防撞词)
5
+ * @returns 哨兵字符串
6
+ */
7
+ export function genSentinel() {
8
+ return `__SSH_DONE_${randomBytes(6).toString("hex")}__`;
9
+ }
10
+ /**
11
+ * 剔除 PTY 回显的命令行(含提示符前缀,如 "user@host:~$ cmd"),并清理残留 CR
12
+ * @param raw 原始输出
13
+ * @param cmd 已发送命令(组合串)
14
+ * @returns 剔除回显后的输出
15
+ */
16
+ export function stripEcho(raw, cmd) {
17
+ const lines = raw.split(/\r?\n/).map((line) => line.replace(/\r/g, "").trimEnd());
18
+ return lines.filter((line) => line !== "" && !line.includes(cmd)).join("\n");
19
+ }
20
+ /**
21
+ * 剥离 ANSI 转义序列(颜色、光标控制等)
22
+ * @param s 原始字符串
23
+ * @returns 清洗后的纯文本
24
+ */
25
+ export function cleanAnsi(s) {
26
+ // eslint-disable-next-line no-control-regex
27
+ return s.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
28
+ }
29
+ /**
30
+ * 合并同一行内的 \r / ANSI 清行覆盖(进度条/旋转动画):保留每行最后一次刷新状态,
31
+ * 避免 "下载 1%\r下载 2%\r...\r下载 100%" 累积成巨量碎片输出
32
+ * 识别 \r 与 ANSI 清行(ESC[2K / ESC[K)+ 光标归位(ESC[G)作为行覆盖分隔符
33
+ * @param s 原始输出(含 ANSI 或已清洗均可)
34
+ * @returns 合并覆盖后的文本(\r\n 正常换行保留)
35
+ */
36
+ export function collapseCarriage(s) {
37
+ // 覆盖分隔符:裸 \r 或 ANSI 清行/光标归位序列(ESC[2K、ESC[K、ESC[G)
38
+ const OVERWRITE = /\r|\x1b\[[0-9]*[KG]/;
39
+ // 按 \r\n 拆成行(保留正常 CRLF 换行),行内按覆盖分隔符拆分取最后一段
40
+ return s
41
+ .split(/\r\n/)
42
+ .map((line) => {
43
+ const segs = line.split(OVERWRITE).filter((seg) => seg !== "");
44
+ if (segs.length <= 1)
45
+ return line;
46
+ return segs[segs.length - 1];
47
+ })
48
+ .join("\n");
49
+ }
50
+ /**
51
+ * 剥离尾部 shell 提示符残留(如 "5cb08b77db01:~$ "、"user@host:~/dir$ "、"root@server:/etc#")
52
+ * @param s 清洗后文本
53
+ * @returns 剥离尾部提示符后的文本
54
+ */
55
+ export function stripPrompt(s) {
56
+ return s
57
+ .replace(/(?:^|[\r\n])\s*(?:[\w.-]+@)?[\w.-]+:[^\r\n]*[$#%] ?(?=[\r\n]|$)/g, "")
58
+ .trim();
59
+ }
60
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,uCAAuC;AAEvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC;;;GAGG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,cAAc,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAA;AACzD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,GAAW;IAChD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IACjF,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC9E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,4CAA4C;IAC5C,OAAO,CAAC,CAAC,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAA;AACvF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAS;IACxC,kDAAkD;IAClD,MAAM,SAAS,GAAG,qBAAqB,CAAA;IACvC,2CAA2C;IAC3C,OAAO,CAAC;SACL,KAAK,CAAC,MAAM,CAAC;SACb,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,CAAA;QAC9D,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;YAAE,OAAO,IAAI,CAAA;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC9B,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,CAAS;IACnC,OAAO,CAAC;SACL,OAAO,CAAC,kEAAkE,EAAE,EAAE,CAAC;SAC/E,IAAI,EAAE,CAAA;AACX,CAAC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@xiaoqiong0v0/opencode-ssh-tool",
3
+ "version": "0.2.4",
4
+ "type": "module",
5
+ "description": "长驻交互式 SSH 会话工具插件,远程命令受 opencode 权限管控",
6
+ "files": [
7
+ "dist",
8
+ "README.md"
9
+ ],
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": "./dist/index.js"
14
+ },
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/xiaoqiong0v0/opencode-ssh-tool.git"
22
+ },
23
+ "homepage": "https://github.com/xiaoqiong0v0/opencode-ssh-tool#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/xiaoqiong0v0/opencode-ssh-tool/issues"
26
+ },
27
+ "keywords": [
28
+ "opencode",
29
+ "plugin",
30
+ "ssh",
31
+ "sftp",
32
+ "terminal"
33
+ ],
34
+ "license": "MIT",
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "dependencies": {
39
+ "@xiaoqiong0v0/opencode-plugin-logger": "^1.0.2",
40
+ "ssh2": "^1.16.0"
41
+ },
42
+ "peerDependencies": {
43
+ "@opencode-ai/plugin": "^1.18.4"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^26.1.1",
47
+ "@types/ssh2": "^1.15.4",
48
+ "typescript": "^7.0.2"
49
+ }
50
+ }