formatPostTime() used local timezone methods, causing test failure
on UTC CI servers. XHS API timestamps are Beijing time (UTC+8),
so use explicit UTC offset with getUTC*() methods.
- wikiFetch return Promise<unknown> instead of Promise<any>
- Add WikiSearchResult type, remove r: any
- Type wikiFetch responses with inline type assertions
- Only append ... to abstract when actually truncated
Add arXiv (search, paper) and Wikipedia (search, summary) public API adapters.
- arxiv/search: search papers by keyword
- arxiv/paper: get paper details by ID
- wikipedia/search: search articles with lang support
- wikipedia/summary: get article summary
Type safety fixes applied: wikiFetch returns unknown, typed search results.
Co-authored-by: BruceLoveDecimal <39156883+BruceLoveDecimal@users.noreply.github.com>
- Remove fetchCreatorNotesByCdp() and captureNoteDetailApiPayload() raw
WebSocket code (~240 lines) — adapters should use IPage, not raw CDP
- Replace direct CDP WebSocket with IPage.evaluate() in-page fetch
- Fix page: any → IPage in all function signatures
- Simplify to two-tier fallback: API+interceptor → DOM parse
- Rebase onto latest main (resolves cdp.ts/daemon.ts conflicts)
- Change VitePress base from '/opencli/' to '/' for custom domain opencli.info
- Add docs/public/CNAME so GitHub Pages preserves custom domain on re-deploy
* docs: deduplicate documentation — single source of truth in docs/
- Remove root CDP.md, CDP.zh-CN.md, CLI-ELECTRON.md (now in docs/advanced/)
- Slim adapter READMEs to one-liner + link to docs/ (11 files)
- Update README.md adapter table links to point to docs/
* docs: set VitePress base path for GitHub Pages deployment
URLSearchParams.toString() encodes spaces as +, but Bilibili's WBI
signature verification expects %20. This mismatch causes search
queries with spaces (e.g. "亚马逊 滞销产品") to fail with
TypeError: Failed to fetch due to CORS-blocked error responses.
Fixes#125
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The build manifest includes antigravity/serve which collides with the
hardcoded antigravity serve in cli.ts. Add a guard to skip registry
entries whose subcommand already exists in the site group.
* feat(hf): add top command for hf papers (daily, weekly, monthly)
* feat(footer): add footerExtra support and derive dates from API response
Add footerExtra callback to CliCommand for custom table footer content.
For weekly/monthly periods, derive date range from API response publishedAt
field with local clock fallback.
* fix: truncate long paper titles
* refactor(hf): remove comments column for consistent output
* feat(hf): add --all flag to return all papers
* feat(hf): add paper id column to output
* fix: restore main.ts as bootstrap, sync footerExtra + CDPBridge + domain pre-nav to cli.ts
- main.ts should remain a lightweight entry point delegating to cli.ts
- Preserve CDPBridge fallback (OPENCLI_CDP_ENDPOINT) — PR had hardcoded BrowserBridge only
- Add domain pre-navigation for cookie/header strategies to cli.ts
- footerExtra feature from PR is properly integrated
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
The old adapter called `search.smzdm.com/ajax/?c=<channel>&s=<q>` which
now returns 404. This caused opencli smzdm search to always return empty
results regardless of keyword.
Fix: navigate directly to `search.smzdm.com/?c=home&s=<keyword>&v=b`
and scrape the rendered DOM via querySelectorAll('li.feed-row-wide').
Also switched from async IIFE to sync IIFE since all data is already in
the DOM after page load — no fetch needed.
Tested: opencli smzdm search --keyword A7M5 returns correct results
with prices and mall names.
Adds 'opencli boss resume --uid <uid>' command that scrapes the chat page
right panel to display candidate resume information including:
- Basic info: name, gender, age, experience, degree, active status
- Work history: time period + company + position
- Education: time period + school + major + degree
- Job being discussed and candidate expectations
Uses UI scraping approach since BOSS Zhipin does not expose a public API
for candidate resume data on the recruiter side.
Add comprehensive Jike (即刻) adapter covering read and write operations.
Read commands:
- user: user posts via m.okjike.com SSR JSON
- topic: topic/circle posts via m.okjike.com SSR JSON
- post: post detail with comments via m.okjike.com SSR JSON
- feed: home timeline via React fiber tree extraction
- search: search posts via React fiber tree extraction
- notifications: notification list via DOM innerText parsing
Write commands (Strategy.UI, browser DOM automation):
- create: publish post via inline compose box
- comment: comment on post via contenteditable paste
- like: like post via _likeButton_ div click
- repost: repost via action bar → popover menu → confirm
Implementation details:
- Three data extraction strategies: SSR JSON, React fiber, DOM manipulation
- Shared JikePost interface and getPostData helper in shared.ts
- All evaluate blocks include try/catch error handling
- Two rounds of parallel Claude + Codex code review applied
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
Replace ad-hoc string escaping with JSON.stringify() for values
interpolated into JavaScript code strings passed to page.evaluate().
- explore.ts: clickLabels were escaped with only single-quote
replacement, which breaks on labels containing backslashes or
newlines. JSON.stringify() handles all edge cases correctly.
- synthesize.ts: buildEvaluateScript() embedded URLs directly inside
single quotes. JSON.stringify() safely handles URLs containing
special characters.
- boss/chatlist: List chat conversations (招聘端聊天列表)
Uses getBossFriendListV2 API with pagination and job filter support.
- boss/chatmsg: Read chat message history with a candidate
Resolves encryptUid to numeric uid/securityId, fetches via historyMsg API.
- boss/send: Send chat message to a candidate via UI automation
BOSS chat uses MQTT protocol (not HTTP), so this command automates the
web chat UI: clicks on user in list → types in contenteditable editor →
clicks the send button.
All three commands use Strategy.COOKIE and require an active BOSS直聘
login session in Chrome.
The previous approach (nativeSetter + Enter keydown on the search input)
does not reliably trigger Twitter's form submission - the synthetic
KeyboardEvent is ignored by React, leaving the page on /explore with
zero API calls captured.
Use history.pushState + PopStateEvent instead, which triggers React
Router's listener and performs a true SPA navigation to /search.
The interceptor survives because no full page reload occurs.
Tested: "opencli", "it's a test" (single quote), "hello" all return
results with correct author attribution.
Add CLI commands to view Chaoxing assignments and exams by reusing
Chrome login session via the Browser Bridge.
Chaoxing has no flat API for listing assignments/exams. The adapter
follows the browser flow: establish session → fetch course list via
backclazzdata API → enter each course via stucoursemiddle redirect →
click tab to capture iframe URL → navigate and parse DOM.
Commands:
opencli chaoxing assignments [--course <name>] [--status] [--limit]
opencli chaoxing exams [--course <name>] [--status] [--limit]
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(intercept): use evaluate() for IIFE wrapping in installInterceptor/getInterceptedRequests
Root cause: daemon migration changed these methods from this.evaluate()
to direct sendCommand('exec'), losing the wrapForEval() IIFE wrapping.
CDP received bare arrow functions that were never invoked.
Fixes#98
* fix(twitter): SPA navigation, data path, and author resolution for INTERCEPT commands
- followers/following: install interceptor on profile page, then click
followers/following link (SPA navigation preserves JS context).
Use JSON.stringify for targetUser to prevent injection. Throw on
navigation failure. Update selector: /verified_followers.
- notifications: install interceptor on home, then pushState+popstate
to /notifications. Validate navigation URL.
- search: fix author resolution (core.screen_name, not legacy).
- All: fix GraphQL data path (remove extra .data level), update author
resolution to try core.screen_name before legacy.screen_name.
- followers: remove erroneous .filter(r => r?.url) — interceptor stores
response body JSON, URL filtering happens at capture time.