chore: 同步上游 OpenCLI main#32
Merged
Merged
Conversation
Keep stale Browser Bridge WebSocket events from clobbering the active daemon connection.\n\nCo-authored-by: Jeff Chen <jeff@adtiming.com>
* docs(browser): clarify named session lifecycle * docs(browser): clarify owned versus bound sessions --------- Co-authored-by: Jeff Chen <jeff@adtiming.com> Co-authored-by: jackwener <jakevingoo@gmail.com>
…h timeline/search) (jackwener#1464) * feat(twitter/list-tweets): include media via extractMedia (parity with timeline/search) list-tweets was the only X recall path that dropped media. timeline.js and search.js both call extractMedia(legacy) and emit has_media/media_urls; list-tweets returned only text fields, so downstream consumers (e.g. ml-scout's rate UI) couldn't render image/video thumbnails on tweets pulled from a list timeline. Changes: - Import extractMedia from ./shared.js - Spread extractMedia(legacy) into extractTimelineTweet return - Add has_media, media_urls to columns array (--format columns parity) - Update unit test to assert the new shape; add coverage for photo and video extraction * chore(manifest): rebuild cli-manifest.json for list-tweets media columns --------- Co-authored-by: ml-scout <ml-scout@anthropic.com>
…jackwener#1555) Mirrors PR jackwener#1464 (list-tweets) and the timeline/search/tweets/likes/thread family: spread `...extractMedia(legacy)` into the row and surface `has_media` + `media_urls` columns. Pure parity, no behavior change for existing callers — media keys do not collide with the original columns. - bookmarks.js: import `extractMedia` from ./shared.js, spread into extractBookmarkTweet row, append columns, export __test__. - bookmark-folder.js: same change on extractFolderTweet, export extractFolderTweet via __test__. - bookmarks.test.js (new): baseline + photo + video + entities-only fallback + dedup + envelope + empty-envelope (8 tests). - bookmark-folder.test.js: update existing baseline expectation with has_media/media_urls, add 3 new media tests (photo / mp4 / no-media). - cli-manifest.json: regenerated; only the two `columns` entries change. Reverse-validated: tests fail when extractMedia spread is removed. Audits unchanged: typed-error-lint 189/189, silent-column-drop 102/103 (pre-existing main resolution noted but not consumed here).
…ackwener#1559) * refactor(notion): replace built-in CDP adapter with external ntn CLI Notion has shipped an official CLI at https://ntn.dev. It uses the public Notion API (blocks / databases / properties / comments) instead of reverse-engineering the Desktop UI, so it survives Notion app updates and exposes a wider command surface than the in-tree adapter could. Changes: - `src/external-clis.yaml` — register `ntn` as first-class external CLI (binary `ntn`, homepage ntn.dev, install via the shell-pipe script on mac/linux) - `clis/notion/` — entire directory removed (8 commands: status / search / read / new / write / sidebar / favorites / export) - `docs/adapters/desktop/notion.md` — removed - `docs/.vitepress/config.mts` — drop nav entry - `docs/adapters/index.md` — drop adapter row - `README.md` / `README.zh-CN.md` — drop notion from feature lines, drop adapter table row, add `ntn` to CLI hub examples - `docs/index.md` / `docs/zh/index.md` / `docs/guide/getting-started.md` — drop notion from electron-control feature copy - `skills/opencli-usage/SKILL.md` — drop notion from electron list - `cli-manifest.json` — rebuilt with --allow-removals=8 Migration for users: `curl -fsSL https://ntn.dev | bash` (or `opencli external install ntn`) Then use `opencli ntn <command>` in place of `opencli notion <command>`. Rationale: the in-tree adapter was reverse-engineered against Notion Desktop CDP and shipped only 8 commands. The official CLI gives users the full Notion API surface and reduces our maintenance burden to zero. Same pattern as gh / obsidian / lark-cli / tg-cli / discord-cli / wx-cli. Verification: - `npx tsc --noEmit` clean - `npx vitest run --project unit` → 1091/1 skipped - `npm run build` (with --allow-removals=8) — manifest 809 entries - grep notion in user-facing docs (README / docs / skills) — only descriptive mentions remain in non-blocking places (comparison / site-recon / electron how-to / design doc), no broken adapter references * fix(notion): align ntn external migration * docs(notion): clarify ntn manual install
…pter (jackwener#1561) * fix(xiaohongshu,rednote): unwrap page.evaluate envelope in search adapter `page.evaluate(...)` returns a `{ session, data }` envelope rather than the raw IIFE return value, but the search adapters were calling `Array.isArray(payload)` directly on the envelope. `Array.isArray` is always false on the envelope, so every search result was silently dropped — status=success, exit 0, empty array, no error. The rednote adapter had this same bug; both share `buildSearchExtractJs` from `xiaohongshu/search.js`. Introduces `unwrapEvaluateResult(payload)` as a shared helper in `clis/xiaohongshu/search.js` (re-exported via the existing import line from `rednote/search.js`). The helper is a defensive ternary: it unwraps when payload looks like an envelope with an array `.data`, otherwise it passes the value through unchanged. This keeps the change back-compat with bridge versions that return the raw value, and preserves the existing `Array.isArray(payload)` typecheck at each call site. Verified manually against `opencli xiaohongshu search "补墙洞"` (a query known to return 20+ results in a logged-in browser tab): previously `[]`, now returns the expected ranked rows with all declared columns (`rank, title, author, likes, published_at, url`) populated. Adds 5 unit tests for `unwrapEvaluateResult` covering raw array passthrough, envelope unwrap, non-envelope object passthrough, null/undefined safety, and the "data is not an array" guard. The existing 19 search tests in `clis/xiaohongshu/search.test.js` still pass — the unwrap is invisible to the existing mocks which already return raw arrays. * fix(xhs): unwrap search evaluate envelopes --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(extension): reuse existing adapter tab group * fix(extension): choose best existing adapter group --------- Co-authored-by: Jeff Chen <jeff@adtiming.com> Co-authored-by: jackwener <jakevingoo@gmail.com>
…#1546) * feat: add DuckDuckGo, Brave, and Yahoo web search adapters Add three new search engine adapters with browser-based DOM extraction: - duckduckgo/search: Search DuckDuckGo via html.duckduckgo.com Supports region, time filters, and XHR-based pagination (--offset) - duckduckgo/suggest: Search suggestion autocomplete (no browser needed) - brave/search: Search Brave Search via search.brave.com Supports GET-based pagination (--offset) - yahoo/search: Search Yahoo (Bing-powered) via search.yahoo.com Supports GET-based pagination (--page) All search adapters use Strategy.PUBLIC with browser:true, navigating the target site and extracting results via page.evaluate() DOM queries. Includes full test coverage (16 tests). * fix: use clampInt from shared utils and add adapter docs - Replace Math.max/Math.min patterns with clampInt() from _shared/common.js to pass the typed-error-lint gate (4 silent-clamp violations resolved) - Add adapter documentation for duckduckgo, brave, and yahoo to fix the doc-coverage CI check - Regenerate cli-manifest.json and typed-error-lint-baseline.json * fix: avoid silent-column-drop overlap in brave/yahoo extractors Change buildExtractorJs to return arrays instead of objects whose keys matched columns. This prevents silent-column-drop audit false positives as per opencli-adapter-author conventions. * fix(search): tighten browser search adapters * chore(search): drop baseline churn * fix(duckduckgo): execute search extractor safely * fix(yahoo): reject unsafe redirect targets --------- Co-authored-by: huzekang <huzekang@opencode.ai> Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(boss): support job-seeker chatlist and chatmsg * fix(boss): type chat-side failure boundaries * fix(boss): guard malformed chat API payloads --------- Co-authored-by: Jeff Chen <jeff@adtiming.com> Co-authored-by: jackwener <jakevingoo@gmail.com>
…ackwener#1538) * fix(facebook/feed): add fallback extraction for empty article nodes Add fallback extraction for Facebook feed posts when [role=article] nodes exist but contain empty text. Includes diagnostic errors, content/author cleanup, nested-container dedupe, and an evaluate-script syntax regression test. * fix(facebook): bound feed fallback extraction * fix(facebook): keep feed fallback available after chrome articles --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
…wener#1573) Recruiter-only BOSS commands (recommend, joblist, stats, resume, mark, exchange, invite, greet, batchgreet) returned a generic `COMMAND_EXEC: 请切换身份后再试 (code=24)` when called from a job-seeker account. The original error hid the actionable bit: this command set needs a recruiter (BOSS-side) account. chatlist / chatmsg already special-case code=24 by falling back to the geek-side fetch when --side=auto. Recruiter-only commands have no geek-side equivalent and were just leaking the raw API code. Fix: add a `checkRecruiterSide` step inside `assertOk` that maps code=24 to AuthRequiredError with a clear message. All 9 recruiter-only commands inherit it through their existing `bossFetch` calls; no adapter-level changes needed. chatlist / chatmsg are unaffected because they use `allowNonZero: true` and never hit the auto-error path. Closes jackwener#1572.
…#1568) * fix(weibo): unwrap page.evaluate envelope in read adapters (jackwener#1567) `page.evaluate(...)` returns a `{ session, data }` envelope rather than the raw IIFE return value, so all weibo cookie-strategy read adapters silently dropped their results on v1.7.19: - `getSelfUid` returned the envelope object instead of the uid string, so `'10001' + uid` produced `'10001[object Object]'` and every feed/me/favorites request hit a broken list_id. - `feed`, `hot`, `comments`, `search`, `favorites` did `Array.isArray` on the envelope (always false) and returned `[]`. - `me`, `user`, `post` returned the envelope wrapper itself instead of the inner profile/post object. Same pattern as jackwener#1561 for xiaohongshu/rednote. Adds an `unwrapEvaluateResult` helper to `clis/weibo/utils.js` (kept local rather than cross-importing from `xiaohongshu/search.js` since weibo is an unrelated site) and wraps every `await page.evaluate(...)` in the 8 read adapters plus the two helper calls in `getSelfUid`. Skipped `publish.js` (write command, out of scope for this read fix). Verified live: - `opencli weibo hot --limit 3` returns 3 real trending items - `opencli weibo feed --limit 3` returns 3 timeline posts with correct `https://weibo.com/<uid>/<mblogid>` URLs (proves `getSelfUid` unwrap works) - `opencli weibo me` returns the logged-in profile object - All 20 weibo unit tests pass (6 new for `unwrapEvaluateResult`) * fix(weibo): fail typed on malformed evaluate payloads --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
…jackwener#1585) `ntn`, `dws`, and `wecom-cli` are opaque executable names — users seeing them in `opencli list` or root help have no way to know they correspond to Notion, DingTalk Workspace, and 企业微信. Repurpose the existing `package` field to double as a human-readable brand label, so help output renders as `ntn(notion)`, `dws(DingTalk Workspace)`, `wecom-cli(企业微信)`. - `src/external-clis.yaml`: add `package:` to ntn / dws / wecom-cli - `src/external.ts`: update JSDoc on `package` to cover both upstream distribution names (tg-cli, discord-cli) and brand labels (notion, 企业微信) - `src/cli.ts:629` (`opencli list`): use `formatExternalCliLabel` so the listing matches root help, which already used it - `src/external.test.ts`: regression test for brand-alias labels Verification: - npx vitest run --project unit src/external.test.ts: 9/9 pass - npm run typecheck: clean - npm run build: 813 manifest entries - Smoke: `opencli list` and `opencli --help` both render the new labels
External CLI ergonomics + two adapter envelope/auth fixes. - feat(external): longbridge CLI passthrough (jackwener#1584) - feat(external-cli): brand alias rendering for ntn/dws/wecom-cli (jackwener#1585) - fix(boss): map code=24 → AuthRequiredError (jackwener#1573) - fix(weibo): unwrap page.evaluate envelope in read adapters (jackwener#1568)
…-01 --end 2025-06-02 (jackwener#1379) * docs: add weibo search_by_user command design spec Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(weibo): add search_by_user helper function tests Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(weibo): add search_by_user command for timed post download to Markdown Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(weibo): remove dead hasori ternary and hardcoded hastext/haspic filters The hasori ternary always evaluated to 1 (bug), and hastext=1 + haspic=1 silently excluded text-only and link-only posts from results. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(weibo): add integration tests for search_by_user helpers Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * bak * fix(weibo): reshape user posts into read adapter --------- Co-authored-by: andrew.asa <asa.andrew@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(youtube): unwrap transcript caption results * fix(youtube): validate transcript caption info shape --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
…ackwener#1580) * fix(chatgpt): unwrap page.evaluate envelope across browser commands The browser bridge wraps every `page.evaluate(...)` return value in a `{ session, data }` envelope. Adapters that read `.length` or `Array.isArray(payload)` directly on the envelope silently see "no data" — same failure mode addressed for `xiaohongshu`/`rednote` in jackwener#1561 and `weibo` in jackwener#1568. This sweep applies the same `unwrapEvaluateResult` helper across every chatgpt `page.evaluate` consumer site, plus typed shape guards (`requireArrayEvaluateResult`, `requireObjectEvaluateResult`) on the critical extraction paths so envelope misses fail loud instead of silently returning empty. ## Sites wrapped `clis/chatgpt/utils.js`: - `currentChatGPTUrl` — string URL - `getPageState` — login/composer probe object - `sendChatGPTMessage` — composer write + send-button readiness - `getVisibleMessages` — conversation transcript array - `getConversationList` / `extractConversationLinks` — sidebar items - `waitForChatGPTUploadPreview` — image upload readiness probe - `uploadChatGPTImages` fallback — DataTransfer upload result - `isGenerating` — boolean "still generating?" probe - `getChatGPTVisibleImageUrls` — visible image URL array - `waitForChatGPTImages` — inline `window.location.href` poll - `getChatGPTImageAssets` — exported asset array `clis/chatgpt/image.js`: - `currentChatGPTLink` — used for error hints + conv link reporting ## Drive-by `getChatGPTImageAssets` was also passing a redundant `urls` second arg to `page.evaluate(string, urls)`. The IIFE inside the string already receives the URL list via the `${urlsJson}` template substitution, and the browser bridge guard in `browser/utils.ts` rejects the second form for string scripts with: page.evaluate string input does not accept args; use page.evaluate(fn, ...args) instead So `opencli chatgpt image <prompt>` blows up at the download step without `--sd true`. Drop the trailing arg as part of the asset-export cleanup. (This supersedes jackwener#1556 — same one-line fix is included here.) ## Validation - `npx tsc --noEmit` — clean - `npx vitest run --project adapter clis/chatgpt/` — 38/38 pass (25 existing + 13 new in `envelope.test.js`) - `npm test` — 3644 passing across 364 files - Live (browser bridge, daemon v1.7.19): `opencli chatgpt image "<prompt>"` → end-to-end generate + download succeeds; the envelope wrap is defensive in 1.7.19 (no envelope observed yet), but pre-empts the same silent-failure mode that hit the merged xiaohongshu/weibo PRs. * fix(chatgpt): fail fast on malformed evaluate payloads --------- Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat: add Flomo memos reader adapter Read your Flomo memos via the signed API. - flomo memos: Lists recent memos with content, tags, timestamps Uses the Flomo v1 API with MD5 signing (secret embedded). Requires FLOMO_ACCESS_TOKEN env variable. Supports pagination via --slug cursor and --limit. * fix: add --token arg for Flomo auth * fix: use COOKIE strategy with browser-based API call Use Strategy.COOKIE + browser:true instead of PUBLIC + manual token. The adapter now reads flomo_token from localStorage in the browser, and makes the signed API call from within the page context via fetch(). Signature is computed in Node.js and injected into the browser eval. No env var or --token flag needed. * fix: use access_token from localStorage.me for API auth Flomo API requires Bearer token from access_token field in localStorage.me (not api_token). Adapter now reads access_token from the browser's localStorage and calls the signed API from Node.js with the Bearer header. * feat: add --since filter, refine flomo adapter API - Add --since <unix_ts> to filter memos by updated_at - Add --limit 200 to fetch all memos in one call - Mark --slug as experimental (cursor pagination unreliable) - 5 tests passing * feat: add images column to flomo memos output * docs: add flomo adapter documentation * fix: use clampInt and rebuild manifest * fix(flomo): harden memos reader contract --------- Co-authored-by: huzekang <huzekang@opencode.ai> Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(douyin): restore publish and delete flow - Use upload auth v5 API instead of legacy STS2 for VOD credentials - Switch TOS upload from AWS4-signature to gateway multipart protocol (init/transfer/finish) - Add ApplyUploadInner → CommitUploadInner pipeline for VOD upload - Bypass enable/transend endpoints that hang for gateway-uploaded videos - Handle fast_detect/pre_check empty responses gracefully with retry+backoff - Add creator backend delete fallback (via work_list id matching) when legacy delete returns permission error - Use CommitUploadInner Vid for create_v2, not completed TOS object key - Accept item_id as fallback when create_v2 returns no aweme_id * fix(douyin): harden publish delete write contracts --------- Co-authored-by: Lukin <mylukin@gmail.com>
) - Replace 2-line tagline (websites/browser/electron/local + reuse logged-in browser) with a single line emphasizing the two core capabilities side by side: 把任意网站变成 CLI & 让 AI Agent 操控登录态浏览器 - Add "Help me fill out this form" as the leading opencli-browser skill example so the table surfaces browser-side capabilities, not just scraping
* feat: add Youdao Notes shared note reader adapter Add a new adapter for reading publicly shared Youdao Notes (有道云笔记). - youdao note <url>: Fetches a public shared note by its share URL using browser-based DOM extraction. Extracts title, content, and keyword tags from the React-rendered page. - Supports note.youdao.com and note.youdao.cn share URLs. - Includes test coverage (3 tests) and documentation. Closes jackwener#1418 * fix: extract full note content from React Redux store Previously the adapter only extracted the AI summary section from the DOM. Now it accesses the React fiber tree to read the full note content from the Redux store (store.content.data.content), which contains the complete note body in Youdao's structured format. The extractor recursively walks Youdao's proprietary node format (key '8' for text content) to reconstruct the full note as plain text. * fix(youdao): harden shared note reader contract --------- Co-authored-by: huzekang <huzekang@opencode.ai> Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(linkedin): add messaging commands Add fail-closed LinkedIn inbox, connect, safe-send, and thread-snapshot commands with adapter tests and docs. * fix(linkedin): align commands with current UI Update inbox to read LinkedIn's normalized messaging API response and connect to use the current custom-invite route. * chore(linkedin): sync cli-manifest.json with rebuilt inbox command Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(linkedin): pass silent-column-drop gate Drop the intermediate timestamp_ms field from inbox rows (it is converted to the timestamp column) and baseline the connect command internal profile-probe object. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(linkedin): validate inbox --limit with a typed error Reject an out-of-range --limit with ArgumentError instead of silently clamping it, satisfying the typed-error lint gate. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(linkedin): harden messaging command contracts * fix(linkedin): reject inbox conversations without thread id --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: jackwener <jakevingoo@gmail.com>
Resolve the remaining silent-empty-fallback typed-error baseline entries across Douyin, Jike, and WeRead adapters.
…jackwener#1590) * feat(bilibili): add summary command for the official AI video summary Adds `opencli bilibili summary <bvid>` — fetches Bilibili's official AI-generated video summary (the "AI总结" shown on the video page) via /x/web-interface/view/conclusion/get. Returns the overall summary followed by the timestamped section outline, so you get a structured digest of a video without watching it. - Resolves cid + up_mid from the view endpoint (both required by the conclusion API), then calls the WBI-signed conclusion endpoint. - Throws a clear EmptyResultError when a video has no AI summary — Bilibili only generates them for some videos. Covered by clis/bilibili/summary.test.js (5 cases): summary + outline, summary without outline, no-summary, view-resolution failure, API error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(bilibili): harden summary command contract --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: jackwener <jakevingoo@gmail.com>
Avoid classifying fallback text inside thrown error messages as silent row data.
* fix(barchart): surface greeks fetch failures * fix(barchart): harden greeks failure contract * fix(barchart): reject malformed greeks row identity --------- Co-authored-by: 你的用户名 <你的邮箱> Co-authored-by: jackwener <jakevingoo@gmail.com>
Per WAWQAQ feedback: the previous Highlights list was bloated with hollow marketing phrases and overlapping bullets (e.g. "Browser Automation for AI Agents" + "AI Agent ready" said the same thing twice, "Pipeable, scriptable, CI-friendly" is generic CLI filler). Cut "AI Agent ready", "Account-safe" (folded into Live Browser Automation), "Deterministic"'s second sentence (folded into Zero LLM cost), and merged "Website → CLI" with "CLI Hub" into "100+ adapters + CLI Hub". Result is 5 concrete capability bullets instead of 9, each tied to a real feature. EN and ZH READMEs kept in sync.
Co-Authored-By: Cody <builder.bot@easymeta.au>
Author
Review 结论:NEEDS WORK对抗性检查
Acceptance Criteria
总结:代码主线没看到 blocker,但文档索引有一处明确回归。修掉 |
Co-Authored-By: Cody <builder.bot@easymeta.au>
Author
|
修复 Trent NEEDS WORK:
最新提交: |
Author
Retry Review:PASS(上次 blocking 已修)复审范围只看上次 FAIL 点: 验证结果:
代码/文档层 PASS。剩余门禁不是代码 review:GitHub 仍有 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
做了什么
jackwener/OpenCLI@0c488bbf合并到 EasyMetaAu/opencli 当前main@71bf8bdc。publishadapter 文档入口。main,合并后再部署 node20。Acceptance Criteria
main差异,并包含上游jackwener/OpenCLI最新main提交0c488bbf。docs/adapters/index.md同时保留 YouTubepublish和上游新增 Youdao adapter。cli-manifest.json。测试方法
npm run test:adapter -- clis/douyin/_shared/browser-fetch.test.js clis/douyin/_shared/vod-upload.test.js clis/douyin/delete.test.js clis/douyin/publish-upload-id.test.js clis/douyin/user-videos.test.js✅ 5 files / 32 tests passednpm run build✅ Manifest compiled: 823 entries