- Add getDaemonHealth() as single entry point for daemon status checks
returning a discriminated union: 'stopped' | 'no-extension' | 'ready'
- Delete browser/discover.ts (was a thin wrapper adding no value)
- Remove isExtensionConnected() (replaced by getDaemonHealth)
- Simplify doctor.ts: remove self-managed auto-start and double status
check — bridge.connect() already handles auto-start, so doctor now
does 1 live test + 1 health check instead of 2 checks + 1 bridge + 1 live + 1 check
- Update bridge.ts to throw typed BrowserConnectError (via formatBrowserConnectError)
instead of plain Error, enabling precise catch without string matching
- Extract _pollUntilReady() in bridge.ts to deduplicate polling loops
- Simplify commanderAdapter.ts renderError(): use err.kind directly
instead of making a separate real-time status check that could race/fail
- Update execution.ts fail-fast to use getDaemonHealth() with typed error kind
- Update all affected tests (doctor, daemon-client, browser bridge)
Fix imports in newly merged LessWrong and hupu/mentions adapter
files that were still referencing the deleted root shim files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(hupu): add hupu cli adapter
* fix(hupu): prevent detail from returning the wrong thread
* refactor: deduplicate shared utilities in hupu adapter
- Merge postHupuJson and postHupuReplyJson into single function with mode parameter
- Move stripHtml and decodeHtmlEntities to utils.ts, remove duplicate definitions
* fix(hupu): add mentions command
* fix: move mentions.ts to clis/hupu/, remove src/clis/hupu duplicates
Post PR #782 restructure: adapter files live at root clis/, not src/clis/.
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
Tests now expect /search_result/<id> for bare note IDs (matching
the note-helpers.ts change from PR #774) and updated empty-shell
hint assertion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(lesswrong): add LessWrong adapter
15 commands for the LessWrong rationality and AI alignment community:
- Post listings: curated, frontpage, new, top, top-week/month/year
- Content: read (full post), comments, shortform (quick takes)
- Discovery: tag, tags, sequences
- Users: user (profile), user-posts
All commands use the public GraphQL API (no browser required).
Time-filtered views use the `after` date parameter.
Tag lookup resolves slugs to IDs via the `tagBySlug` view.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move lesswrong adapter to clis/ (post PR #782 restructure)
New adapter files were created at src/clis/lesswrong/ but PR #782 moved
all adapters to root clis/. Move to correct location.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
Fix imports in newly added deep-research adapter files that were
still referencing the deleted root shim files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
XHS blocks /explore/<id> without a valid xsec_token, causing code 66
(empty result) when passing bare note IDs. The /search_result/<id> path
works without xsec_token when the user is logged in via cookies.
Changes:
- note-helpers.ts: buildNoteUrl now uses /search_result/<id> for bare IDs
- note.ts: remove isBareNoteId branching and simplify empty shell error
Update all documentation and skill files to reference src/ directly,
matching the shim removal in the previous commit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(gemini): add deep-research workflow and docs export result
* fix(gemini): improve deep-research submit and confirm flow
* fix(gemini): return waiting state when deep research is in progress
* fix(gemini): avoid false submit detection on root app transcript changes
* fix(gemini): return pending state when deep-research export is not ready
---------
Co-authored-by: f1480022 <f148002@163.com>
* fix(36kr): replace waitForCapture with DOM polling for search/hot
waitForCapture(6) always times out on 36kr because the API intercept
never captures a matching request. However, the DOM is already fully
rendered with search/hot results by the time the timeout fires.
Replace the 6-second intercept wait with a DOM polling loop that checks
for article links (a[href*="/p/"]) every 300ms, returning immediately
once content is available (typically ~1s vs 6s timeout + error).
Tested on opencli 1.6.2 with both CDP and Browser Bridge modes.
* fix: rebase onto main, remove unused interceptor, fix strategy
- Rebase onto main after clis/ move (PR #782)
- Remove installInterceptor calls (no longer used after waitForCapture removal)
- Change strategy from INTERCEPT to PUBLIC (browser: true) to match actual behavior
- Improve polling loop readability
---------
Co-authored-by: buruguo <buruguo@lambdafintech.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix: review follow-ups — better first-run log, OPENCLI_FETCH=1 skips version check
- Clarify first-run log message: "copying adapters (one-time setup)"
- Add comment explaining why scriptPath uses two levels of ../
- OPENCLI_FETCH=1 now bypasses version-skip to allow forced refresh
* fix: update doc-coverage script path after clis/ move
check-doc-coverage.sh still referenced src/clis/ after PR #782 moved
adapters to root clis/. This caused CI to fail with "0/1 documented".
* fix: resolve package root dynamically for symlink and first-run paths
The symlink at ~/.opencli/node_modules/@jackwener/opencli pointed to
dist/ instead of the package root in prod mode, breaking user TS CLIs
that import from '@jackwener/opencli/registry'.
The first-run scriptPath also resolved incorrectly in dev mode.
Extract findPackageRoot() that walks up to find package.json, fixing
both paths for dev (src/) and prod (dist/src/) layouts.
* refactor: move adapters from src/clis/ to root clis/ for monorepo separation
Separates CLI adapters from the core runtime to prepare for independent
adapter distribution via postinstall fetch.
Key changes:
- Move src/clis/ → clis/ (adapters at repo root)
- Change tsconfig rootDir from "src" to "." so tsc compiles both
- Create root-level shim files (registry.ts, errors.ts, etc.) so adapter
relative imports (../../registry.js) resolve correctly
- Update build-manifest.ts, main.ts paths for new dist/src/ structure
- Expand ensureUserCliCompatShims() to cover all adapter import targets
(types, utils, logger, launcher, browser/*, download/*, pipeline/*)
- Add scripts/fetch-adapters.js postinstall for ~/.opencli/clis/ sync
- Update vitest.config.ts adapter test paths
- Add package.json files field to exclude adapters from npm package
Official adapter files are unconditionally overwritten on update;
user-created files not in the manifest are preserved.
* fix: add dist/clis/ and cli-manifest.json to npm files, harden fetch-adapters
- Add dist/clis/ and dist/cli-manifest.json to package.json files field
so built-in adapters and manifest ship with the npm package
- Replace execSync with execFileSync to prevent command injection
- Add version check to skip redundant adapter fetches
- Track tmpRoot explicitly for reliable cleanup
* fix: address review blockers — manifest-based updates, global-only fetch, first-run fallback
1. Manifest-based update strategy:
- Read old manifest to identify previously-official files
- Clean up files removed upstream (in old manifest but not new)
- User-created files (never in any manifest) remain untouched
2. Only run fetch-adapters on global install (npm_config_global=true)
or explicit OPENCLI_FETCH=1, preventing heavy side effects for
local/dev installs
3. First-run fallback in discovery.ts:
- ensureUserAdapters() checks for adapter-manifest.json
- If missing and ~/.opencli/clis/ is empty, spawns fetch-adapters.js
- Guarantees adapters are available even with --ignore-scripts
* fix: remove OPENCLI_FETCH env var, use internal _OPENCLI_FIRST_RUN instead
* feat: also support OPENCLI_FETCH=1 for explicit adapter fetch trigger
* simplify: replace git clone with local copy from dist/clis/
Adapters already ship in the npm package (dist/clis/), so there's no
need to clone from GitHub. Copy directly from the installed package:
- Eliminates git, curl, tar dependencies
- No network calls in postinstall
- No timeout/offline issues
- Version always matches the installed CLI
- ~65 lines of clone/download code replaced by one cpSync loop
* feat(hupu): add hupu cli adapter
* fix(hupu): prevent detail from returning the wrong thread
* refactor: deduplicate shared utilities in hupu adapter
- Merge postHupuJson and postHupuReplyJson into single function with mode parameter
- Move stripHtml and decodeHtmlEntities to utils.ts, remove duplicate definitions
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat: 推特新增回复图片能力支持本地路径和网络路径
* fix(twitter/reply): fix image upload fallback, restore execCommand, add size limit
- Fix attachReplyImage fallback: use uploaded flag instead of checking
page.setFileInput existence, so base64 fallback actually runs when
CDP setFileInput throws "Unknown action"
- Restore execCommand('insertText') as primary text input method for
Twitter's Draft.js editor, with paste event as fallback
- Add 20MB size limit for remote image downloads to prevent OOM
- Remove unsafe buttons[0] fallback that could click invisible buttons
* fix(twitter/reply): add local image size check and base64 fallback warning
Local images were not validated for size — a 100MB file would fail only
at upload time. Remote images already had MAX_IMAGE_SIZE_BYTES checks.
Also add a console.warn when using the base64 fallback with large
payloads, consistent with xiaohongshu/publish.ts behavior.
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(xiaoe): add 小鹅通 (Xiaoe-tech) student platform adapter
Add 5 YAML adapters for 小鹅通 (xiaoe-tech.com), the leading Chinese
online education platform:
- courses: list purchased courses with URLs and shop names
- detail: course info (name, price, user count, shop)
- catalog: full course outline supporting normal courses (type 50),
columns (type 6), and big columns (type 8)
- play-url: get M3U8 play URL via direct API for video courses,
and Vue component tree search + Performance API polling for
live replay courses
- content: extract rich-text page content as plain text
Technical notes:
- Strategy: cookie (reuses Chrome login session)
- Framework: Vue 2 + Vuex Store (SPA)
- Video courses use a two-step API chain:
detail_info.get → play_sign → getPlayUrl → M3U8
- Live replays use Performance API + Vue data tree polling
- Catalog expands chapters via Vue component method getSecitonList()
- Supports multiple stores (cross-domain cookie sharing via
study.xiaoe-tech.com)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* review: stop truncating xiaoe content
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix: add -v/--verbose to explore, record, generate, cascade
Built-in browser commands were registered directly in cli.ts and
missed the -v/--verbose flag that commanderAdapter.ts wires up for
adapter commands. Also switch explore's lone log.debug() call to
log.verbose() so the flag has visible effect.
Closes#716
* refactor(cli): make builtin command wiring testable
* refactor(cli): simplify verbose wiring, use normal Commander pattern
Replace registerVerboseAction wrapper with simple applyVerbose() helper.
The wrapper broke Commander's builder chain and created awkward
indentation. Now each command uses standard .option().action() with
applyVerbose(opts) as the first line — easier to read and maintain.
* fix(cli): add -v/--verbose to doctor and synthesize commands
These commands were also missing verbose support, same root cause as
explore/record/generate/cascade — registered directly in cli.ts,
bypassing commanderAdapter's automatic -v wiring.
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(twitter): add --images flag to post command
Support attaching up to 4 images when posting tweets via
`opencli twitter post "text" --images /path/a.png,/path/b.jpg`.
Uses the existing CDP DOM.setFileInputFiles mechanism (page.setFileInput)
to inject files into Twitter's file input. Includes proper file validation,
graceful error handling for older extensions, and polling-based upload
readiness detection instead of fixed delays.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(twitter): use attachments DOM signal for upload detection, add tests
Replace unreliable tweet-button-only polling with dual-condition check:
wait for [data-testid="attachments"] with correct [role="group"] count
AND button enabled. Increase timeout to 30s. Add 8 unit tests covering
image upload flow, file validation, and error paths.
Addresses PR #666 review feedback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(twitter): use top-level imports, fix test mocks, faster upload poll
- Use top-level fs/path imports instead of dynamic imports inside func
- Fix test statSync mock to return undefined (not null) for missing files
- Fix test path mock to preserve other exports via importOriginal
- Fix null type error in no-browser-session test
- Reduce upload poll interval from 1s to 500ms for faster detection
- Use JSON.stringify for imageCount interpolation for consistency
* refactor(twitter): extract validation, fail-fast, reduce duplication
- Extract validateImagePaths() with extension validation (jpg/png/gif/webp)
matching xiaohongshu publish pattern
- Validate images before browser navigation (fail-fast on bad input)
- Remove try/catch wrapper around setFileInput — let errors propagate
naturally instead of masking the original error
- Deduplicate tweetButton/tweetButtonInline lookups using fallback OR
- Use constants for MAX_IMAGES, UPLOAD_POLL_MS, UPLOAD_TIMEOUT_MS
- Add tests: unsupported format, validates-before-navigating
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* fix(gemini): stabilize ask reply state handling
* fix: use CommandExecutionError for composer failures and clean up formatting
- Replace raw Error with CommandExecutionError for Node-side composer
failures (prepareComposer, insertText) to match adapter error conventions
- Remove extra blank lines after __test__ export
* refactor: remove dead code and add Chinese sign-in label
- Remove unused areGeminiTurnsEqual and areGeminiLinesEqual functions
- Add Chinese sign-in label (登录) to sign-in detection for consistency
with other Chinese labels already added in this PR
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
Move data processing (HTML stripping, answer mapping) from browser-side
evaluate to Node-side, keeping the evaluate minimal: just fetch + status
check. Uses __httpError sentinel consistent with pixivFetch convention.
- Remove hover_price_text as MOQ source in search normalizeSearchCandidate
to prevent price fields from being misinterpreted as MOQ data
- Rename firstLine() to firstWord() to match its actual behavior (splits
by whitespace, not newlines)
- Add missing "单" unit to item.ts extractSalesText regex
- Add test case verifying hover_price_text is not used for MOQ
* fix(zhihu): make question runtime-compatible
* fix: validate questionId is numeric to prevent interpolation issues
* refactor: simplify evaluate string and harden against injection
- Build URL in Node.js, embed via JSON.stringify for safety-by-design
- Remove unnecessary (page as any) cast — IPage already has evaluate
- Simplify error message construction (no nested ternaries)
- Replace implementation-detail test with numeric ID validation test
* refactor: simplify zhihu question — move stripHtml into evaluate, return clean data
* fix: add colon separator in fetch error message for readability
"request failed Failed to fetch" → "request failed: Failed to fetch"
---------
Co-authored-by: Kyrie <kyrie@mallab.world>
Co-authored-by: jackwener <jakevingoo@gmail.com>
1. marks: correct pageSize from 30 to 15 — douban grid mode shows 15
items per page, causing pagination to stop after the first page.
2. subject: split title/originalTitle correctly — v:itemreviewed contains
both Chinese and original titles concatenated.
3. subject: extract country/region from #info as list, split by "/".
4. subject: extract duration as pure number (min) from v:runtime or #info.
5. subject: return casts as list instead of comma-joined string.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- bilibili subtitle/comments tests: use importOriginal to include
resolveBvid in utils mock
- comments test: use valid BV ID format for aid-resolution error test
- launcher test: skip pgrep test on win32 (detectProcess early-returns)
* fix(windows): graceful degradation and manual CDP override for Electron apps
* fix: validate OPENCLI_CDP_ENDPOINT with probeCDP before use
Fail-fast with a clear error if the manual CDP endpoint is not reachable,
instead of passing a bad URL downstream and getting a confusing error.
---------
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat(bilibili): support b23.tv short URL/short code resolution
Add resolveBvid() in utils.ts to automatically resolve b23.tv short URLs
and short codes to BV IDs. Supports all input formats:
- BV ID: BV1MV9NBtENN (pass through)
- Short code: XYzsqGa
- Short URL: https://b23.tv/XYzsqGa, b23.tv/XYzsqGa
Uses Node.js https.get with 302 redirect only (no body download),
typically ~100-250ms resolution time.
Applied to: subtitle, comments, download commands.
* fix: add timeout, input coercion, and tests for resolveBvid
- 5s timeout on https.get to prevent hanging on unresponsive b23.tv
- Accept unknown input type with String() coercion
- Simplify callers (remove redundant String().trim() wrappers)
- Add unit tests for BV ID passthrough and edge cases
---------
Co-authored-by: chenruinian <chenruinian@Sa1kas-MacBookPro.local>
Co-authored-by: jackwener <jakevingoo@gmail.com>
* feat: auto-downgrade table output to YAML in non-TTY environments
When stdout is not a TTY (pipes, AI agents, subprocesses), automatically
output YAML instead of table with ANSI colors and box-drawing characters.
This makes opencli output parseable by downstream tools and AI agents.
Behavior:
- TTY: table (default, unchanged)
- Non-TTY: yaml (auto-detected)
- OUTPUT env var: overrides auto-detection (yaml/json/table/etc)
- Explicit -f flag: always respected
* fix: TTY detection now works with commanderAdapter default fmt
- fmt='table' from commanderAdapter now correctly triggers non-TTY downgrade
- Priority: explicit -f (non-table) > OUTPUT env var > TTY auto-detect
- Added test for explicit -f precedence over OUTPUT env var
* fix: explicit -f flag now takes precedence over TTY auto-detection
Use Commander's getOptionValueSource to distinguish explicit -f from
default. Explicit -f table in non-TTY keeps table output. Only auto-
downgrade when user didn't pass -f.
Priority: explicit -f > OUTPUT env var > TTY auto-detect > table default
* fix: explicit -f also skips command defaultFormat override
When user passes -f explicitly, command-level defaultFormat (e.g.
gemini/ask defaultFormat:'plain') no longer overrides their choice.
* feat(amazon): unify ranking adapters for three signal boards
* refactor: simplify bestsellers wrapper and fix pagination detection for all ranking types
1. Remove unnecessary __test__ wrapper from bestsellers.ts — the test
now uses normalizeRankingCandidate directly from rankings.ts,
eliminating a needless indirection layer.
2. Fix isRankingPaginationUrl to detect pagination refs for all ranking
types: zg_bs_pg_ (bestsellers), zg_bsnr_pg_ (new releases),
zg_bsms_pg_ (movers & shakers). Previously only matched the
bestsellers-specific ref pattern.
---------
Co-authored-by: 泽加武 <zejiawu@zejiawudeMac-mini.local>
Co-authored-by: jackwener <jakevingoo@gmail.com>