@xiaoqiong0v0/opencode-ssh-tool 0.2.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -25
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/i18n.d.ts +275 -32
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +199 -30
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +305 -264
- package/dist/index.js.map +1 -1
- package/dist/local-session.d.ts +117 -0
- package/dist/local-session.d.ts.map +1 -0
- package/dist/local-session.js +365 -0
- package/dist/local-session.js.map +1 -0
- package/dist/server-manager.d.ts +3 -1
- package/dist/server-manager.d.ts.map +1 -1
- package/dist/server-manager.js +3 -2
- package/dist/server-manager.js.map +1 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +135 -49
- package/dist/server.js.map +1 -1
- package/dist/session-store.d.ts +6 -0
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +15 -0
- package/dist/session-store.js.map +1 -1
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +13 -1
- package/dist/utils.js.map +1 -1
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-manager.js","sourceRoot":"","sources":["../src/server-manager.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAE5C,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,WAAW,EAAwC,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"server-manager.js","sourceRoot":"","sources":["../src/server-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Server } from "node:http";
|
|
2
2
|
import type { SshSession } from "./session.js";
|
|
3
|
+
import { type Lang } from "./i18n.js";
|
|
3
4
|
/** 服务实例信息 */
|
|
4
5
|
export interface ServerHandle {
|
|
5
6
|
server: Server;
|
|
@@ -20,7 +21,8 @@ export interface SessionEntry {
|
|
|
20
21
|
* @param port 端口,0 或未指定 → 系统随机分配(避免冲突)
|
|
21
22
|
* @param getSessions 获取扁平会话条目列表的函数(供本进程页面展示)
|
|
22
23
|
* @param dir 插件缓存根目录(跨进程状态文件/历史文件所在处)
|
|
24
|
+
* @param lang Web 界面语言(默认 en)
|
|
23
25
|
* @returns 服务句柄(含实际端口与 URL)
|
|
24
26
|
*/
|
|
25
|
-
export declare function startServer(port: number, getSessions: () => SessionEntry[], dir: string): Promise<ServerHandle>;
|
|
27
|
+
export declare function startServer(port: number, getSessions: () => SessionEntry[], dir: string, lang?: Lang): Promise<ServerHandle>;
|
|
26
28
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAA;AAIrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAA;AAIrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,EAAoB,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AAEvD,aAAa;AACb,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,IAAI,IAAI,CAAA;CACd;AAED,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,UAAU,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,YAAY,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,IAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAwFlI"}
|
package/dist/server.js
CHANGED
|
@@ -3,14 +3,16 @@ import { createServer } from "node:http";
|
|
|
3
3
|
import { readdirSync, readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { listAllSessions } from "./session-store.js";
|
|
6
|
+
import { tr } from "./i18n.js";
|
|
6
7
|
/**
|
|
7
8
|
* 启动 HTTP 服务(监听 127.0.0.1)
|
|
8
9
|
* @param port 端口,0 或未指定 → 系统随机分配(避免冲突)
|
|
9
10
|
* @param getSessions 获取扁平会话条目列表的函数(供本进程页面展示)
|
|
10
11
|
* @param dir 插件缓存根目录(跨进程状态文件/历史文件所在处)
|
|
12
|
+
* @param lang Web 界面语言(默认 en)
|
|
11
13
|
* @returns 服务句柄(含实际端口与 URL)
|
|
12
14
|
*/
|
|
13
|
-
export function startServer(port, getSessions, dir) {
|
|
15
|
+
export function startServer(port, getSessions, dir, lang = "en") {
|
|
14
16
|
let actualPort = port;
|
|
15
17
|
const server = createServer((req, res) => {
|
|
16
18
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -21,14 +23,14 @@ export function startServer(port, getSessions, dir) {
|
|
|
21
23
|
return;
|
|
22
24
|
}
|
|
23
25
|
if (path === "/api/status") {
|
|
24
|
-
// 跨进程聚合:所有 opencode
|
|
26
|
+
// 跨进程聚合:所有 opencode 进程写入的状态文件(含本进程,SSH + 本地/容器)
|
|
25
27
|
const states = listAllSessions(dir);
|
|
26
28
|
// 按 sessionID 分组(一个会话下多个终端)
|
|
27
29
|
const bySession = new Map();
|
|
28
30
|
for (const s of states) {
|
|
29
31
|
if (!bySession.has(s.sessionID))
|
|
30
32
|
bySession.set(s.sessionID, { title: s.title, directory: s.directory, terminals: [] });
|
|
31
|
-
bySession.get(s.sessionID).terminals.push({ name: s.name, host: s.host, user: s.user, port: s.port, connected: s.connected, busy: s.busy, pending: s.pending });
|
|
33
|
+
bySession.get(s.sessionID).terminals.push({ name: s.name, kind: s.kind, host: s.host, user: s.user, port: s.port, program: s.program, connected: s.connected, busy: s.busy, pending: s.pending });
|
|
32
34
|
}
|
|
33
35
|
const sessions = [...bySession.entries()].map(([sessionID, v]) => ({
|
|
34
36
|
sessionID,
|
|
@@ -44,37 +46,38 @@ export function startServer(port, getSessions, dir) {
|
|
|
44
46
|
const sid = url.searchParams.get("session") ?? "";
|
|
45
47
|
const name = url.searchParams.get("name") ?? "default";
|
|
46
48
|
const entry = getSessions().find((e) => e.sessionID === sid && e.name === name);
|
|
49
|
+
let pairs;
|
|
47
50
|
if (!entry) {
|
|
48
51
|
// 本进程无此会话句柄(其他进程的会话/复用服务)→ 从历史文件读取
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
// 本地/容器会话的历史目录带 local- 前缀,需按状态里的 kind 探测
|
|
53
|
+
const state = listAllSessions(dir).find((s) => s.sessionID === sid && s.name === name);
|
|
54
|
+
const histName = state?.kind === "local" ? `local-${name}` : name;
|
|
55
|
+
pairs = readHistoryFromFile(dir, sid, histName) ?? [];
|
|
56
|
+
if (pairs.length === 0 && !state) {
|
|
57
|
+
// 状态文件与历史都消失 = 会话真正删除,前端剔除该幽灵终端
|
|
58
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
59
|
+
res.end(JSON.stringify({ pairs: [], notFound: true }));
|
|
53
60
|
return;
|
|
54
61
|
}
|
|
55
|
-
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
56
|
-
res.end(text);
|
|
57
|
-
return;
|
|
58
62
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
lines.push(`[ 运行中 ] ${running}`);
|
|
63
|
+
else {
|
|
64
|
+
// 命令+输出消息对(保留 ANSI 颜色,前端解析着色);运行中附加实时进度
|
|
65
|
+
pairs = [];
|
|
66
|
+
for (const pair of entry.session.getHistory().getPairs()) {
|
|
67
|
+
pairs.push({ type: "cmd", ts: pair.ts, text: pair.command });
|
|
68
|
+
pairs.push({ type: "out", text: entry.session.getHistory().readOutput(pair) });
|
|
69
|
+
}
|
|
70
|
+
const running = entry.session.getRunningOutput();
|
|
71
|
+
if (running)
|
|
72
|
+
pairs.push({ type: "run", text: running });
|
|
70
73
|
}
|
|
71
|
-
res.writeHead(200, { "Content-Type": "
|
|
72
|
-
res.end(
|
|
74
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
75
|
+
res.end(JSON.stringify({ pairs }));
|
|
73
76
|
return;
|
|
74
77
|
}
|
|
75
78
|
if (path === "/") {
|
|
76
79
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
77
|
-
res.end(renderPage());
|
|
80
|
+
res.end(renderPage(lang));
|
|
78
81
|
return;
|
|
79
82
|
}
|
|
80
83
|
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
@@ -99,8 +102,8 @@ export function startServer(port, getSessions, dir) {
|
|
|
99
102
|
* 从历史文件读取某终端记录(跨进程场景:本进程无该会话句柄时用)
|
|
100
103
|
* @param dir 插件缓存根目录
|
|
101
104
|
* @param sessionID opencode 会话 ID
|
|
102
|
-
* @param name
|
|
103
|
-
* @returns
|
|
105
|
+
* @param name 终端名(已含 local- 前缀则按本地目录)
|
|
106
|
+
* @returns 结构化消息对(保留 ANSI);目录不存在返回 null
|
|
104
107
|
*/
|
|
105
108
|
function readHistoryFromFile(dir, sessionID, name) {
|
|
106
109
|
const hdir = join(dir, sessionID, name);
|
|
@@ -112,34 +115,43 @@ function readHistoryFromFile(dir, sessionID, name) {
|
|
|
112
115
|
return null;
|
|
113
116
|
}
|
|
114
117
|
files.sort();
|
|
115
|
-
const
|
|
118
|
+
const out = [];
|
|
116
119
|
for (const f of files) {
|
|
117
120
|
try {
|
|
118
121
|
const data = JSON.parse(readFileSync(join(hdir, f), "utf8"));
|
|
119
122
|
if (typeof data.command === "string")
|
|
120
|
-
|
|
123
|
+
out.push({ type: "cmd", ts: data.ts ?? Date.now(), text: data.command });
|
|
121
124
|
if (typeof data.output === "string")
|
|
122
|
-
|
|
125
|
+
out.push({ type: "out", text: data.output });
|
|
123
126
|
}
|
|
124
127
|
catch {
|
|
125
128
|
/* 跳过损坏文件 */
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
|
-
return
|
|
131
|
+
return out;
|
|
129
132
|
}
|
|
130
|
-
/** 渲染终端记录页面(黑底绿字等宽,JS
|
|
131
|
-
function renderPage() {
|
|
133
|
+
/** 渲染终端记录页面(黑底绿字等宽,JS 轮询自动刷新),语言由配置 webLang 决定 */
|
|
134
|
+
function renderPage(lang) {
|
|
135
|
+
const w = (key) => tr(key, lang);
|
|
132
136
|
return `<!DOCTYPE html>
|
|
133
|
-
<html lang="
|
|
137
|
+
<html lang="${lang}">
|
|
134
138
|
<head>
|
|
135
139
|
<meta charset="utf-8">
|
|
136
|
-
<title
|
|
140
|
+
<title>${w("web_title")}</title>
|
|
137
141
|
<style>
|
|
138
142
|
html, body { margin: 0; height: 100%; background: #0d1117; color: #c9d1d9; font-family: system-ui, sans-serif; }
|
|
139
143
|
header { position: sticky; top: 0; display: flex; align-items: center; gap: 12px; padding: 10px 16px; background: #161b22; border-bottom: 1px solid #30363d; }
|
|
140
144
|
header h1 { margin: 0; font-size: 14px; color: #e6edf3; font-weight: 600; }
|
|
141
145
|
select { background: #21262d; color: #c9d1d9; border: 1px solid #30363d; padding: 4px 8px; border-radius: 6px; }
|
|
142
|
-
pre { margin: 0; padding: 16px; font-family: "Cascadia Code", Consolas, "Courier New", monospace; font-size: 13px; line-height: 1.5; white-space: pre; overflow: auto; height: calc(100% - 52px); box-sizing: border-box; color: #c9d1d9; }
|
|
146
|
+
pre { margin: 0; padding: 16px; font-family: "Cascadia Code", Consolas, "Courier New", monospace; font-size: 13px; line-height: 1.5; white-space: pre-wrap; overflow: auto; height: calc(100% - 52px); box-sizing: border-box; color: #c9d1d9; }
|
|
147
|
+
/* 两列布局:左侧时间列(开关控制显隐),右侧内容列(命令/输出格式不变) */
|
|
148
|
+
.row { display: flex; align-items: stretch; }
|
|
149
|
+
.row .t { box-sizing: border-box; flex: 0 0 172px; color: #8b949e; padding: 0 10px 0 6px; border-right: 1px solid #30363d; white-space: pre; }
|
|
150
|
+
.row .c { flex: 1; padding-left: 12px; white-space: pre; }
|
|
151
|
+
body:not(.show-time) .row .t { display: none; }
|
|
152
|
+
/* 命令行:明显区分(背景条 + 左侧蓝条 + 加粗);用 box-shadow 不占布局,保证时间列/分割线与输出行完全对齐 */
|
|
153
|
+
.row.cmdline { background: #161b22; box-shadow: inset 3px 0 0 #58a6ff; font-weight: 600; }
|
|
154
|
+
.tgl { font-size: 12px; color: #8b949e; display: flex; align-items: center; gap: 4px; white-space: nowrap; }
|
|
143
155
|
#meta { font-size: 12px; color: #8b949e; }
|
|
144
156
|
/* 深色细滚动条:匹配暗色主题 */
|
|
145
157
|
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
@@ -160,13 +172,14 @@ function renderPage() {
|
|
|
160
172
|
</head>
|
|
161
173
|
<body>
|
|
162
174
|
<header>
|
|
163
|
-
<h1
|
|
175
|
+
<h1>${w("web_title")}</h1>
|
|
164
176
|
<select id="session" onchange="onSessionChange(true)"></select>
|
|
165
177
|
<select id="terminal" onchange="onSessionChange(true)"></select>
|
|
178
|
+
<label class="tgl"><input type="checkbox" id="showTime" onchange="onShowTimeChange()"> ${w("web_time")}</label>
|
|
166
179
|
<span id="meta"></span>
|
|
167
180
|
</header>
|
|
168
|
-
<pre id="term"
|
|
169
|
-
<button id="toBottom" class="to-bottom-btn" onclick="scrollToNewest()"
|
|
181
|
+
<pre id="term">${w("web_loading")}</pre>
|
|
182
|
+
<button id="toBottom" class="to-bottom-btn" onclick="scrollToNewest()">${w("web_new_messages")}</button>
|
|
170
183
|
<script>
|
|
171
184
|
let sessionsData = [];
|
|
172
185
|
// 是否贴底(用户向上滚动查看历史时不自动下滚,仅贴底时跟随新输出)
|
|
@@ -231,8 +244,13 @@ function renderPage() {
|
|
|
231
244
|
}
|
|
232
245
|
|
|
233
246
|
async function loadSessions() {
|
|
234
|
-
|
|
235
|
-
|
|
247
|
+
let data;
|
|
248
|
+
try {
|
|
249
|
+
const r = await fetch("/api/status");
|
|
250
|
+
data = await r.json();
|
|
251
|
+
} catch (e) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
236
254
|
const newSessions = data.sessions || [];
|
|
237
255
|
const sel = document.getElementById("session");
|
|
238
256
|
// 会话指纹未变化则跳过重建(保留用户选择与焦点)
|
|
@@ -245,7 +263,29 @@ function renderPage() {
|
|
|
245
263
|
for (const s of sessionsData) {
|
|
246
264
|
const opt = document.createElement("option");
|
|
247
265
|
opt.value = s.sessionID;
|
|
248
|
-
opt.textContent = sessionLabel(s) + " (" + s.terminals.length + "
|
|
266
|
+
opt.textContent = sessionLabel(s) + " (" + s.terminals.length + " ${w("web_terminals")})";
|
|
267
|
+
sel.appendChild(opt);
|
|
268
|
+
}
|
|
269
|
+
if (prev && [...sel.options].some(o => o.value === prev)) sel.value = prev;
|
|
270
|
+
else sel.selectedIndex = sessionsData.length ? 0 : -1;
|
|
271
|
+
onSessionChange(false);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 剔除失效终端/会话(transcript notFound 时),自动重建下拉框并切到下一个可用项
|
|
275
|
+
function dropDeadTerminal(sid, name) {
|
|
276
|
+
const s = sessionsData.find(x => x.sessionID === sid);
|
|
277
|
+
if (s) {
|
|
278
|
+
s.terminals = s.terminals.filter(t => (t.name || "default") !== name);
|
|
279
|
+
if (s.terminals.length === 0) sessionsData = sessionsData.filter(x => x.sessionID !== sid);
|
|
280
|
+
}
|
|
281
|
+
lastSessionsKey = "";
|
|
282
|
+
const sel = document.getElementById("session");
|
|
283
|
+
const prev = sel.value;
|
|
284
|
+
sel.innerHTML = "";
|
|
285
|
+
for (const s2 of sessionsData) {
|
|
286
|
+
const opt = document.createElement("option");
|
|
287
|
+
opt.value = s2.sessionID;
|
|
288
|
+
opt.textContent = sessionLabel(s2) + " (" + s2.terminals.length + " ${w("web_terminals")})";
|
|
249
289
|
sel.appendChild(opt);
|
|
250
290
|
}
|
|
251
291
|
if (prev && [...sel.options].some(o => o.value === prev)) sel.value = prev;
|
|
@@ -265,7 +305,7 @@ function renderPage() {
|
|
|
265
305
|
for (const t of (s ? s.terminals : [])) {
|
|
266
306
|
const opt = document.createElement("option");
|
|
267
307
|
opt.value = t.name || "default";
|
|
268
|
-
opt.textContent = (t.name || "default") + " " + (t.connected ? "●" : "○") + (t.busy ? " ⏳" : "");
|
|
308
|
+
opt.textContent = (t.name || "default") + (t.kind === "local" ? " [${w("web_local")}]" : "") + " " + (t.connected ? "●" : "○") + (t.busy ? " ⏳" : "");
|
|
269
309
|
tsel.appendChild(opt);
|
|
270
310
|
}
|
|
271
311
|
if (prev && [...tsel.options].some(o => o.value === prev)) tsel.value = prev;
|
|
@@ -281,18 +321,47 @@ function renderPage() {
|
|
|
281
321
|
const tsel = document.getElementById("terminal");
|
|
282
322
|
const sid = sel.value;
|
|
283
323
|
const name = tsel.value;
|
|
284
|
-
if (!sid || !name) { pre.textContent = "
|
|
324
|
+
if (!sid || !name) { pre.textContent = "${w("web_no_session")}"; return; }
|
|
285
325
|
// 更新前判断是否贴底:用户已向上滚动离开底部则不自动下滚,保持当前位置
|
|
286
326
|
if (pre.scrollTop + pre.clientHeight >= pre.scrollHeight - 30) stickToBottom = true;
|
|
287
|
-
const
|
|
288
|
-
|
|
327
|
+
const showTime = document.getElementById("showTime").checked;
|
|
328
|
+
document.body.classList.toggle("show-time", showTime);
|
|
329
|
+
let data;
|
|
330
|
+
try {
|
|
331
|
+
const r = await fetch("/api/transcript?session=" + encodeURIComponent(sid) + "&name=" + encodeURIComponent(name));
|
|
332
|
+
if (!r.ok) throw new Error("HTTP " + r.status);
|
|
333
|
+
data = await r.json();
|
|
334
|
+
} catch (e) {
|
|
335
|
+
pre.textContent = "${w("web_load_failed")}";
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const pairs = data.pairs || [];
|
|
339
|
+
// 会话已不存在(残留状态文件但记录已清):提示并自动剔除该终端/会话
|
|
340
|
+
if (data.notFound) {
|
|
341
|
+
lastTranscript = "";
|
|
342
|
+
pendingTop = 0;
|
|
343
|
+
pre.innerHTML = '<div class="row"><span class="t"></span><span class="c">' + "${w("web_session_gone")}" + '</span></div>';
|
|
344
|
+
document.getElementById("meta").textContent = "";
|
|
345
|
+
dropDeadTerminal(sid, name);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
289
348
|
// 渲染前旧内容完整高度 = 新消息顶部位置(旧内容不变时高度稳定)
|
|
290
349
|
const prevHeight = pre.scrollHeight;
|
|
291
350
|
// 内容是否有新变化(有新消息才显示悬浮按钮)
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
351
|
+
const sig = JSON.stringify(pairs);
|
|
352
|
+
const changed = sig !== lastTranscript;
|
|
353
|
+
lastTranscript = sig;
|
|
354
|
+
pre.innerHTML = pairs.map((p) => {
|
|
355
|
+
if (p.type === "cmd") {
|
|
356
|
+
const t = showTime ? fmtTime(p.ts) : "";
|
|
357
|
+
return '<div class="row cmdline"><span class="t">' + t + '</span><span class="c">' + ansiToHtml(p.text) + '</span></div>';
|
|
358
|
+
}
|
|
359
|
+
// out / run:输出列,保留 ANSI 彩色,时间列留空(不改变输出格式)
|
|
360
|
+
const label = p.type === "run" ? "${w("web_running")}" : "";
|
|
361
|
+
return '<div class="row"><span class="t"></span><span class="c">' + label + ansiToHtml(p.text) + '</span></div>';
|
|
362
|
+
}).join("");
|
|
363
|
+
const cmdCount = pairs.filter((p) => p.type === "cmd").length;
|
|
364
|
+
document.getElementById("meta").textContent = sid + "/" + name + " · " + cmdCount + " ${w("web_commands")} · ${w("web_auto_refresh")}";
|
|
296
365
|
if (stickToBottom) {
|
|
297
366
|
pre.scrollTop = pre.scrollHeight;
|
|
298
367
|
toBottomBtn.style.display = "none";
|
|
@@ -310,6 +379,23 @@ function renderPage() {
|
|
|
310
379
|
}
|
|
311
380
|
}
|
|
312
381
|
|
|
382
|
+
// 时间戳格式化 yyyy-MM-dd HH:mm:ss
|
|
383
|
+
function fmtTime(ts) {
|
|
384
|
+
const d = new Date(ts);
|
|
385
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
386
|
+
return d.getFullYear() + "-" + p(d.getMonth() + 1) + "-" + p(d.getDate()) + " " + p(d.getHours()) + ":" + p(d.getMinutes()) + ":" + p(d.getSeconds());
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 时间开关:localStorage 持久化,刷新页面保持状态
|
|
390
|
+
function onShowTimeChange() {
|
|
391
|
+
const el = document.getElementById("showTime");
|
|
392
|
+
localStorage.setItem("showTime", el.checked ? "1" : "0");
|
|
393
|
+
document.body.classList.toggle("show-time", el.checked);
|
|
394
|
+
loadTranscript();
|
|
395
|
+
}
|
|
396
|
+
document.getElementById("showTime").checked = localStorage.getItem("showTime") === "1";
|
|
397
|
+
document.body.classList.toggle("show-time", document.getElementById("showTime").checked);
|
|
398
|
+
|
|
313
399
|
// 用户滚动:贴底 → 恢复跟随并隐藏按钮;离开底部 → 取消自动贴底
|
|
314
400
|
const termPre = document.getElementById("term");
|
|
315
401
|
const toBottomBtn = document.getElementById("toBottom");
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAE9C,OAAO,EAAE,YAAY,EAAe,MAAM,WAAW,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAGhC,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/session-store.d.ts
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
export interface SessionState {
|
|
3
3
|
sessionID: string;
|
|
4
4
|
name: string;
|
|
5
|
+
/** 会话类型:ssh 远程 / local 本地·容器 */
|
|
6
|
+
kind?: "ssh" | "local";
|
|
5
7
|
host?: string;
|
|
6
8
|
user?: string;
|
|
7
9
|
port?: number;
|
|
10
|
+
/** 本地/容器会话的程序(如 pwsh、docker exec ...) */
|
|
11
|
+
program?: string;
|
|
8
12
|
connected: boolean;
|
|
9
13
|
busy: boolean;
|
|
10
14
|
pending: number;
|
|
@@ -18,6 +22,8 @@ export interface SessionState {
|
|
|
18
22
|
export declare function writeSessionState(cacheRoot: string, state: SessionState): void;
|
|
19
23
|
/** 删除某会话状态文件(断开/会话删除时调用) */
|
|
20
24
|
export declare function removeSessionState(cacheRoot: string, sessionID: string, name: string): void;
|
|
25
|
+
/** 删除某会话全部终端的状态文件(会话删除时调用,防残留) */
|
|
26
|
+
export declare function removeAllSessionStates(cacheRoot: string, sessionID: string): void;
|
|
21
27
|
/** 聚合所有进程的会话状态(web 服务展示用),含标题/目录 */
|
|
22
28
|
export declare function listAllSessions(cacheRoot: string): SessionState[];
|
|
23
29
|
//# sourceMappingURL=session-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAKA,yBAAyB;AACzB,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AAUD,8BAA8B;AAC9B,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAQ9E;AAED,4BAA4B;AAC5B,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAM3F;AAED,oCAAoC;AACpC,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CAoBjE"}
|
|
1
|
+
{"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAKA,yBAAyB;AACzB,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,gCAAgC;IAChC,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,CAAA;IACtB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AAUD,8BAA8B;AAC9B,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAQ9E;AAED,4BAA4B;AAC5B,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAM3F;AAED,kCAAkC;AAClC,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAYjF;AAED,oCAAoC;AACpC,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CAoBjE"}
|
package/dist/session-store.js
CHANGED
|
@@ -27,6 +27,21 @@ export function removeSessionState(cacheRoot, sessionID, name) {
|
|
|
27
27
|
/* 忽略 */
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
/** 删除某会话全部终端的状态文件(会话删除时调用,防残留) */
|
|
31
|
+
export function removeAllSessionStates(cacheRoot, sessionID) {
|
|
32
|
+
try {
|
|
33
|
+
const dir = join(cacheRoot, SESSIONS_DIR);
|
|
34
|
+
const prefix = `${sessionID}-`;
|
|
35
|
+
for (const f of readdirSync(dir)) {
|
|
36
|
+
if (f.startsWith(prefix) && f.endsWith(".json")) {
|
|
37
|
+
rmSync(join(dir, f), { force: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* 忽略 */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
30
45
|
/** 聚合所有进程的会话状态(web 服务展示用),含标题/目录 */
|
|
31
46
|
export function listAllSessions(cacheRoot) {
|
|
32
47
|
const dir = join(cacheRoot, SESSIONS_DIR);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAE5C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACrF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export declare function genSentinel(): string;
|
|
6
6
|
/**
|
|
7
7
|
* 剔除 PTY 回显的命令行(含提示符前缀,如 "user@host:~$ cmd"),并清理残留 CR
|
|
8
|
+
* 同时剔除含哨兵标记的行(上次命令在哨兵之后到达的回显残留,避免污染下一条输出)
|
|
8
9
|
* @param raw 原始输出
|
|
9
10
|
* @param cmd 已发送命令(组合串)
|
|
10
11
|
* @returns 剔除回显后的输出
|
|
@@ -30,4 +31,10 @@ export declare function collapseCarriage(s: string): string;
|
|
|
30
31
|
* @returns 剥离尾部提示符后的文本
|
|
31
32
|
*/
|
|
32
33
|
export declare function stripPrompt(s: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* 时间戳格式化为 yyyy-MM-dd HH:mm:ss
|
|
36
|
+
* @param ts 毫秒时间戳
|
|
37
|
+
* @returns 格式化时间
|
|
38
|
+
*/
|
|
39
|
+
export declare function fmtTime(ts: number): string;
|
|
33
40
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAI1D;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;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAI1C"}
|
package/dist/utils.js
CHANGED
|
@@ -9,13 +9,15 @@ export function genSentinel() {
|
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* 剔除 PTY 回显的命令行(含提示符前缀,如 "user@host:~$ cmd"),并清理残留 CR
|
|
12
|
+
* 同时剔除含哨兵标记的行(上次命令在哨兵之后到达的回显残留,避免污染下一条输出)
|
|
12
13
|
* @param raw 原始输出
|
|
13
14
|
* @param cmd 已发送命令(组合串)
|
|
14
15
|
* @returns 剔除回显后的输出
|
|
15
16
|
*/
|
|
16
17
|
export function stripEcho(raw, cmd) {
|
|
18
|
+
const SENTINEL_LINE = /__SSH_DONE_[0-9a-f]+__/;
|
|
17
19
|
const lines = raw.split(/\r?\n/).map((line) => line.replace(/\r/g, "").trimEnd());
|
|
18
|
-
return lines.filter((line) => line !== "" && !line.includes(cmd)).join("\n");
|
|
20
|
+
return lines.filter((line) => line !== "" && !line.includes(cmd) && !SENTINEL_LINE.test(line)).join("\n");
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* 剥离 ANSI 转义序列(颜色、光标控制等)
|
|
@@ -57,4 +59,14 @@ export function stripPrompt(s) {
|
|
|
57
59
|
.replace(/(?:^|[\r\n])\s*(?:[\w.-]+@)?[\w.-]+:[^\r\n]*[$#%] ?(?=[\r\n]|$)/g, "")
|
|
58
60
|
.trim();
|
|
59
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* 时间戳格式化为 yyyy-MM-dd HH:mm:ss
|
|
64
|
+
* @param ts 毫秒时间戳
|
|
65
|
+
* @returns 格式化时间
|
|
66
|
+
*/
|
|
67
|
+
export function fmtTime(ts) {
|
|
68
|
+
const d = new Date(ts);
|
|
69
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
70
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
71
|
+
}
|
|
60
72
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +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
|
|
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;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,GAAW;IAChD,MAAM,aAAa,GAAG,wBAAwB,CAAA;IAC9C,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,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC3G,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;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAA;IACtB,MAAM,CAAC,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACnD,OAAO,GAAG,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,EAAE,CAAA;AACnI,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiaoqiong0v0/opencode-ssh-tool",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "长驻交互式 SSH 会话工具插件,远程命令受 opencode 权限管控",
|
|
6
6
|
"files": [
|
|
@@ -36,8 +36,9 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@xiaoqiong0v0/opencode-plugin-logger": "^1.0.
|
|
40
|
-
"ssh2": "^1.16.0"
|
|
39
|
+
"@xiaoqiong0v0/opencode-plugin-logger": "^1.0.4",
|
|
40
|
+
"ssh2": "^1.16.0",
|
|
41
|
+
"string-argv": "^0.3.2"
|
|
41
42
|
},
|
|
42
43
|
"peerDependencies": {
|
|
43
44
|
"@opencode-ai/plugin": "^1.18.4"
|