qiaomu-opencli-browser

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OpenCLI Browser — Browser Automation for AI Agents

OpenCLI Browser — 面向AI Agent的浏览器自动化

Control Chrome step-by-step via CLI. Reuses existing login sessions — no passwords needed.
通过CLI分步控制Chrome,可复用已有的登录会话,无需输入密码。

Prerequisites

前置要求

bash
opencli doctor    # Verify extension + daemon connectivity
Requires: Chrome running + OpenCLI Browser Bridge extension installed.
bash
opencli doctor    # 验证扩展 + 守护进程连通性
要求:Chrome正在运行,且已安装OpenCLI Browser Bridge扩展。

Critical Rules

关键规则

  1. ALWAYS use
    state
    to inspect the page, NEVER use
    screenshot
    state
    returns structured DOM with
    [N]
    element indices, is instant and costs zero tokens.
    screenshot
    requires vision processing and is slow. Only use
    screenshot
    when the user explicitly asks to save a visual.
  2. ALWAYS use
    click
    /
    type
    /
    select
    for interaction, NEVER use
    eval
    to click or type
    eval "el.click()"
    bypasses scrollIntoView and CDP click pipeline, causing failures on off-screen elements. Use
    state
    to find the
    [N]
    index, then
    click <N>
    .
  3. Verify inputs with
    get value
    , not screenshots
    — after
    type
    , run
    get value <index>
    to confirm.
  4. Run
    state
    after every page change
    — after
    open
    ,
    click
    (on links),
    scroll
    , always run
    state
    to see the new elements and their indices. Never guess indices.
  5. Chain commands aggressively with
    &&
    — combine
    open + state
    , multiple
    type
    calls, and
    type + get value
    into single
    &&
    chains. Each tool call has overhead; chaining cuts it.
  6. eval
    is read-only
    — use
    eval
    ONLY for data extraction (
    JSON.stringify(...)
    ), never for clicking, typing, or navigating. Always wrap in IIFE to avoid variable conflicts:
    eval "(function(){ const x = ...; return JSON.stringify(x); })()"
    .
  7. Minimize total tool calls — plan your sequence before acting. A good task completion uses 3-5 tool calls, not 15-20. Combine
    open + state
    as one call. Combine
    type + type + click
    as one call. Only run
    state
    separately when you need to discover new indices.
  8. Prefer
    network
    to discover APIs
    — most sites have JSON APIs. API-based adapters are more reliable than DOM scraping.
  1. 务必使用
    state
    检查页面,绝对不要使用
    screenshot
    state
    会返回带有
    [N]
    元素索引的结构化DOM,速度极快且不消耗任何tokens。
    screenshot
    需要视觉处理,速度很慢。仅在用户明确要求保存可视化内容时使用
    screenshot
  2. 交互务必使用
    click
    /
    type
    /
    select
    ,绝对不要用
    eval
    执行点击或输入操作
    eval "el.click()"
    会绕过scrollIntoView和CDP点击流程,导致屏幕外元素操作失败。请先用
    state
    找到
    [N]
    索引,再执行
    click <N>
  3. 使用
    get value
    验证输入,不要用截图
    — 执行
    type
    后,运行
    get value <index>
    确认输入内容。
  4. 每次页面变更后都要运行
    state
    — 执行
    open
    、(链接的)
    click
    scroll
    操作后,务必运行
    state
    查看新元素及其索引,绝对不要猜测索引。
  5. 尽量用
    &&
    链式调用命令
    — 将
    open + state
    、多个
    type
    调用、
    type + get value
    组合成单个
    &&
    链式命令。每次工具调用都有 overhead,链式调用可以减少开销。
  6. eval
    仅用于只读操作
    eval
    仅可用于数据提取(
    JSON.stringify(...)
    ),绝对不要用于点击、输入或导航。请始终用IIFE包裹避免变量冲突:
    eval "(function(){ const x = ...; return JSON.stringify(x); })()"
  7. 尽量减少总工具调用次数 — 操作前先规划执行序列。一次优秀的任务完成仅需要3-5次工具调用,而非15-20次。将
    open + state
    合并为一次调用,将
    type + type + click
    合并为一次调用。仅当你需要发现新索引时才单独运行
    state
  8. 优先用
    network
    发现API
    — 绝大多数站点都有JSON API,基于API的适配比DOM爬取可靠性更高。

