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 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)`——只填**本地端口**(host 固定 127.0.0.1,默认 `8917`),用于远程开发(见下)。
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
- # 远程机上(和 Comate hook/MCP 同机):装 hook/MCP 并后台起 bridge
44
- ui-inspector install --remote # --port 默认 8917;跳过无浏览器的 native host
45
- # 本地笔记本:把远程 bridge 端口转发到本地 127.0.0.1(dev server 端口自行转发)
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 配置(贴进**远程**的 Comate 并 reload),并把 bridge 后台拉起(日志在 `~/.ui-inspector/bridge.log`)。`link` 本质是 `ssh -N -L <本地bridge端口>:127.0.0.1:<远程bridge端口> <remote>`,保持前台、Ctrl-C 断开。端口参数只有两个:本地 `--bridge-port`、远程 `--remote-bridge-port`(默认相同,相同时只填 `--bridge-port`、省略即默认 8917);弹窗里填的是**本地**那个。dev server 端口由你的远程开发工具自行转发,`link` 不再代管。
50
+ `install --remote` 会打印 MCP 配置(贴进**开发机上的** Comate 并 reload),并把 bridge 后台绑 `0.0.0.0:8917` 拉起(日志在 `~/.ui-inspector/bridge.log`)。
52
51
 
53
- 链路断了(ssh 掉线 / 远程 bridge 或 dev server 停了)时,先在本地自检:
52
+ 然后在浏览器(笔记本):加载扩展(`chrome://extensions` 选 `extension` 目录)→ 点扩展图标,弹窗里把**传输通路**选「远程(HTTP bridge)」、地址填**完整地址** `devlocal.oreateai.com:8917`、生效域名加上你的 dev 域名。
54
53
 
54
+ 验证直连:
55
55
  ```bash
56
- ui-inspector doctor <remote> --dev-port <dev端口> # 定位断在哪层并打印对应恢复命令
56
+ curl http://devlocal.oreateai.com:8917/state # 回 {"ok":true,...} 即通
57
57
  ```
58
+ 不通多半是开发机防火墙没放行该端口,或 bridge 没起(看 `~/.ui-inspector/bridge.log`)。
58
59
 
59
- 恢复顺序:先确保远程 bridge 活(`ui-inspector bridge` / `install --remote`)→ 再接本地隧道(`ui-inspector link`)→ 再看远程 dev server。
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, '127.0.0.1', () => {
87
- process.stdout.write(`ui-inspector bridge listening on http://127.0.0.1:${PORT}\n`);
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 link <remote> --bridge-port <本地> [--remote-bridge-port <远程>] # 本地:SSH 把 bridge 端口转发到本地 127.0.0.1
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
- const hookCommand = `node "${hookPath}"`;
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: 'node', args: [serverPath]}}}, null, 4));
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: {...process.env, UI_INSPECTOR_BRIDGE_PORT: String(port)},
119
+ env,
115
120
  });
116
121
  child.unref();
117
- out(`bridge 已后台启动(pid ${child.pid},端口 ${port}),日志:${logFile}`);
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本地笔记本上执行:ui-inspector link <这台机器的 ssh 目标> --port <dev端口>`);
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 i = process.argv.indexOf('--port');
158
- const port = i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : '8917';
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
- out(`启动 HTTP bridge(远程开发传输):127.0.0.1:${port}`);
161
- out('本地浏览器经 SSH 端口转发访问:ssh -L ' + port + ':127.0.0.1:' + port + ' <remote>');
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|link|doctor>');
295
- out(' install 注册 native host + 个人级 hook,并打印 MCP 配置与扩展目录');
296
- out(' install --remote 远程开发机:装 hook/MCP 并后台起 bridge(跳过 native host,--port 默认 8917)');
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(远程开发用,--port 默认 8917)');
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') {
@@ -4,24 +4,37 @@
4
4
  * 两件事:
5
5
  * 1) 传输适配:把内容脚本的 ingest/state 转发给本地后端。
6
6
  * - native(默认,本地开发):chrome.runtime.sendNativeMessage 按需拉起 host.mjs。
7
- * - http(远程开发):fetch http://127.0.0.1:<bridgePort>(/ingest、/state),远程机上跑 bridge/server.mjs。
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
- // 远程模式下扩展永远连本地 SSH 转发出来的 127.0.0.1,host 固定,只需配端口。
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
- bridgePort: '8917',
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(port, message) {
39
- const base = `http://127.0.0.1:${port}`;
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.bridgePort, message) : viaNative(message);
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 lastStatusId; // 首轮只记录、不弹;之后 id 变化才弹 toast
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
- if (status && status.id !== undefined) {
47
- if (lastStatusId !== undefined && status.id !== lastStatusId) {
48
- window.postMessage(
49
- {__uiInspectorBridge: 'status', text: status.text, level: status.level},
50
- '*'
51
- );
52
- }
53
- lastStatusId = status.id;
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
- // 「修改中」(info) 常驻不消失,「已完成」(success) /「失败」(error) 自动淡出。
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
- if (level === 'success' || level === 'error') {
50
- showStatus._t = window.setTimeout(function () {
51
- hint.style.opacity = '0';
52
- }, 2600);
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 = [];
@@ -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-port"
146
+ id="bridge-target"
148
147
  type="text"
149
- inputmode="numeric"
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
- 只填本地端口(host 固定 127.0.0.1)。远程开发:远程跑
156
- <code>ui-inspector bridge</code>
157
- ,
158
- <code>ui-inspector link</code>
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>
@@ -2,7 +2,7 @@
2
2
  const DEFAULTS = {
3
3
  domains: ['http://localhost/*', 'http://127.0.0.1/*'],
4
4
  transport: 'native',
5
- bridgePort: '8917',
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, bridgePort} = await getConfig();
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-port').value = bridgePort;
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 raw = $('bridge-port').value.trim();
120
- if (transport === 'http' && !/^\d+$/.test(raw)) {
121
- msg('端口要是数字', false);
119
+ const bridgeTarget = $('bridge-target').value.trim() || DEFAULTS.bridgeTarget;
120
+ if (transport === 'http' && !bridgeTarget) {
121
+ msg('请填 bridge 端口或地址', false);
122
122
  return;
123
123
  }
124
- const bridgePort = raw || DEFAULTS.bridgePort;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comate-ui-inspector",
3
- "version": "0.1.3",
3
+ "version": "0.1.4-alpha.2",
4
4
  "description": "按住 Alt 点选页面元素,把「元素 + Vue 组件源码路径」交给 Comate 定位源码修改。浏览器扩展 + Native Messaging host + MCP + hook。",
5
5
  "type": "module",
6
6
  "license": "MIT",