@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 +23 -7
- package/README.zh-CN.md +24 -8
- package/SKILL.md +6 -2
- package/dist/cli-manifest.json +80 -0
- package/dist/clis/coupang/add-to-cart.d.ts +1 -0
- package/dist/clis/coupang/add-to-cart.js +141 -0
- package/dist/clis/coupang/search.d.ts +1 -0
- package/dist/clis/coupang/search.js +453 -0
- package/dist/coupang.d.ts +24 -0
- package/dist/coupang.js +262 -0
- package/dist/coupang.test.d.ts +1 -0
- package/dist/coupang.test.js +62 -0
- package/dist/doctor.d.ts +15 -0
- package/dist/doctor.js +226 -25
- package/dist/doctor.test.js +13 -6
- package/dist/main.js +7 -0
- package/dist/setup.d.ts +4 -0
- package/dist/setup.js +145 -0
- package/dist/tui.d.ts +22 -0
- package/dist/tui.js +139 -0
- package/package.json +1 -1
- package/src/clis/coupang/add-to-cart.ts +149 -0
- package/src/clis/coupang/search.ts +466 -0
- package/src/coupang.test.ts +78 -0
- package/src/coupang.ts +302 -0
- package/src/doctor.test.ts +15 -6
- package/src/doctor.ts +221 -25
- package/src/main.ts +8 -0
- package/src/setup.ts +169 -0
- package/src/tui.ts +171 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://nodejs.org)
|
|
10
10
|
[](./LICENSE)
|
|
11
11
|
|
|
12
|
-
A CLI tool that turns **any website** into a command-line interface
|
|
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.
|
|
49
|
+
2. Run `opencli setup` — it auto-discovers your token and lets you choose which tools to configure:
|
|
50
50
|
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
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
|
[](https://nodejs.org)
|
|
10
10
|
[](./LICENSE)
|
|
11
11
|
|
|
12
|
-
OpenCLI
|
|
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
|
-
-
|
|
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.
|
|
49
|
+
2. 运行 `opencli setup` — 自动发现 Token 并让你选择要配置哪些工具:
|
|
50
50
|
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
+
在终端环境变量中导出(建议写进 `~/.zshrc`):
|
|
70
80
|
|
|
71
81
|
```bash
|
|
72
82
|
export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<你的-token>"
|
|
73
83
|
```
|
|
74
84
|
|
|
75
|
-
|
|
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`
|
|
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.
|
|
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
|
|
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
|
package/dist/cli-manifest.json
CHANGED
|
@@ -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 {};
|