- Remove ~1200 lines of UI automation from post.ts (executeUiInstagramPost
and all its dependencies: composer, upload, caption, retry logic)
- post.ts now uses Private API exclusively (like story.ts/note.ts)
- Remove process.env.VITEST coupling in reel.ts; always use passed-in page
- Replace hardcoded /tmp debug paths with os.tmpdir() in reel.ts and
protocol-capture.ts
- Update post.test.ts: remove UI automation tests, keep Private API and
JS builder function tests (used by reel.ts)
Replace template literal interpolation of Node-side constants with
JSON.stringify() for consistency with codebase evaluate patterns.
Use bracket notation for dynamic property access instead of template
interpolation into a property chain.
* fix doubao image urls in read output
* fix(doubao): derive image selector from messageTextSelectors
Hardcoded image selector only covered the first two text selectors,
so images inside class-based message containers would be missed.
Generate from the shared selector list for consistency.
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* refactor(xiaohongshu): replace blind retry with MutationObserver wait
Instead of retrying the entire navigation when search results are empty,
use a MutationObserver to wait for `section.note-item` elements (or login
wall text) to appear in the DOM, with a 5s timeout. This is faster (resolves
as soon as content renders) and more correct (addresses the root cause of
delayed hydration rather than working around it with a full re-navigation).
* simplify: merge login-wall detection into MutationObserver wait
WAIT_FOR_CONTENT_JS now returns 'content', 'login_wall', or 'timeout'
instead of just true/false. This eliminates the separate login-wall
evaluate call and the redundant loginWall field in the extraction payload.
Two evaluate calls total (wait + extract) instead of three.
* fix: add prepare script so source installs trigger build
npm install from git (e.g. npm install github:jackwener/opencli) skips
prepublishOnly, so dist/ is never generated. The prepare hook runs on
git-based installs; the [ -d src ] guard skips it for registry installs.
* fix: include extension/dist in git so clone works out of the box
.gitignore had conflicting rules: line 3 tried to un-ignore extension/dist/
but line 26 re-ignored it. Remove the later rule so the built extension JS
is tracked in git — users can load the extension directly after clone.
* fix(doubao-app): connect to correct CDP target instead of background page
Doubao desktop app exposes multiple CDP targets. The scoring logic picked
the background page (doubao-background) over the actual chat page because
its URL-as-title contained "doubao", boosting its score above the real
chat page (title "豆包"). This caused all commands (send, ask, read) to
fail with "No textarea found".
- Add `targetFilter` field to ElectronAppEntry for per-app preferred target
- Set doubao-app targetFilter to 'doubao-chat/chat'
- Penalize background/new-tab-page URLs and URL-like titles in scoring
- Thread cdpTargetFilter through execution → runtime → CDPBridge
Closes#634, closes#506
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(cdp): exclude background targets instead of targetFilter
Replace the targetFilter plumbing (4 files, new interface field) with
a single-line fix: exclude `background_page` and `service_worker`
type targets from CDP selection entirely.
Background pages should never be connection targets — they have no
visible DOM and all selectors will fail. This is the root cause of
#506/#634 (doubao-app connecting to empty background page).
Simpler fix: 1 line added vs 4 files modified. No new interface
fields, no per-app configuration needed.
---------
Co-authored-by: 刘启灏 <liuqihao@liuqihaodeMacBook-Pro.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
When other Chrome extensions (tab managers, new-tab overrides) move
automation tabs to a different window, the Browser Bridge now attempts
to move the tab back to the automation window rather than creating a
new one. This preserves the existing page state and avoids redundant
navigation.
Changes:
- resolveTab(): when a provided tabId has drifted to another window but
content is still debuggable, use chrome.tabs.move() to bring it back
- handleNavigate(): after navigation completes, detect if the tab drifted
during navigation and move it back to the session window
- cdp.ts ensureAttached(): log final tab URL and windowId on attach
failure for better diagnosis of extension conflicts
Closes#652 (partially — addresses tab drift recovery and diagnostics)
PR #712 refactored _ensureDaemon to use a single fetchDaemonStatus() call
instead of separate isDaemonRunning(). The test was still mocking the old
function, causing it to fall through to the spawn-daemon path and throw
the wrong error message.
1. eval retry delay: 1000ms → 200ms for SPA navigation errors, 500ms
for debugger detach. SPA navigations recover within ~100ms, the old
1000ms delay was unnecessarily long.
2. Window creation: replace fixed 200ms sleep with tab-load poll.
Listens for chrome.tabs.onUpdated status=complete with 500ms
fallback cap. about:blank loads in ~20ms, saving ~180ms.
3. bridge.ts _ensureDaemon: single fetchDaemonStatus() call instead of
two sequential calls (isExtensionConnected + isDaemonRunning both
called fetchDaemonStatus independently). Saves one HTTP round-trip.
4. goto() post-navigation: coalesce stealth injection + DOM settle into
a single exec call. Previously two sequential round-trips
(Node→daemon→WS→extension→CDP each). Saves ~60-160ms per goto().
Two changes that eliminate the about:blank → target-domain navigation
on first command execution:
1. Extension: getAutomationWindow() accepts an optional initialUrl.
When creating a new window, uses the target URL directly instead
of about:blank. handleNavigate() passes cmd.url through so the
window starts on the correct domain.
2. CLI: Remove isAlreadyOnDomain() check before pre-nav. Instead,
always call page.goto(preNavUrl) — the extension's handleNavigate
already has a fast-path that skips navigation when the tab is
already at the target URL. This avoids an extra exec round-trip
(getCurrentUrl eval) on first command.
Net effect: first command saves ~1-3s (one fewer page load),
subsequent commands behave the same (navigate fast-path handles
domain matching efficiently via chrome.tabs.get).
Both methods had zero production callers — only test mocks referenced them.
newTab() created about:blank pages via CDP Target.createTarget, but no
adapter or pipeline step ever invoked it. closeTab() was similarly unused.
selectTab() and tabs() are kept as they have active production usage
(e.g. doubao adapter). The scoreTarget about:blank penalty is retained
as a defensive measure against user-opened blank tabs.
* docs: improve operate skill with Browser Use best practices
- Add Critical Rules section (state over screenshot, verify with get value)
- Add Command Cost Guide (free/instant vs expensive vision tokens)
- Add Action Chaining Rules (safe to chain vs page-changing)
- Add Tips section
- Fix Core Workflow to use state/get value for verification, not screenshot
- Mark screenshot as "ONLY for user deliverables"
Inspired by Browser Use's design: DOM-first state representation,
action cost awareness, and multi-action chaining patterns.
* docs: fix operate skill — eval read-only, IIFE, interaction rules
- Add rule: NEVER use eval to click/type — use click/type/select commands
(eval bypasses scrollIntoView + CDP pipeline, fails on off-screen elements)
- Add rule: eval is read-only, always wrap in IIFE to avoid variable conflicts
- Reorder Critical Rules for priority
- Add IIFE example in Extract section
Root cause: Claude Code was using eval("el.click()") instead of
click <index>, and hitting "already declared" errors from repeated
eval calls in the same page context.
* feat: Browser Use best practices — click/type/state improvements
Inspired by deep analysis of Browser Use's design patterns:
1. Framework listener detection (React/Vue/Angular)
- Detect __reactProps$ onClick, Vue _vei, Angular ng-reflect-click
- Catches <div onClick> elements that pure ARIA/tag heuristics miss
2. Click CDP fallback
- clickJs() now returns coordinates on failure
- BasePage.click() falls back to CDP Input.dispatchMouseEvent
- Page.clickWithQuads() uses DOM.getContentQuads for inline elements
3. Type improvements
- React-compatible: use native HTMLInputElement.prototype.value setter
- Contenteditable: selectAll + execCommand('insertText') for rich editors
- Autocomplete: detect role=combobox, wait 400ms for dropdown suggestions
4. getContentQuads precise click
- Page.clickWithQuads() for multi-line inline elements (e.g. wrapped <a>)
- Falls back through getContentQuads → getBoxModel → JS click
* fix: address code review — injection, silent failure, setter prototype
1. clickWithQuads: escape ref with JSON.stringify before inserting into
JS strings and CSS selectors (injection risk)
2. base-page click: throw error when both JS click and CDP fallback fail
instead of silently succeeding
3. typeTextJs: use matching prototype for native setter
(HTMLTextAreaElement for textarea, HTMLInputElement for input)
* docs: remove Why opencli section, merge advantages into Highlights, add operate quickstart to CN README
- Remove "Why opencli?" / "为什么选 opencli?" sections from both READMEs
- Incorporate Zero LLM cost, Deterministic, Broad coverage bullets into Highlights
- Add operate command mention to AI Agent ready highlight
- Add browser automation / operate quickstart section to README.zh-CN.md (mirrors English README)
* docs: update Built for AI Agents paragraph, add browser automation and website→CLI to Highlights, remove Dual-Engine
- Rewrite "Built for AI Agents" to emphasize operate skill + browser control + crystallizing into CLIs
- Add "Browser Automation" and "Website → CLI" bullets to Highlights (both EN and CN)
- Remove "Dual-Engine Architecture" bullet from EN Highlights
- Remove "动态加载引擎" from CN Highlights (already covered by other bullets)
* docs: remove human quickstart from operate section, AI-only
Root SKILL.md was already removed in #703. Add per-skill install
commands to both EN and CN READMEs (without --full-depth since
root SKILL.md no longer blocks sub-skill discovery).
- Update adapter count from 50+/60+/66+ to 70+ across all docs (actual: 74 sites)
- Add missing operate commands (eval, network, init, verify) to README
- Add opencli-operate skill to Install AI Skills section in both READMEs
- Replace outdated "Playwright MCP Bridge" with "Browser Bridge" in doubao docs
* fix(twitter): add search input fallback for intermittent SPA navigation failures
The pushState + popstate approach works in most environments but fails
intermittently for some users (see #690), likely due to Twitter A/B
tests or timing race conditions where the pathname hasn't updated when
checked.
This commit adds a fallback strategy: when pushState fails after 2
retries, we type the query into the search input on /explore and press
Enter. This triggers Twitter's own form handler, performing SPA
navigation without a full page reload (keeping the fetch interceptor
alive).
Both strategies use selector-based waiting ([data-testid="primaryColumn"])
rather than fixed delays, with graceful fallthrough on timeout.
Fixes#690
* test(twitter): update search test for fallback evaluate call
The search input fallback adds one extra evaluate() call when pushState
fails. Update the mock chain and assertion count accordingly.
* fix(twitter): guard nativeSetter and add fallback success test
- Add optional chaining on getOwnPropertyDescriptor().set to handle
edge cases where Twitter's sandbox overrides the HTMLInputElement
prototype.
- Add test case covering the full fallback path: pushState fails twice,
search input fallback succeeds, results are returned correctly.
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(skills): add YAML frontmatter for discovery and improve descriptions
- opencli-explorer: add missing frontmatter with name, description, tags
- opencli-oneshot: add missing frontmatter with name, description, tags
- opencli-usage: rewrite description to start with "Use when..." and
include specific platform names for better keyword matching
Root cause of low trigger rate: explorer and oneshot had no frontmatter
at all, making them invisible to AI agent skill discovery. Usage had a
generic description without triggering conditions.
* fix(skills): add capability index, cross-skill links, and plugins entry
- Add "Quick Lookup by Capability" table so agents can find platforms
by what they need (search, trending, feed, AI chat, finance, etc.)
- Add plugins.md entry to main index (was completely hidden)
- Add "Related Skills" section linking to opencli-explorer and
opencli-oneshot for adapter development
- Compress platform listings for scannability
* fix(skills): inline compact command quick-reference table in SKILL.md
Add a self-contained command reference table directly in SKILL.md so
agents that can only read the main skill file still have full command
visibility. Each platform gets one row with all available commands.
Organized into Browser/Desktop/Public API/Management sections.
* fix(twitter): resolve article ID to tweet ID before GraphQL query
Article URLs (x.com/i/article/{articleId}) use a different ID than
tweet status URLs. The GraphQL TweetResultByRestId endpoint requires
the parent tweet ID, not the article ID.
Fix: navigate to the article page first, extract the associated tweet
ID from DOM links, then use that for the GraphQL query.
Fixes article fetching returning "Article not found" for all article URLs.
* fix: distinguish article URLs from status URLs, add explicit error handling
The previous commit routed all inputs through the article page, breaking
status URL and bare ID flows. Now only article URLs trigger the
article→tweet ID resolution. Status URLs and bare IDs keep the original
behavior. Also throws an explicit error if resolution fails instead of
silently falling back to the article ID.
---------
Co-authored-by: buruguo <buruguo@lambdafintech.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(xiaohongshu): clarify empty note shell hint
* fix(xiaohongshu): simplify empty shell detection to title+author check
The 7-field conjunction was overly strict — a note that rendered only
placeholder metrics but no title/author was still a valid empty shell.
Since title and author are always present on real notes, checking just
those two fields is a more reliable and simpler signal.
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat: create skills/ directory structure per issue #605
- Create skills/opencli-usage/ with index and categorized command references
- SKILL.md: main index with installation and prerequisites
- browser.md: all browser-based commands (Bilibili, Twitter, Reddit, etc.)
- desktop.md: desktop adapter commands (Cursor, Codex, Notion, etc.)
- public-api.md: public API commands (HackerNews, V2EX, arXiv, etc.)
- plugins.md: management commands, AI workflow, output formats
- Create skills/opencli-explorer/ from CLI-EXPLORER.md
- Create skills/opencli-oneshot/ from CLI-ONESHOT.md
Addresses #605 - enables skill-based discovery and selective installation
* fix: complete browser.md with all missing adapters and fix incorrect entries
- Added 15 missing browser adapters: Reuters, SMZDM, Ctrip, Barchart,
Jike, Linux.do, WeRead, Jimeng, Pixiv, Web, Weixin, JD, LinkedIn,
Sina Finance, Bloomberg (browser)
- Fixed incomplete entries: Facebook (added 5 missing commands),
Coupang (corrected to match actual CLI), Yollomi (restored all 12
commands), Doubao Web (restored send/read commands), Grok (fixed format)
- Added missing public APIs: StackOverflow, Xiaoyuzhou, Wikipedia
- Updated SKILL.md index to list all supported platforms across all
categories including desktop adapters
* refactor: remove root SKILL.md, migrate Record Workflow to opencli-explorer
- Moved Record Workflow documentation (工作原理, 使用步骤, 页面类型表,
候选 YAML→TS 转换, 故障排查) into skills/opencli-explorer/SKILL.md
- Deleted root SKILL.md — all content now lives under skills/
* docs: add AI skills installation guide to README
Add npx skills add instructions for all 3 skills (opencli-usage,
opencli-explorer, opencli-oneshot) to both README.md and README.zh-CN.md.