1592 Commits

Author SHA1 Message Date
jakevin 432cf23101 chore(linkedin): share people search auth helpers (#2358) 2026-08-23 23:48:37 +08:00
jakevin dfc13ea69b chore(linkedin): share messaging thread URL helpers 2026-08-23 23:28:45 +08:00
jakevin f2de8ab808 chore(linkedin): reuse shared unwrap in connect flows 2026-08-23 23:02:43 +08:00
jakevin b158a4a831 chore(linkedin): reuse shared unwrap in salesnav 2026-08-23 22:46:36 +08:00
jakevin 77a4e19f0b chore(linkedin): reuse shared evaluate unwrap
Reuses the existing LinkedIn shared evaluate envelope unwrap helper across general messaging commands while preserving subsystem-local copies for later batches.
2026-08-23 22:28:52 +08:00
jakevin de16476e26 chore(linkedin): share safety URL decoder
Moves the duplicate Node-side LinkedIn safety URL decoder into the site shared helper while preserving page-realm copies and their browser-local contract.
2026-08-23 22:11:25 +08:00
jakevin e48aac2114 chore(xueqiu): share html stripping helper 2026-08-23 21:52:38 +08:00
jakevin dcab73fb95 chore(browser): share CDP page capabilities 2026-08-23 21:37:09 +08:00
jakevin 6058c1536a chore(instagram): share current user helper 2026-08-23 21:01:05 +08:00
jakevin 40999a5eee chore(instagram): share home navigation helper 2026-08-23 20:42:28 +08:00
jakevin a2dd8c2701 chore(slock): share task identity postcondition 2026-08-23 20:23:32 +08:00
jakevin 2929dfa2c0 chore(linux-do): share formatting helpers 2026-08-23 20:06:18 +08:00
jakevin 2beaf83d62 chore(zhihu): share answer normalization helpers 2026-08-23 19:53:24 +08:00
jakevin 9057441221 chore(zhihu): share answer target parser 2026-08-23 19:37:34 +08:00
jakevin 31d80af07d chore(grok): share site identity helpers (#2341) 2026-08-23 19:18:31 +08:00
jakevin c0c8e60b25 chore(12306): share limit normalization helper (#2340) 2026-08-23 19:00:31 +08:00
jakevin adddc5733a chore(twitter): share archive JSONL helpers (#2339) 2026-08-23 18:40:22 +08:00
jakevin 7145d8b5d0 chore(twitter): share user lookup URL builder (#2338) 2026-08-23 18:11:55 +08:00
jakevin 240930fc8c fix(twitter): repair block and hide reply flows
Fixes #2334, #2335, and #2336.\n\nRepairs Twitter block/unblock profile-state scoping and localized block menu matching, and makes hide-reply retry from the parent conversation using only the preceding article time permalink.\n\nLocal gates: focused block/unblock/hide-reply tests 20/20, full twitter tests 531/531, typecheck, build, validate twitter, typed-error lint new=0, silent-column-drop new=0, diff-check. Hosted checks terminal green on exact head 57d1927d.
2026-08-23 17:54:31 +08:00
jakevin bd4c1e39e1 feat(twitter): add muted word command
Add twitter mute-word <keyword> as a UI write command against the visible Twitter/X muted-word settings form. Confirmation only accepts click-after route transition, new success toast, or new exact muted-word row; pre-write targeting stays scoped to the settings surface.\n\nLocal gates: focused twitter write/block/unblock tests 21/21, full twitter tests 523/523, typecheck, build, validate twitter, diff-check. Hosted checks terminal green on exact head 114a7c7f.
2026-08-23 17:26:56 +08:00
jakevin ae86f7f5ff refactor(bilibili): share relation helpers
Share duplicated Bilibili follow/unfollow relation helpers in a site-local relation module while preserving command-specific validation text and the existing utils.js mock boundary.
2026-08-23 16:53:40 +08:00
jakevin 1d3c97e477 refactor(linkedin-learning): share API fetch helpers 2026-08-23 15:25:43 +08:00
jakevin 80d5d3d6c9 chore: remove orphan internal test hooks (#2330) 2026-08-23 15:02:06 +08:00
jakevin 07aee7cd9f chore(browser): remove dead internal error exports (#2329) 2026-08-23 14:50:15 +08:00
jakevin 14d4665f5d chore(tui): remove unused checkbox prompt (#2328) 2026-08-23 14:40:36 +08:00
jakevin a34705a67f chore(browser): remove legacy DOM click helpers (#2327) 2026-08-23 14:29:26 +08:00
jakevin 70890c7e26 chore(scripts): remove retired explore helpers (#2325) 2026-08-23 14:19:06 +08:00
jakevin 451cd0276c chore(browser): remove retired tab helpers (#2324) 2026-08-23 14:09:07 +08:00
jakevin ff1d59ffec chore(core): remove dead internal symbols (#2323) 2026-08-23 14:00:21 +08:00
jakevin 9846c59f1c fix(adapter): copy shared deps on eject (#2321) 2026-08-23 13:39:32 +08:00
bingame c45105d6d1 fix: 兼容 Windows prepare 脚本 (#2271)
* fix: 兼容 Windows prepare 脚本

* fix(prepare): handle native package-manager runners

---------

Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-08-23 03:29:00 +08:00
Bo Liu f909f1e27f fix(browser): prefer the main Electron window over routed auxiliary windows (#2244) 2026-08-23 03:26:27 +08:00
WeiHaoxuan 078984204c fix(completion): fall back on invalid manifests (#2298)
* fix(completion): fall back on invalid manifests

* test(completion): cover all manifest fallback paths

---------

Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-08-23 03:25:57 +08:00
WeiHaoxuan 60a94cab27 fix(plugin): honor caret ranges below 1.0.0 (#2299) 2026-08-23 03:25:22 +08:00
WeiHaoxuan aa0e6b0743 fix(args): reject invalid integer values (#2300) 2026-08-23 03:24:49 +08:00
Mai Hoàng Anh Vũ 083f78acf5 fix(chatgpt): use data-turn to detect upload previews vs generated images (#2292)
`chatgpt image` with 2+ --image attachments could return the just-uploaded
reference thumbnails instead of the actual generated image.

isUserUploadPreview() classified an <img> as a user upload (to exclude it
from waitForChatGPTImages' before/after diff) using two signals, both
broken against ChatGPT's current DOM:

- turn.querySelector('h4')?.innerText: the heading is visually hidden, so
  real Chrome's innerText resolves to '' (layout-dependent) even though
  .textContent correctly reads "You said:" / "ChatGPT said:". jsdom's
  innerText is always undefined, so the test suite never exercised this
  path either - it happened to pass via the aria-label/alt fallback below.
- button[aria-label^="Open image:"]: ChatGPT's current label for a
  multi-file attachment reads "Open image N of M: <name>", which no
  longer starts with "Open image:", so this selector stopped matching.

With both signals dead, classification fell through to alt-text sniffing.
Right after upload, an attachment thumbnail's alt/aria-label haven't
populated yet, so for a poll or two every uploaded image is misclassified
as "new". waitForChatGPTImages returns as soon as two consecutive polls
agree on a URL set - long enough for that transient window to win when
multiple attachments are involved, so it can return the uploads instead of
the real result.

Fix: check the turn <section>'s own data-turn="user"|"assistant"
attribute first. It's set structurally as soon as the turn mounts, not
tied to the attachment's async metadata, so it isn't subject to the race.
Keep the heading/aria-label checks as a fallback (now using textContent
and a substring aria-label match) for markup that lacks data-turn.

Verified live against chatgpt.com: reproduced the bug with 3 reference
images, then confirmed the patched build returns exactly the one real
generated image instead of the 3 uploaded thumbnails.

Adds regression tests for both the data-turn race and the aria-label
format change; confirmed both fail against the pre-fix code.


Claude-Session: https://claude.ai/code/session_01L29nrhaeQ4W5rjNr27z47h

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 03:21:56 +08:00
Coco-cielleo da333c99a7 fix(xiaohongshu): scope note title/desc/author to #noteContainer (#2317)
* fix(xiaohongshu): scope note fields to #noteContainer

`#detail-title, .title` was queried against the whole document. A note
detail page also renders a recommendation feed whose cards each carry a
`.title`, and `querySelector` returns the first match in document order.
For a note with no title of its own (`#detail-title` absent) the selector
fell through to that feed and reported an unrelated card's title as the
note's title -- on one real note, two consecutive runs returned two
different unrelated titles while the note itself has no title at all.

Scope title/desc/author to `#noteContainer` (falling back to `document`
for older layouts). This is the same class of fix already applied to the
`.interact-container` counts a few lines below.

Also adds JSDOM regression tests for NOTE_EXTRACT_JS, following the
pattern used in clis/aibase/news.test.js.

* fix(xiaohongshu): tighten note fallback scope

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-23 03:21:27 +08:00
一禅小和尚 b173b176aa fix(xiaohongshu): scan text-card media from publish roots (#2297)
* fix(xiaohongshu): detect composer media from document.body

opencli's currentComposerMediaCount() picked the composer root via
titleEl.closest('form, [class*=publish], ...'), but Xiaohongshu's new
React DOM renders the image/card editor in a different subtree, so the
matched root never contained the generated media and the count was
always 0. That broke the native '--card-text' (文字生成图片) flow with
'expected at least N visible media item(s), got 0'.

- Use document.body as the scan root so generated cards are found.
- Add 'image, svg' to the media selector for completeness.

This is the maintained fork of @jackwener/opencli (liuxinyea/OpenCLI).

* fix(xiaohongshu): scope text-card media count

* fix(xiaohongshu): keep publish media scan scoped

---------

Co-authored-by: liuxinye <liuxinye@zingfront.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-23 03:21:01 +08:00
Bo Liu ca25f148d6 fix(twitter): fail typed when a write command does not go through (#2256)
* fix(twitter): fail typed when a write command does not go through

* fix(twitter): preserve uncertain write outcomes

---------

Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-08-23 03:09:22 +08:00
HARRY-BEAR d981c8b1f3 fix(chatgpt): filter Chinese user-upload previews from generated images (#2261)
* fix(chatgpt): filter user-uploaded images in Chinese UI and allow large image payloads

- chatgpt image adapter: the attachment filter in getChatGPTVisibleImageUrls
  only matched the English 'Open image:' button label and English keywords
  (upload/uploaded/attachment). In the Chinese ChatGPT UI the button is
  labeled '打开图片:用户上传的图片' (Open image: user uploaded image) and
  the image alt is empty, so user-uploaded reference images escaped the
  filter and were reported as generated results (the original photo was
  downloaded instead of the generated image). Add the Chinese button label
  prefix and the '上传' keyword to the filter.
- daemon: raise MAX_BODY from 1 MB to 32 MB. The chatgpt image upload
  fallback (base64-in-evaluate) serializes the image into the command body;
  a typical 2 MB photo becomes a >1 MB base64 payload and the daemon
  rejected it with a connection reset ('fetch failed').

* test(chatgpt): cover Chinese upload previews

---------

Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-08-23 03:05:22 +08:00
HARRY-BEAR 7323deb885 fix(1688): extract detail images from shadow DOM with lazy-render scrolling (#2272)
* fix(1688): extract detail images from shadow DOM with lazy-render scrolling

The product detail section lives inside the shadow root of a custom
element (v-detail-e with class html-description). Plain CSS selectors
like `.html-description img` cannot pierce shadowRoot, so the detail
group never matched any element and detail_images was always empty.

Fix by collecting all img/source elements (walking shadow roots) and
checking ancestry through the shadow host chain with closest(), plus
scrolling further and settling on the detail container so its lazy
content renders before extraction.

Adds a jsdom regression test covering shadow-root detail images,
light-DOM main gallery images, and plain-class detail containers.

* fix(1688): address review — restore deleted tests, dedupe the traversal, poll instead of sleep

Review fixes on top of the shadow-DOM detail extraction:

- Restore the two tests this PR replaced. `normalizeAssets` (grouping,
  counts, blob: filtering) and `normalizeMediaUrl` both lost all coverage;
  the 14 -> 15 test count hid that, since three new cases were added while
  two existing ones were removed. Now 17, with the new jsdom cases
  alongside the originals rather than in place of them.

- Inject the module-level `inDetailContainer` via toString() instead of
  keeping a hand-copied twin inside the evaluated script. The copy meant
  the unit tests exercised code that was not what ran in the page, and the
  two could drift silently. This is the convention already used in
  clis/gov-policy/search.js and clis/codex/sidebar.js.

- Check `node.closest(selector)` at each level of the walk, not only
  `host.matches(...)`. A detail container that is a plain element inside a
  shadow root rather than the host itself was previously missed.

- Replace `autoScroll(6) + autoScroll(4) + wait(3)` with one autoScroll,
  a scrollIntoView, and a bounded poll on the deep detail-image count.
  autoScroll keeps no state between calls, so 6+4 was identical to a
  single 10 and the comment about a "second confirmation pass" described
  something that did not happen; the fixed 3s wait was then paid on every
  call even when the content had already rendered. The poll returns as
  soon as the count is stable, capped at ~5s.

- Use an <img> rather than a <source srcset> in the shadow fixture:
  defaultSrcProps does not read srcset, so asserting on it implied
  coverage the adapter does not actually have.

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-23 03:01:18 +08:00
Kagura fea093fe05 fix(extension): omit credentials from daemon ping (fixes #2278) (#2282)
* fix(extension): omit credentials from daemon ping (fixes #2278)

A large localhost cookie jar can push the extension ping past the Node default header limit. The daemon then responds 431, but the extension silently retries and never reaches the WebSocket connection.

Send the ping without credentials so browser cookies are not attached, and log non-OK HTTP statuses so future probe failures remain visible. Keep connection errors quiet because a stopped daemon is the expected idle state.

* build(extension): rebuild dist for daemon ping credentials:omit

extension/dist/background.js is a tracked artifact (.gitignore un-ignores
it via !extension/dist/), so the source-only change in 62d1f202 never
reached the bundle Chrome actually loads — the #2278 431 wedge would have
persisted in production despite the fix being merged.

Rebuild only; no source change. Diff is exactly the credentials:'omit'
and the non-OK warn from the parent commit.

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-23 02:57:31 +08:00
aliouswe 69454ae9e4 fix(doctor): use windowless connectivity probe (#2206) 2026-08-23 02:57:21 +08:00
haoyu zhang 5d6594f12e fix(boss): restore read-only job search and detail (#2291)
* fix(boss): read current search and detail pages

* fix(boss): harden read-only job discovery

* fix(boss): address review — drop the site-auth fork, flatten detail columns

Review fixes on top of the read-only search/detail restore:

- Drop the adapter-local fork of `_shared/site-auth.js`. The fork had
  already diverged — it lost `normalizeRefreshResult` and the
  `config.refresh` branch, which silently removes `opencli auth refresh`
  support for boss. `clis/_shared/site-auth.js` is imported by 65
  adapters; `adapter eject` not copying `_shared/` is a real bug, but it
  affects every one of them and belongs in `src/cli.ts` eject, not in a
  per-adapter copy. Also removes the tautological test that only
  `readFileSync`'d auth.js and asserted on its own import string.

- Flatten `detail`'s row and `columns` back to scalars. The nested
  `location` / `recruiter` / `companyInfo` objects rendered as
  `[object Object]` in table, plain, csv and markdown output, because
  every renderer coerces cells with `String(v)` (`src/output.ts`) and
  none resolves dotted paths — only `-f json/yaml` was usable. Field
  names match the previous flat contract.

- Fix `stageText`, which matched `/融资|上市|不需要融资/` and therefore
  never matched the common `D轮及以上` / `天使轮` forms, leaving `stage`
  permanently empty. The industry filter directly above already excluded
  `轮`.

- Prefer BOSS's semantic `.text-city` / `.text-experiece` / `.text-degree`
  classes over positional `limits[0..2]`, which shifted every field when
  the header gained or lost a node. Positional order remains a fallback.

- Drop the `district` column instead of shipping one that is always null:
  the extractor hardcoded `districtText: ''` and the rendered page
  exposes no district anywhere in the captured fixture.

- Classify a login bounce as `AuthRequiredError`. The retry loop swallows
  every read error, so a session pushed to the login wall previously
  surfaced as "did not expose a complete job posting" — the API path this
  replaced got that classification for free via `assertOk`.

Regenerates `cli-manifest.json` for the new columns.

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-23 02:56:56 +08:00
jakevin 5c0aa36a60 fix(adapters): handle recent extraction drift (#2293)
* fix(adapters): handle recent extraction drift

* fix(adapters): narrow issue-sweep fallbacks

Reject Douban login redirects before accepting cookie identity, avoid unrelated profile-link fallbacks, keep Twitter profile metadata to the proven query-id/features contract, and drop the Douyin error-wording-only change whose test did not execute the browser guard.

* fix(notebooklm): share exact host allowlist

---------

Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-08-23 02:51:20 +08:00
fanxiaoyu db2718fc13 fix(ke,douban): update auth anchors for 2026-08 site redesign (#2305)
* fix(ke,douban): update auth anchors for 2026-08 site redesign

- ke: username moved to .typeShowUser (masked phone e.g. 15****93) in new
  SSR header; legacy .userNick/.user-name/.myInfo anchors no longer render,
  causing false AUTH_REQUIRED despite a valid lianjia_token cookie.
- douban: .bn-more href changed from /people/<id>/ to /passport/setting/,
  breaking the user_id regex. Login detection now keys on account element +
  ck cookie; user_id falls back to any /people/<id>/ link or the dbcl2
  cookie, and may be empty on the new homepage without misreporting auth.

Verified locally via shadow adapters in ~/.opencli/clis: both whoami
commands return logged_in:true, and downstream commands (ke zufang,
douban search) return live data again.

* fix(ke): keep only the ke auth anchor widening; drop the douban change

The douban half of this PR is superseded by #2293, which rewrites
verifyDoubanIdentity with a strictly better mechanism, and it introduced
three problems of its own:

- It read `document.cookie` inside the page, but this file already reads
  cookies at CDP level in hasDoubanSessionCookie and discards them. dbcl2
  is HttpOnly, so the in-page path can never see it — the PR's own comment
  admits this ("HttpOnly 时 JS 取不到,静默跳过").
- `document.querySelector('a[href*="/people/"]')` takes the first
  /people/ link anywhere on the douban homepage, which renders a friends'
  activity feed full of other users' profile links, so whoami could
  silently report a stranger's user_id. #2293 removed this exact selector
  for this exact reason.
- The `ck` guard was unreachable in practice: verifyDoubanIdentity
  already throws upstream when neither dbcl2 nor ck exists, so the new
  branch only added another false AUTH_REQUIRED path to a change whose
  stated purpose was removing false AUTH_REQUIRED.

The ke half stands on its own: prepending `.typeShowUser a span,
.typeShowUser a` while keeping every previous anchor is purely additive
and cannot regress a profile where the old anchors still resolve.

---------

Co-authored-by: fanxiaoyu0 <fanxiaoyu0@users.noreply.github.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-23 02:49:32 +08:00
ele-yufo 42755b9e93 fix(douyin): restore stats via creator item list after metrics_trend retired (#2307)
* fix(douyin): restore stats via creator item list after metrics_trend retired

`item_analysis/metrics_trend` now answers every request with `status_code 4`,
so `douyin stats` fails for all works regardless of age or account (#2197).
Replaying the endpoint with the old unix-timestamp params, with `start_date`/
`end_date`, and with `item_id` instead of `aweme_id` all return the same code,
so the endpoint is gone rather than re-shaped.

The creator item list still serves the full per-work metric set the creator
dashboard renders — 26 fields including view_count, bounce_rate_2s,
completion_rate_5s, avg_view_second, cover_show, cover_click_rate,
fan_view_proportion and subscribe_count — which is a superset of the four
counters metrics_trend used to return. Walk its cursor and pick the requested
work out of the page.

Two details worth keeping:

- The endpoint serializes the work id as a JSON number, so the browser has
  already rounded it past IEEE-754 integer precision before the adapter sees
  it. `sameAwemeId` compares numerically as a fallback, otherwise every lookup
  misses.
- A work that exists but carries no metrics is reported with a distinct hint
  from a work that is absent from the account, so callers can tell "not yours /
  wrong id" apart from "no data yet".

Verified live against a logged-in creator account: 26 metrics returned for a
published work, EMPTY_RESULT for an unknown id, ARGUMENT for a malformed id.

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

* docs(douyin): state what the item list provides instead of naming the dead endpoint

A comment that names a retired endpoint puts it back into the reader's choice
space. The PR description carries the history; the source should carry the
current contract.

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

* chore: rebuild cli-manifest for the douyin stats description

The adapter description changed, and cli-manifest.json is generated and checked
in, so CI's freshness gate fails until it is rebuilt.

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

---------

Co-authored-by: ele-yufo <gentanaka606@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 02:45:01 +08:00
Bo Liu 3c337d7385 fix(douyin): search hashtags through the live endpoint and report empty responses (#2254) 2026-08-23 02:44:38 +08:00
fanxiaoyu 124602d437 fix(dianping): correct 4 wrong cityIds in static city map (#2280)
The trailing rows of CITY_ID had incorrect cityIds, causing dianping
search to silently fall back to the cookie's default city for these
cities. Verified against live https://www.dianping.com/<slug> resolution:

- kunming 昆明: 25  -> 267
- fuzhou  福州: 110 -> 14
- xiamen  厦门: 14  -> 15
- hefei   合肥: 26  -> 110

(fuzhou and hefei previously shared the same id 110, indicating the
last few rows were transposed when the table was hand-written.)

Co-authored-by: fanxiaoyu0 <fanxiaoyu0@users.noreply.github.com>
2026-08-23 02:39:57 +08:00
Bo Liu 50565efdde fix(browser): thread the preferred profile through readiness and status checks (#2262)
The /status path dropped preferredContextId, so a configured default read as multi-profile ambiguity (#2259).
2026-08-17 19:34:12 +08:00