* feat(google): add search, suggest, news, and trends adapters
Four new commands under `google`:
- search: browser-based DOM extraction from google.com/search
- suggest: public JSON API (suggestqueries.google.com)
- news: public RSS feed (top stories + keyword search)
- trends: public RSS feed (daily trending searches by region)
Shared RSS parser in utils.ts with attribute/CDATA support.
Unit tests for parseRssItems, E2E tests with network skip guards.
* refactor(google): downgrade search strategy from COOKIE to PUBLIC
Google search results are public data, no login needed. Browser is
required for DOM rendering, not authentication. Standalone mode
confirmed working in testing.
* fix: update test comment to reflect PUBLIC strategy
Add new YAML adapter to fetch upcoming earnings dates from xueqiu's
company events API (公司大事). Supports A-share and H-share stocks.
Features:
- Filter by subtype=2 (预计财报发布) from event timeline
- Show date, report name, and release status (⏳/✅)
- --next flag to return only the closest upcoming earnings date
- --limit to control result count
Co-authored-by: nekomoto911 <nekomoto911@gmail.com>
Browser adapters using COOKIE/HEADER strategy need the page on the target
domain so credentialed fetch() carries cookies. Previously, execution.ts
hardcoded `cmd.site === 'boss'` to skip this pre-navigation for adapters
that handle their own goto().
Now each adapter self-declares via `navigateBefore: false` on CliCommand.
This is more extensible — new sites that manage their own navigation just
add the field instead of editing execution.ts.
Changes:
- Add `navigateBefore?: boolean | string` to CliCommand interface
- Add `resolvePreNav()` helper in execution.ts (replaces hardcoded check)
- All 14 boss adapters declare `navigateBefore: false`
- Wire through discovery.ts (YAML + manifest) and build-manifest.ts
* refactor(boss): extract common utilities, fix missing login detection
- Add src/clis/boss/common.ts with shared helpers:
- bossFetch(): unified XHR template with auto cookie-expiry detection (code 7/37)
- navigateToChat()/navigateTo(): page navigation helpers
- checkAuth()/assertOk(): centralized login state validation
- fetchFriendList()/fetchRecommendList()/findFriendByUid(): data queries
- clickCandidateInList()/typeAndSendMessage(): UI automation helpers
- verbose(): conditional debug logging
- Refactor all 14 boss adapters to use common.ts:
- chatlist.ts: was missing cookie-expiry check (fixes #login-detect)
- chatmsg.ts: was missing cookie-expiry check (fixes #login-detect)
- Remaining 12 adapters: deduplicated XHR boilerplate and error handling
- Fix execution.ts: skip redundant pre-navigation for TS adapters
- TS adapters handle their own goto(), pre-navigating caused double
page loads and could trigger duplicate login prompts
- Pre-navigation preserved for YAML pipeline commands that need it
Net reduction: ~730 lines of duplicated code across boss adapters.
All 244 unit tests pass.
* fix(review): fix execution.ts pre-nav regression, sanitize UID input, restore docs
- execution.ts: use site-specific skip (boss only) instead of isYamlPipeline.
The original check skipped pre-navigation for ALL TS adapters, but weread,
chaoxing, and others don't do their own goto() and depend on it.
- common.ts: sanitize numericUid to digits-only and use JSON.stringify for
safe interpolation in page.evaluate() (prevents template literal injection).
- resume.ts: restore HTML structure doc comments (scraping selector guide).
- send.ts: restore MQTT architecture note (explains why UI automation is needed).
* fix: restore DEBUG env support in verbose(), improve skipPreNav comment
- verbose() now checks both OPENCLI_VERBOSE and DEBUG=opencli,
matching the original behavior from search.ts and detail.ts
- Clarify skipPreNav comment with TODO for future adapter-level flag
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(instagram,facebook): add write actions and extended commands
Instagram write actions (7 commands, internal REST API + CSRF token):
- like/unlike: like or unlike a user's post by username + index
- comment: comment on a user's post
- save/unsave: bookmark or remove bookmark on a post
- follow/unfollow: follow or unfollow a user
Facebook extended commands (6 commands, DOM scraping):
- friends: friend suggestions list
- groups: list your joined groups with last post time
- memories: On This Day memories
- events: browse event categories
- add-friend: send friend request by username
- join-group: join a group by ID
All commands tested with live data. 258 existing tests pass.
* docs: add adapter documentation for instagram, facebook, lobsters
* docs: add missing medium adapter documentation
* fix(extension): skip chrome-extension:// tabs in resolveTabId fallback
Remove the unsafe fallback that returned `tabs[0]` regardless of URL
type. When no web-accessible tab exists in the automation window (e.g.
a New Tab Override extension replaced about:blank with its own
chrome-extension:// page), we now always create a fresh about:blank
tab instead. This prevents chrome.debugger.attach from failing with
"Cannot access a chrome-extension:// URL of different extension".
Fixes#195, fixes#197
* refactor(extension): rename isWebUrl → isDebuggableUrl & reuse tabs in resolveTabId
Improvements over the original fix:
1. Rename isWebUrl() → isDebuggableUrl(): better reflects the intent —
the function determines whether a URL can be attached via CDP, not
just whether it's a "web" URL (about:blank is debuggable but not
really a web URL).
2. Reuse existing non-debuggable tabs: when a New Tab Override extension
replaces about:blank with chrome-extension://, use chrome.tabs.update()
to navigate the existing tab to about:blank instead of creating a new
one. This prevents orphan tab accumulation since chrome.tabs.create()
may also get intercepted by the same extension.
3. Only fall back to chrome.tabs.create() when the window has zero tabs,
which is the truly empty-window edge case.
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(tiktok): add TikTok adapter with 15 commands
TikTok (15 commands, browser mode):
Read commands:
- profile: user profile info via rehydration script parsing
- search: search videos via internal search API
- explore: trending videos from explore page (DOM scraping)
- user: recent videos from a user page (DOM scraping)
- following: list accounts you follow
- friends: friend suggestions
- live: browse live streams with viewer counts
- notifications: activity notifications
Write commands (verified with real interactions):
- like/unlike: like or unlike a video by URL
- save/unsave: add or remove video from Favorites
- follow/unfollow: follow or unfollow a user
- comment: comment on a video
All write operations verified with live TikTok interactions.
* docs: add missing adapter documentation for doc-coverage CI
* feat(lobsters): add Lobste.rs adapter with hot, newest, active, tag commands
Add public API adapter for Lobste.rs (lobste.rs), a developer-focused
link aggregation community. All commands use the public JSON API and
require no authentication or browser.
Commands:
- hot: hottest stories
- newest: latest stories
- active: most active discussions
- tag: filter stories by tag (e.g. rust, security, programming)
* feat(instagram,facebook): add Instagram and Facebook adapters
Instagram (7 commands, browser mode - internal REST API):
- profile: user profile info (followers, following, posts, bio)
- search: search users
- user: recent posts from a user
- followers: list user's followers
- following: list user's following
- saved: saved posts
- explore: discover trending posts
Facebook (4 commands, browser mode - DOM scraping):
- profile: user/page profile info
- notifications: recent notifications
- feed: news feed posts
- search: search people, pages, posts
All commands require Chrome to be logged in to the respective site.
Instagram uses stable internal API endpoints with cookie auth.
Facebook uses DOM scraping via role attributes and semantic selectors.
* feat: plugin system (Stage 0-2)
- Stage 0: discoverPlugins() scans ~/.opencli/plugins/ at startup
- Stage 1: demo plugin repos (github-trending, hot-digest)
- Stage 2: opencli plugin install/uninstall/list commands
- package.json exports ./registry for TS plugin peerDep support
- 17 new/updated tests, tsc --noEmit clean
* fix: CDPBridge connect timeout unit mismatch (seconds vs ms)
opts.timeout is passed in seconds from runtime.ts but CDPBridge
was using it as milliseconds, causing instant timeout (30ms).
* feat: add registry-api public entry point for TS plugin peerDep support
- Add src/registry-api.ts: re-exports core registration API (cli, Strategy,
getRegistry) without transitive side-effects, safe for plugin imports
- Update package.json exports: './registry' -> './dist/registry-api.js'
- Update src/registry.ts: use globalThis shared registry to ensure single
instance across npm-linked plugin modules
- Update .gitignore for plugin-related artifacts
* fix: symlink host opencli into plugin node_modules on install
After npm install, replace the npm-installed @jackwener/opencli
with a symlink to the running host's package root. This ensures
TS plugins always resolve '@jackwener/opencli/registry' against
the host installation, avoiding version mismatches when the
published npm package lags behind.
* fix: transpile TS plugins to JS on install, deduplicate .ts/.js discovery
- installPlugin: after symlinking host opencli, transpile any .ts files
to .js using esbuild from the host's node_modules/.bin/
- discoverPluginDir: skip .ts files when a .js sibling exists (production
node cannot load .ts directly)
- scanPluginCommands: deduplicate basenames via Set to avoid showing
'aggregate, aggregate' when both .ts and .js exist
* docs: add plugin system user guide
- New docs/guide/plugins.md covering:
- Installation/uninstallation commands
- Creating YAML plugins (zero-dep)
- Creating TS plugins (with peerDep)
- TS plugin install lifecycle (clone → deps → symlink → transpile)
- Example plugins and troubleshooting
- Add Plugins to VitePress sidebar (EN + ZH)
- Link from getting-started.md Next Steps
* fix: address review issues in plugin system
- Security: replace execSync with execFileSync to prevent shell injection
- Replace deprecated npm --production with --omit=dev
- Tighten parseSource regex to [\w.-]+ to reject special chars
- Fix ZH sidebar plugin link (/guide/plugins → /zh/guide/plugins)
- Return plugin name from installPlugin() to avoid duplicated logic
- Use execFileSync for esbuild transpilation
- Fix misleading comment in linkHostOpencli
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* 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)
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
* 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
* 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.