@sleepinsummer/agent-browser-cli 0.3.2 → 0.3.3

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/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 未发布
2
+
3
+ ## v0.3.3 - 2026-05-18
4
+
5
+ - `tabtree` 默认改为 compact 输出,截断长 URL 并省略 `session_key` 以降低 token 消耗;新增 `tabtree --full` 输出完整 URL 和 `session_key`。
6
+ - 优化 `tabtree` 实现,daemon 锁内只复制必要会话字段,锁外完成排序和 JSON 组装。
7
+ - Chrome 扩展 popup 增加 label 唯一性提示:推荐用 CLI 设置 label 以校验当前 daemon 内跨 Profile 唯一性。
8
+ - 平台 npm 包打包时默认对复制后的原生二进制执行 `strip`,减小发布体积;本地 `target/release` 二进制保持不变。
9
+ - 新增 `tabtree` 树形查询命令,支持按 `tab_id`、`profile_id/profile_label`、`browser_id` 过滤,并保留 browser → profile → tab 父子节点;原先临时增加的 `profiles` / `browsers` 摘要命令已移除,统一使用 `tabtree` / `lookup`。
10
+ - 新增 `lookup tab|browser|profile` 反查命令,可由 `tab_id` 反查 `browser_id` / `profile_id` / `profile_label`,或由 `browser_id` 反查所属 profile。
11
+ - 移除扩展默认注入的全局 `alert` / `confirm` / `prompt` 重写,改为 CLI 页面执行期间临时抑制弹窗并在命令结束后恢复原生函数。
12
+ - 新增 `profile-label set|clear`,并在 Chrome 扩展 popup 中支持设置 Profile Label;label 冲突时 CLI 按歧义处理,不参与内部路由主键。
13
+
1
14
  # 更新日志
2
15
 
3
16
  所有重要变更都会记录在这里。日期使用北京时间自然日。
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <a href="https://github.com/sleepinginsummer/agent-browser-cli"><img src="https://img.shields.io/badge/CLI-agentbrowsercli-2ea44f" alt="CLI agentbrowsercli"></a>
11
11
  <a href="https://github.com/sleepinginsummer/agent-browser-cli/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-green" alt="License MIT"></a>
12
12
  <a href="https://github.com/sleepinginsummer/agent-browser-cli"><img src="https://img.shields.io/badge/sys-win%2Fmac%2Flinux-0078D6?labelColor=0078D6&color=C0C0C0" alt="sys win/mac/linux"></a>
13
- <a href="https://github.com/sleepinginsummer/agent-browser-cli/releases"><img src="https://img.shields.io/badge/release-v0.3.1--beta.1-orange" alt="release v0.3.1-beta.1"></a>
13
+ <a href="https://github.com/sleepinginsummer/agent-browser-cli/releases"><img src="https://img.shields.io/badge/release-v0.3.3-orange" alt="release v0.3.3"></a>
14
14
  <a href="https://github.com/sleepinginsummer/agent-browser-cli/pulls"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen" alt="PRs welcome"></a>
15
15
  </p>
16
16
 
@@ -53,32 +53,36 @@
53
53
  - 若干优化,缩短命令执行时间
54
54
  - rust实现cli端
55
55
 
