comate-ui-inspector 0.1.3 → 0.1.4-alpha.1

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
@@ -58,6 +58,20 @@ ui-inspector doctor <remote> --dev-port <dev端口> # 定位断在哪层并打
58
58
 
59
59
  恢复顺序:先确保远程 bridge 活(`ui-inspector bridge` / `install --remote`)→ 再接本地隧道(`ui-inspector link`)→ 再看远程 dev server。
60
60
 
61
+ ### 直连模式(笔记本能直达开发机内网 IP、堡垒机禁转发时用)
62
+
63
+ 如果你的笔记本本来就能直连开发机的内网 IP(比如 `/etc/hosts` 把 `devlocal.oreateai.com` 指到 `10.25.64.64`,浏览器直接就能打开 dev 页),那连 SSH 隧道都不用——把 bridge 暴露到开发机网卡,浏览器直连即可,正好绕开「堡垒机禁止 SSH 转发」:
64
+
65
+ ```bash
66
+ # 开发机上:bridge 绑 0.0.0.0(install --remote 顺带起,或单独跑 bridge)
67
+ ui-inspector install --remote --host 0.0.0.0
68
+ # 或:ui-inspector bridge --host 0.0.0.0 --port 8917
69
+ ```
70
+
71
+ 然后扩展弹窗「传输通路」选 `远程(HTTP bridge)`,地址填**完整地址** `devlocal.oreateai.com:8917`(或 `10.25.64.64:8917`),不用 `ui-inspector link`。
72
+
73
+ > 注意:`--host 0.0.0.0` 会把 bridge 暴露在内网,谁能访问到这个端口谁就能写 pending;仅在可信内网用。dev 页若是 https、扩展连 http bridge 被浏览器拦,则退回隧道模式或给 bridge 上 https。
74
+
61
75
  本地仍需一次:加载扩展(`chrome://extensions` 选 `extension` 目录),在弹窗把传输切到 `远程(HTTP bridge)`、填本地端口、加上你的 dev 域名。bridge 只绑 127.0.0.1、不暴露到网络,访问全程走 SSH 转发,所以 https 页面连 `127.0.0.1` 不触发混合内容拦截。
62
76
 
63
77
  ## 卸载 / 状态
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
@@ -61,7 +61,10 @@ function out(text) {
61
61
  process.stdout.write(`${text}\n`);
62
62
  }
63
63
  function installHookAndMcp() {
64
- const hookCommand = `node "${hookPath}"`;
64
+ // 用绝对路径的 node(当前运行 install 的这个),而不是裸 `node`:
65
+ // Comate 拉起 hook/MCP 时 PATH 极简(不含 nvm/fnm 目录),裸 `node` 会「未找到命令」。
66
+ const nodeBin = process.execPath;
67
+ const hookCommand = `"${nodeBin}" "${hookPath}"`;
65
68
  const config = readHooks() || {hooks: {}};
66
69
  config.hooks = config.hooks || {};
67
70
  const list = Array.isArray(config.hooks.UserPromptSubmit) ? config.hooks.UserPromptSubmit : [];
@@ -75,7 +78,7 @@ function installHookAndMcp() {
75
78
  out('hook already present, skip');
76
79
  }
77
80
  out('\n== 把下面加进 Comate 的 MCP 配置(stdio),然后 reload Comate ==');
78
- out(JSON.stringify({mcpServers: {'ui-inspector': {command: 'node', args: [serverPath]}}}, null, 4));
81
+ out(JSON.stringify({mcpServers: {'ui-inspector': {command: nodeBin, args: [serverPath]}}}, null, 4));
79
82
  }
80
83
 
