Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jackwener/opencli
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.5.5
Choose a base ref
...
head repository: jackwener/opencli
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.5.6
Choose a head ref
  • 20 commits
  • 98 files changed
  • 14 contributors

Commits on Mar 28, 2026

  1. fix(tests): update E2E exit code assertions for usage errors (#567)

    Argument/usage errors now correctly exit with code 2 (EX_USAGE) since
    the exit-codes feature landed. Update the two affected E2E assertions:
    - unknown command → 2 (usage error, not generic failure)
    - plugin update without args → 2 (ArgumentError)
    jackwener authored Mar 28, 2026
    Configuration menu
    Copy the full SHA
    bcaf612 View commit details
    Browse the repository at this point in the history

Commits on Mar 29, 2026

  1. docs: sync docs with codebase (v1.5.5, exit codes, hub table, new ada…

    …pters) (#575)
    
    - SKILL.md: version 1.4.1 → 1.5.5
    - README.md: remove non-existent gws from CLI Hub table; bump adapter
      count to 66+; add Exit Codes section (sysexits.h table + usage example)
    - README.zh-CN.md: replace readwise/gws (not in external-clis.yaml) with
      lark-cli/vercel; add bluesky and douyin to built-in commands table;
      add 退出码 section matching English README; add 66+ adapter count line
    jackwener authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    f44fcd5 View commit details
    Browse the repository at this point in the history
  2. 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
    Astro-Han authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    bb5c2b1 View commit details
    Browse the repository at this point in the history
  3. 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 <[email protected]>
    Co-authored-by: jackwener <[email protected]>
    3 people authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    d8d9643 View commit details
    Browse the repository at this point in the history
  4. 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 <[email protected]>
    
    * test: cover cdp file input upload path
    
    * fix: keep image upload on image-only inputs
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
    Co-authored-by: jackwener <[email protected]>
    3 people authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    5925849 View commit details
    Browse the repository at this point in the history
  5. feat(band): add Band.us adapter — bands, posts, mentions, post comman…

    …ds (#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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * fix(band/mentions): use CSS class selector for bell button instead of locale-dependent text match
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * test(e2e): wire band auth coverage into default matrix
    
    ---------
    
    Co-authored-by: Claude Sonnet 4.6 <[email protected]>
    Co-authored-by: jackwener <[email protected]>
    3 people authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    440c001 View commit details
    Browse the repository at this point in the history
  6. 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    
    * fix(spotify): guard null progress, podcast items, missing tracks data, corrupted tokens, invalid search limit
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
    
    * 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 <[email protected]>
    Co-authored-by: jackwener <[email protected]>
    3 people authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    1ae1c82 View commit details
    Browse the repository at this point in the history
  7. fix(zsxq): require active group context (#579)

    * fix(zsxq): require active group context
    
    * docs(zsxq): add adapter guide
    jackwener authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    f8e9b08 View commit details
    Browse the repository at this point in the history
  8. 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) <[email protected]>
    
    * test(xiaohongshu): cover user note rows
    
    * refactor(xiaohongshu): keep cover out of default columns
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
    Co-authored-by: jackwener <[email protected]>
    3 people authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    d2b563e View commit details
    Browse the repository at this point in the history
  9. 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 <[email protected]>
    
    * fix(douyin): validate user-videos inputs
    
    ---------
    
    Co-authored-by: Claude Sonnet 4.6 <[email protected]>
    Co-authored-by: jackwener <[email protected]>
    3 people authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    79b4e06 View commit details
    Browse the repository at this point in the history
  10. Configuration menu
    Copy the full SHA
    107ed28 View commit details
    Browse the repository at this point in the history
  11. 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 <[email protected]>
    svcvit and jackwener authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    ab7eca3 View commit details
    Browse the repository at this point in the history
  12. 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 <[email protected]>
    Astro-Han and jackwener authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    a32b65b View commit details
    Browse the repository at this point in the history
  13. 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 <[email protected]>
    haoyueb2 and jackwener authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    b280f19 View commit details
    Browse the repository at this point in the history
  14. Configuration menu
    Copy the full SHA
    3eb2e88 View commit details
    Browse the repository at this point in the history
  15. 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 <[email protected]>
    2hangchen and jackwener authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    8c00ad9 View commit details
    Browse the repository at this point in the history
  16. feat(xueqiu): add comments command (#587)

    * feat: add xueqiu comments command
    
    * docs(xueqiu): add comments command docs
    
    ---------
    
    Co-authored-by: jackwener <[email protected]>
    Astro-Han and jackwener authored Mar 29, 2026
    Configuration menu
    Copy the full SHA
    cf79ec5 View commit details
    Browse the repository at this point in the history

Commits on Mar 30, 2026

  1. 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.
    jackwener authored Mar 30, 2026
    Configuration menu
    Copy the full SHA
    770c283 View commit details
    Browse the repository at this point in the history
  2. fix(spotify): follow-up fixes for token refresh, null guards and cred…

    …entials 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 <[email protected]>
    bhutano and jackwener authored Mar 30, 2026
    Configuration menu
    Copy the full SHA
    8e37f66 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    62e3c55 View commit details
    Browse the repository at this point in the history
Loading