terminal-bridge-setup 2.0.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 ADDED
@@ -0,0 +1,65 @@
1
+ # terminal-bridge-setup
2
+
3
+ > 一次性安装器:让 Agent 能通过浏览器 xterm 终端(JumpServer Web 终端 / Arthas Console)执行命令并拿回输出。
4
+
5
+ ## 一键安装
6
+
7
+ 需要 Node.js 18+ 和 Chrome 浏览器:
8
+
9
+ ```bash
10
+ npx terminal-bridge-setup
11
+ ```
12
+
13
+ 安装器自动完成:
14
+
15
+ 1. 释放文件到 `~/.terminal-bridge/`
16
+ 2. 安装代理依赖
17
+ 3. 注册 Native Messaging Host 到 Chrome
18
+ 4. 打开 `chrome://extensions` 引导加载插件
19
+
20
+ ## 加载 Chrome 插件(手动,一次性)
21
+
22
+ `npx` 会自动打开 `chrome://extensions`,按提示操作:
23
+
24
+ 1. 右上角打开「开发者模式」
25
+ 2. 点「加载已解压的扩展程序」
26
+ 3. 选择目录:`~/.terminal-bridge/extension`
27
+ 4. 确认插件 ID 是 `jkbnakjnbahigfefgiipfngheiafoein`
28
+
29
+ ## 使用
30
+
31
+ 1. 打开终端页面(JumpServer Web 终端 / Arthas Console),完成连接
32
+ 2. 点插件图标 →「🔍 捕捉终端」→「🚀 启动代理」
33
+ 3. 两个绿灯亮,Agent 即可通过桥接发命令
34
+
35
+ ## 架构
36
+
37
+ ```
38
+ Agent (命令)
39
+ ↓ ws → 本地代理 (127.0.0.1:8787)
40
+ 本地代理 (prompt 锚点配对 + Arthas 安全基线)
41
+ ↓ ws → Chrome 插件 background
42
+ 插件 (CDP 抓 WS 帧 + 注入 xterm)
43
+ ↓ → 终端页面 xterm → 远端 SSH / JVM
44
+ ```
45
+
46
+ ## 特性
47
+
48
+ - **双终端支持**:JumpServer 堡垒机 Web 终端 + Arthas Web Console
49
+ - **结构化输出**:ANSI 已清理,命令回显已去除,返回纯净文本
50
+ - **sudo 自动重试**:检测 sudo 别名劫持,询问用户后切 root 重试
51
+ - **Arthas 安全基线**:中风险命令(trace/watch)自动补 `-n`,高风险命令(retransform/profiler/stop)无条件禁用
52
+ - **多终端切换**:popup 内 tab 选择器,带 host 标签区分
53
+
54
+ ## 卸载
55
+
56
+ ```bash
57
+ rm -rf ~/.terminal-bridge
58
+ rm "$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.wssniffer.host.json"
59
+ ```
60
+
61
+ 然后在 `chrome://extensions` 移除插件。
62
+
63
+ ## License
64
+
65
+ MIT
package/bin/setup.mjs ADDED
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env node
2
+ // terminal-bridge-setup —— 终端桥接一次性安装器
3
+ //
4
+ // 做 4 件事:
5
+ // 1. 把插件源码 + 代理 + native host 释放到 ~/.terminal-bridge/
6
+ // 2. 在代理目录跑 npm install(装 ws)
7
+ // 3. 注册 native messaging host 到 Chrome(复用 native/install.sh)
8
+ // 4. 打开 chrome://extensions,引导用户"加载已解压扩展"
9
+ //
10
+ // 用法(发布后):
11
+ // npx terminal-bridge-setup
12
+ // 本地测试:
13
+ // node setup-pkg/bin/setup.mjs
14
+
15
+ import { existsSync, mkdirSync, cpSync, rmSync, readFileSync, writeFileSync } from "node:fs";
16
+ import { join, dirname, resolve } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ import { homedir, platform } from "node:os";
19
+ import { spawnSync } from "node:child_process";
20
+
21
+ const __dirname = dirname(fileURLToPath(import.meta.url));
22
+ const FILES_DIR = join(__dirname, "..", "files");
23
+ const INSTALL_DIR = join(homedir(), ".terminal-bridge");
24
+
25
+ // 插件 ID(由 manifest.json 的 key 字段决定,固定不变)
26
+ const EXTENSION_ID = "jkbnakjnbahigfefgiipfngheiafoein";
27
+
28
+ // 颜色输出(简单的 ANSI,不用 chalk 避免依赖)
29
+ const c = {
30
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
31
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
32
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
33
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
34
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
35
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
36
+ };
37
+ const log = (msg) => console.log(msg);
38
+ const ok = (msg) => console.log(c.green("✓ ") + msg);
39
+ const fail = (msg) => { console.error(c.red("✗ ") + msg); process.exit(1); };
40
+ const step = (n, msg) => console.log(`\n${c.bold(c.cyan(`[${n}/4]`))} ${msg}`);
41
+
42
+ // ===================== 步骤 1:释放文件 =====================
43
+ function releaseFiles() {
44
+ step(1, `释放文件到 ${c.dim(INSTALL_DIR)}`);
45
+
46
+ if (!existsSync(FILES_DIR)) {
47
+ fail(`安装包不完整:未找到 ${FILES_DIR}`);
48
+ }
49
+
50
+ // 已存在:提示用户。除非带 --force,否则保留用户的 extension 配置覆盖。
51
+ const isForce = process.argv.includes("--force");
52
+ if (existsSync(INSTALL_DIR)) {
53
+ if (isForce) {
54
+ console.log(c.yellow(" 已存在 ~/.terminal-bridge/,--force 模式:覆盖"));
55
+ rmSync(INSTALL_DIR, { recursive: true, force: true });
56
+ } else {
57
+ // 不强制覆盖时,仍然更新文件(保留 .proxy.pid/.proxy.log 等运行时产物)
58
+ console.log(c.yellow(" ~/.terminal-bridge/ 已存在,将更新文件(运行时产物保留)"));
59
+ }
60
+ }
61
+
62
+ mkdirSync(INSTALL_DIR, { recursive: true });
63
+
64
+ // 复制三个子目录
65
+ for (const sub of ["proxy", "native", "extension"]) {
66
+ const src = join(FILES_DIR, sub);
67
+ const dst = join(INSTALL_DIR, sub);
68
+ if (!existsSync(src)) {
69
+ fail(`安装包缺少 files/${sub}/`);
70
+ }
71
+ cpSync(src, dst, { recursive: true, force: true });
72
+ ok(`释放 ${sub}/`);
73
+ }
74
+ }
75
+
76
+ // ===================== 步骤 2:装代理依赖 =====================
77
+ function installProxyDeps() {
78
+ step(2, "安装代理依赖 (ws)");
79
+
80
+ // 如果 node_modules/ws 已存在(从包里带出来的),跳过
81
+ const wsPath = join(INSTALL_DIR, "proxy", "node_modules", "ws");
82
+ if (existsSync(wsPath)) {
83
+ ok("依赖已存在,跳过");
84
+ return;
85
+ }
86
+
87
+ const proxyDir = join(INSTALL_DIR, "proxy");
88
+ console.log(c.dim(" 运行 npm install ..."));
89
+ const result = spawnSync("npm", ["install", "--silent", "--no-audit", "--no-fund"], {
90
+ cwd: proxyDir,
91
+ stdio: "inherit",
92
+ });
93
+
94
+ if (result.status !== 0) {
95
+ fail("npm install 失败,请检查网络后重试");
96
+ }
97
+ ok("依赖安装完成");
98
+ }
99
+
100
+ // ===================== 步骤 3:注册 native host =====================
101
+ function registerNativeHost() {
102
+ step(3, "注册 Native Messaging Host");
103
+
104
+ // macOS / Linux 路径不同
105
+ const plat = platform();
106
+ let destDir;
107
+ if (plat === "darwin") {
108
+ destDir = join(homedir(), "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts");
109
+ } else if (plat === "linux") {
110
+ destDir = join(homedir(), ".config", "google-chrome", "NativeMessagingHosts");
111
+ } else {
112
+ console.log(c.yellow(" ⚠ Windows 平台需要手动配置 native host,跳过自动注册"));
113
+ console.log(c.dim(" 参考:https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging"));
114
+ return false;
115
+ }
116
+
117
+ const installSh = join(INSTALL_DIR, "native", "install.sh");
118
+ if (!existsSync(installSh)) {
119
+ fail(`未找到 ${installSh}`);
120
+ }
121
+
122
+ // 调用通用 install.sh,传入 INSTALL_DIR 作为项目根
123
+ // install.sh 会:检测 node → 生成 host.sh → 生成 manifest → 复制到 destDir
124
+ const result = spawnSync("bash", [installSh, INSTALL_DIR], { stdio: "inherit" });
125
+ if (result.status !== 0) {
126
+ fail("install.sh 执行失败");
127
+ }
128
+
129
+ // 校验:manifest 确实到了目标目录
130
+ const destManifest = join(destDir, "com.wssniffer.host.json");
131
+ if (!existsSync(destManifest)) {
132
+ fail(`注册校验失败:${destManifest} 不存在`);
133
+ }
134
+ ok(`已注册到 ${destDir}`);
135
+ return true;
136
+ }
137
+
138
+ // ===================== 步骤 4:引导加载插件 =====================
139
+ function guideLoadExtension() {
140
+ step(4, "加载 Chrome 插件");
141
+
142
+ const extDir = join(INSTALL_DIR, "extension");
143
+ console.log("");
144
+ console.log(c.bold("请在 Chrome 中操作:"));
145
+ console.log("");
146
+ console.log(` ${c.cyan("1.")} 打开 ${c.bold("chrome://extensions")}`);
147
+ console.log(` ${c.dim("(我会尝试帮你打开)")}`);
148
+ console.log("");
149
+ console.log(` ${c.cyan("2.")} 右上角打开「${c.bold("开发者模式")}」`);
150
+ console.log("");
151
+ console.log(` ${c.cyan("3.")} 点「${c.bold("加载已解压的扩展程序")}」`);
152
+ console.log(` 选择目录:`);
153
+ console.log(` ${c.green(extDir)}`);
154
+ console.log("");
155
+ console.log(` ${c.cyan("4.")} 加载后确认插件 ID 是:`);
156
+ console.log(` ${c.bold(EXTENSION_ID)}`);
157
+ console.log(` ${c.dim("(ID 由 manifest key 固定,native host 已按此 ID 注册)")}`);
158
+ console.log("");
159
+
160
+ // 尝试用系统默认方式打开 chrome://extensions
161
+ const plat = platform();
162
+ let opened = false;
163
+ try {
164
+ if (plat === "darwin") {
165
+ spawnSync("open", ["chrome://extensions"], { stdio: "ignore" });
166
+ opened = true;
167
+ } else if (plat === "linux") {
168
+ spawnSync("xdg-open", ["chrome://extensions"], { stdio: "ignore" });
169
+ opened = true;
170
+ }
171
+ } catch {}
172
+ if (opened) ok("已尝试打开 chrome://extensions");
173
+ }
174
+
175
+ // ===================== 主流程 =====================
176
+ function main() {
177
+ console.log(c.bold(c.cyan("\n🔌 终端桥接安装器")));
178
+ console.log(c.dim(" JumpServer Web 终端 · Arthas Console\n"));
179
+
180
+ // 前置检查:node 版本
181
+ const nodeVer = process.versions.node.split(".")[0];
182
+ if (Number(nodeVer) < 18) {
183
+ fail(`需要 Node.js >= 18,当前是 ${process.versions.node}`);
184
+ }
185
+ ok(`Node.js ${process.versions.node}`);
186
+
187
+ releaseFiles();
188
+ installProxyDeps();
189
+ registerNativeHost();
190
+ guideLoadExtension();
191
+
192
+ console.log("");
193
+ console.log(c.green(c.bold("✓ 安装完成!")));
194
+ console.log("");
195
+ console.log(c.bold("下一步:"));
196
+ console.log(` ${c.cyan("•")} 打开 JumpServer 终端 或 Arthas Console 页面`);
197
+ console.log(` ${c.cyan("•")} 点插件图标 →「🔍 捕捉终端」→「🚀 启动代理」`);
198
+ console.log(` ${c.cyan("•")} 两个绿灯亮,即可通过 Agent 发命令`);
199
+ console.log("");
200
+ console.log(c.dim(`文件位置:${INSTALL_DIR}`));
201
+ console.log(c.dim(`卸载:rm -rf ${INSTALL_DIR} && rm ~/Library/Application\\ Support/Google/Chrome/NativeMessagingHosts/com.wssniffer.host.json`));
202
+ console.log("");
203
+ }
204
+
205
+ main();