Commit Graph

570 Commits

Author SHA1 Message Date
jackwener fb389f39f1 fix(tests): update E2E exit code assertions for usage errors
Argument/usage errors now correctly exit with code 2 (EX_USAGE) since
the exit-codes feature landed. Update the two affected E2E assertions:
- unknown command → 2 (usage error, not generic failure)
- plugin update without args → 2 (ArgumentError)
2026-03-28 23:37:13 +08:00
jakevin 812f27a05e chore(release): 1.5.5 (#565) v1.5.5 2026-03-28 22:19:59 +08:00
jakevin ab0af2de5c feat(exit-codes): Unix-standard process exit codes for all error types (#564)
* feat(exit-codes): add Unix-standard exit codes to all CliError types

Introduce EXIT_CODES constant table (sysexits.h conventions) and wire
exitCode into every CliError subclass so the process exit code reflects
the semantic type of failure:

  0   success (default)
  1   generic / unexpected error
  2   argument / usage error        (ArgumentError)
 66   empty result / not found      (EmptyResultError, SelectorError)
 69   service unavailable           (BrowserConnectError, AdapterLoadError)
 77   permission / auth required    (AuthRequiredError)
 78   configuration error           (ConfigError)
124   timeout                       (TimeoutError)
130   Ctrl-C / SIGINT               (unchanged, tui.ts)

resolveExitCode() in commanderAdapter.ts reads err.exitCode for typed
CliErrors, and falls back to pattern-matching message text for untyped
adapter errors (auth pattern → 77, not-found pattern → 66, else → 1).

Shell scripts can now distinguish error categories:
  opencli spotify status || echo "exit $?"   # 69 if browser not running
  opencli github issues --repo x 2>/dev/null; [ $? -eq 77 ] && opencli github auth

* fix(exit-codes): address review findings

- TIMEOUT: change from 124 → 75 (EX_TEMPFAIL); 124 is bash timeout(1)'s
  own exit code, creating ambiguity when shell runs `timeout 30 opencli`
- SelectorError: change from EMPTY_RESULT(66) → GENERIC_ERROR(1); a
  missing DOM selector is an adapter bug, not a user "no data" condition
- normalizeArgValue: throw ArgumentError instead of bare CliError so
  invalid bool args correctly exit with USAGE_ERROR(2) not GENERIC_ERROR(1)
- resolveExitCode: explicitly map 'http' classification to GENERIC_ERROR
  to keep exit-code path in sync with the render path
- tui.ts: replace hardcoded process.exit(130) with EXIT_CODES.INTERRUPTED

* feat(exit-codes): replace all hardcoded exit numbers with EXIT_CODES constants

Extend the exit code system to cover every process exit point in the codebase.
No magic numbers remain — all exit codes are now referenced by name.

Semantic upgrades beyond pure renaming:
- plugin update missing args  → USAGE_ERROR (2) instead of 1
- plugin update conflicting   → USAGE_ERROR (2) instead of 1
- opencli install <unknown>   → USAGE_ERROR (2) instead of 1
- unknown command fallback    → USAGE_ERROR (2) instead of 1
- record with no candidates   → EMPTY_RESULT (66) instead of 1
- external CLI install fail   → SERVICE_UNAVAIL (69) instead of 1
- daemon EADDRINUSE           → SERVICE_UNAVAIL (69) instead of 1

Files touched: cli.ts, external.ts, daemon.ts, main.ts,
               clis/antigravity/serve.ts
2026-03-28 22:16:42 +08:00
jakevin 5c655ee3c8 feat(sinafinance): rewrite stock as public API, no browser required (#563)
* feat(sinafinance): rewrite stock as public API adapter

Replace browser-based DOM scraping with direct Sina public APIs:
  suggest3.sinajs.cn — symbol search (GBK, no auth)
  hq.sinajs.cn       — real-time quote (GBK, no auth)

Strategy.PUBLIC, browser: false — no Chrome or login required.
Supports A股 (sh/sz), 港股 (hk prefix), 美股 (gb_ prefix).
US MarketCap parsed from hq field [12]; formatted as T/B/M.

* feat(exit-codes): add Unix-standard exit codes to all CliError types

Introduce EXIT_CODES constant table (sysexits.h conventions) and wire
exitCode into every CliError subclass so the process exit code reflects
the semantic type of failure:

  0   success (default)
  1   generic / unexpected error
  2   argument / usage error        (ArgumentError)
 66   empty result / not found      (EmptyResultError, SelectorError)
 69   service unavailable           (BrowserConnectError, AdapterLoadError)
 77   permission / auth required    (AuthRequiredError)
 78   configuration error           (ConfigError)
124   timeout                       (TimeoutError)
130   Ctrl-C / SIGINT               (unchanged, tui.ts)

resolveExitCode() in commanderAdapter.ts reads err.exitCode for typed
CliErrors, and falls back to pattern-matching message text for untyped
adapter errors (auth pattern → 77, not-found pattern → 66, else → 1).

Shell scripts can now distinguish error categories:
  opencli spotify status || echo "exit $?"   # 69 if browser not running
  opencli github issues --repo x 2>/dev/null; [ $? -eq 77 ] && opencli github auth

* review: regex escape sym, fix change precision, optimize suggest type param
2026-03-28 21:59:44 +08:00
yichuanzhao99-ctrl 0b15561025 添加新浪财经行情及滚动新闻抓取 (#546)
* 添加新浪财经行情及滚动新闻抓取

* review: fix injection vuln, dead code, typos, hardcoded waits

rolling-news:
- Remove dead dateToTimestampParams function and unused CliError import
- Fix column field name typo: clomn → column
- Replace page.wait(5) with selector-based wait
- Remove all commented-out code

stock:
- Fix P0 JS injection: use JSON.stringify() to safely embed args.key/market
- Add null guard for inputEl before calling .focus()
- waitForElement returns null instead of throwing on timeout
- Replace page.wait(5) with selector-based wait
- Extract MARKET_CN/HK/US as named constants
- Throw CliError on NOT_FOUND instead of silent empty return

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-28 21:46:33 +08:00
Cjy-CN f9857f8c7b fix: remove invalid state: 'normal' from chrome.windows.create() (#559)
* fix: remove invalid `state: 'normal'` from chrome.windows.create()

Chrome 146+ rejects 'normal' as an invalid value for the `state` parameter
in chrome.windows.create(). This causes the error:

    Error: Invalid value for state

Root cause analysis:
- The Chrome Extensions API documentation states that `state` parameter
  only accepts 'minimized', 'maximized', and 'fullscreen' as input values
- While WindowState enum includes 'normal', it's meant for reading window
  state, not for setting it during creation
- Chrome 146 enforces stricter validation on the `state` parameter
- When `state` is omitted, the window defaults to 'normal' state anyway

Fix: Remove the `state: 'normal'` parameter entirely. The window will
default to normal state without explicitly setting it.

Tested: `opencli doctor` and `opencli bilibili hot` now work correctly
on Chrome 146.0.7680.165.

* build: rebuild dist after removing state: 'normal'

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-28 21:25:31 +08:00
jakevin c9e29d9f22 chore(release): 1.5.4 (#558) v1.5.4 2026-03-28 20:22:24 +08:00
AstroHan 75ddb6319b fix(extension): probe daemon before WebSocket to eliminate console noise (#534)
* fix(extension): probe daemon via HTTP before WebSocket to eliminate console noise

When the daemon is offline, `new WebSocket()` logs uncatchable
ERR_CONNECTION_REFUSED errors to Chrome's extension error page.
Add `probeAndConnect()` that checks daemon reachability with a
silent `fetch(HEAD)` before attempting WebSocket connection.

All three auto-connect paths (initialize, keepalive alarm, eager
reconnect) now go through the probe, eliminating the error noise
entirely.

Closes #505

* refactor(extension): inline probe into connect(), add /ping to daemon

Instead of a separate probeAndConnect() wrapper that all call sites had
to remember to use, bake the HTTP probe directly into connect() itself.
This makes the guard impossible to accidentally skip when adding new
connection paths in the future.

Also adds a dedicated GET /ping endpoint to the daemon (no X-OpenCLI
header required) so the probe has a clear semantic contract instead of
relying on a 403 side-effect from the root path.

- daemon: GET /ping → 200 {ok:true}, no auth needed, placed before the
  X-OpenCLI header check; only chrome-extension:// and no-origin
  requests reach it (origin check is still enforced above)
- background: connect() is now async; probes /ping with a 1 s timeout
  before new WebSocket(); all call sites (initialize, keepalive alarm,
  scheduleReconnect) remain unchanged
- probeAndConnect() removed — no longer needed

* fix(extension/daemon): address review feedback on probe refactor

- protocol.ts: replace DAEMON_HTTP_URL with DAEMON_PING_URL (clearer
  semantics, single source of truth for the health-check URL)
- background.ts: import DAEMON_PING_URL from protocol instead of
  defining a local constant; check res.ok so an unexpected non-200
  response doesn't fall through to WebSocket; annotate all fire-and-
  forget connect() call sites with `void` to make intent explicit
- daemon.ts: add security comment on /ping documenting the timing
  side-channel tradeoff (loopback-only, accepted risk)

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-28 20:15:51 +08:00
jakevin 5ec34ebc53 feat(hub): add vercel CLI to external CLI hub (#556) 2026-03-28 19:57:49 +08:00
jakevin 959ec5fe1c feat(hub): add lark-cli to external CLI hub (#555) 2026-03-28 19:51:30 +08:00
luo jiyin dbcfbc3bb6 fix(skill): use relative links in migration skill (#551) 2026-03-28 16:45:02 +08:00
pi-dal 5ae9658a21 fix(manifest): preserve dynamic TS arg metadata in help output (#536)
* fix(manifest): preserve runtime arg metadata

* refactor(manifest): build TS metadata from runtime commands

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-28 14:23:16 +08:00
jakevin f415e829a9 docs(readme): restructure quick start, full command list, anti-detection highlight (#544)
* docs(readme): restructure quick start, expand built-in commands, add CLI Hub auto-install note

* docs(readme): highlight anti-CDP fingerprinting and risk-control measures
2026-03-28 12:29:10 +08:00
jakevin 210e6fabb7 docs: CLI Hub intro in header, restore auto-install note
* chore(release): 1.5.2

* test(e2e): stabilize output format checks

* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)

* docs: add perf smart-wait implementation plan

* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers

* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage

* feat(perf): implement waitForCapture() and wait({ selector }) in Page

* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage

* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests

* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters

* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]

* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters

* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog

* fix(types): add waitForCapture to IPage mock helpers in tests

* docs: simplify README to 50-line overview with docs link

Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.

* fix(perf): CDPPage smart wait + MutationObserver selector wait

- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
  matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
  resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context

* docs: move built-in commands table to docs/adapters/index.md

Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.

* docs: move Quick Start before Prerequisites, tone down Electron promo copy

- Reorder sections: Why opencli → Quick Start → Prerequisites
  so users see "how to use" before "what you need"
- Replace "The Most Powerful Update Has Arrived!" marketing copy
  with a plain one-liner description of the Electron feature
- Remove redundant Bun dev/test commands from Prerequisites
  (developer-only content, not relevant to end users)
- Add "(requires Extension)" hint to browser command in Quick Start

* docs: polish Quick Start — one-line source install, Verify setup section

* docs: show 4 sample adapters in Built-in Commands with link to full list

* docs: polish README — Try it out under Verify setup, trim examples, CLI Hub as top-level section

* docs: add CLI Hub intro line in header, restore auto-install note in CLI Hub section
2026-03-28 12:19:15 +08:00
jakevin 73a8508972 docs: polish README — Try it out, trim examples, CLI Hub section
* chore(release): 1.5.2

* test(e2e): stabilize output format checks

* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)

* docs: add perf smart-wait implementation plan

* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers

* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage

* feat(perf): implement waitForCapture() and wait({ selector }) in Page

* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage

* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests

* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters

* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]

* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters

* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog

* fix(types): add waitForCapture to IPage mock helpers in tests

* docs: simplify README to 50-line overview with docs link

Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.

* fix(perf): CDPPage smart wait + MutationObserver selector wait

- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
  matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
  resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context

* docs: move built-in commands table to docs/adapters/index.md

Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.

* docs: move Quick Start before Prerequisites, tone down Electron promo copy

- Reorder sections: Why opencli → Quick Start → Prerequisites
  so users see "how to use" before "what you need"
- Replace "The Most Powerful Update Has Arrived!" marketing copy
  with a plain one-liner description of the Electron feature
- Remove redundant Bun dev/test commands from Prerequisites
  (developer-only content, not relevant to end users)
- Add "(requires Extension)" hint to browser command in Quick Start

* docs: polish Quick Start — one-line source install, Verify setup section

* docs: show 4 sample adapters in Built-in Commands with link to full list

* docs: polish README — Try it out under Verify setup, trim examples, CLI Hub as top-level section
2026-03-28 12:13:29 +08:00
jakevin 0fc3bc2d85 docs: show 4 sample adapters in Built-in Commands
* chore(release): 1.5.2

* test(e2e): stabilize output format checks

* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)

* docs: add perf smart-wait implementation plan

* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers

* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage

* feat(perf): implement waitForCapture() and wait({ selector }) in Page

* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage

* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests

* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters

* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]

* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters

* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog

* fix(types): add waitForCapture to IPage mock helpers in tests

* docs: simplify README to 50-line overview with docs link

Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.

* fix(perf): CDPPage smart wait + MutationObserver selector wait

- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
  matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
  resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context

* docs: move built-in commands table to docs/adapters/index.md

Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.

* docs: move Quick Start before Prerequisites, tone down Electron promo copy

- Reorder sections: Why opencli → Quick Start → Prerequisites
  so users see "how to use" before "what you need"
- Replace "The Most Powerful Update Has Arrived!" marketing copy
  with a plain one-liner description of the Electron feature
- Remove redundant Bun dev/test commands from Prerequisites
  (developer-only content, not relevant to end users)
- Add "(requires Extension)" hint to browser command in Quick Start

* docs: polish Quick Start — one-line source install, Verify setup section

* docs: show 4 sample adapters in Built-in Commands with link to full list
2026-03-28 12:06:56 +08:00
jakevin bc42c8b258 docs: polish Quick Start — one-line source install, Verify setup section
* chore(release): 1.5.2

* test(e2e): stabilize output format checks

* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)

* docs: add perf smart-wait implementation plan

* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers

* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage

* feat(perf): implement waitForCapture() and wait({ selector }) in Page

* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage

* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests

* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters

* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]

* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters

* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog

* fix(types): add waitForCapture to IPage mock helpers in tests

* docs: simplify README to 50-line overview with docs link

Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.

* fix(perf): CDPPage smart wait + MutationObserver selector wait

- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
  matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
  resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context

* docs: move built-in commands table to docs/adapters/index.md

Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.

* docs: move Quick Start before Prerequisites, tone down Electron promo copy

- Reorder sections: Why opencli → Quick Start → Prerequisites
  so users see "how to use" before "what you need"
- Replace "The Most Powerful Update Has Arrived!" marketing copy
  with a plain one-liner description of the Electron feature
- Remove redundant Bun dev/test commands from Prerequisites
  (developer-only content, not relevant to end users)
- Add "(requires Extension)" hint to browser command in Quick Start

* docs: polish Quick Start — one-line source install, Verify setup section
2026-03-28 12:05:59 +08:00
jakevin c4e7a94bc4 docs: README usability — Quick Start first, tone down promo copy
* chore(release): 1.5.2

* test(e2e): stabilize output format checks

* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)

* docs: add perf smart-wait implementation plan

* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers

* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage

* feat(perf): implement waitForCapture() and wait({ selector }) in Page

* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage

* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests

* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters

* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]

* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters

* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog

* fix(types): add waitForCapture to IPage mock helpers in tests

* docs: simplify README to 50-line overview with docs link

Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.

* fix(perf): CDPPage smart wait + MutationObserver selector wait

- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
  matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
  resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context

* docs: move built-in commands table to docs/adapters/index.md

Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.

* docs: move Quick Start before Prerequisites, tone down Electron promo copy

- Reorder sections: Why opencli → Quick Start → Prerequisites
  so users see "how to use" before "what you need"
- Replace "The Most Powerful Update Has Arrived!" marketing copy
  with a plain one-liner description of the Electron feature
- Remove redundant Bun dev/test commands from Prerequisites
  (developer-only content, not relevant to end users)
- Add "(requires Extension)" hint to browser command in Quick Start
2026-03-28 11:57:34 +08:00
jakevin 3b0dcfddf0 docs: move built-in commands table to docs/adapters/index.md
* chore(release): 1.5.2

* test(e2e): stabilize output format checks

* docs: add perf smart-wait design spec (waitForCapture + selector wait + backoff)

* docs: add perf smart-wait implementation plan

* feat(perf): add waitForCaptureJs and waitForSelectorJs to dom-helpers

* feat(perf): extend WaitOptions with selector, add waitForCapture to IPage

* feat(perf): implement waitForCapture() and wait({ selector }) in Page

* feat(perf): implement waitForCapture() and wait({ selector }) in CDPPage

* feat(perf): stepIntercept uses installInterceptor+waitForCapture+getInterceptedRequests

* fix(perf): replace wait(N) with waitForCapture(N) in 7 INTERCEPT adapters

* feat(perf): daemon cold-start uses exponential backoff [50..3000ms]

* fix(perf): replace wait(5) with wait({ selector }) in 15 Twitter UI adapters

* fix(perf): replace wait(N) with wait({ selector }) in medium/substack/bloomberg/sinablog

* fix(types): add waitForCapture to IPage mock helpers in tests

* docs: simplify README to 50-line overview with docs link

Remove redundant command table (already in docs/adapters/index.md).
Keep badges, quick-start, and star history only.

* fix(perf): CDPPage smart wait + MutationObserver selector wait

- CDPPage.wait(N>=1) now uses waitForDomStableJs instead of fixed sleep,
  matching Page.wait() behavior and saving unnecessary idle time
- waitForSelectorJs switches from 100ms polling to MutationObserver,
  resolving instantly when the target element appears in the DOM
- Update dom-helpers tests to stub MutationObserver for Node eval context

* docs: move built-in commands table to docs/adapters/index.md

Replace the 70-row site/command table in README with a one-line link.
All other README sections (Highlights, Why opencli, Quick Start,
External CLI Hub, Desktop App Adapters, Download, Plugins, AI Agents,
Troubleshooting) are preserved unchanged.
2026-03-28 11:51:29 +08:00
jakevin 4f13484aa3 chore(release): 1.5.3 (#533) v1.5.3 2026-03-28 11:02:09 +08:00
AstroHan 5ab3f5d7d5 fix(extension): change automation window state from minimized to normal (#531)
chrome.windows.create rejects state:'minimized' when combined with
width/height (Chrome API constraint). Revert to state:'normal' to fix
the "Invalid value for state" error. The 30s idle timeout from #521
is preserved.

Fixes #526
2026-03-28 10:57:40 +08:00
jakevin 55c3259f28 refactor: slim CI matrix, shared utils, unified logging, remove __test__ leak (#525)
* refactor: slim CI matrix, extract shared utils, unify logging, remove __test__ from public API

- CI: unit-test uses dynamic matrix (PR=ubuntu+22 only, push=full 3OS×2Node);
  adapter-test reduced to ubuntu-latest (OS doesn't affect pure unit tests)
- _shared/common.ts: add sleep() and clampToRange() shared adapter utilities;
  douban/utils.ts and sinablog/utils.ts now use clampToRange instead of duplicate clampLimit
- browser/daemon-client.ts: replace inline setTimeout Promise with local sleep()
- execution.ts: replace conditional console.error with log.debug
- browser/index.ts: remove __test__ from public barrel export;
  browser.test.ts now imports internal helpers directly from source files

* fix: remove unused afterEach import, fix schedule/dispatch CI matrix, clarify clampToRange docs

* refactor: move sleep to src/utils.ts, simplify clamp signature to match lodash convention
2026-03-28 02:19:07 +08:00
jakevin 70bd87b98c perf: smart-wait — waitForCapture, wait({ selector }), daemon backoff
- waitForCapture(): polls window.__opencli_xhr instead of DOM-stable; fixes INTERCEPT adapters returning empty after smart-wait refactor
- wait({ selector }): MutationObserver-based wait; resolves instantly on element insertion
- CDPPage.wait(N): smart DOM-stable wait (matches Page.wait behavior)
- Daemon cold-start: exponential backoff [50..3000ms]
- README: simplified to 50-line overview
2026-03-28 02:16:13 +08:00
wangsl ea0cf4d0b0 fix(network): honor proxy env for node requests (#512)
* fix(network): honor proxy env for node requests

* fix(network): honor default ports in NO_PROXY

* refactor(network): normalize proxy config handling

* refactor(network): delegate proxy env handling to undici

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-28 01:20:39 +08:00
jakevin 9919f03aed chore(release): 1.5.2 (#523)
* chore(release): 1.5.2

* test(e2e): stabilize output format checks
v1.5.2
2026-03-28 00:48:27 +08:00
jakevin 3834292871 perf: smart pre-navigation, DOM-stable waits, in-memory URL tracking (#524)
* perf: smart pre-navigation — skip redundant domain nav + remove hardcoded 2s wait

- Add `getCurrentUrl()` to IPage, Page, and CDPPage to check current browser URL
- Skip pre-navigation entirely if the browser is already on the target domain
- Remove the hardcoded `page.wait(2)` after pre-navigation — `page.goto()` already
  includes smart DOM-settle detection via `waitForDomStable`, making the fixed
  2-second sleep redundant
- Saves ~2s per browser command in the common case (consecutive commands on the
  same site), and ~1-2s even on cold navigation

* perf: smart page.wait() — DOM-stable early return for waits >= 1s

For page.wait(N) where N >= 1 second, use DOM MutationObserver-based
stability detection instead of a fixed sleep. The original wait time
becomes a hard cap, but the call returns as soon as the DOM stops
mutating (500ms quiet period).

This benefits ~200 hardcoded sleep calls across ~40 adapters without
changing any adapter code. A typical page.wait(5) now completes in
<1s when the page is already stable, instead of always waiting 5s.

Short waits (< 1s) are kept as fixed sleeps — these are typically
UI animation delays or anti-bot throttling where DOM-ready is irrelevant.

* refactor: getCurrentUrl() uses in-memory tracking instead of round-trip

Replace the sendCommand('exec', 'window.location.href') call with a
local _lastUrl field set during goto(). This eliminates a daemon HTTP
round-trip for the domain check, making isAlreadyOnDomain() zero-cost.

On fresh tabs (about:blank), _lastUrl is null so we correctly fall
through to navigation — no special-casing needed.
2026-03-28 00:46:30 +08:00
AstroHan 53b06db53d fix(browser): retry settle probe after SPA client-side redirect (#517)
* fix(browser): retry settle probe after SPA client-side redirect

SPA sites like creator.xiaohongshu.com can trigger a client-side
redirect after chrome.tabs reports status 'complete', invalidating
the CDP target. The waitForDomStable probe in page.goto() was
unprotected, causing -32000 "Inspected target navigated or closed".

Wrap the settle probe in try/catch with a single 200ms-delayed retry,
consistent with the existing stealth injection error handling pattern.
The retry gives the SPA redirect time to complete, while the outer
catch ensures settle failure never crashes goto() since navigation
itself already succeeded.

Closes #502

* review: narrow settle retry to target redirects

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-28 00:29:45 +08:00
jakevin f79d5ab838 fix(ci): stabilize public command and plugin e2e checks (#522)
* test(e2e): accept current apple podcasts fetch errors

* fix(ci): stabilize plugin and public command checks

---------

Co-authored-by: pi-dal <hi@pi-dal.com>
2026-03-28 00:19:33 +08:00
jakevin 6e90356649 chore(extension): bump version to 1.5.1 (#519) 2026-03-28 00:09:30 +08:00
AstroHan 0085d63fb8 fix(weread): resolve shelf auth fallback (#518)
* fix(weread): resolve shelf auth fallback

* chore(docs): move local issue notes out of pr

* fix(weread): classify session expiry as auth required

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-28 00:09:28 +08:00
jakevin 0f3021a086 fix: relax extension version check, enable all adapter tests (#520)
* feat: zero onboarding, extension version check, and update notifier

- Fail-fast guard in execution.ts: when daemon is running but extension
  is not connected, immediately surface a setup guide instead of waiting
  for the 30s connect timeout

- Extension version handshake: extension sends `hello` with its version
  on WebSocket connect; daemon stores it and exposes via /status; CLI
  warns on mismatch in both execution path and `opencli doctor`

- `opencli doctor` now shows extension version inline and reports
  version mismatch as an actionable issue

- Non-blocking npm update checker: registers a process exit hook so the
  update notice appears after command output (same pattern as npm/gh/yarn);
  background fetch writes to ~/.opencli/update-check.json for next run

- postinstall: print Browser Bridge setup instructions after shell
  completion install for first-time global install users

Bug fixes caught in review:
- discover.ts: add AbortController timeout to checkDaemonStatus() fetch,
  move clearTimeout after res.json() to cover body streaming
- daemon.ts: clear extensionVersion and reject pending requests in
  ws.on('error') handler, not just ws.on('close')
- update-check.ts: skip update notice when process exits with non-zero
  code; read cache once at module load to avoid double disk I/O;
  guard isNewer() against NaN from pre-release version strings

* fix: relax extension version check to major-only in doctor, remove from hot path

* test: enable all adapter tests via wildcard glob, fix apple-podcasts url field

* fix: clearTimeout in finally block, reset extensionVersion on reconnect, fix e2e regex
2026-03-28 00:08:10 +08:00
jakevin a1561e5361 fix(extension): minimize automation window + reduce idle timeout to 30s (#521)
- Create automation window with `state: 'minimized'` so it never
  appears in the user's taskbar or steals visual attention
- Reduce idle timeout from 120s to 30s — window closes quickly after
  the last command finishes, instead of lingering for 2 minutes
- CDP debugger works fine on minimized windows, no functional impact

Fixes the user-visible issue of a blank data:text/html tab appearing
during command execution.
2026-03-28 00:03:44 +08:00
jakevin f9f11e4b17 chore(release): 1.5.1 (#513) v1.5.1 2026-03-27 19:25:37 +08:00
jakevin 5bd0497244 refactor(plugin): make plugin installs transactional (#509)
* feat(plugin): stage installs before promote

* feat(plugin): make remote updates transactional

* fix(plugin): rollback relink failures

* refactor(plugin): unify transactional publish flow

* refactor(plugin): extract publish pipeline helpers

* refactor(plugin): add structured source model

* refactor(plugin): promote lockfile to structured sources

* fix(plugin): preserve lock reads when migration rewrite fails

* fix(plugin): write lockfiles atomically

* test(plugin): make source helper assertions cross-platform
2026-03-27 18:25:56 +08:00
Guyue a2d1199b50 fix(v2ex): fetch hot topics through browser context (#493)
* Update hot.yaml

* review: fetch v2ex hot data within browser context

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-27 17:50:44 +08:00
jakevin cf99c61df5 perf: smart pre-navigation — skip redundant nav + remove 2s wait (#507)
* perf: smart pre-navigation — skip redundant domain nav + remove hardcoded 2s wait

- Add `getCurrentUrl()` to IPage, Page, and CDPPage to check current browser URL
- Skip pre-navigation entirely if the browser is already on the target domain
- Remove the hardcoded `page.wait(2)` after pre-navigation — `page.goto()` already
  includes smart DOM-settle detection via `waitForDomStable`, making the fixed
  2-second sleep redundant
- Saves ~2s per browser command in the common case (consecutive commands on the
  same site), and ~1-2s even on cold navigation

* perf: smart page.wait() — DOM-stable early return for waits >= 1s

For page.wait(N) where N >= 1 second, use DOM MutationObserver-based
stability detection instead of a fixed sleep. The original wait time
becomes a hard cap, but the call returns as soon as the DOM stops
mutating (500ms quiet period).

This benefits ~200 hardcoded sleep calls across ~40 adapters without
changing any adapter code. A typical page.wait(5) now completes in
<1s when the page is already stable, instead of always waiting 5s.

Short waits (< 1s) are kept as fixed sleeps — these are typically
UI animation delays or anti-bot throttling where DOM-ready is irrelevant.

* refactor: getCurrentUrl() uses in-memory tracking instead of round-trip

Replace the sendCommand('exec', 'window.location.href') call with a
local _lastUrl field set during goto(). This eliminates a daemon HTTP
round-trip for the domain check, making isAlreadyOnDomain() zero-cost.

On fresh tabs (about:blank), _lastUrl is null so we correctly fall
through to navigation — no special-casing needed.
2026-03-27 17:42:48 +08:00
AlexYue 70ad5700c9 feat(plugin): support multi-source plugin install (ssh, git@, generic https) (#504)
Extends parseSource() to accept any git-cloneable URL, not just GitHub:
- ssh://git@host/path/repo.git
- git@host:user/repo.git (SCP-style)
- https://any-host.com/path/repo.git

GitHub shorthand (github:user/repo) and local paths continue to work.
Updated error messages, CLI description, docs, and added 7 new unit tests.

Closes #492
2026-03-27 17:04:57 +08:00
AlexYue 2ad1215ac2 fix(plugin): prevent raw .ts import crash when esbuild transpilation fails (#500) (#503)
When a TS plugin is installed but esbuild is unavailable or transpilation
fails silently, the plugin discovery would attempt to import() the raw
.ts file, causing 'Unknown file extension .ts' on production Node.js.

Changes:
- discovery.ts: Skip raw .ts import when no compiled .js exists; show
  an actionable warning guiding the user to re-transpile or install esbuild
- plugin.ts: Upgrade esbuild-not-found from debug to warn level; log
  the outer catch error instead of silently swallowing it

Closes #500
2026-03-27 16:57:59 +08:00
AstroHan ee59750ddb fix(execution): apply timeout to non-browser commands (#383)
Non-browser commands (`browser: false`) ran without any timeout
protection, even when `timeoutSeconds` was explicitly set. This wraps
the non-browser execution path with `runWithTimeout()` when the
adapter defines a positive `timeoutSeconds`.

Also adds an optional `hint` parameter to `TimeoutError` so the
non-browser path shows a relevant suggestion instead of the
browser-specific `OPENCLI_BROWSER_COMMAND_TIMEOUT` env var hint.
2026-03-27 14:54:28 +08:00
sline 9a9e078462 feat(bluesky): add Bluesky adapter with 9 commands (#215)
Bluesky (9 commands, public AT Protocol API, no auth needed):
- profile: user profile info (followers, following, posts)
- user: recent posts from a user with engagement stats
- trending: trending topics on Bluesky
- search: search users
- feeds: popular feed generators
- followers: list user's followers
- following: list accounts a user follows
- thread: post thread with replies
- starter-packs: user's starter packs

All commands use the public Bluesky API, no browser or login required.
2026-03-27 14:39:43 +08:00
AlexYue 55d0473bcf fix(plugin): handle EXDEV cross-filesystem rename during install (#488)
* fix(plugin): handle EXDEV cross-filesystem rename during install

fs.renameSync() fails with EXDEV when source and destination are on
different filesystem mount points. This commonly happens because plugin
clones land in os.tmpdir() (often /tmp on a tmpfs) while plugins are
installed to ~/.opencli/plugins/ (on the root filesystem).

Add a moveDir() helper that catches EXDEV and falls back to
fs.cpSync() + fs.rmSync(). Applied to both single-plugin and monorepo
install paths.

* review: clean up failed EXDEV fallback installs

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-27 14:22:03 +08:00
AstroHan 1a44d8ccff feat(xiaohongshu): add published_at to search results (#484) (#485)
Derive approximate publish date from note IDs, which follow MongoDB
ObjectID format (first 8 hex chars = Unix timestamp). Exported as a
pure function with UTC+8 offset for China timezone.

Closes #484
2026-03-27 14:20:22 +08:00
jakevin 31cb2291c5 perf: parallel file discovery, plugin scanning, and external CLI caching (#501)
- Parallelize file scanning in discoverClisFromFs and discoverPluginDir
  using Promise.all(files.map(async ...)) instead of serial for-of with
  await, so isCliModule checks run concurrently
- Parallelize plugin directory scanning in discoverPlugins
- Cache loadExternalClis() result to avoid re-parsing YAML on every call
- Invalidate cache in registerExternalCli after writing to disk
- Cache strategyLabel() call in list command to avoid redundant computation
- Add comment explaining why discovery must remain sequential (plugin override semantics)
2026-03-27 14:19:55 +08:00
AstroHan fb5b608607 fix(twitter): use DOM-only scraping for trending to match page results (#486)
Remove guide.json API path that returned data inconsistent with what
users see on the page (#463). Use semantic caret button detection
via data-testid instead of position-based heuristics, and validate
post count text contains digits before displaying.
2026-03-27 14:15:36 +08:00
AlexYue 5e2e1dfe60 fix(plugin): detect symlinked monorepo sub-plugins in discoverPlugins (#487)
* fix(plugin): detect symlinked monorepo sub-plugins in discoverPlugins

discoverPlugins() used entry.isDirectory() to filter plugin directories,
but monorepo sub-plugins are installed as symlinks pointing into
~/.opencli/monorepos/. On most Node.js versions, isDirectory() returns
false for symlinks, causing monorepo plugin commands to be silently
skipped during discovery.

Add entry.isSymbolicLink() check so symlinked plugin directories are
properly discovered and their commands registered.

* fix(plugin): skip broken symlink discovery

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-27 14:12:54 +08:00
AstroHan 39eec0da82 fix(xiaohongshu): adapt publish to new two-step creator center UI (#490)
* fix(xiaohongshu): adapt publish to new two-step creator center UI (#460)

The creator center now requires image upload before showing the
title/content editor form. This caused the publish command to fail
with "Could not find title input".

- Add waitForEditForm() to poll for editor after image upload
- Extract TITLE_SELECTORS constant shared by waitForEditForm and fillField
- Add contenteditable title selectors for new UI
- Make images required (new UI mandates images before editor)
- Update draft button to match both '暂存离开' and '存草稿'
- Exclude title placeholder from content fallback selector
- Update tests to match new flow

* refactor(xiaohongshu): clarify publish surface states

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-27 14:03:10 +08:00
AlexYue 384419f4f5 feat(plugin): support local path install via file:// and absolute path (#491)
Add support for installing plugins from local directories:
  opencli plugin install file:///path/to/my-plugin
  opencli plugin install /path/to/my-plugin

Local plugins are symlinked (not copied) into ~/.opencli/plugins/
so code changes are reflected immediately without reinstall — ideal
for plugin development workflows.

Changes:
- parseSource() now handles file:// URLs and bare absolute paths
- New installLocalPlugin() creates symlink + installs deps + transpiles
- Lock file records 'local:<path>' as source for local plugins
- 6 new test cases for local path parsing and install behavior
2026-03-27 13:45:42 +08:00
AlexYue fa4c44a0c4 feat(plugin): add 'plugin create <name>' scaffold command (#494)
* feat(plugin): add 'plugin create <name>' scaffold command

Generate a ready-to-develop plugin directory with all required files:
- opencli-plugin.json (manifest with name, version, compatibility)
- package.json (ESM, peer dependency on @jackwener/opencli)
- hello.yaml (sample YAML command using httpbin)
- greet.ts (sample TS command using cli() API)
- README.md (install, usage, and development instructions)

Usage:
  opencli plugin create my-plugin
  opencli plugin create my-plugin --dir /path/to/dir
  opencli plugin create my-plugin --description 'My awesome plugin'

Includes 5 test cases for scaffold generation and error handling.

* fix(plugin): align scaffold with local install flow

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-27 13:34:36 +08:00
AlexYue d74263523f fix(plugin): remove legacy LOCK_FILE/MONOREPOS_DIR constants (#495)
Remove the module-level LOCK_FILE and MONOREPOS_DIR constants that were
computed at load time using os.homedir(). These ignored the HOME
environment variable, causing path mismatches when tests use HOME for
isolation.

All usages now go through getLockFilePath() and getMonoreposDir() which
respect process.env.HOME. Updated plugin.test.ts accordingly.
2026-03-27 13:27:47 +08:00
jakevin f9f018d7f4 fix(doctor): remove unused fix option and add release URL to extension install hint (#498)
* feat: zero onboarding, extension version check, and update notifier

- Fail-fast guard in execution.ts: when daemon is running but extension
  is not connected, immediately surface a setup guide instead of waiting
  for the 30s connect timeout

- Extension version handshake: extension sends `hello` with its version
  on WebSocket connect; daemon stores it and exposes via /status; CLI
  warns on mismatch in both execution path and `opencli doctor`

- `opencli doctor` now shows extension version inline and reports
  version mismatch as an actionable issue

- Non-blocking npm update checker: registers a process exit hook so the
  update notice appears after command output (same pattern as npm/gh/yarn);
  background fetch writes to ~/.opencli/update-check.json for next run

- postinstall: print Browser Bridge setup instructions after shell
  completion install for first-time global install users

Bug fixes caught in review:
- discover.ts: add AbortController timeout to checkDaemonStatus() fetch,
  move clearTimeout after res.json() to cover body streaming
- daemon.ts: clear extensionVersion and reject pending requests in
  ws.on('error') handler, not just ws.on('close')
- update-check.ts: skip update notice when process exits with non-zero
  code; read cache once at module load to avoid double disk I/O;
  guard isNewer() against NaN from pre-release version strings

* fix: reduce fail-fast timeout to 300ms and guard stderr.write in exit hook

* fix(doctor): remove unused fix option and add release URL to extension install hint

* fix(e2e): update BrowserBridge unavailable detection regex to match current error format
2026-03-27 13:26:27 +08:00