Command Cost Guide

命令成本指南

CostCommandsWhen to use
Free & instant
state
,
get *
,
eval
,
network
,
scroll
,
keys
Default — use these
Free but changes page
open
,
click
,
type
,
select
,
back
Interaction — run
state
after
Expensive (vision tokens)
screenshot
ONLY when user needs a saved image
成本命令使用时机
免费且即时
state
,
get *
,
eval
,
network
,
scroll
,
keys
默认选择——优先使用这类命令
免费但会修改页面
open
,
click
,
type
,
select
,
back
交互场景——执行后运行
state
成本高(消耗视觉tokens)
screenshot
仅当用户需要保存图片时使用

Action Chaining Rules

链式调用规则

Commands can be chained with
&&
. The browser persists via daemon, so chaining is safe.
Always chain when possible — fewer tool calls = faster completion:
bash
undefined
命令可以通过
&&
进行链式调用。浏览器通过守护进程保持运行状态,因此链式调用是安全的。
尽可能使用链式调用 — 更少的工具调用 = 更快的完成速度:
bash
undefined

GOOD: open + inspect in one call (saves 1 round trip)

优秀示例:打开页面 + 检查合并为一次调用(节省1次往返)

opencli browser open https://example.com && opencli browser state
opencli browser open https://example.com && opencli browser state

GOOD: fill form in one call (saves 2 round trips)

优秀示例:填写表单合并为一次调用(节省2次往返)

opencli browser type 3 "hello" && opencli browser type 4 "world" && opencli browser click 7
opencli browser type 3 "hello" && opencli browser type 4 "world" && opencli browser click 7

GOOD: type + verify in one call

优秀示例:输入 + 验证合并为一次调用

opencli browser type 5 "test@example.com" && opencli browser get value 5
opencli browser type 5 "test@example.com" && opencli browser get value 5

GOOD: click + wait + state in one call (for page-changing clicks)

优秀示例:点击 + 等待 + 状态检查合并为一次调用(适用于会变更页面的点击操作)

opencli browser click 12 && opencli browser wait time 1 && opencli browser state
opencli browser click 12 && opencli browser wait time 1 && opencli browser state

BAD: separate calls for each action (wasteful)

反面示例:每个操作单独调用(浪费资源)

opencli browser type 3 "hello" # Don't do this opencli browser type 4 "world" # when you can chain opencli browser click 7 # all three together

**Page-changing — always put last** in a chain (subsequent commands see stale indices):
- `open <url>`, `back`, `click <link/button that navigates>`

**Rule**: Chain when you already know the indices. Run `state` separately when you need to discover indices first.
opencli browser type 3 "hello" # 不要这么做 opencli browser type 4 "world" # 完全可以把三个操作 opencli browser click 7 # 合并成链式调用

**会变更页面的命令——请始终放在链式调用末尾**(后续命令会读取到过期索引):
- `open <url>`, `back`, `click <会跳转的链接/按钮>`

**规则**:当你已经知道索引时使用链式调用,当你需要先发现索引时单独运行`state`。

Core Workflow

核心工作流

  1. Navigate:
    opencli browser open <url>
  2. Inspect:
    opencli browser state
    → elements with
    [N]
    indices
  3. Interact: use indices —
    click
    ,
    type
    ,
    select
    ,
    keys
  4. Wait (if needed):
    opencli browser wait selector ".loaded"
    or
    wait text "Success"
  5. Verify:
    opencli browser state
    or
    opencli browser get value <N>
  6. Repeat: browser stays open between commands
  7. Save: write a TS adapter to
    ~/.opencli/clis/<site>/<command>.ts
  1. 导航
    opencli browser open <url>
  2. 检查
    opencli browser state
    → 获取带有
    [N]
    索引的元素列表
  3. 交互:使用索引操作 —
    click
    ,
    type
    ,
    select
    ,
    keys
  4. 等待(如需):
    opencli browser wait selector ".loaded"
    wait text "Success"
  5. 验证
    opencli browser state
    opencli browser get value <N>
  6. 重复:命令执行间隙浏览器保持打开状态
  7. 保存:编写TS适配器到
    ~/.opencli/clis/<site>/<command>.ts

Commands

