* feat(adapters): add 懂车帝 (dongchedi) + 瓜子二手车 (guazi) car adapters
Two no-login PUBLIC adapters for Chinese car platforms. Both read
server-rendered data (no cookies, no signature, no browser) and ship
pure parsers unit-tested against frozen real-data fixtures.
dongchedi (6 commands) — parses __NEXT_DATA__ SSR JSON:
search 车系搜索 + 指导价/经销商价
series 车系概览(品牌/价格/懂车分/销量排名/款型数)
models 款型列表 + 价格
specs 配置概览(尺寸/动力/四驱/悬挂/气囊)
score 懂车分 8 维评分 + 同级对比
koubei 车主口碑/评价正文
(Dongchedi's /motor XHR APIs are ByteDance-signature gated; the SSR
pages expose the same data unsigned, so the adapter reads those.)
guazi (2 commands) — parses m.guazi.com mobile SSR HTML:
browse 分城市在售二手车列表(售价/里程/年份)
car 车源详情(售价/上牌/里程/过户/配置/车况)
(Desktop www.guazi.com is signature-locked; mobile SSR is open. Deep
pagination/filtering uses the signed API and is intentionally omitted.)
Gates green: tsc, doc-coverage --strict, silent-column-drop (new=0),
typed-error-lint (no new), 24 adapter tests passing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(adapters): add 汽车之家 (autohome) — brand catalog + 口碑 ratings
Third no-login PUBLIC car adapter (search by brand, not free text).
autohome (2 commands):
brand 按品牌列出全部车系 + 厂商指导价(grade/carhtml/<INITIAL>.html,
中文品牌名→拼音首字母目录页,DL 块按品牌定位)
score 车系口碑评分:总分 + 各维度 + 故障率PPH + 竞品对比
(k.autohome.com.cn/<id> 的 __NEXT_DATA__.baseData,免登录免签名)
Deliberately omitted (would be silently-wrong without a browser running
Autohome's signing/anti-scrape code): free-text keyword search (signature
gated) and full per-trim 参数配置 (rotating CSS font-glyph obfuscation).
Use dongchedi search/specs/koubei for those. Documented in the adapter doc.
Gates green: tsc, doc-coverage --strict (170/170), silent-column-drop
(new=0), typed-error-lint (no new); 31 adapter tests passing across the
three car adapters.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(car-adapters): fail closed on parser drift
* fix(guazi): fail closed on empty SSR listings
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(github): add `github trending` adapter
Add a PUBLIC adapter that lists repositories from
https://github.com/trending — the trending view is a public HTML page with
no official REST API, so the data was previously unreachable through opencli.
The adapter fetches the page server-side (no browser, no auth) and parses
each repo's full name, description, primary language, total stars, forks,
and stars gained in the period.
Flags:
- `--since` daily | weekly | monthly (default daily)
- `--language` filter by language slug, e.g. python, rust, "c++"
- `--limit` 1..25 (GitHub lists at most 25)
Typed errors: ArgumentError for bad --since / --limit, CommandExecutionError
on request/HTTP failure, EmptyResultError when the page yields no repos.
Tests cover parsing (stars/forks/language/description/url, missing language),
limit truncation, language+since URL building, argument validation, the
empty-result path, and non-ok HTTP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(github-trending): rename to standalone `github-trending repos`
Move the trending scraper out of the `github` site namespace into a
dedicated `github-trending` adapter to avoid confusion with the bundled
`gh` external CLI. Command is now `opencli github-trending repos`
(site=github-trending, name=repos), leaving room for a future
`developers` subcommand. The `github` site retains only login/whoami.
Regenerated cli-manifest.json; 8 fixture tests + typecheck pass.
* fix(github-trending): fail closed on parser drift
---------
Co-authored-by: minh <claude@ttfy.cc>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(12306): handle endpoint rotation via 302 redirects
Three related bugs caused the trains command to fail with
"non-JSON body" when 12306 rotated its query endpoint:
1. Node.js fetch defaulted to redirect: 'follow', silently
following 12306's HTTP 302 to error.html and returning
HTML instead of JSON.
2. The 302 response body contained rotation info
(e.g. {"c_url":"leftTicket/queryB"}) but was never read
because resp.status === 302 triggered continue before
consuming the body.
3. Mutating QUERY_ENDPOINTS via unshift() during a for...of
loop caused infinite iteration when the new endpoint was
skipped by the iterator.
Changes:
- Set redirect: 'manual' on fetch to capture 302 responses
- Parse c_url from 302 body and enqueue the rotated endpoint
- Replace for...of with a while queue + Set-based dedup to
safely handle dynamic endpoint discovery
Fixes the trains command against the current 12306 wire
protocol (queryG → 302 → queryB rotation).
* fix(12306): bound leftTicket endpoint rotation
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(gemini/history): expand collapsed Recents sidebar before extraction
opencli gemini history returned EMPTY_RESULT ("No conversation links were
visible in the sidebar") even when logged in, because Gemini collapses the
sidebar "最近"/Recents section by default — the /app/<id> conversation
anchors are absent from the DOM until that section is expanded.
getGeminiConversationList now retries extraction (up to 3 times) and, while
empty, clicks the sidebar-open button plus the Recents expand/collapse toggle
(matched by aria-label in both zh and en) before waiting for the React
sidebar to render and re-extracting.
Verified: opencli gemini history --limit 5 returns 5 conversations (2.5s);
opencli gemini detail <id> reads full conversation content.
Co-Authored-By: Claude <noreply@anthropic.com>
Generated on: cmcc-i5
Generated by: home-cc
* test(gemini): cover collapsed recents history extraction
* fix(gemini): avoid collapsing expanded recents
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(chatgpt): add project commands
Add ChatGPT project management adapters for listing visible projects and uploading local files into project knowledge.
The new project-list command extracts project links from stable sidebar anchors first, with a React Fiber fallback for sidebar builds that do not expose hrefs. The project-file-add command uploads through the project knowledge flow, validates local files before browser interaction, and waits for filename confirmation before reporting success.
For the current ChatGPT project UI, project knowledge uploads live behind the Sources tab rather than the older Add files dialog. The upload helper now prefers that Sources surface, avoids mistaking the chat composer plus button for project knowledge upload, and dispatches a browser-like pointer/mouse sequence so Radix-powered tabs activate reliably in live sessions before setting the source file input.
This intentionally avoids command-level system proxy mutation. Users who need a proxy should configure the browser or network environment outside this adapter, rather than letting a single command toggle OS proxy settings.
Also update the generated CLI manifest and focused adapter tests for command registration, argument contracts, project id parsing, project link extraction, upload confirmation, live Sources-tab upload behavior, and failure wrapping.
Validation:
- pnpm exec tsx src/main.ts chatgpt project-list -f json --trace retain-on-failure --window foreground --keep-tab true (live authenticated local session; returned visible projects)
- pnpm exec tsx src/main.ts chatgpt project-file-add /tmp/opencli-chatgpt-project-upload-validation-pointer-20260621215147.txt --id 6a1791df8fa88191afb5a016ce1f497e -f json --trace retain-on-failure --window foreground --keep-tab true (live authenticated local session; uploaded one text file to project knowledge)
- pnpm exec vitest run --project adapter clis/chatgpt/commands.test.js clis/chatgpt/envelope.test.js clis/chatgpt/image.test.js clis/chatgpt/model.test.js clis/chatgpt/utils.test.js
- pnpm exec tsc --noEmit
- pnpm run build-manifest
* feat(chatgpt): support project chat routing
Add --project to chatgpt ask/send so messages can start a new chat inside a specified ChatGPT project. Reject --project with --conversation before navigation, and parse project-scoped /g/g-p-.../c/<id> conversation URLs so ask can report the created conversation id.
Validation:
- pnpm exec vitest run --project adapter clis/chatgpt/commands.test.js clis/chatgpt/envelope.test.js clis/chatgpt/image.test.js clis/chatgpt/model.test.js clis/chatgpt/utils.test.js
- pnpm exec tsc --noEmit
- pnpm run build-manifest
* feat(chatgpt): extend project routing
Add --project routing to chatgpt new, image, and model so project-scoped work is available beyond ask/send. New and image open the specified project before preparing the composer; model opens the project before switching the intelligence level.
Validation:
- pnpm exec vitest run --project adapter clis/chatgpt/commands.test.js clis/chatgpt/envelope.test.js clis/chatgpt/image.test.js clis/chatgpt/model.test.js clis/chatgpt/utils.test.js
- pnpm exec tsc --noEmit
- pnpm run build-manifest
- pnpm exec tsx src/main.ts chatgpt new --help / image --help / model --help
* fix(chatgpt): harden project command boundaries
* fix(chatgpt): require stable project ids
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(adapters): surface empty results as EmptyResultError, not sentinel rows
Four adapters returned a fabricated row (exit 0) on the not-found/empty
path instead of throwing a typed error, so an agent reading the exit code
or rows could not tell "no results" from success — the framework assigns
EMPTY_RESULT its own exit code precisely so this is detectable:
- maimai/search-talents: returned [{error, query}] on zero candidates.
Worse, `error`/`query` are not in `columns`, so the message was dropped
by column projection — the user saw an empty/garbage row, never the
reason. Now throws EmptyResultError (fixes the silent-column-drop too).
- discord-app/search: returned a synthetic "System" row on no matches.
- pixiv/download: returned a failed sentinel when an illust had 0 pages
(the file already throws typed errors elsewhere). Test updated to
assert the throw.
- xiaohongshu/download: returned a failed sentinel when a note had no
media (the file already throws CliError for the security-block branch).
Per-image partial-failure status rows in the download loops are left
as-is (legitimate batch reporting). Auth-path conversions
(tiktok/facebook string-prefix + maimai in-page throw) are a separate
follow-up since they involve in-page-throw handling.
Adapter suites green; typed-error-lint and silent-column-drop audits
report no new violations.
* fix(adapters): fail closed on malformed empty payloads
* fix(audit): bump undici in lockfile
* fix(pixiv): fail closed on missing pages payload
---------
Co-authored-by: codex-mini0 <codex-mini0@slock.local>
* fix(qwen): anchor waitForAnswer on pre-send turn to stop returning the previous answer
qwen `waitForAnswer` took no baseline and never skipped stale turns — the
`seenAssistantId` variable was assigned but never read (dead code). Since
`getMessageBubbles` returns every turn including the already-complete
previous answer, a follow-up `qwen ask` into an existing conversation
(persistent site session, no --new) saw that prior answer on the first
polls. It was already stable, so the stability check returned it as if it
were the reply to the new prompt — silently wrong output, no error.
Mirror grok's reference fix: capture the last assistant turn's id before
sending (`getBaselineLastAssistantId` in ask.js) and `continue` in
waitForAnswer while the latest assistant id equals that baseline. Removes
the dead `seenAssistantId`.
Tests: getBaselineLastAssistantId helper (mirrors grok), plus a direct
waitForAnswer test asserting the pre-send turn is skipped (times out
rather than returning the stale answer) — reverse-validated.
* fix(qwen): bind answer wait to sent prompt turn
* fix(audit): bump undici in lockfile
* fix(qwen): fail closed when answer anchor is not visible
---------
Co-authored-by: codex-mini0 <codex-mini0@slock.local>
* fix(deepseek): throw TimeoutError on no-reply instead of a silent sentinel row
deepseek `ask` returned `[{ response: '[NO RESPONSE] No reply within Ns.' }]`
(exit 0) on both the normal and --file paths when no reply arrived — the
same sentinel-row anti-pattern fixed for other adapters in #1981. Every
sibling chat adapter throws a typed error here (claude EmptyResultError,
grok/qwen TimeoutError), so an agent branching on exit code / error type
saw "success" and consumed the literal `[NO RESPONSE] ...` string as if it
were the model's answer.
Both paths now throw TimeoutError (exit code TIMEOUT), making the failure
observable. Tests cover both the normal and --file timeout paths;
reverse-validated.
Note: the deeper root cause — `sendMessage` in utils.js can silently
no-op server-side (execCommand + fixed 800ms, the exact pattern send.js
warns against) — is a separate follow-up: it needs the proven
nativeType + aria-disabled-poll path (extracted as a shared helper with
send.js) plus live smoke against the site, which can't be verified
offline. This PR at least converts that silent no-op into a loud timeout.
* fix(audit): bump undici in lockfile
---------
Co-authored-by: codex-mini0 <codex-mini0@slock.local>
Final head ff120c1c4e.
Contract: linkedin connect remains a write command and dry-run by default. --profile-url must be an exact LinkedIn profile URL, and --expected-name is required with strict actual profile-name match. Connect availability, More availability, and invite anchors are now scoped to the owner top-card / name-bearing owner action controls, so sidebar or People-also-viewed Connect/More buttons cannot make dry-run falsely connectable or provide the invite URL for the target profile. Top-level anchor Connect accepts only trusted /preload/custom-invite/ links from owner action scope. Button/More path opens Connect only from an owner-named action bar and fails closed when owner controls cannot be proven. Delivery still requires sent-invitations verification for sent_verified; unverified sends return send_unverified rather than a verified success.
Validation: lead+aux content green. Remote statusCheckRollup is empty, merged under the standing no-check override after final poll confirmed OPEN/non-draft, head unchanged, and MERGEABLE/UNSTABLE with no conflict/dirty/content blocker. Local reviewer validation covered focused clis/linkedin/connect.test.js 1 file/21, full LinkedIn adapter 24 files/213, typecheck, build manifest 1225, docs-build, node --check touched LinkedIn files plus dist/src/main.js, typed-error-lint and silent-column-drop no new, listing-id advisory unchanged, and diff-check clean.
Final head 5f5661c270.
Contract: adds a public read-only Internet Archive adapter with archive search, item, wayback, and snapshots commands. All commands are read access, browser:false, with no login, write, upload, or browser UI side effects. Source of truth is Internet Archive Advanced Search response.docs, /metadata/<identifier>, Wayback available closest snapshot, and CDX JSON header/rows. Fail-closed boundaries: search requires response.docs array, true empty maps to EmptyResultError, rows require stable identifier and numeric downloads; item requires metadata.identifier equals requested identifier and files array, while missing metadata/404 remains empty; wayback distinguishes no closest snapshot true empty from available:true missing URL or 14-digit timestamp malformed CommandExecutionError; snapshots requires top-level CDX array, header array, required columns, and per-row timestamp/original/statuscode/mimetype cells, with malformed shapes typed CommandExecutionError rather than empty snapshot URLs or empty status columns.
Validation: lead+aux final green. GitHub final gate OPEN/non-draft/CLEAN, required checks SUCCESS, adapter/smoke skipped. Local reviewer validation covered current-main merge-tree clean, targeted/full clis/archive 1 file/18 tests, typecheck, build manifest 1231, docs-build, doc coverage 165/165, prod audit clean, node --check touched Archive files/tests, diff-check, typed-error-lint and silent-column-drop no new.
Final head 1eb03c8581.
Contract: Gemini composer submit-button detection expands the existing submit label matcher from send/发送/submit/提交 to also include Traditional Chinese 傳送. The change is scoped to clis/gemini/utils.js and tests. Button search remains constrained to the composer-near root, requires visible and enabled candidates, excludes main menu, microphone, upload, mode/tools/settings/new chat and other non-submit controls, and keeps the existing vertical-distance/right-side small-button scoring. If no valid button is found, send/ask still fall back to Enter. No change to Gemini send/ask submit confirmation semantics, command surface, docs, or manifest behavior.
Validation: lead+aux final green. GitHub final gate OPEN/non-draft/CLEAN, required checks SUCCESS, adapter/smoke skipped. Local reviewer validation covered focused clis/gemini/utils.test.js 1 file/27, full Gemini adapter 6 files/91, typecheck, build manifest 1227, docs-build, node --check touched Gemini files plus dist/src/main.js, typed-error-lint and silent-column-drop no new, listing-id advisory unchanged, and diff-check clean.
Final head 2279aca321.
Contract: chatgpt model keeps the existing write surface and supports instant, medium, high, extra-high, and pro intelligence levels; thinking remains a backward-compatible alias for high. Unsupported or unknown requested levels fail upfront with ArgumentError. The selection path requires a logged-in ChatGPT composer and native click. The model selector prefers stable test id / exact visible option text, covering current English Instant/Medium/High/Extra High/Pro and Chinese 极速/均衡/高级/超高/专业 labels. Unknown localization falls back to order only when composer-intelligence-picker-content exists and exactly five visible menuitemradio options are present; otherwise it typed-fails with CommandExecutionError instead of treating an ordinary menu or drifted DOM as success. Postcondition re-reads current selector/test id after click; when label recognition is unavailable, it reopens the picker and verifies the target checked index in the five-option intelligence picker. High vs Extra High matching uses longest/exact ordering to avoid substring false success. No ask/send/read/image/history surface changes.
Validation: lead+aux content green. Remote statusCheckRollup is empty, merged under the standing no-check override after final poll confirmed OPEN/non-draft, head unchanged, and MERGEABLE/UNSTABLE with no conflict/dirty/content blocker. Local reviewer validation covered current-main replay clean, focused ChatGPT utils+commands tests 77/77, full ChatGPT 6 files/118, typecheck, build manifest 1227, docs-build, doc coverage 164/164, prod audit clean, node --check touched files/tests, diff-check, typed-error-lint and silent-column-drop no new, and merge-tree clean.
Final head 53686e6a62.
Contract: xiaohongshu ask remains a browser-backed write command. Answer success still requires the same-send message_id/conversation_id plus a finished non-empty answer. Source identity/url/xsec_token are still trusted only from a 24-hex note id, xhsdiscover://item/<id>, or trusted XHS note URL. New source metadata is a minimal optional enrichment: note_type, user_id, and published_at are forwarded from the 点点 source payload and omitted when empty; like_count is parsed only from non-negative safe integers, strict decimal compact counts with 万/w/W/亿 and optional +, or legal pure digit/thousands strings. Malformed values such as 1e2, 0x10, 1..2万, bad comma grouping, negatives, and decimal numbers are omitted rather than coerced into successful counts. No extra note/detail round-trip or new write surface.
Validation: lead+aux final green. GitHub final gate OPEN/non-draft/CLEAN, required checks SUCCESS, adapter/smoke skipped. Local reviewer validation covered focused clis/xiaohongshu/ask.test.js 1 file/15, full XHS adapter 20 files/270, typecheck, build manifest 1227, docs-build, node --check ask/touched tests plus dist/src/main.js, diff-check clean, and local merge-tree clean.
Final head e3da72afcb.
Contract: SMZDM search remains a read-only listing command and now enriches rows with updated_at, zhi_count, buzhi_count, favorite_count, and comments while preserving a complete column set with stable defaults. Argument validation for --limit is strict and pre-navigation: only integer numbers or decimal digit strings are accepted, constrained to 1..100; exponent, hex, blank/coercive forms are rejected. Browser Bridge {session,data} envelopes are unwrapped at the boundary. Non-array extraction payloads typed-fail with CommandExecutionError instead of silently returning an empty list. Result URLs are canonicalized in the browser script and only trusted https://www.smzdm.com, https://post.smzdm.com, or trusted relative paths are kept; off-domain/non-https/malformed rows are skipped. Compact metrics such as 1.2万 and k/K counts normalize correctly. No new command/API abstraction or write behavior.
Validation: lead+aux final green. GitHub final gate OPEN/non-draft/CLEAN, required checks SUCCESS, adapter/smoke skipped. Local reviewer validation covered focused clis/smzdm/search.test.js 1 file/8, typecheck, build manifest 1227, docs-build, node --check touched SMZDM files plus dist/src/main.js, typed-error-lint and silent-column-drop no new, listing-id advisory unchanged, and diff-check clean.
Final head d41552312e.
Contract: xiaohongshu publish gains text-image publishing behind --card-text. --card-text/--images require at least one content source. Normal image suffix/path validation and text-image gif append are pre-navigation ArgumentError. Text-image flow enters 文字配图, writes/verifies each card, waits for a new active empty card before multi-card input, generates previews, then Next enters the standard editor before title/body/topics. Explicit --card-style must resolve/click or typed-fail with CommandExecutionError, with no silent fallback to 基础. After Generate/Next and image append, visible media count postconditions must prove cards and appended images landed. Publish/draft final success requires a success marker or leaving the publish page; warning-like text cannot fake success.
Validation: lead+aux final green on d4155231. GitHub final gate OPEN/non-draft/CLEAN with required checks SUCCESS and adapter/smoke skipped. Local reviewer validation covered publish tests 31/31, full Xiaohongshu 17 files/246 tests, typecheck, build manifest 1225, docs-build, doc coverage 164/164, production audit clean, node --check, diff-check, typed-error-lint/silent-column-drop no new, and merge-tree clean.
Adds Xiaohongshu saved and liked collection read commands.\n\nFinal review contract:\n- saved/liked are read-only current-user collection scrapers; no write, unlike, or favorite side effects.\n- --limit validates strictly in 1..100 before browser navigation.\n- Collection page location is read back after goto and after each scroll before trusting captures/DOM.\n- Location must be exactly https://www.xiaohongshu.com/user/profile/<resolvedUserId>; /login or login-wall text maps to AuthRequiredError; other host/path/profile drift maps to CommandExecutionError.\n- Browser Bridge {session,data} envelopes are unwrapped; malformed location, non-array intercepted requests, and non-array DOM extraction fail closed.\n- API/DOM notes require stable note id and xsec_token; output URLs round-trip to note/detail.\n- Auth/private/empty/malformed/API/parser/selector drift typed boundaries are preserved.\n\nValidation:\n- Lead and aux final green on ef12a197.\n- Final GitHub poll: open, non-draft, head ef12a197, statusCheckRollup empty only; merged under standing no-check override because content review is green and no conflict/dirty/content blocker remains.\n- Focused collection/saved/liked tests 3 files / 19.\n- Full XHS adapter 20 files / 253.\n- typecheck, build/manifest 1227, docs-build.\n- node --check touched collection files + dist/src/main.js.\n- typed-error-lint and silent-column-drop no new issues; listing-id advisory 13; diff-check clean.
Handles Xiaohongshu user login walls and hydration races without weakening read contracts.\n\nFinal review contract:\n- xiaohongshu/user remains read-only.\n- Output columns id/title/type/likes/url are unchanged.\n- Profile note URLs remain bound by profile user id + note id + xsec token.\n- Hydration retry waits only when initial user store/notes are not populated and the page is not a login wall.\n- First read with existing notes does not wait unnecessarily.\n- Real empty/private/deleted users still exhaust retry and return EMPTY_RESULT.\n- Initial profile login wall and scroll-continuation login wall both raise AuthRequiredError.\n- Login wall does not degrade into malformed snapshot, generic CommandExecutionError, or empty success.\n- Non-object snapshots, missing store, missing notes, or non-array noteGroups still fail closed with CommandExecutionError.\n- Browser evaluate errors are not swallowed.\n\nValidation:\n- Lead and aux final green on 0cf2dc27.\n- Final GitHub poll MERGEABLE/CLEAN; required checks SUCCESS; adapter/smoke skipped.\n- focused user/user-helpers/rednote tests 3 files / 44.\n- full XHS adapter 17 files / 232.\n- typecheck, build/manifest 1222, docs:build.\n- node --check touched user files + dist/src/main.js.\n- typed-error-lint and silent-column-drop no new issues; diff-check clean; current-main merge-tree clean.
Exposes paid/member metadata for Bilibili and YouTube video reads.\n\nFinal review contract:\n- Scope remains Bilibili/Youtube video read-only metadata; no download, write, or navigation contract changes.\n- Bilibili paid source is /x/web-interface/view data.rights plus upower and redirect_url fields.\n- Bilibili view payload is unwrapped from Browser Bridge {session,data} before reading paid fields.\n- Missing or type-drifted Bilibili paid metadata fails closed with CommandExecutionError rather than defaulting to free/non-member.\n- YouTube source is watch bootstrap playabilityStatus plus locale-independent BADGE_STYLE_TYPE_MEMBERS_ONLY, after Browser Bridge unwrap.\n- YouTube requires string playabilityStatus/playabilityReason and boolean membersOnly; malformed payload fails closed.\n- Existing row identity and output contract are preserved with metadata additions only.\n\nValidation:\n- Lead and aux final green on de431644.\n- Final GitHub poll MERGEABLE/CLEAN; required checks SUCCESS; adapter/smoke skipped.\n- current-main merge-tree clean.\n- Targeted Bilibili/Youtube video tests 2 files / 18.\n- Full Bilibili + YouTube adapters 15 files / 144.\n- build manifest 1222, typecheck, docs-build, doc coverage 164/164.\n- node --check touched files/tests, diff-check clean.\n- typed-error-lint and silent-column-drop no new issues.
Adds a paid-content precheck before Bilibili download side effects.\n\nFinal review contract:\n- bilibili/download remains a download command, but checks /x/web-interface/view before any downloadMedia or yt-dlp call.\n- rights.pay covers VIP/paid OGV; rights.ugc_pay or rights.arc_pay covers UGC single-purchase paid videos; is_upower_exclusive covers charging-exclusive content.\n- Paid hits throw structured PAID_CONTENT / NOPERM before download side effects.\n- VIP content is allowed only when /x/web-interface/nav returns vipStatus === 1.\n- --force fully skips precheck for already-entitled users or cases where cheap entitlement probing is insufficient.\n- Transport reject or non-zero view API code preserves old compatibility and does not block, but successful code:0 envelopes must include object data and object data.rights or fail closed with CommandExecutionError.\n- Free video path, BVID/URL identity, title/cookie/output path, quality format, and yt-dlp missing-result semantics are preserved.\n\nValidation:\n- Lead and aux final green on a7297072.\n- Final GitHub poll MERGEABLE/CLEAN; required checks SUCCESS; adapter/smoke skipped.\n- Focused clis/bilibili/download.test.js 1 file / 7.\n- Full Bilibili adapter 10 files / 94.\n- typecheck, build/manifest 1222, docs:build, node --check touched download files + dist/src/main.js.\n- typed-error-lint and silent-column-drop no new issues; diff-check clean; current-main merge-tree clean.
Adds targeted Discord desktop-app read/navigation commands with fail-closed identity checks.\n\nFinal review contract:\n- Surface remains read/navigation only: goto, channels, servers, threads, read, and thread-read; no Discord write command is introduced.\n- Browser Bridge evaluate results are unwrapped and shape-guarded at the Node boundary for route state, list rows, messages, and thread lists.\n- True empty list/read/thread-read results map to EmptyResultError; malformed rows or browser output map to CommandExecutionError.\n- Targeted read and thread-read verify message rows bind to the requested channel/thread via channel_id; stale, wrong-channel, parent-channel, or missing identity rows typed-fail.\n- List rows require stable identities: channels require Channel/guild_id/channel_id/url, servers require Server/guild_id/url, threads require Thread/guild_id/channel_id/thread_id/url.\n\nValidation:\n- Lead final and aux final green on e84a7d85.\n- merge-tree clean against base 8ed8ca26.\n- Discord app tests 19/19.\n- build manifest 1225, typecheck, docs-build, doc coverage 164/164, node --check touched files/tests, diff-check.\n- typed-error-lint and silent-column-drop no new issues.\n- GitHub required checks SUCCESS; adapter/smoke skipped.
Adds the no-navigation `quickCheck` to each adapter's
registerSiteAuthCommands config so `opencli auth status` (PR #1879) resolves
login state in quick mode (CDP getCookies, no per-site goto) instead of
reporting `unknown`.
- quickCheck reuses each adapter's existing poll cookie gate (has<Site>Cookie),
which is a logged-in-only, no-nav check returning boolean.
- Deliberately NOT wired (stay `unknown` in quick mode, available via --full):
- gitee/hf/deepseek/quark/reuters/zsxq: no reliable logged-in cookie; they
detect via no-nav fetch / localStorage / Bearer which need the site origin.
- doubao/ke/coupang/manus: session cookie is present for anonymous users, so a
cookie quickCheck would false-positive — `unknown` is more honest.
Live: auth status --site resolves logged_in/not_logged_in for cookie-gate sites
(v2ex/github/zhihu/claude/taobao/twitter/bilibili) in quick mode; excluded
sites report unknown. Audits new=0/new=0; suite 5054 passed.
Adds site login/whoami coverage for 55 additional auth adapters using the shared site-auth helper, including the final gitee/hf/v2ex/deepseek/quark batch and fixes from live validation.
Review follow-up:
- remove direct email output from ChatGPT/Grok/Gemini/Qwen whoami
- avoid DeepSeek email fallback as display name
- avoid Upwork first/last name output
- avoid leaking Boss wt2 session cookie as user_id
- rebase on latest main and regenerate cli-manifest.json
Validation:
- clean-HOME npm test: 5049 passed, 1 skipped
- npm run check:typed-error-lint: new=0
- npm run check:silent-column-drop: new=0
- npm run build
- npm run docs:build
- git diff --check
- dist list JSON smoke
- GitHub CI green