* docs: add dingtalk and wecom CLI to external CLI hub
Add dingtalk-workspace-cli and wecom-cli as external CLI integrations
alongside lark-cli, gh, docker, etc.
* feat: register dingtalk and wecom as external CLIs
Add dws (DingTalk Workspace CLI) and wecom-cli to
external-clis.yaml so they are discoverable via opencli list
and auto-installable.
* feat(browser): add ONES adapter support for tasks and worklog commands
Add ONES auth/session commands, task listing/details utilities, and worklog operations, with related docs and helper utilities.
* fix(ones): harden worklog and task-list adapter behavior
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(tieba): add browser adapters for hot posts search and read
* fix(tieba): stabilize search and e2e coverage
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(doubao): add history, detail, meeting-summary and meeting-transcript commands
- history: list conversation history from sidebar
- detail: read a specific conversation by ID, with meeting card detection
- meeting-summary: extract summary and AI chapters from meeting minutes
- meeting-transcript: read or download meeting transcript via browser
Made-with: Cursor
* docs: update doubao command list in adapter index and README.zh-CN
Made-with: Cursor
* fix(doubao): handle meeting-only detail and merge transcript snapshots
* refactor(doubao): model conversation ids as first-class output
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(douyin): add user-videos command with top-10 comments
Adds a new adapter for fetching a public user's video list by sec_uid,
alongside the top-10 hottest comments for each video.
- Navigates to the user's profile page to establish a cookie session
- Fetches video list via /aweme/v1/web/aweme/post/
- Concurrently fetches top-10 comments per video via
/aweme/v1/web/comment/list/ (sorted by hotness, API default)
Output columns: index, aweme_id, title, duration, digg_count,
play_url, top_comments
* refactor(douyin): replace Object.assign with spread in user-videos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(douyin): validate user-videos inputs
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(xiaohongshu): add cover image URL to user notes output
Extract cover image URL from noteCard.cover.urlDefault in
__INITIAL_STATE__ and include it in the user command output columns.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(xiaohongshu): cover user note rows
* refactor(xiaohongshu): keep cover out of default columns
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(spotify): add Spotify playback adapter
Adds a new adapter for controlling Spotify via the official Web API.
Uses Strategy.PUBLIC with OAuth2 — no browser session required.
Commands: auth, status, play, pause, next, prev, volume, search, queue, shuffle, repeat.
Credentials are loaded from ~/.opencli/spotify.env or environment variables.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(spotify): rename index.ts → spotify.ts and fix CliError calls
- Renamed src/clis/spotify/index.ts to spotify.ts so the build-manifest
picks it up (index.js is intentionally excluded from manifest scanning)
- Fixed 4 CliError calls: constructor now requires (code, message, hint?)
so each throw now passes an appropriate error code as first argument
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(spotify): fix token refresh corruption, env parse, null guards, validation
- refreshAccessToken: check res.ok before parsing; construct Tokens object
directly instead of mutating loadTokens() result to avoid writing
undefined/NaN on Spotify error responses; preserve existing refresh_token
when Spotify omits it from the response
- loadEnv: split on first '=' only so values containing '=' are preserved
- SCOPES: remove write/library/top scopes not used by any command
- status: guard against data.item being null (active device but no track)
- volume: validate 0-100 range before API call
- auth: check tokenRes.ok on initial token exchange; add server.on('error')
handler for EADDRINUSE; add 5-minute timeout with clearTimeout on close
* feat(postinstall): auto-create ~/.opencli/spotify.env template on install
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(spotify): guard null progress, podcast items, missing tracks data, corrupted tokens, invalid search limit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(spotify): improve missing credentials error with step-by-step guidance
* fix(spotify): harden setup and add docs coverage
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(band): add bands, posts, and mentions commands for band.us
- bands: lists all Bands via get_band_list_with_filter intercept
- posts: lists posts from a Band via get_posts_and_announcements intercept
- mentions: shows @mention notifications via get_news intercept
All use Strategy.INTERCEPT since band.us API requires an HMAC md header
generated by its own JS. SPA navigation to /band/{no}/post triggers the
band list and posts APIs; bell + @メンション tab click triggers mentions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(band): clean up all three band adapters
- Fix doc comments: Band uses XHR not fetch; clarify INTERCEPT rationale
- bands: replace for-loop with flatMap; explain why band page nav is needed
- posts: remove item.post ?? item fallback (API always wraps in post); rename
finalRequests → requests for consistency; extract stripBandTags helper
- mentions: remove redundant ?? defaults (args have defaults defined); fix
unreadOnly bug (was not applied to post/comment modes); consolidate Band tag
stripping to single regex; cast kwargs types directly instead of converting;
add comments explaining last-response strategy and 'referred' filter flag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band/posts): handle mixed post/announcement items from API
get_posts_and_announcements returns both regular posts and announcements
that have different shapes — some lack post_no and wrap differently.
Restore item.post ?? item fallback and filter out items with no resolvable
identifier to prevent undefined in URLs and empty rows in output.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(band): add post command — full post export with comments and photo download
Exports the complete content of a single Band post:
- Post body (with Band markup tags stripped)
- All comments in chronological order
- Photo URLs shown inline, or downloaded with --output <dir>
Uses Strategy.INTERCEPT with a broad 'band.us' pattern to capture both the
batch request (embedding get_post) and get_comments in one SPA navigation.
Responses are identified client-side by shape: batch_result array vs items
array with comment_id fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(band): replace XHR interception with direct DOM extraction
- bands, posts, post: navigate directly to target URL instead of home→SPA detour
- All three switch from Strategy.INTERCEPT to Strategy.COOKIE with navigateBefore: false
(bands uses framework pre-nav to home; posts/post disable it and goto target directly)
- DOM extraction polls for specific content elements rather than fixed waits
- post: confirm selectors via browser inspection (a.text, time.time, .sCommentList,
.sReplyList for nested replies); add --comments flag to skip comment fetch
- posts: extract from rendered post list DOM; correct comment item selector (div.cComment)
- Fix: post empty-result guard changed from && to handle null data safely
- Fix: photo download now checks HTTP status code before piping to avoid writing
redirect HTML into image files
- Fix: mentions unread client-side filter skipped for 'mentioned' mode since
server already filtered via 未確認のみ button click
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): address code review feedback
- post: replace manual http/https download with shared downloadMedia utility
(handles redirects, timeouts, stream errors correctly)
- post: fix photo URL resolution to use location.href as base, handling
protocol-relative and relative URLs without throwing
- post: switch to node:-prefixed imports per repo convention
- post/posts: remove redundant ArgumentError guards — framework already
validates required args before func() is called
- mentions: INTERCEPT strategy is intentional (Band HMAC prevents DOM-only
approach for notifications; update PR description to clarify)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): address second round of code review feedback
- bands: tighten href selector to /band/{id}(?:/post)?$ so feed/post-detail
links are excluded; only sidebar navigation links match
- mentions: replace fixed page.wait(2) sleeps with polling on
getInterceptedRequests() — waits up to 8 s per action, exits as soon
as the expected number of captures arrives (avoids flakiness on slow XHR)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): fix selector bugs found during testing
- bands: use a.bandCover._link + p.uriText + span.member em selectors
(previous a[href*="/band/"] + .bandName combo leaked "メンバー" text)
- posts: use article.cContentsCard._postMainWrap + span.count selectors
(previous li._postListItem selector matched nothing; DOM changed)
- mentions: fix page.wait(500) → page.wait(0.5) (was waiting 500s not ms);
use timestamp-suffixed URL to force fresh page load each run so the
notification panel is closed; fix get_news vs get_news_count capture
ambiguity with result_data.news check; replace cumulative waitForCaptures
with waitForOneCapture (getInterceptedRequests clears array on each call)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band/mentions): use CSS class selector for bell button instead of locale-dependent text match
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): address third round of code review feedback
- post: pass browser cookies to downloadMedia so Band's login-protected
photo URLs don't fail with 401/403
- post: include photos.length in empty-result guard so photo-only posts
are not falsely reported as not found
- mentions: accumulate captures across poll iterations so get_news_count
responses don't cause early exit before the real get_news arrives
- mentions: update docstring to match actual implementation (client-side
filtering, no tab-click)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): address fourth round of code review feedback
- mentions: fail fast with a clear error when bell button is not found,
instead of silently no-op and waiting 8s before EmptyResultError
- post: use shared formatCookieHeader() instead of manual cookie string
construction
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): address fifth round of code review feedback
- mentions: replace fixed page.wait(2) with polling for bell button
readiness (up to 10s), eliminating the fixed sleep and fail-fast
when the selector is missing
- mentions: add explicit !newsReq guard with a clear error message when
get_news capture times out, instead of falling through to a misleading
"No notifications found"
- posts: skip posts with no permalink href instead of emitting a bogus
'https://www.band.us' URL
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): address sixth round of code review feedback
- post: only send Band cookies to *.band.us photo URLs; third-party CDN
URLs are downloaded without cookies to avoid cross-domain cookie leakage
- bands: strip non-digit chars before parseInt so member counts like
"1,234" parse correctly
- posts: same fix for comment counts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): address seventh round of code review feedback
- posts: check limit before push so --limit 0 returns empty result
- post: indent replies proportionally by depth (' '.repeat(depth))
so multi-level threads remain readable in table output
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band/bands): anchor href regex to prevent matching post-detail URLs
Pattern now requires /band/{id} or /band/{id}/post (with optional trailing
slash) so deeper paths like /band/{id}/post/{postNo} are excluded.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): address ninth round of code review feedback
- mentions: guard bell click with a boolean return so a disappearing
element throws a clear EmptyResultError instead of a raw TypeError
- post: wait for comment list container instead of first .cComment so
posts with zero comments don't incur a fixed 6s delay
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): use page.getCookies() for login detection across all commands
Replaces document.cookie.includes('band_session') with
page.getCookies({ domain: 'band.us' }) so login detection works even
if Band.us marks the session cookie as HttpOnly in the future.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): address eleventh round of code review feedback
- mentions: replace EmptyResultError with SelectorError for missing/
disappeared bell button — produces a clearer SELECTOR error code
- post: assign per-photo filenames using a global index across both
download batches so band-hosted and CDN photos don't overwrite each other
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band): address twelfth round of code review feedback
- post: derive file extension from URL path and include in filename
(e.g. photo_1.jpg) so downloaded photos have correct extensions
- posts: remove dead code guard (!url && !content) — url is always
non-empty here since href-empty posts are already skipped above
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(band/post): use url-scoped getCookies for photo download auth
Domain-scoped getCookies may omit host-only cookies scoped to www.band.us;
using url: 'https://www.band.us' ensures all relevant cookies are included
in the auth header for Band-hosted photo downloads.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(band): add adapter documentation and sidebar entry
Required by CI doc-check --strict: every adapter in src/clis/ must have
a corresponding docs/adapters/browser/*.md file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(e2e): wire band auth coverage into default matrix
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(xiaohongshu): use CDP DOM.setFileInputFiles for image upload
Replace base64 DataTransfer injection with CDP DOM.setFileInputFiles,
which lets Chrome read image files directly from the local filesystem.
This eliminates payload size limits that caused "fetch failed" errors
when uploading large images (>500KB) through the browser bridge.
Changes:
- Add 'set-file-input' action to protocol, extension handler, and CDP executor
- Add Page.setFileInput() method for CLI-side usage
- Rewrite publish image upload to use CDP path, with base64 fallback
for older extension versions that don't support the new action
- Add clear warning when falling back to base64 with large payloads
Closes#542 (partially — image upload reliability)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: cover cdp file input upload path
* fix: keep image upload on image-only inputs
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat: add 知识星球(zsxq) site adapter
Add cookie-based adapter for 知识星球 (zsxq.com) with 5 commands:
- groups: list joined groups
- topics: list topics in current group
- topic: get single topic detail with comments
- search: search topics within a group
- dynamics: latest cross-group activity feed
Uses XHR over Chrome extension (Strategy.COOKIE) to call
https://api.zsxq.com/v2/ APIs with credential forwarding.
* fix(zsxq): map missing topics to not found
* refactor(zsxq): preserve detail response semantics
---------
Co-authored-by: xiaojian <xiaojian@xiaojiandeMac-mini.local>
Co-authored-by: jackwener <jakevingoo@gmail.com>
Argument/usage errors now correctly exit with code 2 (EX_USAGE) since
the exit-codes feature landed. Update the two affected E2E assertions:
- unknown command → 2 (usage error, not generic failure)
- plugin update without args → 2 (ArgumentError)
* feat(exit-codes): add Unix-standard exit codes to all CliError types
Introduce EXIT_CODES constant table (sysexits.h conventions) and wire
exitCode into every CliError subclass so the process exit code reflects
the semantic type of failure:
0 success (default)
1 generic / unexpected error
2 argument / usage error (ArgumentError)
66 empty result / not found (EmptyResultError, SelectorError)
69 service unavailable (BrowserConnectError, AdapterLoadError)
77 permission / auth required (AuthRequiredError)
78 configuration error (ConfigError)
124 timeout (TimeoutError)
130 Ctrl-C / SIGINT (unchanged, tui.ts)
resolveExitCode() in commanderAdapter.ts reads err.exitCode for typed
CliErrors, and falls back to pattern-matching message text for untyped
adapter errors (auth pattern → 77, not-found pattern → 66, else → 1).
Shell scripts can now distinguish error categories:
opencli spotify status || echo "exit $?" # 69 if browser not running
opencli github issues --repo x 2>/dev/null; [ $? -eq 77 ] && opencli github auth
* fix(exit-codes): address review findings
- TIMEOUT: change from 124 → 75 (EX_TEMPFAIL); 124 is bash timeout(1)'s
own exit code, creating ambiguity when shell runs `timeout 30 opencli`
- SelectorError: change from EMPTY_RESULT(66) → GENERIC_ERROR(1); a
missing DOM selector is an adapter bug, not a user "no data" condition
- normalizeArgValue: throw ArgumentError instead of bare CliError so
invalid bool args correctly exit with USAGE_ERROR(2) not GENERIC_ERROR(1)
- resolveExitCode: explicitly map 'http' classification to GENERIC_ERROR
to keep exit-code path in sync with the render path
- tui.ts: replace hardcoded process.exit(130) with EXIT_CODES.INTERRUPTED
* feat(exit-codes): replace all hardcoded exit numbers with EXIT_CODES constants
Extend the exit code system to cover every process exit point in the codebase.
No magic numbers remain — all exit codes are now referenced by name.
Semantic upgrades beyond pure renaming:
- plugin update missing args → USAGE_ERROR (2) instead of 1
- plugin update conflicting → USAGE_ERROR (2) instead of 1
- opencli install <unknown> → USAGE_ERROR (2) instead of 1
- unknown command fallback → USAGE_ERROR (2) instead of 1
- record with no candidates → EMPTY_RESULT (66) instead of 1
- external CLI install fail → SERVICE_UNAVAIL (69) instead of 1
- daemon EADDRINUSE → SERVICE_UNAVAIL (69) instead of 1
Files touched: cli.ts, external.ts, daemon.ts, main.ts,
clis/antigravity/serve.ts
* feat(sinafinance): rewrite stock as public API adapter
Replace browser-based DOM scraping with direct Sina public APIs:
suggest3.sinajs.cn — symbol search (GBK, no auth)
hq.sinajs.cn — real-time quote (GBK, no auth)
Strategy.PUBLIC, browser: false — no Chrome or login required.
Supports A股 (sh/sz), 港股 (hk prefix), 美股 (gb_ prefix).
US MarketCap parsed from hq field [12]; formatted as T/B/M.
* feat(exit-codes): add Unix-standard exit codes to all CliError types
Introduce EXIT_CODES constant table (sysexits.h conventions) and wire
exitCode into every CliError subclass so the process exit code reflects
the semantic type of failure:
0 success (default)
1 generic / unexpected error
2 argument / usage error (ArgumentError)
66 empty result / not found (EmptyResultError, SelectorError)
69 service unavailable (BrowserConnectError, AdapterLoadError)
77 permission / auth required (AuthRequiredError)
78 configuration error (ConfigError)
124 timeout (TimeoutError)
130 Ctrl-C / SIGINT (unchanged, tui.ts)
resolveExitCode() in commanderAdapter.ts reads err.exitCode for typed
CliErrors, and falls back to pattern-matching message text for untyped
adapter errors (auth pattern → 77, not-found pattern → 66, else → 1).
Shell scripts can now distinguish error categories:
opencli spotify status || echo "exit $?" # 69 if browser not running
opencli github issues --repo x 2>/dev/null; [ $? -eq 77 ] && opencli github auth
* review: regex escape sym, fix change precision, optimize suggest type param
* fix: remove invalid `state: 'normal'` from chrome.windows.create()
Chrome 146+ rejects 'normal' as an invalid value for the `state` parameter
in chrome.windows.create(). This causes the error:
Error: Invalid value for state
Root cause analysis:
- The Chrome Extensions API documentation states that `state` parameter
only accepts 'minimized', 'maximized', and 'fullscreen' as input values
- While WindowState enum includes 'normal', it's meant for reading window
state, not for setting it during creation
- Chrome 146 enforces stricter validation on the `state` parameter
- When `state` is omitted, the window defaults to 'normal' state anyway
Fix: Remove the `state: 'normal'` parameter entirely. The window will
default to normal state without explicitly setting it.
Tested: `opencli doctor` and `opencli bilibili hot` now work correctly
on Chrome 146.0.7680.165.
* build: rebuild dist after removing state: 'normal'
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(extension): probe daemon via HTTP before WebSocket to eliminate console noise
When the daemon is offline, `new WebSocket()` logs uncatchable
ERR_CONNECTION_REFUSED errors to Chrome's extension error page.
Add `probeAndConnect()` that checks daemon reachability with a
silent `fetch(HEAD)` before attempting WebSocket connection.
All three auto-connect paths (initialize, keepalive alarm, eager
reconnect) now go through the probe, eliminating the error noise
entirely.
Closes#505
* refactor(extension): inline probe into connect(), add /ping to daemon
Instead of a separate probeAndConnect() wrapper that all call sites had
to remember to use, bake the HTTP probe directly into connect() itself.
This makes the guard impossible to accidentally skip when adding new
connection paths in the future.
Also adds a dedicated GET /ping endpoint to the daemon (no X-OpenCLI
header required) so the probe has a clear semantic contract instead of
relying on a 403 side-effect from the root path.
- daemon: GET /ping → 200 {ok:true}, no auth needed, placed before the
X-OpenCLI header check; only chrome-extension:// and no-origin
requests reach it (origin check is still enforced above)
- background: connect() is now async; probes /ping with a 1 s timeout
before new WebSocket(); all call sites (initialize, keepalive alarm,
scheduleReconnect) remain unchanged
- probeAndConnect() removed — no longer needed
* fix(extension/daemon): address review feedback on probe refactor
- protocol.ts: replace DAEMON_HTTP_URL with DAEMON_PING_URL (clearer
semantics, single source of truth for the health-check URL)
- background.ts: import DAEMON_PING_URL from protocol instead of
defining a local constant; check res.ok so an unexpected non-200
response doesn't fall through to WebSocket; annotate all fire-and-
forget connect() call sites with `void` to make intent explicit
- daemon.ts: add security comment on /ping documenting the timing
side-channel tradeoff (loopback-only, accepted risk)
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* chore(release): 1.5.2
* test(e2e): stabilize output format checks
* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)
* docs: add perf smart-wait implementation plan
* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers
* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage
* feat(perf): implement waitForCapture() and wait({ selector }) in Page
* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage
* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests
* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters
* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]
* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters
* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog
* fix(types): add waitForCapture to IPage mock helpers in tests
* docs: simplify README to 50-line overview with docs link
Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.
* fix(perf): CDPPage smart wait + MutationObserver selector wait
- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context
* docs: move built-in commands table to docs/adapters/index.md
Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.
* docs: move Quick Start before Prerequisites, tone down Electron promo copy
- Reorder sections: Why opencli → Quick Start → Prerequisites
so users see "how to use" before "what you need"
- Replace "The Most Powerful Update Has Arrived!" marketing copy
with a plain one-liner description of the Electron feature
- Remove redundant Bun dev/test commands from Prerequisites
(developer-only content, not relevant to end users)
- Add "(requires Extension)" hint to browser command in Quick Start
* docs: polish Quick Start — one-line source install, Verify setup section
* docs: show 4 sample adapters in Built-in Commands with link to full list
* docs: polish README — Try it out under Verify setup, trim examples, CLI Hub as top-level section
* docs: add CLI Hub intro line in header, restore auto-install note in CLI Hub section
* chore(release): 1.5.2
* test(e2e): stabilize output format checks
* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)
* docs: add perf smart-wait implementation plan
* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers
* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage
* feat(perf): implement waitForCapture() and wait({ selector }) in Page
* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage
* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests
* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters
* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]
* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters
* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog
* fix(types): add waitForCapture to IPage mock helpers in tests
* docs: simplify README to 50-line overview with docs link
Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.
* fix(perf): CDPPage smart wait + MutationObserver selector wait
- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context
* docs: move built-in commands table to docs/adapters/index.md
Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.
* docs: move Quick Start before Prerequisites, tone down Electron promo copy
- Reorder sections: Why opencli → Quick Start → Prerequisites
so users see "how to use" before "what you need"
- Replace "The Most Powerful Update Has Arrived!" marketing copy
with a plain one-liner description of the Electron feature
- Remove redundant Bun dev/test commands from Prerequisites
(developer-only content, not relevant to end users)
- Add "(requires Extension)" hint to browser command in Quick Start
* docs: polish Quick Start — one-line source install, Verify setup section
* docs: show 4 sample adapters in Built-in Commands with link to full list
* docs: polish README — Try it out under Verify setup, trim examples, CLI Hub as top-level section
* chore(release): 1.5.2
* test(e2e): stabilize output format checks
* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)
* docs: add perf smart-wait implementation plan
* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers
* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage
* feat(perf): implement waitForCapture() and wait({ selector }) in Page
* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage
* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests
* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters
* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]
* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters
* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog
* fix(types): add waitForCapture to IPage mock helpers in tests
* docs: simplify README to 50-line overview with docs link
Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.
* fix(perf): CDPPage smart wait + MutationObserver selector wait
- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context
* docs: move built-in commands table to docs/adapters/index.md
Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.
* docs: move Quick Start before Prerequisites, tone down Electron promo copy
- Reorder sections: Why opencli → Quick Start → Prerequisites
so users see "how to use" before "what you need"
- Replace "The Most Powerful Update Has Arrived!" marketing copy
with a plain one-liner description of the Electron feature
- Remove redundant Bun dev/test commands from Prerequisites
(developer-only content, not relevant to end users)
- Add "(requires Extension)" hint to browser command in Quick Start
* docs: polish Quick Start — one-line source install, Verify setup section
* docs: show 4 sample adapters in Built-in Commands with link to full list
* chore(release): 1.5.2
* test(e2e): stabilize output format checks
* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)
* docs: add perf smart-wait implementation plan
* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers
* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage
* feat(perf): implement waitForCapture() and wait({ selector }) in Page
* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage
* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests
* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters
* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]
* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters
* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog
* fix(types): add waitForCapture to IPage mock helpers in tests
* docs: simplify README to 50-line overview with docs link
Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.
* fix(perf): CDPPage smart wait + MutationObserver selector wait
- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context
* docs: move built-in commands table to docs/adapters/index.md
Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.
* docs: move Quick Start before Prerequisites, tone down Electron promo copy
- Reorder sections: Why opencli → Quick Start → Prerequisites
so users see "how to use" before "what you need"
- Replace "The Most Powerful Update Has Arrived!" marketing copy
with a plain one-liner description of the Electron feature
- Remove redundant Bun dev/test commands from Prerequisites
(developer-only content, not relevant to end users)
- Add "(requires Extension)" hint to browser command in Quick Start
* docs: polish Quick Start — one-line source install, Verify setup section
* chore(release): 1.5.2
* test(e2e): stabilize output format checks
* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)
* docs: add perf smart-wait implementation plan
* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers
* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage
* feat(perf): implement waitForCapture() and wait({ selector }) in Page
* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage
* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests
* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters
* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]
* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters
* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog
* fix(types): add waitForCapture to IPage mock helpers in tests
* docs: simplify README to 50-line overview with docs link
Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.
* fix(perf): CDPPage smart wait + MutationObserver selector wait
- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context
* docs: move built-in commands table to docs/adapters/index.md
Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.
* docs: move Quick Start before Prerequisites, tone down Electron promo copy
- Reorder sections: Why opencli → Quick Start → Prerequisites
so users see "how to use" before "what you need"
- Replace "The Most Powerful Update Has Arrived!" marketing copy
with a plain one-liner description of the Electron feature
- Remove redundant Bun dev/test commands from Prerequisites
(developer-only content, not relevant to end users)
- Add "(requires Extension)" hint to browser command in Quick Start
* chore(release): 1.5.2
* test(e2e): stabilize output format checks
* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)
* docs: add perf smart-wait implementation plan
* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers
* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage
* feat(perf): implement waitForCapture() and wait({ selector }) in Page
* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage
* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests
* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters
* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]
* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters
* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog
* fix(types): add waitForCapture to IPage mock helpers in tests
* docs: simplify README to 50-line overview with docs link
Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.
* fix(perf): CDPPage smart wait + MutationObserver selector wait
- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context
* docs: move built-in commands table to docs/adapters/index.md
Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.
chrome.windows.create rejects state:'minimized' when combined with
width/height (Chrome API constraint). Revert to state:'normal' to fix
the "Invalid value for state" error. The 30s idle timeout from #521
is preserved.
Fixes#526
* refactor: slim CI matrix, extract shared utils, unify logging, remove __test__ from public API
- CI: unit-test uses dynamic matrix (PR=ubuntu+22 only, push=full 3OS×2Node);
adapter-test reduced to ubuntu-latest (OS doesn't affect pure unit tests)
- _shared/common.ts: add sleep() and clampToRange() shared adapter utilities;
douban/utils.ts and sinablog/utils.ts now use clampToRange instead of duplicate clampLimit
- browser/daemon-client.ts: replace inline setTimeout Promise with local sleep()
- execution.ts: replace conditional console.error with log.debug
- browser/index.ts: remove __test__ from public barrel export;
browser.test.ts now imports internal helpers directly from source files
* fix: remove unused afterEach import, fix schedule/dispatch CI matrix, clarify clampToRange docs
* refactor: move sleep to src/utils.ts, simplify clamp signature to match lodash convention
* perf: smart pre-navigation — skip redundant domain nav + remove hardcoded 2s wait
- Add `getCurrentUrl()` to IPage, Page, and CDPPage to check current browser URL
- Skip pre-navigation entirely if the browser is already on the target domain
- Remove the hardcoded `page.wait(2)` after pre-navigation — `page.goto()` already
includes smart DOM-settle detection via `waitForDomStable`, making the fixed
2-second sleep redundant
- Saves ~2s per browser command in the common case (consecutive commands on the
same site), and ~1-2s even on cold navigation
* perf: smart page.wait() — DOM-stable early return for waits >= 1s
For page.wait(N) where N >= 1 second, use DOM MutationObserver-based
stability detection instead of a fixed sleep. The original wait time
becomes a hard cap, but the call returns as soon as the DOM stops
mutating (500ms quiet period).
This benefits ~200 hardcoded sleep calls across ~40 adapters without
changing any adapter code. A typical page.wait(5) now completes in
<1s when the page is already stable, instead of always waiting 5s.
Short waits (< 1s) are kept as fixed sleeps — these are typically
UI animation delays or anti-bot throttling where DOM-ready is irrelevant.
* refactor: getCurrentUrl() uses in-memory tracking instead of round-trip
Replace the sendCommand('exec', 'window.location.href') call with a
local _lastUrl field set during goto(). This eliminates a daemon HTTP
round-trip for the domain check, making isAlreadyOnDomain() zero-cost.
On fresh tabs (about:blank), _lastUrl is null so we correctly fall
through to navigation — no special-casing needed.
* fix(browser): retry settle probe after SPA client-side redirect
SPA sites like creator.xiaohongshu.com can trigger a client-side
redirect after chrome.tabs reports status 'complete', invalidating
the CDP target. The waitForDomStable probe in page.goto() was
unprotected, causing -32000 "Inspected target navigated or closed".
Wrap the settle probe in try/catch with a single 200ms-delayed retry,
consistent with the existing stealth injection error handling pattern.
The retry gives the SPA redirect time to complete, while the outer
catch ensures settle failure never crashes goto() since navigation
itself already succeeded.
Closes#502
* review: narrow settle retry to target redirects
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* test(e2e): accept current apple podcasts fetch errors
* fix(ci): stabilize plugin and public command checks
---------
Co-authored-by: pi-dal <hi@pi-dal.com>