bb-browser 0.4.5 → 0.5.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 +82 -34
- package/README.zh-CN.md +82 -34
- package/dist/chunk-AHGAQEFO.js +110 -0
- package/dist/chunk-AHGAQEFO.js.map +1 -0
- package/dist/{chunk-H7M4J4CW.js → chunk-D4HDZEJT.js} +2 -18
- package/dist/chunk-D4HDZEJT.js.map +1 -0
- package/dist/chunk-DBJBHYC7.js +21 -0
- package/dist/chunk-DBJBHYC7.js.map +1 -0
- package/dist/cli.js +1756 -1484
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +2 -1
- package/dist/daemon.js.map +1 -1
- package/dist/jq-HHMLHEPA.js +9 -0
- package/dist/jq-HHMLHEPA.js.map +1 -0
- package/dist/mcp.js +5 -3
- package/dist/mcp.js.map +1 -1
- package/extension/background.js +80 -0
- package/extension/background.js.map +1 -1
- package/extension/dist/background.js +80 -0
- package/extension/dist/background.js.map +1 -1
- package/extension/dist/manifest.json +2 -2
- package/extension/manifest.json +2 -2
- package/package.json +1 -1
- package/dist/chunk-H7M4J4CW.js.map +0 -1
package/README.md
CHANGED
|
@@ -16,33 +16,33 @@
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
You're already logged into
|
|
19
|
+
You're already logged into Twitter, Reddit, YouTube, Zhihu, Bilibili, LinkedIn, GitHub — bb-browser lets AI agents **use that directly**.
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
bb-browser site
|
|
23
|
-
bb-browser site
|
|
24
|
-
bb-browser site
|
|
25
|
-
bb-browser site
|
|
26
|
-
bb-browser site
|
|
27
|
-
bb-browser site
|
|
28
|
-
bb-browser site
|
|
29
|
-
bb-browser site
|
|
22
|
+
bb-browser site twitter/search "AI agent" # search tweets
|
|
23
|
+
bb-browser site zhihu/hot # trending on Zhihu
|
|
24
|
+
bb-browser site arxiv/search "transformer" # search papers
|
|
25
|
+
bb-browser site eastmoney/stock "茅台" # real-time stock quote
|
|
26
|
+
bb-browser site boss/search "AI engineer" # search jobs
|
|
27
|
+
bb-browser site wikipedia/summary "Python" # Wikipedia summary
|
|
28
|
+
bb-browser site youtube/transcript VIDEO_ID # full transcript
|
|
29
|
+
bb-browser site stackoverflow/search "async" # search SO questions
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
**
|
|
32
|
+
**103 commands across 36 platforms.** All using your real browser's login state. [Full list →](https://github.com/epiral/bb-sites)
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## The idea
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
The internet was built for browsers. AI agents have been trying to access it through APIs — but 99% of websites don't offer one.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
bb-browser flips this: **instead of forcing websites to provide machine interfaces, let machines use the human interface directly.** The adapter runs `eval` inside your browser tab, calls `fetch()` with your cookies, or invokes the page's own webpack modules. The website thinks it's you. Because it **is** you.
|
|
39
39
|
|
|
40
40
|
| | Playwright / Selenium | Scraping libs | bb-browser |
|
|
41
41
|
|---|---|---|---|
|
|
42
42
|
| Browser | Headless, isolated | No browser | Your real Chrome |
|
|
43
43
|
| Login state | None, must re-login | Cookie extraction | Already there |
|
|
44
44
|
| Anti-bot | Detected easily | Cat-and-mouse | Invisible — it IS the user |
|
|
45
|
-
|
|
|
45
|
+
| Complex auth | Can't replicate | Reverse engineer | Page handles it itself |
|
|
46
46
|
|
|
47
47
|
## Quick Start
|
|
48
48
|
|
|
@@ -60,9 +60,9 @@ npm install -g bb-browser
|
|
|
60
60
|
### Use
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
bb-browser site update
|
|
64
|
-
bb-browser site
|
|
65
|
-
bb-browser site zhihu/hot
|
|
63
|
+
bb-browser site update # pull community adapters
|
|
64
|
+
bb-browser site recommend # see which adapters match your browsing habits
|
|
65
|
+
bb-browser site zhihu/hot # go
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
### MCP (Claude Code / Cursor)
|
|
@@ -78,30 +78,61 @@ bb-browser site zhihu/hot # go
|
|
|
78
78
|
}
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
##
|
|
81
|
+
## 36 platforms, 103 commands
|
|
82
82
|
|
|
83
83
|
Community-driven via [bb-sites](https://github.com/epiral/bb-sites). One JS file per command.
|
|
84
84
|
|
|
85
|
-
|
|
|
86
|
-
|
|
87
|
-
| **
|
|
88
|
-
| **Twitter/X
|
|
89
|
-
| **
|
|
90
|
-
| **
|
|
91
|
-
| **
|
|
92
|
-
| **
|
|
93
|
-
| **
|
|
94
|
-
| **
|
|
95
|
-
| **
|
|
96
|
-
| **
|
|
97
|
-
|
|
98
|
-
|
|
85
|
+
| Category | Platforms | Commands |
|
|
86
|
+
|----------|-----------|----------|
|
|
87
|
+
| **Search** | Google, Baidu, Bing, DuckDuckGo, Sogou WeChat | search |
|
|
88
|
+
| **Social** | Twitter/X, Reddit, Weibo, Xiaohongshu, Jike, LinkedIn, Hupu | search, feed, thread, user, notifications, hot |
|
|
89
|
+
| **News** | BBC, Reuters, 36kr, Toutiao, Eastmoney | headlines, search, newsflash, hot |
|
|
90
|
+
| **Dev** | GitHub, StackOverflow, HackerNews, CSDN, cnblogs, V2EX, Dev.to, npm, PyPI, arXiv | search, issues, repo, top, thread, package |
|
|
91
|
+
| **Video** | YouTube, Bilibili | search, video, transcript, popular, comments, feed |
|
|
92
|
+
| **Entertainment** | Douban, IMDb, Genius, Qidian | movie, search, top250 |
|
|
93
|
+
| **Finance** | Xueqiu, Eastmoney, Yahoo Finance | stock, hot stocks, feed, watchlist, search |
|
|
94
|
+
| **Jobs** | BOSS Zhipin, LinkedIn | search, detail, profile |
|
|
95
|
+
| **Knowledge** | Wikipedia, Zhihu, Open Library | search, summary, hot, question |
|
|
96
|
+
| **Shopping** | SMZDM | search deals |
|
|
97
|
+
| **Tools** | Youdao, GSMArena, Product Hunt, Ctrip | translate, phone specs, trending products |
|
|
98
|
+
|
|
99
|
+
## 10 minutes to add any website
|
|
99
100
|
|
|
100
101
|
```bash
|
|
101
102
|
bb-browser guide # full tutorial
|
|
102
103
|
```
|
|
103
104
|
|
|
104
|
-
Tell your AI agent "turn XX website into a CLI"
|
|
105
|
+
Tell your AI agent: *"turn XX website into a CLI"*. It reads the guide, reverse-engineers the API with `network --with-body`, writes the adapter, tests it, and submits a PR. All autonomously.
|
|
106
|
+
|
|
107
|
+
Three tiers of adapter complexity:
|
|
108
|
+
|
|
109
|
+
| Tier | Auth method | Example | Time |
|
|
110
|
+
|------|-------------|---------|------|
|
|
111
|
+
| **1** | Cookie (fetch directly) | Reddit, GitHub, V2EX | ~1 min |
|
|
112
|
+
| **2** | Bearer + CSRF token | Twitter, Zhihu | ~3 min |
|
|
113
|
+
| **3** | Webpack injection / Pinia store | Twitter search, Xiaohongshu | ~10 min |
|
|
114
|
+
|
|
115
|
+
We tested this: **20 AI agents ran in parallel, each independently reverse-engineered a website and produced a working adapter.** The marginal cost of adding a new website to the agent-accessible internet is approaching zero.
|
|
116
|
+
|
|
117
|
+
## What this means for AI agents
|
|
118
|
+
|
|
119
|
+
Without bb-browser, an AI agent's world is: **files + terminal + a few APIs with keys.**
|
|
120
|
+
|
|
121
|
+
With bb-browser: **files + terminal + the entire internet.**
|
|
122
|
+
|
|
123
|
+
An agent can now, in under a minute:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Cross-platform research on any topic
|
|
127
|
+
bb-browser site arxiv/search "retrieval augmented generation"
|
|
128
|
+
bb-browser site twitter/search "RAG"
|
|
129
|
+
bb-browser site github search rag-framework
|
|
130
|
+
bb-browser site stackoverflow/search "RAG implementation"
|
|
131
|
+
bb-browser site zhihu/search "RAG"
|
|
132
|
+
bb-browser site 36kr/newsflash
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Six platforms, six dimensions, structured JSON. Faster and broader than any human researcher.
|
|
105
136
|
|
|
106
137
|
## Also a full browser automation tool
|
|
107
138
|
|
|
@@ -116,7 +147,24 @@ bb-browser network requests --with-body --json # capture traffic
|
|
|
116
147
|
bb-browser screenshot # take screenshot
|
|
117
148
|
```
|
|
118
149
|
|
|
119
|
-
All commands support `--json` output and `--tab <id>` for concurrent multi-tab operations.
|
|
150
|
+
All commands support `--json` output, `--jq <expr>` for inline filtering, and `--tab <id>` for concurrent multi-tab operations.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
bb-browser site xueqiu/hot-stock 5 --jq '.items[] | {name, changePercent}'
|
|
154
|
+
# {"name":"云天化","changePercent":"2.08%"}
|
|
155
|
+
# {"name":"东芯股份","changePercent":"-7.60%"}
|
|
156
|
+
|
|
157
|
+
bb-browser site info xueqiu/stock # view adapter args, example, domain
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Daemon configuration
|
|
161
|
+
|
|
162
|
+
The daemon binds to `localhost:19824` by default. You can customize the host with `--host`:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
bb-browser daemon --host 127.0.0.1 # IPv4 only (fix macOS IPv6 issues)
|
|
166
|
+
bb-browser daemon --host 0.0.0.0 # listen on all interfaces (for Tailscale / ZeroTier remote access)
|
|
167
|
+
```
|
|
120
168
|
|
|
121
169
|
## Architecture
|
|
122
170
|
|
package/README.zh-CN.md
CHANGED
|
@@ -16,33 +16,33 @@
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
你已经登录了微博、知乎、B站、小红书、Twitter、GitHub、LinkedIn — bb-browser 让 AI Agent **直接用你的登录态**。
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
bb-browser site
|
|
23
|
-
bb-browser site
|
|
24
|
-
bb-browser site
|
|
25
|
-
bb-browser site
|
|
26
|
-
bb-browser site
|
|
27
|
-
bb-browser site
|
|
28
|
-
bb-browser site
|
|
29
|
-
bb-browser site
|
|
22
|
+
bb-browser site twitter/search "AI agent" # 搜索推文
|
|
23
|
+
bb-browser site zhihu/hot # 知乎热榜
|
|
24
|
+
bb-browser site arxiv/search "transformer" # 搜论文
|
|
25
|
+
bb-browser site eastmoney/stock "茅台" # 实时股票行情
|
|
26
|
+
bb-browser site boss/search "AI 工程师" # 搜职位
|
|
27
|
+
bb-browser site wikipedia/summary "Python" # 维基百科摘要
|
|
28
|
+
bb-browser site youtube/transcript VIDEO_ID # YouTube 字幕全文
|
|
29
|
+
bb-browser site stackoverflow/search "async" # 搜 StackOverflow
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
**
|
|
32
|
+
**36 个平台,103 个命令,全部用你真实浏览器的登录态。** [完整列表 →](https://github.com/epiral/bb-sites)
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## 核心理念
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
互联网是为浏览器构建的。AI Agent 一直试图通过 API 访问它 — 但 99% 的网站不提供 API。
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
bb-browser 翻转了这个逻辑:**不是让网站适配机器,而是让机器使用人的界面。** adapter 在你的浏览器 tab 里跑 `eval`,用你的 Cookie 调 `fetch()`,或者直接调用页面的 webpack 模块。网站以为是你在操作。因为**就是你**。
|
|
39
39
|
|
|
40
40
|
| | Playwright / Selenium | 爬虫库 | bb-browser |
|
|
41
41
|
|---|---|---|---|
|
|
42
42
|
| 浏览器 | 无头、隔离环境 | 没有浏览器 | 你的真实 Chrome |
|
|
43
43
|
| 登录态 | 没有,要重新登录 | 偷 Cookie | 已经在了 |
|
|
44
44
|
| 反爬检测 | 容易被识别 | 猫鼠游戏 | 无法检测 — 它就是用户 |
|
|
45
|
-
|
|
|
45
|
+
| 复杂鉴权 | 无法复制 | 需要逆向 | 页面自己处理 |
|
|
46
46
|
|
|
47
47
|
## 快速开始
|
|
48
48
|
|
|
@@ -60,9 +60,9 @@ npm install -g bb-browser
|
|
|
60
60
|
### 使用
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
bb-browser site update
|
|
64
|
-
bb-browser site
|
|
65
|
-
bb-browser site zhihu/hot
|
|
63
|
+
bb-browser site update # 拉取社区适配器
|
|
64
|
+
bb-browser site recommend # 看看哪些和你的浏览习惯匹配
|
|
65
|
+
bb-browser site zhihu/hot # 开搞
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
### MCP 接入(Claude Code / Cursor)
|
|
@@ -78,30 +78,61 @@ bb-browser site zhihu/hot # 开搞
|
|
|
78
78
|
}
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
##
|
|
81
|
+
## 36 个平台,103 个命令
|
|
82
82
|
|
|
83
83
|
社区驱动,通过 [bb-sites](https://github.com/epiral/bb-sites) 维护。每个命令一个 JS 文件。
|
|
84
84
|
|
|
85
|
-
|
|
|
86
|
-
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
98
|
-
|
|
85
|
+
| 类别 | 平台 | 命令 |
|
|
86
|
+
|------|------|------|
|
|
87
|
+
| **搜索引擎** | Google、百度、Bing、DuckDuckGo、搜狗微信 | search |
|
|
88
|
+
| **社交媒体** | Twitter/X、Reddit、微博、小红书、即刻、LinkedIn、虎扑 | search、feed、thread、user、notifications、hot |
|
|
89
|
+
| **新闻资讯** | BBC、Reuters、36氪、今日头条、东方财富 | headlines、search、newsflash、hot |
|
|
90
|
+
| **技术开发** | GitHub、StackOverflow、HackerNews、CSDN、博客园、V2EX、Dev.to、npm、PyPI、arXiv | search、issues、repo、top、thread、package |
|
|
91
|
+
| **视频平台** | YouTube、B站 | search、video、transcript、popular、comments、feed |
|
|
92
|
+
| **影音娱乐** | 豆瓣、IMDb、Genius、起点中文网 | movie、search、top250 |
|
|
93
|
+
| **财经股票** | 雪球、东方财富、Yahoo Finance | stock、hot-stock、feed、watchlist、search |
|
|
94
|
+
| **求职招聘** | BOSS直聘、LinkedIn | search、detail、profile |
|
|
95
|
+
| **知识百科** | Wikipedia、知乎、Open Library | search、summary、hot、question |
|
|
96
|
+
| **消费购物** | 什么值得买 | search |
|
|
97
|
+
| **实用工具** | 有道翻译、GSMArena、Product Hunt、携程 | translate、手机参数、热门产品 |
|
|
98
|
+
|
|
99
|
+
## 10 分钟,CLI 化任何网站
|
|
99
100
|
|
|
100
101
|
```bash
|
|
101
102
|
bb-browser guide # 完整教程
|
|
102
103
|
```
|
|
103
104
|
|
|
104
|
-
跟你的 AI Agent
|
|
105
|
+
跟你的 AI Agent 说:*「帮我把 XX 网站 CLI 化」*。它会读 guide,用 `network --with-body` 抓包逆向,写 adapter,测试,然后提 PR 到社区仓库。全程自动。
|
|
106
|
+
|
|
107
|
+
三种 adapter 复杂度:
|
|
108
|
+
|
|
109
|
+
| 层级 | 认证方式 | 代表 | 耗时 |
|
|
110
|
+
|------|----------|------|------|
|
|
111
|
+
| **Tier 1** | Cookie(直接 fetch) | Reddit、GitHub、V2EX | ~1 分钟 |
|
|
112
|
+
| **Tier 2** | Bearer + CSRF token | Twitter、知乎 | ~3 分钟 |
|
|
113
|
+
| **Tier 3** | Webpack 注入 / Pinia store | Twitter 搜索、小红书 | ~10 分钟 |
|
|
114
|
+
|
|
115
|
+
实测:**20 个 AI Agent 并发运行,每个独立逆向一个网站并产出可用的 adapter。** 将一个新网站纳入 Agent 可访问范围的边际成本趋近于零。
|
|
116
|
+
|
|
117
|
+
## 对 AI Agent 意味着什么
|
|
118
|
+
|
|
119
|
+
没有 bb-browser,AI Agent 的世界是:**文件系统 + 终端 + 少数有 API key 的服务。**
|
|
120
|
+
|
|
121
|
+
有了 bb-browser:**文件系统 + 终端 + 整个互联网。**
|
|
122
|
+
|
|
123
|
+
一个 Agent 现在可以在一分钟内:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# 跨平台调研任何话题
|
|
127
|
+
bb-browser site arxiv/search "retrieval augmented generation"
|
|
128
|
+
bb-browser site twitter/search "RAG"
|
|
129
|
+
bb-browser site github search rag-framework
|
|
130
|
+
bb-browser site stackoverflow/search "RAG implementation"
|
|
131
|
+
bb-browser site zhihu/search "RAG"
|
|
132
|
+
bb-browser site 36kr/newsflash
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
六个平台,六个维度,结构化 JSON。比任何人类研究员都快、都广。
|
|
105
136
|
|
|
106
137
|
## 同时也是完整的浏览器自动化工具
|
|
107
138
|
|
|
@@ -116,7 +147,24 @@ bb-browser network requests --with-body --json # 抓包
|
|
|
116
147
|
bb-browser screenshot # 截图
|
|
117
148
|
```
|
|
118
149
|
|
|
119
|
-
所有命令支持 `--json`
|
|
150
|
+
所有命令支持 `--json` 输出、`--jq <expr>` 内联过滤、和 `--tab <id>` 多标签页并发操作。
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
bb-browser site xueqiu/hot-stock 5 --jq '.items[] | {name, changePercent}'
|
|
154
|
+
# {"name":"云天化","changePercent":"2.08%"}
|
|
155
|
+
# {"name":"东芯股份","changePercent":"-7.60%"}
|
|
156
|
+
|
|
157
|
+
bb-browser site info xueqiu/stock # 查看 adapter 参数、示例、域名
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Daemon 配置
|
|
161
|
+
|
|
162
|
+
Daemon 默认绑定 `localhost:19824`,可通过 `--host` 自定义监听地址:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
bb-browser daemon --host 127.0.0.1 # 仅 IPv4(解决 macOS IPv6 问题)
|
|
166
|
+
bb-browser daemon --host 0.0.0.0 # 监听所有网卡(用于 Tailscale / ZeroTier 跨机器访问)
|
|
167
|
+
```
|
|
120
168
|
|
|
121
169
|
## 架构
|
|
122
170
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// packages/cli/src/jq.ts
|
|
4
|
+
function splitTopLevel(input, separator) {
|
|
5
|
+
const parts = [];
|
|
6
|
+
let current = "";
|
|
7
|
+
let depth = 0;
|
|
8
|
+
let inString = false;
|
|
9
|
+
for (let i = 0; i < input.length; i++) {
|
|
10
|
+
const char = input[i];
|
|
11
|
+
const prev = input[i - 1];
|
|
12
|
+
if (char === '"' && prev !== "\\") inString = !inString;
|
|
13
|
+
if (!inString) {
|
|
14
|
+
if (char === "{" || char === "(" || char === "[") depth++;
|
|
15
|
+
if (char === "}" || char === ")" || char === "]") depth--;
|
|
16
|
+
if (depth === 0 && input.slice(i, i + separator.length) === separator) {
|
|
17
|
+
parts.push(current.trim());
|
|
18
|
+
current = "";
|
|
19
|
+
i += separator.length - 1;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
current += char;
|
|
24
|
+
}
|
|
25
|
+
if (current.trim()) parts.push(current.trim());
|
|
26
|
+
return parts;
|
|
27
|
+
}
|
|
28
|
+
function parseLiteral(value) {
|
|
29
|
+
const trimmed = value.trim();
|
|
30
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) return JSON.parse(trimmed);
|
|
31
|
+
if (trimmed === "true") return true;
|
|
32
|
+
if (trimmed === "false") return false;
|
|
33
|
+
if (trimmed === "null") return null;
|
|
34
|
+
return Number(trimmed);
|
|
35
|
+
}
|
|
36
|
+
function getField(value, field) {
|
|
37
|
+
return value !== null && typeof value === "object" ? value[field] : void 0;
|
|
38
|
+
}
|
|
39
|
+
function applySegment(inputs, expr) {
|
|
40
|
+
if (expr === ".") return inputs;
|
|
41
|
+
if (expr.startsWith("select(")) {
|
|
42
|
+
const match = expr.match(/^select\((.+?)\s*(==|>)\s*(.+)\)$/);
|
|
43
|
+
if (!match) throw new Error(`\u4E0D\u652F\u6301\u7684 jq \u8868\u8FBE\u5F0F: ${expr}`);
|
|
44
|
+
const [, leftExpr, op, rightExpr] = match;
|
|
45
|
+
const expected = parseLiteral(rightExpr);
|
|
46
|
+
return inputs.filter((item) => {
|
|
47
|
+
const left = applyExpression([item], leftExpr)[0];
|
|
48
|
+
return op === "==" ? left === expected : Number(left) > Number(expected);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (expr.startsWith("{") && expr.endsWith("}")) {
|
|
52
|
+
const body = expr.slice(1, -1).trim();
|
|
53
|
+
if (!body) return inputs.map(() => ({}));
|
|
54
|
+
const entries = splitTopLevel(body, ",");
|
|
55
|
+
return inputs.map((item) => {
|
|
56
|
+
const obj = {};
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const colon = entry.indexOf(":");
|
|
59
|
+
if (colon === -1) {
|
|
60
|
+
const key = entry.trim().replace(/^\./, "");
|
|
61
|
+
obj[key] = applyExpression([item], `.${key}`)[0];
|
|
62
|
+
} else {
|
|
63
|
+
const key = entry.slice(0, colon).trim();
|
|
64
|
+
const valueExpr = entry.slice(colon + 1).trim();
|
|
65
|
+
obj[key] = applyExpression([item], valueExpr)[0];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return obj;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (!expr.startsWith(".")) throw new Error(`\u4E0D\u652F\u6301\u7684 jq \u8868\u8FBE\u5F0F: ${expr}`);
|
|
72
|
+
let current = inputs;
|
|
73
|
+
let remaining = expr.slice(1);
|
|
74
|
+
while (remaining.length > 0) {
|
|
75
|
+
if (remaining.startsWith("[]")) {
|
|
76
|
+
current = current.flatMap((item) => Array.isArray(item) ? item : []);
|
|
77
|
+
remaining = remaining.slice(2);
|
|
78
|
+
} else if (remaining.startsWith("[")) {
|
|
79
|
+
const match = remaining.match(/^\[(-?\d+)\]/);
|
|
80
|
+
if (!match) throw new Error(`\u4E0D\u652F\u6301\u7684 jq \u8868\u8FBE\u5F0F: .${remaining}`);
|
|
81
|
+
const index = Number(match[1]);
|
|
82
|
+
current = current.map((item) => {
|
|
83
|
+
if (!Array.isArray(item)) return void 0;
|
|
84
|
+
return item[index >= 0 ? index : item.length + index];
|
|
85
|
+
});
|
|
86
|
+
remaining = remaining.slice(match[0].length);
|
|
87
|
+
} else if (remaining.startsWith(".")) {
|
|
88
|
+
remaining = remaining.slice(1);
|
|
89
|
+
} else {
|
|
90
|
+
const match = remaining.match(/^([A-Za-z_][A-Za-z0-9_]*)/);
|
|
91
|
+
if (!match) throw new Error(`\u4E0D\u652F\u6301\u7684 jq \u8868\u8FBE\u5F0F: .${remaining}`);
|
|
92
|
+
const field = match[1];
|
|
93
|
+
current = current.map((item) => getField(item, field));
|
|
94
|
+
remaining = remaining.slice(field.length);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return current;
|
|
98
|
+
}
|
|
99
|
+
function applyExpression(inputs, expression) {
|
|
100
|
+
const segments = splitTopLevel(expression.trim(), "|");
|
|
101
|
+
return segments.reduce((current, segment) => applySegment(current, segment.trim()), inputs);
|
|
102
|
+
}
|
|
103
|
+
function applyJq(data, expression) {
|
|
104
|
+
return applyExpression([data], expression).filter((item) => item !== void 0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
applyJq
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=chunk-AHGAQEFO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../packages/cli/src/jq.ts"],"sourcesContent":["function splitTopLevel(input: string, separator: string): string[] {\n const parts: string[] = [];\n let current = \"\";\n let depth = 0;\n let inString = false;\n\n for (let i = 0; i < input.length; i++) {\n const char = input[i];\n const prev = input[i - 1];\n if (char === '\"' && prev !== '\\\\') inString = !inString;\n if (!inString) {\n if (char === '{' || char === '(' || char === '[') depth++;\n if (char === '}' || char === ')' || char === ']') depth--;\n if (depth === 0 && input.slice(i, i + separator.length) === separator) {\n parts.push(current.trim());\n current = \"\";\n i += separator.length - 1;\n continue;\n }\n }\n current += char;\n }\n\n if (current.trim()) parts.push(current.trim());\n return parts;\n}\n\nfunction parseLiteral(value: string): unknown {\n const trimmed = value.trim();\n if (trimmed.startsWith('\"') && trimmed.endsWith('\"')) return JSON.parse(trimmed);\n if (trimmed === \"true\") return true;\n if (trimmed === \"false\") return false;\n if (trimmed === \"null\") return null;\n return Number(trimmed);\n}\n\nfunction getField(value: unknown, field: string): unknown {\n return value !== null && typeof value === \"object\" ? (value as Record<string, unknown>)[field] : undefined;\n}\n\nfunction applySegment(inputs: unknown[], expr: string): unknown[] {\n if (expr === \".\") return inputs;\n if (expr.startsWith(\"select(\")) {\n const match = expr.match(/^select\\((.+?)\\s*(==|>)\\s*(.+)\\)$/);\n if (!match) throw new Error(`不支持的 jq 表达式: ${expr}`);\n const [, leftExpr, op, rightExpr] = match;\n const expected = parseLiteral(rightExpr);\n return inputs.filter((item) => {\n const left = applyExpression([item], leftExpr)[0];\n return op === \"==\" ? left === expected : Number(left) > Number(expected);\n });\n }\n if (expr.startsWith(\"{\") && expr.endsWith(\"}\")) {\n const body = expr.slice(1, -1).trim();\n if (!body) return inputs.map(() => ({}));\n const entries = splitTopLevel(body, \",\");\n return inputs.map((item) => {\n const obj: Record<string, unknown> = {};\n for (const entry of entries) {\n const colon = entry.indexOf(\":\");\n if (colon === -1) {\n const key = entry.trim().replace(/^\\./, \"\");\n obj[key] = applyExpression([item], `.${key}`)[0];\n } else {\n const key = entry.slice(0, colon).trim();\n const valueExpr = entry.slice(colon + 1).trim();\n obj[key] = applyExpression([item], valueExpr)[0];\n }\n }\n return obj;\n });\n }\n if (!expr.startsWith(\".\")) throw new Error(`不支持的 jq 表达式: ${expr}`);\n\n let current = inputs;\n let remaining = expr.slice(1);\n while (remaining.length > 0) {\n if (remaining.startsWith(\"[]\")) {\n current = current.flatMap((item) => Array.isArray(item) ? item : []);\n remaining = remaining.slice(2);\n } else if (remaining.startsWith(\"[\")) {\n const match = remaining.match(/^\\[(-?\\d+)\\]/);\n if (!match) throw new Error(`不支持的 jq 表达式: .${remaining}`);\n const index = Number(match[1]);\n current = current.map((item) => {\n if (!Array.isArray(item)) return undefined;\n return item[index >= 0 ? index : item.length + index];\n });\n remaining = remaining.slice(match[0].length);\n } else if (remaining.startsWith(\".\")) {\n remaining = remaining.slice(1);\n } else {\n const match = remaining.match(/^([A-Za-z_][A-Za-z0-9_]*)/);\n if (!match) throw new Error(`不支持的 jq 表达式: .${remaining}`);\n const field = match[1];\n current = current.map((item) => getField(item, field));\n remaining = remaining.slice(field.length);\n }\n }\n return current;\n}\n\nfunction applyExpression(inputs: unknown[], expression: string): unknown[] {\n const segments = splitTopLevel(expression.trim(), \"|\");\n return segments.reduce((current, segment) => applySegment(current, segment.trim()), inputs);\n}\n\nexport function applyJq(data: unknown, expression: string): unknown[] {\n return applyExpression([data], expression).filter((item) => item !== undefined);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -29,25 +29,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
mod
|
|
30
30
|
));
|
|
31
31
|
|
|
32
|
-
// packages/shared/dist/index.js
|
|
33
|
-
function generateId() {
|
|
34
|
-
return crypto.randomUUID();
|
|
35
|
-
}
|
|
36
|
-
var DAEMON_PORT = 19824;
|
|
37
|
-
var DAEMON_HOST = "localhost";
|
|
38
|
-
var DAEMON_BASE_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
39
|
-
var SSE_HEARTBEAT_INTERVAL = 15e3;
|
|
40
|
-
var COMMAND_TIMEOUT = 3e4;
|
|
41
|
-
|
|
42
32
|
export {
|
|
43
33
|
__commonJS,
|
|
44
34
|
__export,
|
|
45
|
-
__toESM
|
|
46
|
-
generateId,
|
|
47
|
-
DAEMON_PORT,
|
|
48
|
-
DAEMON_HOST,
|
|
49
|
-
DAEMON_BASE_URL,
|
|
50
|
-
SSE_HEARTBEAT_INTERVAL,
|
|
51
|
-
COMMAND_TIMEOUT
|
|
35
|
+
__toESM
|
|
52
36
|
};
|
|
53
|
-
//# sourceMappingURL=chunk-
|
|
37
|
+
//# sourceMappingURL=chunk-D4HDZEJT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// packages/shared/dist/index.js
|
|
4
|
+
function generateId() {
|
|
5
|
+
return crypto.randomUUID();
|
|
6
|
+
}
|
|
7
|
+
var DAEMON_PORT = 19824;
|
|
8
|
+
var DAEMON_HOST = "localhost";
|
|
9
|
+
var DAEMON_BASE_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
10
|
+
var SSE_HEARTBEAT_INTERVAL = 15e3;
|
|
11
|
+
var COMMAND_TIMEOUT = 3e4;
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
generateId,
|
|
15
|
+
DAEMON_PORT,
|
|
16
|
+
DAEMON_HOST,
|
|
17
|
+
DAEMON_BASE_URL,
|
|
18
|
+
SSE_HEARTBEAT_INTERVAL,
|
|
19
|
+
COMMAND_TIMEOUT
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=chunk-DBJBHYC7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../packages/shared/src/protocol.ts","../packages/shared/src/constants.ts"],"sourcesContent":["/**\n * CLI 与 Chrome Extension 之间的通信协议类型定义\n */\n\n/** 支持的操作类型 */\nexport type ActionType =\n | \"open\"\n | \"snapshot\"\n | \"click\"\n | \"hover\"\n | \"fill\"\n | \"type\"\n | \"check\"\n | \"uncheck\"\n | \"select\"\n | \"get\"\n | \"screenshot\"\n | \"close\"\n | \"wait\"\n | \"press\"\n | \"scroll\"\n | \"back\"\n | \"forward\"\n | \"refresh\"\n | \"eval\"\n | \"tab_list\"\n | \"tab_new\"\n | \"tab_select\"\n | \"tab_close\"\n | \"frame\"\n | \"frame_main\"\n | \"dialog\"\n | \"network\"\n | \"console\"\n | \"errors\"\n | \"trace\"\n | \"history\";\n\n/** 请求类型 */\nexport interface Request {\n /** 请求唯一标识 */\n id: string;\n /** 操作类型 */\n action: ActionType;\n /** 目标 URL(open 操作时必填) */\n url?: string;\n /** 元素引用(click, fill, get 操作时使用) */\n ref?: string;\n /** 输入文本(fill 操作时使用) */\n text?: string;\n /** 获取属性类型(get 操作时使用) */\n attribute?: string;\n /** 截图保存路径(screenshot 操作时使用) */\n path?: string;\n /** 是否只输出可交互元素(snapshot 命令使用) */\n interactive?: boolean;\n /** 移除空结构节点(snapshot 命令使用) */\n compact?: boolean;\n /** 限制树深度(snapshot 命令使用) */\n maxDepth?: number;\n /** JavaScript 代码(eval 命令使用) */\n script?: string;\n /** 选项值(select 命令使用) */\n value?: string;\n /** 标签页索引(tab_select, tab_close 命令使用) */\n index?: number;\n /** 标签页 ID(tab_select, tab_close 命令使用,优先于 index) */\n tabId?: number | string;\n /** CSS 选择器(frame 命令使用,定位 iframe) */\n selector?: string;\n /** dialog 响应类型(dialog 命令使用) */\n dialogResponse?: \"accept\" | \"dismiss\";\n /** prompt 对话框的输入文本(dialog accept 时可选) */\n promptText?: string;\n /** network 子命令:requests, route, unroute, clear */\n networkCommand?: \"requests\" | \"route\" | \"unroute\" | \"clear\";\n /** network route 选项 */\n routeOptions?: {\n abort?: boolean;\n body?: string;\n status?: number;\n headers?: Record<string, string>;\n };\n /** 过滤字符串(network requests, console 使用) */\n filter?: string;\n /** network requests 是否包含 body/headers */\n withBody?: boolean;\n /** console 子命令:get, clear */\n consoleCommand?: \"get\" | \"clear\";\n /** errors 子命令:get, clear */\n errorsCommand?: \"get\" | \"clear\";\n /** trace 子命令:start, stop, status */\n traceCommand?: \"start\" | \"stop\" | \"status\";\n /** history 子命令:search, domains */\n historyCommand?: \"search\" | \"domains\";\n /** 按键名(press 命令使用) */\n key?: string;\n /** 修饰键列表(press 命令使用) */\n modifiers?: string[];\n /** 滚动方向(scroll 命令使用) */\n direction?: string;\n /** 滚动距离(scroll 命令使用) */\n pixels?: number;\n /** 等待类型(wait 命令使用) */\n waitType?: string;\n /** 等待毫秒数(wait 命令使用) */\n ms?: number;\n}\n\n/** 元素引用信息 */\nexport interface RefInfo {\n /** CDP backendDOMNodeId(主定位方式) */\n backendDOMNodeId?: number;\n /** 元素的 XPath(向后兼容) */\n xpath?: string;\n /** 可访问性角色 */\n role: string;\n /** 可访问名称 */\n name?: string;\n /** 标签名 */\n tagName?: string;\n}\n\n/** 标签页信息 */\nexport interface TabInfo {\n /** 标签页在窗口中的索引(0-based) */\n index: number;\n /** 标签页 URL */\n url: string;\n /** 标签页标题 */\n title: string;\n /** 是否是当前活动标签页 */\n active: boolean;\n /** 标签页 ID */\n tabId: number;\n}\n\n/** Snapshot 命令返回的数据 */\nexport interface SnapshotData {\n /** 文本格式的可访问性树 */\n snapshot: string;\n /** 元素引用映射,key 为 ref ID */\n refs: Record<string, RefInfo>;\n}\n\n/** 网络请求信息 */\nexport interface NetworkRequestInfo {\n requestId: string;\n url: string;\n method: string;\n type: string;\n timestamp: number;\n status?: number;\n statusText?: string;\n failed?: boolean;\n failureReason?: string;\n requestHeaders?: Record<string, string>;\n requestBody?: string;\n requestBodyTruncated?: boolean;\n responseHeaders?: Record<string, string>;\n responseBody?: string;\n responseBodyBase64?: boolean;\n responseBodyTruncated?: boolean;\n mimeType?: string;\n bodyError?: string;\n}\n\n/** 控制台消息 */\nexport interface ConsoleMessageInfo {\n type: 'log' | 'info' | 'warn' | 'error' | 'debug';\n text: string;\n timestamp: number;\n url?: string;\n lineNumber?: number;\n}\n\n/** JS 错误信息 */\nexport interface JSErrorInfo {\n message: string;\n url?: string;\n lineNumber?: number;\n columnNumber?: number;\n stackTrace?: string;\n timestamp: number;\n}\n\n/** Trace 事件类型 - 录制用户操作 */\nexport interface TraceEvent {\n /** 事件类型 */\n type: 'click' | 'fill' | 'select' | 'check' | 'press' | 'scroll' | 'navigation';\n /** 时间戳 */\n timestamp: number;\n /** 事件发生时的页面 URL */\n url: string;\n \n /** 元素引用 - highlightIndex,可直接用于 @ref */\n ref?: number;\n /** 备用定位 - XPath */\n xpath?: string;\n /** CSS 选择器 */\n cssSelector?: string;\n \n /** 操作参数 - fill/select 的值 */\n value?: string;\n /** 操作参数 - press 的按键 */\n key?: string;\n /** 操作参数 - scroll 方向 */\n direction?: 'up' | 'down' | 'left' | 'right';\n /** 操作参数 - scroll 距离 */\n pixels?: number;\n /** 操作参数 - check/uncheck 状态 */\n checked?: boolean;\n \n /** 语义信息 - 元素角色 */\n elementRole?: string;\n /** 语义信息 - 元素名称 */\n elementName?: string;\n /** 语义信息 - 元素标签 */\n elementTag?: string;\n}\n\n/** Trace 录制状态 */\nexport interface TraceStatus {\n /** 是否正在录制 */\n recording: boolean;\n /** 已录制事件数量 */\n eventCount: number;\n /** 录制的标签页 ID */\n tabId?: number;\n}\n\n/** 响应数据 */\nexport interface ResponseData {\n /** 页面标题 */\n title?: string;\n /** 当前 URL */\n url?: string;\n /** Tab ID */\n tabId?: number;\n /** Snapshot 数据(snapshot 操作返回) */\n snapshotData?: SnapshotData;\n /** 获取的文本或属性值(get 操作返回) */\n value?: string;\n /** 截图路径(screenshot 操作返回) */\n screenshotPath?: string;\n /** 截图 data URL(screenshot 操作返回) */\n dataUrl?: string;\n /** eval 执行结果 */\n result?: unknown;\n /** 标签页列表(tab_list 命令返回) */\n tabs?: TabInfo[];\n /** 当前活动标签页索引(tab_list 命令返回) */\n activeIndex?: number;\n /** Frame 信息(frame 命令返回) */\n frameInfo?: {\n /** iframe 的 CSS 选择器 */\n selector?: string;\n /** iframe 的 name 属性 */\n name?: string;\n /** iframe 的 URL */\n url?: string;\n /** frame ID */\n frameId?: number;\n };\n /** dialog 信息(dialog 命令返回) */\n dialogInfo?: {\n /** 对话框类型:alert, confirm, prompt, beforeunload */\n type: string;\n /** 对话框消息 */\n message: string;\n /** 是否成功处理 */\n handled: boolean;\n };\n /** 网络请求列表(network requests 命令返回) */\n networkRequests?: NetworkRequestInfo[];\n /** 网络路由规则数量(network route/unroute 命令返回) */\n routeCount?: number;\n /** 控制台消息列表(console 命令返回) */\n consoleMessages?: ConsoleMessageInfo[];\n /** JS 错误列表(errors 命令返回) */\n jsErrors?: JSErrorInfo[];\n /** Trace 事件列表(trace stop 命令返回) */\n traceEvents?: TraceEvent[];\n /** Trace 录制状态(trace status 命令返回) */\n traceStatus?: TraceStatus;\n /** History 搜索结果 */\n historyItems?: Array<{\n url: string;\n title: string;\n visitCount: number;\n lastVisitTime: number;\n }>;\n /** History 域名聚合结果 */\n historyDomains?: Array<{\n domain: string;\n visits: number;\n titles: string[];\n }>;\n}\n\n/** 响应类型 */\nexport interface Response {\n /** 对应请求的 ID */\n id: string;\n /** 操作是否成功 */\n success: boolean;\n /** 成功时返回的数据 */\n data?: ResponseData;\n /** 失败时的错误信息 */\n error?: string;\n}\n\n/** SSE 事件类型 */\nexport type SSEEventType = \"connected\" | \"heartbeat\" | \"command\";\n\n/** SSE 事件数据 */\nexport interface SSEEvent {\n type: SSEEventType;\n data: unknown;\n}\n\n/** Daemon 状态 */\nexport interface DaemonStatus {\n running: boolean;\n extensionConnected: boolean;\n pendingRequests: number;\n uptime: number;\n}\n\n/**\n * 生成唯一请求 ID\n * @returns UUID v4 格式的字符串\n */\nexport function generateId(): string {\n return crypto.randomUUID();\n}\n","/**\n * bb-browser 共享常量\n */\n\n/** Daemon HTTP 服务端口 */\nexport const DAEMON_PORT = 19824;\n\n/** Daemon 主机地址 */\nexport const DAEMON_HOST = \"localhost\";\n\n/** Daemon 基础 URL */\nexport const DAEMON_BASE_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}`;\n\n/** SSE 心跳间隔(毫秒) - 15秒确保 MV3 Service Worker 不休眠 */\nexport const SSE_HEARTBEAT_INTERVAL = 15000; // 15 秒\n\n/** 命令执行超时时间(毫秒) */\nexport const COMMAND_TIMEOUT = 30000; // 30 秒\n\n/** SSE 重连延迟(毫秒) */\nexport const SSE_RECONNECT_DELAY = 3000; // 3 秒\n\n/** SSE 最大重连尝试次数 */\nexport const SSE_MAX_RECONNECT_ATTEMPTS = 5;\n"],"mappings":";;;AA6UO,SAAS,aAAqB;AACnC,SAAO,OAAO,WAAW;AAC3B;AC1UO,IAAM,cAAc;AAGpB,IAAM,cAAc;AAGpB,IAAM,kBAAkB,UAAU,WAAW,IAAI,WAAW;AAG5D,IAAM,yBAAyB;AAG/B,IAAM,kBAAkB;","names":[]}
|