56
- ## 性能参考
57
-
58
- 以下为常驻服务已启动、Chrome 扩展已连接时的实测参考,实际耗时会受页面复杂度、网络、Chrome 状态和返回数据量影响。
59
-
60
- | 操作 | 参考耗时 |
61
- | --- | --- |
62
- | 打开百度标签页 | 约 `0.10s` |
63
- | 注入 JS 输入关键词并点击搜索 | `0.27s` |
64
- | 打开百度并搜索“小猫”合计 | 约 `0.37s` |
65
- | `scan --tab --text-only` 读取页面文本 | 约 `0.04-0.12s` |
66
- | `exec 'return document.title'` 注入简单 JS | 约 `0.04-0.12s` |
67
- | `exec 'return document.body.innerText'` 读取正文 | 多数 `0.04-0.05s`,偶发约 `0.30s` |
68
- | 查询 DOM 链接列表 | 约 `0.27-0.36s` |
69
- | `exec --monitor` 页面变化摘要 | 约 `0.72-0.88s` |
70
-
71
- 一般判断:普通读页面和简单 JS 注入是 `50ms` 级;复杂 DOM 查询主要取决于页面结构和返回数据量,常见约 `300ms`;`--monitor` 会额外生成页面变化摘要,通常接近 `0.8s`。
72
-
73
- 与原 Python 调用链的参考对比:
74
-
75
- | 对比项 | Python 版本 | Rust CLI 版本 |
76
- | --- | --- | --- |
77
- | 启动方式 | 每次调用更容易触发 Python 进程、模块加载和连接初始化开销 | CLI 命令复用常驻服务,避免重复初始化浏览器连接 |
78
- | 简单读页面 / JS 注入 | 通常受进程启动和 Python 调用链影响,延迟更不稳定 | 常见 `0.04-0.12s` |
79
- | 连续多次调用 | 多次短命令开销更明显 | 更适合 Agent 高频调用 |
80
-
81
- 该对比只用于说明架构差异带来的性能趋势;具体耗时仍取决于页面复杂度、Chrome 状态和返回数据量。
56
+ ## 他能做的事情
57
+
58
+ 1. 自动化测试
59
+ 可以复用真实浏览器环境做页面流程验证、表单提交、按钮点击、跳转检查、登录态页面测试。
60
+ 2. 前端页面 Debug
61
+ 可以读取 DOM、执行 JS、查看页面状态、截图确认效果,辅助定位前端交互、渲染和数据问题,对接后端接口。
62
+ 3. 页面样式调试
63
+ 可以在真实页面里执行 JS 修改 DOM / CSS,临时验证样式、布局和交互效果,但更偏辅助调试,不是完整设计工具。
64
+ 4. 网页数据采集
65
+ 可以读取页面内容、表格、列表、Cookie 和接口相关状态,适合处理需要登录态的页面数据提取。
66
+ 5. 浏览器操作脚本化
67
+ 可以把打开页面、切换标签页、执行 JS、截图、上传文件等操作串成脚本,做重复性网页任务。
68
+ 6. Agent 辅助操作网页后台
69
+ 适合让 AI Agent 操作管理后台、配置页面、低代码平台、表单系统等已有网页工具。
70
+ 7. 页面结构分析
71
+ 可以简化 HTML、识别主要内容区和列表结构,帮助 Agent 更快理解复杂页面。
72
+ 8. 安全研究和逆向辅助
73
+ 可以在真实浏览器会话里观察页面行为、执行调试脚本、读取前端状态,辅助分析前端逻辑和接口调用。
74
+
75
+ ## 他的能力
76
+
77
+ 1. 扫描当前 Chrome 标签页,获取页面标题、URL 和标签页 ID。
78
+ 2. 切换到指定标签页,复用已有页面和登录态。
79
+ 3. 打开新标签页,支持直接访问目标 URL。
80
+ 4. 在页面中执行 JavaScript,读取 DOM、表单、状态和页面数据。
81
+ 5. 读取当前页面 Cookie,方便处理登录态相关任务。
82
+ 6. 调用 Chrome CDP 能力,执行更底层的页面控制。
83
+ 7. 截取页面截图,用于视觉检查和页面确认。
84
+ 8. 上传本地文件到网页文件选择框。
85
+ 9. 操作下拉框、按钮、表单等常见页面元素。
82
86
 
83
87
  ## 目录结构
84
88
 
@@ -156,7 +160,25 @@ agent-browser-cli set-extension-port 18766
156
160
 
157
161
  Chrome 插件 popup 中也可以修改插件端口并立即重连。插件端口必须和 CLI 配置中的 `extension_port` 一致。
158
162
 
163
+ ### Profile Label
164
+
165
+ 多 Chrome Profile / 多浏览器实例下,`profile_id` 和 `browser_id` 较长。可以给每个 Chrome Profile 设置短 label,之后用 `--profile <label>` 操作。
166
+
167
+ ```bash
168
+ agent-browser-cli lookup tab <tabId>
169
+ agent-browser-cli lookup browser <browser_id>
170
+ agent-browser-cli profile-label set work --profile <profile_id>
171
+ agent-browser-cli tabs --profile work
172
+ ```
173
+
174
+ 也可以在对应 Chrome Profile 的扩展 popup 中设置 Profile Label。label 只作为别名,内部路由仍使用 `browser_id:profile_id:tab_id`;如果当前 daemon 内 label 匹配到多个 profile,CLI 会报歧义。推荐用 CLI 设置 label,因为 CLI 会校验当前 daemon 内跨 Profile 唯一性;popup 是本地便捷入口,不保证跨 Profile 唯一。`tabtree` 默认截断 URL 并省略 `session_key` 以减少 token,需完整字段时加 `--full`。
175
+
176
+
177
+
178
+
179
+ ### 弹窗抑制
159
180
 
181
+ 扩展不再默认重写页面的 `alert` / `confirm` / `prompt`。只有 CLI 执行页面脚本命令期间会临时抑制原生弹窗,命令结束后恢复,避免长期污染业务页面全局函数。
160
182
 
161
183
  ## 快速自检
162
184
 
