terminal-bridge-setup 2.1.0 → 2.3.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/bin/setup.mjs +72 -20
- package/files/native/host.js +40 -8
- package/package.json +1 -1
package/bin/setup.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// terminal-bridge-setup —— 终端桥接一次性安装器
|
|
3
3
|
//
|
|
4
|
-
// 做
|
|
5
|
-
// 1. 把插件源码 + 代理 + native host 释放到 ~/.terminal-bridge/
|
|
4
|
+
// 做 5 件事:
|
|
5
|
+
// 1. 把插件源码 + 代理 + native host + skill 释放到 ~/.terminal-bridge/
|
|
6
6
|
// 2. 在代理目录跑 npm install(装 ws)
|
|
7
7
|
// 3. 注册 native messaging host 到 Chrome(复用 native/install.sh)
|
|
8
|
-
// 4.
|
|
8
|
+
// 4. 安装 skill 到 ~/.agents/skills/(让 Agent 自动发现)
|
|
9
|
+
// 5. 打开 chrome://extensions + Finder,引导用户"加载已解压扩展"
|
|
9
10
|
//
|
|
10
11
|
// 用法(发布后):
|
|
11
12
|
// npx terminal-bridge-setup
|
|
@@ -163,40 +164,91 @@ function guideLoadExtension() {
|
|
|
163
164
|
step(5, STEPS, "加载 Chrome 插件");
|
|
164
165
|
|
|
165
166
|
const extDir = join(INSTALL_DIR, "extension");
|
|
167
|
+
const plat = platform();
|
|
168
|
+
|
|
166
169
|
console.log("");
|
|
167
|
-
console.log(c.bold("
|
|
170
|
+
console.log(c.bold("需要手动加载插件(一次性操作):"));
|
|
168
171
|
console.log("");
|
|
169
|
-
console.log(` ${c.cyan("1.")}
|
|
170
|
-
console.log(` ${c.dim("(我会尝试帮你打开)")}`);
|
|
172
|
+
console.log(` ${c.cyan("1.")} 已为你打开 ${c.bold("chrome://extensions")}`);
|
|
171
173
|
console.log("");
|
|
172
|
-
console.log(` ${c.cyan("2.")} 右上角打开「${c.bold("开发者模式")}
|
|
174
|
+
console.log(` ${c.cyan("2.")} 右上角打开「${c.bold("开发者模式")}」开关`);
|
|
173
175
|
console.log("");
|
|
174
|
-
console.log(` ${c.cyan("3.")}
|
|
175
|
-
console.log(`
|
|
176
|
+
console.log(` ${c.cyan("3.")} 点左上角「${c.bold("加载已解压的扩展程序")}」`);
|
|
177
|
+
console.log(` ${c.dim("已为你打开 Finder,选择这个文件夹:")}`);
|
|
176
178
|
console.log(` ${c.green(extDir)}`);
|
|
177
179
|
console.log("");
|
|
178
180
|
console.log(` ${c.cyan("4.")} 加载后确认插件 ID 是:`);
|
|
179
181
|
console.log(` ${c.bold(EXTENSION_ID)}`);
|
|
180
|
-
console.log(` ${c.dim("(ID
|
|
182
|
+
console.log(` ${c.dim("(ID 固定,native host 已按此注册)")}`);
|
|
181
183
|
console.log("");
|
|
182
184
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
// ① 打开 chrome://extensions
|
|
186
|
+
// macOS: open -a 指定 Chrome;open URL scheme 更可靠
|
|
187
|
+
// Linux: 直接给 google-chrome 传 URL
|
|
188
|
+
let chromeOpened = false;
|
|
189
|
+
try {
|
|
190
|
+
if (plat === "darwin") {
|
|
191
|
+
// 优先用 Google Chrome,回退到默认浏览器
|
|
192
|
+
const r = spawnSync("open", ["-a", "Google Chrome", "chrome://extensions"], { stdio: "ignore" });
|
|
193
|
+
if (r.status !== 0) {
|
|
194
|
+
spawnSync("open", ["chrome://extensions"], { stdio: "ignore" });
|
|
195
|
+
}
|
|
196
|
+
chromeOpened = true;
|
|
197
|
+
} else if (plat === "linux") {
|
|
198
|
+
// 试几个常见的 Chrome 命令名
|
|
199
|
+
for (const bin of ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"]) {
|
|
200
|
+
const r = spawnSync(bin, ["chrome://extensions"], { stdio: "ignore" });
|
|
201
|
+
if (r.status === 0) { chromeOpened = true; break; }
|
|
202
|
+
}
|
|
203
|
+
if (!chromeOpened) spawnSync("xdg-open", ["chrome://extensions"], { stdio: "ignore" });
|
|
204
|
+
chromeOpened = true;
|
|
205
|
+
}
|
|
206
|
+
} catch {}
|
|
207
|
+
if (chromeOpened) ok("已打开 chrome://extensions");
|
|
208
|
+
|
|
209
|
+
// ② 在 Finder/文件管理器里打开 extension 目录,方便用户直接拖拽选择
|
|
186
210
|
try {
|
|
187
211
|
if (plat === "darwin") {
|
|
188
|
-
|
|
189
|
-
|
|
212
|
+
// open <dir> 会用 Finder 打开,并选中该文件夹
|
|
213
|
+
spawnSync("open", [extDir], { stdio: "ignore" });
|
|
214
|
+
ok(`已在 Finder 打开 ${extDir}`);
|
|
190
215
|
} else if (plat === "linux") {
|
|
191
|
-
spawnSync("xdg-open", [
|
|
192
|
-
|
|
216
|
+
spawnSync("xdg-open", [extDir], { stdio: "ignore" });
|
|
217
|
+
ok(`已在文件管理器打开 ${extDir}`);
|
|
193
218
|
}
|
|
194
219
|
} catch {}
|
|
195
|
-
|
|
220
|
+
|
|
221
|
+
// ③ 交互式等待用户确认加载完成,然后校验
|
|
222
|
+
console.log("");
|
|
223
|
+
console.log(c.yellow("→ 完成上述操作后,按 Enter 继续(校验插件是否加载成功)..."));
|
|
224
|
+
console.log(c.dim(" (如果跳过,可稍后在插件 popup 里点「启动代理」验证)"));
|
|
225
|
+
|
|
226
|
+
// 等待用户按 Enter(阻塞读取 stdin)
|
|
227
|
+
return new Promise((resolve) => {
|
|
228
|
+
process.stdin.resume();
|
|
229
|
+
process.stdin.once("data", () => {
|
|
230
|
+
verifyExtensionLoaded();
|
|
231
|
+
resolve();
|
|
232
|
+
});
|
|
233
|
+
// 超时兜底:60 秒没按 Enter 就继续
|
|
234
|
+
setTimeout(() => {
|
|
235
|
+
console.log(c.dim("\n (等待超时,继续完成安装)"));
|
|
236
|
+
process.stdin.pause();
|
|
237
|
+
resolve();
|
|
238
|
+
}, 60000);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 校验插件是否加载:通过探测 native host 间接判断
|
|
243
|
+
// (插件加载后 popup 才能点"启动代理",代理起来说明 native host + 插件都通了)
|
|
244
|
+
function verifyExtensionLoaded() {
|
|
245
|
+
console.log("");
|
|
246
|
+
console.log(c.dim(" 校验方式:稍后在插件 popup 里点「🚀 启动代理」"));
|
|
247
|
+
console.log(c.dim(" 绿灯亮 = 插件 + native host + 代理全部就绪"));
|
|
196
248
|
}
|
|
197
249
|
|
|
198
250
|
// ===================== 主流程 =====================
|
|
199
|
-
function main() {
|
|
251
|
+
async function main() {
|
|
200
252
|
console.log(c.bold(c.cyan("\n🔌 终端桥接安装器")));
|
|
201
253
|
console.log(c.dim(" JumpServer Web 终端 · Arthas Console\n"));
|
|
202
254
|
|
|
@@ -211,7 +263,7 @@ function main() {
|
|
|
211
263
|
installProxyDeps();
|
|
212
264
|
registerNativeHost();
|
|
213
265
|
installSkill();
|
|
214
|
-
guideLoadExtension();
|
|
266
|
+
await guideLoadExtension();
|
|
215
267
|
|
|
216
268
|
console.log("");
|
|
217
269
|
console.log(c.green(c.bold("✓ 安装完成!")));
|
package/files/native/host.js
CHANGED
|
@@ -79,6 +79,21 @@ function isPidAlive(pid) {
|
|
|
79
79
|
} catch { return false; }
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// 通过端口找占用进程的 PID(PID 文件缺失时的回退方案)
|
|
83
|
+
// 用 lsof -ti :port 拿监听该端口的进程 PID
|
|
84
|
+
function findPidByPort(port) {
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
const child = spawn("lsof", ["-ti", `-i:${port}`, "-sTCP:LISTEN"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
87
|
+
let out = "";
|
|
88
|
+
child.stdout.on("data", (d) => { out += d.toString(); });
|
|
89
|
+
child.on("error", () => resolve(null)); // lsof 不存在或没权限
|
|
90
|
+
child.on("close", () => {
|
|
91
|
+
const pid = parseInt(out.trim().split("\n")[0], 10);
|
|
92
|
+
resolve(isNaN(pid) ? null : pid);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
82
97
|
// ============== 命令处理 ==============
|
|
83
98
|
async function startProxy() {
|
|
84
99
|
// 已在运行?
|
|
@@ -120,24 +135,41 @@ async function startProxy() {
|
|
|
120
135
|
|
|
121
136
|
function stopProxy() {
|
|
122
137
|
return new Promise(async (resolve) => {
|
|
123
|
-
|
|
138
|
+
let pid = readPid();
|
|
139
|
+
let pidSource = pid ? "pid file" : null;
|
|
140
|
+
|
|
141
|
+
// PID 文件缺失 → 回退用 lsof 按端口找
|
|
124
142
|
if (!pid) {
|
|
125
|
-
// 没有 PID 文件,但端口可能在监听(手动启动的),尝试用 lsof 找
|
|
126
143
|
const up = await isProxyListening();
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
144
|
+
if (!up) {
|
|
145
|
+
resolve({ ok: true, status: "stopped", msg: "proxy was not running" });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
pid = await findPidByPort(PROXY_PORT);
|
|
149
|
+
pidSource = pid ? "port lookup" : null;
|
|
150
|
+
if (!pid) {
|
|
151
|
+
resolve({ ok: false, status: "running", msg: "无法停止:代理在监听但找不到 PID(lsof 失败),请手动 kill" });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
131
154
|
}
|
|
155
|
+
|
|
132
156
|
try {
|
|
133
157
|
process.kill(pid, "SIGTERM");
|
|
134
158
|
// 等 2s 确认退出
|
|
135
|
-
setTimeout(() => {
|
|
159
|
+
setTimeout(async () => {
|
|
136
160
|
if (isPidAlive(pid)) {
|
|
137
161
|
try { process.kill(pid, "SIGKILL"); } catch {}
|
|
138
162
|
}
|
|
139
163
|
clearPid();
|
|
140
|
-
|
|
164
|
+
// 再确认端口确实释放了
|
|
165
|
+
const stillUp = await isProxyListening();
|
|
166
|
+
resolve({
|
|
167
|
+
ok: !stillUp,
|
|
168
|
+
status: stillUp ? "running" : "stopped",
|
|
169
|
+
msg: stillUp
|
|
170
|
+
? `SIGTERM+SIGKILL 已发(PID ${pid} via ${pidSource})但端口仍被占用,可能有其他进程`
|
|
171
|
+
: `proxy stopped (PID ${pid} via ${pidSource})`,
|
|
172
|
+
});
|
|
141
173
|
}, 2000);
|
|
142
174
|
} catch (err) {
|
|
143
175
|
clearPid();
|
package/package.json
CHANGED