Commit Graph

1149 Commits

Author SHA1 Message Date
Jean Zhang c264531586 feat(zlibrary): add search and info commands (#1211)
* feat(zlibrary): add search and info commands

Add Z-Library adapter with two browser-based commands:

- `search` — Search books by title, author, or ISBN.
  Navigates to /s/<query> and extracts results from
  <z-bookcard> shadow DOM custom elements.

- `info` — Get book details and available download formats
  from a book page URL.

Uses Strategy.COOKIE with browser automation to bypass
Cloudflare protection. The adapter reuses the user's existing
Z-Library login cookies from system Chrome.

Known limitation: actual file downloading requires Playwright's
download event handling (page.on('download')). OpenCLI's browser
automation does not currently intercept file downloads. Users
needing to download files should use Playwright to navigate to
the book URLs discovered by this adapter.

* fix(zlibrary): harden input and empty extraction

---------

Co-authored-by: jean <jean@jeandeMacBook-Pro.local>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-29 14:48:38 +08:00
jakevin 10baf02060 feat(web): make read render-aware (#1209)
* feat(web): make read render-aware

* fix(web): fail when networkidle readiness is unmet

* test(web): avoid unhandled networkidle rejection
2026-04-28 23:07:47 +08:00
jakevin dff3fd8950 feat(browser): manage owned workspaces as tab leases (#1204)
* feat(browser): manage owned workspaces as tab leases

* fix(browser): harden lease reconciliation paths
2026-04-28 21:05:33 +08:00
Xeron ff571fc965 fix(jd): separate main and detail image extraction (#1205)
* fix(jd): separate item image extraction

* chore: update CLI manifest

* test(jd): update item adapter expectations

* fix(jd): collect CSS detail images

* fix(jd): extract detail images from scripts and frames

* fix(jd): recover detail images and selected specs

* fix(jd): fail fast on blocked item extraction

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-28 21:05:18 +08:00
Benjamin Liu 08a2428306 fix(deepseek): fix send button detection and file upload (#1166)
* fix(deepseek): fix send button detection in sendMessage

The previous selector `btn.closest('div')?.querySelector('textarea')`
always returned null because the button itself is a div, so
closest('div') returns the button, which has no textarea inside.
This caused every send to fall through to the Enter key fallback.

Walk up from the textarea to find the input container, then select
the last enabled non-toggle button with an SVG icon (the send
button). Excludes `.ds-toggle-button` elements (DeepThink / Search
toggles) so only the actual send button is clicked.

* fix(deepseek): fail closed when upload never enables send

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-28 14:18:29 +08:00
dependabot[bot] 02b3033954 chore(deps): bump jsdom from 29.0.2 to 29.1.0 (#1199)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 29.0.2 to 29.1.0.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](https://github.com/jsdom/jsdom/compare/v29.0.2...v29.1.0)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-version: 29.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-28 14:10:13 +08:00
jakevin 141ec95c01 feat(browser): bind current tab to bound workspace (#1196)
* feat(browser): bind current tab to bound workspace

* docs(browser): document bound session idle semantics

* test(extension): cover bind-current owned-overwrite refusal

Adds regression for the second guard in handleBindCurrent that refuses
binding when the bound:* workspace already has an owned automation
window. Previously only the non-bound prefix path was tested.

* refactor(browser): rename bind command

* fix(browser): bind only current window tabs

* fix(browser): fail unbind when detach command fails
2026-04-27 17:35:37 +08:00
Benjamin Liu bc9ae39cfc feat(google-scholar): add cite and profile commands, fix search dedup (#1176)
* feat(google-scholar): add cite and profile commands, fix search dedup

- cite: get BibTeX/EndNote/RefMan/RefWorks citation for a paper.
  Clicks the cite button in search results and fetches the citation
  content from Google's citation endpoint.

- profile: view an author's Google Scholar profile (h-index,
  i10-index, citation count, top papers). Accepts author name
  or Scholar user ID.

- search: fix duplicate results caused by CSS selector matching
  both outer container (.gs_r.gs_or.gs_scl) and inner child
  (.gs_ri) for each paper.

Closes #1174, closes #1175

* fix(google-scholar): fail fast on cite and profile misses

* fix(google-scholar): document new commands and lock dedup test

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-27 15:36:49 +08:00
CissiBot 02dbbb1c18 fix(uiverse): harden navigation retries and preview lookup (#1171)
Pre-navigate Uiverse commands and retry detached browser bridge failures so code and preview flows stop falling back to about:blank. Broaden preview element matching for input-root components and cover the new navigation contract in tests.
2026-04-27 15:33:23 +08:00
yorick 07760d00ba fix: separate author name from date text in search results (#1173)
* separate author name from date text in search results

* fix(xiaohongshu): constrain author date stripping

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-27 15:28:03 +08:00
hanzi ac80c4962b Fix twitter post image uploads (#1180) 2026-04-27 15:25:42 +08:00
wjjsn e2b595272b fix(doubao): update message selectors for DOM restructure (#1190)
- Replace broken data-testid selectors with class-based selectors
- Message list: [class*="message-list-S2Fv2S"], .container-PvPoAn
- User messages: [class*="bg-g-send-msg-bubble"]
- Assistant messages: [class*="bg-g-receive-msg-bubble"]
- Add stopLines for UI noise: 请仔细甄别, 下载电脑版

Fixes #1183
2026-04-27 15:20:56 +08:00
darthjaja 23beb9508c fix(youtube): channel videos-tab fallback reads wrong tab from InnerTube response (#1164)
* fix(youtube): channel videos-tab fallback reads wrong tab from InnerTube response

After PR #1109, `opencli youtube channel <id>` still returns empty
`recent_videos` for channels whose Home tab is empty AND whose InnerTube
`/youtubei/v1/browse` response includes multiple tabs.

Root cause: the fallback fetch sends a browse request with the Videos
tab's `params`. The response, however, includes ALL tabs (Home, Videos,
Shorts, ...), with only the requested tab marked `selected: true`. The
existing code reads `tabs?.[0]?.tabRenderer?.content?.richGridRenderer?.contents`
— for multi-tab responses `tabs[0]` is Home (empty), so `richGrid` ends
up `[]` and `recentVideos` stays empty. PR #1109's test channels happened
to return single-tab lists with Videos at index 0, masking the bug.

Fix: find the tab with `selected: true` instead of assuming `tabs[0]`.

Reproducer: `opencli youtube channel UC44DSuDgw7_qccvZzIK3Jpg`
(杀鱼伟-Vi, ~3.1K subs, posts daily). Returns 0 videos pre-patch, 30+
videos post-patch.

`npm run typecheck` clean, `npm test` passes (1952/1952).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(youtube): preserve videos tab fallback

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-27 15:16:09 +08:00
wjjsn 9cd247d74d fix(doubao): use ID selector for send button (#1188)
* fix(doubao): use ID selector for send button

The clickSendButtonScript was searching for the send button by walking up
the DOM tree only 2 levels from the textarea, but the actual send button
#flow-end-msg-send is at level 5. This caused message sending to fail.

Fix by directly selecting the button via its ID.

* test(doubao): update send button selector assertions

* fix(doubao): keep send-button fallback contract

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-27 15:07:59 +08:00
sontjer f099e4cb3a fix(toutiao): fix NON_TITLE_LINES scope error in articles parser (#1179)
* fix(toutiao): move NON_TITLE_LINES inside function scope

NON_TITLE_LINES was defined outside parseToutiaoArticlesText() as a
module-level const. When the function is serialized via .toString()
and injected into browser evaluate context, outer scope variables
are not available, causing 'NON_TITLE_LINES is not defined' error.

Fix: move NON_TITLE_LINES inside the function so it's included in
the serialized string.

* test(toutiao): cover serialized articles parser

---------

Co-authored-by: sontjer <sontjer@github.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-27 15:06:54 +08:00
jakevin a999dcec83 docs: update extension install to Chrome Web Store (#1194)
* docs: update extension install to Chrome Web Store link

Extension is now published on Chrome Web Store. Replace manual
download/unpack instructions with the store link across READMEs
and skill docs.

* docs: restore manual install as Option B alongside Chrome Web Store
2026-04-27 14:36:44 +08:00
jakevin ca8459c400 fix(browser): keep text/javascript API responses in network output 2026-04-27 14:23:01 +08:00
Benjamin Liu 54ffc88283 fix(web): preserve button text in web read output (#1185)
The shared article-download pipeline strips all <button> elements
via STRIPPED_TAGS, which is correct for article adapters (zhihu,
weixin) but causes web read to silently lose meaningful button
content like "Download All" on generic pages.

Override the button stripping in web read's configureTurndown
callback so button text is preserved as inline content.

Fixes #1184
2026-04-26 20:47:03 +08:00
jakevin d9c96f7e3b chore: bump version to 1.7.8 (#1178) v1.7.8 2026-04-25 22:16:05 +08:00
jakevin 0e9e1ce953 chore(extension): restore pre-1.6.8 neon terminal icons (#1177)
Restore the original icons (commit b2fa7da) that were replaced by the
v1.6.8 "refresh icons" change in e9867dc. Per user feedback, the original
neon `>_` design read more clearly and was preferred over the abstract
arrow + dash variant.

Reverts only the four icon PNGs (16/32/48/128); manifest, popup, and
extension version stay where they are.
2026-04-25 21:20:14 +08:00
Ray的新范式 766677422d fix(chatgpt-app): use AX send flow and support zh-CN generating state (#1135)
* fix(chatgpt-app): use AX send flow and support zh-CN generating state

* fix(chatgpt-app): fail fast on stale AX send path

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-24 19:52:51 +08:00
Benjamin Liu c0a49e4b44 feat(weixin): add create-draft and drafts commands for Official Account (#1095)
* feat(weixin): add publish (create draft with cover) and drafts (list drafts)

Closes #441

* fix(weixin): rename publish to create-draft to match issue #441 proposal

* fix(weixin): fail fast on draft auth and empty states

* test(weixin): align adapter imports with repo style

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-24 19:39:34 +08:00
GanFanNewOrder 6827de4ab2 fix(amazon): fall back discussion to product page (#1154)
* fix(amazon): fall back discussion to product page

* fix(amazon): tighten sign-in fallback detection

---------

Co-authored-by: 泽加武 <zejiawu@zejiawudeMac-mini.local>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-23 17:25:46 +08:00
Aaron Chang 43873326c8 feat(toutiao): add articles adapter for 头条号 creator dashboard (#1148)
* feat(toutiao): add articles adapter for 头条号 creator dashboard

Add adapter to fetch article list and stats from 头条号 creator backend (mp.toutiao.com).
Supports pagination (1-4 pages) and returns title, date, status, views, reads, likes, comments.

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

* fix(toutiao): preserve short article titles

---------

Co-authored-by: Aaron Chang <yugenchang@future.ov>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-23 17:24:42 +08:00
Benjamin Liu c8eedee760 fix(deepseek): fix history titles and resume conversation on ask (#1153)
* fix(deepseek): fix history titles and resume conversation on ask

- history: use link.innerText instead of link.querySelector('div') for
  title extraction. DeepSeek changed sidebar DOM; the first child div
  is now an empty ds-focus-ring element, causing all titles to show as
  (untitled).

- ask: when workspace is recycled (idle timeout) and --new is false
  (default), click the most recent sidebar conversation link to resume
  it instead of staying on the blank new-chat page. Skip model
  selection when inside an existing conversation since the selector is
  only rendered on the new-chat page.

- ensureOnDeepSeek: return boolean indicating whether navigation
  occurred, so callers can react to workspace recycling.

Closes #1149

* fix(deepseek): fail fast on explicit model resume

* fix(cli): expose only explicit option sources

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-23 17:10:23 +08:00
GanFanNewOrder 9870258075 feat(powerchina): add procurement search adapter (#1155)
* feat(powerchina): add procurement search adapter

* fix(powerchina): stabilize api detail urls

---------

Co-authored-by: 泽加武 <zejiawu@zejiawudeMac-mini.local>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-23 17:01:38 +08:00
Benjamin Liu a3d0185afa fix(sinafinance): match stock symbol in addition to name (#1158)
* fix(sinafinance): match stock symbol in addition to name

The scoring function only compared user input against the Chinese
display name (p[4] from suggest API), so searching "AAPL" matched
"AAPLU" (score 0.8) over Apple Inc. whose name field is "苹果"
(score 0). Check the symbol field first for exact and partial matches.

Fixes #1157

* docs(sinafinance): add missing commands to adapter index

The index table only listed `news` for sinafinance. Added the other
three commands (`rolling-news`, `stock`, `stock-rank`) and updated
the mode from Public to hybrid since rolling-news and stock-rank
require a browser.

Fixes #1156

* test(sinafinance): lock stock symbol matching

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-23 14:17:24 +08:00
jakevin 9c2eb07037 chore: bump version to 1.7.7 (#1152) v1.7.7 2026-04-23 00:03:11 +08:00
jakevin 60114f99ba fix: quality audit bug fixes across core modules (#1151)
* fix: address quality audit bugs across core modules

- output.ts: fix elapsed=0 not showing (falsy check → undefined check)
- cdp.ts: log WebSocket parse errors and getResponseBody failures in verbose mode
- launcher.ts: replace sync execFileSync('sleep') with async setTimeout
- daemon.ts: add missing extensionCompatRange=null in error handler
- errors.ts: add recursion depth limit to serializeCause
- download/index.ts: remove Promise constructor anti-pattern (void async IIFE),
  use cookie.expirationDate instead of hardcoded 1-year expiry

* fix: log network interceptor parse failures, use correct exit codes

- captureNetworkItems: log JSON.parse failure in verbose mode instead of silent swallow
- emitNetworkError: use USAGE_ERROR only for invalid_args/filter/max_body,
  GENERIC_ERROR for runtime failures (capture_failed, cache_expired, etc.)

* test: add regression tests for elapsed=0 and deep cause chain truncation
2026-04-22 23:55:52 +08:00
jakevin f88b965dc5 fix(daemon): allow extension ping CORS (#1150) 2026-04-22 23:46:44 +08:00
jakevin 648390eacd feat(web,download): absorb #1048 — video/audio/iframe + --stdout (#1146)
* feat(web,download): absorb #1048 media + --stdout into web read

Distill the useful pieces of the abandoned PR #1048 (`web md`) into the
existing shared pipeline instead of introducing a parallel command:

- Turndown rules for <video> / <audio> / <iframe>. Video and audio are
  emitted as inline HTML so renderers that support it keep playback,
  and iframes degrade to markdown links (title + src) so embedded
  content (YouTube, CodePen, …) stays reachable. `iframe` moves out of
  STRIPPED_TAGS since it's now handled explicitly.
- `stdout` option on ArticleDownloadOptions: writes the full markdown
  to process.stdout, skips image download + mkdir + file write, and
  reports saved='-'. Remote image URLs stay intact so piped output is
  self-contained.
- `web read --stdout` wires the above through.
- Lazy-load src rewrite: the extractor now promotes data-src /
  data-original / data-lazy-src / data-srcset onto `src` before the
  HTML is frozen, so the markdown body and the image-download list
  reference the same URL (previously a page with placeholder.gif +
  data-src produced broken image links in the output).

Nothing in #1048 that overlapped with the already-merged #1143
hardening was kept — no new Readability wiring, no duplicate Turndown
config, no new command.

* fix(web): keep stdout streaming output clean

* fix(tests): update iframe e2e assertion and drop relative src import

- article-extract e2e fixture test: iframe now converts to a markdown
  link instead of being stripped, so assert the YouTube embed link
  survives rather than asserting its absence.
- clis/web/read.test.js: replace vi.importActual('../../src/registry.js')
  with a direct __test__.command export from read.js; the relative
  import into src/ tripped the package-exports adapter guardrail.
2026-04-22 18:42:38 +08:00
Kagura 733ac0747d fix(deepseek): separate thinking process from response in --think mode (#1142)
* fix(deepseek): separate thinking process from response in --think mode (#1124)

When --think is enabled, the response now includes separate fields:
- response: clean final answer only
- thinking: chain-of-thought reasoning content
- thinking_time: time spent thinking (e.g. '1')

Supports both English ('Thought for X seconds') and Chinese
('已思考(用时 X 秒)') thinking header patterns.

Fixes #1124

* chore: regenerate cli-manifest.json

* fix(deepseek): DOM-level think/response separation, dynamic columns

Blocker 1: Replace fragile split(/\n\n+/) heuristic in parseThinkingResponse()
with DOM-level extraction in waitForResponse(). The page evaluate now queries
distinct DOM nodes (.ds-markdown--think vs .ds-markdown) for thinking and
response content. The text-level parser falls back to treating everything
after the header as thinking (no split), avoiding silent corruption of
multi-paragraph content.

Blocker 2: Remove static columns declaration from askCommand. The renderer
infers columns from row keys, so non-think output only shows 'response'
while think output shows all three columns.

Tests added for multi-paragraph thinking, multi-paragraph answer, and
non-think column regression guard.

* chore: regenerate cli-manifest.json
2026-04-22 18:03:37 +08:00
jakevin e83148a2c1 feat(download): harden HTML→Markdown pipeline (#1143)
* feat(download): harden HTML→Markdown pipeline

Inspired by the MD-This-Page / markdown-viewer-extension analysis, tighten
the shared article→Markdown converter used by zhihu/weixin/web adapters:

- enable turndown-plugin-gfm (tables, strikethrough, task lists)
- strip script/style/noscript/iframe/canvas/form/button/dialog unconditionally
- strip SVG via a dedicated rule (not in HTMLElementTagNameMap)
- drop base64 data-URI images so they don't bloat .md output
- post-process: collapse NBSP, lone bullet/middle-dot residue,
  trailing whitespace, and 3+ blank lines
- frontmatter shape guarantees ≤2 consecutive newlines even when
  some metadata fields are absent

Adds a minimal local .d.ts for turndown-plugin-gfm and 6 new tests
covering GFM conversion, tag stripping, base64 drop, and whitespace cleanup.

* fix(download): emit canonical markdown strikethrough

* feat(download,browser): finish article pipeline polish

Per the follow-up from the MD-This-Page / markdown-viewer-extension
analysis, land the remaining items in the same PR instead of splitting:

article-download.ts
- extend STRIPPED_TAGS with header/footer/nav/aside (page chrome; the
  article's title/author/publishTime are supplied as separate fields on
  ArticleData, so duplicated DOM is redundant)
- new option ArticleDownloadOptions.cleanSelectors — per-adapter CSS
  selector list removed before conversion, applied as a Turndown rule
  via node.matches so invalid selectors fail silently

browser/article-extract.ts (new)
- generic Readability-based extraction that runs in-page via CDP
  evaluate (no jsdom in Node)
- short-circuits non-HTML documents (text/plain, JSON, XML) and the
  single-<pre> "browser rendering a plain text file" case
- clones the document before any mutation (preserves live page state
  for subsequent snapshot / click)
- isProbablyReaderable gate, Readability.parse on the clone, then a
  fallback chain main → [role="main"] → #main-content → … → body
- library sources are JSON-embedded and eval'd inside a Function scope
  so their backticks / module.exports guards don't collide with the
  surrounding IIFE

Tests
- article-download: page-chrome strip, cleanSelectors match + invalid
  selector silently ignored (2 new)
- article-extract: JS generation contents, default fallback chain,
  response normalization, null / malformed handling, and a Function()
  parse check to catch any template-literal break-out in the embedded
  Readability sources (8 new)

* fix(download): honor selector cleanup in fallback paths

* test(e2e): real-site regression for hardened article pipeline

Adds tests/e2e/article-download-pipeline.test.ts driving `opencli web read`
through 6 representative pages (example.com baseline, Wikipedia GFM tables,
MDN metadata, GitHub fenced code, Vercel SSR blog, Ruan Yifeng CJK+images)
and asserting the post-processing invariants: no base64/script/style leaks,
no blank-line runs, no residue, no trailing whitespace, no NBSP.

Graceful skip on bot detection / transient CDP errors, with a single retry.

All 6 sites pass locally (37s total).

* test(browser): add article extraction e2e fixtures
2026-04-22 14:49:35 +08:00
jakevin 3ec98b9405 feat(51job): comprehensive adapter (search / hot / detail / company) (#1132)
* feat(51job): add comprehensive 51job adapter (search / hot / detail / company)

Four adapters covering the main 51job surface:

- `51job search <keyword>` — keyword job search via we.51job.com/api/job/search-pc.
  Rich filters: --area (40+ city name/alias → 6-digit code), --salary, --experience,
  --degree, --companyType, --companySize, --sort, --page, --limit. Response already
  carries full jobDescribe + HR + company + encCoId, so most callers won't need detail.

- `51job hot` — same endpoint with empty keyword, returns 51job's recommendation feed.

- `51job detail <jobId>` — scrapes jobs.51job.com/x/<jobId>.html. Returns description,
  welfare tags, category, address, age requirement, company meta.

- `51job company <encCoId>` — scrapes jobs.51job.com/all/co<encCoId>.html. Job cards
  carry a `sensorsdata` JSON attribute, so we parse that instead of fragile DOM text.
  Company meta from `.c-info.ellipsis`, intro from `#companyIntroRef`.

All four are Strategy.COOKIE + browser:true + navigateBefore:false. 51job sits
behind Aliyun WAF — bare curl / Node-side fetch always hits the slider challenge
(tried copying acw_sc__v2 + ssxmod_itna cookies to Node, WAF also checks TLS
fingerprint and JS execution). Only reliable path is browser-context fetch via
`page.evaluate(fetch(url, {credentials:'include'}))`, so utils.js exports
`pageFetchJson` that wraps this pattern + detects WAF-served HTML.

Verify fixtures included (~/.opencli/sites/51job/verify/*.json) — four adapters
pass `opencli browser verify 51job/<cmd>` with rowCount / columns / types /
patterns / notEmpty checks. Eyeballed jobId 171699769 on jobs.51job.com/suzhou
matches adapter output.

* fix(51job): tighten city handling and docs

* chore: regenerate cli-manifest.json after 51job column cleanup
2026-04-22 13:16:23 +08:00
lwyang 00d54135ad feat(weread): add ai-outline command (#1141)
* feat(weread): add ai-outline command for AI-generated book outlines

Two-step API flow: fetch chapter UIDs via authenticated chapterInfos,
then retrieve hierarchical AI outline from public outline endpoint.

Supports --depth to control detail level (2=topics, 3=key points,
4=full details) and --raw for structured output (chapter/idx/level/text)
suitable for programmatic consumption.

Closes #1140

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

* fix(weread): tighten ai-outline auth contract

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-22 13:13:11 +08:00
lwyang 69d3a73390 fix(weread/book): add fallback selectors for reader page without cover (#1138)
* fix(weread/book): add fallback selectors for reader page without cover

When the private API session expires, `loadReaderFallbackResult` navigates
to the reader URL. The page now sometimes skips the cover/flyleaf and
renders reading content directly, causing the wait for cover/flyleaf title
selectors to time out.

- Add `.readerTopBar_title_link` to `page.wait` selector (always present)
- Use cascading `firstText()` for title: cover → flyleaf → outline → top bar
- Use cascading `firstText()` for author: cover → flyleaf → outline → document.title

Fixes #1137

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

* fix(weread/book): parse author from trailing title segments

* fix(weread): avoid author guess from document title

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-22 12:30:33 +08:00
Mike Jing 5460a18d71 fix(xiaoyuzhou): correct podcast-episodes API endpoint (#1129)
* fix(xiaoyuzhou): correct podcast-episodes API endpoint

The endpoint `/v1/podcast/listEpisode` returns 404. The correct
endpoint is `/v1/episode/list` (verified against Xiaoyuzhou iOS app
traffic; also matches the `episode-list` implementation in
ultrazg/xyz, a widely-used Xiaoyuzhou API wrapper).

Additionally, the server requires an `order` field in the request
body (returns 400 if omitted). Add `order: 'desc'` so callers get
the latest episodes first, matching typical UX for a podcast feed.

Before:  podcast-episodes -> HTTP 404 for every podcast
After:   podcast-episodes returns the N most recent episodes

Tested against real podcast 626b46ea9cbbf0451cf5a962
(张小珺|商业访谈录) — now returns 140 episodes correctly.

* test(xiaoyuzhou): lock podcast episodes endpoint

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-22 12:17:00 +08:00
jakevin dc724262f2 feat: agent-native retrospective — analyze / verify guards / fixture content checks (#1133)
* feat: agent-native retrospective — analyze / verify guards / fixture content checks

Post-mortem on slow 1point3acres + 51job adapter sessions, consolidated
into one PR. Scope is "reduce uncertainty and catch silent failures"
— the two things that sink agent success rate on first-time adapters.

Changes:
- `browser analyze <url>` — one command returns pattern (A/B/C/D),
  anti-bot vendor (Aliyun/Cloudflare/Akamai/Geetest), nearest adapter,
  and a single-sentence recommended_next_step. Replaces the three-step
  open/wait/network recon loop when it can reach a confident verdict.
- `browser wait xhr <regex>` — poll for a specific XHR URL instead of
  blind `wait time N`, so SPA data-arrival barriers are deterministic.
- Fixture `mustNotContain` / `mustBeTruthy` — catch two silent-failure
  modes `notEmpty` misses: content contamination (sibling DOM bleed)
  and `|| 0` / `|| false` fallbacks.
- `browser verify` post-success site-memory check + `--strict-memory`
  — verify-green no longer hides the case where `~/.opencli/sites/`
  was never written back. Memory only materializes if authors write it.
- CI: guard that committed `cli-manifest.json` matches a fresh build.
  Main was already drifted (#1118 left stale ordering + a missing arg);
  this PR regenerates the manifest and will catch the next drift.

Docs (opencli-adapter-author + opencli-autofix skills):
- `success-rate-pitfalls.md` — 10 concrete silent-failure scenarios
  seen in real adapter sessions, each with defense via fixture /
  adapter patterns.
- `autofix` gains discipline rule #6: verify pattern failure means
  tighten the adapter, never loosen the fixture.
- `site-recon.md` leads with `browser analyze`; `api-discovery.md`
  adds a §0 covering WAF vendor detection and cross-subdomain CORS
  (the two gotchas that burned the 51job session).
- `wait time 3` → `wait time 2`, with `wait xhr` as the robust choice.

* fix: make output-dir defaults host-independent in manifest

Three adapters (chatgpt/image, gemini/image, instagram/download) baked
`path.join(os.homedir(), ...)` into the `default` field of their args.
The committed manifest therefore carried my personal `/Users/jakevin/...`
paths — which agents running on a different host saw as surprising
defaults. The drift guard I just added to CI caught it on the first run.

Runtime behavior is unchanged: each adapter still falls back to
`path.join(os.homedir(), …)` inside `func` when the kwarg is absent.
Only the displayed / registered default becomes a tilde-path.

* fix(cli): enforce strict-memory without fixture

* fix(browser): harden analyze and xhr guards

* fix(browser): fallback to interceptor buffer
2026-04-22 01:59:19 +08:00
jakevin 5935191e04 feat(verify): fixture-based value validation + skill docs for COOKIE pitfalls (#1131)
* feat(verify): fixture-based value validation + skill docs for COOKIE pitfalls

`opencli browser verify` now loads `~/.opencli/sites/<site>/verify/<cmd>.json`
when present and validates row count / columns / types / patterns / notEmpty
against the live adapter output. Without a fixture, behavior is unchanged
(just runs the adapter and prints). New flags `--write-fixture`,
`--update-fixture`, `--no-fixture` seed / refresh / bypass the spec.

Motivation: previous verify only checked that the adapter exited 0 and
produced *something* — shape regressions (author name bleeding across rows,
a column silently becoming null after a site refresh, duplicated thread-level
time on every post) all passed "✓ Adapter works!" and shipped broken.

Skill doc updates (opencli-adapter-author):
- adapter-template.md: new "COOKIE adapter 骨架" section — HttpOnly +
  dual-domain cookie read via `page.getCookies`, Node-side fetch for HTML
  (explaining why `page.evaluate(fetch(...))` is the wrong tool when
  `navigateBefore: false` or the response is non-UTF-8), and empty-state
  sentinel row over `[]`
- api-discovery.md §4: note that BBS engines (Discuz/phpBB/vBulletin) set
  auth cookies on the root domain + HttpOnly, so single-domain `getCookies`
  calls silently miss them
- SKILL.md Step 10/12: make `--write-fixture` part of the runbook, forbid
  debug dumps outside `~/.opencli/sites/<site>/fixtures/` or `/tmp/`

* fix(verify): support positional argv in fixture args + site-memory docs

Reviewer feedback blocker: fixture.args was Record<string, unknown>,
expanded as --key value only, so positional-subject adapters
(<tid>/<url>/<query>) couldn't be verified. Repo convention is
"主语优先 positional".

- verify-fixture.ts: args now accepts Record<string, unknown> | unknown[].
  Object → --k v pairs; array → verbatim passthrough. New helper
  expandFixtureArgs() centralizes the branching.
- cli.ts verify action: swap inline expansion for expandFixtureArgs().
- verify-fixture.test.ts: 6 new cases covering array form, mixed
  positional+flag, empty shapes, passthrough stringification.
- site-memory.md: Layer 2 tree now lists verify/<cmd>.json; new schema
  block distinguishes it from fixtures/<cmd>-<ts>.json; runbook timing
  section gets a Step 10 verify-write row. Repo-tree debug-dump ban
  clarified.
- adapter-template.md: new "Verify fixture" section with named-flag and
  positional recipes, honest about --write-fixture only seeding named.

Smoke-tested 1point3acres/thread (positional <tid>): fixture round-trip
green (args=["1173710","--limit","2"]).
2026-04-22 00:08:19 +08:00
jakevin 0710678986 docs: fix stale references in READMEs and autofix skill doc (#1130)
- Add missing skills (opencli-browser, opencli-usage) to install list, table, and references
- Add missing browser commands (find, extract, frames)
- Update adapter command lists (twitter tweets, bilibili comments, xiaohongshu note+comments, xiaoyuzhou auth, amazon rankings, hackernews)
- Fix CLI Hub names: dingtalk→dws, wecom→wecom-cli
- Fix exit codes example: opencli github issues→opencli gh issue list
- Fix autofix skill doc: page.waitForSelector→page.wait({ selector })
2026-04-21 23:28:13 +08:00
Ray a6d1eca204 fix(bilibili): resolve full video URLs and preserve full description (#1118)
Two issues surfaced post-merge of #1110 by the Copilot reviewer:

1. Help text and docs advertise `video URL` as a valid input for
   `opencli bilibili video <bvid>`, but the original implementation
   delegated the whole input to `resolveBvid()` — which only recognises
   bare `BV...` IDs and `b23.tv` short codes. A canonical bilibili URL
   like `https://www.bilibili.com/video/BV.../` therefore got rewritten
   to `https://b23.tv/www.bilibili.com/video/BV.../` and failed before
   ever calling the view API.

   Fix: pre-extract the BV ID from `bilibili.com/video/<BV>...` and
   `bilibili.com/bangumi/play/<BV>...` URLs (www / m. / with or without
   query string) in `video.js`, and fall through to `resolveBvid()`
   only for bare BV IDs and `b23.tv` links.

2. `description` was being truncated to 200 chars with whitespace
   collapsed before being returned. JSON/YAML consumers silently lost
   the full `desc` value. Other bilibili adapters return raw fields.

   Fix: return the full `d.desc` verbatim and let consumers/display
   layers handle formatting.

Adds four regression tests for the URL paths (full URL, URL with query
string, m.bilibili.com mobile URL) and one for description integrity
(> 200 chars, preserved verbatim).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:14:04 +08:00
Benjamin Liu 92efa38aba fix(deepseek): use position-based model selection instead of text matching (#1123)
* fix(deepseek): use position-based model selection instead of text matching

Fixes #1111

* fix(deepseek): preserve explicit instant model contract

* fix(deepseek): guard expert selector arity

---------

Co-authored-by: Benjamin Liu <beneecs@Benjamins-Mac-mini.local>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-21 23:10:17 +08:00
Dylanwoo 666a955fac feat(twitter): expose has_media and media_urls columns (#1115)
Adds two additive columns to the Twitter read commands (search, timeline,
tweets, thread, likes):

- has_media: boolean — true if the tweet contains any photo, video, or GIF
- media_urls: string[] — photo URLs and mp4 variant URLs for videos/GIFs,
  extracted from legacy.extended_entities.media (falls back to entities.media)

The INTERCEPT/COOKIE payloads already carry this data; this change only
extends the row-mapping layer, so no new network work is needed. Pattern
mirrors #465 (time column).

Shared extraction helper lives in clis/twitter/shared.js so all five
adapters stay consistent, with unit coverage for photo, video (mp4 variant
selection), animated_gif, entities.media fallback, and the empty case.

Closes #1107
2026-04-21 23:02:22 +08:00
jakevin 2ad417949a docs(skills): restore and rewrite opencli-usage as orientation skill (#1128)
* docs(skills): restore and rewrite opencli-usage as orientation skill

The original opencli-usage skill was deleted in PR #1094 as part of the
skill consolidation, but its role (top-level orientation to what opencli
is, how to discover adapters, what flags/env/formats are universal, and
which specialized skill to load next) was not covered elsewhere. Restore
it, but deliberately NOT as a verbatim copy:

- Drop the hand-maintained 100-adapter table. There are 100+ adapters
  and the list moves every week — `opencli list -f json` is the source
  of truth agents should call at the start of a task.
- Replace it with the meta-structure agents actually need: the three
  pillars (adapters / browser driving / external CLI passthrough), the
  strategy tags (PUBLIC | COOKIE | HEADER | INTERCEPT | UI | LOCAL)
  and what each implies for prerequisites, universal flags (-f, -v),
  output formats, env vars, self-repair hook, adapter authoring paths,
  plugins, external CLI passthrough.
- Explicitly list the commands PR #1094 removed (`explore`, `record`,
  `web` / `desktop` top-level groups) so agents don't attempt them.
- Cross-link to the four post-consolidation skills: opencli-browser
  (ad-hoc driving), opencli-adapter-author (writing adapters),
  opencli-autofix (repair flow), smart-search (search routing).

Adapter-author description updated to stop claiming it replaces
opencli-usage.

* docs(skills): tighten opencli-usage validate + doctor scope per review

- validate: describe as registry-level semantic check (description, domain,
  pipeline step names, func|pipeline|_lazy presence, arg duplicates), not
  YAML/TS syntax check — matches src/validate.ts
- doctor: narrow to browser-bridge diagnostic; PUBLIC/LOCAL adapters, list,
  validate, verify, plugins, and external-CLI passthrough do not need it
2026-04-21 22:04:09 +08:00
jakevin 9675f6262e docs: add CHANGELOG entry for 1.7.6 (#1127)
Backfill the 1.7.6 section that was missing from the release PR.
Covers window lifecycle flags, selector-first browser interactions,
agent-native payload, compound form fields, three new adapter commands,
four fixes, skill doc updates, and extension 1.0.2 body-truncation
contract unification.
2026-04-21 21:46:43 +08:00
jakevin dba333d1f9 chore: bump version to 1.7.6, extension to 1.0.2 (#1126) v1.7.6 2026-04-21 21:42:46 +08:00
jakevin 7c35935861 docs: sync live and focus window docs (#1125) 2026-04-21 21:41:47 +08:00
jakevin d36bee04bb feat(cli): add --live and --focus flags for automation window lifecycle (#1122)
--live (OPENCLI_LIVE=1) keeps the automation window open after an adapter
command finishes, so agents or humans can inspect the page state. Default
behavior (immediate closeWindow) is unchanged.

--focus (OPENCLI_WINDOW_FOCUSED=1) surfaces the existing env-var toggle as a
CLI flag so users don't need to shell-export to see the window in foreground.

Both flags are parsed early in main.ts and stripped from argv, so they can be
placed anywhere on the command line and work on any subcommand (adapter or
browser).
2026-04-21 21:14:42 +08:00
jakevin 2f66d48a47 docs(skills): restore and upgrade opencli-browser skill (#1119)
Restore `skills/opencli-browser/SKILL.md`, deleted in #1094, rewritten for
the post-#1116 browser CLI surface: selector-first target contract,
`match_level { exact | stable | reidentified }`, compound fields for
date/time/select/file, structured error codes with `available` vs
`candidates`, new `find` / `extract` / `network --filter` commands,
html tree budgets, tabs/frames, cost guide, recipes, pitfalls.

Review tightened two contract-drift bugs before merge:
- `browser tab list` envelope field is `page`, not `targetId`
- `network --ttl` default is `24h`, not `~5min`

2 reviewers green (codex-mini1, First-principles-1); CI all-green.
2026-04-21 17:32:33 +08:00
jakevin 04a5a171d5 feat(browser): compound expansion + cascading stale-ref + bbox 0.99 dedup (#1116)
* feat(browser): compound expansion + cascading stale-ref + bbox 0.99 dedup

Three agent-native upgrades inspired by browser-use, landed as one PR
because they share the same target / snapshot / find surface.

  1. Compound expansion (compound.ts)
     Date/time/datetime-local/month/week, select, and file inputs now
     emit a `compound` JSON field on `browser find --css` entries with
     format, current value, min/max (date family), full options list
     + selected (select), accept / multiple / files[] (file). Kills
     the three biggest form-page failure modes (wrong date format,
     guessed options, re-uploaded files) without extra round-trips.

  2. Cascading stale-ref (target-resolver.ts)
     Numeric ref resolution now walks three tiers before giving up:
     exact → stable (tag + strong id match, soft signals drifted) →
     reidentified (original ref lost, fingerprint uniquely found a
     live element; re-tag + refresh identity). Every success envelope
     carries `match_level` so callers can tell which tier matched.
     SPA re-renders / i18n label swaps no longer stall agents.

  3. BBox 0.99 containment for interactive descendants (dom-snapshot.ts)
     Adds a second dedup tier on top of the existing 0.95 non-interactive
     one. When a parent is a propagator (tag a/button OR role button/
     link/menuitem/tab/option) and a child is interactive but
     undistinctive (no aria-label/id/testid/name/form-control), fold
     it into the parent — removes `[1]<button> [2]<svg> [3]<span>`
     noise on icon buttons.

Tests: 287/287 pass (src/browser + src/cli.test.ts). Typecheck clean.

* fix(browser): address reviewer blockers on PR #1116

- compound select: walk ALL options to collect selected labels, not just
  the first 50 we serialize. Fixes dropdowns where the selected entry
  sits past COMPOUND_SELECT_OPTIONS_CAP (e.g. country lists, timezones)
  reporting current: "" even though the user picked a valid option.
- match_level: propagate the cascading match tier
  (exact / stable / reidentified) through IPage.click/typeText/scrollTo,
  BasePage, and the cli command envelopes (click / type / select /
  get text|value|html|attributes). Agents now see in JSON that the
  resolver had to fall back, instead of the tier being swallowed.
- compound contract is now also emitted by `browser state`
  (per-ref compounds: sidecar) and by `browser get html --as json`
  (compound field on each node), not only by `browser find --css`.
  Closes the gap where agents using the default snapshot still
  round-tripped `find` for every date / select / file control.

Adds targeted regression tests for each blocker + updates cli.test.ts
mocks to the new envelope shape.
2026-04-21 17:02:01 +08:00