@jackwener/opencli 0.5.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![Node.js Version](https://img.shields.io/node/v/@jackwener/opencli?style=flat-square)](https://nodejs.org)
10
10
  [![License](https://img.shields.io/npm/l/@jackwener/opencli?style=flat-square)](./LICENSE)
11
11
 
12
- A CLI tool that turns **any website** into a command-line interface. **57 commands** across **17 sites** — bilibili, zhihu, xiaohongshu, twitter, reddit, xueqiu, github, v2ex, hackernews, bbc, weibo, boss, yahoo-finance, reuters, smzdm, ctrip, youtube — powered by browser session reuse and AI-native discovery.
12
+ A CLI tool that turns **any website** into a command-line interface — bilibili, zhihu, xiaohongshu, twitter, reddit, and many more — powered by browser session reuse and AI-native discovery.
13
13
 
14
14
  ---
15
15
 
@@ -46,11 +46,21 @@ OpenCLI connects to your browser through the Playwright MCP Bridge extension.
46
46
  ### Playwright MCP Bridge Extension Setup
47
47
 
48
48
  1. Install **[Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm)** extension in Chrome.
49
- 2. Obtain your token by clicking the extension icon in the browser toolbar or from the extension settings page.
49
+ 2. Run `opencli setup` it auto-discovers your token and lets you choose which tools to configure:
50
50
 
51
- **You must configure this token in BOTH your MCP configuration AND system environment variables.**
51
+ ```bash
52
+ opencli setup
53
+ ```
54
+
55
+ The interactive TUI will:
56
+ - 🔍 Auto-discover `PLAYWRIGHT_MCP_EXTENSION_TOKEN` from Chrome (no manual copy needed)
57
+ - ☑️ Show all detected tools (Codex, Cursor, Claude Code, Gemini CLI, etc.)
58
+ - ✏️ Update only the files you select (Space to toggle, Enter to confirm)
52
59
 
53
- First, add it to your MCP client config (e.g. Claude/Cursor):
60
+ <details>
61
+ <summary>Manual setup (alternative)</summary>
62
+
63
+ Add token to your MCP client config (e.g. Claude/Cursor):
54
64
 
55
65
  ```json
56
66
  {
@@ -66,13 +76,15 @@ First, add it to your MCP client config (e.g. Claude/Cursor):
66
76
  }
67
77
  ```
68
78
 
69
- And, so that `opencli` commands can use it directly in the terminal, export it in your shell environment (e.g. `~/.zshrc`):
79
+ Export in shell (e.g. `~/.zshrc`):
70
80
 
71
81
  ```bash
72
82
  export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<your-token-here>"
73
83
  ```
74
84
 
75
- After configuring, run `opencli doctor` to verify your token is correctly set up across all locations:
85
+ </details>
86
+
87
+ Verify with `opencli doctor` — shows colored status for all config locations:
76
88
 
77
89
  ```bash
78
90
  opencli doctor
@@ -84,6 +96,7 @@ opencli doctor
84
96
 
85
97
  ```bash
86
98
  npm install -g @jackwener/opencli
99
+ opencli setup # One-time: configure Playwright MCP token
87
100
  ```
88
101
 
89
102
  Then use directly:
@@ -118,7 +131,7 @@ npm install -g @jackwener/opencli@latest
118
131
 
119
132
  | Site | Commands | Mode |
120
133
  |------|----------|------|
121
- | **bilibili** | `hot` `search` `me` `favorite` ... (11 commands) | 🔐 Browser |
134
+ | **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` | 🔐 Browser |
122
135
  | **zhihu** | `hot` `search` `question` | 🔐 Browser |
123
136
  | **xiaohongshu** | `search` `notifications` `feed` `me` `user` | 🔐 Browser |
124
137
  | **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 🔐 Browser |
@@ -126,6 +139,7 @@ npm install -g @jackwener/opencli@latest
126
139
  | **reddit** | `hot` `frontpage` `search` `subreddit` | 🔐 Browser |
127
140
  | **weibo** | `hot` | 🔐 Browser |
128
141
  | **boss** | `search` | 🔐 Browser |
142
+ | **coupang** | `search` `add-to-cart` | 🔐 Browser |
129
143
  | **youtube** | `search` | 🔐 Browser |
130
144
  | **yahoo-finance** | `quote` | 🔐 Browser |
131
145
  | **reuters** | `search` | 🔐 Browser |
@@ -184,6 +198,8 @@ Explore outputs to `.opencli/explore/<site>/` (manifest.json, endpoints.json, ca
184
198
  - Your login session in Chrome might have expired. Open a normal Chrome tab, navigate to the target site, and log in or refresh the page to prove you are human.
185
199
  - **Node API errors**
186
200
  - Make sure you are using Node.js >= 18. Some dependencies require modern Node APIs.
201
+ - **Token issues**
202
+ - Run `opencli doctor` to diagnose token configuration across all tools.
187
203
 
188
204
  ## Releasing New Versions
189
205
 
package/README.zh-CN.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![Node.js Version](https://img.shields.io/node/v/@jackwener/opencli?style=flat-square)](https://nodejs.org)
10
10
  [![License](https://img.shields.io/npm/l/@jackwener/opencli?style=flat-square)](./LICENSE)
11
11
 
12
- OpenCLI 将任何网站变成命令行工具。**57 个命令**覆盖 **17 个站点** — B站、知乎、小红书、Twitter、Reddit、雪球、GitHub、V2EX、Hacker News、BBC、微博、BOSS直聘、Yahoo Finance、路透社、什么值得买、携程、YouTube — 复用浏览器登录态,AI 驱动探索。
12
+ OpenCLI 将任何网站变成命令行工具 — B站、知乎、小红书、Twitter、Reddit 等众多站点 — 复用浏览器登录态,AI 驱动探索。
13
13
 
14
14
  ---
15
15
 
@@ -29,7 +29,7 @@ OpenCLI 将任何网站变成命令行工具。**57 个命令**覆盖 **17 个
29
29
 
30
30
  ## 亮点
31
31
 
32
- - **57 个命令,17 个站点** — B站、知乎、小红书、Twitter、Reddit、雪球(xueqiu)、GitHub、V2EX、Hacker News、BBC、微博、BOSS直聘、Yahoo Finance、路透社、什么值得买、携程、YouTube
32
+ - **多站点覆盖** — B站、知乎、小红书、Twitter、Reddit 等众多站点
33
33
  - **零风控** — 复用 Chrome 登录态,无需存储任何凭证
34
34
  - **AI 原生** — `explore` 自动发现 API,`synthesize` 生成适配器,`cascade` 探测认证策略
35
35
  - **动态加载引擎** — 声明式的 `.yaml` 或者底层定制的 `.ts` 适配器,放入 `clis/` 文件夹即可自动注册生效
@@ -46,11 +46,21 @@ OpenCLI 通过 Playwright MCP Bridge 扩展与你的浏览器通信。
46
46
  ### Playwright MCP Bridge 扩展配置
47
47
 
48
48
  1. 安装 **[Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm)** 扩展
49
- 2. 在浏览器插件栏点击该插件,或者在插件设置页获取你的 Extension Token
49
+ 2. 运行 `opencli setup` — 自动发现 Token 并让你选择要配置哪些工具:
50
50
 
51
- **你必须将这个 Token 同时配置到你的 MCP 配置文件 AND 环境变量中。**
51
+ ```bash
52
+ opencli setup
53
+ ```
54
+
55
+ 交互式 TUI 会:
56
+ - 🔍 从 Chrome 自动发现 `PLAYWRIGHT_MCP_EXTENSION_TOKEN`(无需手动复制)
57
+ - ☑️ 显示所有支持的工具(Codex、Cursor、Claude Code、Gemini CLI 等)
58
+ - ✏️ 只更新你选中的文件(空格切换,回车确认)
52
59
 
53
- 首先,配置你的 MCP 客户端(如 Claude/Cursor 等):
60
+ <details>
61
+ <summary>手动配置(备选方案)</summary>
62
+
63
+ 配置你的 MCP 客户端(如 Claude/Cursor 等):
54
64
 
55
65
  ```json
56
66
  {
@@ -66,13 +76,15 @@ OpenCLI 通过 Playwright MCP Bridge 扩展与你的浏览器通信。
66
76
  }
67
77
  ```
68
78
 
69
- 并且,为了让 `opencli` 命令行也能直接使用它,你必须在你的终端系统环境变量中导出它(建议写进 `~/.zshrc` 或 `~/.bashrc`):
79
+ 在终端环境变量中导出(建议写进 `~/.zshrc`):
70
80
 
71
81
  ```bash
72
82
  export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<你的-token>"
73
83
  ```
74
84
 
75
- 配置完成后,运行 `opencli doctor` 检测你的 Token 是否在所有位置都正确配置:
85
+ </details>
86
+
87
+ 配置后运行 `opencli doctor` 检查所有位置的 Token 状态:
76
88
 
77
89
  ```bash
78
90
  opencli doctor
@@ -84,6 +96,7 @@ opencli doctor
84
96
 
85
97
  ```bash
86
98
  npm install -g @jackwener/opencli
99
+ opencli setup # 首次使用:配置 Playwright MCP token
87
100
  ```
88
101
 
89
102
  直接使用:
@@ -118,7 +131,7 @@ npm install -g @jackwener/opencli@latest
118
131
 
119
132
  | 站点 | 命令 | 模式 |
120
133
  |------|------|------|
121
- | **bilibili** | `hot` `search` `me` `favorite` ...(共11个) | 🔐 浏览器 |
134
+ | **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` | 🔐 浏览器 |
122
135
  | **zhihu** | `hot` `search` `question` | 🔐 浏览器 |
123
136
  | **xiaohongshu** | `search` `notifications` `feed` `me` `user` | 🔐 浏览器 |
124
137
  | **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 🔐 浏览器 |
@@ -126,6 +139,7 @@ npm install -g @jackwener/opencli@latest
126
139
  | **reddit** | `hot` `frontpage` `search` `subreddit` | 🔐 浏览器 |
127
140
  | **weibo** | `hot` | 🔐 浏览器 |
128
141
  | **boss** | `search` | 🔐 浏览器 |
142
+ | **coupang** | `search` `add-to-cart` | 🔐 浏览器 |
129
143
  | **youtube** | `search` | 🔐 浏览器 |
130
144
  | **yahoo-finance** | `quote` | 🔐 浏览器 |
131
145
  | **reuters** | `search` | 🔐 浏览器 |
@@ -184,6 +198,8 @@ opencli cascade https://api.example.com/data
184
198
  - Chrome 里的登录态可能已经过期(甚至被要求过滑动验证码)。请打开当前 Chrome 页面,在新标签页重新手工登录或刷新该页面。
185
199
  - **Node API 错误 (如 parseArgs, fs 等)**
186
200
  - 确保 Node.js 版本 `>= 18`。旧版不支持我们使用的现代核心库 API。
201
+ - **Token 问题**
202
+ - 运行 `opencli doctor` 诊断所有工具的 Token 配置状态。
187
203
 
188
204
  ## 版本发布
189
205
 
package/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: opencli
3
3
  description: "OpenCLI — Make any website your CLI. Zero risk, AI-powered, reuse Chrome login."
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  author: jackwener
6
6
  tags: [cli, browser, web, mcp, playwright, bilibili, zhihu, twitter, github, v2ex, hackernews, reddit, xiaohongshu, xueqiu, AI, agent]
7
7
  ---
@@ -34,7 +34,8 @@ npm update -g @jackwener/opencli
34
34
 
35
35
  Browser commands require:
36
36
  1. Chrome browser running **(logged into target sites)**
37
- 2. [Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm) extension installed and configured
37
+ 2. [Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm) extension installed
38
+ 3. Run `opencli setup` to auto-discover token and configure all tools
38
39
 
39
40
  > **Note**: You must be logged into the target website in Chrome before running commands. Tabs opened during command execution are auto-closed afterwards.
40
41
 
@@ -139,6 +140,9 @@ opencli list --json # JSON output
139
140
  opencli list -f yaml # YAML output
140
141
  opencli validate # Validate all CLI definitions
141
142
  opencli validate bilibili # Validate specific site
143
+ opencli setup # Interactive token setup (auto-discover + TUI checkbox)
144
+ opencli doctor # Diagnose token config across all tools
145
+ opencli doctor --fix -y # Auto-fix all config files (non-interactive)
142
146
  ```
143
147
 
144
148
  ### AI Agent Workflow
@@ -470,6 +470,86 @@
470
470
  "url"
471
471
  ]
472
472
  },
473
+ {
474
+ "site": "coupang",
475
+ "name": "add-to-cart",
476
+ "description": "Add a Coupang product to cart using logged-in browser session",
477
+ "strategy": "cookie",
478
+ "browser": true,
479
+ "args": [
480
+ {
481
+ "name": "productId",
482
+ "type": "str",
483
+ "required": false,
484
+ "help": "Coupang product ID"
485
+ },
486
+ {
487
+ "name": "url",
488
+ "type": "str",
489
+ "required": false,
490
+ "help": "Canonical product URL"
491
+ }
492
+ ],
493
+ "type": "ts",
494
+ "modulePath": "coupang/add-to-cart.js",
495
+ "domain": "www.coupang.com",
496
+ "columns": [
497
+ "ok",
498
+ "product_id",
499
+ "url",
500
+ "message"
501
+ ]
502
+ },
503
+ {
504
+ "site": "coupang",
505
+ "name": "search",
506
+ "description": "Search Coupang products with logged-in browser session",
507
+ "strategy": "cookie",
508
+ "browser": true,
509
+ "args": [
510
+ {
511
+ "name": "query",
512
+ "type": "str",
513
+ "required": true,
514
+ "help": "Search keyword"
515
+ },
516
+ {
517
+ "name": "page",
518
+ "type": "int",
519
+ "default": 1,
520
+ "required": false,
521
+ "help": "Search result page number"
522
+ },
523
+ {
524
+ "name": "limit",
525
+ "type": "int",
526
+ "default": 20,
527
+ "required": false,
528
+ "help": "Max results (max 50)"
529
+ },
530
+ {
531
+ "name": "filter",
532
+ "type": "str",
533
+ "required": false,
534
+ "help": "Optional search filter (currently supports: rocket)"
535
+ }
536
+ ],
537
+ "type": "ts",
538
+ "modulePath": "coupang/search.js",
539
+ "domain": "www.coupang.com",
540
+ "columns": [
541
+ "rank",
542
+ "title",
543
+ "price",
544
+ "unit_price",
545
+ "rating",
546
+ "review_count",
547
+ "rocket",
548
+ "delivery_type",
549
+ "delivery_promise",
550
+ "url"
551
+ ]
552
+ },
473
553
  {
474
554
  "site": "ctrip",
475
555
  "name": "search",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,141 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { canonicalizeProductUrl, normalizeProductId } from '../../coupang.js';
3
+ function escapeJsString(value) {
4
+ return JSON.stringify(value);
5
+ }
6
+ function buildAddToCartEvaluate(expectedProductId) {
7
+ return `
8
+ (async () => {
9
+ const expectedProductId = ${escapeJsString(expectedProductId)};
10
+ const text = document.body.innerText || '';
11
+ const loginHints = {
12
+ hasLoginLink: Boolean(document.querySelector('a[href*="login"], a[title*="로그인"]')),
13
+ hasMyCoupang: /마이쿠팡/.test(text),
14
+ };
15
+
16
+ const pathMatch = location.pathname.match(/\\/vp\\/products\\/(\\d+)/);
17
+ const currentProductId = pathMatch?.[1] || '';
18
+ if (expectedProductId && currentProductId && expectedProductId !== currentProductId) {
19
+ return { ok: false, reason: 'PRODUCT_MISMATCH', currentProductId, loginHints };
20
+ }
21
+
22
+ const optionSelectors = [
23
+ 'select',
24
+ '[role="listbox"]',
25
+ '.prod-option, .product-option, .option-select, .option-dropdown',
26
+ ];
27
+ const hasRequiredOption = optionSelectors.some((selector) => {
28
+ try {
29
+ const nodes = Array.from(document.querySelectorAll(selector));
30
+ return nodes.some((node) => {
31
+ const label = (node.textContent || '') + ' ' + (node.getAttribute?.('aria-label') || '');
32
+ return /옵션|색상|사이즈|용량|선택/i.test(label);
33
+ });
34
+ } catch {
35
+ return false;
36
+ }
37
+ });
38
+ if (hasRequiredOption) {
39
+ return { ok: false, reason: 'OPTION_REQUIRED', currentProductId, loginHints };
40
+ }
41
+
42
+ const clickCandidate = (elements) => {
43
+ for (const element of elements) {
44
+ if (!(element instanceof HTMLElement)) continue;
45
+ const label = ((element.innerText || '') + ' ' + (element.getAttribute('aria-label') || '')).trim();
46
+ if (/장바구니|카트|cart/i.test(label) && !/sold out|품절/i.test(label)) {
47
+ element.click();
48
+ return true;
49
+ }
50
+ }
51
+ return false;
52
+ };
53
+
54
+ const beforeCount = (() => {
55
+ const node = document.querySelector('[class*="cart"] .count, #headerCartCount, .cart-count');
56
+ const text = node?.textContent || '';
57
+ const num = Number(text.replace(/[^\\d]/g, ''));
58
+ return Number.isFinite(num) ? num : null;
59
+ })();
60
+
61
+ const buttons = Array.from(document.querySelectorAll('button, a[role="button"], input[type="button"]'));
62
+ const clicked = clickCandidate(buttons);
63
+ if (!clicked) {
64
+ return { ok: false, reason: 'ADD_TO_CART_BUTTON_NOT_FOUND', currentProductId, loginHints };
65
+ }
66
+
67
+ await new Promise((resolve) => setTimeout(resolve, 2500));
68
+
69
+ const afterText = document.body.innerText || '';
70
+ const successMessage = /장바구니에 담|장바구니 담기 완료|added to cart/i.test(afterText);
71
+ const afterCount = (() => {
72
+ const node = document.querySelector('[class*="cart"] .count, #headerCartCount, .cart-count');
73
+ const text = node?.textContent || '';
74
+ const num = Number(text.replace(/[^\\d]/g, ''));
75
+ return Number.isFinite(num) ? num : null;
76
+ })();
77
+ const countIncreased =
78
+ beforeCount != null &&
79
+ afterCount != null &&
80
+ afterCount >= beforeCount &&
81
+ (afterCount > beforeCount || beforeCount === 0);
82
+
83
+ return {
84
+ ok: successMessage || countIncreased,
85
+ reason: successMessage || countIncreased ? 'SUCCESS' : 'UNKNOWN',
86
+ currentProductId,
87
+ beforeCount,
88
+ afterCount,
89
+ loginHints,
90
+ };
91
+ })()
92
+ `;
93
+ }
94
+ cli({
95
+ site: 'coupang',
96
+ name: 'add-to-cart',
97
+ description: 'Add a Coupang product to cart using logged-in browser session',
98
+ domain: 'www.coupang.com',
99
+ strategy: Strategy.COOKIE,
100
+ browser: true,
101
+ args: [
102
+ { name: 'productId', required: false, help: 'Coupang product ID' },
103
+ { name: 'url', required: false, help: 'Canonical product URL' },
104
+ ],
105
+ columns: ['ok', 'product_id', 'url', 'message'],
106
+ func: async (page, kwargs) => {
107
+ const rawProductId = kwargs.productId ?? kwargs.product_id;
108
+ const productId = normalizeProductId(rawProductId);
109
+ const targetUrl = canonicalizeProductUrl(kwargs.url, productId);
110
+ if (!productId && !targetUrl) {
111
+ throw new Error('Either --product-id or --url is required');
112
+ }
113
+ const finalUrl = targetUrl || canonicalizeProductUrl('', productId);
114
+ await page.goto(finalUrl);
115
+ await page.wait(3);
116
+ const result = await page.evaluate(buildAddToCartEvaluate(productId));
117
+ const loginHints = result?.loginHints ?? {};
118
+ if (loginHints.hasLoginLink && !loginHints.hasMyCoupang) {
119
+ throw new Error('Coupang login required. Please log into Coupang in Chrome and retry.');
120
+ }
121
+ const actualProductId = normalizeProductId(result?.currentProductId || productId);
122
+ if (result?.reason === 'PRODUCT_MISMATCH') {
123
+ throw new Error(`Product mismatch: expected ${productId}, got ${actualProductId || 'unknown'}`);
124
+ }
125
+ if (result?.reason === 'OPTION_REQUIRED') {
126
+ throw new Error('This product requires option selection and is not supported in v1.');
127
+ }
128
+ if (result?.reason === 'ADD_TO_CART_BUTTON_NOT_FOUND') {
129
+ throw new Error('Could not find an add-to-cart button on the product page.');
130
+ }
131
+ if (!result?.ok) {
132
+ throw new Error('Failed to confirm add-to-cart success.');
133
+ }
134
+ return [{
135
+ ok: true,
136
+ product_id: actualProductId || productId,
137
+ url: finalUrl,
138
+ message: 'Added to cart',
139
+ }];
140
+ },
141
+ });
@@ -0,0 +1 @@
1
+ export {};