Commit Graph

671 Commits

Author SHA1 Message Date
ajia1206 341bb87e09 test(xiaohongshu): redact creator fixture data (#647) 2026-04-02 01:21:53 +08:00
AstroHan 127dc3edea feat: add minimal record write candidates (#665) 2026-04-02 01:21:11 +08:00
jakevin f88e7569e9 refactor: src cleanup — deduplicate errors, cache VM, extract BasePage, remove Playwright MCP legacy (#667)
* refactor: deduplicate transient error checks, cache VM contexts, expose tab ID

- Extract shared isTransientBrowserError() into browser/errors.ts, replacing
  duplicated string-matching lists in daemon-client.ts and pipeline/executor.ts
- Cache compiled vm.Script objects in template.ts with LRU eviction (max 256),
  avoiding per-invocation VM context creation in pipeline loops
- Add getActiveTabId() to IPage interface and Page class for tab state inspection

* refactor: extract BasePage to deduplicate DOM helpers across Page and CDPPage

Both Page (daemon-backed) and CDPPage (direct CDP) had ~200 lines of
identical DOM helper implementations (click, type, scroll, wait, snapshot,
interceptor, etc). Extract shared logic into abstract BasePage class.
Subclasses now only implement transport-specific methods.

* refactor: rename mcp.ts to bridge.ts and clean up Playwright MCP references

The file browser/mcp.ts contained BrowserBridge (daemon session manager),
not MCP functionality. Renamed to bridge.ts for clarity. Also removed all
stale "Playwright MCP" references from comments and variable names across
the codebase — Playwright was removed long ago.
2026-04-02 00:51:15 +08:00
jakevin 9c2a777d11 chore: remove .agents directory (#668)
- Remove redundant .agents/skills and .agents/workflows
- Content already covered in CLI-EXPLORER.md and SKILL.md
2026-04-02 00:27:59 +08:00
jakevin 773178345d refactor: remove bind-current, restore owned-only browser automation model (#664)
* fix(notebooklm): remove bind-current workflow

* fix: relax notebook ID check in open.ts and clean up idle timeout test

- open.ts: only throw when page kind is not 'notebook'; log a warning
  instead of throwing when the notebook ID doesn't match exactly
- background.test.ts: remove unused tabs[1] setup in idle timeout test
  that was leftover from borrowed-session era

* build: rebuild extension dist after bind-current removal
2026-04-01 23:23:48 +08:00
jakevin 1ddca55b4a chore: release v1.5.8 (#663) v1.5.8 2026-04-01 22:12:39 +08:00
小左同学 4818871309 Handle foreign extension embeds before debugger attach (#662)
* Handle foreign extension embeds before debugger attach

* fix(extension): recover owned tabs without mutating borrowed tabs

* fix(extension): avoid adopting unrelated tabs

* Revert "fix(extension): avoid adopting unrelated tabs"

This reverts commit 2cba0c19da.

* Revert "fix(extension): recover owned tabs without mutating borrowed tabs"

This reverts commit 69dfadedae.

* fix(extension): avoid mutating tabs before attach

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-01 22:10:30 +08:00
jakevin c908d9cd47 chore: release v1.5.7 (#654) v1.5.7 2026-04-01 13:09:11 +08:00
jakevin 9b425c7550 feat: Electron auto-launcher — zero-config CDP connection (#653)
* 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: add confirmPrompt() to TUI module

* feat: add Electron app registry with builtin + user-defined apps

* feat: add Electron app launcher with auto-detect and restart

* fix: launcher uses processName for path discovery, platform-guard tests

* feat: integrate Electron auto-launcher into execution pipeline

- CDPBridge.connect() accepts cdpEndpoint parameter instead of requiring env var
- getBrowserFactory() selects CDPBridge for registered Electron apps by site name
- executeCommand() calls resolveElectronEndpoint() for Electron apps, skips daemon check
- Remove requiredEnv/OPENCLI_CDP_ENDPOINT from all chatwise commands
- Remove chatwise-opencli.ps1 wrapper script and chatwise/shared.ts
- Update antigravity/serve.ts to use launcher instead of manual env var
- Replace hardcoded app names in scoreCDPTarget with registry lookup
- Fix Discord bundleId typo (com.iscord.app → com.discord.app)

* fix: resolve review issues — port collision and registry completeness

- Change ChatGPT CDP port from 9224 to 9236 (was colliding with Antigravity)
- scoreCDPTarget now uses full registry (builtin + user-defined) via getAllElectronApps()
- Use displayName (falling back to processName) for target score boosting

* fix: assign unique CDP ports — antigravity 9234, chatgpt 9236

Both were sharing port 9224, which could cause silent mis-connection.
2026-04-01 12:49:52 +08:00
reabiter 12443f049e enhance(v2ex): add content, member, created, node fields to topic output (#648)
- Add content field to display topic body text
- Add member field to show topic author
- Add created field to show topic creation timestamp
- Add node field to show topic category
- Add id field for consistency with hot/latest commands

This makes v2ex topic command return meaningful details that are
not available in hot/latest listings.
2026-04-01 02:10:28 +08:00
Jack Lee ee0c2b65ea feat(youtube): add search filters — --type shorts/video/channel, --upload, --sort (#616)
* feat(youtube): add --type shorts/video/channel, --upload, --sort filters

Uses YouTube's native sp= filter params. Shorts = type 9 (sp=EgIQCQ).
Also parses reelItemRenderer for Shorts results.

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

* feat(youtube): add published time to search results

Shows when video was uploaded (e.g. "8h ago", "4d ago", "3mo ago").

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

* fix(youtube): prevent duplicate sp= params and remove redundant Shorts URL rewrite

- YouTube only supports one sp= parameter; using multiple causes
  unpredictable behavior. Pick the most specific filter with priority:
  type > upload > sort.
- Remove the post-processing Shorts URL rewrite — the reelItemRenderer
  branch already generates /shorts/ URLs directly.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-01 01:32:08 +08:00
warkcod 01057527f5 fix(bilibili): distinguish login-gated subtitles from empty results (#645)
* fix(bilibili): distinguish login-gated subtitles from empty results

* fix(test): use single toSatisfy assertion instead of double rejects.toThrow

Awaiting the same rejected promise twice is unreliable. Combine the
AuthRequiredError type check and message regex into one toSatisfy call.

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-01 00:59:19 +08:00
reabiter 67fb022f16 fix(v2ex): add id field to hot and latest API responses (#646)
* fix(v2ex): add id field to hot and latest API responses

- Add id field to hot.yaml and latest.yaml pipeline output
- Enables downstream commands like 'v2ex topic <id>' to work seamlessly
- Fixes issue where v2ex hot/latest JSON output lacked topic IDs

* enhance(v2ex): add node and url fields to hot/latest output

In addition to the id field, include node name (板块) and topic URL
for richer output. All fields come from the existing API response.

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-04-01 00:25:57 +08:00
jakevin 7ea3f6d3fb feat(stealth): harden CDP debugger detection countermeasures (#644)
Add 6 new anti-detection patches to stealth.ts and CDP-level debugger
statement neutralization to reduce risk of bot detection on platforms
like Xiaohongshu.

New patches:
- Shared toString disguise via WeakMap (undetectable by anti-bot scripts)
- Anti-debugger statement trap (Function/eval patching + CDP Debugger.setBreakpointsActive)
- Console method fingerprinting defense (re-wrap CDP-bound console methods)
- Window dimension detection defense (outerWidth/outerHeight normalization)
- Performance API entry filtering (remove debugger/devtools entries)
- document.$cdc_ property cleanup (backup for window-level cleanup)
- Iframe contentWindow.chrome consistency
2026-03-31 23:34:37 +08:00
ajia1206 818ae61889 fix(douyin): support current creator api response shapes (#618) 2026-03-31 23:01:13 +08:00
jakevin 811e4f5c3e fix(douyin): narrow getDraftCommand return type to fix TS2722 (#643) 2026-03-31 22:59:23 +08:00
AstroHan fea47abcec fix(douyin): repair creator draft flow (#640)
* fix(douyin): handle creator payload shapes

* refactor(douyin): drive draft via creator page

* fix(douyin): save resumable draft session

* fix(douyin): harden draft cover flow

* fix(douyin): wait for stable cover state

* fix(douyin): wait for cover detection result

* fix(douyin): require cover state transition

* fix(douyin): scope cover checks to quick panel

* fix(douyin): narrow quick-check state match

* fix(douyin): drop ambiguous quick-check match

* fix(douyin): test quick-check panel extraction

* fix(douyin): cover busy state extraction

* refactor(douyin): clean up draft tests — temp file cleanup, reduce boilerplate

- Add afterAll cleanup for temp dirs (fixes temp file leak)
- Extract createTempVideo/createTempCover/getDraftCommand helpers
- Remove repeated registry lookup + mkdtempSync boilerplate from each test

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-31 22:37:35 +08:00
AstroHan 519b3cfe85 fix: avoid in-page redirect in facebook search (#642)
* fix(facebook): split search navigation from extraction

* refactor: use settleMs instead of waitUntil:none + wait:4

Replace `waitUntil: none` + separate `wait: 4` step with `settleMs: 4000`
on the navigate step. This is consistent with other Facebook adapters
(feed.yaml, memories.yaml, profile.yaml) and lets the navigate step
handle the timing in one place.

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-31 22:26:07 +08:00
jakevin 57534cf8b3 feat(daemon): replace 5min idle timeout with long-lived daemon model (#641)
* docs: add daemon lifecycle redesign spec

Replace the aggressive 5-minute idle timeout with a long-lived daemon
model that stays running for hours, reducing restart overhead during
development cycles.

* docs: add daemon lifecycle redesign implementation plan

8-task TDD plan for replacing aggressive 5-minute idle timeout with
long-lived daemon model (4h default, dual-condition exit).

* feat(daemon): add DEFAULT_DAEMON_IDLE_TIMEOUT constant (4 hours)

* feat(daemon): replace fixed 5min timeout with dual-condition idle manager (4h default)

* feat(extension): reduce WS reconnect backoff cap from 60s to 5s

* feat(daemon): improve CLI connection-waiting UX with progress messages and 200ms polling

* feat(daemon): add opencli daemon status/stop/restart commands

* test(daemon): add tests for daemon status/stop commands

* fix(daemon): address code review issues — stale constant, restart robustness, timer cleanup, test coverage

* docs: update daemon documentation for new lifecycle and CLI commands

- troubleshooting.md: replace manual curl/pkill with `opencli daemon status/stop/restart`
- browser-bridge.md (en/zh): add Daemon Lifecycle section
- README.md: add `opencli daemon status` to Quick Start
- README.zh-CN.md: add daemon management commands to tips
2026-03-31 22:17:54 +08:00
jakevin 62fde40aab fix(docs): use relative links in adapter index (#629)
VitePress base is /docs/, so absolute links like /adapters/browser/twitter
resolve incorrectly. Changed all links to relative paths (./browser/...,
./desktop/...) so they work correctly on the docs site.
2026-03-31 14:39:15 +08:00
muqiao215 0204dbb018 feat(notebooklm): add read commands and compatibility layer (#622)
* feat(notebooklm): add read commands and compatibility layer

* review: trim notebooklm artifacts and sync docs

---------

Co-authored-by: qiaoqiao147 <camtup044@gmail.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-31 13:19:47 +08:00
fii6 1858eace6e feat(instagram): add media download command (#623)
* feat(instagram): add media download command

* feat(instagram): add media download command

* fix(instagram): align download command with platform conventions

---------

Co-authored-by: fii6 <246637913+fii6@users.noreply.github.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-31 13:04:31 +08:00
Kagura 0fbeb2d77f fix(substack): update selectors for Substack DOM redesign (fixes #621) (#624)
* fix(substack): update selectors for Substack DOM redesign (fixes #621)

Substack replaced <article> elements with role="article" divs and a
new SPA-based feed. The wait() selector 'article' no longer matches,
causing 'Selector not found: article' on feed and publication commands.

- loadSubstackFeed: use 'a[href*="/p/"]' (matches actual post links)
- loadSubstackArchive: use '[role="article"]' (Substack's new ARIA roles)

The evaluate() scraping logic inside both functions is unchanged since
it already uses 'a' href pattern matching, not article tags.

* review: align substack wait selectors with scraper

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-31 12:58:22 +08:00
AstroHan 33ca81b785 fix(weread): recover book details from cached shelf fallback (#628) 2026-03-31 12:47:52 +08:00
GanFanNewOrder 4c3cd3878e fix(ctrip): update search adapter to live endpoint (#627)
* fix(ctrip): update search adapter to live endpoint

* review: make ctrip search a public fetch adapter

---------

Co-authored-by: 泽加武 <zejiawu@zejiawudeMac-mini.local>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-31 12:47:48 +08:00
dependabot[bot] 5f541ea42b chore(deps): bump vitest from 4.1.1 to 4.1.2 (#620)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.1 to 4.1.2.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.2/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 12:43:16 +08:00
geegewu ca2165cbc7 fix(xiaohongshu): support full URL/short link and fix video extraction (#615)
* fix(xiaohongshu): support full URL/short link and fix video extraction

Two issues fixed:

1. URL handling: The download command only accepted bare note IDs and
   constructed `explore/{noteId}` URLs, which lack the `xsec_token`
   parameter now required by Xiaohongshu. This made all video/image
   downloads fail with "No media found". Now accepts full URLs
   (with xsec_token) and short links (xhslink.com) in addition to
   bare note IDs.

2. Video extraction: XHS video player uses blob: URLs in DOM, which
   cannot be downloaded via HTTP. Now extracts real video URLs from
   `window.__INITIAL_STATE__` (SSR data) and inline script JSON
   before falling back to DOM selectors, skipping blob: URLs.

Tested with a video note via short link — successfully downloaded
21.5 MB MP4.

* review: resolve xiaohongshu note id after redirects

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-31 00:32:08 +08:00
ahahaha d0803857f1 feat(xiaohongshu): add note command and nested reply support for comments (#599)
* feat(xiaohongshu): add note command and nested reply support for comments

Add `xiaohongshu note` command to read full note content (title, author,
description, engagement metrics, tags) from public note pages.

Enhance `xiaohongshu comments` with `--with-replies` flag to extract
nested replies (楼中楼), including reply_to attribution and per-reply
like counts. Limit logic counts only top-level comments so replies
are included for free.

Extract shared `parseNoteId` into side-effect-free `note-helpers.ts`
to avoid cross-module command registration leakage.

Normalize non-numeric engagement placeholders ("赞"/"收藏"/"评论")
to "0" for zero-count notes.

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

* docs(xiaohongshu): add note and comments --with-replies to adapter docs

Update xiaohongshu adapter documentation and README command table
to reflect the new note command and enhanced comments with nested
reply support.

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

* docs(xiaohongshu): fix download example to show both note-id and url

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

* fix(xiaohongshu): expand nested reply threads before scraping

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-30 23:29:30 +08:00
Kagura 701d9e859f fix(xiaohongshu): check login wall before autoScroll in search (fixes #597) (#608)
- Add early login-wall detection before autoScroll() in search.ts
  to prevent crash when XHS shows a login gate instead of results
- Add document.body null guard in autoScrollJs (dom-helpers.ts)
- Update search.test.ts: verify autoScroll is not called on login wall
- Add autoScrollJs null-body defense test in dom-helpers.test.ts
2026-03-30 23:12:02 +08:00
AstroHan 59d41f28e1 fix(zhihu): stop question command failing on unused detail fetch (#606)
* fix(zhihu): stop question command failing on unused detail fetch

* review: harden zhihu question fetch path

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-30 23:10:53 +08:00
jakevin fcdc15d385 fix: improve weixin article download extraction (#612) 2026-03-30 23:01:44 +08:00
jakevin 030adc9341 fix: restore root SKILL.md (#609) 2026-03-30 21:30:37 +08:00
jakevin 62e3c55993 chore(release): 1.5.6 (#596) v1.5.6 2026-03-30 13:27:19 +08:00
bhutano 8e37f66e53 fix(spotify): follow-up fixes for token refresh, null guards and credentials guidance (#591)
* fix(spotify): fix token refresh, null guards, env parse, missing credentials guidance, postinstall template

* fix(spotify): restore credential guardrails

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-30 13:03:25 +08:00
jakevin 770c28301a docs: add dingtalk and wecom CLI to external CLI hub (#594)
* 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.
2026-03-30 12:49:36 +08:00
AstroHan cf79ec5c23 feat(xueqiu): add comments command (#587)
* feat: add xueqiu comments command

* docs(xueqiu): add comments command docs

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-30 01:22:44 +08:00
Zhangchen 8c00ad9f02 feat(browser): add ONES adapter support for tasks and worklog commands (#386)
* 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>
2026-03-30 01:06:38 +08:00
Inori333 3eb2e88c85 fix: normalize boolean arg aliases (#585) 2026-03-30 00:44:45 +08:00
Haoyue Bai b280f19321 feat(youtube): mute and pause watch pages for read commands (#578)
* Mute and pause YouTube watch pages for read commands

* fix(youtube): quiet watch pages earlier

* refactor(youtube): avoid watch ui for read commands

* test(youtube): cover html bootstrap parser

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-29 23:22:53 +08:00
AstroHan a32b65be4a feat: add Tieba browser adapters in TypeScript (#581)
* 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>
2026-03-29 22:39:26 +08:00
Ron ab7eca35e4 feat(doubao): add history, detail, meeting-summary (#566)
* 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>
2026-03-29 22:36:24 +08:00
jakevin 107ed28449 refactor(douyin): share user video public api (#580) 2026-03-29 17:50:46 +08:00
Howard 79b4e069f0 feat(douyin): add user-videos command with top comments (#554)
* 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>
2026-03-29 17:45:11 +08:00
PXLZJ d2b563e55b feat(xiaohongshu): add cover image URL to user notes output (#572)
* 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>
2026-03-29 17:40:33 +08:00
jakevin f8e9b08223 fix(zsxq): require active group context (#579)
* fix(zsxq): require active group context

* docs(zsxq): add adapter guide
2026-03-29 17:29:36 +08:00
bhutano 1ae1c82c4a feat(spotify): add Spotify playback adapter (#560)
* 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>
2026-03-29 17:28:37 +08:00
康厚超 440c001a20 feat(band): add Band.us adapter — bands, posts, mentions, post commands (#532)
* 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>
2026-03-29 17:19:26 +08:00
James 5925849414 feat(xiaohongshu): use CDP DOM.setFileInputFiles for image upload (#574)
* 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>
2026-03-29 17:16:55 +08:00
xtftbwvfp d8d9643e89 feat: add 知识星球(zsxq) site adapter (#571)
* 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>
2026-03-29 17:15:27 +08:00
AstroHan bb5c2b1fc6 fix(weread): harden reader fallback and search mapping (#562)
* fix(weread): harden reader fallback and search mapping

* fix(ci): remove stale weread regression test duplicates

* refactor(weread): simplify search fetch and eliminate redundant getCookies

- Parallelize search API + HTML fetch with Promise.all
- Add generic numeric entity decoding (decimal + hex) in decodeHtmlText
- Extract loadWebShelfSnapshotWithVid to pass currentVid downstream,
  avoiding a redundant getCookies call in waitForTrustedWebShelfSnapshot
- Split mixed early-return conditions with individual comments
- Add mirror comments between browser/Node trusted-index logic
2026-03-29 17:02:02 +08:00