命令列表

Navigation

导航

bash
opencli browser open <url>              # Open URL (page-changing)
opencli browser back                    # Go back (page-changing)
opencli browser scroll down             # Scroll (up/down, --amount N)
opencli browser scroll up --amount 1000
bash
opencli browser open <url>              # 打开URL(会变更页面)
opencli browser back                    # 返回上一页(会变更页面)
opencli browser scroll down             # 滚动(上/下,--amount N指定滚动像素)
opencli browser scroll up --amount 1000

Inspect (free & instant)

检查(免费且即时)

bash
opencli browser state                   # Structured DOM with [N] indices — PRIMARY tool
opencli browser screenshot [path.png]   # Save visual to file — ONLY for user deliverables
bash
opencli browser state                   # 带有[N]索引的结构化DOM —— 核心工具
opencli browser screenshot [path.png]   # 保存可视化内容到文件 —— 仅用于用户交付物

Get (free & instant)

获取(免费且即时)

bash
opencli browser get title               # Page title
opencli browser get url                 # Current URL
opencli browser get text <index>        # Element text content
opencli browser get value <index>       # Input/textarea value (use to verify after type)
opencli browser get html                # Full page HTML
opencli browser get html --selector "h1" # Scoped HTML
opencli browser get attributes <index>  # Element attributes
bash
opencli browser get title               # 页面标题
opencli browser get url                 # 当前URL
opencli browser get text <index>        # 元素文本内容
opencli browser get value <index>       # 输入框/文本框的值(用于输入后验证)
opencli browser get html                # 全页HTML
opencli browser get html --selector "h1" # 限定范围的HTML
opencli browser get attributes <index>  # 元素属性

Interact

交互

bash
opencli browser click <index>           # Click element [N]
opencli browser type <index> "text"     # Type into element [N]
opencli browser select <index> "option" # Select dropdown
opencli browser keys "Enter"            # Press key (Enter, Escape, Tab, Control+a)
bash
opencli browser click <index>           # 点击元素[N]
opencli browser type <index> "text"     # 向元素[N]输入文本
opencli browser select <index> "option" # 选择下拉框选项
opencli browser keys "Enter"            # 按下按键(Enter、Escape、Tab、Control+a等)

Wait

等待

Three variants — use the right one for the situation:
bash
opencli browser wait time 3                       # Wait N seconds (fixed delay)
opencli browser wait selector ".loaded"            # Wait until element appears in DOM
opencli browser wait selector ".spinner" --timeout 5000  # With timeout (default 30s)
opencli browser wait text "Success"                # Wait until text appears on page
When to wait: After
open
on SPAs, after
click
that triggers async loading, before
eval
on dynamically rendered content.
三种类型——根据场景选择合适的方式:
bash
opencli browser wait time 3                       # 等待N秒(固定延迟)
opencli browser wait selector ".loaded"            # 等待元素出现在DOM中
opencli browser wait selector ".spinner" --timeout 5000  # 带超时时间(默认30秒)
opencli browser wait text "Success"                # 等待文本出现在页面中
等待的使用场景:在SPA上执行
open
后、点击触发异步加载的操作后、对动态渲染内容执行
eval
前。

Extract (free & instant, read-only)

提取(免费且即时,只读)

Use
eval
ONLY for reading data. Never use it to click, type, or navigate.
bash
opencli browser eval "document.title"
opencli browser eval "JSON.stringify([...document.querySelectorAll('h2')].map(e => e.textContent))"
eval
仅可用于读取数据,绝对不要用于点击、输入或导航。
bash
opencli browser eval "document.title"
opencli browser eval "JSON.stringify([...document.querySelectorAll('h2')].map(e => e.textContent))"

IMPORTANT: wrap complex logic in IIFE to avoid "already declared" errors

重要提示:将复杂逻辑包裹在IIFE中,避免“变量已声明”错误

opencli browser eval "(function(){ const items = [...document.querySelectorAll('.item')]; return JSON.stringify(items.map(e => e.textContent)); })()"

**Selector safety**: Always use fallback selectors — `querySelector` returns `null` on miss:
```bash
opencli browser eval "(function(){ const items = [...document.querySelectorAll('.item')]; return JSON.stringify(items.map(e => e.textContent)); })()"

**选择器安全**:始终使用兜底选择器 —— `querySelector`匹配不到时会返回`null`:
```bash