81
84
  function installNativeHost() {
@@ -104,26 +107,34 @@ function installNativeHost() {
104
107
  }
105
108
 
106
109
  // 后台拉起 bridge 并与本进程脱钩,install --remote 用:装完 hook/MCP 顺手把 bridge 跑起来。
107
- function startBridgeDetached(port) {
110
+ function startBridgeDetached(port, host) {
108
111
  mkdirSync(pendingDir, {recursive: true});
109
112
  const logFile = resolve(pendingDir, 'bridge.log');
110
113
  const fd = openSync(logFile, 'a');
114
+ const env = {...process.env, UI_INSPECTOR_BRIDGE_PORT: String(port)};
115
+ if (host) {
116
+ env.UI_INSPECTOR_BRIDGE_HOST = String(host);
117
+ }
111
118
  const child = spawn(process.execPath, [resolve(pkgRoot, 'bridge/server.mjs')], {
112
119
  detached: true,
113
120
  stdio: ['ignore', fd, fd],
114
- env: {...process.env, UI_INSPECTOR_BRIDGE_PORT: String(port)},
121
+ env,
115
122
  });
116
123
  child.unref();
117
- out(`bridge 已后台启动(pid ${child.pid},端口 ${port}),日志:${logFile}`);
124
+ out(`bridge 已后台启动(pid ${child.pid},监听 ${host || '127.0.0.1'}:${port}),日志:${logFile}`);
118
125
  out(` 停止:kill ${child.pid}`);
119
126
  }
120
127
 
121
- function install({remote, port}) {
128
+ function install({remote, port, host}) {
122
129
  installHookAndMcp();
123
130
  if (remote) {
124
131
  out('\n== 远程开发机:跳过 native host/扩展(这台没浏览器),后台启动 bridge ==');
125
- startBridgeDetached(port);
126
- out(`\n本地笔记本上执行:ui-inspector link <这台机器的 ssh 目标> --port <dev端口>`);
132
+ startBridgeDetached(port, host);
133
+ if (host && host !== '127.0.0.1') {
134
+ out(`\n直连模式:浏览器可直接访问本机 ${host}:${port},弹窗里填该地址,无需 ui-inspector link。`);
135
+ } else {
136
+ out(`\n本地笔记本上执行:ui-inspector link <这台机器的 ssh 目标>`);
137
+ }
127
138
  return;
128
139
  }
129
140
  installNativeHost();
@@ -147,18 +158,27 @@ function link({remote, bridgePort, remoteBridgePort}) {
147
158
  const rbp = remoteBridgePort || bridgePort;
148
159
  const args = ['-N', '-L', `${bridgePort}:127.0.0.1:${rbp}`, remote];
149
160
  out(`转发中(Ctrl-C 断开):ssh ${args.join(' ')}`);
150
- out(` bridge → 本地 127.0.0.1:${bridgePort} ⇢ 远程 127.0.0.1:${rbp}(弹窗里填本地 ${bridgePort};远程 bridge 需在 ${rbp} 监听)`);
161
+ out(
162
+ ` bridge → 本地 127.0.0.1:${bridgePort} ⇢ 远程 127.0.0.1:${rbp}(弹窗里填本地 ${bridgePort};远程 bridge 需在 ${rbp} 监听)`
163
+ );
151
164
  out(` 注:dev server 端口请自行转发(远程开发工具通常已转发,或另跑 ssh -L <dev>:127.0.0.1:<dev> ${remote})`);
152
165
  const child = spawn('ssh', args, {stdio: 'inherit'});
153
166
  child.on('exit', code => process.exit(code ?? 0));
154
167
  }
155
168
 
156
169
  function bridge() {
157
- const i = process.argv.indexOf('--port');
158
- const port = i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : '8917';
170
+ const port = getOpt('--port', '8917');
171
+ const host = getOpt('--host', null);
159
172
  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>');
173
+ if (host) {
174
+ process.env.UI_INSPECTOR_BRIDGE_HOST = String(host);
175
+ }
176
+ out(`启动 HTTP bridge:监听 ${host || '127.0.0.1'}:${port}`);
177
+ if (host && host !== '127.0.0.1') {
178
+ out('直连模式:浏览器可直接访问本机该地址(弹窗里填完整地址),无需 SSH 隧道。');
179
+ } else {
180
+ out('隧道模式:本地经 SSH 转发访问,ssh -L ' + port + ':127.0.0.1:' + port + ' <remote>');
181
+ }
162
182
  import(resolve(pkgRoot, 'bridge/server.mjs'));
163
183
  }
164
184
 
@@ -293,17 +313,17 @@ async function doctor() {
293
313
  function help() {
294
314
  out('用法:ui-inspector <install|uninstall|status|bridge|link|doctor>');
295
315
  out(' install 注册 native host + 个人级 hook,并打印 MCP 配置与扩展目录');
296
- out(' install --remote 远程开发机:装 hook/MCP 并后台起 bridge(跳过 native host,--port 默认 8917)');
316
+ out(' install --remote 远程开发机:装 hook/MCP 并后台起 bridge(--port 默认 8917,--host 0.0.0.0 可直连免隧道)');
297
317
  out(' link <remote> 本地:SSH 把 bridge 端口转发到本地(--bridge-port <本地> [--remote-bridge-port <远程>])');
298
318
  out(' doctor <remote> 本地:自检远程链路断在哪层并打印恢复命令(--bridge-port <本地> [--dev-port <dev端口>])');
299
319
  out(' uninstall 移除上述注册(加 --purge 连 ~/.ui-inspector 数据一起删)');
300
320
  out(' status 查看安装状态');
301
- out(' bridge 启动 HTTP bridge(远程开发用,--port 默认 8917)');
321
+ out(' bridge 启动 HTTP bridge(--port 默认 8917,--host 默认 127.0.0.1;直连填 0.0.0.0)');
302
322
  }
303
323
 
304
324
  const cmd = process.argv[2] || 'help';
305
325
  if (cmd === 'install') {
306
- install({remote: process.argv.includes('--remote'), port: getOpt('--port', '8917')});
326
+ install({remote: process.argv.includes('--remote'), port: getOpt('--port', '8917'), host: getOpt('--host', null)});
307
327
  } else if (cmd === 'link') {
308
328
  link(parseLink());
309
329
  } else if (cmd === 'doctor') {
@@ -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) => {
@@ -142,21 +142,15 @@
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="8917 或 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
+ 隧道模式填端口(如 <code>8917</code>,默认连本地 127.0.0.1);直连模式填完整地址(如
153
+ <code>devlocal.oreateai.com:8917</code>)。
160
154
  </div>
161
155
  </div>
162
156
  </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.1",
4
4
  "description": "按住 Alt 点选页面元素,把「元素 + Vue 组件源码路径」交给 Comate 定位源码修改。浏览器扩展 + Native Messaging host + MCP + hook。",
5
5
  "type": "module",
6
6
  "license": "MIT",