@jackwener/opencli 1.0.3 → 1.0.5
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/.github/workflows/build-extension.yml +21 -3
- package/.github/workflows/docs.yml +52 -0
- package/README.md +28 -28
- package/README.zh-CN.md +28 -28
- package/dist/browser/cdp.d.ts +16 -1
- package/dist/browser/cdp.js +124 -80
- package/dist/browser/daemon-client.d.ts +3 -1
- package/dist/browser/daemon-client.js +4 -0
- package/dist/browser/dom-helpers.d.ts +20 -0
- package/dist/browser/dom-helpers.js +109 -0
- package/dist/browser/mcp.d.ts +1 -0
- package/dist/browser/mcp.js +10 -5
- package/dist/browser/page.d.ts +7 -0
- package/dist/browser/page.js +37 -100
- package/dist/browser.test.js +7 -0
- package/dist/build-manifest.js +3 -1
- package/dist/build-manifest.test.js +34 -0
- package/dist/capabilityRouting.d.ts +2 -0
- package/dist/capabilityRouting.js +30 -0
- package/dist/capabilityRouting.test.d.ts +1 -0
- package/dist/capabilityRouting.test.js +42 -0
- package/dist/chaoxing.test.js +11 -4
- package/dist/cli-manifest.json +635 -1
- package/dist/cli.js +48 -8
- package/dist/clis/antigravity/serve.d.ts +14 -0
- package/dist/clis/antigravity/serve.js +263 -0
- package/dist/clis/bilibili/download.js +4 -14
- package/dist/clis/boss/resume.d.ts +1 -0
- package/dist/clis/boss/resume.js +249 -0
- package/dist/clis/hf/top.d.ts +1 -0
- package/dist/clis/hf/top.js +119 -0
- package/dist/clis/jike/comment.d.ts +1 -0
- package/dist/clis/jike/comment.js +107 -0
- package/dist/clis/jike/create.d.ts +1 -0
- package/dist/clis/jike/create.js +106 -0
- package/dist/clis/jike/feed.d.ts +1 -0
- package/dist/clis/jike/feed.js +67 -0
- package/dist/clis/jike/like.d.ts +1 -0
- package/dist/clis/jike/like.js +61 -0
- package/dist/clis/jike/notifications.d.ts +1 -0
- package/dist/clis/jike/notifications.js +169 -0
- package/dist/clis/jike/post.yaml +58 -0
- package/dist/clis/jike/repost.d.ts +1 -0
- package/dist/clis/jike/repost.js +103 -0
- package/dist/clis/jike/search.d.ts +1 -0
- package/dist/clis/jike/search.js +67 -0
- package/dist/clis/jike/shared.d.ts +19 -0
- package/dist/clis/jike/shared.js +25 -0
- package/dist/clis/jike/topic.yaml +52 -0
- package/dist/clis/jike/user.yaml +51 -0
- package/dist/clis/smzdm/search.js +28 -39
- package/dist/clis/stackoverflow/bounties.yaml +29 -0
- package/dist/clis/stackoverflow/hot.yaml +28 -0
- package/dist/clis/stackoverflow/search.yaml +32 -0
- package/dist/clis/stackoverflow/unanswered.yaml +28 -0
- package/dist/clis/twitter/download.js +6 -16
- package/dist/clis/xiaohongshu/download.js +3 -3
- package/dist/clis/zhihu/download.js +3 -3
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.js +16 -0
- package/dist/download/index.d.ts +12 -8
- package/dist/download/index.js +11 -3
- package/dist/download/index.test.d.ts +1 -0
- package/dist/download/index.test.js +14 -0
- package/dist/engine.js +5 -5
- package/dist/explore.d.ts +1 -0
- package/dist/explore.js +3 -3
- package/dist/generate.js +1 -0
- package/dist/interceptor.js +3 -2
- package/dist/output.d.ts +1 -0
- package/dist/output.js +3 -1
- package/dist/pipeline/executor.test.js +1 -0
- package/dist/pipeline/steps/download.js +14 -18
- package/dist/registry.d.ts +1 -0
- package/dist/registry.js +5 -2
- package/dist/runtime.d.ts +4 -1
- package/dist/runtime.js +2 -2
- package/dist/types.d.ts +12 -0
- package/dist/verify.d.ts +6 -1
- package/dist/verify.js +54 -2
- package/docs/.vitepress/config.mts +193 -0
- package/docs/adapters/browser/apple-podcasts.md +28 -0
- package/docs/adapters/browser/bbc.md +26 -0
- package/docs/adapters/browser/bilibili.md +38 -0
- package/docs/adapters/browser/boss.md +28 -0
- package/docs/adapters/browser/coupang.md +28 -0
- package/docs/adapters/browser/ctrip.md +27 -0
- package/docs/adapters/browser/github.md +26 -0
- package/docs/adapters/browser/hackernews.md +26 -0
- package/docs/adapters/browser/linkedin.md +27 -0
- package/docs/adapters/browser/reddit.md +41 -0
- package/docs/adapters/browser/reuters.md +27 -0
- package/docs/adapters/browser/smzdm.md +27 -0
- package/docs/adapters/browser/twitter.md +47 -0
- package/docs/adapters/browser/v2ex.md +32 -0
- package/docs/adapters/browser/weibo.md +27 -0
- package/docs/adapters/browser/xiaohongshu.md +32 -0
- package/docs/adapters/browser/xiaoyuzhou.md +28 -0
- package/docs/adapters/browser/xueqiu.md +32 -0
- package/docs/adapters/browser/yahoo-finance.md +26 -0
- package/docs/adapters/browser/youtube.md +29 -0
- package/docs/adapters/browser/zhihu.md +30 -0
- package/docs/adapters/desktop/antigravity.md +46 -0
- package/docs/adapters/desktop/chatgpt.md +43 -0
- package/docs/adapters/desktop/chatwise.md +38 -0
- package/docs/adapters/desktop/codex.md +32 -0
- package/docs/adapters/desktop/cursor.md +33 -0
- package/docs/adapters/desktop/discord.md +28 -0
- package/docs/adapters/desktop/feishu.md +20 -0
- package/docs/adapters/desktop/neteasemusic.md +31 -0
- package/docs/adapters/desktop/notion.md +29 -0
- package/docs/adapters/desktop/wechat.md +28 -0
- package/docs/adapters/index.md +49 -0
- package/docs/advanced/cdp.md +103 -0
- package/docs/advanced/download.md +63 -0
- package/docs/advanced/electron.md +125 -0
- package/docs/advanced/remote-chrome.md +72 -0
- package/docs/developer/ai-workflow.md +66 -0
- package/docs/developer/architecture.md +90 -0
- package/docs/developer/contributing.md +136 -0
- package/docs/developer/testing.md +237 -0
- package/docs/developer/ts-adapter.md +87 -0
- package/docs/developer/yaml-adapter.md +108 -0
- package/docs/guide/browser-bridge.md +38 -0
- package/docs/guide/getting-started.md +56 -0
- package/docs/guide/installation.md +37 -0
- package/docs/guide/troubleshooting.md +56 -0
- package/docs/index.md +35 -0
- package/docs/zh/adapters/index.md +5 -0
- package/docs/zh/advanced/cdp.md +3 -0
- package/docs/zh/developer/contributing.md +24 -0
- package/docs/zh/guide/browser-bridge.md +25 -0
- package/docs/zh/guide/getting-started.md +40 -0
- package/docs/zh/guide/installation.md +37 -0
- package/docs/zh/index.md +29 -0
- package/extension/dist/background.js +92 -52
- package/extension/package-lock.json +1156 -0
- package/extension/src/background.test.ts +151 -0
- package/extension/src/background.ts +122 -51
- package/extension/src/protocol.ts +3 -1
- package/package.json +7 -3
- package/src/browser/cdp.ts +154 -82
- package/src/browser/daemon-client.ts +7 -1
- package/src/browser/dom-helpers.ts +116 -0
- package/src/browser/mcp.ts +14 -6
- package/src/browser/page.ts +45 -100
- package/src/browser.test.ts +10 -0
- package/src/build-manifest.test.ts +36 -0
- package/src/build-manifest.ts +2 -1
- package/src/capabilityRouting.test.ts +47 -0
- package/src/capabilityRouting.ts +28 -0
- package/src/chaoxing.test.ts +12 -4
- package/src/cli.ts +30 -8
- package/src/clis/antigravity/serve.ts +329 -0
- package/src/clis/bilibili/download.ts +4 -15
- package/src/clis/boss/resume.ts +262 -0
- package/src/clis/hf/top.ts +141 -0
- package/src/clis/jike/comment.ts +113 -0
- package/src/clis/jike/create.ts +113 -0
- package/src/clis/jike/feed.ts +74 -0
- package/src/clis/jike/like.ts +65 -0
- package/src/clis/jike/notifications.ts +185 -0
- package/src/clis/jike/post.yaml +58 -0
- package/src/clis/jike/repost.ts +114 -0
- package/src/clis/jike/search.ts +74 -0
- package/src/clis/jike/shared.ts +36 -0
- package/src/clis/jike/topic.yaml +52 -0
- package/src/clis/jike/user.yaml +51 -0
- package/src/clis/smzdm/search.ts +30 -39
- package/src/clis/stackoverflow/bounties.yaml +29 -0
- package/src/clis/stackoverflow/hot.yaml +28 -0
- package/src/clis/stackoverflow/search.yaml +32 -0
- package/src/clis/stackoverflow/unanswered.yaml +28 -0
- package/src/clis/twitter/download.ts +6 -17
- package/src/clis/xiaohongshu/download.ts +3 -3
- package/src/clis/zhihu/download.ts +3 -3
- package/src/doctor.ts +18 -2
- package/src/download/index.test.ts +16 -0
- package/src/download/index.ts +22 -4
- package/src/engine.ts +4 -4
- package/src/explore.ts +4 -4
- package/src/generate.ts +1 -0
- package/src/interceptor.ts +3 -2
- package/src/output.ts +3 -1
- package/src/pipeline/executor.test.ts +1 -0
- package/src/pipeline/steps/download.ts +14 -17
- package/src/registry.ts +6 -2
- package/src/runtime.ts +3 -2
- package/src/types.ts +9 -0
- package/src/verify.ts +64 -3
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing to OpenCLI.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. Fork & clone
|
|
9
|
+
git clone git@github.com:<your-username>/opencli.git
|
|
10
|
+
cd opencli
|
|
11
|
+
|
|
12
|
+
# 2. Install dependencies
|
|
13
|
+
npm install
|
|
14
|
+
|
|
15
|
+
# 3. Build
|
|
16
|
+
npm run build
|
|
17
|
+
|
|
18
|
+
# 4. Run a few checks
|
|
19
|
+
npx tsc --noEmit
|
|
20
|
+
npx vitest run src/
|
|
21
|
+
|
|
22
|
+
# 5. Link globally (optional, for testing `opencli` command)
|
|
23
|
+
npm link
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Adding a New Site Adapter
|
|
27
|
+
|
|
28
|
+
This is the most common type of contribution. Start with YAML when possible, and use TypeScript only when you need browser-side logic or multi-step flows.
|
|
29
|
+
|
|
30
|
+
### YAML Adapter (Recommended for data-fetching commands)
|
|
31
|
+
|
|
32
|
+
Create a file like `src/clis/<site>/<command>.yaml`:
|
|
33
|
+
|
|
34
|
+
::: v-pre
|
|
35
|
+
```yaml
|
|
36
|
+
site: mysite
|
|
37
|
+
name: trending
|
|
38
|
+
description: Trending posts on MySite
|
|
39
|
+
domain: www.mysite.com
|
|
40
|
+
strategy: public # public | cookie | header
|
|
41
|
+
browser: false # true if browser session is needed
|
|
42
|
+
|
|
43
|
+
args:
|
|
44
|
+
limit:
|
|
45
|
+
type: int
|
|
46
|
+
default: 20
|
|
47
|
+
description: Number of items
|
|
48
|
+
|
|
49
|
+
pipeline:
|
|
50
|
+
- fetch:
|
|
51
|
+
url: https://api.mysite.com/trending
|
|
52
|
+
|
|
53
|
+
- map:
|
|
54
|
+
rank: ${{ index + 1 }}
|
|
55
|
+
title: ${{ item.title }}
|
|
56
|
+
score: ${{ item.score }}
|
|
57
|
+
url: ${{ item.url }}
|
|
58
|
+
|
|
59
|
+
- limit: ${{ args.limit }}
|
|
60
|
+
|
|
61
|
+
columns: [rank, title, score, url]
|
|
62
|
+
```
|
|
63
|
+
:::
|
|
64
|
+
|
|
65
|
+
See [`hackernews/top.yaml`](https://github.com/jackwener/opencli/blob/main/src/clis/hackernews/top.yaml) for a real example.
|
|
66
|
+
|
|
67
|
+
### TypeScript Adapter (For complex browser interactions)
|
|
68
|
+
|
|
69
|
+
Create a file like `src/clis/<site>/<command>.ts`:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { cli, Strategy } from '../../registry.js';
|
|
73
|
+
|
|
74
|
+
cli({
|
|
75
|
+
site: 'mysite',
|
|
76
|
+
name: 'search',
|
|
77
|
+
description: 'Search MySite',
|
|
78
|
+
domain: 'www.mysite.com',
|
|
79
|
+
strategy: Strategy.COOKIE,
|
|
80
|
+
args: [
|
|
81
|
+
{ name: 'query', required: true, help: 'Search query' },
|
|
82
|
+
{ name: 'limit', type: 'int', default: 10, help: 'Max results' },
|
|
83
|
+
],
|
|
84
|
+
columns: ['title', 'url', 'date'],
|
|
85
|
+
|
|
86
|
+
func: async (page, kwargs) => {
|
|
87
|
+
const { query, limit = 10 } = kwargs;
|
|
88
|
+
// ... browser automation logic
|
|
89
|
+
return data.slice(0, Number(limit)).map((item: any) => ({
|
|
90
|
+
title: item.title,
|
|
91
|
+
url: item.url,
|
|
92
|
+
date: item.created_at,
|
|
93
|
+
}));
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Validate Your Adapter
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
opencli validate # Validate YAML syntax and schema
|
|
102
|
+
opencli <site> <command> --limit 3 -f json # Test your command
|
|
103
|
+
opencli <site> <command> -v # Verbose mode for debugging
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Code Style
|
|
107
|
+
|
|
108
|
+
- **TypeScript strict mode** — avoid `any` where possible.
|
|
109
|
+
- **ES Modules** — use `.js` extensions in imports (TypeScript output).
|
|
110
|
+
- **Naming**: `kebab-case` for files, `camelCase` for variables/functions, `PascalCase` for types/classes.
|
|
111
|
+
- **No default exports** — use named exports.
|
|
112
|
+
|
|
113
|
+
## Commit Convention
|
|
114
|
+
|
|
115
|
+
We use [Conventional Commits](https://www.conventionalcommits.org/):
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
feat(twitter): add thread command
|
|
119
|
+
fix(browser): handle CDP timeout gracefully
|
|
120
|
+
docs: update CONTRIBUTING.md
|
|
121
|
+
test(reddit): add e2e test for save command
|
|
122
|
+
chore: bump vitest to v4
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Submitting a Pull Request
|
|
126
|
+
|
|
127
|
+
1. Create a feature branch: `git checkout -b feat/mysite-trending`
|
|
128
|
+
2. Make your changes and add tests when relevant
|
|
129
|
+
3. Run the checks:
|
|
130
|
+
```bash
|
|
131
|
+
npx tsc --noEmit # Type check
|
|
132
|
+
npx vitest run src/ # Unit tests
|
|
133
|
+
opencli validate # YAML validation (if applicable)
|
|
134
|
+
```
|
|
135
|
+
4. Commit using conventional commit format
|
|
136
|
+
5. Push and open a PR
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Testing Guide
|
|
2
|
+
|
|
3
|
+
> 面向开发者和 AI Agent 的测试参考手册。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [测试架构](#测试架构)
|
|
8
|
+
- [当前覆盖范围](#当前覆盖范围)
|
|
9
|
+
- [本地运行测试](#本地运行测试)
|
|
10
|
+
- [如何添加新测试](#如何添加新测试)
|
|
11
|
+
- [CI/CD 流水线](#cicd-流水线)
|
|
12
|
+
- [浏览器模式](#浏览器模式)
|
|
13
|
+
- [站点兼容性](#站点兼容性)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 测试架构
|
|
18
|
+
|
|
19
|
+
测试分为三层,全部使用 **vitest** 运行:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
tests/
|
|
23
|
+
├── e2e/ # E2E 集成测试(子进程运行真实 CLI)
|
|
24
|
+
│ ├── helpers.ts # runCli() 共享工具
|
|
25
|
+
│ ├── public-commands.test.ts # 公开 API 命令(无需浏览器)
|
|
26
|
+
│ ├── browser-public.test.ts # 浏览器命令(公开数据)
|
|
27
|
+
│ ├── browser-auth.test.ts # 需登录命令(graceful failure 测试)
|
|
28
|
+
│ ├── management.test.ts # 管理命令(list, validate, verify, help)
|
|
29
|
+
│ └── output-formats.test.ts # 输出格式(json/yaml/csv/md)
|
|
30
|
+
├── smoke/ # 烟雾测试(仅定时 / 手动触发)
|
|
31
|
+
│ └── api-health.test.ts # 外部 API 可用性检测
|
|
32
|
+
src/
|
|
33
|
+
├── *.test.ts # 单元测试(已有 8 个)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| 层 | 位置 | 运行方式 | 用途 |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| 单元测试 | `src/**/*.test.ts` | `npx vitest run src/` | 内部模块逻辑 |
|
|
39
|
+
| E2E 测试 | `tests/e2e/*.test.ts` | `npx vitest run tests/e2e/` | 真实 CLI 命令执行 |
|
|
40
|
+
| 烟雾测试 | `tests/smoke/*.test.ts` | `npx vitest run tests/smoke/` | 外部 API 健康 |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 当前覆盖范围
|
|
45
|
+
|
|
46
|
+
### 单元测试(8 个文件)
|
|
47
|
+
|
|
48
|
+
| 文件 | 覆盖内容 |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `browser.test.ts` | JSON-RPC、tab 管理、extension/standalone 模式切换 |
|
|
51
|
+
| `engine.test.ts` | 命令发现与执行 |
|
|
52
|
+
| `registry.test.ts` | 命令注册与策略分配 |
|
|
53
|
+
| `output.test.ts` | 输出格式渲染 |
|
|
54
|
+
| `doctor.test.ts` | Token 诊断 |
|
|
55
|
+
| `coupang.test.ts` | 数据归一化 |
|
|
56
|
+
| `pipeline/template.test.ts` | 模板表达式求值 |
|
|
57
|
+
| `pipeline/transform.test.ts` | 数据变换步骤 |
|
|
58
|
+
|
|
59
|
+
### E2E 测试(~52 个用例)
|
|
60
|
+
|
|
61
|
+
| 文件 | 覆盖站点/功能 | 测试数 |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `public-commands.test.ts` | hackernews/top, v2ex/hot, v2ex/latest, v2ex/topic | 5 |
|
|
64
|
+
| `browser-public.test.ts` | bbc, bilibili×3, weibo, zhihu×2, reddit×2, twitter, xueqiu×2, reuters, youtube, smzdm, boss, ctrip, coupang, xiaohongshu, yahoo-finance, v2ex/daily | 21 |
|
|
65
|
+
| `browser-auth.test.ts` | bilibili/me,dynamic,favorite,history,following + twitter/bookmarks,timeline,notifications + v2ex/me,notifications + xueqiu/feed,watchlist + xiaohongshu/feed,notifications | 14 |
|
|
66
|
+
| `management.test.ts` | list×5 格式, validate×3 级别, verify, --version, --help, unknown cmd | 12 |
|
|
67
|
+
| `output-formats.test.ts` | json, yaml, csv, md 格式验证 | 5 |
|
|
68
|
+
|
|
69
|
+
### 烟雾测试
|
|
70
|
+
|
|
71
|
+
公开 API 可用性(hackernews, v2ex×2, v2ex/topic)+ 全站点注册完整性检查。
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 本地运行测试
|
|
76
|
+
|
|
77
|
+
### 前置条件
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm ci # 安装依赖
|
|
81
|
+
npm run build # 编译(E2E 测试需要 dist/main.js)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 运行命令
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# 全部单元测试
|
|
88
|
+
npx vitest run src/
|
|
89
|
+
|
|
90
|
+
# 全部 E2E 测试(会真实调用外部 API)
|
|
91
|
+
npx vitest run tests/e2e/
|
|
92
|
+
|
|
93
|
+
# 单个测试文件
|
|
94
|
+
npx vitest run tests/e2e/management.test.ts
|
|
95
|
+
|
|
96
|
+
# 全部测试(单元 + E2E)
|
|
97
|
+
npx vitest run
|
|
98
|
+
|
|
99
|
+
# 烟雾测试
|
|
100
|
+
npx vitest run tests/smoke/
|
|
101
|
+
|
|
102
|
+
# watch 模式(开发时推荐)
|
|
103
|
+
npx vitest src/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 浏览器命令本地测试须知
|
|
107
|
+
|
|
108
|
+
- opencli 通过 Browser Bridge 扩展连接已运行的 Chrome 浏览器
|
|
109
|
+
- `browser-public.test.ts` 使用 `tryBrowserCommand()`,站点反爬导致空数据时 warn + pass
|
|
110
|
+
- `browser-auth.test.ts` 验证 **graceful failure**(不 crash 不 hang 即通过)
|
|
111
|
+
- 如需测试完整登录态,保持 Chrome 登录态并安装 Browser Bridge 扩展,手动跑对应测试
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 如何添加新测试
|
|
116
|
+
|
|
117
|
+
### 新增 YAML Adapter(如 `src/clis/producthunt/trending.yaml`)
|
|
118
|
+
|
|
119
|
+
1. **无需额外操作**:`validate` 测试会自动覆盖 YAML 结构验证
|
|
120
|
+
2. 根据 adapter 类型,在对应文件加一个 `it()` block:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// 如果 browser: false(公开 API)→ tests/e2e/public-commands.test.ts
|
|
124
|
+
it('producthunt trending returns data', async () => {
|
|
125
|
+
const { stdout, code } = await runCli(['producthunt', 'trending', '--limit', '3', '-f', 'json']);
|
|
126
|
+
expect(code).toBe(0);
|
|
127
|
+
const data = parseJsonOutput(stdout);
|
|
128
|
+
expect(Array.isArray(data)).toBe(true);
|
|
129
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
130
|
+
expect(data[0]).toHaveProperty('title');
|
|
131
|
+
}, 30_000);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// 如果 browser: true 但可公开访问 → tests/e2e/browser-public.test.ts
|
|
136
|
+
it('producthunt trending returns data', async () => {
|
|
137
|
+
const data = await tryBrowserCommand(['producthunt', 'trending', '--limit', '3', '-f', 'json']);
|
|
138
|
+
expectDataOrSkip(data, 'producthunt trending');
|
|
139
|
+
}, 60_000);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// 如果 browser: true 且需登录 → tests/e2e/browser-auth.test.ts
|
|
144
|
+
it('producthunt me fails gracefully without login', async () => {
|
|
145
|
+
await expectGracefulAuthFailure(['producthunt', 'me', '-f', 'json'], 'producthunt me');
|
|
146
|
+
}, 60_000);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 新增管理命令(如 `opencli export`)
|
|
150
|
+
|
|
151
|
+
在 `tests/e2e/management.test.ts` 添加测试。
|
|
152
|
+
|
|
153
|
+
### 新增内部模块
|
|
154
|
+
|
|
155
|
+
在 `src/` 下对应位置创建 `*.test.ts`。
|
|
156
|
+
|
|
157
|
+
### 决策流程图
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
新增功能 → 是内部模块? → 是 → src/ 下加 *.test.ts
|
|
161
|
+
↓ 否
|
|
162
|
+
是 CLI 命令? → browser: false? → tests/e2e/public-commands.test.ts
|
|
163
|
+
↓ true
|
|
164
|
+
公开数据? → tests/e2e/browser-public.test.ts
|
|
165
|
+
↓ 需登录
|
|
166
|
+
tests/e2e/browser-auth.test.ts
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## CI/CD 流水线
|
|
172
|
+
|
|
173
|
+
### ci.yml(主流水线)
|
|
174
|
+
|
|
175
|
+
| Job | 触发条件 | 内容 |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| **build** | push/PR to main,dev | typecheck + build |
|
|
178
|
+
| **unit-test** | push/PR to main,dev | 单元测试,2 shard 并行 |
|
|
179
|
+
| **smoke-test** | 每周一 08:00 UTC / 手动 | xvfb + real Chrome,外部 API 健康检查 |
|
|
180
|
+
|
|
181
|
+
### e2e-headed.yml(E2E 测试)
|
|
182
|
+
|
|
183
|
+
| Job | 触发条件 | 内容 |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| **e2e-headed** | push/PR to main,dev | xvfb + real Chrome,全部 E2E 测试 |
|
|
186
|
+
|
|
187
|
+
E2E 使用 `browser-actions/setup-chrome` 安装真实 Chrome,配合 `xvfb-run` 提供虚拟显示器,以 headed 模式运行浏览器。
|
|
188
|
+
|
|
189
|
+
### Sharding
|
|
190
|
+
|
|
191
|
+
单元测试使用 vitest 内置 shard:
|
|
192
|
+
|
|
193
|
+
::: v-pre
|
|
194
|
+
```yaml
|
|
195
|
+
strategy:
|
|
196
|
+
matrix:
|
|
197
|
+
shard: [1, 2]
|
|
198
|
+
steps:
|
|
199
|
+
- run: npx vitest run src/ --shard=${{ matrix.shard }}/2
|
|
200
|
+
```
|
|
201
|
+
:::
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 浏览器模式
|
|
206
|
+
|
|
207
|
+
opencli 通过 Browser Bridge 扩展连接浏览器:
|
|
208
|
+
|
|
209
|
+
| 条件 | 模式 | 使用场景 |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| 扩展已安装 | Extension 模式 | 本地用户,连接已登录的 Chrome |
|
|
212
|
+
| 扩展未安装 | CLI 报错提示安装 | 需要安装 Browser Bridge 扩展 |
|
|
213
|
+
|
|
214
|
+
CI 中使用 `OPENCLI_BROWSER_EXECUTABLE_PATH` 指定真实 Chrome 路径:
|
|
215
|
+
|
|
216
|
+
::: v-pre
|
|
217
|
+
```yaml
|
|
218
|
+
env:
|
|
219
|
+
OPENCLI_BROWSER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
|
|
220
|
+
```
|
|
221
|
+
:::
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 站点兼容性
|
|
226
|
+
|
|
227
|
+
在 GitHub Actions 美国 runner 上,部分站点因地域限制或登录要求返回空数据。E2E 测试对这些站点使用 warn + pass 策略,不影响 CI 绿灯。
|
|
228
|
+
|
|
229
|
+
| 站点 | CI 状态 | 限制原因 |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| hackernews, bbc, v2ex | ✅ 返回数据 | 无限制 |
|
|
232
|
+
| yahoo-finance | ✅ 返回数据 | 无限制 |
|
|
233
|
+
| bilibili, zhihu, weibo, xiaohongshu | ⚠️ 空数据 | 地域限制(中国站点) |
|
|
234
|
+
| reddit, twitter, youtube | ⚠️ 空数据 | 需登录或 cookie |
|
|
235
|
+
| smzdm, boss, ctrip, coupang, xueqiu | ⚠️ 空数据 | 地域限制 / 需登录 |
|
|
236
|
+
|
|
237
|
+
> 使用 self-hosted runner(国内服务器)可解决地域限制问题。
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# TypeScript Adapter Guide
|
|
2
|
+
|
|
3
|
+
Use TypeScript adapters when you need browser-side logic, multi-step flows, DOM manipulation, or complex data extraction that goes beyond simple API fetching.
|
|
4
|
+
|
|
5
|
+
## Basic Structure
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { cli, Strategy } from '../../registry.js';
|
|
9
|
+
|
|
10
|
+
cli({
|
|
11
|
+
site: 'mysite',
|
|
12
|
+
name: 'search',
|
|
13
|
+
description: 'Search MySite',
|
|
14
|
+
domain: 'www.mysite.com',
|
|
15
|
+
strategy: Strategy.COOKIE, // PUBLIC | COOKIE | HEADER
|
|
16
|
+
args: [
|
|
17
|
+
{ name: 'query', required: true, help: 'Search query' },
|
|
18
|
+
{ name: 'limit', type: 'int', default: 10, help: 'Max results' },
|
|
19
|
+
],
|
|
20
|
+
columns: ['title', 'url', 'date'],
|
|
21
|
+
|
|
22
|
+
func: async (page, kwargs) => {
|
|
23
|
+
const { query, limit = 10 } = kwargs;
|
|
24
|
+
|
|
25
|
+
// Navigate and extract data
|
|
26
|
+
await page.goto('https://www.mysite.com');
|
|
27
|
+
|
|
28
|
+
const data = await page.evaluate(`
|
|
29
|
+
(async () => {
|
|
30
|
+
const res = await fetch('/api/search?q=${encodeURIComponent(String(query))}', {
|
|
31
|
+
credentials: 'include'
|
|
32
|
+
});
|
|
33
|
+
return (await res.json()).results;
|
|
34
|
+
})()
|
|
35
|
+
`);
|
|
36
|
+
|
|
37
|
+
return data.slice(0, Number(limit)).map((item: any) => ({
|
|
38
|
+
title: item.title,
|
|
39
|
+
url: item.url,
|
|
40
|
+
date: item.created_at,
|
|
41
|
+
}));
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Strategy Types
|
|
47
|
+
|
|
48
|
+
| Strategy | Constant | Use Case |
|
|
49
|
+
|----------|----------|----------|
|
|
50
|
+
| Public | `Strategy.PUBLIC` | No auth needed |
|
|
51
|
+
| Cookie | `Strategy.COOKIE` | Browser session cookies |
|
|
52
|
+
| Header | `Strategy.HEADER` | Custom headers/tokens |
|
|
53
|
+
|
|
54
|
+
## The `page` Object
|
|
55
|
+
|
|
56
|
+
The `page` parameter provides browser interaction methods:
|
|
57
|
+
|
|
58
|
+
- `page.goto(url)` — Navigate to a URL
|
|
59
|
+
- `page.evaluate(script)` — Execute JavaScript in the page context
|
|
60
|
+
- `page.waitForSelector(selector)` — Wait for an element
|
|
61
|
+
- `page.click(selector)` — Click an element
|
|
62
|
+
- `page.type(selector, text)` — Type text into an input
|
|
63
|
+
|
|
64
|
+
## The `kwargs` Object
|
|
65
|
+
|
|
66
|
+
Contains parsed CLI arguments as key-value pairs. Always destructure with defaults:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const { query, limit = 10, format = 'json' } = kwargs;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## AI-Assisted Development
|
|
73
|
+
|
|
74
|
+
Use the AI workflow tools to accelerate adapter creation:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Discover APIs and page structure
|
|
78
|
+
opencli explore https://example.com --site mysite
|
|
79
|
+
|
|
80
|
+
# Auto-generate adapter from explore artifacts
|
|
81
|
+
opencli synthesize mysite
|
|
82
|
+
|
|
83
|
+
# One-shot: explore → synthesize → register
|
|
84
|
+
opencli generate https://example.com --goal "trending"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
See [AI Workflow](/developer/ai-workflow) for the complete guide.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# YAML Adapter Guide
|
|
2
|
+
|
|
3
|
+
YAML adapters are the recommended way to add new commands when the site offers a straightforward API. They use a declarative pipeline approach — no TypeScript required.
|
|
4
|
+
|
|
5
|
+
## Basic Structure
|
|
6
|
+
|
|
7
|
+
::: v-pre
|
|
8
|
+
```yaml
|
|
9
|
+
site: mysite # Site identifier
|
|
10
|
+
name: trending # Command name (opencli mysite trending)
|
|
11
|
+
description: ... # Help text
|
|
12
|
+
domain: www.mysite.com
|
|
13
|
+
strategy: public # public | cookie | header
|
|
14
|
+
browser: false # true if browser session is needed
|
|
15
|
+
|
|
16
|
+
args: # CLI arguments
|
|
17
|
+
limit:
|
|
18
|
+
type: int
|
|
19
|
+
default: 20
|
|
20
|
+
description: Number of items
|
|
21
|
+
|
|
22
|
+
pipeline: # Data processing steps
|
|
23
|
+
- fetch:
|
|
24
|
+
url: https://api.mysite.com/trending
|
|
25
|
+
|
|
26
|
+
- map:
|
|
27
|
+
rank: ${{ index + 1 }}
|
|
28
|
+
title: ${{ item.title }}
|
|
29
|
+
|
|
30
|
+
- limit: ${{ args.limit }}
|
|
31
|
+
|
|
32
|
+
columns: [rank, title, score, url]
|
|
33
|
+
```
|
|
34
|
+
:::
|
|
35
|
+
|
|
36
|
+
## Pipeline Steps
|
|
37
|
+
|
|
38
|
+
### `fetch`
|
|
39
|
+
Fetch data from a URL. Supports template expressions for dynamic URLs.
|
|
40
|
+
|
|
41
|
+
::: v-pre
|
|
42
|
+
```yaml
|
|
43
|
+
- fetch:
|
|
44
|
+
url: https://api.example.com/search?q=${{ args.query }}
|
|
45
|
+
headers:
|
|
46
|
+
Accept: application/json
|
|
47
|
+
```
|
|
48
|
+
:::
|
|
49
|
+
|
|
50
|
+
### `map`
|
|
51
|
+
|
|
52
|
+
::: v-pre
|
|
53
|
+
Transform each item in the result array. Use `${{ item.xxx }}` for field access and `${{ index }}` for position.
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
- map:
|
|
57
|
+
rank: ${{ index + 1 }}
|
|
58
|
+
title: ${{ item.title }}
|
|
59
|
+
url: https://example.com${{ item.path }}
|
|
60
|
+
```
|
|
61
|
+
:::
|
|
62
|
+
|
|
63
|
+
### `limit`
|
|
64
|
+
Truncate results to N items.
|
|
65
|
+
|
|
66
|
+
::: v-pre
|
|
67
|
+
```yaml
|
|
68
|
+
- limit: ${{ args.limit }}
|
|
69
|
+
```
|
|
70
|
+
:::
|
|
71
|
+
|
|
72
|
+
### `filter`
|
|
73
|
+
Filter items by condition.
|
|
74
|
+
|
|
75
|
+
::: v-pre
|
|
76
|
+
```yaml
|
|
77
|
+
- filter: ${{ item.score > 100 }}
|
|
78
|
+
```
|
|
79
|
+
:::
|
|
80
|
+
|
|
81
|
+
### `download`
|
|
82
|
+
Download media files.
|
|
83
|
+
|
|
84
|
+
::: v-pre
|
|
85
|
+
```yaml
|
|
86
|
+
- download:
|
|
87
|
+
url: ${{ item.imageUrl }}
|
|
88
|
+
dir: ./downloads
|
|
89
|
+
filename: ${{ item.title | sanitize }}.jpg
|
|
90
|
+
```
|
|
91
|
+
:::
|
|
92
|
+
|
|
93
|
+
## Template Expressions
|
|
94
|
+
|
|
95
|
+
::: v-pre
|
|
96
|
+
Use `${{ ... }}` for dynamic values:
|
|
97
|
+
|
|
98
|
+
| Expression | Description |
|
|
99
|
+
|-----------|-------------|
|
|
100
|
+
| `${{ args.limit }}` | CLI argument |
|
|
101
|
+
| `${{ item.title }}` | Current item field |
|
|
102
|
+
| `${{ index }}` | Current index (0-based) |
|
|
103
|
+
| `${{ item.x \| sanitize }}` | Pipe filters |
|
|
104
|
+
:::
|
|
105
|
+
|
|
106
|
+
## Real Example
|
|
107
|
+
|
|
108
|
+
See [`src/clis/hackernews/top.yaml`](https://github.com/jackwener/opencli/blob/main/src/clis/hackernews/top.yaml).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Browser Bridge Setup
|
|
2
|
+
|
|
3
|
+
> **⚠️ Important**: Browser commands reuse your Chrome login session. You must be logged into the target website in Chrome before running commands.
|
|
4
|
+
|
|
5
|
+
OpenCLI connects to your browser through a lightweight **Browser Bridge** Chrome Extension + micro-daemon (zero config, auto-start).
|
|
6
|
+
|
|
7
|
+
## Extension Installation
|
|
8
|
+
|
|
9
|
+
### Method 1: Download Pre-built Release (Recommended)
|
|
10
|
+
|
|
11
|
+
1. Go to the GitHub [Releases page](https://github.com/jackwener/opencli/releases) and download the latest `opencli-extension.zip` or `opencli-extension.crx`.
|
|
12
|
+
2. Open `chrome://extensions` and enable **Developer mode** (top-right toggle).
|
|
13
|
+
3. Drag and drop the `.crx` file or the unzipped folder into the extensions page.
|
|
14
|
+
|
|
15
|
+
### Method 2: Load Unpacked Source (For Developers)
|
|
16
|
+
|
|
17
|
+
1. Open `chrome://extensions` and enable **Developer mode**.
|
|
18
|
+
2. Click **Load unpacked** and select the `extension/` directory from the repository.
|
|
19
|
+
|
|
20
|
+
## Verification
|
|
21
|
+
|
|
22
|
+
That's it! The daemon auto-starts when you run any browser command. No tokens, no manual configuration.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
opencli doctor # Check extension + daemon connectivity
|
|
26
|
+
opencli doctor --live # Also test live browser commands
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## How It Works
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
┌─────────────┐ WebSocket ┌──────────────┐ Chrome API ┌─────────┐
|
|
33
|
+
│ opencli │ ◄──────────────► │ micro-daemon │ ◄──────────────► │ Chrome │
|
|
34
|
+
│ (Node.js) │ localhost:19825 │ (auto-start) │ Extension │ Browser │
|
|
35
|
+
└─────────────┘ └──────────────┘ └─────────┘
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The daemon manages the WebSocket connection between your CLI commands and the Chrome extension. The extension executes JavaScript in the context of web pages, with access to the logged-in session.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
> **Make any website or Electron App your CLI.**
|
|
4
|
+
> Zero risk · Reuse Chrome login · AI-powered discovery · Browser + Desktop automation
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/@jackwener/opencli)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
[](https://github.com/jackwener/opencli/blob/main/LICENSE)
|
|
9
|
+
|
|
10
|
+
OpenCLI turns **any website** or **Electron app** into a command-line interface — Bilibili, Zhihu, 小红书, Twitter/X, Reddit, YouTube, Antigravity, and [many more](/adapters/) — powered by browser session reuse and AI-native discovery.
|
|
11
|
+
|
|
12
|
+
## Highlights
|
|
13
|
+
|
|
14
|
+
- **CLI All Electron** — CLI-ify apps like Antigravity Ultra! Now AI can control itself natively.
|
|
15
|
+
- **Account-safe** — Reuses Chrome's logged-in state; your credentials never leave the browser.
|
|
16
|
+
- **AI Agent ready** — `explore` discovers APIs, `synthesize` generates adapters, `cascade` finds auth strategies.
|
|
17
|
+
- **Self-healing setup** — `opencli setup` verifies Browser Bridge connectivity; `opencli doctor` diagnoses daemon, extension, and live browser connectivity.
|
|
18
|
+
- **Dynamic Loader** — Simply drop `.ts` or `.yaml` adapters into the `clis/` folder for auto-registration.
|
|
19
|
+
- **Dual-Engine Architecture** — Supports both YAML declarative data pipelines and robust browser runtime TypeScript injections.
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Install via npm
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g @jackwener/opencli
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Basic Usage
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
opencli list # See all commands
|
|
33
|
+
opencli hackernews top --limit 5 # Public API, no browser
|
|
34
|
+
opencli bilibili hot --limit 5 # Browser command
|
|
35
|
+
opencli zhihu hot -f json # JSON output
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Output Formats
|
|
39
|
+
|
|
40
|
+
All built-in commands support `--format` / `-f`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
opencli bilibili hot -f table # Default: rich terminal table
|
|
44
|
+
opencli bilibili hot -f json # JSON (pipe to jq or LLMs)
|
|
45
|
+
opencli bilibili hot -f yaml # YAML (human-readable)
|
|
46
|
+
opencli bilibili hot -f md # Markdown
|
|
47
|
+
opencli bilibili hot -f csv # CSV
|
|
48
|
+
opencli bilibili hot -v # Verbose: show pipeline debug
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Next Steps
|
|
52
|
+
|
|
53
|
+
- [Installation details](/guide/installation)
|
|
54
|
+
- [Browser Bridge setup](/guide/browser-bridge)
|
|
55
|
+
- [All available adapters](/adapters/)
|
|
56
|
+
- [For developers / AI agents](/developer/contributing)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
- **Node.js**: >= 20.0.0
|
|
6
|
+
- **Chrome** running and logged into the target site (for browser commands)
|
|
7
|
+
|
|
8
|
+
## Install via npm (Recommended)
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g @jackwener/opencli
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Install from Source
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
git clone git@github.com:jackwener/opencli.git
|
|
18
|
+
cd opencli
|
|
19
|
+
npm install
|
|
20
|
+
npm run build
|
|
21
|
+
npm link # Link binary globally
|
|
22
|
+
opencli list # Now you can use it anywhere!
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Update
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g @jackwener/opencli@latest
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Verify Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
opencli --version # Check version
|
|
35
|
+
opencli list # List all commands
|
|
36
|
+
opencli doctor # Diagnose connectivity
|
|
37
|
+
```
|