* feat(twitter): add time column to search output
Extract created_at from tweet data and format as ISO datetime.
This helps users filter tweets by recency during monitoring.
Closes#465
* refactor(twitter): align search timestamp field with created_at
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(imdb): add IMDb adapter with 6 commands
Add a public IMDb adapter using browser-based JSON-LD and __NEXT_DATA__
extraction. All commands use Strategy.PUBLIC with browser: true.
Commands:
- imdb search <query> — search movies, TV shows, and people
- imdb title <id> — get movie/show details (Movie, TVSeries, TVEpisode, TVMiniseries, TVMovie, etc.)
- imdb top — IMDb Top 250 chart
- imdb trending — Most Popular Movies
- imdb person <id> — actor/director info with filmography
- imdb reviews <id> — user reviews (first page, max 25)
Shared utils: ID normalization, ISO 8601 duration formatting, locale
forcing, JSON-LD extraction (supports type array filtering), and
anti-bot challenge detection.
* review: harden imdb adapter loading and tests
* test: unblock PR CI on merge head
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat: add bilibili/comments, xiaohongshu/comments, and rate-limiter plugin docs
- bilibili/comments: fetch top-level replies via /x/v2/reply/main with WBI signing
(bvid → aid resolution + signed params, no DOM dependency)
- xiaohongshu/comments: DOM extraction from note detail page with login-wall detection
and correct handling of 0-like counts (XHS shows "赞" text instead of "0")
- docs/advanced/rate-limiter-plugin.md: documents the onAfterExecute hook pattern
and shows a plug-and-play rate limiter that adds random sleep between platform
commands to reduce bot-detection risk
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(xiaohongshu): allow empty comments results
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(runtime): add runtime detection utility for Bun/Node.js
Add runtime-detect.ts module that detects whether opencli is running
under Bun or Node.js via globalThis.Bun check. Includes helper
functions for version string and label formatting.
Add corresponding unit tests that work correctly under both runtimes.
* feat(runtime): integrate Bun runtime support into CLI tooling
- doctor: show runtime label (e.g. 'node v22.13.0') in diagnostic output
- package.json: add dev:bun, start:bun, test:bun convenience scripts
- E2E helpers: support OPENCLI_TEST_RUNTIME env var for runtime selection
* ci: add Bun compatibility test job and document runtime support
- ci.yml: add bun-test job using oven-sh/setup-bun@v2
- README.md: update Prerequisites to mention Bun, add Runtime Support
section with usage examples for dev:bun, start:bun, test:bun
* ci: pin Bun version in compatibility job
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(producthunt): add Product Hunt CLI adapter
Add three commands:
- posts: RSS feed with optional category filter
- today: latest day's posts from feed
- hot: today's top posts with vote counts (browser INTERCEPT strategy)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(producthunt): add browse command for category best products
Browse top-rated products in any Product Hunt category (e.g. vibe-coding,
ai-agents, developer-tools) with name, tagline, and review count.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(producthunt): add adapter documentation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(producthunt): rebase on main and stabilize selectors
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(ci): include popup assets in extension release
Copy popup assets into the packaged Chrome extension zip and validate that manifest-referenced files exist before publishing the artifact.
Co-authored-by: Codex <noreply@openai.com>
* fix: restore executable permission on bin entries after tsc build (#446) (#452)
tsc does not preserve the +x bit when compiling, so after clean-dist
removes dist/ and tsc regenerates it, dist/main.js loses its executable
permission. This causes 'Permission denied' when users run 'npm run build'
in the installed directory.
Fix: read bin entries from package.json at the end of build-manifest and
chmod 0o755 them (skipped on Windows). Wrapped in try/catch so it never
breaks the build.
Closes#446
* fix: correct positional arg usage in tests (#449)
* fix yahoo-finance quote e2e invocation
* fix positional args in v2ex topic tests
* fix(ci): script extension release packaging
---------
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: jakevin <jakevingoo@gmail.com>
Co-authored-by: pi-dal <hi@pi-dal.com>
* fix(xiaohongshu): improve image-text publish flow
Match visible 图文 tab labels instead of relying on narrow class selectors, fail early when the page is still on the video publish surface, and avoid injecting images into a generic file input. Add regression coverage for the image-text tab flow and the video-page failure case.
* test(xiaohongshu): include publish tests in adapter project
* fix(xiaohongshu): wait for image-text surface before upload
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
tsc does not preserve the +x bit when compiling, so after clean-dist
removes dist/ and tsc regenerates it, dist/main.js loses its executable
permission. This causes 'Permission denied' when users run 'npm run build'
in the installed directory.
Fix: read bin entries from package.json at the end of build-manifest and
chmod 0o755 them (skipped on Windows). Wrapped in try/catch so it never
breaks the build.
Closes#446
* feat(linux-do): refactor adapters with unified feed, tags, user commands
- Replace hot/latest/category with unified `feed` command (tag/category/view routing)
- Add `tags`, `user-topics`, `user-posts` commands
- Add static data files for categories and tags lookup
- Fix error handling: use CliError subclasses instead of raw Error
- Fix Discourse API field mapping in search (tags, created)
- Add strategy: cookie to all YAML adapters
- Update docs and README command listings
- Update E2E tests for new command signatures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* review: resolve linux-do feed from live metadata
* fix: restore linux-do CI
* fix: harden linux-do compatibility
* refactor: stabilize linux-do command migration
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(chatgpt): add model/mode selection and fix response polling
Add --model option to ask and send commands, and a new standalone
model command for switching ChatGPT Desktop models via Accessibility API.
Supported models: auto, instant, thinking, 5.2-instant, 5.2-thinking.
Changes:
- ax.ts: add AX_MODEL_SCRIPT (opens Options popover, searches within
AXPopover to avoid matching sidebar items, supports legacy models
submenu) and AX_GENERATING_SCRIPT (detects "Stop generating" button)
- ask.ts: add --model flag; fix polling to wait for generation to
complete instead of returning partial/thinking intermediate text
- send.ts: add --model flag
- model.ts: new standalone command to switch model/mode
* review: activate chatgpt before model selection
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
- Show helpful hint in popup when disconnected: "This is normal. The
extension connects automatically when you run any opencli command."
- Stop eager reconnect after 6 attempts (reaching 60s backoff) to
reduce ERR_CONNECTION_REFUSED noise in console; keepalive alarm
still retries every ~24s at low frequency.
Split browser-public.test.ts: core sites (bilibili, zhihu, v2ex) run
by default; all other 20+ site tests moved to browser-public-extended
and gated behind OPENCLI_E2E=1 to prevent AI agents from launching
dozens of browser instances.
brew install gws installs a git workspace manager, not Google
Workspace CLI. The npm package @nicholasgasior/gws doesn't exist
either. Remove the misleading entry entirely.
Weibo: add feed, me, user, post, comments commands with cookie-based
auth and proper AuthRequiredError handling.
YouTube: add channel info and video comments via InnerTube API.
Also remove internal source references from file headers.
* feat(tiktok): add video URL to search results
Add a 'url' field to the TikTok search adapter output, constructed from
the author's uniqueId and the video id returned by the API. This allows
downstream consumers (AI agents, pipelines, scripts) to link directly to
each video instead of only having the author handle.
The URL format is: https://www.tiktok.com/@{author}/video/{videoId}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: add url field to 9 search adapters missing it
Add url output to search commands that were missing direct links:
YAML adapters:
- hackernews: surface existing url from map step into columns
- zhihu: pass computed url through map step into columns
- linux-do: construct url from topic id
- instagram: construct profile url from username
- xueqiu: pass computed url through map step into columns
TS adapters:
- arxiv: surface existing url from parseEntries into return + columns
- apple-podcasts: add collectionViewUrl from iTunes API
- medium: add url to columns (already computed in utils)
- weread: construct book url from bookId
This brings search adapter url coverage from 67% to 97% (32/33).
The only adapter without url is dictionary (word lookup, no URL concept).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(weread): use query arg in search
---------
Co-authored-by: Allen Song (Beyondsoft) <v-songjun@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
- Add popup.html/popup.js showing daemon connection status
(Connected / Reconnecting / No daemon connected)
- Add message listener in background.ts to expose WebSocket state
- Add PRIVACY.md with full privacy policy covering all permissions
- Add content_security_policy to manifest.json
- Update description to be clearer for CWS reviewers
* ci: add cross-platform support for E2E and smoke tests
Make headed browser tests (E2E and smoke) runnable on Linux, macOS,
and Windows:
- setup-chrome action: only install xvfb on Linux (macOS/Windows
have native GUI sessions and don't need a virtual display)
- e2e-headed.yml: add OS matrix, use xvfb-run wrapper only on Linux
- ci.yml smoke-test: add OS matrix, use xvfb-run wrapper only on Linux
The browser-actions/setup-chrome action already supports all three
platforms natively.
* ci: exclude Windows from E2E/smoke matrix (Chrome install hangs)
browser-actions/setup-chrome hangs indefinitely during Chrome MSI
installation on Windows runners (observed 10+ min with no progress).
This is a known limitation of Windows CI runners.
Keep Linux + macOS for headed browser tests. Windows is still covered
by build, unit-test, and adapter-test jobs.
* fix: pre-release cleanup — bugs, version sync, and error handling
Bug fixes:
- Fix hasLimit using wrong Set (SEARCH_PARAMS → LIMIT_PARAMS) in
analysis.ts classifyQueryParams
- Remove phantom scroll step from BROWSER_STEPS and KNOWN_STEP_NAMES
(declared but never registered, causes runtime crash if used in YAML)
- Add missing download step to KNOWN_STEP_NAMES (was producing
false-positive validation warnings)
Docs:
- Sync version numbers: SKILL.md, extension/package.json,
extension/manifest.json → 1.3.3
- Add jd, web to README command tables (both EN and zh-CN)
- Update xueqiu commands with fund-holdings, fund-snapshot
Code quality:
- Replace all 22 catch (err: any) with typed error handling using
existing getErrorMessage() utility across 13 files
* fix: remove (err as any) casts in error handling
- antigravity/serve.ts: use typed Error.cause instead of (err as any).cause
- external.ts: move instanceof guard into shouldRetryWithCmdShim,
accept unknown instead of forcing NodeJS.ErrnoException cast at call site
* fix(extension): security hardening — tab isolation, URL validation, cookie scope
Addresses issues raised in #399 (Astro-Han's community triage):
1. Tab isolation bypass: resolveTabId now verifies that an explicit tabId
belongs to the automation window (tab.windowId === session.windowId)
before accepting it. Tabs from the user's browsing session are rejected.
2. URL scheme allowlist: isDebuggableUrl switched from a blocklist
(chrome://, chrome-extension://) to an allowlist (http://, https:// only).
handleNavigate and tabs.new also reject non-http(s) URLs early, blocking
file://, javascript:, and data: scheme abuse.
3. Cookie scope restriction: handleCookies now requires domain or url.
Requests with neither are rejected instead of dumping all browser cookies.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(extension): resolve data: URI vs allowlist conflict, plug tabs.select bypass
- Add BLANK_PAGE constant and whitelist it in isDebuggableUrl so
internal blank tabs are not treated as non-debuggable after the
blocklist-to-allowlist change.
- Add isSafeNavigationUrl for user-facing URL validation (http/https
only), keeping it separate from internal isDebuggableUrl.
- Fix tabs.select to verify tab belongs to automation window before
activating, closing a tab isolation bypass.
- Normalize error message style (-- instead of em dash).
* fix(extension): add try-catch for tabs.select with explicit tabId
Gracefully handle the case where cmd.tabId points to a closed tab
instead of letting the unhandled exception bubble up.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
Replace manual < > comparison with localeCompare({ numeric: true })
so string-encoded numbers (e.g. "99" vs "1000") sort correctly
without requiring an explicit flag. This is a one-line fix that
makes sort just work for all YAML authors.
Co-authored-by: jackwener <jakevingoo@gmail.com>
* chore: fix pre-existing biome lint in template.ts
- isNaN → Number.isNaN (2 occurrences)
- string concatenation → template literal
- biome-ignore for intentional control chars in sanitize regex
* fix(pipeline): evaluate chained || in template engine (#303)
The || handler in evalExpr returned the right side as a literal string
instead of recursively evaluating it. This broke chained fallbacks like
`item.a || item.b || 'default'` — when item.a was falsy, the entire
`item.b || 'default'` was returned as text.
Fix: call evalExpr on the right side so chained || works at any depth.
* perf(pipeline): fast-path string literals in evalExpr to skip VM
When the right side of || is a quoted string like 'N/A', detect it
with a simple regex and return directly instead of falling through
to evalJsExpr which spins up a node:vm sandbox.
* refactor(pipeline): simplify evalExpr by removing hand-rolled operator parsing
Replace the manual regex-based || and arithmetic handlers with a
streamlined flow: pipe filters → fast-path literals → resolvePath →
evalJsExpr (VM). The VM already handles ||, ??, arithmetic, ternary,
etc. natively, so reimplementing them with regex was redundant and
bug-prone (see issue #303).
Key improvements:
- Fix pipe | vs || disambiguation with lookbehind/lookahead regex
(?<!|)|(?!|) so "item.a || item.b | upper" works correctly
- Remove ~20 lines of manual operator handling
- Add numeric literal fast path
- Pipe filter handler now uses evalExpr recursively (not just
resolvePath), enabling filters on complex expressions
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(xueqiu): add danjuan fund account commands
* refactor(xueqiu): convert danjuan fund YAML adapters to TS
- Replace 3 YAML files with 4 TS files (shared utils + 3 commands)
- Extract shared helpers: fetchDanjuanApi, fetchAssetGain, collectHoldings
- Fix double-navigation by using navigateBefore instead of pipeline navigate
- Unify error messages to English with Hint pattern
- Mask real account ID in docs example
- Add explicit default for --account arg
* refactor(xueqiu): optimize danjuan fund adapters
- Single page.evaluate with Promise.all for parallel account fetching
(1 browser round-trip instead of N+1)
- Merge fund-accounts into fund-holdings (account info visible per row)
- 3 files: danjuan-utils.ts (shared), fund-holdings.ts, fund-snapshot.ts
- Strong TypeScript interfaces for all data shapes
- Update docs to reflect 2-command design
* fix(xueqiu): preserve danjuan pre-navigation metadata
* fix(xueqiu): fail on incomplete danjuan snapshots
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(tiktok): add video URL to search results
Add a 'url' field to the TikTok search adapter output, constructed from
the author's uniqueId and the video id returned by the API. This allows
downstream consumers (AI agents, pipelines, scripts) to link directly to
each video instead of only having the author handle.
The URL format is: https://www.tiktok.com/@{author}/video/{videoId}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: guard against empty uniqueId/id producing invalid URL
When uniqueId or id is missing, return empty string instead of
a malformed URL like "https://www.tiktok.com/@/video/".
---------
Co-authored-by: Allen Song (Beyondsoft) <v-songjun@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* chore: ignore worktree directory
* fix(pipeline): check HTTP status in fetch step
* fix(pipeline): align fetch error semantics
* fix(pipeline): use CliError and add warn logging in fetch step
- Replace bare Error with CliError('FETCH_ERROR') for consistent CLI output
- Return error status from browser evaluate instead of throwing inside it
- Add log.warn() for batch item failures in both browser and non-browser paths
* chore: remove unrelated .worktrees/ from .gitignore
* refactor(fetch): use getErrorMessage(), unify sentinel naming to __httpError
- Use project's existing getErrorMessage() utility instead of manual instanceof checks
- Rename sentinel from __fetchError to __httpError for consistency with other adapters
- Simplify sentinel structure (url already available in outer scope, no need to pass through evaluate)
- Add comment explaining why getErrorMessage() can't be used inside evaluate()
- Add comment explaining CDP error message rewriting behavior
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(pixiv): add Pixiv adapter with 6 commands
Add support for Pixiv (pixiv.net) with the following commands:
- ranking: daily/weekly/monthly illustration rankings
- search: search illustrations by keyword/tag
- user: view artist profile info
- illusts: list illustrations by artist
- detail: view illustration details (tags, stats)
- download: download original-quality images
All commands use COOKIE strategy to reuse Chrome's logged-in session.
YAML adapters for simple API fetches (ranking, detail, user), TypeScript
for complex logic (search, illusts, download with Referer header).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(pixiv): add unit tests and E2E auth failure tests
- search.test.ts: auth error, result parsing, limit, empty results (4 tests)
- illusts.test.ts: auth error, empty user, two-step fetch, limit (4 tests)
- download.test.ts: auth error, no images, Referer header, partial failure (4 tests)
- Add pixiv to vitest adapter project include list
- Add 5 pixiv commands to E2E browser-auth graceful failure tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(pixiv): correct ranking API path and YAML arg naming
- ranking: use /ranking.php?format=json (not /ajax/ranking which 404s)
- ranking: fix JSON path from data.body.contents to data.contents
- user/detail: rename hyphenated args (user-id → uid, illust-id → id)
to fix YAML template evaluation (dot access doesn't support hyphens)
All 6 commands verified working against live Pixiv API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(pixiv): use JSON.stringify to prevent code injection in page.evaluate
Address CodeRabbit review: all user inputs (query, userId, illustId,
idsParam) passed to page.evaluate are now serialized via JSON.stringify
instead of direct string interpolation, preventing code injection in
browser context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(pixiv): address code review feedback
- ranking.yaml: add | json filter to page/limit args for defense-in-depth
- user.yaml: guard illusts/manga/novels with typeof check for robustness
- Extract shared createPageMock to test-utils.ts, deduplicate across 3 test files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(pixiv): use minimal page mock and add download E2E test
- test-utils.ts: slim down to minimal mock (goto, evaluate, getCookies)
with overrides support, matching upstream's pragmatic mock style
- Add missing download command to E2E browser-auth graceful failure tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(pixiv): address all remaining CodeRabbit review comments
- detail.yaml: add url to columns to match description mentioning "URLs"
- All adapters: differentiate HTTP errors — 401/403 → AuthRequiredError,
404 → "not found", others → generic "request failed (HTTP N)"
- Tests: use beforeAll to cache registry lookup, avoiding repeated reads
from global singleton
- Tests: assert error type (AuthRequiredError) not just message content
- Tests: add dedicated test cases for non-auth errors (500) and 404
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(pixiv): add adapter docs and indexes
- Add pixiv.md documentation page under docs/adapters/browser/
- Update docs/adapters/index.md with pixiv entry
- Add Pixiv to sidebar in docs/.vitepress/config.mts
- Update README.md and README.zh-CN.md adapter tables
- Add pixiv to download support tables in both READMEs
Completes the documentation checklist for the pixiv adapter PR.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(pixiv): address code review findings
- Use CommandExecutionError instead of raw Error for HTTP failures
- Add page.goto() before page.evaluate() to establish browser context
- Fix search keyword double-encoding in URL construction
- Fix ranking.yaml using rating_count instead of illust_bookmark_count
- Throw on batch detail fetch failure instead of silent empty return
- Add beforeEach mock reset in download tests
- Add novels column to user.yaml output
Ensures pixiv adapter follows upstream CliError conventions and handles
edge cases correctly before submitting to upstream.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(pixiv): improve download description in READMEs
- Replace technical Referer header detail with user-facing description
- Describe what users care about: original quality and multi-page support
Technical details belong in code comments, not user-facing docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(pixiv): expand usage examples with all options
- Add ranking mode examples including R18 variants
- Add search filter examples (mode, order, pagination)
- Organize examples by command category for readability
Users need to know available options without reading source code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(pixiv): address second round of CodeRabbit review comments
- Validate illust-id is numeric to prevent path traversal
- Move URL parsing inside per-item try block for graceful error handling
- Add auth error handling for batch detail request (consistent with step 1)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(pixiv): extract shared pixivFetch helper, add input validation & batch support
- Create utils.ts with pixivFetch() for unified navigate + fetch + error handling
- Refactor search.ts, illusts.ts, download.ts to use pixivFetch (DRY)
- Add user-id/illust-id numeric validation in TS adapters
- Add batch pagination in illusts.ts for limit > 48 (Pixiv server limit)
- Add comment explaining Pixiv search API dual keyword requirement
- Update tests: new invalid-ID test cases, aligned mock format with pixivFetch
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* ci: add cross-platform matrix (Linux/macOS/Windows) to build, unit-test, adapter-test
Add OS matrix with ubuntu-latest, macos-latest, and windows-latest to
the build, unit-test, and adapter-test CI jobs. This ensures cross-
platform compatibility is verified on every push and PR.
Smoke tests remain Linux-only due to xvfb dependency.
Relates to #392 (Windows plugin path issues).
* test: replace hardcoded /tmp with os.tmpdir() for Windows compatibility
Fix Windows CI failures caused by hardcoded '/tmp' paths that don't
exist on Windows. Use os.tmpdir() which returns the correct platform-
specific temp directory on all operating systems.
Files fixed:
- src/engine.test.ts: 3 occurrences (mkdtemp, discoverClis path)
- src/plugin.test.ts: 2 occurrences (getCommitHash test, mock condition)
* test: fix remaining Windows path issues in test files
- engine.test.ts: use pathToFileURL().href for dynamic import paths
(path.join produces backslashes on Windows, breaking ES module imports)
- download.test.ts: replace hardcoded '/tmp' with os.tmpdir() + path.join