BAD: crashes if selector misses

反面示例:选择器匹配不到时会崩溃

opencli browser eval "document.querySelector('.title').textContent"
opencli browser eval "document.querySelector('.title').textContent"

GOOD: fallback with || or ?.

优秀示例:用||或?.做兜底

opencli browser eval "(document.querySelector('.title') || document.querySelector('h1') || {textContent:''}).textContent" opencli browser eval "document.querySelector('.title')?.textContent ?? 'not found'"
undefined
opencli browser eval "(document.querySelector('.title') || document.querySelector('h1') || {textContent:''}).textContent" opencli browser eval "document.querySelector('.title')?.textContent ?? 'not found'"
undefined

Network (API Discovery)

网络(API发现)

bash
opencli browser network                  # Show captured API requests (auto-captured since open)
opencli browser network --detail 3       # Show full response body of request #3
opencli browser network --all            # Include static resources
bash
opencli browser network                  # 展示捕获的API请求(打开页面后自动捕获)
opencli browser network --detail 3       # 展示第3号请求的完整响应体
opencli browser network --all            # 包含静态资源

Sedimentation (Save as CLI)

沉淀(保存为CLI)

bash
opencli browser init hn/top              # Generate adapter scaffold at ~/.opencli/clis/hn/top.ts
opencli browser verify hn/top            # Test the adapter (adds --limit 3 only if `limit` arg is defined)
  • init
    auto-detects the domain from the active browser session (no need to specify it)
  • init
    creates the file + populates
    site
    ,
    name
    ,
    domain
    , and
    columns
    from current page
  • verify
    runs the adapter end-to-end and prints output; if no
    limit
    arg exists in the adapter, it won't pass
    --limit 3
bash
opencli browser init hn/top              # 在~/.opencli/clis/hn/top.ts生成适配器脚手架
opencli browser verify hn/top            # 测试适配器(仅当适配器定义了`limit`参数时才会传递--limit 3)
  • init
    会自动从活跃浏览器会话中识别域名(无需手动指定)
  • init
    会创建文件并基于当前页面填充
    site
    name
    domain
    columns
    字段
  • verify
    会端到端运行适配器并打印输出;如果适配器中没有
    limit
    参数,就不会传递
    --limit 3

Session

会话

bash
opencli browser close                   # Close automation window
bash
opencli browser close                   # 关闭自动化窗口

Example: Extract HN Stories

示例:提取HN文章

bash
opencli browser open https://news.ycombinator.com
opencli browser state                   # See [1] a "Story 1", [2] a "Story 2"...
opencli browser eval "JSON.stringify([...document.querySelectorAll('.titleline a')].slice(0,5).map(a => ({title: a.textContent, url: a.href})))"
opencli browser close
bash
opencli browser open https://news.ycombinator.com
opencli browser state                   # 可以看到[1] a "文章1", [2] a "文章2"...
opencli browser eval "JSON.stringify([...document.querySelectorAll('.titleline a')].slice(0,5).map(a => ({title: a.textContent, url: a.href})))"
opencli browser close

Example: Fill a Form

示例:填写表单

bash
opencli browser open https://httpbin.org/forms/post
opencli browser state                   # See [3] input "Customer Name", [4] input "Telephone"
opencli browser type 3 "OpenCLI" && opencli browser type 4 "555-0100"
opencli browser get value 3             # Verify: "OpenCLI"
opencli browser close
bash
opencli browser open https://httpbin.org/forms/post
opencli browser state                   # 可以看到[3] input "客户姓名", [4] input "电话"
opencli browser type 3 "OpenCLI" && opencli browser type 4 "555-0100"
opencli browser get value 3             # 验证:返回"OpenCLI"
opencli browser close

Saving as Reusable CLI — Complete Workflow

保存为可复用CLI——完整工作流

Step-by-step sedimentation flow:

分步沉淀流程:

bash
undefined
bash
undefined

1. Explore the website

1. 探索网站

opencli browser open https://news.ycombinator.com opencli browser state # Understand DOM structure
opencli browser open https://news.ycombinator.com opencli browser state # 了解DOM结构

2. Discover APIs (crucial for high-quality adapters)

2. 发现API(高质量适配器的核心)