@@ -185,6 +207,14 @@ README 只保留快速入口;完整命令和浏览器操作 SOP 见 [skills/ag
185
207
 
186
208
  ```bash
187
209
  agent-browser-cli tabs
210
+ agent-browser-cli tabtree
211
+ agent-browser-cli tabtree --full
212
+ agent-browser-cli tabtree --profile <profile_label>
213
+ agent-browser-cli tabtree --tab <tabId>
214
+ agent-browser-cli lookup tab <tabId>
215
+ agent-browser-cli lookup browser <browser_id>
216
+ agent-browser-cli profile-label set work --profile <profile_id>
217
+ agent-browser-cli profile-label clear --profile <profile_id>
188
218
  ```
189
219
 
190
220
  ## 更新
@@ -210,6 +240,7 @@ agent-browser-cli stop
210
240
  然后按需清理:
211
241
 
212
242
  ```bash
243
+ npm uninstall -g @sleepinsummer/agent-browser-cli
213
244
  rm -f .agent-browser-cli.log .agent-browser-cli.lock
214
245
  rm -rf ~/.agents/skills/agent-browser-cli
215
246
  ```
package/README_EN.md CHANGED
@@ -53,32 +53,36 @@ Please read https://github.com/sleepinginsummer/agent-browser-cli/blob/main/AI_I
53
53
  - Includes several optimizations to reduce command execution time.
54
54
  - Rust implementation for the CLI side.
55
55
 
56
- ## Performance Reference
57
-
58
- The following numbers are measured with the long-lived service already running and the Chrome extension already connected. Actual latency depends on page complexity, network conditions, Chrome state, and response size.
59
-
60
- | Operation | Reference Latency |
61
- | --- | --- |
62
- | Open a Baidu tab | About `0.10s` |
63
- | Inject JS to enter a keyword and submit search | About `0.27s` |
64
- | Open Baidu and search “小猫” end-to-end | About `0.37s` |
65
- | `scan --tab --text-only` to read page text | About `0.04-0.12s` |
66
- | `exec 'return document.title'` for simple JS | About `0.04-0.12s` |
67
- | `exec 'return document.body.innerText'` to read body text | Mostly `0.04-0.05s`, occasional `0.30s` |
68
- | Query DOM link lists | About `0.27-0.36s` |
69
- | `exec --monitor` page-change summary | About `0.72-0.88s` |
70
-
71
- Rule of thumb: normal page reads and simple JS injection are around the `50ms` level; complex DOM queries depend on page structure and returned data size, commonly around `300ms`; `--monitor` adds page-change summary work and is usually close to `0.8s`.
72
-
73
- Reference comparison with the original Python call chain:
74
-
75
- | Item | Python Version | Rust CLI Version |
76
- | --- | --- | --- |
77
- | Startup model | Each call is more likely to pay for Python process startup, module loading, and connection initialization | CLI commands reuse the long-lived service and avoid repeated browser connection initialization |
78
- | Simple page read / JS injection | Usually more affected by process startup and the Python call chain, so latency is less stable | Commonly `0.04-0.12s` |
79
- | Repeated calls | Overhead is more visible across many short commands | Better suited for high-frequency Agent calls |
80
-
81
- This comparison is intended to describe the performance trend caused by the architecture difference. Actual latency still depends on page complexity, Chrome state, and response size.
56
+ ## What It Can Do
57
+
58
+ 1. Automated testing
59
+ It can reuse a real browser environment for page-flow validation, form submission, button clicks, navigation checks, and login-state page testing.
60
+ 2. Frontend page debugging
61
+ It can read the DOM, execute JS, inspect page state, and capture screenshots to help locate frontend interaction, rendering, and data issues, including backend API integration problems.
62
+ 3. Page style debugging
63
+ It can execute JS on real pages to modify DOM / CSS and temporarily validate styles, layout, and interaction effects. It is more of a debugging assistant than a complete design tool.
64
+ 4. Web data extraction
65
+ It can read page content, tables, lists, cookies, and API-related state, making it suitable for extracting data from pages that require login state.
66
+ 5. Browser operation scripting
67
+ It can chain operations such as opening pages, switching tabs, executing JS, taking screenshots, and uploading files into scripts for repetitive web tasks.
68
+ 6. Agent-assisted web admin operation
69
+ It is suitable for letting AI Agents operate existing web tools such as admin consoles, configuration pages, low-code platforms, and form systems.
70
+ 7. Page structure analysis
71
+ It can simplify HTML and identify main content areas and list structures, helping Agents understand complex pages faster.
72
+ 8. Security research and reverse-engineering assistance
73
+ It can observe page behavior in a real browser session, execute debugging scripts, and read frontend state to assist analysis of frontend logic and API calls.
74
+
75
+ ## Its Capabilities
76
+
77
+ 1. Scan current Chrome tabs and get page titles, URLs, and tab IDs.
78
+ 2. Switch to a specified tab and reuse existing pages and login state.
79
+ 3. Open new tabs and directly visit target URLs.
80
+ 4. Execute JavaScript in pages and read DOM, forms, state, and page data.
81
+ 5. Read cookies from the current page for login-state related tasks.
82
+ 6. Call Chrome CDP capabilities for lower-level page control.
83
+ 7. Capture page screenshots for visual inspection and page confirmation.
84
+ 8. Upload local files to web file picker inputs.
85
+ 9. Operate common page elements such as dropdowns, buttons, and forms.
82
86
 
83
87
  ## Layout
84
88
 
@@ -156,6 +160,10 @@ After manually editing the config file, run `agent-browser-cli restart` so the d
156
160
 
157
161
  The Chrome extension popup can also update the extension port and reconnect immediately. The popup port must match the CLI `extension_port` config.
158
162
 
163
+ ### Dialog Suppression
164
+
165
+ The extension no longer rewrites page `alert` / `confirm` / `prompt` globally. Native dialogs are suppressed only while a CLI page script command is running, then restored after the command finishes.
166
+
159
167
  ## Quick Check
160
168
 
161
169
  ```bash
@@ -177,12 +185,33 @@ On success, it returns:
177
185
  }
178
186
  ```
179
187
 
188
+ ### Profile Label
189
+
190
+ When multiple Chrome Profiles or browser instances are connected, `profile_id` and `browser_id` are long. You can set a short label for each Chrome Profile and then use `--profile <label>`.
191
+
192
+ ```bash
193
+ agent-browser-cli lookup tab <tabId>
194
+ agent-browser-cli lookup browser <browser_id>
195
+ agent-browser-cli profile-label set work --profile <profile_id>
196
+ agent-browser-cli tabs --profile work
197
+ ```
198
+
199
+ You can also set Profile Label from the extension popup in the corresponding Chrome Profile. The label is only an alias; internal routing still uses `browser_id:profile_id:tab_id`. If a label matches multiple profiles in the current daemon, the CLI reports ambiguity. Prefer setting labels via CLI because it checks uniqueness across currently connected profiles; the popup is a local convenience entry and cannot guarantee cross-profile uniqueness. `tabtree` uses compact output by default; pass `--full` for full URLs and session keys.
200
+
180
201
  ## Common Commands
181
202
 
182
203
  The README only keeps the quick entry point. For the full command list and browser operation SOP, see [skills/agent-browser-cli/SKILL.md](./skills/agent-browser-cli/SKILL.md).
183
204
 
184
205
  ```bash
185
206
  agent-browser-cli tabs
207
+ agent-browser-cli tabtree
208
+ agent-browser-cli tabtree --full
209
+ agent-browser-cli tabtree --profile <profile_label>
210
+ agent-browser-cli tabtree --tab <tabId>
211
+ agent-browser-cli lookup tab <tabId>
212
+ agent-browser-cli lookup browser <browser_id>
213
+ agent-browser-cli profile-label set work --profile <profile_id>
214
+ agent-browser-cli profile-label clear --profile <profile_id>
186
215
  ```
187
216
 
188
217
  ## Update
@@ -42,6 +42,7 @@ function withClientIdentity(payload) {
42
42
  async function handleExtMessage(msg, sender) {
43
43
  if (msg.cmd === 'status') return handleStatus();
44
44
  if (msg.cmd === 'setPort') return await handleSetPort(msg);
45
+ if (msg.cmd === 'setProfileLabel') return await handleSetProfileLabel(msg);
45
46
  lastCommandAt = Date.now();
46
47
  if (msg.cmd === 'cookies') return await handleCookies(msg, sender);
47
48
  if (msg.cmd === 'cdp') return await handleCDP(msg, sender);
@@ -137,6 +138,29 @@ async function handleSetPort(msg) {
137
138
  };
138
139
  }
139
140
 
141
+ function normalizeProfileLabel(label) {
142
+ const raw = String(label || '').trim();
143
+ if (!raw) return null;
144
+ if (raw.length > 40) throw new Error('Profile Label 长度不能超过 40 个字符');
145
+ if (!/^[A-Za-z0-9_.-]+$/.test(raw)) throw new Error('Profile Label 只能包含英文、数字、-、_、.');
146
+ return raw;
147
+ }
148
+
149
+ async function handleSetProfileLabel(msg) {
150
+ const label = normalizeProfileLabel(msg.label);
151
+ profileLabel = label;
152
+ await chrome.storage.local.set({ profileLabel });
153
+ await sendTabsUpdate();
154
+ return {
155
+ ok: true,
156
+ data: {
157
+ browserId,
158
+ profileId,
159
+ profileLabel
160
+ }
161
+ };
162
+ }
163
+
140
164
  chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
141
165
  handleExtMessage(msg, sender).then(sendResponse);
142
166
  return true;
@@ -618,6 +642,87 @@ async function injectContentScriptsIntoExistingTabs() {
618
642
  }
619
643
  }
620
644
 
645
+ function buildDialogSuppressionScript(enabled) {
646
+ if (!enabled) {
647
+ return `(() => {
648
+ const state = window.__TMWD_DIALOG_SUPPRESSION__;
649
+ if (!state) return;
650
+ state.count = Math.max(0, Number(state.count || 1) - 1);
651
+ if (state.count > 0) return;
652
+ try {
653
+ window.alert = state.alert;
654
+ window.confirm = state.confirm;
655
+ window.prompt = state.prompt;
656
+ } finally {
657
+ delete window.__TMWD_DIALOG_SUPPRESSION__;
658
+ }
659
+ })()`;
660
+ }
661
+ return `(() => {
662
+ const existing = window.__TMWD_DIALOG_SUPPRESSION__;
663
+ if (existing) {
664
+ existing.count = Number(existing.count || 1) + 1;
665
+ return;
666
+ }
667
+ const state = {
668
+ count: 1,
669
+ alert: window.alert,
670
+ confirm: window.confirm,
671
+ prompt: window.prompt
672
+ };
673
+ window.__TMWD_DIALOG_SUPPRESSION__ = state;
674
+ const toast = (type, msg) => {
675
+ try { console.log('[TMWD] ' + type + ' suppressed during CLI command:', msg); } catch (_) {}
676
+ try {
677
+ const d = document.createElement('div');
678
+ d.textContent = '[' + type + '] ' + msg;
679
+ Object.assign(d.style, {
680
+ position:'fixed', top:'12px', right:'12px', zIndex:'2147483647',
681
+ background:'#222', color:'#fff', padding:'10px 18px', borderRadius:'8px',
682
+ fontSize:'14px', maxWidth:'420px', wordBreak:'break-all',
683
+ boxShadow:'0 4px 16px rgba(0,0,0,.3)', opacity:'1',
684
+ transition:'opacity .5s', pointerEvents:'none'
685
+ });
686
+ (document.body || document.documentElement).appendChild(d);
687
+ setTimeout(() => { d.style.opacity = '0'; }, 3000);
688
+ setTimeout(() => { d.remove(); }, 3600);
689
+ } catch (_) {}
690
+ };
691
+ window.alert = function(msg) { toast('alert', msg); };
692
+ window.confirm = function(msg) { toast('confirm', msg); return true; };
693
+ window.prompt = function(msg, def) { toast('prompt', msg); return def || null; };
694
+ })()`;
695
+ }
696
+
697
+ async function setDialogSuppressionByScripting(tabId, enabled) {
698
+ try {
699
+ await chrome.scripting.executeScript({
700
+ target: { tabId },
701
+ world: 'MAIN',
702
+ func: async (script) => await eval(script),
703
+ args: [buildDialogSuppressionScript(enabled)]
704
+ });
705
+ return true;
706
+ } catch (e) {
707
+ console.log('[TMWD-WS] dialog suppression scripting failed:', e.message);
708
+ return false;
709
+ }
710
+ }
711
+
712
+ async function setDialogSuppressionByCdp(tabId, enabled) {
713
+ try {
714
+ await chrome.debugger.sendCommand({ tabId }, 'Runtime.evaluate', {
715
+ expression: buildDialogSuppressionScript(enabled),
716
+ awaitPromise: true,
717
+ returnByValue: true
718
+ });
719
+ return true;
720
+ } catch (e) {
721
+ console.log('[TMWD-WS] dialog suppression CDP failed:', e.message);
722
+ return false;
723
+ }
724
+ }
725
+
621
726
  // --- Shared page/CDP script builder core ---
622
727
  function buildExecScript(code, errorHandler) {
623
728
  return `(async () => {
@@ -764,6 +869,7 @@ async function handleWsExec(data) {
764
869
  const newTabIds = new Set();
765
870
  const onCreated = (tab) => { newTabIds.add(tab.id); };
766
871
  chrome.tabs.onCreated.addListener(onCreated);
872
+ await setDialogSuppressionByScripting(tabId, true);
767
873
  try {
768
874
  let res;
769
875
  try {
@@ -788,9 +894,11 @@ async function handleWsExec(data) {
788
894
  const wrappedCode = buildCdpScript(data.code);
789
895
  try {
790
896
  await chrome.debugger.attach({ tabId }, '1.3');
897
+ await setDialogSuppressionByCdp(tabId, true);
791
898
  const cdpRes = await chrome.debugger.sendCommand({ tabId }, 'Runtime.evaluate', {
792
899
  expression: wrappedCode, awaitPromise: true, returnByValue: true
793
900
  });
901
+ await setDialogSuppressionByCdp(tabId, false);
794
902
  await chrome.debugger.detach({ tabId });
795
903
  if (cdpRes.exceptionDetails) {
796
904
  const desc = cdpRes.exceptionDetails.exception?.description || 'CDP Error';
@@ -799,6 +907,7 @@ async function handleWsExec(data) {
799
907
  res = cdpRes.result.value;
800
908
  }
801
909
  } catch (cdpErr) {
910
+ try { await setDialogSuppressionByCdp(tabId, false); } catch (_) {}
802
911
  try { await chrome.debugger.detach({ tabId }); } catch (_) {}
803
912
  res = { ok: false, error: { name: 'Error', message: 'CDP fallback failed: ' + cdpErr.message, stack: '' } };
804
913
  }
@@ -812,7 +921,7 @@ async function handleWsExec(data) {
812
921
  try { const t = await chrome.tabs.get(id); newTabs.push({id: t.id, url: t.url, title: t.title}); } catch (_) {}
813
922
  }
814
923
  if (res?.ok) {
815
- ws.send(JSON.stringify({ type: 'result', id: data.id, result: res.data, newTabs }));
924
+ ws.send(JSON.stringify({ type: 'result', id: data.id, result: res.data ?? null, newTabs }));
816
925
  } else {
817
926
  console.log(res);
818
927
  ws.send(JSON.stringify({ type: 'error', id: data.id, error: res?.error || 'Unknown error', newTabs }));
@@ -820,6 +929,7 @@ async function handleWsExec(data) {
820
929
  } catch (e) {
821
930
  ws.send(JSON.stringify({ type: 'error', id: data.id, error: { name: e.name || 'Error', message: e.message || String(e), stack: e.stack || '' } }));
822
931
  } finally {
932
+ await setDialogSuppressionByScripting(tabId, false);
823
933
  chrome.tabs.onCreated.removeListener(onCreated);
824
934
  }
825
935
  }
@@ -1,24 +1,3 @@
1
- // Disable alert/confirm/prompt to prevent page JS from blocking extension
2
- (function() {
3
- const _log = console.log.bind(console);
4
- function toast(type, msg) {
5
- _log('[TMWD] ' + type + ' suppressed:', msg);
6
- try {
7
- const d = document.createElement('div');
8
- d.textContent = '[' + type + '] ' + msg;
9
- Object.assign(d.style, {
10
- position:'fixed', top:'12px', right:'12px', zIndex:'2147483647',
11
- background:'#222', color:'#fff', padding:'10px 18px', borderRadius:'8px',
12
- fontSize:'14px', maxWidth:'420px', wordBreak:'break-all',
13
- boxShadow:'0 4px 16px rgba(0,0,0,.3)', opacity:'1',
14
- transition:'opacity .5s', pointerEvents:'none'
15
- });
16
- (document.body || document.documentElement).appendChild(d);
17
- setTimeout(() => { d.style.opacity = '0'; }, 3000);
18
- setTimeout(() => { d.remove(); }, 3600);
19
- } catch(e) {}
20
- }
21
- window.alert = function(msg) { toast('alert', msg); };
22
- window.confirm = function(msg) { toast('confirm', msg); return true; };
23
- window.prompt = function(msg, def) { toast('prompt', msg); return def || null; };
24
- })();
1
+ // Deprecated: dialog suppression is no longer injected globally.
2
+ // background.js now enables alert/confirm/prompt suppression only during CLI page execution
3
+ // and restores the native functions after the command finishes.
@@ -23,17 +23,6 @@
23
23
  "service_worker": "background.js"
24
24
  },
25
25
  "content_scripts": [
26
- {
27
- "matches": [
28
- "<all_urls>"
29
- ],
30
- "js": [
31
- "disable_dialogs.js"
32
- ],
33
- "run_at": "document_start",
34
- "all_frames": true,
35
- "world": "MAIN"
36
- },
37
26
  {
38
27
  "matches": [
39
28
  "<all_urls>"
@@ -9,6 +9,7 @@ button{background:#264f78;color:#fff;border:none;padding:4px 12px;cursor:pointer
9
9
  button:hover{background:#37699e}
10
10
  input{width:86px;background:#252526;color:#d4d4d4;border:1px solid #3c3c3c;border-radius:3px;padding:4px;font:12px monospace}
11
11
  label{display:inline-flex;align-items:center;gap:6px;margin-right:6px}
12
+ #profileLabel{width:150px}
12
13
  .panel{margin-bottom:8px;padding:6px;background:#252526;border-radius:3px}
13
14
  .row{display:flex;align-items:center;gap:6px;margin-top:6px}
14
15
  .status{color:#9cdcfe}
@@ -26,6 +27,17 @@ pre{white-space:pre-wrap;word-break:break-all;margin:0;padding:6px;background:#2
26
27
  </div>
27
28
  <div id="portMsg"></div>
28
29
  </div>
30
+ <h3>👤 Profile</h3>
31
+ <div class="panel">
32
+ <div id="profileStatus" class="status">读取 Profile...</div>
33
+ <div class="row">
34
+ <label>Label <input id="profileLabel" type="text" maxlength="40" placeholder="work"></label>
35
+ <button id="saveProfileLabel">保存</button>
36
+ <button id="clearProfileLabel">清空</button>
37
+ </div>
38
+ <div id="profileMsg"></div>
39
+ <div class="status">建议用 CLI 设置 label,可校验跨 Profile 唯一性。</div>
40
+ </div>
29
41
  <h3>🍪 Cookies</h3>
30
42
  <button id="refresh">刷新</button>
31
43
  <pre id="out">点击刷新获取 cookies...</pre>
@@ -1,8 +1,12 @@
1
1
  document.addEventListener('DOMContentLoaded', () => {
2
2
  const btn = document.getElementById('refresh');
3
3
  const savePortBtn = document.getElementById('savePort');
4
+ const saveProfileLabelBtn = document.getElementById('saveProfileLabel');
5
+ const clearProfileLabelBtn = document.getElementById('clearProfileLabel');
4
6
  btn.addEventListener('click', fetchCookies);
5
7
  savePortBtn.addEventListener('click', savePort);
8
+ saveProfileLabelBtn.addEventListener('click', saveProfileLabel);
9
+ clearProfileLabelBtn.addEventListener('click', clearProfileLabel);
6
10
  refreshBridgeStatus();
7
11
  fetchCookies();
8
12
  });
@@ -16,9 +20,49 @@ async function refreshBridgeStatus() {
16
20
  const data = resp.data || {};
17
21
  portInput.value = data.wsPort || 18765;
18
22
  status.textContent = `状态: ${data.wsConnected ? '已连接' : '未连接'} ${data.wsUrl || ''}`;
23
+ renderProfileStatus(data);
19
24
  } catch (e) {
20
25
  status.textContent = '状态读取失败: ' + e.message;
21
26
  status.className = 'error';
27
+ const profileStatus = document.getElementById('profileStatus');
28
+ profileStatus.textContent = 'Profile 读取失败: ' + e.message;
29
+ profileStatus.className = 'error';
30
+ }
31
+ }
32
+
33
+ function renderProfileStatus(data) {
34
+ const profileStatus = document.getElementById('profileStatus');
35
+ const profileLabelInput = document.getElementById('profileLabel');
36
+ const profileId = data.profileId || '-';
37
+ const browserId = data.browserId || '-';
38
+ const label = data.profileLabel || '';
39
+ profileLabelInput.value = label;
40
+ profileStatus.textContent = `Profile: ${label || '(未设置)'} / ${profileId} / ${browserId}`;
41
+ profileStatus.className = 'status';
42
+ }
43
+
44
+ async function saveProfileLabel() {
45
+ const input = document.getElementById('profileLabel');
46
+ await setProfileLabel(input.value);
47
+ }
48
+
49
+ async function clearProfileLabel() {
50
+ const input = document.getElementById('profileLabel');
51
+ input.value = '';
52
+ await setProfileLabel(null);
53
+ }
54
+
55
+ async function setProfileLabel(label) {
56
+ const profileMsg = document.getElementById('profileMsg');
57
+ try {
58
+ const resp = await chrome.runtime.sendMessage({ cmd: 'setProfileLabel', label });
59
+ if (!resp?.ok) throw new Error(resp?.error || 'unknown');
60
+ profileMsg.textContent = `Success: Profile Label ${resp.data?.profileLabel || '已清空'}`;
61
+ profileMsg.className = 'status';
62
+ await refreshBridgeStatus();
63
+ } catch (e) {
64
+ profileMsg.textContent = '保存失败: ' + e.message;
65
+ profileMsg.className = 'error';
22
66
  }
23
67
  }
24
68
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleepinsummer/agent-browser-cli",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Agent-oriented browser sensing and control CLI backed by a native Rust daemon.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -24,11 +24,11 @@
24
24
  "postinstall": "node npm/postinstall.js"
25
25
  },
26
26
  "optionalDependencies": {
27
- "@sleepinsummer/agent-browser-cli-darwin-arm64": "0.3.2",
28
- "@sleepinsummer/agent-browser-cli-darwin-x64": "0.3.2",
29
- "@sleepinsummer/agent-browser-cli-linux-x64": "0.3.2",
30
- "@sleepinsummer/agent-browser-cli-linux-arm64": "0.3.2",
31
- "@sleepinsummer/agent-browser-cli-win32-x64": "0.3.2"
27
+ "@sleepinsummer/agent-browser-cli-darwin-arm64": "0.3.3",
28
+ "@sleepinsummer/agent-browser-cli-darwin-x64": "0.3.3",
29
+ "@sleepinsummer/agent-browser-cli-linux-x64": "0.3.3",
30
+ "@sleepinsummer/agent-browser-cli-linux-arm64": "0.3.3",
31
+ "@sleepinsummer/agent-browser-cli-win32-x64": "0.3.3"
32
32
  },
33
33
  "engines": {
34
34
  "node": ">=18"
@@ -43,7 +43,15 @@ agent-browser-cli status
43
43
  agent-browser-cli doctor
44
44
  agent-browser-cli logs --tail 100
45
45
  agent-browser-cli tabs
46
+ agent-browser-cli tabtree
47
+ agent-browser-cli tabtree --full
48
+ agent-browser-cli tabtree --profile work
49
+ agent-browser-cli tabtree --tab <tabId>
50
+ agent-browser-cli lookup tab <tabId>
51
+ agent-browser-cli lookup browser <browser_id>
52
+ agent-browser-cli lookup profile work
46
53
  agent-browser-cli tabs --profile work
54
+ agent-browser-cli profile-label set work --profile <profile_id>
47
55
  agent-browser-cli scan --tabs-only
48
56
  agent-browser-cli scan --profile work --tab <tabId> --text-only
49
57
  agent-browser-cli open --profile work https://example.com
@@ -81,7 +89,7 @@ agent-browser-cli send-keys --target '@e2' 'Enter'
81
89
  agent-browser-cli mouse-click '@e3'
82
90
  ```
83
91
 
84
- 所有高层操作都支持 `--tab <tabId>`,多 Chrome Profile / 多浏览器实例时还支持 `--profile <profile_id-or-label>` 和 `--browser <browser_id>`。`tabs` 输出会包含 `browser_id`、`profile_id`、`profile_label`、`tab_id`、`session_key`。只传 `--tab` 且存在歧义时,必须补 `--profile` 或 `--browser`,不要猜。`@e` 只在当前 daemon、当前 session_key、最近一次 `snapshot` 内有效。`@e` 只接受 `@e1` 这种带 `@` 的格式。
92
+ 所有高层操作都支持 `--tab <tabId>`,多 Chrome Profile / 多浏览器实例时还支持 `--profile <profile_id-or-label>` 和 `--browser <browser_id>`。`tabs` 输出会包含 `browser_id`、`profile_id`、`profile_label`、`tab_id`、`session_key`。`tabtree` 用树形结构列出 browser → profile → tabs,支持 `--tab <tabId>`、`--profile <profile_id-or-label>`、`--browser <browser_id>` 过滤,过滤结果仍保留父子节点;默认 compact 输出会截断 URL 并省略 `session_key`,需要完整字段时用 `tabtree --full`;`lookup tab <tabId>` 可由 tab 反查 `browser_id` / `profile_id` / `profile_label`,`lookup browser <browser_id>` 可反查所属 profile。`profile-label set <label> --profile <profile_id>` 可设置当前 Chrome Profile 的 label 并校验当前 daemon 内唯一性,`profile-label clear --profile <profile_id>` 可清空;popup 设置是本地便捷入口,不保证跨 Profile 唯一;label 只允许英文、数字、`-`、`_`、`.`,当前 daemon 内匹配到多个 profile 时会报歧义,不能猜。只传 `--tab` 且存在歧义时,必须补 `--profile` 或 `--browser`。`@e` 只在当前 daemon、当前 session_key、最近一次 `snapshot` 内有效。`@e` 只接受 `@e1` 这种带 `@` 的格式。
85
93
 
86
94
  慢页面要把等待和监控分开:
87
95
 
@@ -91,6 +99,8 @@ agent-browser-cli click '@e1' --wait-js 'return document.body.innerText.includes
91
99
 
92
100
  `--wait-js` 负责等慢加载;`--monitor` 只负责操作前后页面 diff,默认关闭。
93
101
 
102
+ 弹窗处理:扩展默认不改写业务页面的 `alert` / `confirm` / `prompt`。只有 CLI 页面执行命令期间临时抑制弹窗,结束后恢复;如果怀疑页面原生弹窗行为异常,先让用户重载扩展和页面。
103
+
94
104
  ## 端口和扩展
95
105
 
96
106
  固定 API 端口: