Root cause: getAutomationWindow and resolveTabId used about:blank which
New Tab Override extensions intercept immediately, replacing it with
chrome-extension:// URLs that cannot be debugged.
Changes:
- Window creation: about:blank → data:text/html
- reuseTab fallback: about:blank → data:text/html
- newTab handler: about:blank → data:text/html
- Added diagnostic logging to resolveTabId for debugging
- Synced extension version to 1.2.4
Ref: #249
resolveTabId's reuseTab path now verifies the URL is actually debuggable
after navigating to about:blank. If a New Tab Override extension intercepts
it (setting it back to chrome-extension://), falls back to a data: URI,
then creates a fresh tab as last resort.
This fixes the persistent 'attach failed: Cannot access chrome-extension://'
error for users with New Tab Override extensions installed.
Ref: #249
- resolveTabId: validate URL even for explicit tabId, fall through to
auto-resolve when tab is not debuggable or has been closed
- handleNavigate: wait for URL change before checking 'complete' status
to avoid race condition with stale about:blank
- ensureAttached: pre-check tab URL, verify cached attach with probe,
invalidate cache on URL change via onUpdated listener
- daemon-client: recognize transient extension errors (disconnected,
attach failed) as retryable with 1500ms delay; fresh command ID per attempt
- pipeline executor: add per-step retry for browser steps (up to 2 retries
on transient errors); cleanup automation window on pipeline failure
- page.ts: selectTab/newTab/closeTab properly update/invalidate _tabId
- daemon.ts: add WebSocket ping/pong heartbeat (15s interval, 2-miss disconnect)
- Increase automation window idle timeout from 30s to 120s
- Fix timeout param edge cases in BrowserBridge._ensureDaemon
- Remove unused chalk import; fix trailing import placement
Closes#249
* feat(douban): add movie adapter with search, top250, subject, marks, reviews commands
- search: search movies by keyword
- top250: get top 250 movies
- subject: get movie details by id
- marks: export personal viewing marks
- reviews: export personal movie reviews
* review: resolve douban adapter blockers
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(devto): add devto adapter
* refactor(devto): improve adapters to match project conventions
- Make tag/username args positional for natural CLI usage:
opencli devto tag javascript (instead of --tag javascript)
opencli devto user ben (instead of --username ben)
- Add rank field (index + 1) matching hackernews/lobsters pattern
- Add tags field from tag_list for richer output
- Remove redundant author column from user command (already filtering by user)
- Use type: str (project convention) instead of type: string
- Increase default limit from 10 to 20 (matching other adapters)
- Update docs with positional arg examples
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(wikipedia): fix search arg name + add random and trending commands
- fix: search.ts referenced `args.keyword` but the argument is defined
as `query`, causing the search term to always be undefined
- feat: add `random` command (random article summary via REST API)
- feat: add `trending` command (most-read articles, yesterday's data)
All commands are PUBLIC strategy, no browser required, reuse wikiFetch.
* refactor(wikipedia): extract shared types + add docs for random/trending
- Extract WikiSummary, WikiMostReadArticle types to utils.ts
- Extract EXTRACT_MAX_LEN/DESC_MAX_LEN constants
- Add formatSummaryRow() helper to eliminate duplicate mapping in
summary.ts and random.ts
- Update docs with random and trending command examples
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
Support switching between For You (algorithmic) and Following
(chronological) timelines via `--type for-you|following`.
Both endpoints share the same response structure; only the GraphQL
endpoint name and queryId differ. QueryId is resolved dynamically
from fa0311/twitter-openapi with a hardcoded fallback, and validated
against /^[A-Za-z0-9_-]+$/ to prevent injection from upstream.
* fix(doctor): refresh status after live check to resolve#121
* refactor: reorder live check before status read for natural consistency
Instead of calling checkDaemonStatus() twice (before and after the
connectivity check), reorder so that the live connectivity check runs
first, then read daemon status only once. This:
- Eliminates redundant checkDaemonStatus() call
- Naturally avoids the timing inconsistency (fixes#121)
- Also fixes the sessions query using stale status
- Simplifies test assertions to avoid over-coupling to exact wording
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* 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