@jackwener/opencli 1.5.6 → 1.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/README.md +4 -2
- package/README.zh-CN.md +4 -1
- package/SKILL.md +879 -0
- package/dist/browser/cdp.d.ts +1 -0
- package/dist/browser/cdp.js +30 -27
- package/dist/browser/daemon-client.d.ts +7 -1
- package/dist/browser/daemon-client.js +3 -0
- package/dist/browser/dom-helpers.js +1 -0
- package/dist/browser/dom-helpers.test.js +14 -1
- package/dist/browser/mcp.js +18 -13
- package/dist/browser/page.js +22 -2
- package/dist/browser/page.test.d.ts +1 -0
- package/dist/browser/page.test.js +44 -0
- package/dist/browser/stealth.js +198 -0
- package/dist/browser/stealth.test.d.ts +1 -0
- package/dist/browser/stealth.test.js +134 -0
- package/dist/browser.test.js +1 -1
- package/dist/build-manifest.d.ts +1 -0
- package/dist/build-manifest.js +5 -1
- package/dist/build-manifest.test.js +2 -0
- package/dist/cli-manifest.json +544 -137
- package/dist/cli.js +20 -3
- package/dist/clis/antigravity/serve.d.ts +1 -1
- package/dist/clis/antigravity/serve.js +5 -8
- package/dist/clis/bilibili/subtitle.js +4 -0
- package/dist/clis/bilibili/subtitle.test.d.ts +1 -0
- package/dist/clis/bilibili/subtitle.test.js +48 -0
- package/dist/clis/chatwise/ask.js +0 -2
- package/dist/clis/chatwise/export.js +0 -2
- package/dist/clis/chatwise/history.js +0 -2
- package/dist/clis/chatwise/model.js +0 -2
- package/dist/clis/chatwise/new.js +1 -2
- package/dist/clis/chatwise/read.js +0 -2
- package/dist/clis/chatwise/screenshot.js +1 -2
- package/dist/clis/chatwise/send.js +0 -2
- package/dist/clis/chatwise/status.js +1 -2
- package/dist/clis/ctrip/search.d.ts +13 -0
- package/dist/clis/ctrip/search.js +73 -48
- package/dist/clis/ctrip/search.test.d.ts +1 -0
- package/dist/clis/ctrip/search.test.js +64 -0
- package/dist/clis/douyin/_shared/sts2.js +8 -2
- package/dist/clis/douyin/_shared/sts2.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/sts2.test.js +27 -0
- package/dist/clis/douyin/activities.js +4 -2
- package/dist/clis/douyin/activities.test.js +34 -1
- package/dist/clis/douyin/collections.js +1 -1
- package/dist/clis/douyin/collections.test.js +24 -2
- package/dist/clis/douyin/draft.d.ts +8 -11
- package/dist/clis/douyin/draft.js +302 -185
- package/dist/clis/douyin/draft.test.d.ts +1 -1
- package/dist/clis/douyin/draft.test.js +357 -2
- package/dist/clis/douyin/hashtag.js +9 -2
- package/dist/clis/douyin/hashtag.test.js +35 -2
- package/dist/clis/douyin/profile.js +1 -1
- package/dist/clis/douyin/profile.test.js +36 -1
- package/dist/clis/douyin/videos.js +22 -5
- package/dist/clis/douyin/videos.test.js +45 -2
- package/dist/clis/facebook/search.test.d.ts +5 -0
- package/dist/clis/facebook/search.test.js +60 -0
- package/dist/clis/facebook/search.yaml +4 -3
- package/dist/clis/instagram/download.d.ts +16 -0
- package/dist/clis/instagram/download.js +225 -0
- package/dist/clis/instagram/download.test.d.ts +1 -0
- package/dist/clis/instagram/download.test.js +118 -0
- package/dist/clis/notebooklm/bind-current.d.ts +1 -0
- package/dist/clis/notebooklm/bind-current.js +29 -0
- package/dist/clis/notebooklm/bind-current.test.d.ts +1 -0
- package/dist/clis/notebooklm/bind-current.test.js +35 -0
- package/dist/clis/notebooklm/binding.test.d.ts +1 -0
- package/dist/clis/notebooklm/binding.test.js +44 -0
- package/dist/clis/notebooklm/compat.test.d.ts +3 -0
- package/dist/clis/notebooklm/compat.test.js +16 -0
- package/dist/clis/notebooklm/current.d.ts +1 -0
- package/dist/clis/notebooklm/current.js +28 -0
- package/dist/clis/notebooklm/get.d.ts +1 -0
- package/dist/clis/notebooklm/get.js +37 -0
- package/dist/clis/notebooklm/history.d.ts +1 -0
- package/dist/clis/notebooklm/history.js +25 -0
- package/dist/clis/notebooklm/history.test.d.ts +1 -0
- package/dist/clis/notebooklm/history.test.js +58 -0
- package/dist/clis/notebooklm/list.d.ts +1 -0
- package/dist/clis/notebooklm/list.js +35 -0
- package/dist/clis/notebooklm/note-list.d.ts +1 -0
- package/dist/clis/notebooklm/note-list.js +28 -0
- package/dist/clis/notebooklm/note-list.test.d.ts +1 -0
- package/dist/clis/notebooklm/note-list.test.js +56 -0
- package/dist/clis/notebooklm/notes-get.d.ts +1 -0
- package/dist/clis/notebooklm/notes-get.js +47 -0
- package/dist/clis/notebooklm/notes-get.test.d.ts +1 -0
- package/dist/clis/notebooklm/notes-get.test.js +72 -0
- package/dist/clis/notebooklm/rpc.d.ts +36 -0
- package/dist/clis/notebooklm/rpc.js +189 -0
- package/dist/clis/notebooklm/rpc.test.d.ts +1 -0
- package/dist/clis/notebooklm/rpc.test.js +105 -0
- package/dist/clis/notebooklm/shared.d.ts +87 -0
- package/dist/clis/notebooklm/shared.js +3 -0
- package/dist/clis/notebooklm/source-fulltext.d.ts +1 -0
- package/dist/clis/notebooklm/source-fulltext.js +44 -0
- package/dist/clis/notebooklm/source-fulltext.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-fulltext.test.js +106 -0
- package/dist/clis/notebooklm/source-get.d.ts +1 -0
- package/dist/clis/notebooklm/source-get.js +40 -0
- package/dist/clis/notebooklm/source-get.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-get.test.js +84 -0
- package/dist/clis/notebooklm/source-guide.d.ts +1 -0
- package/dist/clis/notebooklm/source-guide.js +44 -0
- package/dist/clis/notebooklm/source-guide.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-guide.test.js +104 -0
- package/dist/clis/notebooklm/source-list.d.ts +1 -0
- package/dist/clis/notebooklm/source-list.js +30 -0
- package/dist/clis/notebooklm/status.d.ts +1 -0
- package/dist/clis/notebooklm/status.js +31 -0
- package/dist/clis/notebooklm/summary.d.ts +1 -0
- package/dist/clis/notebooklm/summary.js +30 -0
- package/dist/clis/notebooklm/summary.test.d.ts +1 -0
- package/dist/clis/notebooklm/summary.test.js +78 -0
- package/dist/clis/notebooklm/utils.d.ts +37 -0
- package/dist/clis/notebooklm/utils.js +739 -0
- package/dist/clis/notebooklm/utils.test.d.ts +1 -0
- package/dist/clis/notebooklm/utils.test.js +390 -0
- package/dist/clis/substack/utils.d.ts +4 -0
- package/dist/clis/substack/utils.js +8 -2
- package/dist/clis/substack/utils.test.d.ts +1 -0
- package/dist/clis/substack/utils.test.js +46 -0
- package/dist/clis/v2ex/hot.yaml +4 -1
- package/dist/clis/v2ex/latest.yaml +4 -1
- package/dist/clis/v2ex/topic.yaml +6 -1
- package/dist/clis/weixin/download.d.ts +9 -0
- package/dist/clis/weixin/download.js +76 -6
- package/dist/clis/weread/book.js +108 -2
- package/dist/clis/weread/commands.test.js +262 -152
- package/dist/clis/weread/utils.d.ts +10 -0
- package/dist/clis/weread/utils.js +27 -7
- package/dist/clis/xiaohongshu/comments.d.ts +3 -0
- package/dist/clis/xiaohongshu/comments.js +76 -17
- package/dist/clis/xiaohongshu/comments.test.js +70 -9
- package/dist/clis/xiaohongshu/download.d.ts +4 -1
- package/dist/clis/xiaohongshu/download.js +83 -22
- package/dist/clis/xiaohongshu/download.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/download.test.js +75 -0
- package/dist/clis/xiaohongshu/note-helpers.d.ts +12 -0
- package/dist/clis/xiaohongshu/note-helpers.js +23 -0
- package/dist/clis/xiaohongshu/note.d.ts +7 -0
- package/dist/clis/xiaohongshu/note.js +76 -0
- package/dist/clis/xiaohongshu/note.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/note.test.js +136 -0
- package/dist/clis/xiaohongshu/search.js +9 -0
- package/dist/clis/xiaohongshu/search.test.js +10 -4
- package/dist/clis/youtube/search.js +57 -17
- package/dist/clis/zhihu/question.js +19 -17
- package/dist/clis/zhihu/question.test.d.ts +1 -0
- package/dist/clis/zhihu/question.test.js +54 -0
- package/dist/commanderAdapter.js +9 -0
- package/dist/commanderAdapter.test.js +25 -0
- package/dist/commands/daemon.d.ts +9 -0
- package/dist/commands/daemon.js +124 -0
- package/dist/commands/daemon.test.d.ts +1 -0
- package/dist/commands/daemon.test.js +185 -0
- package/dist/completion.js +3 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/daemon.d.ts +1 -1
- package/dist/daemon.js +25 -14
- package/dist/daemon.test.d.ts +1 -0
- package/dist/daemon.test.js +65 -0
- package/dist/discovery.d.ts +9 -0
- package/dist/discovery.js +47 -2
- package/dist/electron-apps.d.ts +29 -0
- package/dist/electron-apps.js +65 -0
- package/dist/electron-apps.test.d.ts +1 -0
- package/dist/electron-apps.test.js +43 -0
- package/dist/engine.test.js +41 -9
- package/dist/execution.js +20 -16
- package/dist/extension-manifest-regression.test.js +1 -0
- package/dist/idle-manager.d.ts +19 -0
- package/dist/idle-manager.js +54 -0
- package/dist/launcher.d.ts +36 -0
- package/dist/launcher.js +152 -0
- package/dist/launcher.test.d.ts +1 -0
- package/dist/launcher.test.js +57 -0
- package/dist/main.js +3 -3
- package/dist/registry.d.ts +1 -0
- package/dist/registry.js +31 -3
- package/dist/registry.test.js +13 -0
- package/dist/runtime.d.ts +5 -3
- package/dist/runtime.js +12 -5
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +3 -0
- package/dist/serialization.test.js +17 -1
- package/dist/tui.d.ts +7 -0
- package/dist/tui.js +52 -0
- package/dist/tui.test.d.ts +1 -0
- package/dist/tui.test.js +19 -0
- package/dist/weixin-download.test.js +14 -0
- package/docs/.vitepress/config.mts +1 -0
- package/docs/adapters/browser/notebooklm.md +69 -0
- package/docs/adapters/browser/xiaohongshu.md +19 -10
- package/docs/adapters/index.md +67 -66
- package/docs/guide/browser-bridge.md +12 -0
- package/docs/guide/troubleshooting.md +9 -4
- package/docs/superpowers/plans/2026-03-31-daemon-lifecycle-redesign.md +857 -0
- package/docs/superpowers/specs/2026-03-31-daemon-lifecycle-redesign.md +208 -0
- package/docs/zh/guide/browser-bridge.md +12 -0
- package/extension/dist/background.js +250 -11
- package/extension/manifest.json +2 -1
- package/extension/src/background.test.ts +202 -2
- package/extension/src/background.ts +175 -10
- package/extension/src/cdp.test.ts +75 -0
- package/extension/src/cdp.ts +89 -3
- package/extension/src/protocol.ts +7 -5
- package/package.json +1 -1
- package/src/browser/cdp.ts +24 -17
- package/src/browser/daemon-client.ts +7 -1
- package/src/browser/dom-helpers.test.ts +15 -1
- package/src/browser/dom-helpers.ts +1 -0
- package/src/browser/mcp.ts +18 -13
- package/src/browser/page.test.ts +58 -0
- package/src/browser/page.ts +18 -2
- package/src/browser/stealth.test.ts +153 -0
- package/src/browser/stealth.ts +198 -0
- package/src/browser.test.ts +1 -1
- package/src/build-manifest.test.ts +2 -0
- package/src/build-manifest.ts +6 -1
- package/src/cli.ts +21 -3
- package/src/clis/antigravity/SKILL.md +3 -12
- package/src/clis/antigravity/serve.ts +5 -10
- package/src/clis/bilibili/subtitle.test.ts +60 -0
- package/src/clis/bilibili/subtitle.ts +4 -0
- package/src/clis/chatwise/ask.ts +0 -2
- package/src/clis/chatwise/export.ts +0 -2
- package/src/clis/chatwise/history.ts +0 -2
- package/src/clis/chatwise/model.ts +0 -2
- package/src/clis/chatwise/new.ts +1 -2
- package/src/clis/chatwise/read.ts +0 -2
- package/src/clis/chatwise/screenshot.ts +1 -2
- package/src/clis/chatwise/send.ts +0 -2
- package/src/clis/chatwise/status.ts +1 -2
- package/src/clis/ctrip/search.test.ts +73 -0
- package/src/clis/ctrip/search.ts +97 -47
- package/src/clis/douyin/_shared/sts2.test.ts +31 -0
- package/src/clis/douyin/_shared/sts2.ts +11 -3
- package/src/clis/douyin/activities.test.ts +41 -1
- package/src/clis/douyin/activities.ts +12 -3
- package/src/clis/douyin/collections.test.ts +35 -2
- package/src/clis/douyin/collections.ts +1 -1
- package/src/clis/douyin/draft.test.ts +444 -2
- package/src/clis/douyin/draft.ts +382 -218
- package/src/clis/douyin/hashtag.test.ts +42 -2
- package/src/clis/douyin/hashtag.ts +11 -3
- package/src/clis/douyin/profile.test.ts +43 -1
- package/src/clis/douyin/profile.ts +9 -2
- package/src/clis/douyin/videos.test.ts +52 -2
- package/src/clis/douyin/videos.ts +49 -15
- package/src/clis/facebook/search.test.ts +70 -0
- package/src/clis/facebook/search.yaml +4 -3
- package/src/clis/instagram/download.test.ts +159 -0
- package/src/clis/instagram/download.ts +286 -0
- package/src/clis/notebooklm/bind-current.test.ts +43 -0
- package/src/clis/notebooklm/bind-current.ts +36 -0
- package/src/clis/notebooklm/binding.test.ts +53 -0
- package/src/clis/notebooklm/compat.test.ts +19 -0
- package/src/clis/notebooklm/current.ts +38 -0
- package/src/clis/notebooklm/get.ts +53 -0
- package/src/clis/notebooklm/history.test.ts +70 -0
- package/src/clis/notebooklm/history.ts +36 -0
- package/src/clis/notebooklm/list.ts +40 -0
- package/src/clis/notebooklm/note-list.test.ts +64 -0
- package/src/clis/notebooklm/note-list.ts +42 -0
- package/src/clis/notebooklm/notes-get.test.ts +88 -0
- package/src/clis/notebooklm/notes-get.ts +67 -0
- package/src/clis/notebooklm/rpc.test.ts +126 -0
- package/src/clis/notebooklm/rpc.ts +286 -0
- package/src/clis/notebooklm/shared.ts +98 -0
- package/src/clis/notebooklm/source-fulltext.test.ts +123 -0
- package/src/clis/notebooklm/source-fulltext.ts +69 -0
- package/src/clis/notebooklm/source-get.test.ts +100 -0
- package/src/clis/notebooklm/source-get.ts +60 -0
- package/src/clis/notebooklm/source-guide.test.ts +121 -0
- package/src/clis/notebooklm/source-guide.ts +69 -0
- package/src/clis/notebooklm/source-list.ts +45 -0
- package/src/clis/notebooklm/status.ts +34 -0
- package/src/clis/notebooklm/summary.test.ts +94 -0
- package/src/clis/notebooklm/summary.ts +45 -0
- package/src/clis/notebooklm/utils.test.ts +446 -0
- package/src/clis/notebooklm/utils.ts +893 -0
- package/src/clis/substack/utils.test.ts +54 -0
- package/src/clis/substack/utils.ts +10 -2
- package/src/clis/v2ex/hot.yaml +4 -1
- package/src/clis/v2ex/latest.yaml +4 -1
- package/src/clis/v2ex/topic.yaml +6 -1
- package/src/clis/weixin/download.ts +95 -6
- package/src/clis/weread/book.ts +142 -2
- package/src/clis/weread/commands.test.ts +314 -154
- package/src/clis/weread/utils.ts +33 -4
- package/src/clis/xiaohongshu/comments.test.ts +85 -9
- package/src/clis/xiaohongshu/comments.ts +76 -17
- package/src/clis/xiaohongshu/download.test.ts +96 -0
- package/src/clis/xiaohongshu/download.ts +83 -22
- package/src/clis/xiaohongshu/note-helpers.ts +25 -0
- package/src/clis/xiaohongshu/note.test.ts +164 -0
- package/src/clis/xiaohongshu/note.ts +86 -0
- package/src/clis/xiaohongshu/search.test.ts +11 -4
- package/src/clis/xiaohongshu/search.ts +13 -0
- package/src/clis/youtube/search.ts +57 -17
- package/src/clis/zhihu/question.test.ts +71 -0
- package/src/clis/zhihu/question.ts +27 -15
- package/src/commanderAdapter.test.ts +30 -0
- package/src/commanderAdapter.ts +7 -0
- package/src/commands/daemon.test.ts +238 -0
- package/src/commands/daemon.ts +135 -0
- package/src/completion.ts +2 -1
- package/src/constants.ts +3 -0
- package/src/daemon.test.ts +88 -0
- package/src/daemon.ts +26 -14
- package/src/discovery.ts +52 -2
- package/src/electron-apps.test.ts +50 -0
- package/src/electron-apps.ts +89 -0
- package/src/engine.test.ts +45 -9
- package/src/execution.ts +24 -19
- package/src/extension-manifest-regression.test.ts +1 -0
- package/src/idle-manager.ts +60 -0
- package/src/launcher.test.ts +67 -0
- package/src/launcher.ts +185 -0
- package/src/main.ts +3 -2
- package/src/registry.test.ts +15 -0
- package/src/registry.ts +32 -3
- package/src/runtime.ts +13 -7
- package/src/serialization.test.ts +19 -1
- package/src/serialization.ts +2 -0
- package/src/tui.test.ts +23 -0
- package/src/tui.ts +65 -0
- package/src/weixin-download.test.ts +27 -0
- package/tests/e2e/browser-public-extended.test.ts +6 -2
- package/chatwise-opencli.ps1 +0 -82
- package/dist/clis/chatwise/shared.d.ts +0 -2
- package/dist/clis/chatwise/shared.js +0 -6
- package/src/clis/chatwise/shared.ts +0 -8
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Everything else is just JS code sent via 'exec'.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export type Action = 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input';
|
|
8
|
+
export type Action = 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'bind-current';
|
|
9
9
|
|
|
10
10
|
export interface Command {
|
|
11
11
|
/** Unique request ID */
|
|
@@ -26,6 +26,10 @@ export interface Command {
|
|
|
26
26
|
index?: number;
|
|
27
27
|
/** Cookie domain filter */
|
|
28
28
|
domain?: string;
|
|
29
|
+
/** Optional hostname/domain to require for current-tab binding */
|
|
30
|
+
matchDomain?: string;
|
|
31
|
+
/** Optional pathname prefix to require for current-tab binding */
|
|
32
|
+
matchPathPrefix?: string;
|
|
29
33
|
/** Screenshot format: png (default) or jpeg */
|
|
30
34
|
format?: 'png' | 'jpeg';
|
|
31
35
|
/** JPEG quality (0-100), only for jpeg format */
|
|
@@ -58,7 +62,5 @@ export const DAEMON_PING_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}/ping`;
|
|
|
58
62
|
|
|
59
63
|
/** Base reconnect delay for extension WebSocket (ms) */
|
|
60
64
|
export const WS_RECONNECT_BASE_DELAY = 2000;
|
|
61
|
-
/** Max reconnect delay (ms) */
|
|
62
|
-
export const WS_RECONNECT_MAX_DELAY =
|
|
63
|
-
/** Idle timeout before daemon auto-exits (ms) */
|
|
64
|
-
export const DAEMON_IDLE_TIMEOUT = 5 * 60 * 1000;
|
|
65
|
+
/** Max reconnect delay (ms) — kept short since daemon is long-lived */
|
|
66
|
+
export const WS_RECONNECT_MAX_DELAY = 5000;
|
package/package.json
CHANGED
package/src/browser/cdp.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
waitForSelectorJs,
|
|
30
30
|
} from './dom-helpers.js';
|
|
31
31
|
import { isRecord, saveBase64ToFile } from '../utils.js';
|
|
32
|
+
import { getAllElectronApps } from '../electron-apps.js';
|
|
32
33
|
|
|
33
34
|
export interface CDPTarget {
|
|
34
35
|
type?: string;
|
|
@@ -56,11 +57,11 @@ export class CDPBridge implements IBrowserFactory {
|
|
|
56
57
|
private _pending = new Map<number, { resolve: (val: unknown) => void; reject: (err: Error) => void; timer: ReturnType<typeof setTimeout> }>();
|
|
57
58
|
private _eventListeners = new Map<string, Set<(params: unknown) => void>>();
|
|
58
59
|
|
|
59
|
-
async connect(opts?: { timeout?: number; workspace?: string }): Promise<IPage> {
|
|
60
|
+
async connect(opts?: { timeout?: number; workspace?: string; cdpEndpoint?: string }): Promise<IPage> {
|
|
60
61
|
if (this._ws) throw new Error('CDPBridge is already connected. Call close() before reconnecting.');
|
|
61
62
|
|
|
62
|
-
const endpoint = process.env.OPENCLI_CDP_ENDPOINT;
|
|
63
|
-
if (!endpoint) throw new Error('
|
|
63
|
+
const endpoint = opts?.cdpEndpoint ?? process.env.OPENCLI_CDP_ENDPOINT;
|
|
64
|
+
if (!endpoint) throw new Error('CDP endpoint not provided (pass cdpEndpoint or set OPENCLI_CDP_ENDPOINT)');
|
|
64
65
|
|
|
65
66
|
let wsUrl = endpoint;
|
|
66
67
|
if (endpoint.startsWith('http')) {
|
|
@@ -326,7 +327,17 @@ class CDPPage implements IPage {
|
|
|
326
327
|
}
|
|
327
328
|
|
|
328
329
|
async getCurrentUrl(): Promise<string | null> {
|
|
329
|
-
return this._lastUrl;
|
|
330
|
+
if (this._lastUrl) return this._lastUrl;
|
|
331
|
+
try {
|
|
332
|
+
const current = await this.evaluate('window.location.href');
|
|
333
|
+
if (typeof current === 'string' && current) {
|
|
334
|
+
this._lastUrl = current;
|
|
335
|
+
return current;
|
|
336
|
+
}
|
|
337
|
+
} catch {
|
|
338
|
+
// Best-effort: direct CDP sessions may not have a ready page yet.
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
330
341
|
}
|
|
331
342
|
|
|
332
343
|
async installInterceptor(pattern: string): Promise<void> {
|
|
@@ -404,19 +415,15 @@ function scoreCDPTarget(target: CDPTarget, preferredPattern?: RegExp): number {
|
|
|
404
415
|
if (url === '' || url === 'about:blank') score -= 40;
|
|
405
416
|
|
|
406
417
|
if (title && title !== 'devtools') score += 25;
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (url.includes('cursor')) score += 100;
|
|
417
|
-
if (url.includes('chatwise')) score += 100;
|
|
418
|
-
if (url.includes('notion')) score += 100;
|
|
419
|
-
if (url.includes('discord')) score += 100;
|
|
418
|
+
|
|
419
|
+
// Boost score for known Electron app names from the registry (builtin + user-defined)
|
|
420
|
+
const appNames = Object.values(getAllElectronApps()).map(a => (a.displayName ?? a.processName).toLowerCase());
|
|
421
|
+
for (const name of appNames) {
|
|
422
|
+
if (title.includes(name)) { score += 120; break; }
|
|
423
|
+
}
|
|
424
|
+
for (const name of appNames) {
|
|
425
|
+
if (url.includes(name)) { score += 100; break; }
|
|
426
|
+
}
|
|
420
427
|
|
|
421
428
|
return score;
|
|
422
429
|
}
|
|
@@ -19,7 +19,7 @@ function generateId(): string {
|
|
|
19
19
|
|
|
20
20
|
export interface DaemonCommand {
|
|
21
21
|
id: string;
|
|
22
|
-
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input';
|
|
22
|
+
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'bind-current';
|
|
23
23
|
tabId?: number;
|
|
24
24
|
code?: string;
|
|
25
25
|
workspace?: string;
|
|
@@ -27,6 +27,8 @@ export interface DaemonCommand {
|
|
|
27
27
|
op?: string;
|
|
28
28
|
index?: number;
|
|
29
29
|
domain?: string;
|
|
30
|
+
matchDomain?: string;
|
|
31
|
+
matchPathPrefix?: string;
|
|
30
32
|
format?: 'png' | 'jpeg';
|
|
31
33
|
quality?: number;
|
|
32
34
|
fullPage?: boolean;
|
|
@@ -145,3 +147,7 @@ export async function listSessions(): Promise<BrowserSessionInfo[]> {
|
|
|
145
147
|
return Array.isArray(result) ? result : [];
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
export async function bindCurrentTab(workspace: string, opts: { matchDomain?: string; matchPathPrefix?: string } = {}): Promise<unknown> {
|
|
151
|
+
return sendCommand('bind-current', { workspace, ...opts });
|
|
152
|
+
}
|
|
153
|
+
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { waitForCaptureJs, waitForSelectorJs } from './dom-helpers.js';
|
|
2
|
+
import { autoScrollJs, waitForCaptureJs, waitForSelectorJs } from './dom-helpers.js';
|
|
3
|
+
|
|
4
|
+
describe('autoScrollJs', () => {
|
|
5
|
+
it('returns early without error when document.body is null', async () => {
|
|
6
|
+
const g = globalThis as any;
|
|
7
|
+
const origDoc = g.document;
|
|
8
|
+
g.document = { body: null, documentElement: {} };
|
|
9
|
+
g.window = g;
|
|
10
|
+
const code = autoScrollJs(3, 500);
|
|
11
|
+
// Should resolve without throwing
|
|
12
|
+
await expect(eval(code)).resolves.not.toThrow();
|
|
13
|
+
g.document = origDoc;
|
|
14
|
+
delete g.window;
|
|
15
|
+
});
|
|
16
|
+
});
|
|
3
17
|
|
|
4
18
|
describe('waitForCaptureJs', () => {
|
|
5
19
|
it('returns a non-empty string', () => {
|
|
@@ -109,6 +109,7 @@ export function scrollJs(direction: string, amount: number): string {
|
|
|
109
109
|
export function autoScrollJs(times: number, delayMs: number): string {
|
|
110
110
|
return `
|
|
111
111
|
(async () => {
|
|
112
|
+
if (!document.body) return;
|
|
112
113
|
for (let i = 0; i < ${times}; i++) {
|
|
113
114
|
const lastHeight = document.body.scrollHeight;
|
|
114
115
|
window.scrollTo(0, lastHeight);
|
package/src/browser/mcp.ts
CHANGED
|
@@ -57,21 +57,30 @@ export class BrowserBridge implements IBrowserFactory {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
private async _ensureDaemon(timeoutSeconds?: number): Promise<void> {
|
|
60
|
-
// Use default if not provided, zero, or negative
|
|
61
60
|
const effectiveSeconds = (timeoutSeconds && timeoutSeconds > 0) ? timeoutSeconds : Math.ceil(DAEMON_SPAWN_TIMEOUT / 1000);
|
|
62
61
|
const timeoutMs = effectiveSeconds * 1000;
|
|
63
62
|
|
|
63
|
+
// Fast path: extension already connected
|
|
64
64
|
if (await isExtensionConnected()) return;
|
|
65
|
+
|
|
66
|
+
// Daemon running but no extension — wait for extension with progress
|
|
65
67
|
if (await isDaemonRunning()) {
|
|
68
|
+
if (process.env.OPENCLI_VERBOSE || process.stderr.isTTY) {
|
|
69
|
+
process.stderr.write('⏳ Waiting for Chrome extension to connect...\n');
|
|
70
|
+
process.stderr.write(' Make sure Chrome is open and the OpenCLI extension is enabled.\n');
|
|
71
|
+
}
|
|
72
|
+
const deadline = Date.now() + timeoutMs;
|
|
73
|
+
while (Date.now() < deadline) {
|
|
74
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
75
|
+
if (await isExtensionConnected()) return;
|
|
76
|
+
}
|
|
66
77
|
throw new Error(
|
|
67
78
|
'Daemon is running but the Browser Extension is not connected.\n' +
|
|
68
79
|
'Please install and enable the opencli Browser Bridge extension in Chrome.',
|
|
69
80
|
);
|
|
70
81
|
}
|
|
71
82
|
|
|
72
|
-
//
|
|
73
|
-
// npx tsx src/main.ts → src/browser/mcp.ts → src/daemon.ts
|
|
74
|
-
// node dist/main.js → dist/browser/mcp.js → dist/daemon.js
|
|
83
|
+
// No daemon — spawn one
|
|
75
84
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
76
85
|
const parentDir = path.resolve(__dirname, '..');
|
|
77
86
|
const daemonTs = path.join(parentDir, 'daemon.ts');
|
|
@@ -79,12 +88,10 @@ export class BrowserBridge implements IBrowserFactory {
|
|
|
79
88
|
const isTs = fs.existsSync(daemonTs);
|
|
80
89
|
const daemonPath = isTs ? daemonTs : daemonJs;
|
|
81
90
|
|
|
82
|
-
if (process.env.OPENCLI_VERBOSE) {
|
|
83
|
-
|
|
91
|
+
if (process.env.OPENCLI_VERBOSE || process.stderr.isTTY) {
|
|
92
|
+
process.stderr.write('⏳ Starting daemon...\n');
|
|
84
93
|
}
|
|
85
94
|
|
|
86
|
-
// For compiled .js, use the current node binary directly (fast).
|
|
87
|
-
// For .ts dev mode, node can't run .ts files — use tsx via --import.
|
|
88
95
|
const spawnArgs = isTs
|
|
89
96
|
? [process.execPath, '--import', 'tsx/esm', daemonPath]
|
|
90
97
|
: [process.execPath, daemonPath];
|
|
@@ -96,15 +103,13 @@ export class BrowserBridge implements IBrowserFactory {
|
|
|
96
103
|
});
|
|
97
104
|
this._daemonProc.unref();
|
|
98
105
|
|
|
99
|
-
// Wait for daemon
|
|
100
|
-
const backoffs = [50, 100, 200, 400, 800, 1500, 3000];
|
|
106
|
+
// Wait for daemon + extension with faster polling
|
|
101
107
|
const deadline = Date.now() + timeoutMs;
|
|
102
|
-
|
|
103
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
108
|
+
while (Date.now() < deadline) {
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
104
110
|
if (await isExtensionConnected()) return;
|
|
105
111
|
}
|
|
106
112
|
|
|
107
|
-
// Daemon might be up but extension not connected — give a useful error
|
|
108
113
|
if (await isDaemonRunning()) {
|
|
109
114
|
throw new Error(
|
|
110
115
|
'Daemon is running but the Browser Extension is not connected.\n' +
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const { sendCommandMock } = vi.hoisted(() => ({
|
|
4
|
+
sendCommandMock: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock('./daemon-client.js', () => ({
|
|
8
|
+
sendCommand: sendCommandMock,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import { Page } from './page.js';
|
|
12
|
+
|
|
13
|
+
describe('Page.getCurrentUrl', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
sendCommandMock.mockReset();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('reads the real browser URL when no local navigation cache exists', async () => {
|
|
19
|
+
sendCommandMock.mockResolvedValueOnce('https://notebooklm.google.com/notebook/nb-live');
|
|
20
|
+
|
|
21
|
+
const page = new Page('site:notebooklm');
|
|
22
|
+
const url = await page.getCurrentUrl();
|
|
23
|
+
|
|
24
|
+
expect(url).toBe('https://notebooklm.google.com/notebook/nb-live');
|
|
25
|
+
expect(sendCommandMock).toHaveBeenCalledTimes(1);
|
|
26
|
+
expect(sendCommandMock).toHaveBeenCalledWith('exec', expect.objectContaining({
|
|
27
|
+
workspace: 'site:notebooklm',
|
|
28
|
+
}));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('caches the discovered browser URL for later reads', async () => {
|
|
32
|
+
sendCommandMock.mockResolvedValueOnce('https://notebooklm.google.com/notebook/nb-live');
|
|
33
|
+
|
|
34
|
+
const page = new Page('site:notebooklm');
|
|
35
|
+
expect(await page.getCurrentUrl()).toBe('https://notebooklm.google.com/notebook/nb-live');
|
|
36
|
+
expect(await page.getCurrentUrl()).toBe('https://notebooklm.google.com/notebook/nb-live');
|
|
37
|
+
|
|
38
|
+
expect(sendCommandMock).toHaveBeenCalledTimes(1);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('Page.evaluate', () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
sendCommandMock.mockReset();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('retries once when the inspected target navigated during exec', async () => {
|
|
48
|
+
sendCommandMock
|
|
49
|
+
.mockRejectedValueOnce(new Error('{"code":-32000,"message":"Inspected target navigated or closed"}'))
|
|
50
|
+
.mockResolvedValueOnce(42);
|
|
51
|
+
|
|
52
|
+
const page = new Page('site:notebooklm');
|
|
53
|
+
const value = await page.evaluate('21 + 21');
|
|
54
|
+
|
|
55
|
+
expect(value).toBe(42);
|
|
56
|
+
expect(sendCommandMock).toHaveBeenCalledTimes(2);
|
|
57
|
+
});
|
|
58
|
+
});
|
package/src/browser/page.ts
CHANGED
|
@@ -108,7 +108,17 @@ export class Page implements IPage {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
async getCurrentUrl(): Promise<string | null> {
|
|
111
|
-
return this._lastUrl;
|
|
111
|
+
if (this._lastUrl) return this._lastUrl;
|
|
112
|
+
try {
|
|
113
|
+
const current = await this.evaluate('window.location.href');
|
|
114
|
+
if (typeof current === 'string' && current) {
|
|
115
|
+
this._lastUrl = current;
|
|
116
|
+
return current;
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// Best-effort: some commands may run before a debuggable tab is ready.
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
112
122
|
}
|
|
113
123
|
|
|
114
124
|
/** Close the automation window in the extension */
|
|
@@ -122,7 +132,13 @@ export class Page implements IPage {
|
|
|
122
132
|
|
|
123
133
|
async evaluate(js: string): Promise<unknown> {
|
|
124
134
|
const code = wrapForEval(js);
|
|
125
|
-
|
|
135
|
+
try {
|
|
136
|
+
return await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
137
|
+
} catch (err) {
|
|
138
|
+
if (!isRetryableSettleError(err)) throw err;
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
140
|
+
return sendCommand('exec', { code, ...this._cmdOpts() });
|
|
141
|
+
}
|
|
126
142
|
}
|
|
127
143
|
|
|
128
144
|
async getCookies(opts: { domain?: string; url?: string } = {}): Promise<BrowserCookie[]> {
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generateStealthJs } from './stealth.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tests for the stealth anti-detection module.
|
|
6
|
+
*
|
|
7
|
+
* We test the generated JS string for expected content and structure.
|
|
8
|
+
* Evaluating in Node is fragile because stealth patches target browser
|
|
9
|
+
* globals (navigator, Performance, HTMLIFrameElement) that don't exist
|
|
10
|
+
* or behave differently in Node. Instead we verify the code string
|
|
11
|
+
* contains the right patches and is syntactically valid.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
describe('generateStealthJs', () => {
|
|
15
|
+
it('returns a non-empty string', () => {
|
|
16
|
+
const code = generateStealthJs();
|
|
17
|
+
expect(typeof code).toBe('string');
|
|
18
|
+
expect(code.length).toBeGreaterThan(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('is a valid self-contained IIFE', () => {
|
|
22
|
+
const code = generateStealthJs();
|
|
23
|
+
// Should start/end as an IIFE
|
|
24
|
+
expect(code.trim()).toMatch(/^\(\(\) => \{/);
|
|
25
|
+
expect(code.trim()).toMatch(/\}\)\(\)$/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('patches navigator.webdriver', () => {
|
|
29
|
+
const code = generateStealthJs();
|
|
30
|
+
expect(code).toContain("navigator, 'webdriver'");
|
|
31
|
+
expect(code).toContain('() => false');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('stubs window.chrome', () => {
|
|
35
|
+
const code = generateStealthJs();
|
|
36
|
+
expect(code).toContain('window.chrome');
|
|
37
|
+
expect(code).toContain('runtime');
|
|
38
|
+
expect(code).toContain('loadTimes');
|
|
39
|
+
expect(code).toContain('csi');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('fakes navigator.plugins if empty', () => {
|
|
43
|
+
const code = generateStealthJs();
|
|
44
|
+
expect(code).toContain('navigator.plugins');
|
|
45
|
+
expect(code).toContain('PDF Viewer');
|
|
46
|
+
expect(code).toContain('Chrome PDF Viewer');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('ensures navigator.languages is non-empty', () => {
|
|
50
|
+
const code = generateStealthJs();
|
|
51
|
+
expect(code).toContain('navigator.languages');
|
|
52
|
+
expect(code).toContain("'en-US'");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('normalizes Permissions.query for notifications', () => {
|
|
56
|
+
const code = generateStealthJs();
|
|
57
|
+
expect(code).toContain('Permissions');
|
|
58
|
+
expect(code).toContain('notifications');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('cleans automation artifacts', () => {
|
|
62
|
+
const code = generateStealthJs();
|
|
63
|
+
expect(code).toContain('__playwright');
|
|
64
|
+
expect(code).toContain('__puppeteer');
|
|
65
|
+
expect(code).toContain("'cdc_'");
|
|
66
|
+
expect(code).toContain("'__cdc_'");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('filters CDP patterns from Error.stack', () => {
|
|
70
|
+
const code = generateStealthJs();
|
|
71
|
+
expect(code).toContain('puppeteer_evaluation_script');
|
|
72
|
+
expect(code).toContain("'pptr:'");
|
|
73
|
+
expect(code).toContain("'debugger://'");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('neutralizes debugger statement traps', () => {
|
|
77
|
+
const code = generateStealthJs();
|
|
78
|
+
// Should patch Function constructor with new.target / Reflect.construct
|
|
79
|
+
expect(code).toContain('_OrigFunction');
|
|
80
|
+
expect(code).toContain('_PatchedFunction');
|
|
81
|
+
expect(code).toContain('new.target');
|
|
82
|
+
expect(code).toContain('Reflect.construct');
|
|
83
|
+
// Should patch eval
|
|
84
|
+
expect(code).toContain('_origEval');
|
|
85
|
+
expect(code).toContain('_patchedEval');
|
|
86
|
+
// Regex to strip debugger (lookbehind for statement boundaries)
|
|
87
|
+
expect(code).toContain('_debuggerRe');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('uses shared toString disguise via WeakMap', () => {
|
|
91
|
+
const code = generateStealthJs();
|
|
92
|
+
// Shared infrastructure at the top of the IIFE
|
|
93
|
+
expect(code).toContain('_origToString');
|
|
94
|
+
expect(code).toContain('WeakMap');
|
|
95
|
+
expect(code).toContain('_disguised');
|
|
96
|
+
expect(code).toContain('_disguise');
|
|
97
|
+
// Should NOT have per-instance toString overrides on Function/eval
|
|
98
|
+
// (they go through _disguise instead)
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('defends console method fingerprinting', () => {
|
|
102
|
+
const code = generateStealthJs();
|
|
103
|
+
expect(code).toContain('_consoleMethods');
|
|
104
|
+
expect(code).toContain("'log'");
|
|
105
|
+
expect(code).toContain("'warn'");
|
|
106
|
+
expect(code).toContain("'error'");
|
|
107
|
+
expect(code).toContain('[native code]');
|
|
108
|
+
// Uses saved _origToString reference
|
|
109
|
+
expect(code).toContain('_origToString.call');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('defends window dimension detection', () => {
|
|
113
|
+
const code = generateStealthJs();
|
|
114
|
+
expect(code).toContain('outerWidth');
|
|
115
|
+
expect(code).toContain('outerHeight');
|
|
116
|
+
expect(code).toContain('innerWidth');
|
|
117
|
+
expect(code).toContain('innerHeight');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('filters Performance API entries', () => {
|
|
121
|
+
const code = generateStealthJs();
|
|
122
|
+
expect(code).toContain('getEntries');
|
|
123
|
+
expect(code).toContain('getEntriesByType');
|
|
124
|
+
expect(code).toContain('getEntriesByName');
|
|
125
|
+
expect(code).toContain('_suspiciousPatterns');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('cleans document $cdc_ properties', () => {
|
|
129
|
+
const code = generateStealthJs();
|
|
130
|
+
expect(code).toContain("'$cdc_'");
|
|
131
|
+
expect(code).toContain("'$chrome_'");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('patches iframe contentWindow.chrome consistency', () => {
|
|
135
|
+
const code = generateStealthJs();
|
|
136
|
+
expect(code).toContain('contentWindow');
|
|
137
|
+
expect(code).toContain('HTMLIFrameElement');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('uses non-enumerable guard flag on EventTarget.prototype', () => {
|
|
141
|
+
const code = generateStealthJs();
|
|
142
|
+
expect(code).toContain('EventTarget.prototype');
|
|
143
|
+
expect(code).toContain("'__lsn'");
|
|
144
|
+
expect(code).toContain('enumerable: false');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('generates syntactically valid JavaScript', () => {
|
|
148
|
+
const code = generateStealthJs();
|
|
149
|
+
// new Function() parses the code without executing it in a real
|
|
150
|
+
// browser context, catching syntax errors from template literal issues.
|
|
151
|
+
expect(() => new Function(code)).not.toThrow();
|
|
152
|
+
});
|
|
153
|
+
});
|