Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ strategy: public # public | cookie | header
browser: false # true if browser session is needed

args:
query:
positional: true
type: str
required: true
description: Search keyword
limit:
type: int
default: 20
Expand Down Expand Up @@ -76,7 +81,7 @@ cli({
domain: 'www.mysite.com',
strategy: Strategy.COOKIE,
args: [
{ name: 'query', required: true, help: 'Search query' },
{ name: 'query', positional: true, required: true, help: 'Search query' },
{ name: 'limit', type: 'int', default: 10, help: 'Max results' },
],
columns: ['title', 'url', 'date'],
Expand Down Expand Up @@ -118,6 +123,39 @@ opencli <site> <command> --limit 3 -f json
opencli <site> <command> -v
```

## Arg Design Convention

Use **positional** for the primary, required argument of a command (the "what" — query, symbol, id, url, username). Use **named options** (`--flag`) for secondary/optional configuration (limit, format, sort, page, filters, language, date).

**Rule of thumb**: Think about how the user will type the command. `opencli xueqiu stock SH600519` is more natural than `opencli xueqiu stock --symbol SH600519`.

| Arg type | Positional? | Examples |
|----------|-------------|----------|
| Main target (query, symbol, id, url, username) | ✅ `positional: true` | `search '茅台'`, `stock SH600519`, `download BV1xxx` |
| Configuration (limit, format, sort, page, type, filters) | ❌ Named `--flag` | `--limit 10`, `--format json`, `--sort hot`, `--location seattle` |

Do **not** convert an argument to positional just because it appears first in the file. If the argument is optional, acts like a filter, or selects a mode/configuration, it should usually stay a named option.

YAML example:
```yaml
args:
query:
positional: true # ← primary arg, user types it directly
type: str
required: true
limit:
type: int # ← config arg, user types --limit 10
default: 20
```

TS example:
```typescript
args: [
{ name: 'query', positional: true, required: true, help: 'Search query' },
{ name: 'limit', type: 'int', default: 10, help: 'Max results' },
]
```

## Testing

See [TESTING.md](./TESTING.md) for the full guide and exact test locations.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Run `opencli list` for the live registry.
| **notion** | `status` `search` `read` `new` `write` `sidebar` `favorites` `export` | Desktop |
| **discord-app** | `status` `send` `read` `channels` `servers` `search` `members` | Desktop |
| **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` | Public / Browser |
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | Browser |
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` `earnings-date` | Browser |
| **antigravity** | `status` `send` `read` `new` `dump` `extract-code` `model` `watch` `serve` | Desktop |
| **chatgpt** | `status` `new` `send` `read` `ask` | Desktop |
| **xiaohongshu** | `search` `notifications` `feed` `user` `download` `creator-notes` `creator-note-detail` `creator-notes-summary` `creator-profile` `creator-stats` | Browser |
Expand Down Expand Up @@ -228,11 +228,11 @@ brew install yt-dlp

```bash
# Download images/videos from Xiaohongshu note
opencli xiaohongshu download --note-id abc123 --output ./xhs
opencli xiaohongshu download abc123 --output ./xhs

# Download Bilibili video (requires yt-dlp)
opencli bilibili download --bvid BV1xxx --output ./bilibili
opencli bilibili download --bvid BV1xxx --quality 1080p # Specify quality
opencli bilibili download BV1xxx --output ./bilibili
opencli bilibili download BV1xxx --quality 1080p # Specify quality

# Download Twitter media from user
opencli twitter download elonmusk --limit 20 --output ./twitter
Expand Down
8 changes: 4 additions & 4 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ npm install -g @jackwener/opencli@latest
| **notion** | `status` `search` `read` `new` `write` `sidebar` `favorites` `export` | 桌面端 |
| **discord-app** | `status` `send` `read` `channels` `servers` `search` `members` | 桌面端 |
| **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` | 公开 / 浏览器 |
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 浏览器 |
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` `earnings-date` | 浏览器 |
| **antigravity** | `status` `send` `read` `new` `dump` `extract-code` `model` `watch` `serve` | 桌面端 |
| **chatgpt** | `status` `new` `send` `read` `ask` | 桌面端 |
| **xiaohongshu** | `search` `notifications` `feed` `user` `download` `creator-notes` `creator-note-detail` `creator-notes-summary` `creator-profile` `creator-stats` | 浏览器 |
Expand Down Expand Up @@ -229,11 +229,11 @@ brew install yt-dlp

```bash
# 下载小红书笔记中的图片/视频
opencli xiaohongshu download --note-id abc123 --output ./xhs
opencli xiaohongshu download abc123 --output ./xhs

# 下载B站视频(需要 yt-dlp)
opencli bilibili download --bvid BV1xxx --output ./bilibili
opencli bilibili download --bvid BV1xxx --quality 1080p # 指定画质
opencli bilibili download BV1xxx --output ./bilibili
opencli bilibili download BV1xxx --quality 1080p # 指定画质

# 下载 Twitter 用户的媒体
opencli twitter download elonmusk --limit 20 --output ./twitter
Expand Down
2 changes: 1 addition & 1 deletion src/clis/bilibili/following.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ cli({
description: '获取 Bilibili 用户的关注列表',
strategy: Strategy.COOKIE,
args: [
{ name: 'uid', required: false, help: '目标用户 ID(默认为当前登录用户)' },
{ name: 'uid', positional: true, required: false, help: '目标用户 ID(默认为当前登录用户)' },
{ name: 'page', type: 'int', required: false, default: 1, help: '页码' },
{ name: 'limit', type: 'int', required: false, default: 50, help: '每页数量 (最大 50)' },
],
Expand Down
2 changes: 1 addition & 1 deletion src/clis/boss/detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ cli({
navigateBefore: false,
browser: true,
args: [
{ name: 'security-id', required: true, help: 'Security ID from search results (securityId field)' },
{ name: 'security-id', positional: true, required: true, help: 'Security ID from search results (securityId field)' },
],
columns: [
'name', 'salary', 'experience', 'degree', 'city', 'district',
Expand Down
2 changes: 1 addition & 1 deletion src/clis/boss/greet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ cli({
navigateBefore: false,
browser: true,
args: [
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate (from recommend)' },
{ name: 'uid', positional: true, required: true, help: 'Encrypted UID of the candidate (from recommend)' },
{ name: 'security-id', required: true, help: 'Security ID of the candidate' },
{ name: 'job-id', required: true, help: 'Encrypted job ID' },
{ name: 'text', default: '', help: 'Custom greeting message (uses default template if empty)' },
Expand Down
2 changes: 1 addition & 1 deletion src/clis/boss/invite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ cli({
navigateBefore: false,
browser: true,
args: [
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate' },
{ name: 'uid', positional: true, required: true, help: 'Encrypted UID of the candidate' },
{ name: 'time', required: true, help: 'Interview time (e.g. 2025-04-01 14:00)' },
{ name: 'address', default: '', help: 'Interview address (uses saved address if empty)' },
{ name: 'contact', default: '', help: 'Contact person name (uses saved contact if empty)' },
Expand Down
2 changes: 1 addition & 1 deletion src/clis/boss/mark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ cli({
navigateBefore: false,
browser: true,
args: [
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate' },
{ name: 'uid', positional: true, required: true, help: 'Encrypted UID of the candidate' },
{ name: 'label', required: true, help: 'Label name (新招呼/沟通中/已约面/已获取简历/已交换电话/已交换微信/不合适/收藏) or label ID' },
{ name: 'remove', type: 'boolean', default: false, help: 'Remove the label instead of adding' },
],
Expand Down
2 changes: 1 addition & 1 deletion src/clis/boss/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ cli({
navigateBefore: false,
browser: true,
args: [
{ name: 'uid', required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
{ name: 'uid', positional: true, required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
{ name: 'text', required: true, positional: true, help: 'Message text to send' },
],
columns: ['status', 'detail'],
Expand Down
2 changes: 1 addition & 1 deletion src/clis/coupang/add-to-cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ cli({
strategy: Strategy.COOKIE,
browser: true,
args: [
{ name: 'product-id', required: false, help: 'Coupang product ID' },
{ name: 'product-id', positional: true, required: false, help: 'Coupang product ID' },
{ name: 'url', required: false, help: 'Canonical product URL' },
],
columns: ['ok', 'product_id', 'url', 'message'],
Expand Down
2 changes: 1 addition & 1 deletion src/clis/grok/ask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ export const askCommand = cli({
strategy: Strategy.COOKIE,
browser: true,
args: [
{ name: 'prompt', type: 'string', required: true, help: 'Prompt to send to Grok' },
{ name: 'prompt', positional: true, type: 'string', required: true, help: 'Prompt to send to Grok' },
{ name: 'timeout', type: 'int', default: 120, help: 'Max seconds to wait for response (default: 120)' },
{ name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending (default: false)' },
{ name: 'web', type: 'boolean', default: false, help: 'Use the explicit grok.com consumer web flow (default: false)' },
Expand Down
1 change: 1 addition & 0 deletions src/clis/instagram/comment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ args:
positional: true
description: Username of the post author
text:
positional: true
type: str
required: true
description: Comment text
Expand Down
2 changes: 1 addition & 1 deletion src/clis/jike/repost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ cli({
browser: true,
args: [
{ name: 'id', type: 'string', required: true, positional: true, help: '帖子 ID' },
{ name: 'text', type: 'string', required: false, help: '转发附言(可选)' },
{ name: 'text', positional: true, type: 'string', required: false, help: '转发附言(可选)' },
],
columns: ['status', 'message'],
func: async (page, kwargs) => {
Expand Down
1 change: 1 addition & 0 deletions src/clis/jimeng/generate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ browser: true

args:
prompt:
positional: true
type: string
required: true
description: "图片描述 prompt"
Expand Down
1 change: 1 addition & 0 deletions src/clis/linux-do/category.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ browser: true

args:
slug:
positional: true
type: str
required: true
description: Category slug (use 'categories' command to find)
Expand Down
1 change: 1 addition & 0 deletions src/clis/tiktok/comment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ args:
positional: true
description: TikTok video URL
text:
positional: true
type: str
required: true
description: Comment text
Expand Down
6 changes: 3 additions & 3 deletions src/clis/twitter/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Twitter/X download — download images and videos from tweets.
*
* Usage:
* opencli twitter download --username elonmusk --limit 10 --output ./twitter
* opencli twitter download elonmusk --limit 10 --output ./twitter
* opencli twitter download --tweet-url https://x.com/xxx/status/123 --output ./twitter
*/

Expand All @@ -27,7 +27,7 @@ cli({
domain: 'x.com',
strategy: Strategy.COOKIE,
args: [
{ name: 'username', help: 'Twitter username (downloads from media tab)' },
{ name: 'username', positional: true, help: 'Twitter username (downloads from media tab)' },
{ name: 'tweet-url', help: 'Single tweet URL to download' },
{ name: 'limit', type: 'int', default: 10, help: 'Number of tweets to scan' },
{ name: 'output', default: './twitter-downloads', help: 'Output directory' },
Expand All @@ -44,7 +44,7 @@ cli({
index: 0,
type: '-',
status: 'failed',
size: 'Must provide --username or --tweet-url',
size: 'Must provide a username or --tweet-url',
}];
}

Expand Down
2 changes: 1 addition & 1 deletion src/clis/twitter/followers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ cli({
strategy: Strategy.INTERCEPT,
browser: true,
args: [
{ name: 'user', type: 'string', required: false },
{ name: 'user', positional: true, type: 'string', required: false },
{ name: 'limit', type: 'int', default: 50 },
],
columns: ['screen_name', 'name', 'bio', 'followers'],
Expand Down
2 changes: 1 addition & 1 deletion src/clis/twitter/following.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ cli({
strategy: Strategy.INTERCEPT,
browser: true,
args: [
{ name: 'user', type: 'string', required: false },
{ name: 'user', positional: true, type: 'string', required: false },
{ name: 'limit', type: 'int', default: 50 },
],
columns: ['screen_name', 'name', 'bio', 'followers'],
Expand Down
2 changes: 1 addition & 1 deletion src/clis/twitter/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ cli({
strategy: Strategy.COOKIE,
browser: true,
args: [
{ name: 'tweet-id', type: 'string', required: true },
{ name: 'tweet-id', positional: true, type: 'string', required: true },
{ name: 'limit', type: 'int', default: 50 },
],
columns: ['id', 'author', 'text', 'likes', 'retweets', 'url'],
Expand Down
2 changes: 1 addition & 1 deletion src/clis/xiaohongshu/creator-note-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ cli({
strategy: Strategy.COOKIE,
browser: true,
args: [
{ name: 'note-id', type: 'string', required: true, help: 'Note ID (from creator-notes or note-detail page URL)' },
{ name: 'note-id', positional: true, type: 'string', required: true, help: 'Note ID (from creator-notes or note-detail page URL)' },
],
columns: ['section', 'metric', 'value', 'extra'],
func: async (page, kwargs) => {
Expand Down
2 changes: 1 addition & 1 deletion src/clis/xiaohongshu/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ cli({
domain: 'www.xiaohongshu.com',
strategy: Strategy.COOKIE,
args: [
{ name: 'note-id', required: true, help: 'Note ID (from URL)' },
{ name: 'note-id', positional: true, required: true, help: 'Note ID (from URL)' },
{ name: 'output', default: './xiaohongshu-downloads', help: 'Output directory' },
],
columns: ['index', 'type', 'status', 'size'],
Expand Down
Loading