opencli browser eval "fetch('/api/...').then(r=>r.json())" # Trigger API calls opencli browser network # See captured API requests opencli browser network --detail 0 # Inspect response body
opencli browser eval "fetch('/api/...').then(r=>r.json())" # 触发API调用 opencli browser network # 查看捕获的API请求 opencli browser network --detail 0 # 检查响应体

3. Generate scaffold

3. 生成脚手架

opencli browser init hn/top # Creates ~/.opencli/clis/hn/top.ts
opencli browser init hn/top # 创建~/.opencli/clis/hn/top.ts

4. Edit the adapter (fill in func logic)

4. 编辑适配器(填充函数逻辑)

- If API found: use fetch() directly (Strategy.PUBLIC or COOKIE)

- 如果找到API:直接使用fetch()(Strategy.PUBLIC或COOKIE)

- If no API: use page.evaluate() for DOM extraction (Strategy.UI)

- 如果没有API:使用page.evaluate()做DOM提取(Strategy.UI)

5. Verify

5. 验证

opencli browser verify hn/top # Runs the adapter and shows output
opencli browser verify hn/top # 运行适配器并展示输出

6. If verify fails, edit and retry

6. 如果验证失败,编辑后重试

7. Close when done

7. 完成后关闭

opencli browser close
undefined
opencli browser close
undefined

Example adapter:

适配器示例:

typescript
// ~/.opencli/clis/hn/top.ts
import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'hn',
  name: 'top',
  description: 'Top Hacker News stories',
  domain: 'news.ycombinator.com',
  strategy: Strategy.PUBLIC,
  browser: false,
  args: [{ name: 'limit', type: 'int', default: 5 }],
  columns: ['rank', 'title', 'score', 'url'],
  func: async (_page, kwargs) => {
    const limit = Math.min(Math.max(1, kwargs.limit ?? 5), 50);
    const resp = await fetch('https://hacker-news.firebaseio.com/v0/topstories.json');
    const ids = await resp.json();
    return Promise.all(
      ids.slice(0, limit).map(async (id: number, i: number) => {
        const item = await (await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`)).json();
        return { rank: i + 1, title: item.title, score: item.score, url: item.url ?? '' };
      })
    );
  },
});
Save to
~/.opencli/clis/<site>/<command>.ts
→ immediately available as
opencli <site> <command>
.
typescript
// ~/.opencli/clis/hn/top.ts
import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'hn',
  name: 'top',
  description: 'Top Hacker News stories',
  domain: 'news.ycombinator.com',
  strategy: Strategy.PUBLIC,
  browser: false,
  args: [{ name: 'limit', type: 'int', default: 5 }],
  columns: ['rank', 'title', 'score', 'url'],
  func: async (_page, kwargs) => {
    const limit = Math.min(Math.max(1, kwargs.limit ?? 5), 50);
    const resp = await fetch('https://hacker-news.firebaseio.com/v0/topstories.json');
    const ids = await resp.json();
    return Promise.all(
      ids.slice(0, limit).map(async (id: number, i: number) => {
        const item = await (await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`)).json();
        return { rank: i + 1, title: item.title, score: item.score, url: item.url ?? '' };
      })
    );
  },
});
保存到
~/.opencli/clis/<site>/<command>.ts
→ 立即可通过
opencli <site> <command>
调用。

Strategy Guide

策略指南

StrategyWhenbrowser:
Strategy.PUBLIC
Public API, no auth
false
Strategy.COOKIE
Needs login cookies
true
Strategy.UI
Direct DOM interaction
true
Always prefer API over UI — if you discovered an API during browsing, use
fetch()
directly.
策略使用场景browser: 取值
Strategy.PUBLIC
公开API,无需鉴权
false
Strategy.COOKIE
需要登录Cookie
true
Strategy.UI
直接DOM交互
true
始终优先选择API而非UI —— 如果浏览过程中发现了API,直接使用
fetch()

Tips

