Compare commits

...

179 Commits

Author SHA1 Message Date
jackwener 3984e0ae5d feat(extension): smart dual-track JS executor — chrome.scripting with CDP fallback
Replace chrome.debugger (CDP) with chrome.scripting.executeScript for
JS evaluation. Zero CDP fingerprint on sites without strict CSP.

- Add extension/src/scripting.ts: tries chrome.scripting first, auto
  falls back to CDP on CSP errors (e.g. Twitter/X, Google)
- Add 'scripting' permission + host_permissions to manifest.json
- Route exec through scripting executor, keep CDP only for screenshot
- Remember CSP-blocked tabs to skip straight to CDP on subsequent calls
2026-03-21 10:53:13 +08:00
jackwener 4e274a92b7 v1.1.1
Build Chrome Extension / build (push) Has been cancelled
Release / release (push) Has been cancelled
2026-03-21 10:49:39 +08:00
jakevin d1ade61e8c fix(twitter): rewrite trending from YAML to TS with DOM scraping fallback (#189)
* fix(twitter): rewrite trending from YAML to TS with DOM scraping fallback

The old REST API /i/api/2/guide.json returns 503. Replace with a TS
adapter that:
- Tries legacy guide.json API first (with proper auth headers)
- Falls back to DOM scraping via [data-testid='trend'] elements
- Filters out promoted content
- Follows the same Strategy.COOKIE pattern as timeline.ts

* fix: use 'help' instead of 'description' in Arg (matches Arg interface)

* docs(steam): add adapter documentation, update READMEs

- Create docs/adapters/browser/steam.md
- Add steam entry to README.md and README.zh-CN.md
- Fixes doc-coverage CI check (44/44)
2026-03-21 10:44:32 +08:00
jakevin 5d84c6f63e Merge pull request #187 from yanCode/codex/fix-apple-podcasts
Fix Apple Podcasts search query handling and top chart failures
2026-03-21 10:32:03 +08:00
Noah cbd50ccb91 feat(steam): add top sellers command (#178)
YAML adapter for Steam Store top selling games via public API.
Displays game name, price (in cents), discount %, and store URL.

Made-with: Cursor
2026-03-21 10:30:20 +08:00
Alex Yang 3f16d42e27 feat(twitter): add block, unblock, and hide-reply commands (#182)
Add three new Twitter/X UI-strategy commands:
- `block` / `unblock` — block or unblock a user by username
- `hide-reply` — hide a bot/spam reply on your own tweet thread
2026-03-21 10:27:22 +08:00
Sheng-Yan, Zhang c8e8c773c0 Fix apple-podcasts search and top handling 2026-03-21 09:24:41 +08:00
jakevin 5de920a994 docs: fix additional issues found in deep review (#181)
- Add missing codex/dump to README command tables
- Fix reddit subreddit --name → positional
- Update architecture.md for PR #152 engine.ts split:
  engine.ts → discovery.ts + execution.ts + commanderAdapter.ts
- Add intercept and ui strategies to auth table
2026-03-21 03:10:05 +08:00
Yunxiao_Li 476fe115de docs(chatgpt): sync read docs with AX behavior (#180) 2026-03-21 03:00:34 +08:00
jakevin 8d45019119 docs: sync documentation with PR #150 arg renames and positional changes (#179) 2026-03-21 02:59:22 +08:00
jackwener 36cf3067f7 feat: register gws CLI + use Commander passThroughOptions for external CLI passthrough (closes #147) 2026-03-21 02:29:36 +08:00
jakevin 516f1be3e6 refactor: deep CLI layer architecture improvements (#164)
CLI Layer:
1. execution.ts: auto-manages browser sessions, simplified signature
2. runtime.ts: add getBrowserFactory()
3. serialization.ts: new module for serialization helpers
4. cli.ts: format all built-in commands, extract inferHost()
5. commanderAdapter.ts: pure thin adapter

Src-wide cleanup:
6. download/index.ts: shared VIDEO_PLATFORM_DOMAINS, reuse isBinaryInstalled
7. Move site helpers: coupang/bilibili/chaoxing.ts -> clis/*/utils.ts
8. explore.ts: decompose into analyzeEndpoints, inferCapabilities, writeArtifacts

244 tests pass. 240 commands registered.
2026-03-21 02:24:12 +08:00
jakevin d556eeb512 refactor: deep CLI layer architecture improvements (#152)
1. execution.ts: executeCommand auto-manages browser sessions
   - Signature simplified: (cmd, kwargs, debug) — callers dont handle browser
   - Internal runCommand() does lazy-loading, func/pipeline dispatch
   - shouldUseBrowserSession + domain pre-nav moved here from adapter

2. runtime.ts: add getBrowserFactory()
   - Eliminates 4x duplicate CDPBridge/BrowserBridge selection

3. serialization.ts: new module (79 LOC)
   - serializeArg, serializeCommand, formatArgSummary, formatRegistryHelpText
   - registry.ts re-exports for backward compat (160 -> 96 LOC)

4. cli.ts: format all built-in commands
   - Un-compressed explore/generate/cascade from 300-char single lines
   - Extracted inferHost() helper
   - Uses getBrowserFactory() instead of inline CDPBridge selection
   - Clear section comments

5. commanderAdapter.ts: pure thin adapter (113 LOC)
   - Only does: arg collection → executeCommand → renderOutput
   - Zero browser/session/strategy logic

All 244 tests pass. No behavioral changes.
2026-03-21 02:09:46 +08:00
jakevin d7c895592f refactor!: standardize all CLI arg names to kebab-case (#150)
* refactor!: standardize all CLI arg names to kebab-case

BREAKING CHANGE: All CLI argument names have been renamed for consistency.

Renames:
- keyword -> query (11 search commands)
- bookId -> book-id (weread)
- productId -> product-id (coupang)
- post_id -> post-id (reddit)
- job_id -> job-id (boss)
- tweet_id -> tweet-id (twitter)
- note_id -> note-id (xiaohongshu)
- security_id -> security-id (boss)
- model_name -> model-name (chatwise/codex/cursor)
- max_length -> max-length (reddit)
- experience_level -> experience-level (linkedin)
- job_type -> job-type (linkedin)
- date_posted -> date-posted (linkedin)

36 renames across 32 files. All 240 commands now use kebab-case.

* refactor!: standardize positional vs --named arg style

BREAKING CHANGE: Required "subject" args are now positional.

Rules applied:
- query, id, text, url, username → positional (when required)
- output → always --named

Also fixes engine.ts YAML arg parser to support positional property.

50 changes across 49 files. All 240 commands verified consistent.
2026-03-21 01:53:11 +08:00
jakevin eeace115cb fix: harden external CLI hub — command injection, denylist, sync API, build-copy (#149) 2026-03-21 01:26:02 +08:00
jakevin 35676a101f refactor: extract serialization helpers to registry.ts and stabilize arg schema (#148)
- Add serializeArg() with stable schema (all fields always present)
- Add serializeCommand() for structured output (json/yaml)
- Add formatArgSummary() for human-readable arg display (<required> [--optional])
- Add formatRegistryHelpText() for --help appendix
- Refactor cli.ts to use these shared helpers (~30 lines removed)
- Non-structured formats now show arg signatures instead of comma-joined names
2026-03-21 01:21:18 +08:00
AstroHan cd0c6f874e feat: enhance --help with registry metadata and enrich list --json with full arg schema (#142)
* feat: add `opencli describe` command for unified CLI capability discovery

Add a new `describe` command that helps AI agents discover and understand
both built-in site commands and external CLI tools through a single entry point.

- Built-in commands: reads structured data from CliCommand registry
  (args with type/choices/default, columns, strategy, domain)
- External CLIs: collects help text via `binary --help`, extracts
  subcommand names + summaries, passes through raw help text
- Supports `--format json` for programmatic consumption by AI agents
- Graceful degradation: parse failures return raw help text, uninstalled
  CLIs show install instructions without triggering auto-install

Closes #141

* fix: address code review findings for describe command

- Strip trailing colons from Cobra-style subcommand names (browse: → browse)
- Use CliError instead of bare Error for consistent error handling with hints
- Remove decorative section separators to match project comment style
- Validate --format flag (text/json only) with clear error message
- Truncate raw help output to 50 lines to prevent excessive output
- Add deduplication test for multi-section command groups

* refactor: replace describe command with enhanced --help and list --json

Per maintainer feedback, remove the standalone `describe` command and instead:

1. Enhance --help for all built-in commands:
   - Show argument choices (from registry, not shown by Commander)
   - Show execution metadata: Strategy / Browser / Domain
   - Show output columns

2. Enhance `list -f json/yaml` with full argument schema:
   - args field now includes type, required, positional, choices, default, help
   - Added columns and domain fields for structured formats
   - Table/csv/md formats unchanged (args remain comma-joined names)

This follows the principle that --help is the standard CLI discovery
mechanism and AI models already know to use it.

* fix: stabilize JSON schema and fix positional choices rendering

- Always output columns/domain in json/yaml ([] and null when empty)
- Use <name> instead of --name for positional args with choices
- Remove extra blank line when no choices args present
2026-03-21 01:12:05 +08:00
ajia1206 0b71c6c4da fix: correct xiaohongshu creator metric parsing (#146) 2026-03-21 01:02:51 +08:00
Kasumi 7700704923 feat: add Bloomberg adapter (#145)
* feat: add Bloomberg adapter with RSS feeds and article extraction

* refactor: improve Bloomberg adapter review fixes

- news.ts: reorder flow (goto before loadStory), increase wait times for slow hydration, add clarity comments
- utils.ts: clarify validateBloombergLink regex (use non-capturing group)
- build-manifest.ts: log warning on scanTs parse failure (match scanYaml pattern)
- public-commands.test.ts: use it.each for section RSS tests (better isolation & reporting)

---------

Co-authored-by: ByteYue <yj976240184@gmail.com>
2026-03-21 00:35:30 +08:00
jackwener 4d3b972d67 feat: auto-discover and dynamically register any local CLI on the fly 2026-03-20 23:00:27 +08:00
AlexYue 15d3583c60 docs: add missing adapter docs, fix sidebar 404s, add doc-check CI (#140)
* docs: add missing adapter docs, fix sidebar 404s, add doc-check CI

- Add doc pages for 11 undocumented adapters: arxiv, barchart,
  chaoxing, grok, hf, jike, jimeng, linux-do, sinafinance,
  stackoverflow, weread, wikipedia
- Update adapters/index.md with all new adapter entries
- Update VitePress sidebar config with 12 new entries
- Remove broken zh/ sidebar refs (troubleshooting, testing)
- Add doc-check CI workflow (adapter coverage + build + link check)
- Add scripts/check-doc-coverage.sh for adapter doc enforcement
- Enhance PR template with adapter doc checklist

* fix(ci): use --root-dir instead of --base for lychee link checker

lychee v0.23 requires --base to be a URL or absolute path.
Use --root-dir for resolving root-relative links in local files.

* fix(ci): remove lychee link-check job, rely on VitePress build

VitePress links use extension-less paths (e.g. /adapters/browser/twitter)
which lychee cannot resolve. The docs-build job already catches all
broken internal links via VitePress dead link detection during build.
2026-03-20 22:08:38 +08:00
AlexYue 53a95ed0ce Open ci: migrate docs deployment to cross-repo build via opencli-website (#138) 2026-03-20 20:55:08 +08:00
jackwener 0a2591842c docs: emphasize AI agent integration via AGENT.md 2026-03-20 20:50:34 +08:00
jakevin d9a71da596 chore(main): release 1.1.0 (#134) 2026-03-20 20:40:42 +08:00
jackwener 9a79501bfd 1.1.0
Build Chrome Extension / build (push) Has been cancelled
Release / release (push) Has been cancelled
2026-03-20 20:37:30 +08:00
jackwener 46d0f00aa6 chore: bump SKILL.md version to 1.1.0 2026-03-20 20:37:29 +08:00
jackwener b3e32d8a05 feat: add external CLI hub for discovery, auto-installation, and execution of external tools. 2026-03-20 20:30:40 +08:00
jackwener 36bc57a962 fix(serve): update model mappings to match actual Antigravity UI
- Default to 'claude sonnet 4.6'
- Map 'sonnet' -> 'claude sonnet 4.6'
- Map 'opus' -> 'claude opus 4.6'
- Map 'gemini.*pro' -> 'gemini 3.1 pro (high)'
- Map 'gemini.*flash' -> 'gemini 3 flash'
- Map 'gpt' -> 'gpt-oss 120b'
2026-03-20 18:55:45 +08:00
jackwener 0e8c96b6d9 feat(serve): implement auto new conv, model mapping, and precise completion detection
- Auto-click New Conversation if session has only 1 message
- Map Anthropic models (claude-3-7-sonnet) to Antigravity UI models
- Refactor waitForReply to check for Cancel/Stop button presence to
  detect generation completion reliably, with text stability fallback
2026-03-20 18:52:00 +08:00
jackwener c63af6d418 feat(serve): use CDP mouse click + Input.insertText for reliable message injection
- Replace document.execCommand (deprecated) with CDP Input.insertText
- Use Input.dispatchMouseEvent to physically click + focus the Lexical editor
  before text injection (fixes focus issues with JS-only .focus())
- Improve getLastAssistantReply: strip echoed user message, thinking blocks,
  Copy button text, and de-duplicate repeated content artifacts
2026-03-20 18:24:49 +08:00
jackwener 35a0fed8a0 feat: add antigravity serve command — Anthropic API proxy
- New command: opencli antigravity serve --port 8082
- Starts HTTP server compatible with Anthropic /v1/messages API
- Connects to Antigravity via CDP (OPENCLI_CDP_ENDPOINT)
- Uses Input.dispatchKeyEvent for reliable Enter key submission
- Polls for reply with text-change detection + 3s stability check
- Precise DOM walker for extracting last assistant reply
- Lazy CDP connection (connects on first request)
- Auto-reconnect on CDP connection loss
- CORS headers for Claude Code compatibility

Usage:
  OPENCLI_CDP_ENDPOINT=http://127.0.0.1:9224 opencli antigravity serve
  ANTHROPIC_BASE_URL=http://localhost:8082 claude
2026-03-20 18:16:41 +08:00
jackwener 593436e4cb fix(xiaohongshu): use fixed UTC+8 offset in trend timestamp formatting (CI timezone fix) 2026-03-20 17:57:31 +08:00
jackwener 02793e990e feat: add sinafinance 7x24 news adapter (#131)
Based on PR #131 by larria, with fixes applied:
- Renamed command 724 → news for clarity
- Fixed indentation to 2-space project standard
- Added SinaNewsItem type (removed item: any)
- Added res.ok check + CliError for HTTP failures
- Added stripHtml() for rich_text content
- Updated README, README.zh-CN, SKILL.md

Co-authored-by: larria <1115524+larria@users.noreply.github.com>
2026-03-20 17:22:42 +08:00
jackwener 03f067d907 fix: use UTC+8 for XHS timestamp formatting (CI timezone fix)
formatPostTime() used local timezone methods, causing test failure
on UTC CI servers. XHS API timestamps are Beijing time (UTC+8),
so use explicit UTC offset with getUTC*() methods.
2026-03-20 17:13:00 +08:00
云比云 7e973ca592 feat(boss): add 8 new recruitment management commands (#133)
New commands:
- joblist: view my published jobs
- recommend: view recommended candidates (new greetings list)
- greet: send greeting to initiate chat with candidate
- mark: add/remove labels on candidates
- invite: send interview invitation
- stats: job statistics (chats count)
- batchgreet: batch greet recommended candidates
- exchange: request phone/wechat exchange

All commands tested and build passing (222 entries).
2026-03-20 17:10:48 +08:00
jackwener 4600b9d46d fix: type safety for wikiFetch and arxiv abstract truncation
- wikiFetch return Promise<unknown> instead of Promise<any>
- Add WikiSearchResult type, remove r: any
- Type wikiFetch responses with inline type assertions
- Only append ... to abstract when actually truncated
2026-03-20 17:08:30 +08:00
BruceLoveDecimal 3cda14a2ab feat: add arxiv and wikipedia adapters (#132)
Add arXiv (search, paper) and Wikipedia (search, summary) public API adapters.

- arxiv/search: search papers by keyword
- arxiv/paper: get paper details by ID  
- wikipedia/search: search articles with lang support
- wikipedia/summary: get article summary

Type safety fixes applied: wikiFetch returns unknown, typed search results.

Co-authored-by: BruceLoveDecimal <39156883+BruceLoveDecimal@users.noreply.github.com>
2026-03-20 17:08:17 +08:00
jackwener 4f74b45963 refactor: remove raw CDP code, use IPage throughout
- Remove fetchCreatorNotesByCdp() and captureNoteDetailApiPayload() raw
  WebSocket code (~240 lines) — adapters should use IPage, not raw CDP
- Replace direct CDP WebSocket with IPage.evaluate() in-page fetch
- Fix page: any → IPage in all function signatures
- Simplify to two-tier fallback: API+interceptor → DOM parse
- Rebase onto latest main (resolves cdp.ts/daemon.ts conflicts)
2026-03-20 16:34:08 +08:00
ajia1206 8f1725982e feat: xiaohongshu creator flows migration (#124)
Migrated xiaohongshu creator flows to v1.0.2+.
- creator-notes with API + DOM fallback
- creator-note-detail with audience/trend data
- creator-notes-summary batch overview
- Tests: 3 files, 9 tests

Co-authored-by: ajia <491387123@qq.com>
2026-03-20 16:33:48 +08:00
AlexYue 2876750891 fix(docs): use base '/' for custom domain and add CNAME file (#129)
- Change VitePress base from '/opencli/' to '/' for custom domain opencli.info
- Add docs/public/CNAME so GitHub Pages preserves custom domain on re-deploy
2026-03-20 16:31:28 +08:00
jakevin 4ab4f88bcd chore(main): release 1.0.6 (#128)
Build Chrome Extension / build (push) Has been cancelled
Release / release (push) Has been cancelled
2026-03-20 16:25:30 +08:00
AlexYue 9eb7a1eaa1 docs: add VitePress documentation site with GitHub Pages deployment (#127)
* docs: deduplicate documentation — single source of truth in docs/

- Remove root CDP.md, CDP.zh-CN.md, CLI-ELECTRON.md (now in docs/advanced/)
- Slim adapter READMEs to one-liner + link to docs/ (11 files)
- Update README.md adapter table links to point to docs/

* docs: set VitePress base path for GitHub Pages deployment
2026-03-20 16:08:34 +08:00
Chencheng Li 4cabca12df fix: use %20 instead of + for spaces in Bilibili WBI signed requests (#126)
URLSearchParams.toString() encodes spaces as +, but Bilibili's WBI
signature verification expects %20. This mismatch causes search
queries with spaces (e.g. "亚马逊 滞销产品") to fail with
TypeError: Failed to fetch due to CORS-blocked error responses.

Fixes #125

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 16:07:46 +08:00
jackwener fafa990acd v1.0.5
Build Chrome Extension / build (push) Has been cancelled
Release / release (push) Has been cancelled
2026-03-20 15:51:52 +08:00
jackwener 3bde01aa1c fix: prevent duplicate command registration crash
The build manifest includes antigravity/serve which collides with the
hardcoded antigravity serve in cli.ts. Add a guard to skip registry
entries whose subcommand already exists in the site group.
2026-03-20 15:51:51 +08:00
jackwener 152cc48091 v1.0.4
Release / release (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
2026-03-20 15:36:06 +08:00
jackwener dff8f1e9c4 refactor: deep audit fixes P0-P3
P0: page.ts screenshot async I/O, cdp.ts send() 30s timeout
P1: cdp.ts event-based goto, implement scroll/screenshot/networkRequests,
    extract dom-helpers.ts shared module for Page/CDPPage
P2: engine.ts readdir withFileTypes, explore.ts parallel refetch
P3: registry.ts strategy ordering, output.ts CSV \r escape,
    interceptor.ts error tracking array
2026-03-20 15:27:07 +08:00
jackwener 9d8b6441be feat: Add antigravity serve command to start an Anthropic-compatible API proxy server for Antigravity via CDP. 2026-03-20 14:31:13 +08:00
Kasumi 1e0e4cd660 fix(manifest): infer browser mode for public TS adapters (#115) 2026-03-20 14:24:26 +08:00
K1tyoo 024d9908b3 feat(hf): add top command for hf papers (daily, weekly, monthly) (#110)
* feat(hf): add top command for hf papers (daily, weekly, monthly)

* feat(footer): add footerExtra support and derive dates from API response

Add footerExtra callback to CliCommand for custom table footer content.
For weekly/monthly periods, derive date range from API response publishedAt
field with local clock fallback.

* fix: truncate long paper titles

* refactor(hf): remove comments column for consistent output

* feat(hf): add --all flag to return all papers

* feat(hf): add paper id column to output

* fix: restore main.ts as bootstrap, sync footerExtra + CDPBridge + domain pre-nav to cli.ts

- main.ts should remain a lightweight entry point delegating to cli.ts
- Preserve CDPBridge fallback (OPENCLI_CDP_ENDPOINT) — PR had hardcoded BrowserBridge only
- Add domain pre-navigation for cookie/header strategies to cli.ts
- footerExtra feature from PR is properly integrated

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-20 14:23:06 +08:00
jackwener 943e286815 chore: track package-lock.json for CI reproducibility 2026-03-20 14:14:01 +08:00
AlexYue 31f58ae699 docs: add VitePress documentation site (#112)
- Add VitePress with full navigation, sidebar, i18n (en/zh), local search
- Create 50+ doc pages: guide, adapters (browser + desktop), developer, advanced
- Migrate content from README.md, CONTRIBUTING.md, TESTING.md, CDP.md, CLI-ELECTRON.md
- Migrate all 11 adapter READMEs to structured documentation
- Add new pages: architecture, yaml-adapter guide, ts-adapter guide, ai-workflow
- Add GitHub Actions workflow for deploying to GitHub Pages
- Add Chinese locale pages (getting-started, installation, browser-bridge, etc.)
- Add docs:dev, docs:build, docs:preview npm scripts
2026-03-20 14:11:56 +08:00
ylongwang 812db29ed8 fix(smzdm): navigate to search page directly instead of deprecated ajax API (#113)
The old adapter called `search.smzdm.com/ajax/?c=<channel>&s=<q>` which
now returns 404. This caused opencli smzdm search to always return empty
results regardless of keyword.

Fix: navigate directly to `search.smzdm.com/?c=home&s=<keyword>&v=b`
and scrape the rendered DOM via querySelectorAll('li.feed-row-wide').

Also switched from async IIFE to sync IIFE since all data is already in
the DOM after page load — no fetch needed.

Tested: opencli smzdm search --keyword A7M5 returns correct results
with prices and mall names.
2026-03-20 14:11:20 +08:00
jackwener 47a898125f feat: add conservative capability routing 2026-03-20 14:10:51 +08:00
VK b60c69950d feat(stackoverflow): add search, hot, unanswered, and bounties commands (#116) 2026-03-20 14:10:15 +08:00
Wing Huang ce38a1604e feat(boss): add resume command to view candidate profile (#119)
Adds 'opencli boss resume --uid <uid>' command that scrapes the chat page
right panel to display candidate resume information including:
- Basic info: name, gender, age, experience, degree, active status
- Work history: time period + company + position
- Education: time period + school + major + degree
- Job being discussed and candidate expectations

Uses UI scraping approach since BOSS Zhipin does not expose a public API
for candidate resume data on the recruiter side.
2026-03-20 14:08:29 +08:00
AstroHan d6e0aa120b feat(jike): add Jike adapter with 10 commands (#117)
Add comprehensive Jike (即刻) adapter covering read and write operations.

Read commands:
- user: user posts via m.okjike.com SSR JSON
- topic: topic/circle posts via m.okjike.com SSR JSON
- post: post detail with comments via m.okjike.com SSR JSON
- feed: home timeline via React fiber tree extraction
- search: search posts via React fiber tree extraction
- notifications: notification list via DOM innerText parsing

Write commands (Strategy.UI, browser DOM automation):
- create: publish post via inline compose box
- comment: comment on post via contenteditable paste
- like: like post via _likeButton_ div click
- repost: repost via action bar → popover menu → confirm

Implementation details:
- Three data extraction strategies: SSR JSON, React fiber, DOM manipulation
- Shared JikePost interface and getPostData helper in shared.ts
- All evaluate blocks include try/catch error handling
- Two rounds of parallel Claude + Codex code review applied
2026-03-20 14:08:12 +08:00
jackwener 44f0bbe94d feat: add workspace-aware browser sessions 2026-03-20 13:41:04 +08:00
jackwener 0ea6e4a15c fix: address review findings and docs cleanup 2026-03-20 12:28:30 +08:00
jackwener ee35ee723f chore: release version 1.0.3
Build Chrome Extension / build (push) Has been cancelled
Release / release (push) Has been cancelled
2026-03-20 11:27:56 +08:00
jackwener 92fc13d60e docs: update extension installation instructions 2026-03-20 11:22:39 +08:00
jackwener f5f7a9500e chore: rename extension to OpenCLI 2026-03-20 11:03:02 +08:00
jackwener 3229294f08 ci: remove redundant build step in release workflow 2026-03-20 11:01:11 +08:00
jackwener f945b51f43 ci: fix non-existent v6 actions causing workflows to fail instantly 2026-03-20 10:59:34 +08:00
jackwener e9a3ef7538 chore: merge feature/ext-github-action and resolve conflicts 2026-03-20 10:57:37 +08:00
jackwener 39b6413e47 Merge branch 'refactor/remove-any'
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-03-20 10:56:46 +08:00
jackwener 33957ea0ba chore: save local changes 2026-03-20 10:56:19 +08:00
jackwener 691f835bdf chore: ignore extension build artifacts and pem keys 2026-03-20 10:54:03 +08:00
jackwener 5b447e7a11 refactor: strictly type output and registry pipelines, removing any where possible 2026-03-20 10:53:25 +08:00
jackwener d7bf5d6e04 ci: add github action for building extension zip and crx 2026-03-20 10:51:27 +08:00
AlexYue 390dbe7199 fix: use JSON.stringify for safe JS string interpolation in evaluate() (#109)
Replace ad-hoc string escaping with JSON.stringify() for values
interpolated into JavaScript code strings passed to page.evaluate().

- explore.ts: clickLabels were escaped with only single-quote
  replacement, which breaks on labels containing backslashes or
  newlines. JSON.stringify() handles all edge cases correctly.

- synthesize.ts: buildEvaluateScript() embedded URLs directly inside
  single quotes. JSON.stringify() safely handles URLs containing
  special characters.
2026-03-20 10:47:09 +08:00
jackwener 40846291e6 feat: introduce opencli command-line interface with web exploration, generation, and validation tools, and refactor browser utilities. 2026-03-20 10:47:05 +08:00
Wing Huang f8dea7d8fc feat(boss): add chatlist, chatmsg, and send commands (#95)
- boss/chatlist: List chat conversations (招聘端聊天列表)
  Uses getBossFriendListV2 API with pagination and job filter support.

- boss/chatmsg: Read chat message history with a candidate
  Resolves encryptUid to numeric uid/securityId, fetches via historyMsg API.

- boss/send: Send chat message to a candidate via UI automation
  BOSS chat uses MQTT protocol (not HTTP), so this command automates the
  web chat UI: clicks on user in list → types in contenteditable editor →
  clicks the send button.

All three commands use Strategy.COOKIE and require an active BOSS直聘
login session in Chrome.
2026-03-20 10:40:20 +08:00
Yunxiao_Li 67474bb6db fix(chatgpt): read replies from AX tree instead of clipboard shortcut (#106) 2026-03-20 10:28:27 +08:00
AstroHan d1986f0144 fix(twitter): replace search input approach with pushState+popstate SPA navigation (#105)
The previous approach (nativeSetter + Enter keydown on the search input)
does not reliably trigger Twitter's form submission - the synthetic
KeyboardEvent is ignored by React, leaving the page on /explore with
zero API calls captured.

Use history.pushState + PopStateEvent instead, which triggers React
Router's listener and performs a true SPA navigation to /search.
The interceptor survives because no full page reload occurs.

Tested: "opencli", "it's a test" (single quote), "hello" all return
results with correct author attribution.
2026-03-20 10:20:38 +08:00
zhutiancillm ebc5c09ad9 fix(twitter): fix newline handling in post command via clipboard paste (#107)
Co-authored-by: zhutiancillm <zhutiancillm@users.noreply.github.com>
2026-03-20 10:20:13 +08:00
bhwang 055403abc1 docs: correct note_id params for xiaohongshu (#108) 2026-03-20 10:19:50 +08:00
jackwener 10af754c89 docs: align CDP release notes 2026-03-20 00:51:55 +08:00
jackwener a7e5307226 feat: Add Chrome DevTools Protocol (CDP) support as an alternative browser automation backend, configurable via OPENCLI_CDP_ENDPOINT.
Release / release (push) Has been cancelled
2026-03-20 00:48:39 +08:00
jackwener c21250bd88 1.0.1 2026-03-20 00:29:10 +08:00
dev-Flyblue 0fa9573790 feat: add Chaoxing (学习通) adapter — assignments & exams (#101)
Add CLI commands to view Chaoxing assignments and exams by reusing
Chrome login session via the Browser Bridge.

Chaoxing has no flat API for listing assignments/exams. The adapter
follows the browser flow: establish session → fetch course list via
backclazzdata API → enter each course via stucoursemiddle redirect →
click tab to capture iframe URL → navigate and parse DOM.

Commands:
  opencli chaoxing assignments [--course <name>] [--status] [--limit]
  opencli chaoxing exams [--course <name>] [--status] [--limit]

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 00:14:08 +08:00
AstroHan 65e30b9ac4 fix(intercept): IIFE wrapping for installInterceptor/getInterceptedRequests (#100)
* fix(intercept): use evaluate() for IIFE wrapping in installInterceptor/getInterceptedRequests

Root cause: daemon migration changed these methods from this.evaluate()
to direct sendCommand('exec'), losing the wrapForEval() IIFE wrapping.
CDP received bare arrow functions that were never invoked.

Fixes #98

* fix(twitter): SPA navigation, data path, and author resolution for INTERCEPT commands

- followers/following: install interceptor on profile page, then click
  followers/following link (SPA navigation preserves JS context).
  Use JSON.stringify for targetUser to prevent injection. Throw on
  navigation failure. Update selector: /verified_followers.
- notifications: install interceptor on home, then pushState+popstate
  to /notifications. Validate navigation URL.
- search: fix author resolution (core.screen_name, not legacy).
- All: fix GraphQL data path (remove extra .data level), update author
  resolution to try core.screen_name before legacy.screen_name.
- followers: remove erroneous .filter(r => r?.url) — interceptor stores
  response body JSON, URL filtering happens at capture time.
2026-03-20 00:13:20 +08:00
jackwener 540f3c677a docs: add desktop app adapters section to root READMEs
Integrate README links for all 10 desktop app CLI adapters:
- Cursor, Codex, Antigravity, ChatGPT, ChatWise
- Notion, Discord, Feishu, WeChat, NeteaseMusic
2026-03-20 00:10:20 +08:00
jackwener b5c1b242e2 fix(extension): use idle-timeout for automation window lifecycle
Replace eager close-window (which caused race conditions when
parallel commands shared the window) with an idle-based timer:

- Window auto-closes 30s after the last command completes
- Each incoming command resets the idle timer
- Consecutive commands reuse the same window (faster)
- No race conditions with parallel execution
- Close-window action kept for explicit cleanup if needed
2026-03-20 00:05:48 +08:00
jackwener 2f6d28a3e9 feat(extension): auto-close automation window after command completes
- Add 'close-window' action to extension protocol and background.ts
- Add Page.closeWindow() method to send close-window command
- browserSession() now closes automation window in cleanup
- Remove domain pre-navigation + 2s wait from main.ts (CDP handles
  cross-domain cookies natively, no same-origin workaround needed)
- Net effect: commands run faster, no stale windows left behind
2026-03-19 23:59:31 +08:00
jackwener fde618063f chore: pre-release cleanup
- Delete unused extension/src/executor.ts (chrome.scripting experiment)
- Remove 15 no-op backward-compat exports from doctor.ts
- Remove getTokenFingerprint no-op from browser/index.ts
- Rename PlaywrightMCP → BrowserBridge across all source files
  (backward-compat alias kept in mcp.ts and browser/index.ts)
- Remove unnecessary host_permissions from extension manifest
- Sync extension package.json version to 0.2.0
- All 14 tests pass
2026-03-19 23:51:36 +08:00
jackwener 89947fee50 feat(extension): isolated automation window
All opencli operations now run in a dedicated Chrome window instead
of hijacking the user's active tab. The automation window:
- Created on first command via chrome.windows.create({ focused: false })
- 1280x900 viewport, auto-cleaned up when closed
- All tabs resolved within this window only
- User's main browsing session is never touched

Tested: twitter trending , zhihu hot 
2026-03-19 23:34:27 +08:00
jackwener 2e962b2e7c chore: pre-release cleanup
- Fix daemon per-command timeout: 30s → 120s (was shorter than CLI-layer timeouts)
- Remove debug command: grok/debug.ts
- Sync extension version to 1.0.0
- Rename PlaywrightMCP → BrowserBridge (keep backward-compat alias)
- Add accept/reply-dm to README command tables
- Clean up consoleMessages() JSDoc in page.ts
- Update runtime.ts comment
2026-03-19 22:49:54 +08:00
jackwener 13e2345089 feat(twitter): add scroll-to-load for accept and reply-dm
Both commands now scroll the conversation list to load more items
before processing. Scrolls up to 20-30 times, stops after 3
consecutive scrolls with no new items loaded.

Previously limited to ~14 visible conversations, now loads as many
as needed (up to --max).
2026-03-19 22:03:36 +08:00
jackwener 4bf946edaf feat(twitter): add accept and reply-dm commands
accept: Auto-accept DM requests matching keywords (comma-separated OR)
  opencli twitter accept --keyword '群,微信' --max 20

reply-dm: Send message to recent DM conversations with skip-replied
  opencli twitter reply-dm --text '我的微信 wxkabi' --max 20

Both commands:
- Use click-based DOM interaction (data-testid selectors)
- 10-minute timeout for batch operations
- Support new Twitter /i/chat UI and /messages URL
2026-03-19 21:56:30 +08:00
jackwener 4398202b05 fix(twitter): rewrite accept command + per-command timeout
- Rewrite accept.ts: use [data-testid=conversation] click-based approach
  instead of extracting href links (requests page has no /messages/xxx links)
- Support comma-separated keywords for OR matching (e.g. '群,微信')
- Add timeoutSeconds: 600 (10 min) for batch DM operations
- Bump default OPENCLI_BROWSER_COMMAND_TIMEOUT from 45s to 60s
- Track visited conversations to avoid infinite loops
2026-03-19 21:37:34 +08:00
jackwener b01bf6769b feat(twitter): add accept command to auto-accept DM requests by keyword
Usage:
  opencli twitter accept --keyword '微信' --max 20

Workflow:
1. Navigate to /messages/requests
2. Click into each conversation
3. If message contains keyword, click Accept
4. After accept (auto-redirects to /messages), go back to requests
5. Repeat until --max reached or no more matches
2026-03-19 21:26:08 +08:00
jackwener 6d3e595d36 fix: daemon spawn uses --import tsx/esm for dev mode .ts files
process.execPath is always plain 'node' even under tsx,
so .ts files could not be executed. Use --import tsx/esm
flag to enable TypeScript loading in spawned daemon.
2026-03-19 21:05:48 +08:00
AstroHan edb21ca67b feat: add WeRead (微信读书) adapter with 7 commands (#89)
Add weread adapter for issue #82, covering search, rankings, book details,
bookshelf, notebooks, highlights, and notes.

Public commands (no login required):
- weread search <keyword> — search books
- weread ranking [category] — book rankings (all/rising/category ID)

Private commands (cookie auth via browser):
- weread book <bookId> — book details
- weread shelf — personal bookshelf
- weread notebooks — books with highlights/notes
- weread highlights <bookId> — underlines in a book
- weread notes <bookId> — personal notes on a book

Closes #82
2026-03-19 21:03:20 +08:00
Pleasure1234 1f270397f6 fix: dedupe history and improve Discord channel parsing (#77) 2026-03-19 20:58:40 +08:00
BruceLoveDecimal aa2f37be32 Add apple-podcasts coverage and docs (#92)
Co-authored-by: 刘启灏 <liuqihao@liuqihaodeMacBook-Pro.local>
2026-03-19 20:58:08 +08:00
AstroHan aeb1cb6a3a fix: install XHR interceptor after navigation to prevent context reset (#91)
goto() triggers a full page navigation that resets the JS execution
context, wiping any previously injected fetch/XHR monkey-patches.
The old code installed the interceptor on x.com then navigated away,
so the interceptor was always destroyed before it could capture data.

Fix: navigate directly to the target page, install interceptor after
page load, then scroll to trigger API calls via pagination.

Also fixes the same bug in notifications.ts.

Closes #86
2026-03-19 20:57:28 +08:00
jackwener 4e260ecdeb fix: include pre-built extension dist/ for zero-step install 2026-03-19 20:54:49 +08:00
jackwener f7c7230854 fix: include pre-built extension dist/ in repo for zero-step install 2026-03-19 20:53:45 +08:00
jackwener 48e277bd0b 1.0.0
Release / release (push) Has been cancelled
2026-03-19 17:00:45 +08:00
jackwener 8bb03ecc9b feat: replace Playwright MCP with lightweight daemon + Chrome Extension
Major architecture change:
- Replace @playwright/mcp with lightweight micro-daemon + Chrome Extension
- Zero-config: no tokens, no MCP server, auto-start daemon
- Extension: 10.55KB gzipped, 4+1 action protocol
- Graceful shutdown, exponential backoff, log forwarding
- All docs updated for Browser Bridge architecture
2026-03-19 17:00:29 +08:00
jackwener 3b6f72ca08 docs: fix extension install instructions — no store yet, no restart needed
- Remove 'Chrome Web Store' references (not published yet)
- Add detailed unpacked extension install steps (chrome://extensions)
- Remove 'restart Chrome' advice (Service Worker activates immediately)
- Direct users to chrome://extensions for troubleshooting
2026-03-19 16:57:40 +08:00
jackwener beda0b714c docs: remove all remaining Playwright references from docs
Updated 6 files:
- CDP.md, CDP.zh-CN.md: Browser Bridge instead of Playwright MCP Bridge
- CLI-ELECTRON.md: Browser Bridge / IPage abstraction wording
- CLI-EXPLORER.md: browser tools instead of Playwright MCP tools
- TESTING.md: Browser Bridge extension mode, removed token references
- src/clis/chatgpt/README{,.zh-CN}.md: CDP instead of Playwright

Zero Playwright references remaining across all .md files.
2026-03-19 16:55:20 +08:00
jackwener 3b33ade214 docs: update README, README.zh-CN, SKILL.md for new Browser Bridge architecture
- Replace all Playwright MCP Bridge references with opencli Browser Bridge
- Remove token setup, MCP config, and manual setup sections
- Simplify prerequisites: just install extension, zero config
- Update troubleshooting: daemon status/logs commands
- Update env vars: add OPENCLI_DAEMON_PORT, OPENCLI_VERBOSE
- Update SKILL.md tags: mcp,playwright → chrome-extension,cdp
2026-03-19 16:49:10 +08:00
jackwener ebe4683a9e fix: add screenshot mock to executor.test.ts for IPage compat
tsc --noEmit failed because createMockPage() was missing the
screenshot() method added to IPage in the round 2 review fix.
2026-03-19 16:43:58 +08:00
jackwener 59c0d639a5 refactor: fix 9 issues from round 2 code review
Bug fixes:
- #1 /logs?level=error returned 404 — use pathname for route matching
- #2 Duplicate initialization — added 'initialized' guard flag

Should fix:
- #4 Added screenshot() to IPage interface
- #5 Graceful shutdown rejects pending requests before exit
- #6 Use process.execPath instead of 'npx tsx' for faster daemon spawn

Cleanup:
- #7 Removed duplicate 'browser' keyword in package.json
- #8 Removed unused normalizeEvaluateSource import from browser.ts
- #9 Changed dynamic import to static import in intercept.ts
- #10 Added explicit throw at end of sendCommand for clarity

61 tests pass (4 test files). Extension: 10.55KB.
2026-03-19 16:36:06 +08:00
jackwener 3d1f9640ea feat: forward extension console logs to daemon
Extension side:
- Hook console.log/warn/error → forward via WS as { type: 'log', level, msg, ts }
- Original console output preserved (for chrome://extensions debug)

Daemon side:
- Ring buffer (200 entries) stores extension logs
- Logs printed to daemon stderr with emoji prefix (📋/⚠️/)
- GET /logs — returns buffered logs (optional ?level= filter)
- DELETE /logs — clears log buffer

Usage:
  curl localhost:19825/logs              # view all logs
  curl localhost:19825/logs?level=error  # errors only
  curl -X DELETE localhost:19825/logs    # clear buffer

Extension build: 10.48KB
2026-03-19 16:21:17 +08:00
jackwener 8e8c4a0229 feat: add exponential backoff reconnect + CDP screenshot support
Exponential backoff:
- Reconnect delay: 2s, 4s, 8s, 16s, ..., capped at 60s
- Resets to base delay on successful connection
- Reduces idle CPU waste vs fixed 3s reconnect

Screenshot via CDP Page.captureScreenshot:
- New 'screenshot' action in protocol (5th action)
- Supports format (png/jpeg), quality, fullPage
- Full-page: uses Emulation.setDeviceMetricsOverride for scroll height
- CLI-side: page.screenshot() with optional file save
- Extension build: 9.81KB (+1.7KB from 8.11KB)

Inspired by bb-browser's architecture patterns.
2026-03-19 16:21:17 +08:00
jackwener 01b8b6b5bf refactor: fix 14 issues from deep code review
P0 Critical:
- #1 Fix double IIFE wrapping: unified wrapForEval() replaces
  normalizeEvaluateSource + ad-hoc wrap in page.evaluate()
- #2 Fix navigate race: check tab.status before addListener,
  reduced timeout 30s→15s

P1 Should Fix:
- #8 Remove unused permissions (scripting, host_permissions, content_scripts)
- #10 Add retry (3x, 500ms) + timeout (30s) to sendCommand()

P2 Cleanup:
- #3 Extract isWebUrl() to safely handle undefined tab.url
- #4 Sanitize maxDepth with Math.max/min bounds
- #6 Delete empty src/daemon/ directory
- #7 Remove dead createJsonRpcRequest + its test
- #9 Remove stale IIFE-mode comment
- #11 Validate body.id in daemon request handler
- #12 Guard ensureAttached: detach+re-attach on 'already attached'
- #14 Extract _tabOpt() helper (removes 13x spread duplication)
- #15 Add verbose warning for unsupported consoleMessages()

All 35 unit tests pass.
2026-03-19 16:21:17 +08:00
jackwener b2fa7daf57 feat: replace @playwright/mcp with lightweight daemon + Chrome Extension
Architecture:
- Micro-daemon (HTTP + WebSocket bridge, ~190 lines, auto-start/idle-exit)
- Chrome MV3 Extension using chrome.debugger CDP (10KB build)
- 5 protocol actions: exec, navigate, tabs, cookies, screenshot
- All DOM ops via JS evaluate — no extension update needed for new features

Key features:
- CDP Runtime.evaluate for JS execution in page context
- Tab management, cookie access via Chrome APIs
- Auto-start daemon on cold boot, idle auto-exit (5min)
- Minimal permissions: debugger, tabs, cookies, activeTab, alarms

Tested: zhihu hot (14.3s), twitter timeline (9.3s)
2026-03-19 16:21:17 +08:00
jackwener 0374b77d16 feat: Introduce Netease Music CLI with CDP enabler 2026-03-19 05:06:48 +08:00
jackwener c3efc5b492 0.9.8
Release / release (push) Has been cancelled
2026-03-19 01:38:55 +08:00
jackwener f85464c1aa 0.9.7 2026-03-19 01:38:49 +08:00
backtime1993 a4f94912cd fix(main): navigate to domain before cookie/header strategy commands in CDP mode (#71)
When using CDP mode (OPENCLI_CDP_ENDPOINT), the browser page context is
the user's active tab which may be on an unrelated domain. Cookie/header
strategy commands that use fetch() with credentials: 'include' then fail
with "Failed to fetch" due to the browser's same-origin policy.

Fix: before executing cookie/header strategy commands, navigate to the
command's declared domain so the fetch runs in same-origin context.
This mirrors the pre-navigation already done in the cascade command.

Affects all cookie-strategy adapters (bilibili, twitter, zhihu, xueqiu,
etc.) when OPENCLI_CDP_ENDPOINT is enabled and the active Chrome tab is
on a different site.

Co-authored-by: kensei <backtime1993@gmail.com>
2026-03-19 01:38:10 +08:00
Shuming Ying deb568dbe5 fix(browser): avoid selecting non-server playwright cli paths (#74)
Co-authored-by: root <root@localhost.localdomain>
2026-03-19 01:31:10 +08:00
Jingyu 32619fa553 fix(xiaohongshu): restore user profile note fetching (#69) 2026-03-19 00:10:33 +08:00
dependabot[bot] 1d871b35f0 chore(deps): bump commander from 13.1.0 to 14.0.3 (#67)
Bumps [commander](https://github.com/tj/commander.js) from 13.1.0 to 14.0.3.
- [Release notes](https://github.com/tj/commander.js/releases)
- [Changelog](https://github.com/tj/commander.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tj/commander.js/compare/v13.1.0...v14.0.3)

---
updated-dependencies:
- dependency-name: commander
  dependency-version: 14.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-19 00:03:01 +08:00
dependabot[bot] 75e6ed4593 chore(ci): bump actions/setup-node from 4 to 6 (#65)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-19 00:02:29 +08:00
dependabot[bot] b07434b2a1 chore(ci): bump actions/checkout from 4 to 6 (#66)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-18 23:59:47 +08:00
AlexYue 515ce75f3b ci: add Dependabot, security audit, release-please, and CI optimization (#64)
* chore(ci): add Dependabot for npm and GitHub Actions updates

- Weekly npm dependency updates with PR limit of 10
- Weekly GitHub Actions version updates with PR limit of 5
- Conventional commit prefixes (chore(deps), chore(ci))

* ci: add security audit workflow

- Run npm audit on push/PR and weekly schedule
- Fail on high-severity vulnerabilities using audit-ci
- Only audit production dependencies

* ci: add release-please for automated changelog and versioning

- Auto-generate CHANGELOG.md from Conventional Commits
- Create version bump PRs on push to main
- Works alongside existing release.yml for npm publish

* ci: add concurrency controls and Node.js version matrix

- Add concurrency groups to ci, e2e-headed, security workflows
  to cancel duplicate runs on the same branch
- Test unit tests across Node 18/20/22 with fail-fast: false
- Update test step name to show Node version

* chore: bump minimum Node.js version from 18 to 20

- Update engines.node in package.json to >=20.0.0
- Update prerequisites in README.md and README.zh-CN.md
- Remove Node 18 from CI test matrix

* review: fix release token and prod-only audit scope

* docs: align Node 20 troubleshooting guidance

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-18 23:52:49 +08:00
AlexYue f539a44cfd docs: add issue/PR templates and contributing guide (#63)
* docs: add issue/PR templates and contributing guide
- Add GitHub Issue forms: bug report, feature request, new site adapter
- Add PR template with CI-aligned checklist (typecheck, test, validate)
- Add CONTRIBUTING.md with adapter development workflow and testing guide

* docs: simplify adapter request and fix contributor example

* docs: trim contribution and issue templates

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-18 23:49:54 +08:00
jackwener 832370f6e2 feat: add Feishu (飞书/Lark) Desktop adapter via AppleScript (5 commands)
Feishu uses custom 'Lark Framework' (Chromium-based but NOT Electron).
CDP port test failed — --remote-debugging-port has no effect.
Uses AppleScript + clipboard approach (same as WeChat/ChatGPT).

Commands: status, send, read, search (Cmd+K), new (Cmd+N)
Includes adapter READMEs (EN+ZH).
2026-03-18 23:03:23 +08:00
jackwener fc9fc32d14 feat: add WeChat (微信) Desktop adapter via AppleScript (6 commands)
WeChat is a native macOS app (not Electron), so uses AppleScript + clipboard:
- status: check if running + window count
- send: clipboard paste + Enter
- read: Cmd+A → Cmd+C with clipboard backup/restore
- search: Cmd+F + type query
- chats: switch to chats tab (Cmd+1)
- contacts: switch to contacts tab (Cmd+2)

Includes adapter READMEs (EN+ZH).
Total: 30 sites · 157 commands
2026-03-18 22:51:18 +08:00
jackwener 43b753fa02 fix(xiaohongshu): repair command args and request capture 2026-03-18 22:47:39 +08:00
jackwener be194a1849 refactor: rename discord → discord-app to distinguish from web version
Desktop Electron app adapters should use '-app' suffix when a web version also exists.
2026-03-18 22:43:26 +08:00
jackwener 63489fb596 chore: remove untested feishu/wechat adapters, polish CLI-ELECTRON.md
- Remove feishu and wechat adapters (not tested yet, will re-add later)
- Remove their rows from README.md and README.zh-CN.md
- Significantly polish CLI-ELECTRON.md skill guide:
  - Add Electron detection guide (check for Electron Framework)
  - Add Non-Electron AppleScript pattern section
  - Add port assignment table for all CDP adapters
  - Improve code examples with real working TypeScript
2026-03-18 22:29:15 +08:00
stometaverse c370bd0582 feat(xiaohongshu): add 4 creator analytics commands (creator-profile, creator-stats, creator-notes, creator-note-detail) (#49)
* feat(xiaohongshu): add 4 creator analytics commands

Add creator backend support for Xiaohongshu (小红书), enabling
creators to access their analytics data from the command line.

New commands:
- creator-profile: account info (followers, likes, creator level)
- creator-stats: 7-day/30-day overview (views, likes, collects,
  comments, shares, new followers) with daily trend data
- creator-notes: note list with per-note metrics from note manager
- creator-note-detail: single note analytics breakdown
  (organic vs promoted vs video traffic)

API discovery:
- /api/galaxy/creator/home/personal_info (cookie auth, 200 OK)
- /api/galaxy/creator/data/note_detail_new (cookie auth, 200 OK)
- /api/galaxy/creator/data/note_detail?note_id=xxx (cookie auth, 200 OK)
- Note manager DOM extraction for note list (bypasses v2 signature)

All endpoints verified working with real creator account.
Screenshots (redacted) included in docs/screenshots/.

Requires: Chrome logged into creator.xiaohongshu.com

* chore: remove screenshots from repo (will host externally for PR)

* review: fix creator analytics CLI integration

Co-authored-by: stone16 <stone2paul@gmail.com>

* test: add site-scoped test runner

Co-authored-by: stone16 <stone2paul@gmail.com>

* review: ignore publish timestamps in creator note metrics

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-18 22:27:25 +08:00
AlexYue 8a355dfd2d feat: Add download support for xhs, twi, bilibili, zhihu (#22)
* feat: add download support for images, videos, and articles

Add comprehensive download functionality to OpenCLI with support for
multiple platforms and content types.

- Add `src/download/index.ts`: HTTP download with progress, yt-dlp
  wrapper for video platforms, cookie export to Netscape format for
  authenticated downloads
- Add `src/download/progress.ts`: Terminal progress bars, multi-file
  download tracker with status summary
- Add `src/pipeline/steps/download.ts`: New `download` pipeline step
  for declarative YAML pipelines

- Register `download` step in executor.ts
- Add template filters: `slugify`, `sanitize`, `ext`, `basename` for
  filename templating

- `xiaohongshu download`: Download images and videos from notes
- `bilibili download`: Download videos using yt-dlp with cookie auth
- `twitter download`: Download media from user timeline or single tweet
- `zhihu download`: Export articles to Markdown with optional image
  download

```yaml
pipeline:
  - download:
      url: ${{ item.imageUrl }}
      dir: ./downloads
      filename: ${{ item.title | sanitize }}.jpg
      concurrency: 5
      skip_existing: true
      use_ytdlp: false
      type: auto  # auto|image|video|document
```

- Concurrent downloads with configurable parallelism
- Progress bars with file size display
- Skip existing files option
- Cookie forwarding for authenticated downloads
- yt-dlp integration for video platforms (YouTube, Bilibili, Twitter)
- HTML to Markdown conversion for article export

- yt-dlp: Required for video downloads from streaming platforms
- ffmpeg: Optional for video format conversion

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: add download support documentation

- Add Download Support section to both README.md and README.zh-CN.md
- Document supported platforms: Xiaohongshu, Bilibili, Twitter, Zhihu
- Include prerequisites (yt-dlp installation)
- Add usage examples for all download commands
- Document the `download` pipeline step for YAML adapters
- Update built-in commands table with new `download` commands

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: preserve zhihu ordered list content

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-18 22:20:13 +08:00
foreverxdord 700d970f13 feat: add grok.com site support (#60)
Add support for grok.com site with two commands:
- ask: Send a message to Grok and get response
- debug: Debug grok page structure

Implementation uses Playwright CDP protocol with fallback DOM selectors
(div.message-bubble, [data-testid="message-bubble"]) for reliability.

Co-authored-by: xdord <xdord@xdorddeMac-mini.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:11:36 +08:00
jackwener b1fda7da3b feat: add Feishu (飞书) adapter + Notion favorites command
Feishu/Lark (5 commands via AppleScript):
- status, send, new, search (Cmd+K), read
- Lark Framework wraps Chromium v131 but doesn't expose CDP
- Uses AppleScript + clipboard automation (same as WeChat/ChatGPT)

Notion:
- Added favorites command (list pages from Favorites section)

Total: 32 sites · 162 commands
2026-03-18 21:19:47 +08:00
jackwener 40a6a4cace fix(notion): use precise DOM selectors for favorites extraction 2026-03-18 21:15:24 +08:00
jackwener 920ca3f7e5 feat(notion): add favorites command to list favorited pages 2026-03-18 21:06:18 +08:00
jackwener 799a616359 feat: add WeChat (微信) Desktop adapter via AppleScript (5 commands)
WeChat Mac is native Cocoa (not Electron), so CDP is not available.
Uses AppleScript + clipboard automation instead:
- status: check if WeChat is running
- send: paste + Enter in active conversation
- new: Cmd+N for new chat
- search: Cmd+F and type query
- read: Cmd+A → Cmd+C to copy chat content

Total: 30 sites · 156 commands
2026-03-18 21:03:00 +08:00
jackwener 9c2a983e8b feat: add Notion + Discord Desktop adapters (14 new commands via CDP)
Notion (7 commands):
- status, search (Quick Find), read, new, write, sidebar, export

Discord (7 commands):
- status, send, read, channels, servers, search, members

Both apps are Electron-based, connected via --remote-debugging-port.
Notion port: 9230, Discord port: 9232.
Includes adapter READMEs (EN+ZH) for both.

Total: 29 sites · 151 commands
2026-03-18 20:53:44 +08:00
jackwener 3d1ea9b15c feat: add ChatWise Desktop adapter (9 commands via CDP)
Release / release (push) Has been cancelled
- status, new, send, read, ask, model, screenshot, history, export
- Electron-based multi-LLM client (GPT-4/Claude/Gemini)
- Includes adapter READMEs (EN+ZH)
- Fix truncated README table rows
- Total: 27 sites · 137 commands
2026-03-18 20:48:05 +08:00
AstroHan e1d4a6e5e6 feat(linux-do): add linux.do adapter with 6 commands (#43) (#56)
Add linux.do (Discourse-based forum) support with 6 YAML pipeline commands:
- hot: trending topics with period filter (all/daily/weekly/monthly/yearly)
- latest: newest topics
- categories: list all categories with slug/id for further queries
- category: browse topics within a specific category
- topic: post details with replies (first page)
- search: search topics by keyword

All commands use navigate+evaluate pattern with cookie auth
(linux.do enforces login_required on all endpoints).

Security: user inputs sanitized via | json filter + encodeURIComponent.
HTML content stripped with block-tag spacing and full entity decoding.
2026-03-18 20:25:12 +08:00
stometaverse a06cdbf0ac feat: add jimeng (即梦AI) CLI support (#57)
Add two CLI commands for Jimeng (即梦AI) — ByteDance's AI image generation platform:

- generate: Text-to-image generation with model selection and configurable wait time
- history: View recent generation history with prompt, model, status, and image URLs

Both commands use browser automation with cookie-based authentication on jimeng.jianying.com.
2026-03-18 20:23:45 +08:00
jackwener e76de39f42 feat: desktop adapter improvements — bug fixes + 9 new commands
Release / release (push) Has been cancelled
P0 Bug Fixes:
- codex: add missing args/IPage imports, add wait(0.5) before Enter in send
- cursor: new.ts uses Meta+N shortcut (more robust), composer.ts simplified
- chatgpt: send.ts now backs up and restores clipboard
- antigravity: send.ts/model.ts columns unified to PascalCase
- codex: read.ts column renamed Thread_Content → Content

P1 New Features:
- ask: one-shot send+wait+read for cursor, codex, chatgpt (send → poll DOM → return response)
- screenshot: DOM + accessibility snapshot export for cursor, codex

P2 New Features:
- history: list sidebar chat sessions for cursor, codex
- export: save full conversation as Markdown for cursor, codex

Total: 26 sites · 128 commands
2026-03-18 19:52:39 +08:00
jackwener 813631e468 docs: remove unnecessary --remote-allow-origins, add CDP launch to ChatGPT README
- Remove --remote-allow-origins from antigravity README, README.zh-CN, SKILL.md (not needed for local usage)
- Update ChatGPT README to document both AppleScript and CDP approaches
- Document ChatGPT Electron launch: /Applications/ChatGPT.app/Contents/MacOS/ChatGPT --remote-debugging-port=9224
2026-03-18 19:42:45 +08:00
jackwener 2afbb99660 feat: add ChatGPT Desktop native support + Cursor/Codex advanced commands
Release / release (push) Has been cancelled
- Add ChatGPT macOS Desktop adapter (status, new, send, read) via AppleScript
- Add Cursor composer, model, extract-code commands via CDP
- Add Codex model command via CDP
- Create adapter READMEs for ChatGPT (EN+ZH) and Cursor (EN+ZH)
- Fix README.md duplicate table rows (6 sites were listed twice)
- Update command count: 26 sites · 119 commands
- Bump version to 0.9.5
2026-03-18 19:40:08 +08:00
jackwener aa55c88069 0.9.4
Release / release (push) Has been cancelled
2026-03-18 17:41:41 +08:00
jackwener b32fe1cbc3 feat: add advanced cursor and codex capabilities 2026-03-18 17:41:41 +08:00
jackwener 981cc1bc5e docs: add CLI-ELECTRON.md as an agent skill guide 2026-03-18 17:17:46 +08:00
jackwener cd63231b7e chore(release): 0.9.2
Release / release (push) Has been cancelled
2026-03-18 17:15:01 +08:00
jackwener 685658f7bd build: update cli-manifest 2026-03-18 17:15:01 +08:00
jackwener cd6f7a1f7e fix(codex): use precise selector for read command 2026-03-18 17:14:47 +08:00
jackwener 4ce0345c9a chore(release): 0.9.1
Release / release (push) Has been cancelled
2026-03-18 17:06:47 +08:00
jackwener 3cc2cb5504 feat(codex): implement generic CDP adapters for OpenAI Codex desktop app 2026-03-18 17:06:47 +08:00
jackwener abac070ce4 docs: update root README and SKILL with electron app marketing copy 2026-03-18 16:51:18 +08:00
jackwener 79fbac844e chore(release): 0.9.0
Release / release (push) Has been cancelled
2026-03-18 16:44:36 +08:00
jackwener 7e776e2bd5 feat(antigravity): support cli all electron app via CDP 2026-03-18 16:44:36 +08:00
jackwener bde1c53a3e fix(xiaoyuzhou): validate limits and tighten e2e 2026-03-18 16:38:09 +08:00
AstroHan 5e667b9c2f feat(xiaoyuzhou): add podcast platform adapter (#18) (#53)
Three public commands for Xiaoyuzhou (小宇宙) podcast platform:
- podcast <id>: view podcast profile
- podcast-episodes <id> [--limit]: list recent episodes (up to 15)
- episode <id>: view episode details

Uses __NEXT_DATA__ extraction from SSR pages, no auth required.
Includes unit tests (16), E2E tests (3), and README updates.
2026-03-18 16:34:03 +08:00
jackwener 64e3a2d627 0.8.0
Release / release (push) Has been cancelled
2026-03-18 15:25:51 +08:00
jackwener 849d9faea1 refactor(main): remove duplicate argument coercion in favor of engine validation 2026-03-18 15:24:59 +08:00
jackwener 29ea5ce059 feat(engine): add lightweight runtime validation and coercion for CLI arguments 2026-03-18 15:20:55 +08:00
jackwener 12c4b8853b feat(pipeline): extract STEP_HANDLERS into dynamic PipelineRegistry 2026-03-18 15:19:23 +08:00
jackwener cfad003220 fix(browser): throw explicit BrowserConnectError on Playwright MCP JSON-RPC silent failures 2026-03-18 15:18:01 +08:00
jakevin abfd4b902c feat(browser): add CDP remote connection support for server environments (#52)
* feat(browser): add CDP remote connection support for server environments

This feature enables OpenCLI to connect to a Chrome browser running on a
different machine (e.g., your local computer) from a headless server
environment via Chrome DevTools Protocol (CDP).

Server environments (CI, cloud VMs, headless Linux) cannot run Chrome with
a GUI or install the Playwright MCP Bridge extension. This makes it
impossible to use OpenCLI commands that require browser authentication.

Add support for the `OPENCLI_CDP_ENDPOINT` environment variable, which
tells OpenCLI to connect to a remote Chrome instance via CDP instead of
using the local extension mode.

1. Start Chrome with remote debugging on local machine:
   ```
   chrome --remote-debugging-port=9222 --user-data-dir="$HOME/chrome-debug"
   ```

2. Create SSH tunnel to forward port to server:
   ```
   ssh -R 9222:localhost:9222 your-server
   ```

3. Run OpenCLI on server:
   ```
   export OPENCLI_CDP_ENDPOINT="http://localhost:9222"
   opencli bilibili hot --limit 5
   ```

- src/browser.ts: Add CDP endpoint detection in buildMcpArgs()
- src/doctor.ts: Show CDP mode status in doctor report
- README.md: Add "Remote Chrome (Server/Headless)" section
- README.zh-CN.md: Add corresponding Chinese documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: extract CDP connection guide into separate files

* docs: clarify CDP vs SSH/Proxy distinction in CDP guides

* docs: restructure CDP guides into 3 distinct phases (preparation, tunnel, execution)

---------

Co-authored-by: ByteYue <yj976240184@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-18 15:11:14 +08:00
Alex Yang 2d52abde7c fix(barchart): add CSRF retry and mostActive fallback to flow command (#51)
The flow command returned no data because:
1. The CSRF token may not be in the DOM yet when Angular is still
   initializing — add a polling loop (up to 5s) to wait for it
2. The unusual_activity list is empty outside market hours — fall back
   to the mostActive list which always has data
3. Remove the DOM table fallback that never matched (barchart uses
   Angular components, not standard <tr> elements)
2026-03-18 14:43:51 +08:00
jackwener f102501e4a chore(release): bump version to v0.7.11
Release / release (push) Has been cancelled
2026-03-18 13:35:13 +08:00
jackwener 2a983b6b8d feat(browser): auto-bootstrap playwright mcp via npx 2026-03-18 13:32:41 +08:00
jackwener c114a9d7f1 ci: gate pkg.pr.new publish workflow 2026-03-18 13:26:37 +08:00
jackwener d2e179ced5 fix(barchart): preserve flow semantics and nearest expiry 2026-03-18 13:23:28 +08:00
Alex Yang c806f795cc feat(barchart): add stock quote, options, greeks, and flow commands (#45)
* feat(barchart): add stock quote, options chain, greeks, and flow commands

Add 4 new barchart.com CLI commands:
- `barchart quote`: stock price, volume, market cap, P/E, EPS
- `barchart options`: options chain with strike, bid/ask, greeks, IV, OI
- `barchart greeks`: near-the-money greeks overview (delta, gamma, theta, vega, rho)
- `barchart flow`: unusual options activity sorted by volume/OI ratio

Auth uses CSRF token from <meta name="csrf-token"> + session cookies
via the internal proxy API, with DOM fallback for the quote command.

* feat(barchart): add --expiration date filter to greeks command
2026-03-18 12:13:25 +08:00
Alex Yang de5495bdd7 ci: add pkg.pr.new workflow for continuous package previews (#46)
Publishes preview versions of the package on every push and PR,
allowing reviewers to install and test exact commit builds.
2026-03-18 11:59:25 +08:00
Zhang ShengYan d6222ff932 fix: discover global @playwright/mcp for nvm/npm installs (#42)
* fix: discover global @playwright/mcp in nvm/npm installs

* test: cover global mcp discovery paths

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-17 22:36:06 +08:00
jackwener e0395ce5ed test: make Vitest project order explicit
Add explicit group ordering for Vitest projects so unit tests run before e2e tests, while keeping the e2e ordering fix from PR #38.\n\nCo-authored-by: RbBtSn0w <hamiltonsnow@gmail.com>
2026-03-17 17:46:03 +08:00
jackwener 4c8c6e8be7 fix(twitter): migrate bookmarks to direct GraphQL
Release / release (push) Has been cancelled
chore: bump version to 0.7.10
2026-03-17 17:30:01 +08:00
jackwener 7b5bdfa7d5 fix(twitter): harden remaining twitter commands
Co-authored-by: Sheng-Yan, Zhang <yancode@qq.com>
2026-03-17 17:26:36 +08:00
jackwener 546c0b997a feat: Enhance setup output with token save confirmation and improved browser connectivity guidance. 2026-03-17 17:20:16 +08:00
jackwener 1e34e7e6d3 chore: bump version to 0.7.9 2026-03-17 17:11:13 +08:00
jackwener 9ae9eb3fc6 fix(twitter): rewrite timeline adapter to use direct GraphQL API
The previous implementation injected a fetch interceptor after page
navigation, but by that time the HomeTimeline API call had already
completed, resulting in 'no data captured' every time.

Rewrote to directly call Twitter's HomeTimeline GraphQL endpoint
(same pattern as profile.ts and thread.ts):
- Dynamic queryId resolution with hardcoded fallback
- Pagination support with cursor
- Filters out promoted content
- Returns structured tweet data (id, author, text, likes, retweets,
  replies, views, created_at, url)

Fixes #36
2026-03-17 17:11:02 +08:00
jackwener 612c0ab1af v0.7.8: P0 architecture refactor - split browser.ts, unified errors, strict mode
Release / release (push) Has been cancelled
2026-03-17 17:02:03 +08:00
jackwener 68840fc85c refactor: P0 architecture improvements
- Split browser.ts (700 lines) into src/browser/ module (page, mcp, errors, discover, tabs, index)
- Add unified error handling: CliError base class + logger module
- Enable TypeScript strict mode, fix 12 type errors
- Extract inline build scripts to scripts/clean-yaml.cjs and copy-yaml.cjs
- All 178 unit tests pass, build produces 83 entries across 19 sites
2026-03-17 17:01:51 +08:00
jackwener 8263a06a85 fix(completion): insert fpath before compinit in .zshrc
The postinstall script was appending the fpath line at the end of .zshrc,
but compinit (called earlier by oh-my-zsh or directly) would have already
finished scanning. This caused zsh completion to silently fail for most
users.

Now the script detects the first compinit / oh-my-zsh source line and
inserts the fpath entry before it, ensuring completion works immediately.
2026-03-17 16:53:07 +08:00
jackwener eb2c3fdf89 fix: support Chrome Dev and Chrome Beta browser variants
Add Chrome Dev and Chrome Beta profile paths to discoverExtensionToken()
and checkExtensionInstalled() across macOS, Linux, and Windows.

Closes #30
2026-03-17 16:36:19 +08:00
jackwener 43ed0ace59 0.7.6
Release / release (push) Has been cancelled
2026-03-17 16:35:20 +08:00
jackwener de962eb5fb feat: support commands completion (#32)
Add full shell tab-completion for opencli, supporting Bash, Zsh, and Fish.

Co-authored-by: RinChanNOWWW <rin_chan_now@outlook.com>

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-17 16:33:19 +08:00
448 changed files with 30000 additions and 3174 deletions
@@ -0,0 +1,249 @@
---
name: cross-project-adapter-migration
description: "Cross-project CLI command migration workflow for opencli. Use when importing commands from external CLI projects (python/node) like rdt-cli, twitter-cli, etc. Covers: source analysis → gap matrix → batch migration → README/SKILL.md update."
---
# Cross-Project Adapter Migration
> 从外部 CLI 项目(Python/Node/Go 等)批量迁移命令到 opencli 的标准化流程。
## When to Use
- 用户说"把 xxx-cli 的命令迁移过来"
- 用户说"看看 xxx 项目有什么可以借鉴的"
- 用户说"对齐 xxx-cli 的功能"
- 在为新平台扩展 opencli 时,发现已有第三方 CLI 工具
## Prerequisites
- 熟悉 [CLI-EXPLORER.md](file:///Users/jakevin/code/opencli/CLI-EXPLORER.md)adapter 开发决策树)
- 熟悉 [SKILL.md](file:///Users/jakevin/code/opencli/SKILL.md)(命令参考 & 模板)
---
## Phase 1: 源项目分析
### 1.1 克隆 & 理解源项目
```bash
# 克隆源项目到 /tmp 做分析
git clone <source_repo_url> /tmp/<source-cli>
```
分析重点:
- **命令列表**:找到所有可用命令(查看 CLI 入口文件、help 输出或 README
- **认证方式**CookieAPI KeyOAuth?浏览器自动化?
- **数据源**:公开 APIGraphQL?页面抓取?
- **输出字段**:每个命令返回哪些数据字段
### 1.2 生成命令清单
列出源项目所有命令,包括:
| 命令 | 类型 | API/方法 | 输出字段 |
|------|------|---------|---------|
| `xxx feed` | Read | `GET /api/feed` | title, author, time |
| `xxx post` | Write | `POST /api/tweet` | status, id |
---
## Phase 2: 功能对比矩阵
### 2.1 查看 opencli 现有命令
```bash
ls src/clis/<site>/ # 查看已有适配器
opencli list | grep <site> # 确认已注册命令
```
### 2.2 生成对比矩阵
对每个源项目命令,标注三种状态:
| 功能 | 源项目 | opencli 现有 | 行动 |
|------|--------|-------------|------|
| feed | ✅ `xxx feed` | ❌ 无 | ✅ **新增** |
| search | ✅ `xxx search` | ✅ `search.ts` | ❌ 已有,跳过 |
| hot | ✅ `xxx hot` | ⚠️ `hot.yaml`(不完整) | ✅ **增强** |
| like | ✅ `xxx like` | ✅ `like.ts` | ❌ 已有,跳过 |
### 2.3 筛选迁移目标
去掉已有的、低价值的,保留高价值缺失命令,按 Read/Write 分类:
**筛选原则**
- ✅ 高使用频率的命令优先
- ✅ 已有但不完整的命令标记为"增强"
- ❌ 源项目特有但 opencli 架构不支持的功能(如需要持久化存储的)跳过
- ❌ 与现有功能完全重复的跳过
---
## Phase 3: 批量实现
> [!IMPORTANT]
> 实现前必须查阅 [CLI-EXPLORER.md](file:///Users/jakevin/code/opencli/CLI-EXPLORER.md) 确认策略选择。
### 3.1 选择实现方式
基于决策树分类:
| 类别 | 方式 | 适用条件 |
|------|------|---------|
| **Read + 简单 API** | YAML pipeline | 纯 fetch/select/map,无复杂 JS |
| **Read + GraphQL/分页/签名** | TypeScript adapter | 需要 JS 逻辑 |
| **Write 操作** | TypeScript + `Strategy.UI` | 点击/输入等 DOM 操作 |
| **Write + API** | TypeScript + `Strategy.COOKIE/HEADER` | 直接 POST API |
### 3.2 实现顺序
**先 Read 后 Write,先 YAML 后 TS**
1. **Phase A**: YAML Read 适配器(最快,通常每个 10-20 行)
2. **Phase B**: TS Read 适配器(需要 evaluate/intercept 的)
3. **Phase C**: TS Write 适配器(需 UI 自动化或 POST API
### 3.3 实现模板
#### YAML Read 适配器模板(Cookie 策略)
```yaml
site: <site>
name: <command>
description: <描述>
domain: www.<site>.com
strategy: cookie
browser: true
args:
limit:
type: int
default: 20
pipeline:
- navigate: https://www.<site>.com
- evaluate: |
(async () => {
const res = await fetch('<api_endpoint>', { credentials: 'include' });
const d = await res.json();
return (d.data?.items || []).map(item => ({
title: item.title,
// ... map source fields
}));
})()
- map:
rank: ${{ index + 1 }}
title: ${{ item.title }}
- limit: ${{ args.limit }}
columns: [rank, title]
```
#### TS Write 适配器模板(UI 策略)
```typescript
import { cli, Strategy } from '../../registry.js';
cli({
site: '<site>',
name: '<command>',
description: '<描述>',
strategy: Strategy.UI,
args: [{ name: 'target', required: true, help: '<参数说明>' }],
columns: ['status', 'message'],
func: async (page, kwargs) => {
await page.goto(`https://www.<site>.com/${kwargs.target}`);
await page.wait({ text: '<expected_text>', timeout: 10 });
// 获取 snapshot 找到目标按钮
const snapshot = await page.accessibility.snapshot();
// 点击按钮 ...
return [{ status: 'success', message: '<action> completed' }];
},
});
```
### 3.4 公共模式复用
迁移过程中如果发现多个适配器共享逻辑,考虑提取到 `src/clis/<site>/utils.ts` 工具文件:
```typescript
// src/clis/<site>/utils.ts
export async function fetchWithAuth(page, url) { ... }
export function parseItem(raw) { ... }
```
---
## Phase 4: 验证 & 发布
### 4.1 构建验证
```bash
npx tsc --noEmit # TypeScript 编译检查
opencli list | grep <site> # 确认所有命令已注册
```
### 4.2 运行验证(关键!)
每个新命令必须实际运行:
```bash
# Read 命令
opencli <site> <command> --limit 3 -f json
opencli <site> <command> --limit 3 -v # verbose 查看 pipeline
# Write 命令(谨慎!会实际操作)
opencli <site> <command> <test_target>
```
### 4.3 更新文档
迁移完成后必须更新以下文件:
1. **README.md** — 在对应平台区域添加新命令示例
2. **SKILL.md** — 在 Commands Reference 中添加新命令
### 4.4 提交 & 推送
```bash
git add -A
git commit -m "feat(<site>): migrate <N> commands from <source-cli>
- Phase A: <N> YAML adapters (read operations)
- Phase B: <N> TS adapters (write operations)
- Source: <source_repo_url>"
git push
```
---
## Checklist
- [ ] 源项目命令清单已生成
- [ ] 对比矩阵已确认,高价值缺失命令已筛选
- [ ] 用户确认迁移范围
- [ ] Phase A: YAML Read 适配器已完成
- [ ] Phase B: TS Read 适配器已完成
- [ ] Phase C: TS Write 适配器已完成
- [ ] `npx tsc --noEmit` 编译通过
- [ ] 所有新命令已实际运行验证
- [ ] README.md 已更新
- [ ] SKILL.md 已更新
- [ ] 已 commit + push
## 实战案例参考
### rdt-cli → opencli Reddit2026-03-16
- **源项目**: `rdt-cli`25 个 Python 命令)
- **筛选结果**: 13 个高价值命令
- **实现**: 7 个 YAMLread + 6 个 TSwrite
- **产出**: +11 文件,+767 行代码,Reddit 适配器从 4 → 15+275%
### twitter-cli → opencli Twitter2026-03-16
- **源项目**: `twitter-cli`20+ Python 命令)
- **筛选结果**: 11 个待实现
- **策略**: Read 用 `Strategy.COOKIE` + GraphQL fetchWrite 用 `Strategy.UI`
@@ -0,0 +1,54 @@
---
description: Migrate commands from an external CLI project into opencli adapters
---
// turbo-all
## Steps
1. Clone the source CLI project for analysis:
```bash
git clone <source_repo_url> /tmp/<source-cli>
```
2. Analyze source project: list all commands, auth method, API endpoints, and output fields.
3. Check existing opencli adapters for the target site:
```bash
ls src/clis/<site>/
opencli list | grep <site>
```
4. Generate a comparison matrix table (source commands vs opencli existing). Mark each as: ✅ **New** / ✅ **Enhance** / ❌ **Skip**. Ask user to confirm which commands to migrate.
5. Implement YAML Read adapters first (highest ROI, 10-20 lines each). Place files in `src/clis/<site>/<name>.yaml`.
6. Implement TS Read adapters for complex cases (GraphQL, pagination, signing). Place files in `src/clis/<site>/<name>.ts`.
7. Implement TS Write adapters using `Strategy.UI` or `Strategy.COOKIE`. Place files in `src/clis/<site>/<name>.ts`.
8. Verify build:
```bash
npx tsc --noEmit
```
9. Verify all commands are registered:
```bash
opencli list | grep <site>
```
10. Run each new command to verify it works:
```bash
opencli <site> <command> --limit 3 -f json
```
11. Update README.md with new command examples in the appropriate platform section.
12. Update SKILL.md Commands Reference with new commands.
13. Commit and push:
```bash
git add -A
git commit -m "feat(<site>): migrate <N> commands from <source-cli>"
git push
```
+83
View File
@@ -0,0 +1,83 @@
name: "🐛 Bug Report"
description: Report a bug or unexpected behavior in OpenCLI
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report a bug. A short reproduction and any error output are usually enough.
- type: textarea
id: description
attributes:
label: Description
description: A clear and concise description of the bug.
placeholder: What happened?
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: How can we reproduce this behavior?
value: |
1. Run `opencli ...`
2. ...
3. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What did you expect to happen?
validations:
required: true
- type: input
id: version
attributes:
label: OpenCLI Version
description: "Run `opencli --version` to find out."
placeholder: "0.8.0"
validations:
required: true
- type: dropdown
id: node-version
attributes:
label: Node.js Version
options:
- "20.x"
- "22.x"
- Other
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating System
options:
- macOS
- Linux
- Windows
- Other
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs / Screenshots
description: |
Paste any relevant error output. Run with `-v` for verbose logs:
```
opencli <command> -v
```
render: shell
validations:
required: false
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: 📖 Documentation
url: https://github.com/jackwener/opencli#readme
about: Check the README and docs before opening an issue.
- name: 🧪 Testing Guide
url: https://github.com/jackwener/opencli/blob/main/TESTING.md
about: How to run and write tests for OpenCLI.
@@ -0,0 +1,42 @@
name: "✨ Feature Request"
description: Suggest a new feature or improvement
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Have an idea to make OpenCLI better? We'd love to hear it!
- type: textarea
id: description
attributes:
label: Feature Description
description: A clear and concise description of the feature you'd like.
validations:
required: true
- type: textarea
id: use-case
attributes:
label: Use Case
description: What problem does this solve? Who benefits from this feature?
placeholder: "As a user, I want to ... so that ..."
validations:
required: true
- type: textarea
id: proposed-solution
attributes:
label: Proposed Solution
description: If you have a specific implementation in mind, describe it here.
validations:
required: false
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Any alternative approaches you've thought about?
validations:
required: false
@@ -0,0 +1,57 @@
name: "🌐 New Site Adapter Request"
description: Request support for a new website
title: "[Site]: "
labels: ["new-adapter"]
body:
- type: markdown
attributes:
value: |
Want OpenCLI to support a new site? Tell us about it!
- type: input
id: site-name
attributes:
label: Site Name
description: The name of the website.
placeholder: "e.g. Product Hunt"
validations:
required: true
- type: input
id: site-url
attributes:
label: Site URL
description: The main URL of the website.
placeholder: "https://www.producthunt.com"
validations:
required: true
- type: textarea
id: commands
attributes:
label: Desired Commands
description: What commands would you like? List them with a brief description.
value: |
- `hot` — trending / popular items
- `search` — search the site
validations:
required: true
- type: textarea
id: api-examples
attributes:
label: Example Links or API Endpoints
description: Share any example page URLs or API endpoints if you have them (optional).
placeholder: |
Example page: https://www.producthunt.com/posts/example
GET https://api.producthunt.com/v2/posts?order=votes
Response: { "posts": [{ "name": "...", "tagline": "..." }] }
validations:
required: false
- type: checkboxes
id: contribution
attributes:
label: Willing to Contribute?
options:
- label: I'm willing to submit a PR for this adapter
+27
View File
@@ -0,0 +1,27 @@
version: 2
updates:
# npm dependencies
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
labels:
- "dependencies"
commit-message:
prefix: "chore(deps)"
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
labels:
- "dependencies"
- "ci"
commit-message:
prefix: "chore(ci)"
+31
View File
@@ -0,0 +1,31 @@
## Description
<!-- Briefly describe your changes and link to any related issues. -->
Related issue:
## Type of Change
- [ ] 🐛 Bug fix
- [ ] ✨ New feature
- [ ] 🌐 New site adapter
- [ ] 📝 Documentation
- [ ] ♻️ Refactor
- [ ] 🔧 CI / build / tooling
## Checklist
- [ ] I ran the checks relevant to this PR
- [ ] I updated tests or docs if needed
- [ ] I included output or screenshots when useful
### Documentation (if adding/modifying an adapter)
- [ ] Added doc page under `docs/adapters/` (if new adapter)
- [ ] Updated `docs/adapters/index.md` table (if new adapter)
- [ ] Updated sidebar in `docs/.vitepress/config.mts` (if new adapter)
## Screenshots / Output
<!-- If applicable, paste CLI output or screenshots here. -->
+80
View File
@@ -0,0 +1,80 @@
name: Build Chrome Extension
on:
push:
branches: [ "main" ]
tags: [ "v*.*.*" ]
pull_request:
branches: [ "main" ]
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
cache-dependency-path: extension/package-lock.json
- name: Install extension dependencies
run: npm ci
working-directory: extension
- name: Build extension
run: npm run build
working-directory: extension
- name: Prepare extension package
run: |
rm -rf extension-package
mkdir -p extension-package
cp extension/manifest.json extension-package/
cp -R extension/dist extension-package/
cp -R extension/icons extension-package/
- name: Create Extension ZIP
run: |
cd extension-package
zip -r ../opencli-extension.zip .
- name: Create Extension CRX
run: |
npm install -g crx3
if [ -n "${{ secrets.CRX_PRIVATE_KEY }}" ]; then
echo "Found CRX_PRIVATE_KEY, signing extension..."
echo "${{ secrets.CRX_PRIVATE_KEY }}" > crx-key.pem
crx3 pack extension-package -o opencli-extension.crx -p crx-key.pem
rm crx-key.pem
else
echo "No CRX_PRIVATE_KEY configured. Generating CRX with a temporary random key..."
crx3 pack extension-package -o opencli-extension.crx
fi
- name: Upload Artifacts (Action Run)
uses: actions/upload-artifact@v4
with:
name: opencli-extension-build
path: |
opencli-extension.zip
opencli-extension.crx
retention-days: 7
- name: Attach to GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: |
opencli-extension.zip
opencli-extension.crx
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+8 -2
View File
@@ -9,6 +9,10 @@ on:
- cron: '0 8 * * 1' # Weekly Monday 08:00 UTC — smoke tests
workflow_dispatch:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
# ── Fast gate: typecheck + build ──
build:
@@ -34,20 +38,22 @@ jobs:
unit-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: ['20', '22']
shard: [1, 2]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests (shard ${{ matrix.shard }}/2)
- name: Run unit tests (Node ${{ matrix.node-version }}, shard ${{ matrix.shard }}/2)
run: npx vitest run src/ --reporter=verbose --shard=${{ matrix.shard }}/2
# ── Smoke tests (scheduled / manual only) ──
+36
View File
@@ -0,0 +1,36 @@
name: Doc Check
on:
pull_request:
branches: [main, dev]
concurrency:
group: doc-check-${{ github.ref }}
cancel-in-progress: true
jobs:
# ── Adapter doc coverage ──
doc-coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check adapter doc coverage
run: bash scripts/check-doc-coverage.sh --strict
# ── VitePress build validation ──
docs-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build docs (catches broken links & sidebar refs)
run: npm run docs:build
+17
View File
@@ -0,0 +1,17 @@
name: Trigger Website Rebuild (Docs Updated)
on:
push:
branches: [main]
paths: ['docs/**']
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Trigger opencli-website rebuild
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.WEBSITE_DEPLOY_TOKEN }}
repository: jackwener/opencli-website
event-type: docs-updated
+4
View File
@@ -7,6 +7,10 @@ on:
branches: [main, dev]
workflow_dispatch:
concurrency:
group: e2e-${{ github.ref }}
cancel-in-progress: true
jobs:
e2e-headed:
runs-on: ubuntu-latest
+30
View File
@@ -0,0 +1,30 @@
name: Publish Any Commit
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
permissions: {}
jobs:
publish:
if: ${{ vars.PKG_PR_NEW_ENABLED == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Publish to pkg.pr.new
run: npx pkg-pr-new publish
+25
View File
@@ -0,0 +1,25 @@
name: Release Please
on:
push:
branches: [main]
permissions:
contents: write
pull-requests: write
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- name: Ensure release-please token is configured
run: |
if [ -z "${{ secrets.RELEASE_PLEASE_TOKEN }}" ]; then
echo "RELEASE_PLEASE_TOKEN secret is required so release PRs can trigger downstream CI workflows." >&2
exit 1
fi
- uses: googleapis/release-please-action@v4
with:
release-type: node
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
-3
View File
@@ -26,9 +26,6 @@ jobs:
- name: Type check
run: npx tsc --noEmit
- name: Build
run: npm run build
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
+36
View File
@@ -0,0 +1,36 @@
name: Security Audit
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
schedule:
- cron: '0 9 * * 1' # Weekly Monday 09:00 UTC
permissions:
contents: read
concurrency:
group: security-${{ github.ref }}
cancel-in-progress: true
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: npm audit (production)
run: npm audit --omit=dev --audit-level=high
- name: Check for known vulnerabilities
run: npx --yes audit-ci@^7 --high --skip-dev
+11
View File
@@ -1,6 +1,17 @@
node_modules/
dist/
!extension/dist/
*.tsbuildinfo
.opencli/
.mcp.json
*.log
.DS_Store
# VitePress
docs/.vitepress/dist
docs/.vitepress/cache
# Extensions & Secrets
*.pem
*.crx
*.zip
+31
View File
@@ -0,0 +1,31 @@
# Changelog
## [1.1.0](https://github.com/jackwener/opencli/compare/v1.0.6...v1.1.0) (2026-03-20)
### Features
* add antigravity serve command — Anthropic API proxy ([35a0fed](https://github.com/jackwener/opencli/commit/35a0fed8a0c1cb714298f672c19f017bbc9a9630))
* add arxiv and wikipedia adapters ([#132](https://github.com/jackwener/opencli/issues/132)) ([3cda14a](https://github.com/jackwener/opencli/commit/3cda14a2ab502e3bebfba6cdd9842c35b2b66b41))
* add external CLI hub for discovery, auto-installation, and execution of external tools. ([b3e32d8](https://github.com/jackwener/opencli/commit/b3e32d8a05744c9bcdfef96f5ff3085ac72bd353))
* add sinafinance 7x24 news adapter ([#131](https://github.com/jackwener/opencli/issues/131)) ([02793e9](https://github.com/jackwener/opencli/commit/02793e990ef4bdfdde9d7a748960b8a9ed6ea988))
* **boss:** add 8 new recruitment management commands ([#133](https://github.com/jackwener/opencli/issues/133)) ([7e973ca](https://github.com/jackwener/opencli/commit/7e973ca59270029f33021a483ca4974dc3975d36))
* **serve:** implement auto new conv, model mapping, and precise completion detection ([0e8c96b](https://github.com/jackwener/opencli/commit/0e8c96b6d9baebad5deb90b9e0620af5570b259d))
* **serve:** use CDP mouse click + Input.insertText for reliable message injection ([c63af6d](https://github.com/jackwener/opencli/commit/c63af6d41808dddf6f0f76789aa6c042f391f0b0))
* xiaohongshu creator flows migration ([#124](https://github.com/jackwener/opencli/issues/124)) ([8f17259](https://github.com/jackwener/opencli/commit/8f1725982ec06d121d7c15b5cf3cda2f5941c32a))
### Bug Fixes
* **docs:** use base '/' for custom domain and add CNAME file ([#129](https://github.com/jackwener/opencli/issues/129)) ([2876750](https://github.com/jackwener/opencli/commit/2876750891bc8a66be577b06ead4db61852c8e81))
* **serve:** update model mappings to match actual Antigravity UI ([36bc57a](https://github.com/jackwener/opencli/commit/36bc57a9624cdfaa50ffb2c1ad7f9c518c5e6c55))
* type safety for wikiFetch and arxiv abstract truncation ([4600b9d](https://github.com/jackwener/opencli/commit/4600b9d46dc7b56ff564c5f100c3a94c6a792c06))
* use UTC+8 for XHS timestamp formatting (CI timezone fix) ([03f067d](https://github.com/jackwener/opencli/commit/03f067d90764487f0439705df36e1a5c969a7f98))
* **xiaohongshu:** use fixed UTC+8 offset in trend timestamp formatting (CI timezone fix) ([593436e](https://github.com/jackwener/opencli/commit/593436e4cb5852f396fbaaa9f87ef1a0b518e76d))
## [1.0.6](https://github.com/jackwener/opencli/compare/v1.0.5...v1.0.6) (2026-03-20)
### Bug Fixes
* use %20 instead of + for spaces in Bilibili WBI signed requests ([#126](https://github.com/jackwener/opencli/issues/126)) ([4cabca1](https://github.com/jackwener/opencli/commit/4cabca12dfa6ca027b938b80ee6b940b5e89ea5c)), closes [#125](https://github.com/jackwener/opencli/issues/125)
+13 -12
View File
@@ -9,12 +9,12 @@
---
## AI Agent 开发者必读:用 Playwright MCP Bridge 探索
## AI Agent 开发者必读:用浏览器探索
> [!CAUTION]
> **你(AI Agent)必须通过 Playwright MCP Bridge 打开浏览器去访问目标网站!**
> **你(AI Agent)必须通过浏览器打开目标网站去探索**
> 不要只靠 `opencli explore` 命令或静态分析来发现 API。
> 你拥有 Playwright MCP 工具,必须主动用它们浏览网页、观察网络请求、模拟用户交互。
> 你拥有浏览器工具,必须主动用它们浏览网页、观察网络请求、模拟用户交互。
### 为什么?
@@ -36,7 +36,7 @@
| ❌ 错误做法 | ✅ 正确做法 |
|------------|------------|
| 只用 `opencli explore` 命令,等结果自动出来 | 用 MCP Bridge 打开浏览器,主动浏览页面 |
| 只用 `opencli explore` 命令,等结果自动出来 | 用浏览器工具打开页面,主动浏览 |
| 直接在代码里 `fetch(url)`,不看浏览器实际请求 | 先在浏览器中确认 API 可用,再写代码 |
| 页面打开后直接抓包,期望所有 API 都出现 | 模拟点击交互(展开评论/切换标签/加载更多) |
| 遇到 HTTP 200 但空数据就放弃 | 检查是否需要 Wbi 签名或 Cookie 鉴权 |
@@ -196,7 +196,7 @@ cat src/clis/<site>/feed.ts # 读最相似的那个
写 TS 适配器之前,先看看你的目标站点有没有**现成的 helper 函数**可以复用:
#### Bilibili (`src/bilibili.ts`)
#### Bilibili (`src/clis/bilibili/utils.ts`)
| 函数 | 用途 | 何时使用 |
|------|------|----------|
@@ -342,10 +342,11 @@ name: search
description: 知乎搜索
args:
keyword:
query:
type: str
required: true
description: Search keyword
positional: true
description: Search query
limit:
type: int
default: 10
@@ -355,7 +356,7 @@ pipeline:
- evaluate: |
(async () => {
const q = encodeURIComponent('${{ args.keyword }}');
const q = encodeURIComponent('${{ args.query }}');
const res = await fetch('/api/v4/search_v3?q=' + q + '&t=general&limit=${{ args.limit }}', {
credentials: 'include'
});
@@ -455,7 +456,7 @@ cli({
name: 'search',
description: 'Search tweets',
strategy: Strategy.HEADER,
args: [{ name: 'keyword', required: true }],
args: [{ name: 'query', required: true, positional: true }],
columns: ['rank', 'author', 'text', 'likes'],
func: async (page, kwargs) => {
await page.goto('https://x.com');
@@ -474,7 +475,7 @@ cli({
'X-Twitter-Auth-Type': 'OAuth2Session',
};
const variables = JSON.stringify({ rawQuery: '${kwargs.keyword}', count: 20 });
const variables = JSON.stringify({ rawQuery: '${kwargs.query}', count: 20 });
const url = '/i/api/graphql/xxx/SearchTimeline?variables=' + encodeURIComponent(variables);
const res = await fetch(url, { headers, credentials: 'include' });
return await res.json();
@@ -631,7 +632,7 @@ git add src/clis/mysite/ && git commit -m "feat(mysite): add hot" && git push
```typescript
import { cli, Strategy } from '../../registry.js';
import type { IPage } from '../../types.js';
import { apiGet } from '../../bilibili.js'; // 复用平台 SDK
import { apiGet } from './utils.js'; // 复用平台 SDK
cli({
site: 'bilibili',
@@ -694,7 +695,7 @@ cli({
| 嵌套字段访问 | `${{ item.node?.title }}` 不工作 | 在 evaluate 中 flatten 数据,不在模板中用 optional chaining |
| 缺少 `strategy: public` | 公开 API 也启动浏览器,7s → 1s | 公开 API 加上 `strategy: public` + `browser: false` |
| evaluate 返回字符串 | map 步骤收到 `""` 而非数组 | pipeline 有 auto-parse,但建议在 evaluate 内 `.map()` 整形 |
| 搜索参数被 URL 编码 | `${{ args.keyword }}` 被浏览器二次编码 | 在 evaluate 内用 `encodeURIComponent()` 手动编码 |
| 搜索参数被 URL 编码 | `${{ args.query }}` 被浏览器二次编码 | 在 evaluate 内用 `encodeURIComponent()` 手动编码 |
| Cookie 过期 | 返回 401 / 空数据 | 在浏览器里重新登录目标站点 |
| Extension tab 残留 | Chrome 多出 `chrome-extension://` tab | 已自动清理;若残留,手动关闭即可 |
| TS evaluate 格式 | `() => {}``result is not a function` | TS 中 `page.evaluate()` 必须用 IIFE`(async () => { ... })()` |
+167
View File
@@ -0,0 +1,167 @@
# Contributing to OpenCLI
Thanks for your interest in contributing to OpenCLI.
## Quick Start
```bash
# 1. Fork & clone
git clone git@github.com:<your-username>/opencli.git
cd opencli
# 2. Install dependencies
npm install
# 3. Build
npm run build
# 4. Run a few checks
npx tsc --noEmit
npx vitest run src/
# 5. Link globally (optional, for testing `opencli` command)
npm link
```
## Adding a New Site Adapter
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.
### YAML Adapter (Recommended for data-fetching commands)
Create a file like `src/clis/<site>/<command>.yaml`:
```yaml
site: mysite
name: trending
description: Trending posts on MySite
domain: www.mysite.com
strategy: public # public | cookie | header
browser: false # true if browser session is needed
args:
limit:
type: int
default: 20
description: Number of items
pipeline:
- fetch:
url: https://api.mysite.com/trending
- map:
rank: ${{ index + 1 }}
title: ${{ item.title }}
score: ${{ item.score }}
url: ${{ item.url }}
- limit: ${{ args.limit }}
columns: [rank, title, score, url]
```
See [`hackernews/top.yaml`](src/clis/hackernews/top.yaml) for a real example.
### TypeScript Adapter (For complex browser interactions)
Create a file like `src/clis/<site>/<command>.ts`:
```typescript
import { cli, Strategy } from '../../registry.js';
cli({
site: 'mysite',
name: 'search',
description: 'Search MySite',
domain: 'www.mysite.com',
strategy: Strategy.COOKIE,
args: [
{ name: 'query', required: true, help: 'Search query' },
{ name: 'limit', type: 'int', default: 10, help: 'Max results' },
],
columns: ['title', 'url', 'date'],
func: async (page, kwargs) => {
const { query, limit = 10 } = kwargs;
await page.goto('https://www.mysite.com');
const data = await page.evaluate(`
(async () => {
const res = await fetch('/api/search?q=${encodeURIComponent(query)}', {
credentials: 'include'
});
return (await res.json()).results;
})()
`);
return data.slice(0, Number(limit)).map((item: any) => ({
title: item.title,
url: item.url,
date: item.created_at,
}));
},
});
```
Use `opencli explore <url>` to discover APIs and see [CLI-EXPLORER.md](./CLI-EXPLORER.md) if you need the full adapter workflow.
### Validate Your Adapter
```bash
# Validate YAML syntax and schema
opencli validate
# Test your command
opencli <site> <command> --limit 3 -f json
# Verbose mode for debugging
opencli <site> <command> -v
```
## Testing
See [TESTING.md](./TESTING.md) for the full guide and exact test locations.
```bash
npx vitest run src/ # Unit tests
npx vitest run tests/e2e/ # E2E tests
npx vitest run # All tests
```
## Code Style
- **TypeScript strict mode** — avoid `any` where possible.
- **ES Modules** — use `.js` extensions in imports (TypeScript output).
- **Naming**: `kebab-case` for files, `camelCase` for variables/functions, `PascalCase` for types/classes.
- **No default exports** — use named exports.
## Commit Convention
We use [Conventional Commits](https://www.conventionalcommits.org/):
```
feat(twitter): add thread command
fix(browser): handle CDP timeout gracefully
docs: update CONTRIBUTING.md
test(reddit): add e2e test for save command
chore: bump vitest to v4
```
Common scopes: site name (`twitter`, `reddit`) or module name (`browser`, `pipeline`, `engine`).
## Submitting a Pull Request
1. Create a feature branch: `git checkout -b feat/mysite-trending`
2. Make your changes and add tests when relevant
3. Run the checks that apply:
```bash
npx tsc --noEmit # Type check
npx vitest run src/ # Unit tests
opencli validate # YAML validation (if applicable)
```
4. Commit using conventional commit format
5. Push and open a PR
## License
By contributing, you agree that your contributions will be licensed under the [Apache-2.0 License](./LICENSE).
+186 -80
View File
@@ -1,7 +1,7 @@
# OpenCLI
> **Make any website your CLI.**
> Zero risk · Reuse Chrome login · AI-powered discovery · 80+ commands · 19 sites
> **Make any website, Electron App, or Local Tool your CLI.**
> Zero risk · Reuse Chrome login · AI-powered discovery · Universal CLI Hub
[中文文档](./README.zh-CN.md)
@@ -9,7 +9,12 @@
[![Node.js Version](https://img.shields.io/node/v/@jackwener/opencli?style=flat-square)](https://nodejs.org)
[![License](https://img.shields.io/npm/l/@jackwener/opencli?style=flat-square)](./LICENSE)
A CLI tool that turns **any website** into a command-line interface — Bilibili, Zhihu, 小红书, Twitter/X, Reddit, YouTube, and [many more](#built-in-commands) — powered by browser session reuse and AI-native discovery.
A CLI tool that turns **any website** or **Electron app** into a command-line interface — Bilibili, Zhihu, 小红书, Twitter/X, Reddit, YouTube, Antigravity, and [many more](#built-in-commands) — powered by browser session reuse and AI-native discovery.
**Built for AI Agents**: Simply configure an instruction in your global `AGENT.md` or `.cursorrules` guiding the AI to execute `opencli list` via Bash to discover available tools. Register your favorite local CLIs (`opencli register mycli`), and the AI will automatically learn how to invoke all your tools perfectly!
**CLI All Electron Apps! The Most Powerful Update Has Arrived!**
Turn ANY Electron application into a CLI tool! Recombine, script, and extend applications like Antigravity Ultra seamlessly. Now AI can control itself natively. Unlimited possibilities await!
---
@@ -19,8 +24,11 @@ A CLI tool that turns **any website** into a command-line interface — Bilibili
- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start)
- [Built-in Commands](#built-in-commands)
- [Desktop App Adapters](#desktop-app-adapters)
- [Download Support](#download-support)
- [Output Formats](#output-formats)
- [For AI Agents (Developer Guide)](#for-ai-agents-developer-guide)
- [Remote Chrome (Server/Headless)](#remote-chrome-serverheadless)
- [Testing](#testing)
- [Troubleshooting](#troubleshooting)
- [Releasing New Versions](#releasing-new-versions)
@@ -30,78 +38,50 @@ A CLI tool that turns **any website** into a command-line interface — Bilibili
## Highlights
- **CLI All Electron** — CLI-ify apps like Antigravity Ultra! Now AI can control itself natively using cc/openclaw!
- **Account-safe** — Reuses Chrome's logged-in state; your credentials never leave the browser.
- **AI Agent ready** — `explore` discovers APIs, `synthesize` generates adapters, `cascade` finds auth strategies.
- **Self-healing setup** — `opencli setup` auto-discovers tokens; `opencli doctor` diagnoses config across 10+ tools; `--fix` repairs them all.
- **External CLI Hub** — Discover, auto-install, and passthrough commands to any external CLI (gh, obsidian, docker, kubectl, etc). Zero setup.
- **Self-healing setup** — `opencli setup` verifies Browser Bridge connectivity; `opencli doctor` diagnoses daemon, extension, and live browser connectivity.
- **Dynamic Loader** — Simply drop `.ts` or `.yaml` adapters into the `clis/` folder for auto-registration.
- **Dual-Engine Architecture** — Supports both YAML declarative data pipelines and robust browser runtime TypeScript injections.
## Prerequisites
- **Node.js**: >= 18.0.0
- **Node.js**: >= 20.0.0
- **Chrome** running **and logged into the target site** (e.g. bilibili.com, zhihu.com, xiaohongshu.com).
> **⚠️ Important**: Browser commands reuse your Chrome login session. You must be logged into the target website in Chrome before running commands. If you get empty data or errors, check your login status first.
OpenCLI connects to your browser through the Playwright MCP Bridge extension.
OpenCLI connects to your browser through a lightweight **Browser Bridge** Chrome Extension + micro-daemon (zero config, auto-start).
### Playwright MCP Bridge Extension Setup
### Browser Bridge Extension Setup
1. Install **[Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm)** extension in Chrome.
2. Run `opencli setup` — discovers the token, distributes it to your tools, and verifies connectivity:
You can install the extension via either method:
```bash
opencli setup
```
**Method 1: Download Pre-built Release (Recommended)**
1. Go to the GitHub [Releases page](https://github.com/jackwener/opencli/releases) and download the latest `opencli-extension.zip` or `opencli-extension.crx`.
2. Open `chrome://extensions` and enable **Developer mode** (top-right toggle).
3. Drag and drop the `.crx` file or the unzipped folder into the extensions page.
The interactive TUI will:
- 🔍 Auto-discover `PLAYWRIGHT_MCP_EXTENSION_TOKEN` from Chrome (no manual copy needed)
- ☑️ Show all detected tools (Codex, Cursor, Claude Code, Gemini CLI, etc.)
- ✏️ Update only the files you select (Space to toggle, Enter to confirm)
- 🔌 Auto-verify browser connectivity after writing configs
**Method 2: Load Unpacked Source (For Developers)**
1. Open `chrome://extensions` and enable **Developer mode**.
2. Click **Load unpacked** and select the `extension/` directory from this repository.
> **Tip**: Use `opencli doctor` for ongoing diagnosis and maintenance:
That's it! The daemon auto-starts when you run any browser command. No tokens, no manual configuration.
> **Tip**: Use `opencli doctor` for ongoing diagnosis:
> ```bash
> opencli doctor # Read-only token & config diagnosis
> opencli doctor --live # Also test live browser connectivity
> opencli doctor --fix # Fix mismatched configs (interactive)
> opencli doctor --fix -y # Fix all configs non-interactively
> opencli doctor # Check extension + daemon connectivity
> opencli doctor --live # Also test live browser commands
> ```
<details>
<summary>Manual setup (alternative)</summary>
Add token to your MCP client config (e.g. Claude/Cursor):
```json
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp@latest", "--extension"],
"env": {
"PLAYWRIGHT_MCP_EXTENSION_TOKEN": "<your-token-here>"
}
}
}
}
```
Export in shell (e.g. `~/.zshrc`):
```bash
export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<your-token-here>"
```
</details>
## Quick Start
### Install via npm (recommended)
```bash
npm install -g @jackwener/opencli
opencli setup # One-time: configure Playwright MCP token
```
Then use directly:
@@ -134,29 +114,155 @@ npm install -g @jackwener/opencli@latest
## Built-in Commands
**19 sites · 80+ commands** — run `opencli list` for the live registry.
Run `opencli list` for the live registry.
| Site | Commands | Count | Mode |
|------|----------|:-----:|------|
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` | 18 | 🔐 Browser |
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 15 | 🔐 Browser |
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` | 11 | 🔐 Browser |
| **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` | 6 | 🌐 / 🔐 |
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 6 | 🔐 Browser |
| **xiaohongshu** | `search` `notifications` `feed` `me` `user` | 5 | 🔐 Browser |
| **youtube** | `search` `video` `transcript` | 3 | 🔐 Browser |
| **zhihu** | `hot` `search` `question` | 3 | 🔐 Browser |
| **boss** | `search` `detail` | 2 | 🔐 Browser |
| **coupang** | `search` `add-to-cart` | 2 | 🔐 Browser |
| **bbc** | `news` | 1 | 🌐 Public |
| **ctrip** | `search` | 1 | 🔐 Browser |
| **github** | `search` | 1 | 🌐 Public |
| **hackernews** | `top` | 1 | 🌐 Public |
| **linkedin** | `search` | 1 | 🔐 Browser |
| **reuters** | `search` | 1 | 🔐 Browser |
| **smzdm** | `search` | 1 | 🔐 Browser |
| **weibo** | `hot` | 1 | 🔐 Browser |
| **yahoo-finance** | `quote` | 1 | 🔐 Browser |
| Site | Commands | Mode |
|------|----------|------|
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` `download` `accept` `reply-dm` | Browser |
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | Browser |
| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` | Desktop |
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` | Browser |
| **codex** | `status` `send` `read` `new` `dump` `extract-diff` `model` `ask` `screenshot` `history` `export` | Desktop |
| **chatwise** | `status` `new` `send` `read` `ask` `model` `history` `export` `screenshot` | Desktop |
| **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 |
| **antigravity** | `status` `send` `read` `new` `dump` `extract-code` `model` `watch` | 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 |
| **apple-podcasts** | `search` `episodes` `top` | Public |
| **xiaoyuzhou** | `podcast` `podcast-episodes` `episode` | Public |
| **zhihu** | `hot` `search` `question` `download` | Browser |
| **youtube** | `search` `video` `transcript` | Browser |
| **boss** | `search` `detail` `recommend` `joblist` `greet` `batchgreet` `send` `chatlist` `chatmsg` `invite` `mark` `exchange` `resume` `stats` | Browser |
| **coupang** | `search` `add-to-cart` | Browser |
| **bbc** | `news` | Public |
| **bloomberg** | `main` `markets` `economics` `industries` `tech` `politics` `businessweek` `opinions` `feeds` `news` | Public / Browser |
| **ctrip** | `search` | Browser |
| **github** | `search` | Public |
| **arxiv** | `search` `paper` | Public |
| **wikipedia** | `search` `summary` | Public |
| **hackernews** | `top` | Public |
| **linkedin** | `search` | Browser |
| **reuters** | `search` | Browser |
| **smzdm** | `search` | Browser |
| **weibo** | `hot` | Browser |
| **yahoo-finance** | `quote` | Browser |
| **sinafinance** | `news` | 🌐 Public |
| **barchart** | `quote` `options` `greeks` `flow` | Browser |
| **chaoxing** | `assignments` `exams` | Browser |
| **grok** | `ask` | Desktop |
| **hf** | `top` | Public |
| **jike** | `feed` `search` `create` `like` `comment` `repost` `notifications` `post` `topic` `user` | Browser |
| **jimeng** | `generate` `history` | Browser |
| **linux-do** | `hot` `latest` `search` `categories` `category` `topic` | Public |
| **stackoverflow** | `hot` `search` `bounties` `unanswered` | Public |
| **steam** | `top-sellers` | Public |
| **weread** | `shelf` `search` `book` `highlights` `notes` `notebooks` `ranking` | Browser |
> **Bloomberg note**: The RSS-backed Bloomberg listing commands (`main`, section feeds, `feeds`) work without a browser. `bloomberg news` is for standard Bloomberg story/article pages that your current Chrome session can already access. Audio and some other non-standard pages may fail, and OpenCLI does not bypass Bloomberg paywall or entitlement checks.
### External CLI Hub
OpenCLI acts as a universal hub for your existing command-line tools. It provides unified discovery, automatic installation, and pure passthrough execution.
| External CLI | Description | Commands Example |
|--------------|-------------|------------------|
| **gh** | GitHub CLI | `opencli gh pr list --limit 5` |
| **obsidian** | Obsidian vault management | `opencli obsidian search query="AI"` |
| **docker** | Docker command-line interface | `opencli docker ps` |
| **kubectl** | Kubernetes command-line tool | `opencli kubectl get pods` |
| **readwise** | Readwise & Reader CLI | `opencli readwise login` |
**Zero Configuration**: OpenCLI purely passes your inputs to the underlying binary via standard I/O streams. The external CLI works exactly as it naturally would, maintaining its standard output formats.
**Auto-Installation**: If you run `opencli gh ...` and `gh` is not installed on your system, OpenCLI will automatically try to install it using your system's package manager (e.g., `brew install gh`) before seamlessly re-running the command.
**Register Your Own**:
Add any local CLI to your OpenCLI registry so AI agents can automatically discover it via the `opencli list` command.
```bash
opencli register mycli
```
### Desktop App Adapters
Each desktop adapter has its own detailed documentation with commands reference, setup guide, and examples:
| App | Description | Doc |
|-----|-------------|-----|
| **Cursor** | Control Cursor IDE — Composer, chat, code extraction | [Doc](./docs/adapters/desktop/cursor.md) |
| **Codex** | Drive OpenAI Codex CLI agent headlessly | [Doc](./docs/adapters/desktop/codex.md) |
| **Antigravity** | Control Antigravity Ultra from terminal | [Doc](./docs/adapters/desktop/antigravity.md) |
| **ChatGPT** | Automate ChatGPT macOS desktop app | [Doc](./docs/adapters/desktop/chatgpt.md) |
| **ChatWise** | Multi-LLM client (GPT-4, Claude, Gemini) | [Doc](./docs/adapters/desktop/chatwise.md) |
| **Notion** | Search, read, write Notion pages | [Doc](./docs/adapters/desktop/notion.md) |
| **Discord** | Discord Desktop — messages, channels, servers | [Doc](./docs/adapters/desktop/discord.md) |
| **Feishu** | 飞书/Lark Desktop via AppleScript | [Doc](./docs/adapters/desktop/feishu.md) |
| **WeChat** | 微信 Desktop via AppleScript + Accessibility | [Doc](./docs/adapters/desktop/wechat.md) |
| **NeteaseMusic** | 网易云音乐 Desktop via CEF/CDP | [Doc](./docs/adapters/desktop/neteasemusic.md) |
## Download Support
OpenCLI supports downloading images, videos, and articles from supported platforms.
### Supported Platforms
| Platform | Content Types | Notes |
|----------|---------------|-------|
| **xiaohongshu** | Images, Videos | Downloads all media from a note |
| **bilibili** | Videos | Requires `yt-dlp` installed |
| **twitter** | Images, Videos | Downloads from user media tab or single tweet |
| **zhihu** | Articles (Markdown) | Exports articles with optional image download |
### Prerequisites
For video downloads from streaming platforms, you need to install `yt-dlp`:
```bash
# Install yt-dlp
pip install yt-dlp
# or
brew install yt-dlp
```
### Usage Examples
```bash
# Download images/videos from Xiaohongshu note
opencli xiaohongshu download --note-id 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
# Download Twitter media from user
opencli twitter download elonmusk --limit 20 --output ./twitter
# Download single tweet media
opencli twitter download --tweet-url "https://x.com/user/status/123" --output ./twitter
# Export Zhihu article to Markdown
opencli zhihu download "https://zhuanlan.zhihu.com/p/xxx" --output ./zhihu
# Export with local images
opencli zhihu download "https://zhuanlan.zhihu.com/p/xxx" --download-images
```
### Pipeline Step (for YAML adapters)
The `download` step can be used in YAML pipelines:
```yaml
pipeline:
- fetch: https://api.example.com/media
- download:
url: ${{ item.imageUrl }}
dir: ./downloads
filename: ${{ item.title | sanitize }}.jpg
concurrency: 5
skip_existing: true
```
## Output Formats
@@ -201,7 +307,7 @@ Explore outputs to `.opencli/explore/<site>/` (manifest.json, endpoints.json, ca
See **[TESTING.md](./TESTING.md)** for the full testing guide, including:
- Current test coverage (unit + E2E tests across 19 sites)
- Current test coverage (unit + E2E tests across browser and desktop adapters)
- How to run tests locally
- How to add tests when creating new adapters
- CI/CD pipeline with sharding
@@ -217,15 +323,15 @@ npx vitest run tests/e2e/ # E2E tests
## Troubleshooting
- **"Failed to connect to Playwright MCP Bridge"**
- Ensure the Playwright MCP extension is installed and **enabled** in your running Chrome.
- Restart the Chrome browser if you just installed the extension.
- **"Extension not connected"**
- Ensure the opencli Browser Bridge extension is installed and **enabled** in `chrome://extensions`.
- **Empty data returns or 'Unauthorized' error**
- 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.
- 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.
- **Node API errors**
- Make sure you are using Node.js >= 18. Some dependencies require modern Node APIs.
- **Token issues**
- Run `opencli doctor` to diagnose token configuration across all tools.
- Make sure you are using Node.js >= 20. Some dependencies require modern Node APIs.
- **Daemon issues**
- Check daemon status: `curl localhost:19825/status`
- View extension logs: `curl localhost:19825/logs`
## Releasing New Versions
+165 -81
View File
@@ -1,7 +1,7 @@
# OpenCLI
> **把任何网站变成你的命令行工具。**
> 零风控 · 复用 Chrome 登录 · AI 自动发现接口 · 80+ 命令 · 19 站点
> **把任何网站、本地工具、Electron 应用变成能够让 AI 调用的命令行**
> 零风控 · 复用 Chrome 登录 · AI 自动发现接口 · 全能 CLI 枢纽
[English](./README.md)
@@ -9,7 +9,14 @@
[![Node.js Version](https://img.shields.io/node/v/@jackwener/opencli?style=flat-square)](https://nodejs.org)
[![License](https://img.shields.io/npm/l/@jackwener/opencli?style=flat-square)](./LICENSE)
OpenCLI 将任何网站变成命令行工具 — B站、知乎、小红书、Twitter/X、Reddit、YouTube 等 [19 个站点](#内置命令) — 复用浏览器登录态,AI 驱动探索。
OpenCLI 将任何网站或 Electron 应用(如 Antigravity变成命令行工具 — B站、知乎、小红书、Twitter/X、Reddit、YouTube 等[多种站点与应用](#内置命令) — 复用浏览器登录态,AI 驱动探索。
**专为 AI Agent 打造**:只需在全局 `.cursorrules``AGENT.md` 中配置简单指令,引导 AI 通过 Bash 执行 `opencli list` 来检索可用的 CLI 工具及其用法。随后,将你常用的 CLI 列表整合注册进去(`opencli register mycli`),AI 便能瞬间学会自动调用相应的本地工具!
**opencli 支持 CLI 化所有 electron 应用!最强大更新来袭!**
CLI all electron!现在支持把所有 electron 应用 CLI 化,从而组合出各种神奇的能力。
如果你在使用诸如 Antigravity Ultra 等工具时觉得不够灵活或难以扩展,现在通过 OpenCLI 把他 CLI 化,轻松打破界限。
现在,**AI 可以自己控制自己**!结合 cc/openclaw 就可以远程控制任何 electron 应用!无限玩法!!
---
@@ -19,8 +26,11 @@ OpenCLI 将任何网站变成命令行工具 — B站、知乎、小红书、Twi
- [前置要求](#前置要求)
- [快速开始](#快速开始)
- [内置命令](#内置命令)
- [桌面应用适配器](#桌面应用适配器)
- [下载支持](#下载支持)
- [输出格式](#输出格式)
- [致 AI Agent(开发者指南)](#致-ai-agent开发者指南)
- [远程 Chrome(服务器/无头环境)](#远程-chrome服务器无头环境)
- [常见问题排查](#常见问题排查)
- [版本发布](#版本发布)
- [License](#license)
@@ -29,78 +39,49 @@ OpenCLI 将任何网站变成命令行工具 — B站、知乎、小红书、Twi
## 亮点
- **多站点覆盖** — B站、知乎、小红书、Twitter、Reddit 等 19 个站点,80+ 命令
- **CLI All Electron** — 支持把所有 electron 应用(如 Antigravity UltraCLI 化,让 AI 控制自己!
- **多站点覆盖** — 覆盖 B站、知乎、小红书、Twitter、Reddit,以及多种桌面应用
- **零风控** — 复用 Chrome 登录态,无需存储任何凭证
- **自修复配置** — `opencli setup` 自动发现 Token`opencli doctor` 诊断 10+ 工具配置;`--fix` 一键修复
- **自修复配置** — `opencli setup` 检查 Browser Bridge 连通性`opencli doctor` 诊断 daemon、扩展和浏览器连接状态
- **AI 原生** — `explore` 自动发现 API`synthesize` 生成适配器,`cascade` 探测认证策略
- **动态加载引擎** — 声明式的 `.yaml` 或者底层定制的 `.ts` 适配器,放入 `clis/` 文件夹即可自动注册生效
## 前置要求
- **Node.js**: >= 18.0.0
- **Node.js**: >= 20.0.0
- **Chrome** 浏览器正在运行,且**已登录目标网站**(如 bilibili.com、zhihu.com、xiaohongshu.com
> **⚠️ 重要**:大多数命令复用你的 Chrome 登录状态。运行命令前,你必须已在 Chrome 中打开目标网站并完成登录。如果获取到空数据或报错,请先检查你的浏览器登录状态。
OpenCLI 通过 Playwright MCP Bridge 扩展与你的浏览器通信
OpenCLI 通过轻量化的 **Browser Bridge** Chrome 扩展 + 微型 daemon 与浏览器通信(零配置,自动启动)
### Playwright MCP Bridge 扩展配置
### Browser Bridge 扩展配置
1. 安装 **[Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm)** 扩展
2. 运行 `opencli setup` — 自动发现 Token、分发到各工具、验证连通性:
你可以选择以下任一方式安装扩展
```bash
opencli setup
```
**方式一:下载构建好的安装包(推荐)**
1. 到 GitHub [Releases 页面](https://github.com/jackwener/opencli/releases) 下载最新的 `opencli-extension.zip``opencli-extension.crx`
2. 打开 Chrome 的 `chrome://extensions`,启用右上角的 **开发者模式**
3.`.crx` 拖入浏览器窗口,或将解压后的文件夹拖入即可完成安装。
交互式 TUI 会:
- 🔍 从 Chrome 自动发现 `PLAYWRIGHT_MCP_EXTENSION_TOKEN`(无需手动复制)
- ☑️ 显示所有支持的工具(Codex、Cursor、Claude Code、Gemini CLI 等)
- ✏️ 只更新你选中的文件(空格切换,回车确认)
- 🔌 完成后自动验证浏览器连通性
**方式二:加载源码(针对开发者)**
1. 同样在 `chrome://extensions` 开启 **开发者模式**
2. 点击 **加载已解压的扩展程序**,选择本仓库代码树中的 `extension/` 文件夹。
> **Tip**:后续诊断和维护用 `opencli doctor`
完成!运行任何 opencli 浏览器命令时,后台微型 daemon 会自动启动与浏览器通信。无需配 API Token,零代码配置。
> **Tip**:后续诊断用 `opencli doctor`
> ```bash
> opencli doctor # 只读 Token 与配置诊断
> opencli doctor --live # 额外测试浏览器连通性
> opencli doctor --fix # 修复不一致的配置(交互确认)
> opencli doctor --fix -y # 无交互直接修复所有配置
> opencli doctor # 检查扩展和 daemon 连通性
> opencli doctor --live # 额外测试浏览器命令
> ```
<details>
<summary>手动配置(备选方案)</summary>
配置你的 MCP 客户端(如 Claude/Cursor 等):
```json
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp@latest", "--extension"],
"env": {
"PLAYWRIGHT_MCP_EXTENSION_TOKEN": "<你的-token>"
}
}
}
}
```
在终端环境变量中导出(建议写进 `~/.zshrc`):
```bash
export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<你的-token>"
```
</details>
## 快速开始
### npm 全局安装(推荐)
```bash
npm install -g @jackwener/opencli
opencli setup # 首次使用:配置 Playwright MCP token
```
直接使用:
@@ -133,29 +114,133 @@ npm install -g @jackwener/opencli@latest
## 内置命令
**19 个站点 · 80+ 命令**运行 `opencli list` 查看完整注册表。
运行 `opencli list` 查看完整注册表。
| 站点 | 命令 | 数量 | 模式 |
|------|------|:----:|------|
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` | 18 | 🔐 浏览器 |
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 15 | 🔐 浏览器 |
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` | 11 | 🔐 浏览器 |
| **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` | 6 | 🌐 / 🔐 |
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 6 | 🔐 浏览器 |
| **xiaohongshu** | `search` `notifications` `feed` `me` `user` | 5 | 🔐 浏览器 |
| **youtube** | `search` `video` `transcript` | 3 | 🔐 浏览器 |
| **zhihu** | `hot` `search` `question` | 3 | 🔐 浏览器 |
| **boss** | `search` `detail` | 2 | 🔐 浏览器 |
| **coupang** | `search` `add-to-cart` | 2 | 🔐 浏览器 |
| **bbc** | `news` | 1 | 🌐 公共 API |
| **ctrip** | `search` | 1 | 🔐 浏览器 |
| **github** | `search` | 1 | 🌐 公共 API |
| **hackernews** | `top` | 1 | 🌐 公共 API |
| **linkedin** | `search` | 1 | 🔐 浏览器 |
| **reuters** | `search` | 1 | 🔐 浏览器 |
| **smzdm** | `search` | 1 | 🔐 浏览器 |
| **weibo** | `hot` | 1 | 🔐 浏览器 |
| **yahoo-finance** | `quote` | 1 | 🔐 浏览器 |
| 站点 | 命令 | 模式 |
|------|------|------|
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` `download` `accept` `reply-dm` | 浏览器 |
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 浏览器 |
| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` | 桌面端 |
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` | 浏览器 |
| **codex** | `status` `send` `read` `new` `dump` `extract-diff` `model` `ask` `screenshot` `history` `export` | 桌面端 |
| **chatwise** | `status` `new` `send` `read` `ask` `model` `history` `export` `screenshot` | 桌面端 |
| **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` | 浏览器 |
| **antigravity** | `status` `send` `read` `new` `dump` `extract-code` `model` `watch` | 桌面端 |
| **chatgpt** | `status` `new` `send` `read` `ask` | 桌面端 |
| **xiaohongshu** | `search` `notifications` `feed` `user` `download` `creator-notes` `creator-note-detail` `creator-notes-summary` `creator-profile` `creator-stats` | 浏览器 |
| **apple-podcasts** | `search` `episodes` `top` | 公开 |
| **xiaoyuzhou** | `podcast` `podcast-episodes` `episode` | 公开 |
| **zhihu** | `hot` `search` `question` `download` | 浏览器 |
| **youtube** | `search` `video` `transcript` | 浏览器 |
| **boss** | `search` `detail` `recommend` `joblist` `greet` `batchgreet` `send` `chatlist` `chatmsg` `invite` `mark` `exchange` `resume` `stats` | 浏览器 |
| **coupang** | `search` `add-to-cart` | 浏览器 |
| **bbc** | `news` | 公共 API |
| **bloomberg** | `main` `markets` `economics` `industries` `tech` `politics` `businessweek` `opinions` `feeds` `news` | 公共 API / 浏览器 |
| **ctrip** | `search` | 浏览器 |
| **github** | `search` | 公共 API |
| **arxiv** | `search` `paper` | 公开 |
| **wikipedia** | `search` `summary` | 公开 |
| **hackernews** | `top` | 公共 API |
| **linkedin** | `search` | 浏览器 |
| **reuters** | `search` | 浏览器 |
| **smzdm** | `search` | 浏览器 |
| **weibo** | `hot` | 浏览器 |
| **yahoo-finance** | `quote` | 浏览器 |
| **sinafinance** | `news` | 🌐 公开 |
| **barchart** | `quote` `options` `greeks` `flow` | 浏览器 |
| **chaoxing** | `assignments` `exams` | 浏览器 |
| **grok** | `ask` | 桌面端 |
| **hf** | `top` | 公开 |
| **jike** | `feed` `search` `create` `like` `comment` `repost` `notifications` `post` `topic` `user` | 浏览器 |
| **jimeng** | `generate` `history` | 浏览器 |
| **linux-do** | `hot` `latest` `search` `categories` `category` `topic` | 公开 |
| **stackoverflow** | `hot` `search` `bounties` `unanswered` | 公开 |
| **steam** | `top-sellers` | 公开 |
| **weread** | `shelf` `search` `book` `highlights` `notes` `notebooks` `ranking` | 浏览器 |
> **Bloomberg 说明**Bloomberg 的 RSS 列表命令(`main`、各栏目 feed、`feeds`)无需浏览器即可使用。`bloomberg news` 适用于当前 Chrome 会话本身就能访问的标准 Bloomberg 文章页。音频页和部分非标准页面可能失败,OpenCLI 也不会绕过 Bloomberg 的付费墙、登录或权限校验。
### 桌面应用适配器
每个桌面适配器都有自己详细的文档说明,包括命令参考、启动配置与使用示例:
| 应用 | 描述 | 文档 |
|-----|-------------|-----|
| **Cursor** | 控制 Cursor IDE — Composer、对话、代码提取等 | [README](./src/clis/cursor/README.md) |
| **Codex** | 在后台(无头)驱动 OpenAI Codex CLI Agent | [README](./src/clis/codex/README.md) |
| **Antigravity** | 在终端直接控制 Antigravity Ultra | [README](./src/clis/antigravity/README.md) |
| **ChatGPT** | 自动化操作 ChatGPT macOS 桌面客户端 | [README](./src/clis/chatgpt/README.md) |
| **ChatWise** | 多 LLM 客户端(GPT-4、Claude、Gemini | [README](./src/clis/chatwise/README.md) |
| **Notion** | 搜索、读取、写入 Notion 页面 | [README](./src/clis/notion/README.md) |
| **Discord** | Discord 桌面版 — 消息、频道、服务器 | [README](./src/clis/discord-app/README.md) |
| **Feishu** | 飞书/Lark 桌面版 (AppleScript 驱动) | [README](./src/clis/feishu/README.md) |
| **WeChat** | 微信 Mac 桌面端 (AppleScript + 无障碍接口) | [README](./src/clis/wechat/README.md) |
| **NeteaseMusic** | 网易云音乐 (CEF/CDP 驱动) | [README](./src/clis/neteasemusic/README.md) |
## 下载支持
OpenCLI 支持从各平台下载图片、视频和文章。
### 支持的平台
| 平台 | 内容类型 | 说明 |
|------|----------|------|
| **小红书** | 图片、视频 | 下载笔记中的所有媒体文件 |
| **B站** | 视频 | 需要安装 `yt-dlp` |
| **Twitter/X** | 图片、视频 | 从用户媒体页或单条推文下载 |
| **知乎** | 文章(Markdown) | 导出文章,可选下载图片到本地 |
### 前置依赖
下载流媒体平台的视频需要安装 `yt-dlp`
```bash
# 安装 yt-dlp
pip install yt-dlp
# 或者
brew install yt-dlp
```
### 使用示例
```bash
# 下载小红书笔记中的图片/视频
opencli xiaohongshu download --note-id abc123 --output ./xhs
# 下载B站视频(需要 yt-dlp
opencli bilibili download --bvid BV1xxx --output ./bilibili
opencli bilibili download --bvid BV1xxx --quality 1080p # 指定画质
# 下载 Twitter 用户的媒体
opencli twitter download elonmusk --limit 20 --output ./twitter
# 下载单条推文的媒体
opencli twitter download --tweet-url "https://x.com/user/status/123" --output ./twitter
# 导出知乎文章为 Markdown
opencli zhihu download "https://zhuanlan.zhihu.com/p/xxx" --output ./zhihu
# 导出并下载图片
opencli zhihu download "https://zhuanlan.zhihu.com/p/xxx" --download-images
```
### Pipeline Step(用于 YAML 适配器)
`download` step 可以在 YAML 管线中使用:
```yaml
pipeline:
- fetch: https://api.example.com/media
- download:
url: ${{ item.imageUrl }}
dir: ./downloads
filename: ${{ item.title | sanitize }}.jpg
concurrency: 5
skip_existing: true
```
## 输出格式
@@ -198,16 +283,15 @@ opencli cascade https://api.example.com/data
## 常见问题排查
- **"Failed to connect to Playwright MCP Bridge"** 报错
- 确保你当前的 Chrome 已安装且**开启了** Playwright MCP Bridge 浏览器插件
- 如果是刚装完插件,需要重启 Chrome 浏览器。
- **"Extension not connected" 报错**
- 确保你当前的 Chrome 已安装且**开启了** opencli Browser Bridge 扩展(在 `chrome://extensions` 中检查)
- **返回空数据,或者报错 "Unauthorized"**
- Chrome 里的登录态可能已经过期(甚至被要求过滑动验证码)。请打开当前 Chrome 页面,在新标签页重新手工登录或刷新该页面。
- Chrome 里的登录态可能已经过期。请打开当前 Chrome 页面,在新标签页重新手工登录或刷新该页面。
- **Node API 错误 (如 parseArgs, fs 等)**
- 确保 Node.js 版本 `>= 18`。旧版不支持我们使用的现代核心库 API
- **Token 问题**
- 运行 `opencli doctor` 诊断所有工具的 Token 配置状态。
- 使用 `opencli doctor --live` 测试浏览器连通性。
- 确保 Node.js 版本 `>= 20`
- **Daemon 问题**
- 检查 daemon 状态:`curl localhost:19825/status`
- 查看扩展日志:`curl localhost:19825/logs`
## 版本发布
+124 -46
View File
@@ -1,18 +1,18 @@
---
name: opencli
description: "OpenCLI — Make any website your CLI. Zero risk, AI-powered, reuse Chrome login. 80+ commands across 19 sites."
version: 0.7.3
description: "OpenCLI — Make any website or Electron App your CLI. Zero risk, AI-powered, reuse Chrome login. 150+ commands across 30+ sites."
version: 1.1.0
author: jackwener
tags: [cli, browser, web, mcp, playwright, bilibili, zhihu, twitter, github, v2ex, hackernews, reddit, xiaohongshu, xueqiu, youtube, boss, coupang, AI, agent]
tags: [cli, browser, web, chrome-extension, cdp, bilibili, zhihu, twitter, github, v2ex, hackernews, reddit, xiaohongshu, xueqiu, youtube, boss, coupang, AI, agent]
---
# OpenCLI
> Make any website your CLI. Reuse Chrome login, zero risk, AI-powered discovery.
> Make any website or Electron App your CLI. Reuse Chrome login, zero risk, AI-powered discovery.
> [!CAUTION]
> **AI Agent 必读:创建或修改任何适配器之前,你必须先阅读 [CLI-EXPLORER.md](./CLI-EXPLORER.md)**
> 该文档包含完整的 API 发现工作流(必须使用 Playwright MCP Bridge 浏览器探索)、5 级认证策略决策树、平台 SDK 速查表、`tap` 步骤调试流程、分页 API 模板、级联请求模式、以及常见陷阱。
> 该文档包含完整的 API 发现工作流(必须使用浏览器探索)、5 级认证策略决策树、平台 SDK 速查表、`tap` 步骤调试流程、分页 API 模板、级联请求模式、以及常见陷阱。
> **本文件(SKILL.md)仅提供命令参考和简化模板,不足以正确开发适配器。**
## Install & Run
@@ -34,8 +34,8 @@ npm update -g @jackwener/opencli
Browser commands require:
1. Chrome browser running **(logged into target sites)**
2. [Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm) extension installed
3. Run `opencli setup` to auto-discover token and configure all tools
2. **opencli Browser Bridge** Chrome extension installed (load `extension/` as unpacked in `chrome://extensions`)
3. No further setup needed — the daemon auto-starts on first browser command
> **Note**: You must be logged into the target website in Chrome before running commands. Tabs opened during command execution are auto-closed afterwards.
@@ -48,7 +48,7 @@ Public API commands (`hackernews`, `github search`, `v2ex`) need no browser.
```bash
# Bilibili (browser)
opencli bilibili hot --limit 10 # B站热门视频
opencli bilibili search --keyword "rust" # 搜索视频
opencli bilibili search "rust" # 搜索视频 (query positional)
opencli bilibili me # 我的信息
opencli bilibili favorite # 我的收藏
opencli bilibili history --limit 20 # 观看历史
@@ -61,15 +61,19 @@ opencli bilibili following --limit 20 # 我的关注列表 (支持 --uid 查
# 知乎 (browser)
opencli zhihu hot --limit 10 # 知乎热榜
opencli zhihu search --keyword "AI" # 搜索
opencli zhihu question --id 34816524 # 问题详情和回答
opencli zhihu search "AI" # 搜索 (query positional)
opencli zhihu question 34816524 # 问题详情和回答 (id positional)
# 小红书 (browser)
opencli xiaohongshu search --keyword "美食" # 搜索笔记
opencli xiaohongshu search "美食" # 搜索笔记 (query positional)
opencli xiaohongshu notifications # 通知(mentions/likes/connections
opencli xiaohongshu feed --limit 10 # 推荐 Feed
opencli xiaohongshu me # 我的信息
opencli xiaohongshu user --uid xxx # 用户主页
opencli xiaohongshu user xxx # 用户主页 (id positional)
opencli xiaohongshu creator-notes --limit 10 # 创作者笔记列表
opencli xiaohongshu creator-note-detail --note-id xxx # 笔记详情
opencli xiaohongshu creator-notes-summary # 笔记数据概览
opencli xiaohongshu creator-profile # 创作者资料
opencli xiaohongshu creator-stats # 创作者数据统计
# 雪球 Xueqiu (browser)
opencli xueqiu hot-stock --limit 10 # 雪球热门股票榜
@@ -77,15 +81,15 @@ opencli xueqiu stock --symbol SH600519 # 查看股票实时行情
opencli xueqiu watchlist # 获取自选股/持仓列表
opencli xueqiu feed # 我的关注 timeline
opencli xueqiu hot --limit 10 # 雪球热榜
opencli xueqiu search --keyword "特斯拉" # 搜索
opencli xueqiu search "特斯拉" # 搜索 (query positional)
# GitHub (public)
opencli github search --keyword "cli" # 搜索仓库
opencli github search "cli" # 搜索仓库 (query positional)
# Twitter/X (browser)
opencli twitter trending --limit 10 # 热门话题
opencli twitter bookmarks --limit 20 # 获取收藏的书签推文
opencli twitter search --keyword "AI" # 搜索推文
opencli twitter search "AI" # 搜索推文 (query positional)
opencli twitter profile elonmusk # 用户资料
opencli twitter timeline --limit 20 # 时间线
opencli twitter thread 1234567890 # 推文 thread(原文 + 回复)
@@ -100,15 +104,15 @@ opencli reddit hot --limit 10 # 热门帖子
opencli reddit hot --subreddit programming # 指定子版块
opencli reddit frontpage --limit 10 # 首页 /r/all
opencli reddit popular --limit 10 # /r/popular 热门
opencli reddit search --query "AI" --sort top --time week # 搜索(支持排序+时间过滤)
opencli reddit subreddit --name rust --sort top --time month # 子版块浏览(支持时间过滤)
opencli reddit read --post_id 1abc123 # 阅读帖子 + 评论
opencli reddit user --username spez # 用户资料(karma、注册时间)
opencli reddit user-posts --username spez # 用户发帖历史
opencli reddit user-comments --username spez # 用户评论历史
opencli reddit upvote --post_id xxx --direction up # 投票(up/down/none
opencli reddit save --post_id xxx # 收藏帖子
opencli reddit comment --post_id xxx --text "Great!" # 发表评论
opencli reddit search "AI" --sort top --time week # 搜索(支持排序+时间过滤)
opencli reddit subreddit rust --sort top --time month # 子版块浏览(支持时间过滤)
opencli reddit read --post-id 1abc123 # 阅读帖子 + 评论
opencli reddit user spez # 用户资料(karma、注册时间)
opencli reddit user-posts spez # 用户发帖历史
opencli reddit user-comments spez # 用户评论历史
opencli reddit upvote --post-id xxx --direction up # 投票(up/down/none
opencli reddit save --post-id xxx # 收藏帖子
opencli reddit comment --post-id xxx "Great!" # 发表评论 (text positional)
opencli reddit subscribe --subreddit python # 订阅子版块
opencli reddit saved --limit 10 # 我的收藏
opencli reddit upvoted --limit 10 # 我的赞
@@ -116,7 +120,7 @@ opencli reddit upvoted --limit 10 # 我的赞
# V2EX (public + browser)
opencli v2ex hot --limit 10 # 热门话题
opencli v2ex latest --limit 10 # 最新话题
opencli v2ex topic --id 1024 # 主题详情
opencli v2ex topic 1024 # 主题详情 (id positional)
opencli v2ex daily # 每日签到 (browser)
opencli v2ex me # 我的信息 (browser)
opencli v2ex notifications --limit 10 # 通知 (browser)
@@ -131,41 +135,113 @@ opencli bbc news --limit 10 # BBC News RSS headlines
opencli weibo hot --limit 10 # 微博热搜
# BOSS直聘 (browser)
opencli boss search --query "AI agent" # 搜索职位
opencli boss detail --securityId xxx # 职位详情
opencli boss search "AI agent" # 搜索职位 (query positional)
opencli boss detail --security-id xxx # 职位详情
opencli boss recommend --limit 10 # 推荐职位
opencli boss joblist --limit 10 # 职位列表
opencli boss greet --security-id xxx # 打招呼
opencli boss batchgreet --job-id xxx # 批量打招呼
opencli boss send --uid xxx "消息内容" # 发消息 (text positional)
opencli boss chatlist --limit 10 # 聊天列表
opencli boss chatmsg --security-id xxx # 聊天记录
opencli boss invite --security-id xxx # 邀请沟通
opencli boss mark --security-id xxx # 标记管理
opencli boss exchange --security-id xxx # 交换联系方式
opencli boss resume # 简历管理
opencli boss stats # 数据统计
# YouTube (browser)
opencli youtube search --query "rust" # 搜索视频
opencli youtube video --url "https://www.youtube.com/watch?v=xxx" # 视频元数据(标题、播放量、描述等)
opencli youtube transcript --url "https://www.youtube.com/watch?v=xxx" # 获取视频字幕/转录
opencli youtube transcript --url "xxx" --lang zh-Hans --mode raw # 指定语言 + 原始时间戳模式
opencli youtube search "rust" # 搜索视频 (query positional)
opencli youtube video "https://www.youtube.com/watch?v=xxx" # 视频元数据
opencli youtube transcript "https://www.youtube.com/watch?v=xxx" # 获取视频字幕/转录
opencli youtube transcript "xxx" --lang zh-Hans --mode raw # 指定语言 + 原始时间戳模式
# Yahoo Finance (browser)
opencli yahoo-finance quote --symbol AAPL # 股票行情
# Sina Finance
opencli sinafinance news --limit 10 --type 1 # 7x24实时快讯 (0=全部 1=A股 2=宏观 3=公司 4=数据 5=市场 6=国际 7=观点 8=央行 9=其它)
# Reuters (browser)
opencli reuters search --query "AI" # 路透社搜索
opencli reuters search "AI" # 路透社搜索 (query positional)
# 什么值得买 (browser)
opencli smzdm search --keyword "耳机" # 搜索好价
opencli smzdm search "耳机" # 搜索好价 (query positional)
# 携程 (browser)
opencli ctrip search --query "三亚" # 搜索目的地
opencli ctrip search "三亚" # 搜索目的地 (query positional)
# Antigravity (Electron/CDP)
opencli antigravity status # 检查 CDP 连接
opencli antigravity send "hello" # 发送文本到当前 agent 聊天框
opencli antigravity read # 读取整个聊天记录面板
opencli antigravity new # 清空聊天、开启新对话
opencli antigravity extract-code # 自动抽取 AI 回复中的代码块
opencli antigravity model claude # 切换底层模型
opencli antigravity watch # 流式监听增量消息
# Barchart (browser)
opencli barchart quote --symbol AAPL # 股票行情
opencli barchart options --symbol AAPL # 期权链
opencli barchart greeks --symbol AAPL # 期权 Greeks
opencli barchart flow --limit 20 # 异常期权活动
# Jike 即刻 (browser)
opencli jike feed --limit 10 # 动态流
opencli jike search "AI" # 搜索 (query positional)
opencli jike create "内容" # 发布动态 (text positional)
opencli jike like xxx # 点赞 (id positional)
opencli jike comment xxx "评论" # 评论 (id + text positional)
opencli jike repost xxx # 转发 (id positional)
opencli jike notifications # 通知
# Linux.do (public)
opencli linux-do hot --limit 10 # 热门话题
opencli linux-do latest --limit 10 # 最新话题
opencli linux-do search "rust" # 搜索 (query positional)
opencli linux-do topic 1024 # 主题详情 (id positional)
# StackOverflow (public)
opencli stackoverflow hot --limit 10 # 热门问题
opencli stackoverflow search "typescript" # 搜索 (query positional)
opencli stackoverflow bounties --limit 10 # 悬赏问题
# WeRead 微信读书 (browser)
opencli weread shelf --limit 10 # 书架
opencli weread search "AI" # 搜索图书 (query positional)
opencli weread book xxx # 图书详情 (book-id positional)
opencli weread highlights xxx # 划线笔记 (book-id positional)
opencli weread notes xxx # 想法笔记 (book-id positional)
opencli weread ranking --limit 10 # 排行榜
# Jimeng 即梦 AI (browser)
opencli jimeng generate --prompt "描述" # AI 生图
opencli jimeng history --limit 10 # 生成历史
# Grok (Desktop)
opencli grok ask "问题" # 提问 Grok (text positional)
# HuggingFace (public)
opencli hf top --limit 10 # 热门模型
# 超星学习通 (browser)
opencli chaoxing assignments # 作业列表
opencli chaoxing exams # 考试列表
```
### Management Commands
```bash
opencli list # List all commands
opencli list # List all commands (including External CLIs)
opencli list --json # JSON output
opencli list -f yaml # YAML output
opencli install <name> # Auto-install an external CLI (e.g., gh, obsidian)
opencli register <name> # Register a local custom CLI for unified discovery
opencli validate # Validate all CLI definitions
opencli validate bilibili # Validate specific site
opencli setup # Interactive token setup (auto-discover + TUI checkbox)
opencli doctor # Diagnose token & extension config across all tools
opencli setup # Interactive Browser Bridge setup and connectivity check
opencli doctor # Diagnose daemon, extension, and browser connectivity
opencli doctor --live # Also test live browser connectivity
opencli doctor --fix # Fix mismatched configs (interactive confirmation)
opencli doctor --fix -y # Fix all configs non-interactively
```
### AI Agent Workflow
@@ -218,7 +294,7 @@ opencli bilibili hot -v # Show each pipeline step and data flow
> [!IMPORTANT]
> **完整模式 — 在写任何代码之前,先阅读 [CLI-EXPLORER.md](./CLI-EXPLORER.md)。**
> 它包含:① AI Agent 浏览器探索工作流(必须用 Playwright MCP 抓包验证 API② 认证策略决策树 ③ 平台 SDK(如 Bilibili 的 `apiGet`/`fetchJson`)④ YAML vs TS 选择指南 ⑤ `tap` 步骤调试方法 ⑥ 级联请求模板 ⑦ 常见陷阱表。
> 它包含:① AI Agent 浏览器探索工作流 ② 认证策略决策树 ③ 平台 SDK(如 Bilibili 的 `apiGet`/`fetchJson`)④ YAML vs TS 选择指南 ⑤ `tap` 步骤调试方法 ⑥ 级联请求模板 ⑦ 常见陷阱表。
> **下方仅为简化模板参考,直接使用极易踩坑。**
### YAML Pipeline (declarative, recommended)
@@ -288,7 +364,7 @@ cli({
site: 'mysite',
name: 'search',
strategy: Strategy.INTERCEPT, // Or COOKIE
args: [{ name: 'keyword', required: true }],
args: [{ name: 'query', required: true, positional: true }],
columns: ['rank', 'title', 'url'],
func: async (page, kwargs) => {
await page.goto('https://www.mysite.com/search');
@@ -339,7 +415,7 @@ cli({
```yaml
# Arguments with defaults
${{ args.keyword }}
${{ args.query }}
${{ args.limit | default(20) }}
# Current item (in map/filter)
@@ -365,16 +441,18 @@ ${{ index + 1 }}
| Variable | Default | Description |
|----------|---------|-------------|
| `OPENCLI_DAEMON_PORT` | 19825 | Daemon listen port |
| `OPENCLI_BROWSER_CONNECT_TIMEOUT` | 30 | Browser connection timeout (sec) |
| `OPENCLI_BROWSER_COMMAND_TIMEOUT` | 45 | Command execution timeout (sec) |
| `OPENCLI_BROWSER_EXPLORE_TIMEOUT` | 120 | Explore timeout (sec) |
| `PLAYWRIGHT_MCP_EXTENSION_TOKEN` | — | Auto-approve extension connection |
| `OPENCLI_VERBOSE` | — | Show daemon/extension logs |
## Troubleshooting
| Issue | Solution |
|-------|----------|
| `npx not found` | Install Node.js: `brew install node` |
| `Timed out connecting to browser` | 1) Chrome must be open 2) Install MCP Bridge extension and configure token |
| `Extension not connected` | 1) Chrome must be open 2) Install opencli Browser Bridge extension |
| `Target page context` error | Add `navigate:` step before `evaluate:` in YAML |
| Empty table data | Check if evaluate returns JSON string (MCP parsing) or data path is wrong |
| Empty table data | Check if evaluate returns correct data path |
| Daemon issues | `curl localhost:19825/status` to check, `curl localhost:19825/logs` for extension logs |
+7 -7
View File
@@ -105,10 +105,10 @@ npx vitest src/
### 浏览器命令本地测试须知
- `PLAYWRIGHT_MCP_EXTENSION_TOKEN` 时,opencli 自动启动一个独立浏览器实例
- opencli 通过 Browser Bridge 扩展连接已运行的 Chrome 浏览器
- `browser-public.test.ts` 使用 `tryBrowserCommand()`,站点反爬导致空数据时 warn + pass
- `browser-auth.test.ts` 验证 **graceful failure**(不 crash 不 hang 即通过)
- 如需测试完整登录态,保持 Chrome 登录态 + 设置 `PLAYWRIGHT_MCP_EXTENSION_TOKEN`,手动跑对应测试
- 如需测试完整登录态,保持 Chrome 登录态并安装 Browser Bridge 扩展,手动跑对应测试
---
@@ -202,12 +202,12 @@ steps:
## 浏览器模式
opencli 根据 `PLAYWRIGHT_MCP_EXTENSION_TOKEN` 环境变量自动选择模式
opencli 通过 Browser Bridge 扩展连接浏览器
| 条件 | 模式 | MCP 参数 | 使用场景 |
|---|---|---|---|
| Token 已设置 | Extension 模式 | `--extension` | 本地用户,连接已登录的 Chrome |
| Token 未设置 | Standalone 模式 | (无特殊 flag) | CI 或无扩展环境,自启浏览器 |
| 条件 | 模式 | 使用场景 |
|---|---|---|
| 扩展已安装 | Extension 模式 | 本地用户,连接已登录的 Chrome |
| 扩展未安装 | CLI 报错提示安装 | 需要安装 Browser Bridge 扩展 |
CI 中使用 `OPENCLI_BROWSER_EXECUTABLE_PATH` 指定真实 Chrome 路径:
+205
View File
@@ -0,0 +1,205 @@
import { defineConfig } from 'vitepress'
export default defineConfig({
base: '/docs/',
title: 'OpenCLI',
description: 'Make any website or Electron App your CLI — AI-powered, account-safe, self-healing.',
head: [
['meta', { property: 'og:title', content: 'OpenCLI Documentation' }],
['meta', { property: 'og:description', content: 'Make any website or Electron App your CLI.' }],
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
],
locales: {
root: {
label: 'English',
lang: 'en',
themeConfig: {
nav: [
{ text: 'Guide', link: '/guide/getting-started' },
{ text: 'Adapters', link: '/adapters/' },
{ text: 'Developer', link: '/developer/contributing' },
{ text: 'Advanced', link: '/advanced/cdp' },
],
sidebar: {
'/guide/': [
{
text: 'Guide',
items: [
{ text: 'Getting Started', link: '/guide/getting-started' },
{ text: 'Installation', link: '/guide/installation' },
{ text: 'Browser Bridge', link: '/guide/browser-bridge' },
{ text: 'Troubleshooting', link: '/guide/troubleshooting' },
],
},
],
'/adapters/': [
{
text: 'Adapters Overview',
items: [
{ text: 'All Adapters', link: '/adapters/' },
],
},
{
text: 'Browser Adapters',
collapsed: false,
items: [
{ text: 'Twitter / X', link: '/adapters/browser/twitter' },
{ text: 'Reddit', link: '/adapters/browser/reddit' },
{ text: 'Bilibili', link: '/adapters/browser/bilibili' },
{ text: 'Zhihu', link: '/adapters/browser/zhihu' },
{ text: 'Xiaohongshu', link: '/adapters/browser/xiaohongshu' },
{ text: 'Weibo', link: '/adapters/browser/weibo' },
{ text: 'YouTube', link: '/adapters/browser/youtube' },
{ text: 'Xueqiu', link: '/adapters/browser/xueqiu' },
{ text: 'V2EX', link: '/adapters/browser/v2ex' },
{ text: 'Bloomberg', link: '/adapters/browser/bloomberg' },
{ text: 'LinkedIn', link: '/adapters/browser/linkedin' },
{ text: 'Coupang', link: '/adapters/browser/coupang' },
{ text: 'BOSS Zhipin', link: '/adapters/browser/boss' },
{ text: 'Ctrip', link: '/adapters/browser/ctrip' },
{ text: 'Reuters', link: '/adapters/browser/reuters' },
{ text: 'SMZDM', link: '/adapters/browser/smzdm' },
{ text: 'Jike', link: '/adapters/browser/jike' },
{ text: 'Jimeng', link: '/adapters/browser/jimeng' },
{ text: 'LINUX DO', link: '/adapters/browser/linux-do' },
{ text: 'Chaoxing', link: '/adapters/browser/chaoxing' },
{ text: 'Grok', link: '/adapters/browser/grok' },
{ text: 'WeRead', link: '/adapters/browser/weread' },
],
},
{
text: 'Public API Adapters',
collapsed: false,
items: [
{ text: 'HackerNews', link: '/adapters/browser/hackernews' },
{ text: 'GitHub', link: '/adapters/browser/github' },
{ text: 'BBC', link: '/adapters/browser/bbc' },
{ text: 'Apple Podcasts', link: '/adapters/browser/apple-podcasts' },
{ text: 'Xiaoyuzhou', link: '/adapters/browser/xiaoyuzhou' },
{ text: 'Yahoo Finance', link: '/adapters/browser/yahoo-finance' },
{ text: 'arXiv', link: '/adapters/browser/arxiv' },
{ text: 'Barchart', link: '/adapters/browser/barchart' },
{ text: 'Hugging Face', link: '/adapters/browser/hf' },
{ text: 'Sina Finance', link: '/adapters/browser/sinafinance' },
{ text: 'Stack Overflow', link: '/adapters/browser/stackoverflow' },
{ text: 'Wikipedia', link: '/adapters/browser/wikipedia' },
],
},
{
text: 'Desktop Adapters',
collapsed: false,
items: [
{ text: 'Cursor', link: '/adapters/desktop/cursor' },
{ text: 'Codex', link: '/adapters/desktop/codex' },
{ text: 'Antigravity', link: '/adapters/desktop/antigravity' },
{ text: 'ChatGPT', link: '/adapters/desktop/chatgpt' },
{ text: 'ChatWise', link: '/adapters/desktop/chatwise' },
{ text: 'Notion', link: '/adapters/desktop/notion' },
{ text: 'Discord', link: '/adapters/desktop/discord' },
{ text: 'Feishu', link: '/adapters/desktop/feishu' },
{ text: 'WeChat', link: '/adapters/desktop/wechat' },
{ text: 'NeteaseMusic', link: '/adapters/desktop/neteasemusic' },
],
},
],
'/developer/': [
{
text: 'Developer Guide',
items: [
{ text: 'Contributing', link: '/developer/contributing' },
{ text: 'Testing', link: '/developer/testing' },
{ text: 'Architecture', link: '/developer/architecture' },
{ text: 'YAML Adapter Guide', link: '/developer/yaml-adapter' },
{ text: 'TypeScript Adapter Guide', link: '/developer/ts-adapter' },
{ text: 'AI Workflow', link: '/developer/ai-workflow' },
],
},
],
'/advanced/': [
{
text: 'Advanced',
items: [
{ text: 'Chrome DevTools Protocol', link: '/advanced/cdp' },
{ text: 'Electron Apps', link: '/advanced/electron' },
{ text: 'Remote Chrome', link: '/advanced/remote-chrome' },
{ text: 'Download Support', link: '/advanced/download' },
],
},
],
},
},
},
zh: {
label: '中文',
lang: 'zh-CN',
link: '/zh/',
themeConfig: {
nav: [
{ text: '指南', link: '/zh/guide/getting-started' },
{ text: '适配器', link: '/zh/adapters/' },
{ text: '开发者', link: '/zh/developer/contributing' },
{ text: '进阶', link: '/zh/advanced/cdp' },
],
sidebar: {
'/zh/guide/': [
{
text: '指南',
items: [
{ text: '快速开始', link: '/zh/guide/getting-started' },
{ text: '安装', link: '/zh/guide/installation' },
{ text: 'Browser Bridge', link: '/zh/guide/browser-bridge' },
],
},
],
'/zh/adapters/': [
{
text: '适配器概览',
items: [
{ text: '所有适配器', link: '/zh/adapters/' },
],
},
],
'/zh/developer/': [
{
text: '开发者指南',
items: [
{ text: '贡献指南', link: '/zh/developer/contributing' },
],
},
],
'/zh/advanced/': [
{
text: '进阶',
items: [
{ text: 'Chrome DevTools Protocol', link: '/zh/advanced/cdp' },
],
},
],
},
},
},
},
themeConfig: {
search: {
provider: 'local',
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/jackwener/opencli' },
{ icon: 'npm', link: 'https://www.npmjs.com/package/@jackwener/opencli' },
],
editLink: {
pattern: 'https://github.com/jackwener/opencli/edit/main/docs/:path',
text: 'Edit this page on GitHub',
},
footer: {
message: 'Released under the Apache-2.0 License.',
copyright: 'Copyright © 2024-present jackwener',
},
},
})
+28
View File
@@ -0,0 +1,28 @@
# Apple Podcasts
**Mode**: 🌐 Public · **Domain**: `podcasts.apple.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli apple-podcasts search` | |
| `opencli apple-podcasts episodes` | |
| `opencli apple-podcasts top` | |
## Usage Examples
```bash
# Quick start
opencli apple-podcasts search --limit 5
# JSON output
opencli apple-podcasts search -f json
# Verbose mode
opencli apple-podcasts search -v
```
## Prerequisites
- No browser required — uses public API
+27
View File
@@ -0,0 +1,27 @@
# arXiv
**Mode**: 🌐 Public · **Domain**: `arxiv.org`
## Commands
| Command | Description |
|---------|-------------|
| `opencli arxiv search` | Search arXiv papers |
| `opencli arxiv paper` | Get arXiv paper details by ID |
## Usage Examples
```bash
# Search for papers
opencli arxiv search "transformer attention" --limit 10
# Get paper details by arXiv ID
opencli arxiv paper 2301.00001
# JSON output
opencli arxiv search "LLM" -f json
```
## Prerequisites
- No browser required — uses public arXiv API
+32
View File
@@ -0,0 +1,32 @@
# Barchart
**Mode**: 🌐 Public · **Domain**: `barchart.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli barchart quote` | Stock quote with price, volume, and key metrics |
| `opencli barchart options` | Options chain with greeks, IV, volume, and open interest |
| `opencli barchart greeks` | Options greeks overview (IV, delta, gamma, theta, vega) |
| `opencli barchart flow` | Unusual options activity / options flow |
## Usage Examples
```bash
# Get stock quote
opencli barchart quote --symbol AAPL
# View options chain
opencli barchart options --symbol TSLA
# Options greeks overview
opencli barchart greeks --symbol NVDA
# Unusual options flow
opencli barchart flow --limit 20 -f json
```
## Prerequisites
- No browser required — uses public API
+26
View File
@@ -0,0 +1,26 @@
# BBC News
**Mode**: 🌐 Public · **Domain**: `bbc.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli bbc news` | |
## Usage Examples
```bash
# Quick start
opencli bbc news --limit 5
# JSON output
opencli bbc news -f json
# Verbose mode
opencli bbc news -v
```
## Prerequisites
- No browser required — uses public API
+38
View File
@@ -0,0 +1,38 @@
# Bilibili
**Mode**: 🔐 Browser · **Domain**: `bilibili.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli bilibili hot` | |
| `opencli bilibili search` | |
| `opencli bilibili me` | |
| `opencli bilibili favorite` | |
| `opencli bilibili history` | |
| `opencli bilibili feed` | |
| `opencli bilibili subtitle` | |
| `opencli bilibili dynamic` | |
| `opencli bilibili ranking` | |
| `opencli bilibili following` | |
| `opencli bilibili user-videos` | |
| `opencli bilibili download` | |
## Usage Examples
```bash
# Quick start
opencli bilibili hot --limit 5
# JSON output
opencli bilibili hot -f json
# Verbose mode
opencli bilibili hot -v
```
## Prerequisites
- Chrome running and **logged into** bilibili.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+70
View File
@@ -0,0 +1,70 @@
# Bloomberg
**Mode**: 🌐 / 🔐 Mixed · **Domains**: `feeds.bloomberg.com`, `www.bloomberg.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli bloomberg main` | Bloomberg homepage top stories from RSS |
| `opencli bloomberg markets` | Bloomberg Markets top stories from RSS |
| `opencli bloomberg economics` | Bloomberg Economics top stories from RSS |
| `opencli bloomberg industries` | Bloomberg Industries top stories from RSS |
| `opencli bloomberg tech` | Bloomberg Tech top stories from RSS |
| `opencli bloomberg politics` | Bloomberg Politics top stories from RSS |
| `opencli bloomberg businessweek` | Bloomberg Businessweek top stories from RSS |
| `opencli bloomberg opinions` | Bloomberg Opinion top stories from RSS |
| `opencli bloomberg feeds` | List the RSS feed aliases used by the adapter |
| `opencli bloomberg news <link>` | Read a standard Bloomberg story/article page and return title, summary, media links, and article text |
## What works today
- RSS-backed listing commands work without a browser:
- `main`
- `markets`
- `economics`
- `industries`
- `tech`
- `politics`
- `businessweek`
- `opinions`
- `feeds`
- `bloomberg news` works on standard Bloomberg story/article pages that expose `#__NEXT_DATA__` and are accessible to your current Chrome session.
## Current limitations
- Audio pages and some other non-standard Bloomberg URLs may fail.
- Some Bloomberg pages can return bot-protection or access-gated responses instead of article data.
- This adapter is for data retrieval/extraction only. It does **not** bypass Bloomberg paywall, login, entitlement, or other access checks.
## Usage Examples
```bash
# List supported RSS feed aliases
opencli bloomberg feeds
# Fetch Bloomberg homepage headlines
opencli bloomberg main --limit 5
# Fetch a section feed as JSON
opencli bloomberg tech --limit 3 -f json
# Read a standard article page
opencli bloomberg news https://www.bloomberg.com/news/articles/2026-03-19/example -f json
# Relative article paths also work
opencli bloomberg news /news/articles/2026-03-19/example
```
## Prerequisites
- RSS commands do not require Chrome.
- `bloomberg news` requires:
- Chrome running
- a Chrome session that can already access the target Bloomberg article page
- the [Browser Bridge extension](/guide/browser-bridge)
## Notes
- RSS commands support `--limit` with a maximum of 20 items.
- If `bloomberg news` fails on a page from RSS, try a different standard story/article link first; not every Bloomberg URL in feeds is a normal article page.
+28
View File
@@ -0,0 +1,28 @@
# BOSS Zhipin
**Mode**: 🔐 Browser · **Domain**: `zhipin.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli boss search` | |
| `opencli boss detail` | |
## Usage Examples
```bash
# Quick start
opencli boss search --limit 5
# JSON output
opencli boss search -f json
# Verbose mode
opencli boss search -v
```
## Prerequisites
- Chrome running and **logged into** zhipin.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+39
View File
@@ -0,0 +1,39 @@
# 超星学习通 (Chaoxing)
**Mode**: 🔐 Browser · **Domain**: `mooc2-ans.chaoxing.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli chaoxing assignments` | 学习通作业列表 |
| `opencli chaoxing exams` | 学习通考试列表 |
## Usage Examples
```bash
# List all assignments
opencli chaoxing assignments --limit 20
# Filter exams by course name
opencli chaoxing exams --course "高等数学"
# Filter exams by status
opencli chaoxing exams --status ongoing
# JSON output
opencli chaoxing assignments -f json
```
### Options
| Option | Description |
|--------|-------------|
| `--course` | Filter by course name (fuzzy match) |
| `--status` | Filter by status: `all`, `upcoming`, `ongoing`, `finished` |
| `--limit` | Max number of results (default: 20) |
## Prerequisites
- Chrome running and **logged into** mooc2-ans.chaoxing.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+28
View File
@@ -0,0 +1,28 @@
# Coupang
**Mode**: 🔐 Browser · **Domain**: `coupang.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli coupang search` | |
| `opencli coupang add-to-cart` | |
## Usage Examples
```bash
# Quick start
opencli coupang search --limit 5
# JSON output
opencli coupang search -f json
# Verbose mode
opencli coupang search -v
```
## Prerequisites
- Chrome running and **logged into** coupang.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+27
View File
@@ -0,0 +1,27 @@
# Ctrip (携程)
**Mode**: 🔐 Browser · **Domain**: `ctrip.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli ctrip search` | |
## Usage Examples
```bash
# Quick start
opencli ctrip search --limit 5
# JSON output
opencli ctrip search -f json
# Verbose mode
opencli ctrip search -v
```
## Prerequisites
- Chrome running and **logged into** ctrip.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+26
View File
@@ -0,0 +1,26 @@
# GitHub
**Mode**: 🌐 Public · **Domain**: `github.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli github search` | |
## Usage Examples
```bash
# Quick start
opencli github search --limit 5
# JSON output
opencli github search -f json
# Verbose mode
opencli github search -v
```
## Prerequisites
- No browser required — uses public API
+35
View File
@@ -0,0 +1,35 @@
# Grok
**Mode**: 🔐 Browser · **Domain**: `grok.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli grok ask` | Send a message to Grok and get response |
## Usage Examples
```bash
# Ask Grok a question
opencli grok ask --prompt "Explain quantum computing in simple terms"
# Start a new chat session
opencli grok ask --prompt "Hello" --new
# Set custom timeout (default: 120s)
opencli grok ask --prompt "Write a long essay" --timeout 180
```
### Options
| Option | Description |
|--------|-------------|
| `--prompt` | The message to send (required) |
| `--timeout` | Wait timeout in seconds (default: 120) |
| `--new` | Start a new chat session (default: false) |
## Prerequisites
- Chrome running and **logged into** grok.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+26
View File
@@ -0,0 +1,26 @@
# HackerNews
**Mode**: 🌐 Public · **Domain**: `news.ycombinator.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli hackernews top` | |
## Usage Examples
```bash
# Quick start
opencli hackernews top --limit 5
# JSON output
opencli hackernews top -f json
# Verbose mode
opencli hackernews top -v
```
## Prerequisites
- No browser required — uses public API
+42
View File
@@ -0,0 +1,42 @@
# Hugging Face
**Mode**: 🌐 Public · **Domain**: `huggingface.co`
## Commands
| Command | Description |
|---------|-------------|
| `opencli hf top` | Top upvoted Hugging Face papers |
## Usage Examples
```bash
# Today's top papers
opencli hf top --limit 10
# All papers (no limit)
opencli hf top --all
# Specific date
opencli hf top --date 2025-03-01
# Weekly/monthly top papers
opencli hf top --period weekly
opencli hf top --period monthly
# JSON output
opencli hf top -f json
```
### Options
| Option | Description |
|--------|-------------|
| `--limit` | Number of papers (default: 20) |
| `--all` | Return all papers, ignoring limit |
| `--date` | Date in `YYYY-MM-DD` format (defaults to most recent) |
| `--period` | Time period: `daily`, `weekly`, or `monthly` (default: daily) |
## Prerequisites
- No browser required — uses public Hugging Face API
+45
View File
@@ -0,0 +1,45 @@
# 即刻 (Jike)
**Mode**: 🔐 Browser · **Domain**: `web.okjike.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli jike feed` | 即刻首页动态流 |
| `opencli jike search` | 搜索即刻帖子 |
| `opencli jike post` | 帖子详情及评论 |
| `opencli jike topic` | 话题详情 |
| `opencli jike user` | 用户资料 |
| `opencli jike create` | 发布即刻动态 |
| `opencli jike comment` | 评论即刻帖子 |
| `opencli jike like` | 点赞即刻帖子 |
| `opencli jike repost` | 转发即刻帖子 |
| `opencli jike notifications` | 即刻通知 |
## Usage Examples
```bash
# View feed
opencli jike feed --limit 10
# Search posts
opencli jike search "AI" --limit 20
# View post details and comments
opencli jike post <post-id>
# Create a new post
opencli jike create --content "Hello Jike!"
# Like a post
opencli jike like <post-id>
# JSON output
opencli jike feed -f json
```
## Prerequisites
- Chrome running and **logged into** web.okjike.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+39
View File
@@ -0,0 +1,39 @@
# 即梦AI (Jimeng)
**Mode**: 🔐 Browser · **Domain**: `jimeng.jianying.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli jimeng generate` | 即梦AI 文生图 — 输入 prompt 生成图片 |
| `opencli jimeng history` | 查看生成历史 |
## Usage Examples
```bash
# Generate an image
opencli jimeng generate --prompt "一只在星空下的猫"
# Use a specific model
opencli jimeng generate --prompt "cyberpunk city" --model high_aes_general_v50
# Set custom wait timeout
opencli jimeng generate --prompt "sunset landscape" --wait 60
# View generation history
opencli jimeng history --limit 10
```
### Options (generate)
| Option | Description |
|--------|-------------|
| `--prompt` | Image description prompt (required) |
| `--model` | Model: `high_aes_general_v50` (5.0 Lite), `high_aes_general_v42` (4.6), `high_aes_general_v40` (4.0) |
| `--wait` | Wait seconds for generation (default: 40) |
## Prerequisites
- Chrome running and **logged into** jimeng.jianying.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+27
View File
@@ -0,0 +1,27 @@
# LinkedIn
**Mode**: 🔐 Browser · **Domain**: `linkedin.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli linkedin search` | |
## Usage Examples
```bash
# Quick start
opencli linkedin search --limit 5
# JSON output
opencli linkedin search -f json
# Verbose mode
opencli linkedin search -v
```
## Prerequisites
- Chrome running and **logged into** linkedin.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+45
View File
@@ -0,0 +1,45 @@
# LINUX DO
**Mode**: 🔐 Browser · **Domain**: `linux.do`
## Commands
| Command | Description |
|---------|-------------|
| `opencli linux-do hot` | 热门话题 |
| `opencli linux-do latest` | 最新话题 |
| `opencli linux-do categories` | 板块列表 |
| `opencli linux-do category` | 板块话题 |
| `opencli linux-do search` | 搜索话题 |
| `opencli linux-do topic` | 话题详情 |
## Usage Examples
```bash
# Hot topics this week
opencli linux-do hot --limit 20
# Hot topics by period
opencli linux-do hot --period daily
opencli linux-do hot --period monthly
# Latest topics
opencli linux-do latest --limit 10
# List all categories
opencli linux-do categories
# Search topics
opencli linux-do search "NixOS"
# View topic details
opencli linux-do topic 12345
# JSON output
opencli linux-do hot -f json
```
## Prerequisites
- Chrome running and **logged into** linux.do
- [Browser Bridge extension](/guide/browser-bridge) installed
+41
View File
@@ -0,0 +1,41 @@
# Reddit
**Mode**: 🔐 Browser · **Domain**: `reddit.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli reddit hot` | |
| `opencli reddit frontpage` | |
| `opencli reddit popular` | |
| `opencli reddit search` | |
| `opencli reddit subreddit` | |
| `opencli reddit read` | |
| `opencli reddit user` | |
| `opencli reddit user-posts` | |
| `opencli reddit user-comments` | |
| `opencli reddit upvote` | |
| `opencli reddit save` | |
| `opencli reddit comment` | |
| `opencli reddit subscribe` | |
| `opencli reddit saved` | |
| `opencli reddit upvoted` | |
## Usage Examples
```bash
# Quick start
opencli reddit hot --limit 5
# JSON output
opencli reddit hot -f json
# Verbose mode
opencli reddit hot -v
```
## Prerequisites
- Chrome running and **logged into** reddit.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+27
View File
@@ -0,0 +1,27 @@
# Reuters
**Mode**: 🔐 Browser · **Domain**: `reuters.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli reuters search` | |
## Usage Examples
```bash
# Quick start
opencli reuters search --limit 5
# JSON output
opencli reuters search -f json
# Verbose mode
opencli reuters search -v
```
## Prerequisites
- Chrome running and **logged into** reuters.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+35
View File
@@ -0,0 +1,35 @@
# 新浪财经 (Sina Finance)
**Mode**: 🌐 Public · **Domain**: `finance.sina.com.cn`
## Commands
| Command | Description |
|---------|-------------|
| `opencli sinafinance news` | 新浪财经 7×24 小时实时快讯 |
## Usage Examples
```bash
# Latest financial news
opencli sinafinance news --limit 20
# Filter by type
opencli sinafinance news --type 1 # A股
opencli sinafinance news --type 2 # 宏观
opencli sinafinance news --type 6 # 国际
# JSON output
opencli sinafinance news -f json
```
### Options
| Option | Description |
|--------|-------------|
| `--limit` | Max results, up to 50 (default: 20) |
| `--type` | News type: `0`=全部, `1`=A股, `2`=宏观, `3`=公司, `4`=数据, `5`=市场, `6`=国际, `7`=观点, `8`=央行, `9`=其它 |
## Prerequisites
- No browser required — uses public API
+27
View File
@@ -0,0 +1,27 @@
# SMZDM (什么值得买)
**Mode**: 🔐 Browser · **Domain**: `smzdm.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli smzdm search` | |
## Usage Examples
```bash
# Quick start
opencli smzdm search --limit 5
# JSON output
opencli smzdm search -f json
# Verbose mode
opencli smzdm search -v
```
## Prerequisites
- Chrome running and **logged into** smzdm.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+35
View File
@@ -0,0 +1,35 @@
# Stack Overflow
**Mode**: 🌐 Public · **Domain**: `stackoverflow.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli stackoverflow hot` | Hot questions |
| `opencli stackoverflow search` | Search questions |
| `opencli stackoverflow bounties` | Questions with active bounties |
| `opencli stackoverflow unanswered` | Unanswered questions |
## Usage Examples
```bash
# Hot questions
opencli stackoverflow hot --limit 10
# Search questions
opencli stackoverflow search "async await" --limit 20
# Active bounties
opencli stackoverflow bounties --limit 10
# Unanswered questions
opencli stackoverflow unanswered --limit 10
# JSON output
opencli stackoverflow hot -f json
```
## Prerequisites
- No browser required — uses public Stack Exchange API
+26
View File
@@ -0,0 +1,26 @@
# Steam
**Mode**: 🌐 Public · **Domain**: `store.steampowered.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli steam top-sellers` | Top selling games on Steam |
## Usage Examples
```bash
# Quick start
opencli steam top-sellers
# Limit results
opencli steam top-sellers --limit 5
# JSON output
opencli steam top-sellers -f json
```
## Prerequisites
- No login required (public API)
+50
View File
@@ -0,0 +1,50 @@
# Twitter / X
**Mode**: 🔐 Browser · **Domain**: `twitter.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli twitter trending` | |
| `opencli twitter bookmarks` | |
| `opencli twitter profile` | |
| `opencli twitter search` | |
| `opencli twitter timeline` | |
| `opencli twitter thread` | |
| `opencli twitter following` | |
| `opencli twitter followers` | |
| `opencli twitter notifications` | |
| `opencli twitter post` | |
| `opencli twitter reply` | |
| `opencli twitter delete` | |
| `opencli twitter like` | |
| `opencli twitter article` | |
| `opencli twitter follow` | |
| `opencli twitter unfollow` | |
| `opencli twitter bookmark` | |
| `opencli twitter unbookmark` | |
| `opencli twitter block` | |
| `opencli twitter unblock` | |
| `opencli twitter hide-reply` | |
| `opencli twitter download` | |
| `opencli twitter accept` | |
| `opencli twitter reply-dm` | |
## Usage Examples
```bash
# Quick start
opencli twitter trending --limit 5
# JSON output
opencli twitter trending -f json
# Verbose mode
opencli twitter trending -v
```
## Prerequisites
- Chrome running and **logged into** twitter.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+32
View File
@@ -0,0 +1,32 @@
# V2EX
**Mode**: 🌐 / 🔐 · **Domain**: `v2ex.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli v2ex hot` | |
| `opencli v2ex latest` | |
| `opencli v2ex topic` | |
| `opencli v2ex daily` | |
| `opencli v2ex me` | |
| `opencli v2ex notifications` | |
## Usage Examples
```bash
# Quick start
opencli v2ex hot --limit 5
# JSON output
opencli v2ex hot -f json
# Verbose mode
opencli v2ex hot -v
```
## Prerequisites
- Chrome running and **logged into** v2ex.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+27
View File
@@ -0,0 +1,27 @@
# Weibo (微博)
**Mode**: 🔐 Browser · **Domain**: `weibo.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli weibo hot` | |
## Usage Examples
```bash
# Quick start
opencli weibo hot --limit 5
# JSON output
opencli weibo hot -f json
# Verbose mode
opencli weibo hot -v
```
## Prerequisites
- Chrome running and **logged into** weibo.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+48
View File
@@ -0,0 +1,48 @@
# 微信读书 (WeRead)
**Mode**: 🔐 Browser · **Domain**: `weread.qq.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli weread shelf` | List books on your bookshelf |
| `opencli weread search` | Search books on WeRead |
| `opencli weread book` | View book details |
| `opencli weread ranking` | Book rankings by category |
| `opencli weread notebooks` | List books that have highlights or notes |
| `opencli weread highlights` | List your highlights (underlines) in a book |
| `opencli weread notes` | List your notes (thoughts) on a book |
## Usage Examples
```bash
# View your bookshelf
opencli weread shelf --limit 20
# Search books
opencli weread search "三体"
# View book details
opencli weread book <book-id>
# Book rankings
opencli weread ranking --limit 10
# List books with notes/highlights
opencli weread notebooks
# View highlights for a book
opencli weread highlights <book-id>
# View your notes
opencli weread notes <book-id>
# JSON output
opencli weread shelf -f json
```
## Prerequisites
- Chrome running and **logged into** weread.qq.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+30
View File
@@ -0,0 +1,30 @@
# Wikipedia
**Mode**: 🌐 Public · **Domain**: `wikipedia.org`
## Commands
| Command | Description |
|---------|-------------|
| `opencli wikipedia search` | Search Wikipedia articles |
| `opencli wikipedia summary` | Get Wikipedia article summary |
## Usage Examples
```bash
# Search articles
opencli wikipedia search "quantum computing" --limit 10
# Get article summary
opencli wikipedia summary --title "Artificial intelligence"
# Search in other languages
opencli wikipedia search "人工智能" --lang zh
# JSON output
opencli wikipedia search "Rust" -f json
```
## Prerequisites
- No browser required — uses public Wikipedia API
+36
View File
@@ -0,0 +1,36 @@
# Xiaohongshu (小红书)
**Mode**: 🔐 Browser · **Domain**: `xiaohongshu.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli xiaohongshu search` | |
| `opencli xiaohongshu notifications` | |
| `opencli xiaohongshu feed` | |
| `opencli xiaohongshu user` | |
| `opencli xiaohongshu download` | |
| `opencli xiaohongshu creator-notes` | |
| `opencli xiaohongshu creator-note-detail` | |
| `opencli xiaohongshu creator-notes-summary` | |
| `opencli xiaohongshu creator-profile` | |
| `opencli xiaohongshu creator-stats` | |
## Usage Examples
```bash
# Quick start
opencli xiaohongshu search --limit 5
# JSON output
opencli xiaohongshu search -f json
# Verbose mode
opencli xiaohongshu search -v
```
## Prerequisites
- Chrome running and **logged into** xiaohongshu.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+28
View File
@@ -0,0 +1,28 @@
# Xiaoyuzhou (小宇宙)
**Mode**: 🌐 Public · **Domain**: `xiaoyuzhou.fm`
## Commands
| Command | Description |
|---------|-------------|
| `opencli xiaoyuzhou podcast` | |
| `opencli xiaoyuzhou podcast-episodes` | |
| `opencli xiaoyuzhou episode` | |
## Usage Examples
```bash
# Quick start
opencli xiaoyuzhou podcast --limit 5
# JSON output
opencli xiaoyuzhou podcast -f json
# Verbose mode
opencli xiaoyuzhou podcast -v
```
## Prerequisites
- No browser required — uses public API
+32
View File
@@ -0,0 +1,32 @@
# Xueqiu (雪球)
**Mode**: 🔐 Browser · **Domain**: `xueqiu.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli xueqiu feed` | |
| `opencli xueqiu hot-stock` | |
| `opencli xueqiu hot` | |
| `opencli xueqiu search` | |
| `opencli xueqiu stock` | |
| `opencli xueqiu watchlist` | |
## Usage Examples
```bash
# Quick start
opencli xueqiu feed --limit 5
# JSON output
opencli xueqiu feed -f json
# Verbose mode
opencli xueqiu feed -v
```
## Prerequisites
- Chrome running and **logged into** xueqiu.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+26
View File
@@ -0,0 +1,26 @@
# Yahoo Finance
**Mode**: 🌐 Public · **Domain**: `finance.yahoo.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli yahoo-finance quote` | |
## Usage Examples
```bash
# Quick start
opencli yahoo-finance quote --limit 5
# JSON output
opencli yahoo-finance quote -f json
# Verbose mode
opencli yahoo-finance quote -v
```
## Prerequisites
- No browser required — uses public API
+29
View File
@@ -0,0 +1,29 @@
# YouTube
**Mode**: 🔐 Browser · **Domain**: `youtube.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli youtube search` | |
| `opencli youtube video` | |
| `opencli youtube transcript` | |
## Usage Examples
```bash
# Quick start
opencli youtube search --limit 5
# JSON output
opencli youtube search -f json
# Verbose mode
opencli youtube search -v
```
## Prerequisites
- Chrome running and **logged into** youtube.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+30
View File
@@ -0,0 +1,30 @@
# Zhihu
**Mode**: 🔐 Browser · **Domain**: `zhihu.com`
## Commands
| Command | Description |
|---------|-------------|
| `opencli zhihu hot` | |
| `opencli zhihu search` | |
| `opencli zhihu question` | |
| `opencli zhihu download` | |
## Usage Examples
```bash
# Quick start
opencli zhihu hot --limit 5
# JSON output
opencli zhihu hot -f json
# Verbose mode
opencli zhihu hot -v
```
## Prerequisites
- Chrome running and **logged into** zhihu.com
- [Browser Bridge extension](/guide/browser-bridge) installed
+46
View File
@@ -0,0 +1,46 @@
# Antigravity
🔥 **CLI All Electron Apps! The Most Powerful Update Has Arrived!** 🔥
Turn your local Antigravity desktop application into a programmable AI node via Chrome DevTools Protocol (CDP). This allows you to compose complex LLM workflows entirely through the terminal by manipulating the actual UI natively, bypassing any API restrictions.
## Prerequisites
Start the Antigravity desktop app with the Chrome DevTools `remote-debugging-port` flag:
```bash
# Start Antigravity in the background
/Applications/Antigravity.app/Contents/MacOS/Electron \
--remote-debugging-port=9224
```
> Depending on your installation, the executable might be named differently, e.g., `Antigravity` instead of `Electron`.
Then set the target port:
```bash
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9224"
```
## Commands
### `opencli antigravity status`
Check the Chromium CDP connection. Returns the current window title and active internal URL.
### `opencli antigravity send <message>`
Send a text prompt to the AI. Automatically locates the Lexical editor input box, types the prompt securely, and hits Enter.
### `opencli antigravity read`
Scrape the entire current conversation history block as pure text.
### `opencli antigravity new`
Click the "New Conversation" button to instantly clear the UI state and start fresh.
### `opencli antigravity extract-code`
Extract any multi-line code blocks from the current conversation view. Ideal for automated script extraction (e.g. `opencli antigravity extract-code > script.sh`).
### `opencli antigravity model <name>`
Quickly target and switch the active LLM engine. Example: `opencli antigravity model claude` or `opencli antigravity model gemini`.
### `opencli antigravity watch`
A long-running, streaming process that continuously polls the Antigravity UI for chat updates and outputs them in real-time to standard output.
+43
View File
@@ -0,0 +1,43 @@
# ChatGPT
Control the **ChatGPT macOS Desktop App** directly from the terminal. OpenCLI supports two automation approaches for ChatGPT.
## Approach 1: AppleScript (Default, No Setup)
The current built-in commands use native AppleScript automation — no extra launch flags needed.
### Prerequisites
1. Install the official [ChatGPT Desktop App](https://openai.com/chatgpt/mac/) from OpenAI.
2. Grant **Accessibility permissions** to your terminal app in **System Settings → Privacy & Security → Accessibility**.
### Commands
- `opencli chatgpt status`: Check if the ChatGPT app is currently running.
- `opencli chatgpt new`: Activate ChatGPT and press `Cmd+N` to start a new conversation.
- `opencli chatgpt send "message"`: Copy your message to clipboard, activate ChatGPT, paste, and submit.
- `opencli chatgpt read`: Read the last visible message from the focused ChatGPT window via the Accessibility tree.
## Approach 2: CDP (Advanced, Electron Debug Mode)
ChatGPT Desktop is also an Electron app and can be launched with a remote debugging port:
```bash
/Applications/ChatGPT.app/Contents/MacOS/ChatGPT \
--remote-debugging-port=9224
```
```bash
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9224"
```
> The CDP approach enables future advanced commands like DOM inspection, model switching, and code extraction.
## How It Works
- **AppleScript mode**: Uses `osascript` to control ChatGPT, `pbcopy`/`pbpaste` to paste prompts, and the macOS Accessibility tree to read visible chat messages.
- **CDP mode**: Connects via Chrome DevTools Protocol to the Electron renderer process.
## Limitations
- macOS only (AppleScript dependency)
- AppleScript mode requires Accessibility permissions
- `read` returns the last visible message in the focused ChatGPT window — scroll first if the message you want is not visible
+38
View File
@@ -0,0 +1,38 @@
# ChatWise
Control the **ChatWise Desktop App** from the terminal via Chrome DevTools Protocol (CDP). ChatWise is an Electron-based multi-LLM client supporting GPT-4, Claude, Gemini, and more.
## Prerequisites
1. Install [ChatWise](https://chatwise.app/).
2. Launch with remote debugging port:
```bash
/Applications/ChatWise.app/Contents/MacOS/ChatWise \
--remote-debugging-port=9228
```
## Setup
```bash
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9228"
```
## Commands
### Diagnostics
- `opencli chatwise status`: Check CDP connection status.
- `opencli chatwise screenshot`: Export DOM + accessibility snapshot.
### Chat
- `opencli chatwise new`: Start a new conversation (`Cmd+N`).
- `opencli chatwise send "message"`: Send a message to the active chat.
- `opencli chatwise read`: Read the current conversation.
- `opencli chatwise ask "prompt"`: Send + wait for response + return it (one-shot).
### AI Features
- `opencli chatwise model`: Get the current AI model.
- `opencli chatwise model gpt-4`: Switch to a different model.
### Organization
- `opencli chatwise history`: List conversations from the sidebar.
- `opencli chatwise export`: Export conversation as Markdown.
+32
View File
@@ -0,0 +1,32 @@
# Codex
Control the **OpenAI Codex Desktop App** headless or headfully via Chrome DevTools Protocol (CDP). Because Codex is built on Electron, OpenCLI can directly drive its internal UI, automate slash commands, and manipulate its AI agent threads.
## Prerequisites
1. You must have the official OpenAI Codex app installed.
2. Launch it via the terminal and expose the remote debugging port:
```bash
# macOS
/Applications/Codex.app/Contents/MacOS/Codex --remote-debugging-port=9222
```
## Setup
```bash
export OPENCLI_CODEX_CDP_ENDPOINT="http://127.0.0.1:9222"
```
## Commands
### Diagnostics
- `opencli codex status`: Checks connection and reads the current active window URL/title.
- `opencli codex dump`: Dumps the full UI DOM and Accessibility tree into `/tmp`.
### Agent Manipulation
- `opencli codex new`: Simulates `Cmd+N` to start a completely fresh and isolated Git Worktree thread context.
- `opencli codex send "message"`: Robustly finds the active Thread Composer and injects your text.
- *Pro-tip*: You can trigger internal shortcuts, e.g., `opencli codex send "/review"`.
- `opencli codex read`: Extracts the entire current thread history and AI reasoning logs.
- `opencli codex extract-diff`: Automatically scrapes any visual Patch chunks and Code Diffs.
- `opencli codex model`: Get the currently active AI model.
+33
View File
@@ -0,0 +1,33 @@
# Cursor
Control the **Cursor IDE** from the terminal via Chrome DevTools Protocol (CDP). Since Cursor is built on Electron (VS Code fork), OpenCLI can drive its internal UI, automate Composer interactions, and manipulate chat sessions.
## Prerequisites
1. Install [Cursor](https://cursor.sh/).
2. Launch it with the remote debugging port:
```bash
/Applications/Cursor.app/Contents/MacOS/Cursor --remote-debugging-port=9226
```
## Setup
```bash
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9226"
```
## Commands
### Diagnostics
- `opencli cursor status`: Check CDP connection status.
- `opencli cursor dump`: Dump the full DOM and Accessibility snapshot to `/tmp/cursor-dom.html` and `/tmp/cursor-snapshot.json`.
### Chat Manipulation
- `opencli cursor new`: Press `Cmd+N` to start a new file/tab.
- `opencli cursor send "message"`: Inject text into the active Composer/Chat input and submit.
- `opencli cursor read`: Extract the full conversation history from the active chat panel.
### AI Features
- `opencli cursor composer "prompt"`: Open the Composer panel (`Cmd+I`) and send a prompt for inline AI editing.
- `opencli cursor model`: Get the currently active AI model (e.g., `claude-4.5-sonnet`).
- `opencli cursor extract-code`: Extract all code blocks from the current conversation.
+28
View File
@@ -0,0 +1,28 @@
# Discord
Control the **Discord Desktop App** from the terminal via Chrome DevTools Protocol (CDP).
## Prerequisites
Launch with remote debugging port:
```bash
/Applications/Discord.app/Contents/MacOS/Discord --remote-debugging-port=9232
```
## Setup
```bash
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9232"
```
## Commands
| Command | Description |
|---------|-------------|
| `opencli discord status` | Check CDP connection |
| `opencli discord send "message"` | Send a message in the active channel |
| `opencli discord read` | Read recent messages |
| `opencli discord channels` | List channels in the current server |
| `opencli discord servers` | List all joined servers |
| `opencli discord search "query"` | Search messages (Cmd+F) |
| `opencli discord members` | List online members |
+20
View File
@@ -0,0 +1,20 @@
# Feishu (飞书/Lark)
Control **Feishu/Lark Desktop** from the terminal via AppleScript.
> **Note:** Feishu uses a custom `Lark Framework` (Chromium-based but NOT Electron). CDP is not available, so this adapter uses AppleScript + clipboard.
## Prerequisites
1. Feishu/Lark must be running and logged in
2. Terminal must have **Accessibility permission**
## Commands
| Command | Description |
|---------|-------------|
| `opencli feishu status` | Check if Feishu/Lark is running |
| `opencli feishu send "msg"` | Send message in active chat (paste + Enter) |
| `opencli feishu read` | Read current chat (Cmd+A → Cmd+C) |
| `opencli feishu search "query"` | Global search (Cmd+K) |
| `opencli feishu new` | New message/document (Cmd+N) |
+31
View File
@@ -0,0 +1,31 @@
# NeteaseMusic (网易云音乐)
Control **NeteaseMusic** (网易云音乐) from the terminal via Chrome DevTools Protocol (CDP). The app uses Chromium Embedded Framework (CEF).
## Prerequisites
Launch with remote debugging port:
```bash
/Applications/NeteaseMusic.app/Contents/MacOS/NeteaseMusic --remote-debugging-port=9234
```
## Setup
```bash
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9234"
```
## Commands
| Command | Description |
|---------|-------------|
| `opencli neteasemusic status` | Check CDP connection |
| `opencli neteasemusic playing` | Current song info (title, artist, album) |
| `opencli neteasemusic play` | Play / Pause toggle |
| `opencli neteasemusic next` | Skip to next song |
| `opencli neteasemusic prev` | Go to previous song |
| `opencli neteasemusic search "query"` | Search songs, artists |
| `opencli neteasemusic playlist` | Show current playback queue |
| `opencli neteasemusic like` | Like / unlike current song |
| `opencli neteasemusic lyrics` | Get lyrics of current song |
| `opencli neteasemusic volume [0-100]` | Get or set volume |
+29
View File
@@ -0,0 +1,29 @@
# Notion
Control the **Notion Desktop App** from the terminal via Chrome DevTools Protocol (CDP).
## Prerequisites
Launch with remote debugging port:
```bash
/Applications/Notion.app/Contents/MacOS/Notion --remote-debugging-port=9230
```
## Setup
```bash
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9230"
```
## Commands
| Command | Description |
|---------|-------------|
| `opencli notion status` | Check CDP connection |
| `opencli notion search "query"` | Quick Find search (Cmd+P) |
| `opencli notion read` | Read the current page content |
| `opencli notion new "title"` | Create a new page (Cmd+N) |
| `opencli notion write "text"` | Append text to the current page |
| `opencli notion sidebar` | List pages from the sidebar |
| `opencli notion favorites` | List pages from the Favorites section |
| `opencli notion export` | Export page as Markdown |
+28
View File
@@ -0,0 +1,28 @@
# WeChat (微信)
Control **WeChat Mac Desktop** from the terminal via AppleScript + Accessibility API.
> **Note:** WeChat is a native macOS app (not Electron), so CDP is not available. This adapter uses AppleScript keyboard simulation and clipboard operations.
## Prerequisites
1. WeChat must be running and logged in
2. Terminal must have **Accessibility permission** (System Settings → Privacy & Security → Accessibility)
## Commands
| Command | Description |
|---------|-------------|
| `opencli wechat status` | Check if WeChat is running |
| `opencli wechat send "msg"` | Send message in the active chat (clipboard paste + Enter) |
| `opencli wechat read` | Read current chat content (Cmd+A → Cmd+C) |
| `opencli wechat search "keyword"` | Open search and type a query (Cmd+F) |
| `opencli wechat chats` | Switch to Chats tab (Cmd+1) |
| `opencli wechat contacts` | Switch to Contacts tab (Cmd+2) |
## Limitations
- **No CDP support** — WeChat is native Cocoa, not Electron
- `send` requires the correct conversation to be already open
- `read` captures whatever is visible via select-all + copy
- `search` types the query but cannot programmatically click results
+62
View File
@@ -0,0 +1,62 @@
# All Adapters
Run `opencli list` for the live registry.
## Browser Adapters
| Site | Commands | Mode |
|------|----------|------|
| **[twitter](/adapters/browser/twitter)** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` `download` `accept` `reply-dm` | 🔐 Browser |
| **[reddit](/adapters/browser/reddit)** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 🔐 Browser |
| **[bilibili](/adapters/browser/bilibili)** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` | 🔐 Browser |
| **[zhihu](/adapters/browser/zhihu)** | `hot` `search` `question` `download` | 🔐 Browser |
| **[xiaohongshu](/adapters/browser/xiaohongshu)** | `search` `notifications` `feed` `me` `user` `download` | 🔐 Browser |
| **[xueqiu](/adapters/browser/xueqiu)** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 🔐 Browser |
| **[youtube](/adapters/browser/youtube)** | `search` `video` `transcript` | 🔐 Browser |
| **[v2ex](/adapters/browser/v2ex)** | `hot` `latest` `topic` `daily` `me` `notifications` | 🌐 / 🔐 |
| **[bloomberg](/adapters/browser/bloomberg)** | `main` `markets` `economics` `industries` `tech` `politics` `businessweek` `opinions` `feeds` `news` | 🌐 / 🔐 |
| **[weibo](/adapters/browser/weibo)** | `hot` | 🔐 Browser |
| **[linkedin](/adapters/browser/linkedin)** | `search` | 🔐 Browser |
| **[coupang](/adapters/browser/coupang)** | `search` `add-to-cart` | 🔐 Browser |
| **[boss](/adapters/browser/boss)** | `search` `detail` | 🔐 Browser |
| **[ctrip](/adapters/browser/ctrip)** | `search` | 🔐 Browser |
| **[reuters](/adapters/browser/reuters)** | `search` | 🔐 Browser |
| **[smzdm](/adapters/browser/smzdm)** | `search` | 🔐 Browser |
| **[jike](/adapters/browser/jike)** | `feed` `search` `post` `topic` `user` `create` `comment` `like` `repost` `notifications` | 🔐 Browser |
| **[jimeng](/adapters/browser/jimeng)** | `generate` `history` | 🔐 Browser |
| **[linux-do](/adapters/browser/linux-do)** | `hot` `latest` `categories` `category` `search` `topic` | 🔐 Browser |
| **[chaoxing](/adapters/browser/chaoxing)** | `assignments` `exams` | 🔐 Browser |
| **[grok](/adapters/browser/grok)** | `ask` | 🔐 Browser |
| **[weread](/adapters/browser/weread)** | `shelf` `search` `book` `ranking` `notebooks` `highlights` `notes` | 🔐 Browser |
## Public API Adapters
| Site | Commands | Mode |
|------|----------|------|
| **[hackernews](/adapters/browser/hackernews)** | `top` | 🌐 Public |
| **[github](/adapters/browser/github)** | `search` | 🌐 Public |
| **[bbc](/adapters/browser/bbc)** | `news` | 🌐 Public |
| **[apple-podcasts](/adapters/browser/apple-podcasts)** | `search` `episodes` `top` | 🌐 Public |
| **[xiaoyuzhou](/adapters/browser/xiaoyuzhou)** | `podcast` `podcast-episodes` `episode` | 🌐 Public |
| **[yahoo-finance](/adapters/browser/yahoo-finance)** | `quote` | 🌐 Public |
| **[arxiv](/adapters/browser/arxiv)** | `search` `paper` | 🌐 Public |
| **[barchart](/adapters/browser/barchart)** | `quote` `options` `greeks` `flow` | 🌐 Public |
| **[hf](/adapters/browser/hf)** | `top` | 🌐 Public |
| **[sinafinance](/adapters/browser/sinafinance)** | `news` | 🌐 Public |
| **[stackoverflow](/adapters/browser/stackoverflow)** | `hot` `search` `bounties` `unanswered` | 🌐 Public |
| **[wikipedia](/adapters/browser/wikipedia)** | `search` `summary` | 🌐 Public |
## Desktop Adapters
| App | Description | Commands |
|-----|-------------|----------|
| **[Cursor](/adapters/desktop/cursor)** | Control Cursor IDE | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` |
| **[Codex](/adapters/desktop/codex)** | Drive OpenAI Codex CLI agent | `status` `send` `read` `new` `extract-diff` `model` `ask` `screenshot` `history` `export` |
| **[Antigravity](/adapters/desktop/antigravity)** | Control Antigravity Ultra | `status` `send` `read` `new` `dump` `extract-code` `model` `watch` |
| **[ChatGPT](/adapters/desktop/chatgpt)** | Automate ChatGPT macOS app | `status` `new` `send` `read` `ask` |
| **[ChatWise](/adapters/desktop/chatwise)** | Multi-LLM client | `status` `new` `send` `read` `ask` `model` `history` `export` `screenshot` |
| **[Notion](/adapters/desktop/notion)** | Search, read, write pages | `status` `search` `read` `new` `write` `sidebar` `favorites` `export` |
| **[Discord](/adapters/desktop/discord)** | Desktop messages & channels | `status` `send` `read` `channels` `servers` `search` `members` |
| **[Feishu](/adapters/desktop/feishu)** | 飞书/Lark via AppleScript | `status` `send` `read` `search` `new` |
| **[WeChat](/adapters/desktop/wechat)** | 微信 via AppleScript | `status` `send` `read` `search` `chats` `contacts` |
| **[NeteaseMusic](/adapters/desktop/neteasemusic)** | 网易云音乐 via CDP | `status` `playing` `play` `next` `prev` `search` `playlist` `like` `lyrics` `volume` |
+103
View File
@@ -0,0 +1,103 @@
# Connecting OpenCLI via CDP (Remote/Headless Servers)
If you cannot use the opencli Browser Bridge extension (e.g., in a remote headless server environment without a UI), OpenCLI provides an alternative: connecting directly to Chrome via **CDP (Chrome DevTools Protocol)**.
Because CDP binds to `localhost` by default for security reasons, accessing it from a remote server requires an additional networking tunnel.
This guide is broken down into three phases:
1. **Preparation**: Start Chrome with CDP enabled locally.
2. **Network Tunnels**: Expose that CDP port to your remote server using either **SSH Tunnels** or **Reverse Proxies**.
3. **Execution**: Run OpenCLI on your server.
---
## Phase 1: Preparation (Local Machine)
First, you need to start a Chrome browser on your local machine with remote debugging enabled.
**macOS:**
```bash
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir="$HOME/chrome-debug-profile" \
--remote-allow-origins="*"
```
**Linux:**
```bash
google-chrome \
--remote-debugging-port=9222 \
--user-data-dir="$HOME/chrome-debug-profile" \
--remote-allow-origins="*"
```
**Windows:**
```cmd
"C:\Program Files\Google\Chrome\Application\chrome.exe" ^
--remote-debugging-port=9222 ^
--user-data-dir="%USERPROFILE%\chrome-debug-profile" ^
--remote-allow-origins="*"
```
> **Note**: The `--remote-allow-origins="*"` flag is often required for modern Chrome versions to accept cross-origin CDP WebSocket connections (e.g. from reverse proxies like ngrok).
Once this browser instance opens, **log into the target websites you want to use** (e.g., bilibili.com, zhihu.com) so that the session contains the correct cookies.
---
## Phase 2: Remote Access Methods
Once CDP is running locally on port `9222`, you must securely expose this port to your remote server. Choose one of the two methods below depending on your network conditions.
### Method A: SSH Tunnel (Recommended)
If your local machine has SSH access to the remote server, this is the most secure and straightforward method.
Run this command on your **Local Machine** to forward the remote server's port `9222` back to your local port `9222`:
```bash
ssh -R 9222:localhost:9222 your-server-user@your-server-ip
```
Leave this SSH session running in the background.
### Method B: Reverse Proxy (ngrok / frp / socat)
If you cannot establish a direct SSH connection (e.g., due to NAT or firewalls), you can use an intranet penetration tool like `ngrok`.
Run this command on your **Local Machine** to expose your local port `9222` to the public internet securely via ngrok:
```bash
ngrok http 9222
```
This will print a forwarding URL, such as `https://abcdef.ngrok.app`. **Copy this URL**.
---
## Phase 3: Execution (Remote Server)
Now switch to your **Remote Server** where OpenCLI is installed.
Depending on the network tunnel method you chose in Phase 2, set the `OPENCLI_CDP_ENDPOINT` environment variable and run your commands.
### If you used Method A (SSH Tunnel):
```bash
export OPENCLI_CDP_ENDPOINT="http://localhost:9222"
opencli doctor # Verify connection
opencli bilibili hot --limit 5 # Test a command
```
### If you used Method B (Reverse Proxy like ngrok):
```bash
# Use the URL you copied from ngrok earlier
export OPENCLI_CDP_ENDPOINT="https://abcdef.ngrok.app"
opencli doctor # Verify connection
opencli bilibili hot --limit 5 # Test a command
```
> *Tip: If you provide a standard HTTP/HTTPS CDP endpoint, OpenCLI requests the `/json` target list and picks the most likely inspectable app/page target automatically. If multiple app targets exist, you can further narrow selection with `OPENCLI_CDP_TARGET` (for example `antigravity` or `codex`).*
If you plan to use this setup frequently, you can persist the environment variable by adding the `export` line to your `~/.bashrc` or `~/.zshrc` on the server.
+63
View File
@@ -0,0 +1,63 @@
# Download Support
OpenCLI supports downloading images, videos, and articles from supported platforms.
## Supported Platforms
| Platform | Content Types | Notes |
|----------|---------------|-------|
| **xiaohongshu** | Images, Videos | Downloads all media from a note |
| **bilibili** | Videos | Requires `yt-dlp` installed |
| **twitter** | Images, Videos | Downloads from user media tab or single tweet |
| **zhihu** | Articles (Markdown) | Exports articles with optional image download |
## Prerequisites
For video downloads from streaming platforms, install `yt-dlp`:
```bash
# Install yt-dlp
pip install yt-dlp
# or
brew install yt-dlp
```
## Usage Examples
```bash
# Download images/videos from Xiaohongshu note
opencli xiaohongshu download --note-id abc123 --output ./xhs
# Download Bilibili video (requires yt-dlp)
opencli bilibili download --bvid BV1xxx --output ./bilibili
opencli bilibili download --bvid BV1xxx --quality 1080p
# Download Twitter media from user
opencli twitter download elonmusk --limit 20 --output ./twitter
# Download single tweet media
opencli twitter download --tweet-url "https://x.com/user/status/123" --output ./twitter
# Export Zhihu article to Markdown
opencli zhihu download "https://zhuanlan.zhihu.com/p/xxx" --output ./zhihu
# Export with local images
opencli zhihu download "https://zhuanlan.zhihu.com/p/xxx" --download-images
```
## Pipeline Step (YAML Adapters)
The `download` step can be used in YAML pipelines:
::: v-pre
```yaml
pipeline:
- fetch: https://api.example.com/media
- download:
url: ${{ item.imageUrl }}
dir: ./downloads
filename: ${{ item.title | sanitize }}.jpg
concurrency: 5
skip_existing: true
```
:::
+125
View File
@@ -0,0 +1,125 @@
---
description: How to CLI-ify and automate any Electron Desktop Application via CDP
---
# CLI-ifying Electron Applications (Skill Guide)
Based on the successful automation of **Cursor**, **Codex**, **Antigravity**, **ChatWise**, **Notion**, and **Discord** desktop apps, this guide serves as the standard operating procedure (SOP) for adapting ANY Electron-based application into an OpenCLI adapter.
## Core Concept
Electron apps are essentially local Chromium browser instances. By exposing a debugging port (CDP — Chrome DevTools Protocol) at launch time, we can use the Browser Bridge to pierce through the UI layer, accessing and controlling all underlying state including React/Vue components and Shadow DOM.
> **Note:** Not all desktop apps are Electron. WeChat (native Cocoa) and Feishu/Lark (custom Lark Framework) embed Chromium but do NOT expose CDP. For those apps, use the AppleScript + clipboard approach instead (see [Non-Electron Pattern](#non-electron-pattern-applescript)).
### Launching the Target App
```bash
/Applications/AppName.app/Contents/MacOS/AppName --remote-debugging-port=9222
```
### Verifying Electron
```bash
# Check for Electron Framework in the app bundle
ls /Applications/AppName.app/Contents/Frameworks/Electron\ Framework.framework
# If this directory exists → Electron → CDP works
# If not → check for libEGL.dylib (embedded Chromium/CEF, CDP may not work)
```
## The 5-Command Pattern (CDP / Electron)
Every new Electron adapter should implement these 5 commands in `src/clis/<app_name>/`:
### 1. `status.ts` — Connection Test
```typescript
export const statusCommand = cli({
site: 'myapp',
name: 'status',
domain: 'localhost',
strategy: Strategy.UI,
browser: true, // Requires CDP connection
args: [],
columns: ['Status', 'Url', 'Title'],
func: async (page: IPage) => {
const url = await page.evaluate('window.location.href');
const title = await page.evaluate('document.title');
return [{ Status: 'Connected', Url: url, Title: title }];
},
});
```
### 2. `dump.ts` — Reverse Engineering Core
Modern app DOMs are huge and obfuscated. **Never guess selectors.** Dump first, then extract precise class names with AI or `grep`:
```typescript
const dom = await page.evaluate('document.body.innerHTML');
fs.writeFileSync('/tmp/app-dom.html', dom);
const snap = await page.snapshot({ interactive: false });
fs.writeFileSync('/tmp/app-snapshot.json', JSON.stringify(snap, null, 2));
```
### 3. `send.ts` — Advanced Text Injection
Electron apps often use complex rich-text editors (Monaco, Lexical, ProseMirror). Setting `.value` directly is ignored by React state.
**Best practice:** Use `document.execCommand('insertText')` to perfectly simulate real user input, fully piercing React state:
```javascript
const composer = document.querySelector('[contenteditable="true"]');
composer.focus();
document.execCommand('insertText', false, 'Hello');
```
Then submit with `await page.pressKey('Enter')`.
### 4. `read.ts` — Context Extraction
Don't extract the entire page text. Use `dump.ts` output to find the real "conversation container":
- Look for semantic selectors: `[role="log"]`, `[data-testid="conversation"]`, `[data-content-search-turn-key]`
- Format output as Markdown — readable by both humans and LLMs
### 5. `new.ts` — Keyboard Shortcuts
Many GUI actions respond to native shortcuts rather than button clicks:
```typescript
const isMac = process.platform === 'darwin';
await page.pressKey(isMac ? 'Meta+N' : 'Control+N');
await page.wait(1); // Wait for re-render
```
## Environment Variable
```bash
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9222"
```
## Non-Electron Pattern (AppleScript)
For native macOS apps (WeChat, Feishu) that don't expose CDP:
```typescript
export const statusCommand = cli({
site: 'myapp',
strategy: Strategy.PUBLIC,
browser: false, // No browser needed
func: async (page: IPage | null) => {
const output = execSync("osascript -e 'application \"MyApp\" is running'", { encoding: 'utf-8' }).trim();
return [{ Status: output === 'true' ? 'Running' : 'Stopped' }];
},
});
```
Core techniques:
- **status**: `osascript -e 'application "AppName" is running'`
- **send**: `pbcopy` → activate window → `Cmd+V``Enter`
- **read**: `Cmd+A``Cmd+C``pbpaste`
- **search**: Activate → `Cmd+F`/`Cmd+K``keystroke "query"`
## Pitfalls & Gotchas
1. **Port conflicts (EADDRINUSE)**: Only one app per port. Use unique ports: Codex=9222, ChatGPT=9224, Cursor=9226, ChatWise=9228, Notion=9230, Discord=9232
2. **IPage abstraction**: OpenCLI wraps the browser page as `IPage` (`src/types.ts`). Use `page.pressKey()` and `page.evaluate()`, NOT direct DOM APIs
3. **Timing**: Always add `await page.wait(0.5)` to `1.0` after DOM mutations. Returning too early disconnects prematurely
4. **AppleScript requires Accessibility**: Terminal app must be granted permission in System Settings → Privacy & Security → Accessibility
## Port Assignment Table
| App | Port | Mode |
|-----|------|------|
| Codex | 9222 | CDP |
| ChatGPT | 9224 | CDP / AppleScript |
| Cursor | 9226 | CDP |
| ChatWise | 9228 | CDP |
| Notion | 9230 | CDP |
| Discord App | 9232 | CDP |
+72
View File
@@ -0,0 +1,72 @@
# Remote Chrome
Run OpenCLI on a server or headless environment by connecting to a remote Chrome instance.
## Use Cases
- Running CLI commands on a remote server
- CI/CD automation with headed browser
- Shared team browser sessions
## Setup
### 1. Start Chrome on the Remote Machine
```bash
# On the remote machine (or your Mac)
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222
```
### 2. SSH Tunnel (If Needed)
If the remote Chrome is on a different machine, create an SSH tunnel:
```bash
# On your local machine or server
ssh -L 9222:127.0.0.1:9222 user@remote-host
```
::: warning
Use `127.0.0.1` instead of `localhost` in the SSH command to avoid IPv6 resolution issues that can cause timeouts.
:::
### 3. Configure OpenCLI
```bash
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9222"
```
### 4. Verify
```bash
# Test the connection
curl http://127.0.0.1:9222/json/version
# Run a diagnostic
opencli doctor
```
## CI/CD Integration
For CI/CD environments, use a real Chrome instance with `xvfb`:
::: v-pre
```yaml
steps:
- uses: browser-actions/setup-chrome@latest
id: setup-chrome
- run: |
xvfb-run --auto-servernum \
${{ steps.setup-chrome.outputs.chrome-path }} \
--remote-debugging-port=9222 &
```
:::
Set the browser executable path:
::: v-pre
```yaml
env:
OPENCLI_BROWSER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
```
:::
+66
View File
@@ -0,0 +1,66 @@
# AI Workflow
OpenCLI is designed with AI agents in mind. This guide covers the AI-native discovery and code generation tools.
## Quick Mode (One-Shot)
Generate a single command for a specific page URL — just a URL + one-line goal, 4 steps done:
```bash
opencli generate https://example.com --goal "trending"
```
This runs: explore → synthesize → register in one shot.
For the complete one-shot workflow details, see [CLI-ONESHOT.md](https://github.com/jackwener/opencli/blob/main/CLI-ONESHOT.md).
## Full Mode (Explorer Workflow)
### Step 1: Deep Explore
Discover APIs, infer capabilities, and detect framework:
```bash
opencli explore https://example.com --site mysite
```
Outputs to `.opencli/explore/<site>/`:
- `manifest.json` — Site metadata
- `endpoints.json` — Discovered API endpoints
- `capabilities.json` — Inferred capabilities
- `auth.json` — Authentication strategy details
### Step 2: Synthesize
Generate YAML adapters from explore artifacts:
```bash
opencli synthesize mysite
```
### Step 3: Strategy Cascade
Auto-probe authentication strategies: `PUBLIC → COOKIE → HEADER`:
```bash
opencli cascade https://api.example.com/data
```
### Step 4: Validate & Test
```bash
opencli validate # Validate generated YAML
opencli <site> <command> --limit 3 -f json # Test the command
```
## 5-Tier Authentication Strategy
The explorer uses a decision tree to determine the best authentication approach:
1. **PUBLIC** — No auth, direct API call
2. **COOKIE** — Reuse Chrome session cookies
3. **HEADER** — Custom auth headers
4. **BROWSER** — Full browser automation
5. **CDP** — Chrome DevTools Protocol for Electron apps
For the complete browser exploration workflow and debugging guide, see [CLI-EXPLORER.md](https://github.com/jackwener/opencli/blob/main/CLI-EXPLORER.md).
+103
View File
@@ -0,0 +1,103 @@
# Architecture
OpenCLI is built on a **Dual-Engine Architecture** that supports both declarative YAML pipelines and programmatic TypeScript adapters.
## High-Level Architecture
```
┌─────────────────────────────────────────────────────┐
│ opencli CLI │
│ (Commander.js entry point) │
├─────────────────────────────────────────────────────┤
│ Engine Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ Registry │ │ Dynamic │ │ Output │ │
│ │ (commands) │ │ Loader │ │ Formatter │ │
│ └──────────────┘ └──────────────┘ └────────────┘ │
├─────────────────────────────────────────────────────┤
│ Adapter Layer │
│ ┌─────────────────┐ ┌──────────────────────────┐ │
│ │ YAML Pipeline │ │ TypeScript Adapters │ │
│ │ (declarative) │ │ (browser/desktop/AI) │ │
│ └─────────────────┘ └──────────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ Connection Layer │
│ ┌─────────────────┐ ┌──────────────────────────┐ │
│ │ Browser Bridge │ │ CDP (Chrome DevTools) │ │
│ │ (Extension+WS) │ │ (Electron apps) │ │
│ └─────────────────┘ └──────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
## Core Modules
### Registry (`src/registry.ts`)
Central command registry. All adapters register their commands via the `cli()` function with metadata: site, name, description, domain, strategy, args, columns.
### Discovery (`src/discovery.ts`)
CLI discovery and manifest loading. Discovers commands from YAML and TypeScript adapter files, parses YAML pipelines, and registers them into the central registry.
### Execution (`src/execution.ts`)
Command execution: argument validation, lazy loading of adapter modules, and executing the appropriate handler function.
### Commander Adapter (`src/commanderAdapter.ts`)
Bridges the Registry commands to Commander.js subcommands. Handles positional args, named options, browser session wiring, and output formatting. Isolates all Commander-specific logic so the core is framework-agnostic.
### Browser (`src/browser.ts`)
Manages connections to Chrome via the Browser Bridge WebSocket daemon. Handles JSON-RPC messaging, tab management, and extension/standalone mode switching.
### Pipeline (`src/pipeline/`)
The YAML pipeline engine. Processes declarative steps:
- **fetch** — HTTP requests with cookie/header strategies
- **map** — Data transformation with template expressions
- **limit** — Result truncation
- **filter** — Conditional filtering
- **download** — Media download support
### Output (`src/output.ts`)
Unified output formatting: `table`, `json`, `yaml`, `md`, `csv`.
## Authentication Strategies
OpenCLI uses a 3-tier authentication strategy:
| Strategy | How It Works | When to Use |
|----------|-------------|-------------|
| `public` | Direct HTTP fetch, no auth | Public APIs (HackerNews, BBC) |
| `cookie` | Reuse Chrome cookies via Browser Bridge | Logged-in sites (Bilibili, Zhihu) |
| `header` | Custom auth headers | API-key based services |
| `intercept` | Network request interception | GraphQL/XHR capture (Twitter) |
| `ui` | DOM interaction via accessibility snapshot | Desktop apps, write operations |
## Directory Structure
```
src/
├── main.ts # Entry point
├── cli.ts # Commander.js CLI setup + built-in commands
├── commanderAdapter.ts # Registry → Commander bridge
├── discovery.ts # CLI discovery, manifest loading, YAML parsing
├── execution.ts # Arg validation, command execution
├── registry.ts # Command registry
├── serialization.ts # Command serialization helpers
├── runtime.ts # Browser session & timeout management
├── browser/ # Browser Bridge connection
├── output.ts # Output formatting
├── doctor.ts # Diagnostic tool
├── pipeline/ # YAML pipeline engine
│ ├── runner.ts
│ ├── template.ts
│ ├── transform.ts
│ └── steps/
│ ├── fetch.ts
│ ├── map.ts
│ ├── limit.ts
│ ├── filter.ts
│ └── download.ts
└── clis/ # Site adapters
├── twitter/
├── reddit/
├── bilibili/
├── cursor/
└── ...
```
+136
View File
@@ -0,0 +1,136 @@
# Contributing
Thanks for your interest in contributing to OpenCLI.
## Quick Start
```bash
# 1. Fork & clone
git clone git@github.com:<your-username>/opencli.git
cd opencli
# 2. Install dependencies
npm install
# 3. Build
npm run build
# 4. Run a few checks
npx tsc --noEmit
npx vitest run src/
# 5. Link globally (optional, for testing `opencli` command)
npm link
```
## Adding a New Site Adapter
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.
### YAML Adapter (Recommended for data-fetching commands)
Create a file like `src/clis/<site>/<command>.yaml`:
::: v-pre
```yaml
site: mysite
name: trending
description: Trending posts on MySite
domain: www.mysite.com
strategy: public # public | cookie | header
browser: false # true if browser session is needed
args:
limit:
type: int
default: 20
description: Number of items
pipeline:
- fetch:
url: https://api.mysite.com/trending
- map:
rank: ${{ index + 1 }}
title: ${{ item.title }}
score: ${{ item.score }}
url: ${{ item.url }}
- limit: ${{ args.limit }}
columns: [rank, title, score, url]
```
:::
See [`hackernews/top.yaml`](https://github.com/jackwener/opencli/blob/main/src/clis/hackernews/top.yaml) for a real example.
### TypeScript Adapter (For complex browser interactions)
Create a file like `src/clis/<site>/<command>.ts`:
```typescript
import { cli, Strategy } from '../../registry.js';
cli({
site: 'mysite',
name: 'search',
description: 'Search MySite',
domain: 'www.mysite.com',
strategy: Strategy.COOKIE,
args: [
{ name: 'query', required: true, help: 'Search query' },
{ name: 'limit', type: 'int', default: 10, help: 'Max results' },
],
columns: ['title', 'url', 'date'],
func: async (page, kwargs) => {
const { query, limit = 10 } = kwargs;
// ... browser automation logic
return data.slice(0, Number(limit)).map((item: any) => ({
title: item.title,
url: item.url,
date: item.created_at,
}));
},
});
```
### Validate Your Adapter
```bash
opencli validate # Validate YAML syntax and schema
opencli <site> <command> --limit 3 -f json # Test your command
opencli <site> <command> -v # Verbose mode for debugging
```
## Code Style
- **TypeScript strict mode** — avoid `any` where possible.
- **ES Modules** — use `.js` extensions in imports (TypeScript output).
- **Naming**: `kebab-case` for files, `camelCase` for variables/functions, `PascalCase` for types/classes.
- **No default exports** — use named exports.
## Commit Convention
We use [Conventional Commits](https://www.conventionalcommits.org/):
```
feat(twitter): add thread command
fix(browser): handle CDP timeout gracefully
docs: update CONTRIBUTING.md
test(reddit): add e2e test for save command
chore: bump vitest to v4
```
## Submitting a Pull Request
1. Create a feature branch: `git checkout -b feat/mysite-trending`
2. Make your changes and add tests when relevant
3. Run the checks:
```bash
npx tsc --noEmit # Type check
npx vitest run src/ # Unit tests
opencli validate # YAML validation (if applicable)
```
4. Commit using conventional commit format
5. Push and open a PR
+237
View File
@@ -0,0 +1,237 @@
# Testing Guide
> 面向开发者和 AI Agent 的测试参考手册。
## 目录
- [测试架构](#测试架构)
- [当前覆盖范围](#当前覆盖范围)
- [本地运行测试](#本地运行测试)
- [如何添加新测试](#如何添加新测试)
- [CI/CD 流水线](#cicd-流水线)
- [浏览器模式](#浏览器模式)
- [站点兼容性](#站点兼容性)
---
## 测试架构
测试分为三层,全部使用 **vitest** 运行:
```
tests/
├── e2e/ # E2E 集成测试(子进程运行真实 CLI)
│ ├── helpers.ts # runCli() 共享工具
│ ├── public-commands.test.ts # 公开 API 命令(无需浏览器)
│ ├── browser-public.test.ts # 浏览器命令(公开数据)
│ ├── browser-auth.test.ts # 需登录命令(graceful failure 测试)
│ ├── management.test.ts # 管理命令(list, validate, verify, help
│ └── output-formats.test.ts # 输出格式(json/yaml/csv/md
├── smoke/ # 烟雾测试(仅定时 / 手动触发)
│ └── api-health.test.ts # 外部 API 可用性检测
src/
├── *.test.ts # 单元测试(已有 8 个)
```
| 层 | 位置 | 运行方式 | 用途 |
|---|---|---|---|
| 单元测试 | `src/**/*.test.ts` | `npx vitest run src/` | 内部模块逻辑 |
| E2E 测试 | `tests/e2e/*.test.ts` | `npx vitest run tests/e2e/` | 真实 CLI 命令执行 |
| 烟雾测试 | `tests/smoke/*.test.ts` | `npx vitest run tests/smoke/` | 外部 API 健康 |
---
## 当前覆盖范围
### 单元测试(8 个文件)
| 文件 | 覆盖内容 |
|---|---|
| `browser.test.ts` | JSON-RPC、tab 管理、extension/standalone 模式切换 |
| `engine.test.ts` | 命令发现与执行 |
| `registry.test.ts` | 命令注册与策略分配 |
| `output.test.ts` | 输出格式渲染 |
| `doctor.test.ts` | Token 诊断 |
| `coupang.test.ts` | 数据归一化 |
| `pipeline/template.test.ts` | 模板表达式求值 |
| `pipeline/transform.test.ts` | 数据变换步骤 |
### E2E 测试(~52 个用例)
| 文件 | 覆盖站点/功能 | 测试数 |
|---|---|---|
| `public-commands.test.ts` | hackernews/top, v2ex/hot, v2ex/latest, v2ex/topic | 5 |
| `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 |
| `browser-auth.test.ts` | bilibili/me,dynamic,favorite,history,following + twitter/bookmarks,timeline,notifications + v2ex/me,notifications + xueqiu/feed,watchlist + xiaohongshu/feed,notifications | 14 |
| `management.test.ts` | list×5 格式, validate×3 级别, verify, --version, --help, unknown cmd | 12 |
| `output-formats.test.ts` | json, yaml, csv, md 格式验证 | 5 |
### 烟雾测试
公开 API 可用性(hackernews, v2ex×2, v2ex/topic+ 全站点注册完整性检查。
---
## 本地运行测试
### 前置条件
```bash
npm ci # 安装依赖
npm run build # 编译(E2E 测试需要 dist/main.js
```
### 运行命令
```bash
# 全部单元测试
npx vitest run src/
# 全部 E2E 测试(会真实调用外部 API)
npx vitest run tests/e2e/
# 单个测试文件
npx vitest run tests/e2e/management.test.ts
# 全部测试(单元 + E2E
npx vitest run
# 烟雾测试
npx vitest run tests/smoke/
# watch 模式(开发时推荐)
npx vitest src/
```
### 浏览器命令本地测试须知
- opencli 通过 Browser Bridge 扩展连接已运行的 Chrome 浏览器
- `browser-public.test.ts` 使用 `tryBrowserCommand()`,站点反爬导致空数据时 warn + pass
- `browser-auth.test.ts` 验证 **graceful failure**(不 crash 不 hang 即通过)
- 如需测试完整登录态,保持 Chrome 登录态并安装 Browser Bridge 扩展,手动跑对应测试
---
## 如何添加新测试
### 新增 YAML Adapter(如 `src/clis/producthunt/trending.yaml`
1. **无需额外操作**`validate` 测试会自动覆盖 YAML 结构验证
2. 根据 adapter 类型,在对应文件加一个 `it()` block
```typescript
// 如果 browser: false(公开 API)→ tests/e2e/public-commands.test.ts
it('producthunt trending returns data', async () => {
const { stdout, code } = await runCli(['producthunt', 'trending', '--limit', '3', '-f', 'json']);
expect(code).toBe(0);
const data = parseJsonOutput(stdout);
expect(Array.isArray(data)).toBe(true);
expect(data.length).toBeGreaterThanOrEqual(1);
expect(data[0]).toHaveProperty('title');
}, 30_000);
```
```typescript
// 如果 browser: true 但可公开访问 → tests/e2e/browser-public.test.ts
it('producthunt trending returns data', async () => {
const data = await tryBrowserCommand(['producthunt', 'trending', '--limit', '3', '-f', 'json']);
expectDataOrSkip(data, 'producthunt trending');
}, 60_000);
```
```typescript
// 如果 browser: true 且需登录 → tests/e2e/browser-auth.test.ts
it('producthunt me fails gracefully without login', async () => {
await expectGracefulAuthFailure(['producthunt', 'me', '-f', 'json'], 'producthunt me');
}, 60_000);
```
### 新增管理命令(如 `opencli export`
`tests/e2e/management.test.ts` 添加测试。
### 新增内部模块
`src/` 下对应位置创建 `*.test.ts`
### 决策流程图
```
新增功能 → 是内部模块? → 是 → src/ 下加 *.test.ts
↓ 否
是 CLI 命令? → browser: false? → tests/e2e/public-commands.test.ts
↓ true
公开数据? → tests/e2e/browser-public.test.ts
↓ 需登录
tests/e2e/browser-auth.test.ts
```
---
## CI/CD 流水线
### ci.yml(主流水线)
| Job | 触发条件 | 内容 |
|---|---|---|
| **build** | push/PR to main,dev | typecheck + build |
| **unit-test** | push/PR to main,dev | 单元测试,2 shard 并行 |
| **smoke-test** | 每周一 08:00 UTC / 手动 | xvfb + real Chrome,外部 API 健康检查 |
### e2e-headed.ymlE2E 测试)
| Job | 触发条件 | 内容 |
|---|---|---|
| **e2e-headed** | push/PR to main,dev | xvfb + real Chrome,全部 E2E 测试 |
E2E 使用 `browser-actions/setup-chrome` 安装真实 Chrome,配合 `xvfb-run` 提供虚拟显示器,以 headed 模式运行浏览器。
### Sharding
单元测试使用 vitest 内置 shard
::: v-pre
```yaml
strategy:
matrix:
shard: [1, 2]
steps:
- run: npx vitest run src/ --shard=${{ matrix.shard }}/2
```
:::
---
## 浏览器模式
opencli 通过 Browser Bridge 扩展连接浏览器:
| 条件 | 模式 | 使用场景 |
|---|---|---|
| 扩展已安装 | Extension 模式 | 本地用户,连接已登录的 Chrome |
| 扩展未安装 | CLI 报错提示安装 | 需要安装 Browser Bridge 扩展 |
CI 中使用 `OPENCLI_BROWSER_EXECUTABLE_PATH` 指定真实 Chrome 路径:
::: v-pre
```yaml
env:
OPENCLI_BROWSER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
```
:::
---
## 站点兼容性
在 GitHub Actions 美国 runner 上,部分站点因地域限制或登录要求返回空数据。E2E 测试对这些站点使用 warn + pass 策略,不影响 CI 绿灯。
| 站点 | CI 状态 | 限制原因 |
|---|---|---|
| hackernews, bbc, v2ex | ✅ 返回数据 | 无限制 |
| yahoo-finance | ✅ 返回数据 | 无限制 |
| bilibili, zhihu, weibo, xiaohongshu | ⚠️ 空数据 | 地域限制(中国站点) |
| reddit, twitter, youtube | ⚠️ 空数据 | 需登录或 cookie |
| smzdm, boss, ctrip, coupang, xueqiu | ⚠️ 空数据 | 地域限制 / 需登录 |
> 使用 self-hosted runner(国内服务器)可解决地域限制问题。
+87
View File
@@ -0,0 +1,87 @@
# TypeScript Adapter Guide
Use TypeScript adapters when you need browser-side logic, multi-step flows, DOM manipulation, or complex data extraction that goes beyond simple API fetching.
## Basic Structure
```typescript
import { cli, Strategy } from '../../registry.js';
cli({
site: 'mysite',
name: 'search',
description: 'Search MySite',
domain: 'www.mysite.com',
strategy: Strategy.COOKIE, // PUBLIC | COOKIE | HEADER
args: [
{ name: 'query', required: true, help: 'Search query' },
{ name: 'limit', type: 'int', default: 10, help: 'Max results' },
],
columns: ['title', 'url', 'date'],
func: async (page, kwargs) => {
const { query, limit = 10 } = kwargs;
// Navigate and extract data
await page.goto('https://www.mysite.com');
const data = await page.evaluate(`
(async () => {
const res = await fetch('/api/search?q=${encodeURIComponent(String(query))}', {
credentials: 'include'
});
return (await res.json()).results;
})()
`);
return data.slice(0, Number(limit)).map((item: any) => ({
title: item.title,
url: item.url,
date: item.created_at,
}));
},
});
```
## Strategy Types
| Strategy | Constant | Use Case |
|----------|----------|----------|
| Public | `Strategy.PUBLIC` | No auth needed |
| Cookie | `Strategy.COOKIE` | Browser session cookies |
| Header | `Strategy.HEADER` | Custom headers/tokens |
## The `page` Object
The `page` parameter provides browser interaction methods:
- `page.goto(url)` — Navigate to a URL
- `page.evaluate(script)` — Execute JavaScript in the page context
- `page.waitForSelector(selector)` — Wait for an element
- `page.click(selector)` — Click an element
- `page.type(selector, text)` — Type text into an input
## The `kwargs` Object
Contains parsed CLI arguments as key-value pairs. Always destructure with defaults:
```typescript
const { query, limit = 10, format = 'json' } = kwargs;
```
## AI-Assisted Development
Use the AI workflow tools to accelerate adapter creation:
```bash
# Discover APIs and page structure
opencli explore https://example.com --site mysite
# Auto-generate adapter from explore artifacts
opencli synthesize mysite
# One-shot: explore → synthesize → register
opencli generate https://example.com --goal "trending"
```
See [AI Workflow](/developer/ai-workflow) for the complete guide.
+108
View File
@@ -0,0 +1,108 @@
# YAML Adapter Guide
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.
## Basic Structure
::: v-pre
```yaml
site: mysite # Site identifier
name: trending # Command name (opencli mysite trending)
description: ... # Help text
domain: www.mysite.com
strategy: public # public | cookie | header
browser: false # true if browser session is needed
args: # CLI arguments
limit:
type: int
default: 20
description: Number of items
pipeline: # Data processing steps
- fetch:
url: https://api.mysite.com/trending
- map:
rank: ${{ index + 1 }}
title: ${{ item.title }}
- limit: ${{ args.limit }}
columns: [rank, title, score, url]
```
:::
## Pipeline Steps
### `fetch`
Fetch data from a URL. Supports template expressions for dynamic URLs.
::: v-pre
```yaml
- fetch:
url: https://api.example.com/search?q=${{ args.query }}
headers:
Accept: application/json
```
:::
### `map`
::: v-pre
Transform each item in the result array. Use `${{ item.xxx }}` for field access and `${{ index }}` for position.
```yaml
- map:
rank: ${{ index + 1 }}
title: ${{ item.title }}
url: https://example.com${{ item.path }}
```
:::
### `limit`
Truncate results to N items.
::: v-pre
```yaml
- limit: ${{ args.limit }}
```
:::
### `filter`
Filter items by condition.
::: v-pre
```yaml
- filter: ${{ item.score > 100 }}
```
:::
### `download`
Download media files.
::: v-pre
```yaml
- download:
url: ${{ item.imageUrl }}
dir: ./downloads
filename: ${{ item.title | sanitize }}.jpg
```
:::
## Template Expressions
::: v-pre
Use `${{ ... }}` for dynamic values:
| Expression | Description |
|-----------|-------------|
| `${{ args.limit }}` | CLI argument |
| `${{ item.title }}` | Current item field |
| `${{ index }}` | Current index (0-based) |
| `${{ item.x \| sanitize }}` | Pipe filters |
:::
## Real Example
See [`src/clis/hackernews/top.yaml`](https://github.com/jackwener/opencli/blob/main/src/clis/hackernews/top.yaml).
+38
View File
@@ -0,0 +1,38 @@
# Browser Bridge Setup
> **⚠️ Important**: Browser commands reuse your Chrome login session. You must be logged into the target website in Chrome before running commands.
OpenCLI connects to your browser through a lightweight **Browser Bridge** Chrome Extension + micro-daemon (zero config, auto-start).
## Extension Installation
### Method 1: Download Pre-built Release (Recommended)
1. Go to the GitHub [Releases page](https://github.com/jackwener/opencli/releases) and download the latest `opencli-extension.zip` or `opencli-extension.crx`.
2. Open `chrome://extensions` and enable **Developer mode** (top-right toggle).
3. Drag and drop the `.crx` file or the unzipped folder into the extensions page.
### Method 2: Load Unpacked Source (For Developers)
1. Open `chrome://extensions` and enable **Developer mode**.
2. Click **Load unpacked** and select the `extension/` directory from the repository.
## Verification
That's it! The daemon auto-starts when you run any browser command. No tokens, no manual configuration.
```bash
opencli doctor # Check extension + daemon connectivity
opencli doctor --live # Also test live browser commands
```
## How It Works
```
┌─────────────┐ WebSocket ┌──────────────┐ Chrome API ┌─────────┐
│ opencli │ ◄──────────────► │ micro-daemon │ ◄──────────────► │ Chrome │
│ (Node.js) │ localhost:19825 │ (auto-start) │ Extension │ Browser │
└─────────────┘ └──────────────┘ └─────────┘
```
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.
+56
View File
@@ -0,0 +1,56 @@
# Getting Started
> **Make any website or Electron App your CLI.**
> Zero risk · Reuse Chrome login · AI-powered discovery · Browser + Desktop automation
[![npm](https://img.shields.io/npm/v/@jackwener/opencli?style=flat-square)](https://www.npmjs.com/package/@jackwener/opencli)
[![Node.js Version](https://img.shields.io/node/v/@jackwener/opencli?style=flat-square)](https://nodejs.org)
[![License](https://img.shields.io/npm/l/@jackwener/opencli?style=flat-square)](https://github.com/jackwener/opencli/blob/main/LICENSE)
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.
## Highlights
- **CLI All Electron** — CLI-ify apps like Antigravity Ultra! Now AI can control itself natively.
- **Account-safe** — Reuses Chrome's logged-in state; your credentials never leave the browser.
- **AI Agent ready** — `explore` discovers APIs, `synthesize` generates adapters, `cascade` finds auth strategies.
- **Self-healing setup** — `opencli setup` verifies Browser Bridge connectivity; `opencli doctor` diagnoses daemon, extension, and live browser connectivity.
- **Dynamic Loader** — Simply drop `.ts` or `.yaml` adapters into the `clis/` folder for auto-registration.
- **Dual-Engine Architecture** — Supports both YAML declarative data pipelines and robust browser runtime TypeScript injections.
## Quick Start
### Install via npm
```bash
npm install -g @jackwener/opencli
```
### Basic Usage
```bash
opencli list # See all commands
opencli hackernews top --limit 5 # Public API, no browser
opencli bilibili hot --limit 5 # Browser command
opencli zhihu hot -f json # JSON output
```
### Output Formats
All built-in commands support `--format` / `-f`:
```bash
opencli bilibili hot -f table # Default: rich terminal table
opencli bilibili hot -f json # JSON (pipe to jq or LLMs)
opencli bilibili hot -f yaml # YAML (human-readable)
opencli bilibili hot -f md # Markdown
opencli bilibili hot -f csv # CSV
opencli bilibili hot -v # Verbose: show pipeline debug
```
## Next Steps
- [Installation details](/guide/installation)
- [Browser Bridge setup](/guide/browser-bridge)
- [All available adapters](/adapters/)
- [For developers / AI agents](/developer/contributing)
+37
View File
@@ -0,0 +1,37 @@
# Installation
## Requirements
- **Node.js**: >= 20.0.0
- **Chrome** running and logged into the target site (for browser commands)
## Install via npm (Recommended)
```bash
npm install -g @jackwener/opencli
```
## Install from Source
```bash
git clone git@github.com:jackwener/opencli.git
cd opencli
npm install
npm run build
npm link # Link binary globally
opencli list # Now you can use it anywhere!
```
## Update
```bash
npm install -g @jackwener/opencli@latest
```
## Verify Installation
```bash
opencli --version # Check version
opencli list # List all commands
opencli doctor # Diagnose connectivity
```
+56
View File
@@ -0,0 +1,56 @@
# Troubleshooting
## Common Issues
### "Extension not connected"
- Ensure the opencli Browser Bridge extension is installed and **enabled** in `chrome://extensions`.
- Run `opencli doctor` to diagnose connectivity.
### Empty data or 'Unauthorized' error
- 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.
- Some sites have geographic restrictions (e.g., Bilibili, Zhihu from outside China).
### Node API errors
- Make sure you are using **Node.js >= 20**. Some dependencies require modern Node APIs.
- Run `node --version` to verify.
### Daemon issues
```bash
# Check daemon status
curl localhost:19825/status
# View extension logs
curl localhost:19825/logs
# Kill and restart daemon
pkill -f opencli-daemon
opencli doctor
```
### Desktop adapter connection issues
For Electron/CDP-based adapters (Cursor, Codex, etc.):
1. Make sure the app is launched with `--remote-debugging-port=XXXX`
2. Verify the endpoint is set: `echo $OPENCLI_CDP_ENDPOINT`
3. Test the endpoint: `curl http://127.0.0.1:XXXX/json/version`
### Build errors
```bash
# Clean rebuild
rm -rf dist/
npm run build
# Type check
npx tsc --noEmit
```
## Getting Help
- [GitHub Issues](https://github.com/jackwener/opencli/issues) — Bug reports and feature requests
- Run `opencli doctor --live` for comprehensive diagnostics
+35
View File
@@ -0,0 +1,35 @@
---
layout: home
hero:
name: OpenCLI
text: Make any website or Electron App your CLI
tagline: Zero risk · Reuse Chrome login · AI-powered discovery · Browser + Desktop automation
actions:
- theme: brand
text: Get Started
link: /guide/getting-started
- theme: alt
text: View on GitHub
link: https://github.com/jackwener/opencli
features:
- icon: 🖥️
title: CLI All Electron
details: Turn ANY Electron application into a CLI tool — Cursor, Codex, Antigravity, ChatGPT, Notion, and more. AI can control itself natively.
- icon: 🔐
title: Account Safe
details: Reuses Chrome's logged-in state. Your credentials never leave the browser — no tokens, no exposed passwords.
- icon: 🤖
title: AI Agent Ready
details: "explore discovers APIs, synthesize generates adapters, cascade finds auth strategies. Built for AI-first workflows."
- icon: ⚡
title: Dual-Engine Architecture
details: Supports both YAML declarative data pipelines and robust browser runtime TypeScript injections for maximum flexibility.
- icon: 🔧
title: Self-Healing Setup
details: "opencli setup verifies Browser Bridge connectivity. opencli doctor diagnoses daemon, extension, and live browser."
- icon: 📦
title: Dynamic Loader
details: Simply drop .ts or .yaml adapters into the clis/ folder for auto-registration. Zero boilerplate.
---
+5
View File
@@ -0,0 +1,5 @@
# 所有适配器
运行 `opencli list` 查看完整命令列表。
详细文档请参考 [英文版本](/adapters/)。
+3
View File
@@ -0,0 +1,3 @@
# Chrome DevTools Protocol
详细文档请参考 [英文版本](/advanced/cdp)。
+24
View File
@@ -0,0 +1,24 @@
# 贡献指南
详细贡献指南请参考 [英文版本](/developer/contributing)。
## 快速开始
```bash
git clone git@github.com:<your-username>/opencli.git
cd opencli
npm install
npm run build
npx tsc --noEmit
npx vitest run src/
```
## 提交规范
使用 [Conventional Commits](https://www.conventionalcommits.org/)
```
feat(twitter): add thread command
fix(browser): handle CDP timeout gracefully
docs: update CONTRIBUTING.md
```
+25
View File
@@ -0,0 +1,25 @@
# Browser Bridge 设置
> **⚠️ 重要**: 浏览器命令复用你的 Chrome 登录会话。运行命令前必须在 Chrome 中登录目标网站。
OpenCLI 通过轻量级 **Browser Bridge** Chrome 扩展 + 微守护进程连接浏览器(零配置,自动启动)。
## 扩展安装
### 方法 1:下载预构建版本(推荐)
1. 前往 GitHub [Releases 页面](https://github.com/jackwener/opencli/releases) 下载最新的 `opencli-extension.zip``opencli-extension.crx`
2. 打开 `chrome://extensions`,启用**开发者模式**。
3. 拖放 `.crx` 文件或解压后的文件夹到扩展页面。
### 方法 2:加载源码(开发者)
1. 打开 `chrome://extensions`,启用**开发者模式**。
2. 点击**加载已解压的扩展程序**,选择仓库中的 `extension/` 目录。
## 验证
```bash
opencli doctor # 检查扩展 + 守护进程连接
opencli doctor --live # 同时测试实时浏览器命令
```
+40
View File
@@ -0,0 +1,40 @@
# 快速开始
> **让任何网站或 Electron 应用成为你的 CLI。**
> 零风险 · 复用 Chrome 登录态 · AI 驱动发现 · 浏览器 + 桌面自动化
OpenCLI 将**任何网站**或 **Electron 应用**变成命令行界面 — Bilibili、知乎、小红书、Twitter/X、Reddit、YouTube、Antigravity 等 — 基于浏览器会话复用和 AI 原生发现。
## 安装
```bash
npm install -g @jackwener/opencli
```
## 基本使用
```bash
opencli list # 查看所有命令
opencli hackernews top --limit 5 # 公开 API,无需浏览器
opencli bilibili hot --limit 5 # 浏览器命令
opencli zhihu hot -f json # JSON 输出
```
## 输出格式
所有命令支持 `--format` / `-f`
```bash
opencli bilibili hot -f table # 默认:终端表格
opencli bilibili hot -f json # JSON
opencli bilibili hot -f yaml # YAML
opencli bilibili hot -f md # Markdown
opencli bilibili hot -f csv # CSV
```
## 下一步
- [安装详情](/zh/guide/installation)
- [Browser Bridge 设置](/zh/guide/browser-bridge)
- [所有适配器](/zh/adapters/)
- [开发者指南](/zh/developer/contributing)
+37
View File
@@ -0,0 +1,37 @@
# 安装
## 系统要求
- **Node.js**: >= 20.0.0
- **Chrome** 已运行并登录目标网站(浏览器命令需要)
## 通过 npm 安装(推荐)
```bash
npm install -g @jackwener/opencli
```
## 从源码安装
```bash
git clone git@github.com:jackwener/opencli.git
cd opencli
npm install
npm run build
npm link
opencli list
```
## 更新
```bash
npm install -g @jackwener/opencli@latest
```
## 验证安装
```bash
opencli --version
opencli list
opencli doctor
```
+29
View File
@@ -0,0 +1,29 @@
---
layout: home
hero:
name: OpenCLI
text: 让任何网站或 Electron 应用成为你的 CLI
tagline: 零风险 · 复用 Chrome 登录态 · AI 驱动发现 · 浏览器 + 桌面自动化
actions:
- theme: brand
text: 快速开始
link: /zh/guide/getting-started
- theme: alt
text: 在 GitHub 查看
link: https://github.com/jackwener/opencli
features:
- icon: 🖥️
title: CLI 所有 Electron 应用
details: 将任何 Electron 应用变成 CLI 工具 — Cursor、Codex、Antigravity、ChatGPT、Notion 等。AI 可以原生控制自身。
- icon: 🔐
title: 账号安全
details: 复用 Chrome 登录态,凭证永远不会离开浏览器 — 无 token,无密码泄露。
- icon: 🤖
title: AI Agent 就绪
details: explore 发现 APIsynthesize 生成适配器,cascade 查找认证策略。为 AI 优先工作流而生。
- icon: ⚡
title: 双引擎架构
details: 同时支持 YAML 声明式数据管道和强大的浏览器运行时 TypeScript 注入。
---
+1
View File
@@ -0,0 +1 @@
node_modules/
+501
View File
@@ -0,0 +1,501 @@
const DAEMON_PORT = 19825;
const DAEMON_HOST = "localhost";
const DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`;
const WS_RECONNECT_BASE_DELAY = 2e3;
const WS_RECONNECT_MAX_DELAY = 6e4;
const attached = /* @__PURE__ */ new Set();
async function ensureAttached(tabId) {
if (attached.has(tabId)) return;
try {
await chrome.debugger.attach({ tabId }, "1.3");
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (msg.includes("Another debugger is already attached")) {
try {
await chrome.debugger.detach({ tabId });
} catch {
}
try {
await chrome.debugger.attach({ tabId }, "1.3");
} catch {
throw new Error(`attach failed: ${msg}`);
}
} else {
throw new Error(`attach failed: ${msg}`);
}
}
attached.add(tabId);
try {
await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
} catch {
}
}
async function evaluate$1(tabId, expression) {
await ensureAttached(tabId);
const result = await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
expression,
returnByValue: true,
awaitPromise: true
});
if (result.exceptionDetails) {
const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error";
throw new Error(errMsg);
}
return result.result?.value;
}
async function screenshot(tabId, options = {}) {
await ensureAttached(tabId);
const format = options.format ?? "png";
if (options.fullPage) {
const metrics = await chrome.debugger.sendCommand({ tabId }, "Page.getLayoutMetrics");
const size = metrics.cssContentSize || metrics.contentSize;
if (size) {
await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", {
mobile: false,
width: Math.ceil(size.width),
height: Math.ceil(size.height),
deviceScaleFactor: 1
});
}
}
try {
const params = { format };
if (format === "jpeg" && options.quality !== void 0) {
params.quality = Math.max(0, Math.min(100, options.quality));
}
const result = await chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", params);
return result.data;
} finally {
if (options.fullPage) {
await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride").catch(() => {
});
}
}
}
function detach$1(tabId) {
if (!attached.has(tabId)) return;
attached.delete(tabId);
try {
chrome.debugger.detach({ tabId });
} catch {
}
}
function registerListeners$1() {
chrome.tabs.onRemoved.addListener((tabId) => {
attached.delete(tabId);
});
chrome.debugger.onDetach.addListener((source) => {
if (source.tabId) attached.delete(source.tabId);
});
}
const cspBlockedTabs = /* @__PURE__ */ new Set();
async function evaluate(tabId, expression) {
if (cspBlockedTabs.has(tabId)) {
return evaluate$1(tabId, expression);
}
try {
const results = await chrome.scripting.executeScript({
target: { tabId },
world: "MAIN",
func: (expr) => {
try {
const result = (0, eval)(expr);
if (result && typeof result === "object" && typeof result.then === "function") {
return result.then(
(v) => JSON.stringify({ ok: true, v }),
(e) => JSON.stringify({ ok: false, err: e?.message || String(e) })
);
}
return JSON.stringify({ ok: true, v: result });
} catch (e) {
return JSON.stringify({ ok: false, err: e?.message || String(e) });
}
},
args: [expression]
});
if (!results || results.length === 0) {
throw new Error("executeScript returned no results");
}
const frame = results[0];
if (frame.error) {
throw new Error(frame.error.message || String(frame.error));
}
const raw = frame.result;
if (raw === null || raw === void 0) {
return evaluate$1(tabId, expression);
}
if (typeof raw === "string") {
const parsed = JSON.parse(raw);
if (!parsed.ok) {
const err = parsed.err || "";
if (err.includes("Content Security Policy") || err.includes("'unsafe-eval'")) {
cspBlockedTabs.add(tabId);
return evaluate$1(tabId, expression);
}
throw new Error(err || "Eval error");
}
return parsed.v;
}
return raw;
} catch (e) {
const msg = e?.message || String(e);
if (msg.includes("Content Security Policy") || msg.includes("'unsafe-eval'")) {
cspBlockedTabs.add(tabId);
return evaluate$1(tabId, expression);
}
throw e;
}
}
const evaluateAsync = evaluate;
function detach(tabId) {
cspBlockedTabs.delete(tabId);
detach$1(tabId);
}
function registerListeners() {
registerListeners$1();
chrome.tabs.onRemoved.addListener((tabId) => {
cspBlockedTabs.delete(tabId);
});
}
let ws = null;
let reconnectTimer = null;
let reconnectAttempts = 0;
const _origLog = console.log.bind(console);
const _origWarn = console.warn.bind(console);
const _origError = console.error.bind(console);
function forwardLog(level, args) {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
try {
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
ws.send(JSON.stringify({ type: "log", level, msg, ts: Date.now() }));
} catch {
}
}
console.log = (...args) => {
_origLog(...args);
forwardLog("info", args);
};
console.warn = (...args) => {
_origWarn(...args);
forwardLog("warn", args);
};
console.error = (...args) => {
_origError(...args);
forwardLog("error", args);
};
function connect() {
if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) return;
try {
ws = new WebSocket(DAEMON_WS_URL);
} catch {
scheduleReconnect();
return;
}
ws.onopen = () => {
console.log("[opencli] Connected to daemon");
reconnectAttempts = 0;
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
};
ws.onmessage = async (event) => {
try {
const command = JSON.parse(event.data);
const result = await handleCommand(command);
ws?.send(JSON.stringify(result));
} catch (err) {
console.error("[opencli] Message handling error:", err);
}
};
ws.onclose = () => {
console.log("[opencli] Disconnected from daemon");
ws = null;
scheduleReconnect();
};
ws.onerror = () => {
ws?.close();
};
}
function scheduleReconnect() {
if (reconnectTimer) return;
reconnectAttempts++;
const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
reconnectTimer = setTimeout(() => {
reconnectTimer = null;
connect();
}, delay);
}
const automationSessions = /* @__PURE__ */ new Map();
const WINDOW_IDLE_TIMEOUT = 3e4;
function getWorkspaceKey(workspace) {
return workspace?.trim() || "default";
}
function resetWindowIdleTimer(workspace) {
const session = automationSessions.get(workspace);
if (!session) return;
if (session.idleTimer) clearTimeout(session.idleTimer);
session.idleDeadlineAt = Date.now() + WINDOW_IDLE_TIMEOUT;
session.idleTimer = setTimeout(async () => {
const current = automationSessions.get(workspace);
if (!current) return;
try {
await chrome.windows.remove(current.windowId);
console.log(`[opencli] Automation window ${current.windowId} (${workspace}) closed (idle timeout)`);
} catch {
}
automationSessions.delete(workspace);
}, WINDOW_IDLE_TIMEOUT);
}
async function getAutomationWindow(workspace) {
const existing = automationSessions.get(workspace);
if (existing) {
try {
await chrome.windows.get(existing.windowId);
return existing.windowId;
} catch {
automationSessions.delete(workspace);
}
}
const win = await chrome.windows.create({
url: "about:blank",
focused: false,
width: 1280,
height: 900,
type: "normal"
});
const session = {
windowId: win.id,
idleTimer: null,
idleDeadlineAt: Date.now() + WINDOW_IDLE_TIMEOUT
};
automationSessions.set(workspace, session);
console.log(`[opencli] Created automation window ${session.windowId} (${workspace})`);
resetWindowIdleTimer(workspace);
return session.windowId;
}
chrome.windows.onRemoved.addListener((windowId) => {
for (const [workspace, session] of automationSessions.entries()) {
if (session.windowId === windowId) {
console.log(`[opencli] Automation window closed (${workspace})`);
if (session.idleTimer) clearTimeout(session.idleTimer);
automationSessions.delete(workspace);
}
}
});
let initialized = false;
function initialize() {
if (initialized) return;
initialized = true;
chrome.alarms.create("keepalive", { periodInMinutes: 0.4 });
registerListeners();
connect();
console.log("[opencli] OpenCLI extension initialized");
}
chrome.runtime.onInstalled.addListener(() => {
initialize();
});
chrome.runtime.onStartup.addListener(() => {
initialize();
});
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === "keepalive") connect();
});
async function handleCommand(cmd) {
const workspace = getWorkspaceKey(cmd.workspace);
resetWindowIdleTimer(workspace);
try {
switch (cmd.action) {
case "exec":
return await handleExec(cmd, workspace);
case "navigate":
return await handleNavigate(cmd, workspace);
case "tabs":
return await handleTabs(cmd, workspace);
case "cookies":
return await handleCookies(cmd);
case "screenshot":
return await handleScreenshot(cmd, workspace);
case "close-window":
return await handleCloseWindow(cmd, workspace);
case "sessions":
return await handleSessions(cmd);
default:
return { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
}
} catch (err) {
return {
id: cmd.id,
ok: false,
error: err instanceof Error ? err.message : String(err)
};
}
}
function isWebUrl(url) {
if (!url) return false;
return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
}
async function resolveTabId(tabId, workspace) {
if (tabId !== void 0) return tabId;
const windowId = await getAutomationWindow(workspace);
const tabs = await chrome.tabs.query({ windowId });
const webTab = tabs.find((t) => t.id && isWebUrl(t.url));
if (webTab?.id) return webTab.id;
if (tabs.length > 0 && tabs[0]?.id) return tabs[0].id;
const newTab = await chrome.tabs.create({ windowId, url: "about:blank", active: true });
if (!newTab.id) throw new Error("Failed to create tab in automation window");
return newTab.id;
}
async function listAutomationTabs(workspace) {
const session = automationSessions.get(workspace);
if (!session) return [];
try {
return await chrome.tabs.query({ windowId: session.windowId });
} catch {
automationSessions.delete(workspace);
return [];
}
}
async function listAutomationWebTabs(workspace) {
const tabs = await listAutomationTabs(workspace);
return tabs.filter((tab) => isWebUrl(tab.url));
}
async function handleExec(cmd, workspace) {
if (!cmd.code) return { id: cmd.id, ok: false, error: "Missing code" };
const tabId = await resolveTabId(cmd.tabId, workspace);
try {
const data = await evaluateAsync(tabId, cmd.code);
return { id: cmd.id, ok: true, data };
} catch (err) {
return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
}
}
async function handleNavigate(cmd, workspace) {
if (!cmd.url) return { id: cmd.id, ok: false, error: "Missing url" };
const tabId = await resolveTabId(cmd.tabId, workspace);
await chrome.tabs.update(tabId, { url: cmd.url });
await new Promise((resolve) => {
chrome.tabs.get(tabId).then((tab2) => {
if (tab2.status === "complete") {
resolve();
return;
}
const listener = (id, info) => {
if (id === tabId && info.status === "complete") {
chrome.tabs.onUpdated.removeListener(listener);
resolve();
}
};
chrome.tabs.onUpdated.addListener(listener);
setTimeout(() => {
chrome.tabs.onUpdated.removeListener(listener);
resolve();
}, 15e3);
});
});
const tab = await chrome.tabs.get(tabId);
return { id: cmd.id, ok: true, data: { title: tab.title, url: tab.url, tabId } };
}
async function handleTabs(cmd, workspace) {
switch (cmd.op) {
case "list": {
const tabs = await listAutomationWebTabs(workspace);
const data = tabs.map((t, i) => ({
index: i,
tabId: t.id,
url: t.url,
title: t.title,
active: t.active
}));
return { id: cmd.id, ok: true, data };
}
case "new": {
const windowId = await getAutomationWindow(workspace);
const tab = await chrome.tabs.create({ windowId, url: cmd.url ?? "about:blank", active: true });
return { id: cmd.id, ok: true, data: { tabId: tab.id, url: tab.url } };
}
case "close": {
if (cmd.index !== void 0) {
const tabs = await listAutomationWebTabs(workspace);
const target = tabs[cmd.index];
if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
await chrome.tabs.remove(target.id);
detach(target.id);
return { id: cmd.id, ok: true, data: { closed: target.id } };
}
const tabId = await resolveTabId(cmd.tabId, workspace);
await chrome.tabs.remove(tabId);
detach(tabId);
return { id: cmd.id, ok: true, data: { closed: tabId } };
}
case "select": {
if (cmd.index === void 0 && cmd.tabId === void 0)
return { id: cmd.id, ok: false, error: "Missing index or tabId" };
if (cmd.tabId !== void 0) {
await chrome.tabs.update(cmd.tabId, { active: true });
return { id: cmd.id, ok: true, data: { selected: cmd.tabId } };
}
const tabs = await listAutomationWebTabs(workspace);
const target = tabs[cmd.index];
if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
await chrome.tabs.update(target.id, { active: true });
return { id: cmd.id, ok: true, data: { selected: target.id } };
}
default:
return { id: cmd.id, ok: false, error: `Unknown tabs op: ${cmd.op}` };
}
}
async function handleCookies(cmd) {
const details = {};
if (cmd.domain) details.domain = cmd.domain;
if (cmd.url) details.url = cmd.url;
const cookies = await chrome.cookies.getAll(details);
const data = cookies.map((c) => ({
name: c.name,
value: c.value,
domain: c.domain,
path: c.path,
secure: c.secure,
httpOnly: c.httpOnly,
expirationDate: c.expirationDate
}));
return { id: cmd.id, ok: true, data };
}
async function handleScreenshot(cmd, workspace) {
const tabId = await resolveTabId(cmd.tabId, workspace);
try {
const data = await screenshot(tabId, {
format: cmd.format,
quality: cmd.quality,
fullPage: cmd.fullPage
});
return { id: cmd.id, ok: true, data };
} catch (err) {
return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
}
}
async function handleCloseWindow(cmd, workspace) {
const session = automationSessions.get(workspace);
if (session) {
try {
await chrome.windows.remove(session.windowId);
} catch {
}
if (session.idleTimer) clearTimeout(session.idleTimer);
automationSessions.delete(workspace);
}
return { id: cmd.id, ok: true, data: { closed: true } };
}
async function handleSessions(cmd) {
const now = Date.now();
const data = await Promise.all([...automationSessions.entries()].map(async ([workspace, session]) => ({
workspace,
windowId: session.windowId,
tabCount: (await chrome.tabs.query({ windowId: session.windowId })).filter((tab) => isWebUrl(tab.url)).length,
idleMsRemaining: Math.max(0, session.idleDeadlineAt - now)
})));
return { id: cmd.id, ok: true, data };
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Some files were not shown because too many files have changed in this diff Show More