--- name: qiaomu-opencli description: | Use when running OpenCLI commands to interact with websites (Bilibili, Twitter, Reddit, Xiaohongshu, etc.), desktop apps (Cursor, Notion, Claude), or public APIs (HackerNews, arXiv, npm, pubmed). Also covers browser automation (navigate/click/type/extract), auto-fixing broken adapters, and creating new adapters for any website. Covers 136 site adapters, 7 app adapters, 13 external CLIs. version: 1.7.22 author: joeseesun upstream: jackwener/opencli tags: [opencli, cli, browser, web, chrome-extension, cdp, bilibili, twitter, reddit, xiaohongshu, github, youtube, AI, agent, automation] --- # OpenCLI โ€” Complete Guide > Make any website or Electron App your CLI. Reuse Chrome login, zero risk, AI-powered discovery. --- ## Install & Run ```bash npm install -g @jackwener/opencli opencli npm update -g @jackwener/opencli # Update opencli --version # Check version ``` ## Prerequisites Browser commands require: 1. Chrome running **(logged into target sites)** 2. **opencli Browser Bridge** Chrome extension installed 3. Daemon auto-starts on first browser command ```bash opencli doctor # Diagnose extension + daemon connectivity ``` > Public API commands (`hackernews`, `arxiv`, `npm`, `pubmed`โ€ฆ) need no browser. --- ## Command Quick Reference Usage: `opencli [args] [--limit N] [-f json|yaml|md|csv|table]` Type legend: ๐ŸŒ = Browser (needs Chrome login) ยท โœ… = Public API ยท ๐Ÿ–ฅ๏ธ = Desktop (Electron/CDP) ยท ๐Ÿ”ง = External CLI ### Website Adapters (136 sites) | Site | Type | Commands | |------|------|----------| | **1688** | ๐ŸŒ | `search` `item` `download` `store` `assets` | | **1point3acres** | ๐ŸŒ | `hot` `latest` `digest` `search` `thread` `forum` `forums` `user` `notifications` | | **36kr** | ๐ŸŒ | `hot` `news` `search` `article` | | **51job** | ๐ŸŒ | `search` `hot` `detail` `company` | | **aibase** | ๐ŸŒ | `news` | | **amazon** | ๐ŸŒ | `bestsellers` `search` `product` `offer` `discussion` `movers-shakers` `new-releases` | | **apple-podcasts** | โœ… | `top` `search` `episodes` | | **arxiv** | โœ… | `search` `paper` `recent` `author` | | **baidu-scholar** | ๐ŸŒ | `search` | | **band** | ๐ŸŒ | `bands` `posts` `post` `mentions` | | **barchart** | ๐ŸŒ | `quote` `options` `greeks` `flow` | | **bbc** | โœ… | `news` `topic` | | **bilibili** | ๐ŸŒ | `hot` `search` `me` `favorite` `history` `feed` `feed-detail` `user-videos` `subtitle` `dynamic` `ranking` `following` `comments` `download` `video` | | **binance** | โœ… | `top` `ticker` `price` `klines` `gainers` `losers` `pairs` `trades` `depth` `asks` `prices` | | **bloomberg** | โœ…๐ŸŒ | RSS: `main` `markets` `tech` `politics` `economics` `opinions` ยท Browser: `news` | | **bluesky** | ๐ŸŒ | `search` `profile` `user` `feeds` `followers` `following` `thread` `trending` `starter-packs` | | **boss** | ๐ŸŒ | `search` `detail` `recommend` `joblist` `greet` `batchgreet` `send` `chatlist` `chatmsg` `invite` `mark` `exchange` `resume` `stats` | | **brave** | ๐ŸŒ | `search` | | **chaoxing** | ๐ŸŒ | `assignments` `exams` | | **claude** | ๐ŸŒ | `status` `new` `send` `read` `ask` `detail` `history` | | **cnki** | ๐ŸŒ | `search` | | **coingecko** | โœ… | `top` `coin` `trending` `categories` `exchanges` `derivatives` `global` | | **coupang** | ๐ŸŒ | `search` `add-to-cart` | | **crates** | โœ… | `search` `crate` | | **ctrip** | ๐ŸŒ | `search` `hotel-search` `hotel-suggest` `flight` | | **dblp** | โœ… | `search` `author` `paper` `venue` | | **deepseek** | ๐ŸŒ | `status` `new` `send` `read` `ask` `detail` `history` | | **defillama** | โœ… | `protocols` `protocol` | | **devto** | โœ… | `top` `tag` `user` | | **dianping** | ๐ŸŒ | `search` `shop` | | **dictionary** | โœ… | `search` `synonyms` `examples` | | **dockerhub** | โœ… | `search` `image` | | **doubao** | ๐ŸŒ | `status` `new` `send` `read` `ask` `detail` `history` `meeting-summary` `meeting-transcript` | | **douban** | ๐ŸŒ | `search` `top250` `subject` `photos` `download` `marks` `reviews` `movie-hot` `book-hot` | | **douyin** | ๐ŸŒ | `profile` `videos` `user-videos` `activities` `collections` `hashtag` `location` `stats` `publish` `draft` `drafts` `delete` `update` | | **duckduckgo** | ๐ŸŒ | `search` `suggest` | | **eastmoney** | ๐ŸŒ | `quote` `kline` `hot-rank` `rank` `index-board` `sectors` `northbound` `money-flow` `kuaixun` `announcement` `etf` `convertible` `holders` `longhu` | | **endoflife** | โœ… | `product` | | **facebook** | ๐ŸŒ | `feed` `profile` `search` `friends` `groups` `events` `notifications` `memories` `add-friend` `join-group` `marketplace-inbox` `marketplace-listings` | | **flathub** | โœ… | `search` `app` | | **gemini** | ๐ŸŒ | `ask` `new` `image` `deep-research` `deep-research-result` | | **gitee** | ๐ŸŒ | `search` `trending` `user` | | **google** | โœ… | `news` `search` `suggest` `trends` | | **google-scholar** | ๐ŸŒ | `search` `profile` `cite` | | **goproxy** | โœ… | `module` `versions` | | **gov-law** | ๐ŸŒ | `search` `recent` | | **gov-policy** | ๐ŸŒ | `search` `recent` | | **grok** | ๐ŸŒ | `ask` | | **hackernews** | โœ… | `top` `new` `best` `ask` `show` `jobs` `search` `user` | | **hf** | โœ… | `top` | | **homebrew** | โœ… | `popular` `formula` `cask` | | **hupu** | ๐ŸŒ | `hot` `search` `detail` `like` `unlike` `reply` `mentions` | | **imdb** | โœ… | `top` `trending` `search` `title` `person` `reviews` | | **indeed** | ๐ŸŒ | `search` `job` | | **instagram** | ๐ŸŒ | `explore` `profile` `search` `user` `followers` `following` `follow` `unfollow` `like` `unlike` `comment` `save` `unsave` `saved` `reel` `story` `post` `note` `download` `collection-create` `collection-delete` | | **jd** | ๐ŸŒ | `item` | | **jike** | ๐ŸŒ | `feed` `search` `create` `like` `comment` `repost` `notifications` `post` `topic` `user` | | **jimeng** | ๐ŸŒ | `generate` `history` | | **ke** | ๐ŸŒ | `ershoufang` `chengjiao` `xiaoqu` `zufang` | | **lesswrong** | โœ… | `frontpage` `curated` `new` `top` `top-week` `top-month` `top-year` `shortform` `read` `comments` `user` `user-posts` `sequences` `tags` `tag` | | **lichess** | โœ… | `top` `user` | | **linkedin** | ๐ŸŒ | `search` `timeline` | | **linux-do** | ๐ŸŒ | `hot` `latest` `feed` `search` `categories` `category` `tags` `topic` `topic-content` `user-posts` `user-topics` | | **lobsters** | โœ… | `hot` `newest` `active` `tag` | | **maimai** | ๐ŸŒ | `search-talents` | | **maven** | โœ… | `search` `artifact` | | **mdn** | โœ… | `search` | | **medium** | ๐ŸŒ | `feed` `search` `user` | | **mubu** | ๐ŸŒ | `search` `recent` `doc` `docs` `notes` | | **notebooklm** | ๐ŸŒ | `status` `list` `open` `current` `get` `history` `summary` `note-list` `notes-get` `source-list` `source-get` `source-fulltext` `source-guide` | | **nowcoder** | ๐ŸŒ | `hot` `trending` `search` `suggest` `jobs` `companies` `experience` `salary` `practice` `papers` `topics` `creators` `detail` `notifications` `recommend` `referral` | | **npm** | โœ… | `search` `package` `downloads` | | **nuget** | โœ… | `search` `package` | | **nvd** | โœ… | `cve` | | **oeis** | โœ… | `search` `sequence` | | **ones** | ๐ŸŒ | `login` `logout` `me` `tasks` `task` `my-tasks` `worklog` `token-info` | | **openalex** | โœ… | `search` `work` | | **openfda** | โœ… | `drug-label` `food-recall` | | **openreview** | โœ… | `search` `paper` `author` `venue` `reviews` | | **osv** | โœ… | `vulnerability` `query` | | **packagist** | โœ… | `search` `package` | | **pixiv** | ๐ŸŒ | `ranking` `search` `user` `illusts` `detail` `download` | | **powerchina** | ๐ŸŒ | `search` | | **producthunt** | โœ… | `today` `hot` `browse` `posts` | | **pubmed** | โœ… | `search` `article` `author` `citations` `related` | | **pypi** | โœ… | `package` `downloads` | | **quark** | ๐ŸŒ | `ls` `mkdir` `mv` `rename` `rm` `save` `share-tree` | | **qwen** | ๐ŸŒ | `status` `new` `send` `read` `ask` `image` `detail` `history` | | **reddit** | ๐ŸŒ | `hot` `frontpage` `home` `popular` `search` `subreddit` `subreddit-info` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `reply` `subscribe` `saved` `upvoted` `whoami` | | **rednote** | ๐ŸŒ | `search` `feed` `user` `note` `comments` `download` `notifications` | | **rest-countries** | โœ… | `country` `region` | | **reuters** | ๐ŸŒ | `search` | | **rfc** | โœ… | `rfc` | | **rubygems** | โœ… | `search` `gem` | | **sinablog** | ๐ŸŒ | `hot` `search` `article` `user` | | **sinafinance** | โœ… | `news` | | **smzdm** | ๐ŸŒ | `search` | | **spotify** | โœ… | `auth` `status` `play` `pause` `next` `prev` `volume` `search` `queue` `shuffle` `repeat` | | **stackoverflow** | โœ… | `hot` `search` `bounties` `unanswered` | | **steam** | โœ… | `top-sellers` | | **substack** | ๐ŸŒ | `feed` `search` `publication` | | **taobao** | ๐ŸŒ | `search` `detail` `reviews` `add-cart` `cart` | | **tdx** | ๐ŸŒ | `hot-rank` | | **ths** | ๐ŸŒ | `hot-rank` | | **tieba** | ๐ŸŒ | `hot` `search` `posts` `read` | | **tiktok** | ๐ŸŒ | `explore` `search` `profile` `user` `following` `follow` `unfollow` `like` `unlike` `comment` `save` `unsave` `live` `notifications` `friends` | | **toutiao** | ๐ŸŒ | `hot` `articles` | | **tvmaze** | โœ… | `search` `show` | | **twitter** | ๐ŸŒ | `trending` `bookmarks` `search` `profile` `timeline` `thread` `article` `follow` `unfollow` `bookmark` `unbookmark` `bookmark-folders` `post` `like` `likes` `reply` `quote` `retweet` `unretweet` `delete` `block` `unblock` `followers` `following` `notifications` `download` `lists` `list-tweets` `list-add` `list-remove` `tweets` `unlike` `spaces` | | **uisdc** | ๐ŸŒ | `news` | | **uiverse** | ๐ŸŒ | `code` `preview` | | **v2ex** | โœ…๐ŸŒ | Public: `hot` `latest` `topic` `node` `nodes` `member` `user` `replies` ยท Browser: `daily` `me` `notifications` | | **wanfang** | ๐ŸŒ | `search` | | **web** | ๐ŸŒ | `read` โ€” any URL to Markdown | | **weibo** | ๐ŸŒ | `hot` `search` `feed` `user` `me` `post` `comments` | | **wikidata** | โœ… | `search` `entity` | | **wikipedia** | โœ… | `search` `summary` `random` `trending` | | **weixin** | ๐ŸŒ | `download` โ€” ๅ…ฌไผ—ๅท article to Markdown | | **weread** | ๐ŸŒ | `shelf` `search` `book` `highlights` `notes` `notebooks` `ranking` | | **wttr** | โœ… | `current` `forecast` | | **xianyu** | ๐ŸŒ | `search` `item` `chat` | | **xiaoe** | ๐ŸŒ | `courses` `catalog` `content` `detail` `play-url` | | **xiaohongshu** | ๐ŸŒ | `search` `notifications` `feed` `user` `note` `comments` `download` `publish` `creator-notes` `creator-note-detail` `creator-notes-summary` `creator-profile` `creator-stats` | | **xiaoyuzhou** | โœ… | `podcast` `podcast-episodes` `episode` | | **xueqiu** | ๐ŸŒ | `hot-stock` `stock` `watchlist` `feed` `hot` `search` `comments` `earnings-date` `fund-holdings` `fund-snapshot` | | **yahoo** | ๐ŸŒ | `search` | | **yahoo-finance** | ๐ŸŒ | `quote` | | **yollomi** | ๐ŸŒ | `models` `generate` `video` `upload` `remove-bg` `edit` `background` `face-swap` `object-remover` `restore` `try-on` `upscale` | | **youtube** | ๐ŸŒ | `search` `video` `transcript` `feed` `history` `channel` `playlist` `comments` `like` `unlike` `subscribe` `unsubscribe` `subscriptions` `watch-later` | | **yuanbao** | ๐ŸŒ | `new` `ask` | | **zhihu** | ๐ŸŒ | `hot` `search` `question` | | **zlibrary** | ๐ŸŒ | `search` `info` | | **zsxq** | ๐ŸŒ | `groups` `dynamics` `topics` `topic` `search` | ### Desktop Apps (CDP/Electron) | App | Commands | |-----|----------| | **antigravity** | `status` `send` `read` `new` `dump` `extract-code` `model` `watch` | | **chatgpt-app** | `status` `new` `send` `read` `ask` `model` | | **chatwise** | `status` `new` `send` `read` `ask` `model` `history` `export` `screenshot` | | **codex** | `status` `send` `read` `new` `dump` `extract-diff` `model` `ask` `screenshot` `history` `export` | | **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` | | **discord-app** | `status` `send` `read` `channels` `servers` `search` `members` | | **doubao-app** | `status` `new` `send` `read` `ask` `screenshot` `dump` | | **notion** | `status` `search` `read` `new` `write` `sidebar` `favorites` `export` | ### External CLI (passthrough) | CLI | Installed | Description | |-----|-----------|-------------| | **gh** | โœ… | GitHub CLI โ€” repos, PRs, issues, releases | | **obsidian** | โœ… | Obsidian vault โ€” notes, search, tags | | **docker** | โœ… | Docker CLI | | **lark-cli** | โœ… | Lark/Feishu โ€” messages, docs, calendar, tasks (200+ commands) | | **vercel** | โœ… | Vercel โ€” deploy, domains, env vars, logs | | **wx** | โœ… | WeChat local data CLI โ€” sessions, messages, search, export | | **discord** | โŒ | Discord CLI | | **dws** | โŒ | DingTalk Workspace | | **longbridge** | โŒ | Longbridge CLI โ€” market data, account, trading | | **ntn** | โŒ | Notion CLI | | **tg** | โŒ | Telegram CLI | | **wecom-cli** | โŒ | WeCom/ไผไธšๅพฎไฟก | ```bash opencli external install discord # Install external CLI opencli external list # Show all external CLIs opencli gh pr list --limit 5 # Passthrough to gh opencli wx messages # Passthrough to wx-cli ``` ### Management Commands ```bash opencli list [-f json|yaml] # All available commands opencli doctor # Diagnose extension + daemon opencli daemon status|restart|stop # Daemon control opencli adapter eject [site] # Eject adapter to ~/.opencli/clis/ for local editing opencli adapter reset [site] # Reset ejected adapter to upstream opencli profile list|use|rename # Chrome profile management opencli plugin list|install|update # Plugin management ``` --- ## Browser Automation Control Chrome step-by-step. Reuses existing login sessions. ### Critical Rules 1. **Always `state` first** โ€” never guess element indices. `state` returns structured DOM with `[N]` indices, is instant and costs zero tokens. 2. **Never use `eval` to click or type** โ€” use `click ` and `type "text"` instead. 3. **Verify inputs with `get value`** โ€” after `type`, run `get value ` to confirm. 4. **Run `state` after every page change** โ€” after `open`, `click` (on links), `scroll`. 5. **Chain commands aggressively** โ€” `open + state`, `type + type + click` in one `&&` chain. Target 3-5 tool calls total. 6. **`eval` is read-only** โ€” use ONLY for data extraction (`JSON.stringify(...)`), never for clicking/typing/navigating. 7. **Prefer `network` to discover APIs** โ€” JSON APIs are more reliable than DOM scraping. ### Command Cost Guide | Cost | Commands | |------|----------| | Free & instant | `state`, `get *`, `eval`, `network`, `scroll`, `keys` | | Free but changes page | `open`, `click`, `type`, `select`, `back` | | Expensive (vision) | `screenshot` โ€” ONLY when user needs a saved image | ### Commands ```bash # Navigation opencli browser open opencli browser back opencli browser scroll down|up [--amount N] # Inspect (free) opencli browser state # Primary โ€” structured DOM with [N] indices opencli browser screenshot [path.png] # Only for user deliverables # Get (free) opencli browser get title|url|html opencli browser get text|value|attributes # Interact opencli browser click opencli browser type "text" opencli browser select "option" opencli browser keys "Enter" # Enter, Escape, Tab, Control+a # Wait opencli browser wait time 3 opencli browser wait selector ".loaded" [--timeout 5000] opencli browser wait text "Success" # Extract (read-only) opencli browser eval "JSON.stringify([...document.querySelectorAll('h2')].map(e => e.textContent))" # Network (API discovery) opencli browser network opencli browser network --detail 3 # Full response body of request #3 # Save as CLI opencli browser init hn/top # Scaffold adapter at ~/.opencli/clis/hn/top.ts opencli browser verify hn/top # Test the adapter opencli browser close ``` ### Chaining Examples ```bash # GOOD: open + inspect in one call opencli browser open https://example.com && opencli browser state # GOOD: fill form in one call opencli browser type 3 "hello" && opencli browser type 4 "world" && opencli browser click 7 # GOOD: click + wait + state (for page-changing clicks) opencli browser click 12 && opencli browser wait time 1 && opencli browser state ``` ### Troubleshooting | Error | Fix | |-------|-----| | "Browser not connected" | Run `opencli doctor` | | "attach failed: chrome-extension://" | Disable 1Password temporarily | | Element not found | `opencli browser scroll down && opencli browser state` | | Stale indices after page change | Run `opencli browser state` again | --- ## Auto-Fix Broken Adapters When an `opencli` command fails because a website changed its DOM/API, **automatically diagnose, fix, and retry** โ€” don't just report the error. ### Safety Boundaries - **`AUTH_REQUIRED`** (exit 77) โ€” STOP. Tell user to log in. Do not modify code. - **`BROWSER_CONNECT`** (exit 69) โ€” STOP. Tell user to run `opencli doctor`. - **CAPTCHA / rate limiting** โ€” STOP. Not an adapter issue. - **Only modify** the file at `RepairContext.adapter.sourcePath`. Never touch `src/`, `extension/`, `tests/`, `package.json`. - **Max 3 repair rounds** per failure. ### Before Repairing: "Empty" โ‰  "Broken" `EMPTY_RESULT` is often NOT a bug โ€” platforms degrade results under anti-scrape heuristics. Before patching: - Retry with an alternative query (e.g., `"AI ๆ”ป็•ฅ"` vs `"AI"`) - Spot-check in a normal Chrome tab โ€” if visible there but empty via adapter, it's auth/rate-limit - `0 results` from a search is a valid answer, not a bug ### Repair Workflow **Step 1: Collect diagnostics** ```bash OPENCLI_DIAGNOSTIC=1 opencli [args...] 2>diagnostic.json cat diagnostic.json | sed -n '/___OPENCLI_DIAGNOSTIC___/{n;p;}' ``` Output is a `RepairContext` JSON with: `error.code`, `adapter.sourcePath`, `adapter.source`, `page.snapshot`, `page.networkRequests`. **Step 2: Classify the failure** | Error Code | Likely Cause | Strategy | |-----------|-------------|----------| | SELECTOR | DOM restructured | Explore current DOM โ†’ find new selector | | EMPTY_RESULT | API response schema changed | Check network โ†’ find new response path | | API_ERROR | Endpoint URL changed | Discover new API via network intercept | | AUTH_REQUIRED | Cookies expired | **STOP** โ€” tell user to log in | | TIMEOUT | Page loads differently | Add/update wait conditions | **Step 3: Explore live site** ```bash # DOM changed opencli browser open https://example.com/target-page && opencli browser state # API changed opencli browser open https://example.com && opencli browser network opencli browser network --detail ``` **Step 4: Patch the adapter** Read `RepairContext.adapter.sourcePath` and make minimal targeted fixes: - Selector update: replace `.old-class` with `.new-class` - API endpoint: update URL in `fetch()` - Response schema: fix data path (`data.results` โ†’ `data.data.items`) **Step 5: Verify** ```bash opencli [args...] # Run without diagnostic mode ``` If still failing, go back to Step 1. After 3 rounds, stop and report what was tried. --- ## Create New Adapters ### Quick Method (4 steps โ€” for a single URL + goal) 1. **Open page + capture API** ```bash # Navigate and wait 3-5s for page to load # Check network for JSON APIs opencli browser open && opencli browser wait time 3 && opencli browser network ``` 2. **Lock down the target API** โ€” find the one returning `application/json` with the data you need 3. **Verify the API is reusable** ```bash # Test in browser context fetch('/api/endpoint', { credentials: 'include' }).then(r => r.json()) ``` 4. **Write adapter from template** (see templates below) **Auth decision tree:** ``` fetch(url) works? โ†’ Tier 1: PUBLIC (browser: false) fetch(url, {credentials:'include'}) works? โ†’ Tier 2: COOKIE (most common) Needs Bearer/CSRF header? โ†’ Tier 3: HEADER Page requests it but fetch can't? โ†’ Tier 4: INTERCEPT (installInterceptor) ``` ### Adapter Templates **Tier 1 โ€” Public API (fastest)** ```typescript // ~/.opencli/clis//.ts import { cli, Strategy } from '@jackwener/opencli/registry'; cli({ site: 'mysite', name: 'hot', description: 'ไธ€ๅฅ่ฏๆ่ฟฐ', domain: 'www.example.com', strategy: Strategy.PUBLIC, browser: false, args: [{ name: 'limit', type: 'int', default: 20 }], columns: ['rank', 'title', 'score'], func: async (_page, kwargs) => { const res = await fetch('https://api.example.com/hot'); const data = await res.json(); return data.items.slice(0, kwargs.limit).map((item: any, i: number) => ({ rank: i + 1, title: item.title, score: item.score, })); }, }); ``` **Tier 2 โ€” Cookie auth (most common)** ```typescript import { cli, Strategy } from '@jackwener/opencli/registry'; cli({ site: 'mysite', name: 'feed', description: 'ไธ€ๅฅ่ฏๆ่ฟฐ', domain: 'www.example.com', strategy: Strategy.COOKIE, browser: true, args: [{ name: 'limit', type: 'int', default: 20 }], columns: ['rank', 'title', 'value'], func: async (page, kwargs) => { await page.goto('https://www.example.com'); const data = await page.evaluate(`(async () => { const res = await fetch('/api/feed', { credentials: 'include' }); const d = await res.json(); return (d.data?.items || []).map(item => ({ title: item.title, value: item.value })); })()`); return (data as any[]).slice(0, kwargs.limit).map((item, i) => ({ rank: i + 1, title: item.title || '', value: item.value || '', })); }, }); ``` **Tier 4 โ€” Intercept (for sites with signing/anti-scrape)** ```typescript import { cli, Strategy } from '@jackwener/opencli/registry'; cli({ site: 'mysite', name: 'feed', description: 'ไธ€ๅฅ่ฏๆ่ฟฐ', domain: 'www.example.com', strategy: Strategy.INTERCEPT, browser: true, args: [{ name: 'limit', type: 'int', default: 20 }], columns: ['rank', 'title', 'value'], func: async (page, kwargs) => { await page.goto('https://www.example.com/target'); await page.wait(3); await page.installInterceptor('api-keyword'); // URL substring match await page.autoScroll({ times: 2, delayMs: 2000 }); // Trigger lazy load const requests = await page.getInterceptedRequests(); if (!requests?.length) return []; const results: any[] = []; for (const req of requests) { results.push(...(req.data?.data?.items || [])); } return results.slice(0, kwargs.limit).map((item, i) => ({ rank: i + 1, title: item.title || '', value: item.value || '', })); }, }); ``` ### Test After Writing ```bash npm run build # Syntax check opencli list | grep mysite # Confirm registered opencli mysite mycommand --limit 3 -v # Verify actual output ``` ### Full Exploration Guide (for new sites) When you need to explore a site from scratch rather than a single URL: **Step 1: API Discovery** ```bash opencli explore https://www.example.com --site mysite ``` Outputs to `.opencli/explore/mysite/`: `endpoints.json`, `capabilities.json`, `auth.json` Or auto-generate everything: ```bash opencli generate https://www.example.com --goal "hot" ``` **Key discovery tactics:** - **`.json` suffix trick** โ€” Reddit-style sites: just append `.json` to URL for clean REST data - **`__INITIAL_STATE__`** โ€” SSR sites (Bilibili, Xiaohongshu) embed data in `window.__INITIAL_STATE__` - **Active interaction** โ€” Lazy-load APIs (comments, subtitles) only appear after clicking a button - **Pinia/Vuex intercept** โ€” Vue sites: call store actions to trigger signed requests without reverse-engineering signatures **Step 2: Pick auth tier** โ€” run `opencli cascade ` to auto-detect **Step 3: Find existing adapter to copy** โ€” `ls clis//`, copy the closest one, change 3 fields: `name`, API URL, field mapping **Step 4: Test** ```bash opencli mysite hot --limit 3 -v # verbose: see pipeline data flow opencli mysite hot -f json | jq '.[0]' # confirm JSON structure ``` **Common pitfalls:** | Pitfall | Fix | |---------|-----| | Missing `navigate` before `evaluate` | Add `page.goto()` before evaluate | | Public API starts browser anyway | Add `strategy: Strategy.PUBLIC, browser: false` | | `evaluate` returns empty on SPA | Add `wait selector` or `wait time` before evaluate | | Cookie expired | Re-login in Chrome | | `EMPTY_RESULT` after intercept | Check if `installInterceptor` keyword matches actual URL |