1493 Commits

Author SHA1 Message Date
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
genoooool a86d64705c docs(plugin): add X Article publisher to examples (#2190) 2026-08-09 23:46:03 +08:00
bulexu a93f6e71bb fix(chat): preserve selected models and wait for Kimi replies (#2266)
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-09 22:52:27 +08:00
Bo Liu 27ba33fcb8 test(launcher): skip pgrep-backed app detection tests on Windows (#2269)
findAppProcessPids intentionally returns [] on win32; five of the six app-scoped tests from #2232 fail on the Windows CI shard and the sixth passes only vacuously.
2026-08-08 23:28:05 +08:00
Bo Liu 18f1ceba32 fix(codex): resolve the ChatGPT executable inside Codex.app (#2232)
* fix(codex): resolve the ChatGPT executable inside Codex.app

* fix(codex): try the ChatGPT executable first and sync the launch doc

* fix(codex): scope executable process detection to app bundle

* fix(codex): resolve symlinked app process identity

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-08 20:05:32 +08:00
Bo Liu 46dd226875 fix(discovery): warn when yaml adapters are skipped instead of dropping them silently (#2229)
* fix(discovery): warn when yaml adapters are skipped instead of dropping them silently

* fix(discovery): stay quiet for yaml adapters that already have a .js replacement

* fix(discovery): audit skipped yaml adapters in manifest path

* fix(discovery): require loadable js replacements for yaml warning suppression

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-08 19:51:49 +08:00
oliver 12831fc793 fix(discord-app): extract guild ids from navigation items (#2233)
* fix(discord-app): extract guild ids from navigation items

* fix(discord-app): scope guild nav extraction

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-08 19:09:02 +08:00
Bo Liu 2065a4f90e fix(twitter): read the profile link until it settles in whoami (#2253)
* fix(twitter): read the profile link until it settles in whoami

* fix(twitter): harden whoami identity settling

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-08 18:48:24 +08:00
Bo Liu da3eb951eb fix(twitter): stop reporting another tweet as the posted permalink (#2251)
* fix(twitter): stop reporting another tweet as the posted permalink

* fix(twitter): require fresh post success evidence

* fix(twitter): require fresh reply success evidence

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-08 18:25:50 +08:00
Bo Liu 865f5aa021 fix(instagram): download through the media info endpoint and expand ~ in --path (#2248)
* fix(instagram): download through the media info endpoint and expand ~ in --path

* fix(instagram): harden media info downloads

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-08 17:58:45 +08:00
Bo Liu ce5f3762a1 fix(gemini): fail typed on image failures and expand ~ in the output path (#2246)
* fix(gemini): fail typed on image failures and expand ~ in the output path

* fix(gemini): unwrap image bridge envelopes

* fix(gemini): clear transient image candidates

* fix(gemini): fail closed on malformed image probes

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-08 17:36:49 +08:00
Bo Liu e169cc19b3 fix(instagram): like and unlike posts through the post page controls (#2243)
* fix(instagram): like and unlike posts through the post page controls

* fix(instagram): verify post like persistence

* fix(instagram): confirm already-like state in feed

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-08 17:26:41 +08:00
Bo Liu 073c214507 fix(instagram): stop depending on web_profile_info for business accounts (#2238)
* fix(instagram): resolve business-account user ids without web_profile_info

* fix(instagram): harden business account fallback

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-08 16:55:48 +08:00
Bo Liu 05cfbee66e fix(codex): select slash-command picker options from send (#2239)
* fix(codex): select slash-command picker options from send

* fix(codex): unwrap bridge envelopes in send flows

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-08 16:41:14 +08:00
Bo Liu 9a689c98ef fix(chatgpt): never save in-progress canvas frames as generated images (#2237)
* fix(chatgpt): never save in-progress canvas frames as generated images

* fix(chatgpt): reject data image candidates case-insensitively

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-08-08 16:23:48 +08:00
Bo Liu 0850d8f4d4 chore(deps): bump js-yaml to 4.3.1 for GHSA-5p4m-2wfm-xmqj (#2267)
The advisory published 2026-08-06 fails the audit CI job on every branch; 4.3.1 is the patched release.
2026-08-08 16:14:24 +08:00
lingjiuu 8b9faef054 fix(weread-official): bump skill version to 1.0.4 (#2227) 2026-08-08 15:52:16 +08:00
ngcat 399c0de2a7 feat(twitter): add resumable likes and bookmarks archives (#2143)
* feat: port twitter full-sync and close-window hardening onto 1.8.6

Rebase our xfetch-oriented OpenCLI mods onto upstream main organically:
keep the 1.8.x likes/bookmarks media metadata and auth hardening, then
add --all/--resume-file/--output-file JSONL streaming with U+2028/U+2029
escaping, raise the full-archive page budget, retry browser lease close
failures, and expose browser tab current-window diagnostics.

* fix(twitter): preserve resume state when max-pages stops early

--max-pages is a safety budget, not archive exhaustion. Keep the resume
file and report complete=false so full-sync can continue instead of
restarting from the top.

* test(cli): expect browser tab current-window in structured help

The full-sync branch adds `browser tab current-window`, so the nested
tab help snapshot must count 5 commands instead of 4.

* fix(twitter): make archive resume state fail closed

* fix(twitter): reject mismatched archive resume output

---------

Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-07-30 20:36:27 +08:00
Alex Su dc9dde41f3 feat(pinterest): add Pinterest adapter suite (#2177)
* feat(pinterest): add Pinterest adapter suite

Adds `opencli pinterest` with 19 commands over Pinterest's internal resource API
(`POST /resource/<Name>Resource/<action>/`, form-urlencoded `source_url` + `data`,
`X-CSRFToken` + `X-Pinterest-PWS-Handler` headers, `bookmark` paging).

- Read: search-pins / search-boards / search-users, pin, user, user-pins,
  user-boards, board-pins, board-sections, download. Reads work anonymously
  because Pinterest issues a csrftoken to logged-out sessions too.
- Write: save (boardless repin lands in "Quick saves"), pin-create /
  pin-update / pin-delete, board-create / board-update / board-delete,
  board-section-create / board-section-delete.
- Deletes require `--confirm`; without it the command resolves and names the
  target, then exits non-zero via ArgumentError (pin title + board for
  pin-delete, pin count for board-delete, section title for
  board-section-delete).
- Boards are addressed by `<username>/<slug>`, a board URL, or the numeric
  `boardId` (BoardResource accepts `board_id` and reports the board's url, which
  is reused so the id path costs no extra round trip); sections by id or slug.
  Display names are not accepted — a name alone cannot say which account a board
  belongs to. A Pinterest site route such as a `/pin/<id>/` URL is rejected as
  such instead of being parsed as the board `pin/<id>`.
- Board URLs are percent-decoded before use: Pinterest hands out encoded slugs
  for non-ASCII board names, and posting those verbatim answers HTTP 404. Slugs
  are compared Unicode-normalized so an NFD-composed accent still matches.
- Sections can only be set by a follow-up move. PinResource/create and
  RepinResource/create accept a section key, answer HTTP 200, and file the pin
  at the board root anyway; only PinResource/update honours it (under
  `board_section_id`, not `section_id`). So `save --section` and
  `pin-create --section` create then move, and report the created pin id if the
  move fails rather than claiming success.
- Omitting an optional text flag leaves the field alone; passing an empty string
  clears it. Pinterest refuses link edits on pins it scraped, and answers 401
  for that, so its own message is surfaced rather than only "log in".
- Typed errors throughout: ArgumentError for bad refs, unknown sections,
  `--section` without `--board`, and limits (validated before any request, with
  no silent clamp); AuthRequiredError on 401 (and 403 on writes only, since
  reads are anonymous); CommandExecutionError for malformed payloads and
  unresolvable targets.

Live-verified end-to-end against a logged-in account: all 10 read commands, and
the full write cycle (board-create → board-section-create → board-update →
pin-create → pin-update → save → the three deletes, preview and confirmed),
including section placement checked on each pin's own `section` field, non-ASCII
board/section slugs, board-id addressing, and clearing a description. 131 tests;
full suite 6258 passed; `tsc --noEmit` clean; `opencli validate` 0 errors;
typed-error-lint and silent-column-drop both new=0; doc coverage 174/174.

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

* fix(pinterest): drop board-update --privacy empty default

`coerceAndValidateArgs` applies an arg's default and then enforces `choices`
against it, so `default: ''` on a public|secret flag rejected every run that
omitted `--privacy`:

    $ opencli pinterest board-update janedoe/my-board --name Foo
    error: ARGUMENT  Argument "privacy" must be one of: public, secret. Received: ""

Leaving the default off keeps the flag optional; the command already reads it
as `String(kwargs.privacy ?? '')`.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 20:31:59 +08:00
ele-yufo 7702d8d534 feat(midjourney): add image generation adapter (#2201)
* feat(midjourney): add complete browser adapter

* fix(midjourney): verify paid action settings

---------

Co-authored-by: yufo <yufo@MacBook-Pro.local>
Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-07-30 20:25:49 +08:00
INKWWW 0ae77bdedc feat(twitter): add bounded collection command (#2173)
* refactor(twitter): share user timeline transport

* feat(twitter): add bounded collection command

* fix(twitter): fail closed on incomplete collection timelines

---------

Co-authored-by: Hanyue Chen <hanyuec@Hanyues-MacBook-Pro.local>
Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-07-30 20:12:43 +08:00
jakevin a80e5a3d58 fix(facebook): handle current feed and profile DOM (#2200)
Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-07-30 01:27:36 +08:00
jakevin 0de1d56796 fix(tiktok): use the current explore feed endpoint (#2199)
Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-07-30 01:14:37 +08:00
jakevin 03ba558386 fix(boss): probe the current geek jobs route (#2198) 2026-07-30 01:07:44 +08:00
ele-yufo 77812f0e3d fix(douyin): walk work_list cursor and reject non-sec_uid input (#2196) 2026-07-29 20:30:07 +08:00
Ao Liu b4f5df9d19 fix(xiaohongshu): extract direct comment reply target (#2175) 2026-07-29 20:26:21 +08:00
Sebastion b58f26006d fix(autoresearch): pass claude prompt via stdin (#2184)
* fix(autoresearch): pass Claude prompt via stdin to prevent shell injection

The modify() function in autoresearch/commands/run.ts interpolated a prompt
string — built from git log messages and scope file names — directly into
a shell command executed by execSync. The double-quote escaping only handled
literal quotes, leaving $(...), backticks and backslashes able to trigger
command substitution.

Switch to the same pattern already used in autoresearch/commands/fix.ts:
pass the prompt via the execSync 'input' option so it is delivered on stdin
and never parsed by the shell.

* fix(autoresearch): invoke Claude without a shell

---------

Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-07-29 20:19:58 +08:00
Bo Liu ab28a1f2eb fix(amazon): honor the input marketplace instead of rewriting to amazon.com (#2185)
* fix(amazon): honor the input marketplace instead of rewriting to amazon.com

* fix(amazon): reject amazon.<label>.<tld> look-alikes and localize the auth hint

* fix(amazon): allow only known marketplace domains

---------

Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-07-29 20:14:38 +08:00
Semianchuk Vitalii da8d32fc59 fix: escape pipes in markdown output and fix contributing docs (#2059)
markdown table cells with | in them were breaking the table layout —
added escaping so pipes get rendered as \| properly.

also fixed a few things in CONTRIBUTING.md:
- the page.evaluate example had a template injection issue where user
  input could break out of the template string. switched to passing
  args through the function parameter instead.
- pipeline adapter example was missing the required access field, so
  anyone following the guide would get a crash on registration
- removed a pointless .map(h => h) identity copy on table headers
- fixed consoleMessages('error') filter that was also returning warnings
2026-07-29 20:11:42 +08:00
Bo Liu 13635649e4 fix(download): keep a row-less table from crashing markdown conversion (#2187)
* fix(download): keep a row-less table from crashing markdown conversion

* test(download): pin row-less table text with a caption fixture
2026-07-29 20:09:37 +08:00
Felo Restrepo 89fe2f2288 fix(twitter article): include images from atomic blocks in markdown output (#2189)
* fix(twitter article): include images, canonicalize URLs, add metadata fields

The article adapter skipped atomic blocks entirely, silently dropping all
images from Twitter article markdown output.

This patch:
1. Resolves atomic blocks -> entity -> mediaId -> media_entities -> image URL
   and emits images as ![caption](url?format=jpg&name=large) inline.
2. Canonicalizes pbs.twimg.com URLs to the ?format=<ext>&name=large form
   used by the standard Twitter media CDN (matches reference clipping format).
3. Adds two new output columns: published_at (from tweet.legacy.created_at)
   and preview_text (from articleResults.preview_text) to support building
   Obsidian-style frontmatter at save time.

Tested with https://x.com/0xblacklight/status/2069503920918106370
10 images with captions, canonical URLs, all metadata populated.

* fix(twitter/article): resolve media by Draft entity key

---------

Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-07-29 20:06:23 +08:00
Haoqian b866dbeebe fix(huodongxing): handle temporary busy event pages (#2103) 2026-07-29 19:58:50 +08:00
Zhongyue Lin 03c0157614 fix(twitter/profile): recover counts + bio after X relocates them out of legacy (#2188) (#2193)
* fix(twitter/profile): recover counts + bio after X relocates them out of legacy (#2188)

`twitter profile` returned followers/following/tweets/likes = 0 and an empty
bio while name/screen_name/created_at/verified stayed correct. X moved the count
fields (followers_count/friends_count/statuses_count/favourites_count) and the
bio (description) out of `result.legacy` into a new container — the same drift
#1745 handled for name/created_at by reading `result.core`.

Rather than hard-code the (unknown) new path, resolve each field from its known
homes first (legacy → core → top-level result), then fall back to a bounded
breadth-first search that returns the shallowest match. The BFS refuses to cross
into containers describing a *different* entity (pinned_tweet, entities, media,
…) so it can never report an embedded tweet's favourites_count as the user's
likes or its text as the bio — a wrong-but-confident value would be worse than
0 / ''. This restores the counts/bio today and stays robust if X relocates them
again.

Legacy-path responses resolve identically (existing test unchanged). Adds
offline regression tests for the relocated-field case, legacy precedence over a
deeper decoy, the embedded-tweet guard, all-missing fallbacks, and resolver
type/empty handling.

* fix(twitter/profile): map observed current schema

---------

Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-07-29 19:58:11 +08:00
Jian Cui da3c26e610 fix(1point3acres): detect login via current Discuz X user-panel markup (#2145)
whoami and login-gated commands reported AUTH_REQUIRED for logged-in users
because the identity probe only matched the legacy Discuz member panel
(`#um .vwmy h4 a`), which the site no longer renders.

- Match the current header username link (`a[title="访问我的空间"]`) while
  keeping the legacy selectors as fallbacks; the existing uid regex already
  handles the `space-uid-<uid>.html` href.
- Also accept the logged-in header menu ids (`#g_upmine`, `#extcreditmenu`)
  as a login signal, so a future wording/markup change of the username link
  does not reintroduce a false AUTH_REQUIRED.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 19:53:37 +08:00
Zhongyue Lin a8dddcb291 fix(facebook/search): preserve query identity + drop redirect shims (#2090) (#2194)
* fix(facebook/search): preserve query identity + drop redirect shims (#2090)

The #2126 extractor deduped and reported result URLs as `origin + pathname`,
dropping the query string. But `permalink.php?story_fbid=…`, `story.php?…` and
`watch/?v=…` carry their identity in the query — so two *different* posts or
videos collapsed into a single row and only the first survived dedup.

Add `entityKey(u)` that keeps only the identity params (story_fbid, fbid, id, v,
story_id) and strips FB's per-render tracking nonces (__cft__, __tn__, ref).
Distinct posts now stay distinct, while the same post rendered twice with
different nonces still dedupes to one row. Vanity paths without identity params
keep collapsing to the bare pathname (unchanged).

Also reject `l.` / `lm.` `facebook.com` hosts: their `/l.php?u=…` outbound-link
wrappers passed the host regex and the vanity path catch-all, leaking external
redirect shims into the results.

Adds offline regression tests for distinct permalink/watch identities, nonce
dedup, and the redirect-shim guard.

* fix(facebook): scope query identity by destination

---------

Co-authored-by: OpenCLI-sol <opencli-sol@users.noreply.github.com>
2026-07-29 19:49:06 +08:00
AriesWarrior 254c54a99b fix(boss): distinguish environment rejection from auth expiry (#2127) 2026-07-29 19:43:59 +08:00
Bo Liu 4d859546d3 chore(deps): bump js-yaml to 4.3.0 for GHSA-52cp-r559-cp3m (#2186) 2026-07-29 19:39:33 +08:00
Bo Liu 5256711a25 enrich(ctrip): expand the adapter across Ctrip's travel verticals (#2156)
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
* enrich(ctrip): add train ticket search command

ctrip search already suggests railway stations but there was no way to query the
actual departures. ctrip train <from> <to> --date fills that gap on the public
trains.ctrip.com list page, browser-mode + cookie like flight/hotel-search. Rows
are read by stable class-keyed fields rather than positional innerText;
incomplete cards are dropped, not sentinel-filled.

* enrich(ctrip): add hotel detail command

Single-hotel profile from the detail-page SSR: rating sub-scores, hot facilities, check-in/out policy.

* enrich(ctrip): add bus ticket search command

Intercity coach search via the newbus results deep link (landing SPA does not hydrate under the bridge).

* enrich(ctrip): add ferry ticket search command

Passenger ferry sailings via the ship.ctrip.com results deep link, sibling of bus.

* enrich(ctrip): add cruise package search command

Resolves a departure port name to its legacy per-port code, then reads the .route_info cards.

* enrich(ctrip): add tour package search command

Group and self-guided tour search via the vacations sv=<destination> deep link, stable-class cards.

* enrich(ctrip): add flight+hotel package search command

Shares the vacations product extractor with tour (freetravel section); folds a 万 count multiplier into the shared parser.

* enrich(ctrip): raise CommandExecutionError on rendered-but-unparsed results

Matches the drift handling bus/ferry/train use, so genuine-empty stays EmptyResultError.

* enrich(ctrip): generalize shared list helpers, drop dead train constants

parseListLimit / parsePlaceName replace the train-named helpers now reused across bus/ferry/cruise/tour/package with neutral hints; ferry ship-name/duration read by pattern, not position.

* enrich(ctrip): add attraction listing command

* enrich(ctrip): add round-trip flight search command

* enrich(ctrip): scope attraction to city id and harden flight-round

* fix(ctrip): repoint one-way flight to Ctrip's migrated .flight-item cards

* fix(ctrip): harden travel adapter boundaries

* fix(ctrip): preserve raw limit strings

* test(ctrip): avoid adapter src import

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-07-21 01:58:26 +08:00
Bo Liu 0124c4eb7d feat(trip): add Trip.com international adapter (#2158)
* feat(trip): add Trip.com international flight search adapter

Trip.com is the English-facing sibling of the ctrip adapter. trip flight
searches worldwide one-way flights, browser-mode + cookie like ctrip flight.
Results are read from .result-item cards by stable data-testid anchors rather
than positional innerText; incomplete cards are dropped, not sentinel-filled.

Closes #2157

* enrich(trip): add hotel-search command

* enrich(trip): add hotel detail command

Single-hotel profile from the detail-page SSR (same shape ctrip hotel uses); also documents the existing hotel-search command.

* enrich(trip): add round-trip flight search command

Reuses the shared .result-item flight extractor against a triptype=rt search URL.

* enrich(trip): rename parseFlightLimit to parseListLimit

The 1-50 limit parser is shared by hotel-search and both flight commands, so a neutral name reads truer than the flight-specific one.

* enrich(trip): add attractions and experiences search command

Anchors on each things-to-do card's stable detail link (name + per-row url) and reads rating/reviews/booked/price by data-format pattern, since the cards use hashed CSS-module classes.

* enrich(trip): add train route timetable command

Reads the per-country SEO route timetable (departure/arrival times, stations, duration, changes) by stable class fields; per-journey fares sit behind the booking step.

* enrich(trip): add car-rental listing command

* enrich(trip): add airport-transfer listing command

* enrich(trip): add tour-package search command

* enrich(trip): add public destination-suggest command

* enrich(trip): add flight+hotel package search command

* docs(trip): note eSIM plans surface via attraction search

* enrich(trip): add live-promotions deals command

* enrich(trip): treat empty deals parse as drift, not empty result

* enrich(trip): split tour no-match (empty) from schema drift

* fix(trip): type public fetch drift failures

* fix(trip): require package flight identity

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-07-21 01:35:12 +08:00
cypggs c1ee31d063 feat(kimi/usage): read quota from membership subscription page (#2104)
* feat(kimi/usage): read quota from membership subscription page

Replace the /code/console page with /membership/subscription?tab=quota so the command surfaces the total usage percentage plus 5h/7h rate limits, gift quota, and booster balance.

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

* feat(deepseek/usage): add usage command for platform.deepseek.com

Reads DeepSeek platform usage data from https://platform.deepseek.com/usage
via internal API (get_user_summary) for account-level data and DOM extraction
for time-dimension summary cards.

Output columns:
- balance / bonusBalance (充值/赠送余额)
- cumulativeSpend (累计消费金额)
- monthlySpend / monthlyApiCalls / monthlyTokens (本月数据)
- currentTokenEstimation (当前可用 Tokens 预估)
- timePeriod / periodSpend / periodApiCalls / periodTokens (时间维度)

* test(usage): harden kimi and deepseek usage contracts

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-07-20 17:57:27 +08:00
pi-dal 0e73c3c2c2 feat(google): add images search adapter
Adds a read-only Google Images browser search adapter with typed parser boundaries.\n\nReviewed-by: codex-mini0\nReviewed-by: First-principles-0
2026-07-19 19:07:23 +08:00
AstroHan 1cb353d57a feat(chatgpt): add GPT-5.6 Pro model target
Adds the GPT-5.6 Pro ChatGPT model target with exact postcondition proof.\n\nReviewed-by: codex-mini1\nReviewed-by: First-principles-1
2026-07-19 19:04:02 +08:00
Zhongyue Lin 5add09f078 fix(eastmoney): correct mislabeled convertible ytm/remainingYears columns (#2109) (#2131)
* fix(eastmoney): correct mislabeled convertible ytm/remainingYears columns (#2109)

eastmoney convertible emitted systematically impossible ytm / remainingYears
(20/20 wrong). Cross-verification (12/12 fingerprint) shows the clist fields
were mislabeled: f239 is the putback trigger price (= convPrice × 0.7), not YTM,
and f238 is the pure-bond premium %, not the remaining term.

Relabel to the true semantics (pureBondPremiumPct / putTriggerPrice) and drop
the known-wrong ytm / remainingYears columns rather than keep emitting garbage.
Rename SORTS.ytm -> 'put-trigger' so --sort no longer claims to order by a value
it doesn't hold. Extract mapConvertibleRows() and add JSON-fixture tests.

Real YTM / remaining term aren't in this response's fields; adding the correct
f-codes needs a live push2 field dump cross-checked against jisilu — left as a
follow-up.

* fix(eastmoney): harden convertible field output

* fix(eastmoney): require convertible identity strings

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-07-19 17:30:00 +08:00
jakevin 9a53369bd6 fix(instagram): fetch user feed by username (#2153) 2026-07-19 17:25:17 +08:00
jakevin f0c07e781c fix(zhihu): accept http pagination next urls (#2151) 2026-07-19 17:25:03 +08:00
jakevin 95420d27ae fix(ths): fetch hot rank from public API (#2152) 2026-07-19 17:23:49 +08:00
Bo Liu f3586293d0 feat(toutiao): add recommend channel feed, fix hot --limit being ignored (#2149)
* feat(toutiao): add recommend channel feed, fix hot --limit being ignored

hot declared func(_page, kwargs) while browser:false commands receive a single
args object, so kwargs was always undefined and --limit silently fell back to
30. Its unit tests passed only because they called func(null, kwargs) by hand.

* fix(toutiao): require recommend article identity

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-07-19 17:15:36 +08:00
Ocean cde6198f3d feat(xiaohongshu): 评论输出 images 字段 + 滚动加载健壮性改进 (#2136)
* feat(xiaohongshu): extract comment images and improve scroll-loading robustness

Add an images field to xiaohongshu/rednote comments (top-level and nested
replies), scraped from .comment-picture galleries while excluding avatars
and inline note-content-emoji stickers. Also make the comment-loading
scroll loop keep going until --limit is satisfied or growth stalls for
several rounds (instead of bailing after one stalled round), and drive
scroll through the scroller element, scrollIntoView, and window.scrollTo
together since the actual scrollable ancestor varies by layout.

* fix(xiaohongshu): validate comment image payloads

* fix(xiaohongshu): scope comment image extraction

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-07-19 17:12:12 +08:00
jakevin b0f84c99c9 fix: click hit-testing + facebook feed/search DOM drift (#2076 #2071 #2089 #2090) (#2126)
* fix(browser): hit-test the click point and retarget handler-less nodes (#2076 #2071)

click() reported {clicked:true} whenever a CDP Input.dispatchMouseEvent
didn't throw, even when the synthetic click silently landed on an overlay
(#2076) or on a handler-less child like an <svg> icon whose click handler
lives on the wrapping <div> (#2071).

Now boundingRectResolvedJs (in a click-only mode) hit-tests the centre via
elementFromPoint and classifies it: target (element/descendant) and ancestor
(open shadow-DOM host or own wrapper — a CDP click still reaches the target)
are trusted; an unrelated overlay ('other') forces a direct DOM-click fallback.
On a miss it probes inset points for a hitting one. If the resolved node owns
no click handler, the click retargets to a nearby clickable ancestor so the
handler fires — cursor:pointer is excluded from that decision because it is
inherited. The result now surfaces click_method (cdp|js|ax), hit, and
retargeted so agents can tell a trusted click from the fallback. hover() and
dblClick() keep their original plain-centre behaviour (click-only opt-in).

Runtime tests execute the generated JS against a fake DOM (with cursor
inheritance modelled) covering target/ancestor/other, retarget, and probe.

* fix(facebook): extract modern feed posts via the action-menu anchor (#2089)

Modern facebook.com no longer wraps feed posts in [role="article"] nor
exposes the Like/Comment/Share aria-labels the fallback keyed on, so feed
extracted 0 rows. Add a container source that anchors on each post's
"Actions for this post" menu and walks up to the highest ancestor holding
exactly one such menu (stopping before page landmarks), a bounded
scroll-to-load loop so lazily-streamed posts render, all-digit decoy author
rejection, and hidden-char / Reels-carousel decoy filtering. jsdom fixtures
cover the modern shape and keep the legacy [role="article"] path working.

* fix(facebook): extract search results from role=feed entity links (#2090)

Modern /search/top renders results inside [role="feed"] as entity/content
links (people, pages, groups, posts) rather than [role="article"]/[role=
"listitem"], and seeds hidden-char decoy links back to /search. Rewrite the
adapter (pipeline -> func, so the extractor is unit-testable) to collect
anchors inside the feed, keep only real facebook.com entity/content hrefs,
and drop /search decoys, chrome links, off-domain spam, and obfuscated text.
Preserves the #625 navigate-before-extract guard. jsdom fixtures included.
2026-07-13 03:22:40 +08:00
jakevin 654019eeba fix: batch of 5 issue fixes (#1753 #2087 #2091 #2095 #2108) (#2125)
* fix(plugin): pass --ignore-scripts to plugin npm install (#1753)

Plugin repos are cloned from untrusted third-party Git URLs. Without
--ignore-scripts, `npm install` runs preinstall/install/postinstall
lifecycle scripts (of the plugin and every transitive dep) at install
time with the user's privileges. Adapter plugins don't need lifecycle
scripts — adapter code is loaded later by the discovery path — so deny
that execution vector unconditionally. Adds a test asserting the flag.

* fix(chatgpt): verify whoami via /api/auth/session, not legacy cookie (#2087)

verifyChatgptIdentity hard-gated on the legacy
`__Secure-next-auth.session-token` cookie before probing
/api/auth/session, so logged-in users on cookie-less sessions got a
false AUTH_REQUIRED. The session endpoint (200 + user.id) is
authoritative; drop the cookie precondition from verify. The login
`poll` keeps its cheap non-navigating cookie gate so verify (which
navigates) doesn't run every ~2s and yank the user off the OAuth form.
Also prefix-match the session cookie so the quickCheck/status/refresh
fast paths stop false-negativing on NextAuth chunked (.0/.1) cookies.

* fix(instagram): collect explore_grid media across nested layouts (#2091)

Instagram stopped populating the flat layout_content.medias[] path;
media now nest across mixed layout shapes (one_by_two_item.clips.items[]
.media, fill_items[].media, ...), so explore returned []. Recursively
walk each sectional item collecting every distinct node.media, dedupe by
pk/id/code (skipping descent into a collected media so carousel children
aren't counted as separate posts), and fall back to play_count for
clips/reels engagement.

* fix(extension): upload files via file-chooser interception (#2108)

DOM.setFileInputFiles with a nodeId/backendNodeId is rejected "-32000 Not
allowed" when the debugger is attached via chrome.debugger (crbug
928255), breaking file upload on every site. Switch setFileInputFiles to
the file-chooser interception flow: enable Page.setInterceptFileChooser-
Dialog, programmatically open the chooser, and use the backendNodeId from
the intercepted Page.fileChooserOpened event (which Chrome accepts). The
event listener is registered before the click and settles on any matching
event so a malformed one rejects fast. Includes the rebuilt bundle.

* fix(chatgpt): use page.sleep in the poll loops #2099 missed (#2095)

#2099 converted the main streaming loops to page.sleep but did not touch
image.js, deep-research-result.js, or the image-poll re-navigation waits
in utils.js. Those still called page.wait(n>=1), which injects a whole-
subtree+attributes MutationObserver DOM-stability wait rather than a
sleep — during ChatGPT streaming the observer never goes quiet and pegs
the renderer. Convert the remaining poll-loop sleeps to page.sleep;
one-shot post-navigation settles are left as-is.
2026-07-13 02:36:43 +08:00