小贴士

  1. Always
    state
    first
    — never guess element indices, always inspect first
  2. Sessions persist — browser stays open between commands, no need to re-open
  3. Use
    eval
    for data extraction
    eval "JSON.stringify(...)"
    is faster than multiple
    get
    calls
  4. Use
    network
    to find APIs
    — JSON APIs are more reliable than DOM scraping
  5. Alias:
    opencli op
    is shorthand for
    opencli browser
  1. 始终先运行
    state
    —— 绝对不要猜测元素索引,务必先检查
  2. 会话会持久化 —— 命令间隙浏览器保持打开,无需重复打开
  3. eval
    做数据提取
    ——
    eval "JSON.stringify(...)"
    比多次
    get
    调用更快
  4. network
    查找API
    —— JSON API比DOM爬取可靠性更高
  5. 别名
    opencli op
    opencli browser
    的简写

Common Pitfalls

常见陷阱

  1. form.submit()
    fails in automation
    — Don't use
    form.submit()
    or
    eval
    to submit forms. Navigate directly to the search URL instead:
    bash
    # BAD: form.submit() often silently fails
    opencli browser eval "document.querySelector('form').submit()"
    # GOOD: construct the URL and navigate
    opencli browser open "https://github.com/search?q=opencli&type=repositories"
  2. GitHub DOM changes frequently — Prefer
    data-testid
    attributes when available; they are more stable than class names or tag structure.
  3. SPA pages need
    wait
    before extraction
    — After
    open
    or
    click
    on single-page apps, the DOM isn't ready immediately. Always
    wait selector
    or
    wait text
    before
    eval
    .
  4. Use
    state
    before clicking
    — Run
    opencli browser state
    to inspect available interactive elements and their indices. Never guess indices from memory.
  5. evaluate
    runs in browser context
    page.evaluate()
    in adapters executes inside the browser. Node.js APIs (
    fs
    ,
    path
    ,
    process
    ) are NOT available. Use
    fetch()
    for network calls, DOM APIs for page data.
  6. Backticks in
    page.evaluate
    break JSON storage
    — When writing adapters that will be stored/transported as JSON, avoid template literals inside
    page.evaluate
    . Use string concatenation or function-style evaluate:
    typescript
    // BAD: template literal backticks break when adapter is in JSON
    page.evaluate(`document.querySelector("${selector}")`)
    // GOOD: function-style evaluate
    page.evaluate((sel) => document.querySelector(sel), selector)
  1. 自动化中
    form.submit()
    会失败
    —— 不要使用
    form.submit()
    eval
    提交表单,直接跳转到搜索URL即可:
    bash
    # 反面示例:form.submit()经常静默失败
    opencli browser eval "document.querySelector('form').submit()"
    # 优秀示例:构造URL直接跳转
    opencli browser open "https://github.com/search?q=opencli&type=repositories"
  2. GitHub DOM变更频繁 —— 优先使用可用的
    data-testid
    属性,它们比类名或标签结构更稳定
  3. SPA页面提取前需要
    wait
    —— 在单页应用上执行
    open
    click
    后,DOM不会立即准备就绪。
    eval
    前务必先执行
    wait selector
    wait text
  4. 点击前先运行
    state
    —— 执行
    opencli browser state
    检查可用的交互元素及其索引,绝对不要凭记忆猜测索引
  5. evaluate
    在浏览器上下文运行
    —— 适配器中的
    page.evaluate()
    在浏览器内部执行,无法使用Node.js API(
    fs
    path
    process
    )。网络调用使用
    fetch()
    ,页面数据获取使用DOM API
  6. page.evaluate
    中的反引号会破坏JSON存储
    —— 编写需要以JSON存储/传输的适配器时,避免在
    page.evaluate
    中使用模板字面量,使用字符串拼接或函数式evaluate:
    typescript
    // 反面示例:适配器存储为JSON时,模板字面量的反引号会被破坏
    page.evaluate(`document.querySelector("${selector}")`)
    // 优秀示例:函数式evaluate
    page.evaluate((sel) => document.querySelector(sel), selector)

Troubleshooting

故障排查

ErrorFix
"Browser not connected"Run
opencli doctor
"attach failed: chrome-extension://"Disable 1Password temporarily
Element not found
opencli browser scroll down && opencli browser state
Stale indices after page changeRun
opencli browser state
again to get fresh indices
错误修复方案
"Browser not connected"运行
opencli doctor
"attach failed: chrome-extension://"临时禁用1Password
元素未找到执行
opencli browser scroll down && opencli browser state
页面变更后索引过期重新运行
opencli browser state
获取最新索引