comate-ui-inspector 0.1.3 → 0.1.4-alpha.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 +14 -15
- package/bridge/server.mjs +4 -2
- package/cli.mjs +27 -120
- package/extension/background.js +19 -6
- package/extension/bridge-content.js +10 -9
- package/extension/inspector-main.js +6 -7
- package/extension/popup.html +7 -10
- package/extension/popup.js +7 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,32 +33,31 @@ ui-inspector install # 注册 native host + 个人级 hook,并打印
|
|
|
33
33
|
- **生效域名**:默认已含 `localhost` / `127.0.0.1`;项目域名(如 `https://local.oreateai.com`)自己加,添加时浏览器会弹权限申请,允许后即在该域名注入选取脚本。
|
|
34
34
|
- **传输通路**:
|
|
35
35
|
- `本地(Native Messaging)`——默认,浏览器按需拉起 `host.mjs`,零常驻。
|
|
36
|
-
- `远程(HTTP bridge
|
|
36
|
+
- `远程(HTTP bridge)`——填开发机 bridge 的**完整地址**(如 `devlocal.oreateai.com:8917`),用于远程开发(见下)。
|
|
37
37
|
|
|
38
|
-
## 远程开发(HTTP bridge
|
|
38
|
+
## 远程开发(HTTP bridge 直连)
|
|
39
39
|
|
|
40
|
-
浏览器在本地、代码与 Comate 在远程开发机时,Native Messaging 跨不了机,改用 HTTP bridge
|
|
40
|
+
浏览器在本地、代码与 Comate 在远程开发机时,Native Messaging 跨不了机,改用 **HTTP bridge 直连**:在开发机把 bridge 暴露到网卡,浏览器用开发机可达地址直接连,无需 SSH 隧道。
|
|
41
|
+
|
|
42
|
+
前提:你的笔记本能直连开发机的内网地址(通常 `/etc/hosts` 把 `devlocal.oreateai.com` 指到开发机 IP,浏览器本来就能打开 dev 页)。
|
|
41
43
|
|
|
42
44
|
```bash
|
|
43
|
-
#
|
|
44
|
-
ui-inspector install --remote
|
|
45
|
-
#
|
|
46
|
-
ui-inspector link <remote-ssh-目标> # 本地/远程 bridge 都用默认 8917
|
|
47
|
-
# 本地与远程 bridge 端口不同时才两个都填:
|
|
48
|
-
ui-inspector link <remote-ssh-目标> --bridge-port <本地> --remote-bridge-port <远程>
|
|
45
|
+
# 开发机上(和 Comate hook/MCP 同机):装 hook/MCP 并后台起 bridge(默认绑 0.0.0.0)
|
|
46
|
+
ui-inspector install --remote
|
|
47
|
+
# 或单独起:ui-inspector bridge --host 0.0.0.0 --port 8917
|
|
49
48
|
```
|
|
50
49
|
|
|
51
|
-
`install --remote` 会打印 MCP
|
|
50
|
+
`install --remote` 会打印 MCP 配置(贴进**开发机上的** Comate 并 reload),并把 bridge 后台绑 `0.0.0.0:8917` 拉起(日志在 `~/.ui-inspector/bridge.log`)。
|
|
52
51
|
|
|
53
|
-
|
|
52
|
+
然后在浏览器(笔记本):加载扩展(`chrome://extensions` 选 `extension` 目录)→ 点扩展图标,弹窗里把**传输通路**选「远程(HTTP bridge)」、地址填**完整地址** `devlocal.oreateai.com:8917`、生效域名加上你的 dev 域名。
|
|
54
53
|
|
|
54
|
+
验证直连:
|
|
55
55
|
```bash
|
|
56
|
-
|
|
56
|
+
curl http://devlocal.oreateai.com:8917/state # 回 {"ok":true,...} 即通
|
|
57
57
|
```
|
|
58
|
+
不通多半是开发机防火墙没放行该端口,或 bridge 没起(看 `~/.ui-inspector/bridge.log`)。
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
本地仍需一次:加载扩展(`chrome://extensions` 选 `extension` 目录),在弹窗把传输切到 `远程(HTTP bridge)`、填本地端口、加上你的 dev 域名。bridge 只绑 127.0.0.1、不暴露到网络,访问全程走 SSH 转发,所以 https 页面连 `127.0.0.1` 不触发混合内容拦截。
|
|
60
|
+
> 注意:`--host 0.0.0.0` 会把 bridge 暴露在内网,谁能访问到这个端口谁就能写 pending;仅在可信内网用。dev 页若是 https、扩展连 http bridge 被浏览器拦,则给 bridge 上 https。
|
|
62
61
|
|
|
63
62
|
## 卸载 / 状态
|
|
64
63
|
|
package/bridge/server.mjs
CHANGED
|
@@ -17,6 +17,8 @@ import {homedir} from 'node:os';
|
|
|
17
17
|
const storeFile = resolve(homedir(), '.ui-inspector/pending.json');
|
|
18
18
|
const statusFile = resolve(homedir(), '.ui-inspector/status.json');
|
|
19
19
|
const PORT = Number(process.env.UI_INSPECTOR_BRIDGE_PORT) || 8917;
|
|
20
|
+
// 默认只绑 127.0.0.1(走 SSH 隧道);直连模式(笔记本能直达开发机内网 IP)时用 0.0.0.0 暴露到网卡。
|
|
21
|
+
const HOST = process.env.UI_INSPECTOR_BRIDGE_HOST || '127.0.0.1';
|
|
20
22
|
|
|
21
23
|
function readStore() {
|
|
22
24
|
try {
|
|
@@ -83,6 +85,6 @@ const server = createServer((req, res) => {
|
|
|
83
85
|
json(res, 404, {ok: false, error: 'not found'});
|
|
84
86
|
});
|
|
85
87
|
|
|
86
|
-
server.listen(PORT,
|
|
87
|
-
process.stdout.write(`ui-inspector bridge listening on http
|
|
88
|
+
server.listen(PORT, HOST, () => {
|
|
89
|
+
process.stdout.write(`ui-inspector bridge listening on http://${HOST}:${PORT}\n`);
|
|
88
90
|
});
|
package/cli.mjs
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* npm i -g comate-ui-inspector
|
|
5
5
|
* ui-inspector install # 注册 native host + 写个人级 hook + 打印 MCP 配置与扩展目录
|
|
6
6
|
* ui-inspector install --remote # 远程开发机:装 hook/MCP 并后台起 bridge(跳过无浏览器的 native host)
|
|
7
|
-
* ui-inspector
|
|
8
|
-
* ui-inspector doctor <remote> --bridge-port <本地> [--dev-port <dev端口>] # 本地:自检远程链路断在哪层并打印恢复命令
|
|
7
|
+
* ui-inspector install --remote # 远程开发机:装 hook/MCP 并后台起 bridge(默认绑 0.0.0.0 供直连)
|
|
9
8
|
* ui-inspector uninstall # 移除 native host 清单 + 个人级 hook(--purge 连 ~/.ui-inspector 一起删)
|
|
10
9
|
* ui-inspector status # 查看安装状态
|
|
11
10
|
* ui-inspector bridge # 启动 HTTP bridge(远程开发用,--port 默认 8917)
|
|
@@ -18,7 +17,6 @@ import {dirname, join, resolve} from 'node:path';
|
|
|
18
17
|
import {fileURLToPath} from 'node:url';
|
|
19
18
|
import {homedir, platform} from 'node:os';
|
|
20
19
|
import {spawn} from 'node:child_process';
|
|
21
|
-
import http from 'node:http';
|
|
22
20
|
|
|
23
21
|
const HOST_NAME = 'com.oreate.ui_inspector';
|
|
24
22
|
const EXT_ID = 'akpiagdjfeeaioifjepbmonpallececo';
|
|
@@ -61,7 +59,10 @@ function out(text) {
|
|
|
61
59
|
process.stdout.write(`${text}\n`);
|
|
62
60
|
}
|
|
63
61
|
function installHookAndMcp() {
|
|
64
|
-
|
|
62
|
+
// 用绝对路径的 node(当前运行 install 的这个),而不是裸 `node`:
|
|
63
|
+
// Comate 拉起 hook/MCP 时 PATH 极简(不含 nvm/fnm 目录),裸 `node` 会「未找到命令」。
|
|
64
|
+
const nodeBin = process.execPath;
|
|
65
|
+
const hookCommand = `"${nodeBin}" "${hookPath}"`;
|
|
65
66
|
const config = readHooks() || {hooks: {}};
|
|
66
67
|
config.hooks = config.hooks || {};
|
|
67
68
|
const list = Array.isArray(config.hooks.UserPromptSubmit) ? config.hooks.UserPromptSubmit : [];
|
|
@@ -75,7 +76,7 @@ function installHookAndMcp() {
|
|
|
75
76
|
out('hook already present, skip');
|
|
76
77
|
}
|
|
77
78
|
out('\n== 把下面加进 Comate 的 MCP 配置(stdio),然后 reload Comate ==');
|
|
78
|
-
out(JSON.stringify({mcpServers: {'ui-inspector': {command:
|
|
79
|
+
out(JSON.stringify({mcpServers: {'ui-inspector': {command: nodeBin, args: [serverPath]}}}, null, 4));
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
function installNativeHost() {
|
|
@@ -104,26 +105,31 @@ function installNativeHost() {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
// 后台拉起 bridge 并与本进程脱钩,install --remote 用:装完 hook/MCP 顺手把 bridge 跑起来。
|
|
107
|
-
function startBridgeDetached(port) {
|
|
108
|
+
function startBridgeDetached(port, host) {
|
|
108
109
|
mkdirSync(pendingDir, {recursive: true});
|
|
109
110
|
const logFile = resolve(pendingDir, 'bridge.log');
|
|
110
111
|
const fd = openSync(logFile, 'a');
|
|
112
|
+
const env = {...process.env, UI_INSPECTOR_BRIDGE_PORT: String(port)};
|
|
113
|
+
if (host) {
|
|
114
|
+
env.UI_INSPECTOR_BRIDGE_HOST = String(host);
|
|
115
|
+
}
|
|
111
116
|
const child = spawn(process.execPath, [resolve(pkgRoot, 'bridge/server.mjs')], {
|
|
112
117
|
detached: true,
|
|
113
118
|
stdio: ['ignore', fd, fd],
|
|
114
|
-
env
|
|
119
|
+
env,
|
|
115
120
|
});
|
|
116
121
|
child.unref();
|
|
117
|
-
out(`bridge 已后台启动(pid ${child.pid}
|
|
122
|
+
out(`bridge 已后台启动(pid ${child.pid},监听 ${host || '127.0.0.1'}:${port}),日志:${logFile}`);
|
|
118
123
|
out(` 停止:kill ${child.pid}`);
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
function install({remote, port}) {
|
|
126
|
+
function install({remote, port, host}) {
|
|
122
127
|
installHookAndMcp();
|
|
123
128
|
if (remote) {
|
|
129
|
+
const bindHost = host || '0.0.0.0';
|
|
124
130
|
out('\n== 远程开发机:跳过 native host/扩展(这台没浏览器),后台启动 bridge ==');
|
|
125
|
-
startBridgeDetached(port);
|
|
126
|
-
out(`\n
|
|
131
|
+
startBridgeDetached(port, bindHost);
|
|
132
|
+
out(`\n浏览器在扩展弹窗里把传输设为「远程(HTTP bridge)」、地址填「本机可达地址:${port}」(如 devlocal.oreateai.com:${port})即可直连。`);
|
|
127
133
|
return;
|
|
128
134
|
}
|
|
129
135
|
installNativeHost();
|
|
@@ -133,32 +139,13 @@ function install({remote, port}) {
|
|
|
133
139
|
out('完成后:按住 Alt 点元素多选 → 回 Comate 写修改意见。');
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
// 本地笔记本:把远程 bridge 端口转发到本地 127.0.0.1。端口参数只有两个:
|
|
137
|
-
// --bridge-port <本地>(默认 8917)、--remote-bridge-port <远程>(默认与本地相同)。
|
|
138
|
-
// 本地=远程时只填 --bridge-port;不同才两个都填。dev server 端口自行转发。
|
|
139
|
-
function link({remote, bridgePort, remoteBridgePort}) {
|
|
140
|
-
if (!remote) {
|
|
141
|
-
out('用法:ui-inspector link <remote-ssh-目标> [--bridge-port <本地端口>] [--remote-bridge-port <远程端口>]');
|
|
142
|
-
out('例: ui-inspector link dev-box # 本地/远程都用默认 8917');
|
|
143
|
-
out(' ui-inspector link dev-box --bridge-port 9000 # 本地=远程=9000');
|
|
144
|
-
out(' ui-inspector link dev-box --bridge-port 9000 --remote-bridge-port 8917 # 本地 9000 → 远程 8917');
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const rbp = remoteBridgePort || bridgePort;
|
|
148
|
-
const args = ['-N', '-L', `${bridgePort}:127.0.0.1:${rbp}`, remote];
|
|
149
|
-
out(`转发中(Ctrl-C 断开):ssh ${args.join(' ')}`);
|
|
150
|
-
out(` bridge → 本地 127.0.0.1:${bridgePort} ⇢ 远程 127.0.0.1:${rbp}(弹窗里填本地 ${bridgePort};远程 bridge 需在 ${rbp} 监听)`);
|
|
151
|
-
out(` 注:dev server 端口请自行转发(远程开发工具通常已转发,或另跑 ssh -L <dev>:127.0.0.1:<dev> ${remote})`);
|
|
152
|
-
const child = spawn('ssh', args, {stdio: 'inherit'});
|
|
153
|
-
child.on('exit', code => process.exit(code ?? 0));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
142
|
function bridge() {
|
|
157
|
-
const
|
|
158
|
-
const
|
|
143
|
+
const port = getOpt('--port', '8917');
|
|
144
|
+
const host = getOpt('--host', '0.0.0.0');
|
|
159
145
|
process.env.UI_INSPECTOR_BRIDGE_PORT = String(port);
|
|
160
|
-
|
|
161
|
-
out(
|
|
146
|
+
process.env.UI_INSPECTOR_BRIDGE_HOST = String(host);
|
|
147
|
+
out(`启动 HTTP bridge:监听 ${host}:${port}`);
|
|
148
|
+
out(`浏览器直连:扩展弹窗地址填「本机可达地址:${port}」(如 devlocal.oreateai.com:${port})。`);
|
|
162
149
|
import(resolve(pkgRoot, 'bridge/server.mjs'));
|
|
163
150
|
}
|
|
164
151
|
|
|
@@ -216,98 +203,18 @@ function getOpt(name, def) {
|
|
|
216
203
|
return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : def;
|
|
217
204
|
}
|
|
218
205
|
|
|
219
|
-
function parseLink() {
|
|
220
|
-
const rest = process.argv.slice(3);
|
|
221
|
-
let remote;
|
|
222
|
-
let bridgePort = '8917';
|
|
223
|
-
let remoteBridgePort;
|
|
224
|
-
let devPort;
|
|
225
|
-
for (let i = 0; i < rest.length; i++) {
|
|
226
|
-
const a = rest[i];
|
|
227
|
-
if (a === '--bridge-port') {
|
|
228
|
-
bridgePort = rest[++i];
|
|
229
|
-
} else if (a === '--remote-bridge-port') {
|
|
230
|
-
remoteBridgePort = rest[++i];
|
|
231
|
-
} else if (a === '--dev-port') {
|
|
232
|
-
devPort = rest[++i]; // 仅 doctor 用;放这里一并消费,避免其值被当成 remote
|
|
233
|
-
} else if (!a.startsWith('-')) {
|
|
234
|
-
remote = a;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return {remote, bridgePort, remoteBridgePort, devPort};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// 探测本地某端口的 http 可达性(2s 超时)。用 node:http 而非 fetch,兼容 node 18+。
|
|
241
|
-
function probe(port, path) {
|
|
242
|
-
return new Promise(resolve => {
|
|
243
|
-
const req = http.get({host: '127.0.0.1', port: Number(port), path, timeout: 2000}, res => {
|
|
244
|
-
let body = '';
|
|
245
|
-
res.on('data', chunk => (body += chunk));
|
|
246
|
-
res.on('end', () => resolve({state: 'ok', code: res.statusCode, body}));
|
|
247
|
-
});
|
|
248
|
-
req.on('timeout', () => {
|
|
249
|
-
req.destroy();
|
|
250
|
-
resolve({state: 'timeout'});
|
|
251
|
-
});
|
|
252
|
-
req.on('error', err => resolve({state: err.code === 'ECONNREFUSED' ? 'refused' : 'error', err: String(err)}));
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// 一键自检:定位远程开发链路(本地隧道 → 远程 bridge → 远程 dev server)断在哪层,并打印恢复命令。
|
|
257
|
-
async function doctor() {
|
|
258
|
-
const {remote, bridgePort, remoteBridgePort, devPort} = parseLink();
|
|
259
|
-
const rm = remote || '<remote>';
|
|
260
|
-
const linkCmd =
|
|
261
|
-
`ui-inspector link ${rm}` +
|
|
262
|
-
(bridgePort !== '8917' ? ` --bridge-port ${bridgePort}` : '') +
|
|
263
|
-
(remoteBridgePort && remoteBridgePort !== bridgePort ? ` --remote-bridge-port ${remoteBridgePort}` : '');
|
|
264
|
-
out('ui-inspector doctor(远程开发链路自检):');
|
|
265
|
-
|
|
266
|
-
const b = await probe(bridgePort, '/state');
|
|
267
|
-
if (b.state === 'ok') {
|
|
268
|
-
out(` [1] bridge ✓ 127.0.0.1:${bridgePort} 可达——本地→隧道→远程 bridge 全通`);
|
|
269
|
-
} else if (b.state === 'refused') {
|
|
270
|
-
out(` [1] bridge ✗ 本地 127.0.0.1:${bridgePort} 无人监听 → SSH 隧道断了(或没起)`);
|
|
271
|
-
out(` 恢复:${linkCmd}`);
|
|
272
|
-
} else {
|
|
273
|
-
out(` [1] bridge ✗ 隧道在、但远程 bridge 无响应(${b.state})`);
|
|
274
|
-
out(` 恢复:登远程机跑 ui-inspector bridge(或 install --remote),看 ~/.ui-inspector/bridge.log`);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (devPort) {
|
|
278
|
-
const d = await probe(devPort, '/');
|
|
279
|
-
if (d.state === 'refused') {
|
|
280
|
-
out(` [2] dev ✗ 本地 127.0.0.1:${devPort} 不可达 → 该端口没转发,或远程 dev server 没起`);
|
|
281
|
-
} else if (d.state === 'timeout') {
|
|
282
|
-
out(` [2] dev ✗ 127.0.0.1:${devPort} 超时 → 远程 dev server 可能挂了`);
|
|
283
|
-
} else {
|
|
284
|
-
out(` [2] dev ✓ 127.0.0.1:${devPort} 可达(HTTP ${d.code})`);
|
|
285
|
-
}
|
|
286
|
-
} else {
|
|
287
|
-
out(' [2] dev - 未传 --dev-port,跳过 dev server 检查');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
out('\n恢复顺序:先确保远程 bridge 活 → 再接本地隧道(link)→ 再看远程 dev server(自行转发)。');
|
|
291
|
-
}
|
|
292
|
-
|
|
293
206
|
function help() {
|
|
294
|
-
out('用法:ui-inspector <install|uninstall|status|bridge
|
|
295
|
-
out(' install
|
|
296
|
-
out(' install --remote 远程开发机:装 hook/MCP 并后台起 bridge
|
|
297
|
-
out(' link <remote> 本地:SSH 把 bridge 端口转发到本地(--bridge-port <本地> [--remote-bridge-port <远程>])');
|
|
298
|
-
out(' doctor <remote> 本地:自检远程链路断在哪层并打印恢复命令(--bridge-port <本地> [--dev-port <dev端口>])');
|
|
207
|
+
out('用法:ui-inspector <install|uninstall|status|bridge>');
|
|
208
|
+
out(' install 本地:注册 native host + 个人级 hook,并打印 MCP 配置与扩展目录');
|
|
209
|
+
out(' install --remote 远程开发机:装 hook/MCP 并后台起 bridge(默认绑 0.0.0.0 供浏览器直连,--port 默认 8917)');
|
|
299
210
|
out(' uninstall 移除上述注册(加 --purge 连 ~/.ui-inspector 数据一起删)');
|
|
300
211
|
out(' status 查看安装状态');
|
|
301
|
-
out(' bridge 启动 HTTP bridge
|
|
212
|
+
out(' bridge 启动 HTTP bridge(--port 默认 8917,--host 默认 0.0.0.0 供直连)');
|
|
302
213
|
}
|
|
303
214
|
|
|
304
215
|
const cmd = process.argv[2] || 'help';
|
|
305
216
|
if (cmd === 'install') {
|
|
306
|
-
install({remote: process.argv.includes('--remote'), port: getOpt('--port', '8917')});
|
|
307
|
-
} else if (cmd === 'link') {
|
|
308
|
-
link(parseLink());
|
|
309
|
-
} else if (cmd === 'doctor') {
|
|
310
|
-
doctor();
|
|
217
|
+
install({remote: process.argv.includes('--remote'), port: getOpt('--port', '8917'), host: getOpt('--host', null)});
|
|
311
218
|
} else if (cmd === 'uninstall') {
|
|
312
219
|
uninstall({purge: process.argv.includes('--purge')});
|
|
313
220
|
} else if (cmd === 'status') {
|
package/extension/background.js
CHANGED
|
@@ -4,24 +4,37 @@
|
|
|
4
4
|
* 两件事:
|
|
5
5
|
* 1) 传输适配:把内容脚本的 ingest/state 转发给本地后端。
|
|
6
6
|
* - native(默认,本地开发):chrome.runtime.sendNativeMessage 按需拉起 host.mjs。
|
|
7
|
-
* - http(远程开发):fetch
|
|
7
|
+
* - http(远程开发):fetch bridge 的 /ingest、/state。bridgeTarget 为端口(→127.0.0.1:端口)或完整地址。
|
|
8
8
|
* 2) 动态注册内容脚本:目标域名不再硬编码在 manifest,改由弹窗(popup)写入 storage,
|
|
9
9
|
* 这里按配置用 chrome.scripting 注册(MAIN + ISOLATED 两份)。
|
|
10
10
|
*/
|
|
11
11
|
const HOST = 'com.oreate.ui_inspector';
|
|
12
12
|
|
|
13
13
|
// localhost / 127.0.0.1 是通用开发地址,作默认种子;项目专属域名(如 local.oreateai.com)在弹窗里加。
|
|
14
|
-
//
|
|
14
|
+
// bridgeTarget:隧道模式填端口(→ 127.0.0.1:端口);直连模式填 host:port 或完整 URL。
|
|
15
15
|
const DEFAULTS = {
|
|
16
16
|
domains: ['http://localhost/*', 'http://127.0.0.1/*'],
|
|
17
17
|
transport: 'native',
|
|
18
|
-
|
|
18
|
+
bridgeTarget: '8917',
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
function getConfig() {
|
|
22
22
|
return chrome.storage.local.get(DEFAULTS);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// 把弹窗里的 bridgeTarget 归一成请求 base:
|
|
26
|
+
// 纯数字 → http://127.0.0.1:<端口>;带 http(s):// → 原样;其余 → 补 http://
|
|
27
|
+
function bridgeBase(target) {
|
|
28
|
+
const t = String(target || '').trim();
|
|
29
|
+
if (/^\d+$/.test(t)) {
|
|
30
|
+
return `http://127.0.0.1:${t}`;
|
|
31
|
+
}
|
|
32
|
+
if (/^https?:\/\//i.test(t)) {
|
|
33
|
+
return t.replace(/\/+$/, '');
|
|
34
|
+
}
|
|
35
|
+
return `http://${t}`.replace(/\/+$/, '');
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
// ---------- 传输 ----------
|
|
26
39
|
function viaNative(message) {
|
|
27
40
|
return new Promise(resolve => {
|
|
@@ -35,8 +48,8 @@ function viaNative(message) {
|
|
|
35
48
|
});
|
|
36
49
|
}
|
|
37
50
|
|
|
38
|
-
async function viaHttp(
|
|
39
|
-
const base =
|
|
51
|
+
async function viaHttp(target, message) {
|
|
52
|
+
const base = bridgeBase(target);
|
|
40
53
|
try {
|
|
41
54
|
if (message.type === 'ingest') {
|
|
42
55
|
const r = await fetch(`${base}/ingest`, {
|
|
@@ -55,7 +68,7 @@ async function viaHttp(port, message) {
|
|
|
55
68
|
|
|
56
69
|
async function dispatch(message) {
|
|
57
70
|
const cfg = await getConfig();
|
|
58
|
-
return cfg.transport === 'http' ? viaHttp(cfg.
|
|
71
|
+
return cfg.transport === 'http' ? viaHttp(cfg.bridgeTarget, message) : viaNative(message);
|
|
59
72
|
}
|
|
60
73
|
|
|
61
74
|
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
(function () {
|
|
12
12
|
const alive = () => !!(chrome.runtime && chrome.runtime.id);
|
|
13
|
-
let
|
|
13
|
+
let statusSeeded = false; // 首轮只记录当前状态(避免重放旧 toast),之后任何变化都弹
|
|
14
|
+
let lastStatusId = null;
|
|
14
15
|
|
|
15
16
|
window.addEventListener('message', event => {
|
|
16
17
|
if (event.source !== window || !event.data || event.data.__uiInspector !== 'selection') {
|
|
@@ -43,14 +44,14 @@
|
|
|
43
44
|
window.postMessage({__uiInspectorBridge: 'cleared'}, '*');
|
|
44
45
|
}
|
|
45
46
|
const status = resp.status;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
const id = status && status.id !== undefined ? status.id : null;
|
|
48
|
+
if (!statusSeeded) {
|
|
49
|
+
// 首轮:记录当前状态 id(可能是上轮遗留),本轮不弹,避免刷新页面重放旧 toast
|
|
50
|
+
statusSeeded = true;
|
|
51
|
+
lastStatusId = id;
|
|
52
|
+
} else if (status && id !== lastStatusId) {
|
|
53
|
+
lastStatusId = id;
|
|
54
|
+
window.postMessage({__uiInspectorBridge: 'status', text: status.text, level: status.level}, '*');
|
|
54
55
|
}
|
|
55
56
|
});
|
|
56
57
|
} catch {
|
|
@@ -38,19 +38,18 @@
|
|
|
38
38
|
}, 1800);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
// MCP notify_browser 推送的状态,复用底部这颗 pill
|
|
42
|
-
//
|
|
41
|
+
// MCP notify_browser 推送的状态,复用底部这颗 pill。
|
|
42
|
+
// 都会自动淡出,避免卡住:info(修改中) 6s,success/error 2.6s。
|
|
43
43
|
function showStatus(text, level) {
|
|
44
44
|
window.clearTimeout(showHint._t);
|
|
45
45
|
window.clearTimeout(showStatus._t);
|
|
46
46
|
hint.textContent = text;
|
|
47
47
|
hint.style.background = level === 'success' ? '#16a34a' : level === 'error' ? '#dc2626' : '#111';
|
|
48
48
|
hint.style.opacity = '1';
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
49
|
+
var ttl = level === 'success' || level === 'error' ? 2600 : 6000;
|
|
50
|
+
showStatus._t = window.setTimeout(function () {
|
|
51
|
+
hint.style.opacity = '0';
|
|
52
|
+
}, ttl);
|
|
54
53
|
}
|
|
55
54
|
function cssPath(el) {
|
|
56
55
|
var parts = [];
|
package/extension/popup.html
CHANGED
|
@@ -142,21 +142,18 @@
|
|
|
142
142
|
class="row"
|
|
143
143
|
style="margin-top: 6px"
|
|
144
144
|
>
|
|
145
|
-
<span style="color: #656d76">127.0.0.1 :</span>
|
|
146
145
|
<input
|
|
147
|
-
id="bridge-
|
|
146
|
+
id="bridge-target"
|
|
148
147
|
type="text"
|
|
149
|
-
|
|
150
|
-
placeholder="8917"
|
|
151
|
-
style="max-width: 90px"
|
|
148
|
+
placeholder="devlocal.oreateai.com:8917"
|
|
152
149
|
/>
|
|
153
150
|
</div>
|
|
154
151
|
<div class="hint">
|
|
155
|
-
|
|
156
|
-
<code>
|
|
157
|
-
|
|
158
|
-
<code>ui-inspector
|
|
159
|
-
|
|
152
|
+
填开发机 bridge 的完整地址(如
|
|
153
|
+
<code>devlocal.oreateai.com:8917</code>
|
|
154
|
+
)。开发机上
|
|
155
|
+
<code>ui-inspector install --remote</code>
|
|
156
|
+
起 bridge(默认绑 0.0.0.0 供直连)。
|
|
160
157
|
</div>
|
|
161
158
|
</div>
|
|
162
159
|
</section>
|
package/extension/popup.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const DEFAULTS = {
|
|
3
3
|
domains: ['http://localhost/*', 'http://127.0.0.1/*'],
|
|
4
4
|
transport: 'native',
|
|
5
|
-
|
|
5
|
+
bridgeTarget: '8917',
|
|
6
6
|
};
|
|
7
7
|
const PROTECTED = new Set(DEFAULTS.domains); // 默认项不可删、不回收权限
|
|
8
8
|
|
|
@@ -106,23 +106,22 @@ function toggleBridge() {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
async function renderTransport() {
|
|
109
|
-
const {transport,
|
|
109
|
+
const {transport, bridgeTarget} = await getConfig();
|
|
110
110
|
for (const r of document.querySelectorAll('input[name="transport"]')) {
|
|
111
111
|
r.checked = r.value === transport;
|
|
112
112
|
}
|
|
113
|
-
$('bridge-
|
|
113
|
+
$('bridge-target').value = bridgeTarget;
|
|
114
114
|
toggleBridge();
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
async function saveTransport() {
|
|
118
118
|
const transport = document.querySelector('input[name="transport"]:checked')?.value || 'native';
|
|
119
|
-
const
|
|
120
|
-
if (transport === 'http' &&
|
|
121
|
-
msg('
|
|
119
|
+
const bridgeTarget = $('bridge-target').value.trim() || DEFAULTS.bridgeTarget;
|
|
120
|
+
if (transport === 'http' && !bridgeTarget) {
|
|
121
|
+
msg('请填 bridge 端口或地址', false);
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
|
-
|
|
125
|
-
await chrome.storage.local.set({transport, bridgePort});
|
|
124
|
+
await chrome.storage.local.set({transport, bridgeTarget});
|
|
126
125
|
msg('已保存传输设置');
|
|
127
126
|
}
|
|
128
127
|
|
package/package.json
CHANGED