`opencli host install` is no longer a required setup step. Global
postinstall writes the Chrome Native Messaging manifest; the first
browser command still repairs it if Chrome was not installed yet.
Backoff reconnect until hello-ok, slice native frames by UTF-8 bytes,
and give each host a per-pid socket so shutdown cannot unlink a successor.
RPC failure is not ready. Rebuild the committed extension dist, bump it
to 1.0.24, and drop the daemonVersion alias. Replace the TCP e2e fake
daemon with a unix-socket host and a connectNative Chrome smoke.
Chrome parents opencli-host via connectNative. The CLI only connects to a
unix socket under ~/.opencli/run. Drop :19825, HTTP, WebSocket, CORS, and
opencli daemon. Session leases and Command/Result stay in the host mux.
Reuses the existing LinkedIn shared evaluate envelope unwrap helper across general messaging commands while preserving subsystem-local copies for later batches.
Moves the duplicate Node-side LinkedIn safety URL decoder into the site shared helper while preserving page-realm copies and their browser-local contract.
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.
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.
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.
* 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>
`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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>