@jackwener/opencli 1.5.5 → 1.5.7
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 +26 -0
- package/README.md +31 -4
- package/README.zh-CN.md +40 -5
- package/SKILL.md +1 -1
- package/dist/browser/cdp.d.ts +1 -0
- package/dist/browser/cdp.js +30 -27
- package/dist/browser/daemon-client.d.ts +11 -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.d.ts +6 -0
- package/dist/browser/page.js +37 -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 +1821 -252
- 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/band/bands.d.ts +1 -0
- package/dist/clis/band/bands.js +72 -0
- package/dist/clis/band/mentions.d.ts +1 -0
- package/dist/clis/band/mentions.js +127 -0
- package/dist/clis/band/post.d.ts +1 -0
- package/dist/clis/band/post.js +175 -0
- package/dist/clis/band/posts.d.ts +1 -0
- package/dist/clis/band/posts.js +94 -0
- 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/doubao/detail.d.ts +1 -0
- package/dist/clis/doubao/detail.js +33 -0
- package/dist/clis/doubao/detail.test.d.ts +1 -0
- package/dist/clis/doubao/detail.test.js +42 -0
- package/dist/clis/doubao/history.d.ts +1 -0
- package/dist/clis/doubao/history.js +28 -0
- package/dist/clis/doubao/history.test.d.ts +1 -0
- package/dist/clis/doubao/history.test.js +37 -0
- package/dist/clis/doubao/meeting-summary.d.ts +1 -0
- package/dist/clis/doubao/meeting-summary.js +39 -0
- package/dist/clis/doubao/meeting-transcript.d.ts +1 -0
- package/dist/clis/doubao/meeting-transcript.js +36 -0
- package/dist/clis/doubao/utils.d.ts +27 -0
- package/dist/clis/doubao/utils.js +317 -0
- package/dist/clis/doubao/utils.test.d.ts +1 -0
- package/dist/clis/doubao/utils.test.js +24 -0
- package/dist/clis/douyin/_shared/public-api.d.ts +33 -0
- package/dist/clis/douyin/_shared/public-api.js +29 -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/user-videos.d.ts +5 -0
- package/dist/clis/douyin/user-videos.js +74 -0
- package/dist/clis/douyin/user-videos.test.d.ts +1 -0
- package/dist/clis/douyin/user-videos.test.js +108 -0
- 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/ones/common.d.ts +32 -0
- package/dist/clis/ones/common.js +144 -0
- package/dist/clis/ones/enrich-tasks.d.ts +5 -0
- package/dist/clis/ones/enrich-tasks.js +37 -0
- package/dist/clis/ones/login.d.ts +1 -0
- package/dist/clis/ones/login.js +80 -0
- package/dist/clis/ones/logout.d.ts +1 -0
- package/dist/clis/ones/logout.js +17 -0
- package/dist/clis/ones/me.d.ts +1 -0
- package/dist/clis/ones/me.js +30 -0
- package/dist/clis/ones/my-tasks.d.ts +1 -0
- package/dist/clis/ones/my-tasks.js +120 -0
- package/dist/clis/ones/resolve-labels.d.ts +10 -0
- package/dist/clis/ones/resolve-labels.js +64 -0
- package/dist/clis/ones/task-helpers.d.ts +29 -0
- package/dist/clis/ones/task-helpers.js +212 -0
- package/dist/clis/ones/task-helpers.test.d.ts +1 -0
- package/dist/clis/ones/task-helpers.test.js +12 -0
- package/dist/clis/ones/task.d.ts +1 -0
- package/dist/clis/ones/task.js +66 -0
- package/dist/clis/ones/tasks.d.ts +1 -0
- package/dist/clis/ones/tasks.js +79 -0
- package/dist/clis/ones/token-info.d.ts +1 -0
- package/dist/clis/ones/token-info.js +42 -0
- package/dist/clis/ones/worklog.d.ts +11 -0
- package/dist/clis/ones/worklog.js +267 -0
- package/dist/clis/ones/worklog.test.d.ts +1 -0
- package/dist/clis/ones/worklog.test.js +20 -0
- package/dist/clis/spotify/spotify.d.ts +1 -0
- package/dist/clis/spotify/spotify.js +316 -0
- package/dist/clis/spotify/utils.d.ts +21 -0
- package/dist/clis/spotify/utils.js +66 -0
- package/dist/clis/spotify/utils.test.d.ts +1 -0
- package/dist/clis/spotify/utils.test.js +67 -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/tieba/commands.test.d.ts +4 -0
- package/dist/clis/tieba/commands.test.js +79 -0
- package/dist/clis/tieba/hot.d.ts +1 -0
- package/dist/clis/tieba/hot.js +48 -0
- package/dist/clis/tieba/posts.d.ts +1 -0
- package/dist/clis/tieba/posts.js +85 -0
- package/dist/clis/tieba/read.d.ts +1 -0
- package/dist/clis/tieba/read.js +140 -0
- package/dist/clis/tieba/search.d.ts +1 -0
- package/dist/clis/tieba/search.js +108 -0
- package/dist/clis/tieba/utils.d.ts +101 -0
- package/dist/clis/tieba/utils.js +240 -0
- package/dist/clis/tieba/utils.test.d.ts +1 -0
- package/dist/clis/tieba/utils.test.js +290 -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 +206 -13
- package/dist/clis/weread/commands.test.js +331 -0
- package/dist/clis/weread/private-api-regression.test.d.ts +1 -0
- package/dist/{weread-private-api-regression.test.js → clis/weread/private-api-regression.test.js} +92 -30
- package/dist/clis/weread/search-regression.test.d.ts +1 -0
- package/dist/clis/weread/search-regression.test.js +407 -0
- package/dist/clis/weread/search.js +143 -7
- package/dist/clis/weread/shelf.js +13 -95
- package/dist/clis/weread/utils.d.ts +56 -0
- package/dist/clis/weread/utils.js +234 -7
- package/dist/clis/weread/utils.test.js +71 -1
- 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/publish.d.ts +1 -1
- package/dist/clis/xiaohongshu/publish.js +78 -31
- package/dist/clis/xiaohongshu/publish.test.js +66 -1
- package/dist/clis/xiaohongshu/search.js +9 -0
- package/dist/clis/xiaohongshu/search.test.js +10 -4
- package/dist/clis/xiaohongshu/user-helpers.d.ts +1 -0
- package/dist/clis/xiaohongshu/user-helpers.js +2 -0
- package/dist/clis/xiaohongshu/user-helpers.test.js +18 -0
- package/dist/clis/xueqiu/comments.d.ts +118 -0
- package/dist/clis/xueqiu/comments.js +354 -0
- package/dist/clis/xueqiu/comments.test.d.ts +1 -0
- package/dist/clis/xueqiu/comments.test.js +696 -0
- package/dist/clis/youtube/search.js +57 -17
- package/dist/clis/youtube/transcript.js +2 -4
- package/dist/clis/youtube/utils.d.ts +9 -0
- package/dist/clis/youtube/utils.js +67 -3
- package/dist/clis/youtube/utils.test.d.ts +1 -0
- package/dist/clis/youtube/utils.test.js +37 -0
- package/dist/clis/youtube/video.js +16 -15
- 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/clis/zsxq/dynamics.d.ts +1 -0
- package/dist/clis/zsxq/dynamics.js +47 -0
- package/dist/clis/zsxq/groups.d.ts +1 -0
- package/dist/clis/zsxq/groups.js +32 -0
- package/dist/clis/zsxq/search.d.ts +1 -0
- package/dist/clis/zsxq/search.js +43 -0
- package/dist/clis/zsxq/search.test.d.ts +1 -0
- package/dist/clis/zsxq/search.test.js +24 -0
- package/dist/clis/zsxq/topic.d.ts +1 -0
- package/dist/clis/zsxq/topic.js +47 -0
- package/dist/clis/zsxq/topic.test.d.ts +1 -0
- package/dist/clis/zsxq/topic.test.js +29 -0
- package/dist/clis/zsxq/topics.d.ts +1 -0
- package/dist/clis/zsxq/topics.js +25 -0
- package/dist/clis/zsxq/topics.test.d.ts +1 -0
- package/dist/clis/zsxq/topics.test.js +24 -0
- package/dist/clis/zsxq/utils.d.ts +97 -0
- package/dist/clis/zsxq/utils.js +230 -0
- package/dist/commanderAdapter.js +10 -1
- package/dist/commanderAdapter.test.js +64 -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/external-clis.yaml +17 -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/types.d.ts +5 -0
- package/dist/weixin-download.test.js +14 -0
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/band.md +63 -0
- package/docs/adapters/browser/notebooklm.md +69 -0
- package/docs/adapters/browser/ones.md +59 -0
- package/docs/adapters/browser/spotify.md +62 -0
- package/docs/adapters/browser/tieba.md +45 -0
- package/docs/adapters/browser/xiaohongshu.md +19 -10
- package/docs/adapters/browser/xueqiu.md +5 -0
- package/docs/adapters/browser/zsxq.md +49 -0
- package/docs/adapters/index.md +67 -63
- package/docs/adapters-doc/ones.md +32 -0
- 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 +794 -513
- package/extension/src/background.test.ts +202 -2
- package/extension/src/background.ts +189 -10
- package/extension/src/cdp.ts +54 -0
- package/extension/src/protocol.ts +11 -5
- package/package.json +1 -1
- package/scripts/postinstall.js +16 -0
- package/src/browser/cdp.ts +24 -17
- package/src/browser/daemon-client.ts +11 -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 +34 -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/band/bands.ts +76 -0
- package/src/clis/band/mentions.ts +134 -0
- package/src/clis/band/post.ts +187 -0
- package/src/clis/band/posts.ts +106 -0
- 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/doubao/detail.test.ts +53 -0
- package/src/clis/doubao/detail.ts +41 -0
- package/src/clis/doubao/history.test.ts +45 -0
- package/src/clis/doubao/history.ts +32 -0
- package/src/clis/doubao/meeting-summary.ts +53 -0
- package/src/clis/doubao/meeting-transcript.ts +48 -0
- package/src/clis/doubao/utils.test.ts +45 -0
- package/src/clis/doubao/utils.ts +371 -0
- package/src/clis/douyin/_shared/public-api.ts +84 -0
- 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/user-videos.test.ts +122 -0
- package/src/clis/douyin/user-videos.ts +101 -0
- 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/ones/common.ts +187 -0
- package/src/clis/ones/enrich-tasks.ts +47 -0
- package/src/clis/ones/login.ts +103 -0
- package/src/clis/ones/logout.ts +19 -0
- package/src/clis/ones/me.ts +34 -0
- package/src/clis/ones/my-tasks.ts +148 -0
- package/src/clis/ones/resolve-labels.ts +80 -0
- package/src/clis/ones/task-helpers.test.ts +14 -0
- package/src/clis/ones/task-helpers.ts +214 -0
- package/src/clis/ones/task.ts +79 -0
- package/src/clis/ones/tasks.ts +92 -0
- package/src/clis/ones/token-info.ts +46 -0
- package/src/clis/ones/worklog.test.ts +24 -0
- package/src/clis/ones/worklog.ts +306 -0
- package/src/clis/spotify/spotify.ts +328 -0
- package/src/clis/spotify/utils.test.ts +87 -0
- package/src/clis/spotify/utils.ts +92 -0
- package/src/clis/substack/utils.test.ts +54 -0
- package/src/clis/substack/utils.ts +10 -2
- package/src/clis/tieba/commands.test.ts +86 -0
- package/src/clis/tieba/hot.ts +52 -0
- package/src/clis/tieba/posts.ts +108 -0
- package/src/clis/tieba/read.ts +158 -0
- package/src/clis/tieba/search.ts +119 -0
- package/src/clis/tieba/utils.test.ts +322 -0
- package/src/clis/tieba/utils.ts +348 -0
- 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 +256 -13
- package/src/clis/weread/commands.test.ts +409 -0
- package/src/{weread-private-api-regression.test.ts → clis/weread/private-api-regression.test.ts} +108 -30
- package/src/clis/weread/search-regression.test.ts +440 -0
- package/src/clis/weread/search.ts +189 -9
- package/src/clis/weread/shelf.ts +20 -122
- package/src/clis/weread/utils.test.ts +81 -1
- package/src/clis/weread/utils.ts +293 -7
- 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/publish.test.ts +79 -1
- package/src/clis/xiaohongshu/publish.ts +84 -30
- package/src/clis/xiaohongshu/search.test.ts +11 -4
- package/src/clis/xiaohongshu/search.ts +13 -0
- package/src/clis/xiaohongshu/user-helpers.test.ts +23 -0
- package/src/clis/xiaohongshu/user-helpers.ts +4 -0
- package/src/clis/xueqiu/comments.test.ts +823 -0
- package/src/clis/xueqiu/comments.ts +461 -0
- package/src/clis/youtube/search.ts +57 -17
- package/src/clis/youtube/transcript.ts +2 -4
- package/src/clis/youtube/utils.test.ts +43 -0
- package/src/clis/youtube/utils.ts +69 -0
- package/src/clis/youtube/video.ts +16 -15
- package/src/clis/zhihu/question.test.ts +71 -0
- package/src/clis/zhihu/question.ts +27 -15
- package/src/clis/zsxq/dynamics.ts +60 -0
- package/src/clis/zsxq/groups.ts +41 -0
- package/src/clis/zsxq/search.test.ts +29 -0
- package/src/clis/zsxq/search.ts +54 -0
- package/src/clis/zsxq/topic.test.ts +34 -0
- package/src/clis/zsxq/topic.ts +68 -0
- package/src/clis/zsxq/topics.test.ts +29 -0
- package/src/clis/zsxq/topics.ts +36 -0
- package/src/clis/zsxq/utils.ts +351 -0
- package/src/commanderAdapter.test.ts +77 -0
- package/src/commanderAdapter.ts +8 -1
- 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/external-clis.yaml +17 -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/types.ts +5 -0
- package/src/weixin-download.test.ts +27 -0
- package/tests/e2e/band-auth.test.ts +20 -0
- package/tests/e2e/browser-auth-helpers.ts +18 -0
- package/tests/e2e/browser-auth.test.ts +35 -47
- package/tests/e2e/browser-public-extended.test.ts +6 -2
- package/tests/e2e/browser-public.test.ts +288 -0
- package/tests/e2e/management.test.ts +1 -1
- package/tests/e2e/plugin-management.test.ts +1 -1
- package/vitest.config.ts +1 -0
- package/chatwise-opencli.ps1 +0 -82
- package/dist/clis/chatwise/shared.d.ts +0 -2
- package/dist/clis/chatwise/shared.js +0 -6
- package/dist/weread-private-api-regression.test.d.ts +0 -1
- package/dist/weread-search-regression.test.d.ts +0 -1
- package/dist/weread-search-regression.test.js +0 -39
- package/src/clis/chatwise/shared.ts +0 -8
- package/src/weread-search-regression.test.ts +0 -44
|
@@ -1,580 +1,861 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
//#region src/protocol.ts
|
|
2
|
+
/** Default daemon port */
|
|
3
|
+
var DAEMON_PORT = 19825;
|
|
4
|
+
var DAEMON_HOST = "localhost";
|
|
5
|
+
var DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`;
|
|
6
|
+
/** Lightweight health-check endpoint — probed before each WebSocket attempt. */
|
|
7
|
+
var DAEMON_PING_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}/ping`;
|
|
8
|
+
/** Base reconnect delay for extension WebSocket (ms) */
|
|
9
|
+
var WS_RECONNECT_BASE_DELAY = 2e3;
|
|
10
|
+
/** Max reconnect delay (ms) */
|
|
11
|
+
var WS_RECONNECT_MAX_DELAY = 6e4;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/cdp.ts
|
|
14
|
+
/**
|
|
15
|
+
* CDP execution via chrome.debugger API.
|
|
16
|
+
*
|
|
17
|
+
* chrome.debugger only needs the "debugger" permission — no host_permissions.
|
|
18
|
+
* It can attach to any http/https tab. Avoid chrome:// and chrome-extension://
|
|
19
|
+
* tabs (resolveTabId in background.ts filters them).
|
|
20
|
+
*/
|
|
21
|
+
var attached = /* @__PURE__ */ new Set();
|
|
22
|
+
/** Internal blank page used when no user URL is provided. */
|
|
23
|
+
var BLANK_PAGE$1 = "data:text/html,<html></html>";
|
|
24
|
+
/** Check if a URL can be attached via CDP — only allow http(s) and our internal blank page. */
|
|
10
25
|
function isDebuggableUrl$1(url) {
|
|
11
|
-
|
|
12
|
-
|
|
26
|
+
if (!url) return true;
|
|
27
|
+
return url.startsWith("http://") || url.startsWith("https://") || url === BLANK_PAGE$1;
|
|
13
28
|
}
|
|
14
29
|
async function ensureAttached(tabId) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
attached.add(tabId);
|
|
57
|
-
try {
|
|
58
|
-
await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
|
|
59
|
-
} catch {
|
|
60
|
-
}
|
|
30
|
+
try {
|
|
31
|
+
const tab = await chrome.tabs.get(tabId);
|
|
32
|
+
if (!isDebuggableUrl$1(tab.url)) {
|
|
33
|
+
attached.delete(tabId);
|
|
34
|
+
throw new Error(`Cannot debug tab ${tabId}: URL is ${tab.url ?? "unknown"}`);
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (e instanceof Error && e.message.startsWith("Cannot debug tab")) throw e;
|
|
38
|
+
attached.delete(tabId);
|
|
39
|
+
throw new Error(`Tab ${tabId} no longer exists`);
|
|
40
|
+
}
|
|
41
|
+
if (attached.has(tabId)) try {
|
|
42
|
+
await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
|
|
43
|
+
expression: "1",
|
|
44
|
+
returnByValue: true
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
} catch {
|
|
48
|
+
attached.delete(tabId);
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
await chrome.debugger.attach({ tabId }, "1.3");
|
|
52
|
+
} catch (e) {
|
|
53
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
54
|
+
const hint = msg.includes("chrome-extension://") ? ". Tip: another Chrome extension may be interfering — try disabling other extensions" : "";
|
|
55
|
+
if (msg.includes("Another debugger is already attached")) {
|
|
56
|
+
try {
|
|
57
|
+
await chrome.debugger.detach({ tabId });
|
|
58
|
+
} catch {}
|
|
59
|
+
try {
|
|
60
|
+
await chrome.debugger.attach({ tabId }, "1.3");
|
|
61
|
+
} catch {
|
|
62
|
+
throw new Error(`attach failed: ${msg}${hint}`);
|
|
63
|
+
}
|
|
64
|
+
} else throw new Error(`attach failed: ${msg}${hint}`);
|
|
65
|
+
}
|
|
66
|
+
attached.add(tabId);
|
|
67
|
+
try {
|
|
68
|
+
await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
|
|
69
|
+
} catch {}
|
|
61
70
|
}
|
|
62
71
|
async function evaluate(tabId, expression) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
72
|
+
await ensureAttached(tabId);
|
|
73
|
+
const result = await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
|
|
74
|
+
expression,
|
|
75
|
+
returnByValue: true,
|
|
76
|
+
awaitPromise: true
|
|
77
|
+
});
|
|
78
|
+
if (result.exceptionDetails) {
|
|
79
|
+
const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error";
|
|
80
|
+
throw new Error(errMsg);
|
|
81
|
+
}
|
|
82
|
+
return result.result?.value;
|
|
83
|
+
}
|
|
84
|
+
var evaluateAsync = evaluate;
|
|
85
|
+
/**
|
|
86
|
+
* Capture a screenshot via CDP Page.captureScreenshot.
|
|
87
|
+
* Returns base64-encoded image data.
|
|
88
|
+
*/
|
|
76
89
|
async function screenshot(tabId, options = {}) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
90
|
+
await ensureAttached(tabId);
|
|
91
|
+
const format = options.format ?? "png";
|
|
92
|
+
if (options.fullPage) {
|
|
93
|
+
const metrics = await chrome.debugger.sendCommand({ tabId }, "Page.getLayoutMetrics");
|
|
94
|
+
const size = metrics.cssContentSize || metrics.contentSize;
|
|
95
|
+
if (size) await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", {
|
|
96
|
+
mobile: false,
|
|
97
|
+
width: Math.ceil(size.width),
|
|
98
|
+
height: Math.ceil(size.height),
|
|
99
|
+
deviceScaleFactor: 1
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const params = { format };
|
|
104
|
+
if (format === "jpeg" && options.quality !== void 0) params.quality = Math.max(0, Math.min(100, options.quality));
|
|
105
|
+
return (await chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", params)).data;
|
|
106
|
+
} finally {
|
|
107
|
+
if (options.fullPage) await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride").catch(() => {});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Set local file paths on a file input element via CDP DOM.setFileInputFiles.
|
|
112
|
+
* This bypasses the need to send large base64 payloads through the message channel —
|
|
113
|
+
* Chrome reads the files directly from the local filesystem.
|
|
114
|
+
*
|
|
115
|
+
* @param tabId - Target tab ID
|
|
116
|
+
* @param files - Array of absolute local file paths
|
|
117
|
+
* @param selector - CSS selector to find the file input (optional, defaults to first file input)
|
|
118
|
+
*/
|
|
119
|
+
async function setFileInputFiles(tabId, files, selector) {
|
|
120
|
+
await ensureAttached(tabId);
|
|
121
|
+
await chrome.debugger.sendCommand({ tabId }, "DOM.enable");
|
|
122
|
+
const doc = await chrome.debugger.sendCommand({ tabId }, "DOM.getDocument");
|
|
123
|
+
const query = selector || "input[type=\"file\"]";
|
|
124
|
+
const result = await chrome.debugger.sendCommand({ tabId }, "DOM.querySelector", {
|
|
125
|
+
nodeId: doc.root.nodeId,
|
|
126
|
+
selector: query
|
|
127
|
+
});
|
|
128
|
+
if (!result.nodeId) throw new Error(`No element found matching selector: ${query}`);
|
|
129
|
+
await chrome.debugger.sendCommand({ tabId }, "DOM.setFileInputFiles", {
|
|
130
|
+
files,
|
|
131
|
+
nodeId: result.nodeId
|
|
132
|
+
});
|
|
104
133
|
}
|
|
105
134
|
async function detach(tabId) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
135
|
+
if (!attached.has(tabId)) return;
|
|
136
|
+
attached.delete(tabId);
|
|
137
|
+
try {
|
|
138
|
+
await chrome.debugger.detach({ tabId });
|
|
139
|
+
} catch {}
|
|
112
140
|
}
|
|
113
141
|
function registerListeners() {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const _origError = console.error.bind(console);
|
|
142
|
+
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
143
|
+
attached.delete(tabId);
|
|
144
|
+
});
|
|
145
|
+
chrome.debugger.onDetach.addListener((source) => {
|
|
146
|
+
if (source.tabId) attached.delete(source.tabId);
|
|
147
|
+
});
|
|
148
|
+
chrome.tabs.onUpdated.addListener(async (tabId, info) => {
|
|
149
|
+
if (info.url && !isDebuggableUrl$1(info.url)) await detach(tabId);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/background.ts
|
|
154
|
+
var ws = null;
|
|
155
|
+
var reconnectTimer = null;
|
|
156
|
+
var reconnectAttempts = 0;
|
|
157
|
+
var _origLog = console.log.bind(console);
|
|
158
|
+
var _origWarn = console.warn.bind(console);
|
|
159
|
+
var _origError = console.error.bind(console);
|
|
133
160
|
function forwardLog(level, args) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
161
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
162
|
+
try {
|
|
163
|
+
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
164
|
+
ws.send(JSON.stringify({
|
|
165
|
+
type: "log",
|
|
166
|
+
level,
|
|
167
|
+
msg,
|
|
168
|
+
ts: Date.now()
|
|
169
|
+
}));
|
|
170
|
+
} catch {}
|
|
140
171
|
}
|
|
141
172
|
console.log = (...args) => {
|
|
142
|
-
|
|
143
|
-
|
|
173
|
+
_origLog(...args);
|
|
174
|
+
forwardLog("info", args);
|
|
144
175
|
};
|
|
145
176
|
console.warn = (...args) => {
|
|
146
|
-
|
|
147
|
-
|
|
177
|
+
_origWarn(...args);
|
|
178
|
+
forwardLog("warn", args);
|
|
148
179
|
};
|
|
149
180
|
console.error = (...args) => {
|
|
150
|
-
|
|
151
|
-
|
|
181
|
+
_origError(...args);
|
|
182
|
+
forwardLog("error", args);
|
|
152
183
|
};
|
|
184
|
+
/**
|
|
185
|
+
* Probe the daemon via its /ping HTTP endpoint before attempting a WebSocket
|
|
186
|
+
* connection. fetch() failures are silently catchable; new WebSocket() is not
|
|
187
|
+
* — Chrome logs ERR_CONNECTION_REFUSED to the extension error page before any
|
|
188
|
+
* JS handler can intercept it. By keeping the probe inside connect() every
|
|
189
|
+
* call site remains unchanged and the guard can never be accidentally skipped.
|
|
190
|
+
*/
|
|
153
191
|
async function connect() {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
192
|
+
if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) return;
|
|
193
|
+
try {
|
|
194
|
+
if (!(await fetch(DAEMON_PING_URL, { signal: AbortSignal.timeout(1e3) })).ok) return;
|
|
195
|
+
} catch {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
ws = new WebSocket(DAEMON_WS_URL);
|
|
200
|
+
} catch {
|
|
201
|
+
scheduleReconnect();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
ws.onopen = () => {
|
|
205
|
+
console.log("[opencli] Connected to daemon");
|
|
206
|
+
reconnectAttempts = 0;
|
|
207
|
+
if (reconnectTimer) {
|
|
208
|
+
clearTimeout(reconnectTimer);
|
|
209
|
+
reconnectTimer = null;
|
|
210
|
+
}
|
|
211
|
+
ws?.send(JSON.stringify({
|
|
212
|
+
type: "hello",
|
|
213
|
+
version: chrome.runtime.getManifest().version
|
|
214
|
+
}));
|
|
215
|
+
};
|
|
216
|
+
ws.onmessage = async (event) => {
|
|
217
|
+
try {
|
|
218
|
+
const result = await handleCommand(JSON.parse(event.data));
|
|
219
|
+
ws?.send(JSON.stringify(result));
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.error("[opencli] Message handling error:", err);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
ws.onclose = () => {
|
|
225
|
+
console.log("[opencli] Disconnected from daemon");
|
|
226
|
+
ws = null;
|
|
227
|
+
scheduleReconnect();
|
|
228
|
+
};
|
|
229
|
+
ws.onerror = () => {
|
|
230
|
+
ws?.close();
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* After MAX_EAGER_ATTEMPTS (reaching 60s backoff), stop scheduling reconnects.
|
|
235
|
+
* The keepalive alarm (~24s) will still call connect() periodically, but at a
|
|
236
|
+
* much lower frequency — reducing console noise when the daemon is not running.
|
|
237
|
+
*/
|
|
238
|
+
var MAX_EAGER_ATTEMPTS = 6;
|
|
195
239
|
function scheduleReconnect() {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
240
|
+
if (reconnectTimer) return;
|
|
241
|
+
reconnectAttempts++;
|
|
242
|
+
if (reconnectAttempts > MAX_EAGER_ATTEMPTS) return;
|
|
243
|
+
const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
|
|
244
|
+
reconnectTimer = setTimeout(() => {
|
|
245
|
+
reconnectTimer = null;
|
|
246
|
+
connect();
|
|
247
|
+
}, delay);
|
|
248
|
+
}
|
|
249
|
+
var automationSessions = /* @__PURE__ */ new Map();
|
|
250
|
+
var WINDOW_IDLE_TIMEOUT = 3e4;
|
|
207
251
|
function getWorkspaceKey(workspace) {
|
|
208
|
-
|
|
252
|
+
return workspace?.trim() || "default";
|
|
209
253
|
}
|
|
210
254
|
function resetWindowIdleTimer(workspace) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
255
|
+
const session = automationSessions.get(workspace);
|
|
256
|
+
if (!session) return;
|
|
257
|
+
if (session.idleTimer) clearTimeout(session.idleTimer);
|
|
258
|
+
session.idleDeadlineAt = Date.now() + WINDOW_IDLE_TIMEOUT;
|
|
259
|
+
session.idleTimer = setTimeout(async () => {
|
|
260
|
+
const current = automationSessions.get(workspace);
|
|
261
|
+
if (!current) return;
|
|
262
|
+
if (!current.owned) {
|
|
263
|
+
console.log(`[opencli] Borrowed workspace ${workspace} detached from window ${current.windowId} (idle timeout)`);
|
|
264
|
+
automationSessions.delete(workspace);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
await chrome.windows.remove(current.windowId);
|
|
269
|
+
console.log(`[opencli] Automation window ${current.windowId} (${workspace}) closed (idle timeout)`);
|
|
270
|
+
} catch {}
|
|
271
|
+
automationSessions.delete(workspace);
|
|
272
|
+
}, WINDOW_IDLE_TIMEOUT);
|
|
225
273
|
}
|
|
274
|
+
/** Get or create the dedicated automation window. */
|
|
226
275
|
async function getAutomationWindow(workspace) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return session.windowId;
|
|
276
|
+
const existing = automationSessions.get(workspace);
|
|
277
|
+
if (existing) try {
|
|
278
|
+
await chrome.windows.get(existing.windowId);
|
|
279
|
+
return existing.windowId;
|
|
280
|
+
} catch {
|
|
281
|
+
automationSessions.delete(workspace);
|
|
282
|
+
}
|
|
283
|
+
const session = {
|
|
284
|
+
windowId: (await chrome.windows.create({
|
|
285
|
+
url: BLANK_PAGE,
|
|
286
|
+
focused: false,
|
|
287
|
+
width: 1280,
|
|
288
|
+
height: 900,
|
|
289
|
+
type: "normal"
|
|
290
|
+
})).id,
|
|
291
|
+
idleTimer: null,
|
|
292
|
+
idleDeadlineAt: Date.now() + WINDOW_IDLE_TIMEOUT,
|
|
293
|
+
owned: true,
|
|
294
|
+
preferredTabId: null
|
|
295
|
+
};
|
|
296
|
+
automationSessions.set(workspace, session);
|
|
297
|
+
console.log(`[opencli] Created automation window ${session.windowId} (${workspace})`);
|
|
298
|
+
resetWindowIdleTimer(workspace);
|
|
299
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
300
|
+
return session.windowId;
|
|
253
301
|
}
|
|
254
302
|
chrome.windows.onRemoved.addListener((windowId) => {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
}
|
|
303
|
+
for (const [workspace, session] of automationSessions.entries()) if (session.windowId === windowId) {
|
|
304
|
+
console.log(`[opencli] Automation window closed (${workspace})`);
|
|
305
|
+
if (session.idleTimer) clearTimeout(session.idleTimer);
|
|
306
|
+
automationSessions.delete(workspace);
|
|
307
|
+
}
|
|
262
308
|
});
|
|
263
|
-
|
|
309
|
+
var initialized = false;
|
|
264
310
|
function initialize() {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
311
|
+
if (initialized) return;
|
|
312
|
+
initialized = true;
|
|
313
|
+
chrome.alarms.create("keepalive", { periodInMinutes: .4 });
|
|
314
|
+
registerListeners();
|
|
315
|
+
connect();
|
|
316
|
+
console.log("[opencli] OpenCLI extension initialized");
|
|
271
317
|
}
|
|
272
318
|
chrome.runtime.onInstalled.addListener(() => {
|
|
273
|
-
|
|
319
|
+
initialize();
|
|
274
320
|
});
|
|
275
321
|
chrome.runtime.onStartup.addListener(() => {
|
|
276
|
-
|
|
322
|
+
initialize();
|
|
277
323
|
});
|
|
278
324
|
chrome.alarms.onAlarm.addListener((alarm) => {
|
|
279
|
-
|
|
325
|
+
if (alarm.name === "keepalive") connect();
|
|
280
326
|
});
|
|
281
327
|
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
return false;
|
|
328
|
+
if (msg?.type === "getStatus") sendResponse({
|
|
329
|
+
connected: ws?.readyState === WebSocket.OPEN,
|
|
330
|
+
reconnecting: reconnectTimer !== null
|
|
331
|
+
});
|
|
332
|
+
return false;
|
|
289
333
|
});
|
|
290
334
|
async function handleCommand(cmd) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
335
|
+
const workspace = getWorkspaceKey(cmd.workspace);
|
|
336
|
+
resetWindowIdleTimer(workspace);
|
|
337
|
+
try {
|
|
338
|
+
switch (cmd.action) {
|
|
339
|
+
case "exec": return await handleExec(cmd, workspace);
|
|
340
|
+
case "navigate": return await handleNavigate(cmd, workspace);
|
|
341
|
+
case "tabs": return await handleTabs(cmd, workspace);
|
|
342
|
+
case "cookies": return await handleCookies(cmd);
|
|
343
|
+
case "screenshot": return await handleScreenshot(cmd, workspace);
|
|
344
|
+
case "close-window": return await handleCloseWindow(cmd, workspace);
|
|
345
|
+
case "sessions": return await handleSessions(cmd);
|
|
346
|
+
case "set-file-input": return await handleSetFileInput(cmd, workspace);
|
|
347
|
+
case "bind-current": return await handleBindCurrent(cmd, workspace);
|
|
348
|
+
default: return {
|
|
349
|
+
id: cmd.id,
|
|
350
|
+
ok: false,
|
|
351
|
+
error: `Unknown action: ${cmd.action}`
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
} catch (err) {
|
|
355
|
+
return {
|
|
356
|
+
id: cmd.id,
|
|
357
|
+
ok: false,
|
|
358
|
+
error: err instanceof Error ? err.message : String(err)
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/** Internal blank page used when no user URL is provided. */
|
|
363
|
+
var BLANK_PAGE = "data:text/html,<html></html>";
|
|
364
|
+
/** Check if a URL can be attached via CDP — only allow http(s) and our internal blank page. */
|
|
321
365
|
function isDebuggableUrl(url) {
|
|
322
|
-
|
|
323
|
-
|
|
366
|
+
if (!url) return true;
|
|
367
|
+
return url.startsWith("http://") || url.startsWith("https://") || url === BLANK_PAGE;
|
|
324
368
|
}
|
|
369
|
+
/** Check if a URL is safe for user-facing navigation (http/https only). */
|
|
325
370
|
function isSafeNavigationUrl(url) {
|
|
326
|
-
|
|
371
|
+
return url.startsWith("http://") || url.startsWith("https://");
|
|
327
372
|
}
|
|
373
|
+
/** Minimal URL normalization for same-page comparison: root slash + default port only. */
|
|
328
374
|
function normalizeUrlForComparison(url) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return url;
|
|
339
|
-
}
|
|
375
|
+
if (!url) return "";
|
|
376
|
+
try {
|
|
377
|
+
const parsed = new URL(url);
|
|
378
|
+
if (parsed.protocol === "https:" && parsed.port === "443" || parsed.protocol === "http:" && parsed.port === "80") parsed.port = "";
|
|
379
|
+
const pathname = parsed.pathname === "/" ? "" : parsed.pathname;
|
|
380
|
+
return `${parsed.protocol}//${parsed.host}${pathname}${parsed.search}${parsed.hash}`;
|
|
381
|
+
} catch {
|
|
382
|
+
return url;
|
|
383
|
+
}
|
|
340
384
|
}
|
|
341
385
|
function isTargetUrl(currentUrl, targetUrl) {
|
|
342
|
-
|
|
386
|
+
return normalizeUrlForComparison(currentUrl) === normalizeUrlForComparison(targetUrl);
|
|
387
|
+
}
|
|
388
|
+
function matchesDomain(url, domain) {
|
|
389
|
+
if (!url) return false;
|
|
390
|
+
try {
|
|
391
|
+
const parsed = new URL(url);
|
|
392
|
+
return parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`);
|
|
393
|
+
} catch {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function matchesBindCriteria(tab, cmd) {
|
|
398
|
+
if (!tab.id || !isDebuggableUrl(tab.url)) return false;
|
|
399
|
+
if (cmd.matchDomain && !matchesDomain(tab.url, cmd.matchDomain)) return false;
|
|
400
|
+
if (cmd.matchPathPrefix) try {
|
|
401
|
+
if (!new URL(tab.url).pathname.startsWith(cmd.matchPathPrefix)) return false;
|
|
402
|
+
} catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
343
406
|
}
|
|
407
|
+
function isNotebooklmWorkspace(workspace) {
|
|
408
|
+
return workspace === "site:notebooklm";
|
|
409
|
+
}
|
|
410
|
+
function classifyNotebooklmUrl(url) {
|
|
411
|
+
if (!url) return "other";
|
|
412
|
+
try {
|
|
413
|
+
const parsed = new URL(url);
|
|
414
|
+
if (parsed.hostname !== "notebooklm.google.com") return "other";
|
|
415
|
+
return parsed.pathname.startsWith("/notebook/") ? "notebook" : "home";
|
|
416
|
+
} catch {
|
|
417
|
+
return "other";
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function scoreWorkspaceTab(workspace, tab) {
|
|
421
|
+
if (!tab.id || !isDebuggableUrl(tab.url)) return -1;
|
|
422
|
+
if (isNotebooklmWorkspace(workspace)) {
|
|
423
|
+
const kind = classifyNotebooklmUrl(tab.url);
|
|
424
|
+
if (kind === "other") return -1;
|
|
425
|
+
if (kind === "notebook") return tab.active ? 400 : 300;
|
|
426
|
+
return tab.active ? 200 : 100;
|
|
427
|
+
}
|
|
428
|
+
return -1;
|
|
429
|
+
}
|
|
430
|
+
function setWorkspaceSession(workspace, session) {
|
|
431
|
+
const existing = automationSessions.get(workspace);
|
|
432
|
+
if (existing?.idleTimer) clearTimeout(existing.idleTimer);
|
|
433
|
+
automationSessions.set(workspace, {
|
|
434
|
+
...session,
|
|
435
|
+
idleTimer: null,
|
|
436
|
+
idleDeadlineAt: Date.now() + WINDOW_IDLE_TIMEOUT
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
async function maybeBindWorkspaceToExistingTab(workspace) {
|
|
440
|
+
if (!isNotebooklmWorkspace(workspace)) return null;
|
|
441
|
+
const tabs = await chrome.tabs.query({});
|
|
442
|
+
let bestTab = null;
|
|
443
|
+
let bestScore = -1;
|
|
444
|
+
for (const tab of tabs) {
|
|
445
|
+
const score = scoreWorkspaceTab(workspace, tab);
|
|
446
|
+
if (score > bestScore) {
|
|
447
|
+
bestScore = score;
|
|
448
|
+
bestTab = tab;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (!bestTab?.id || bestScore < 0) return null;
|
|
452
|
+
setWorkspaceSession(workspace, {
|
|
453
|
+
windowId: bestTab.windowId,
|
|
454
|
+
owned: false,
|
|
455
|
+
preferredTabId: bestTab.id
|
|
456
|
+
});
|
|
457
|
+
console.log(`[opencli] Workspace ${workspace} bound to existing tab ${bestTab.id} in window ${bestTab.windowId}`);
|
|
458
|
+
resetWindowIdleTimer(workspace);
|
|
459
|
+
return bestTab.id;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Resolve target tab in the automation window.
|
|
463
|
+
* If explicit tabId is given, use that directly.
|
|
464
|
+
* Otherwise, find or create a tab in the dedicated automation window.
|
|
465
|
+
*/
|
|
344
466
|
async function resolveTabId(tabId, workspace) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
467
|
+
if (tabId !== void 0) try {
|
|
468
|
+
const tab = await chrome.tabs.get(tabId);
|
|
469
|
+
const session = automationSessions.get(workspace);
|
|
470
|
+
const matchesSession = session ? session.preferredTabId !== null ? session.preferredTabId === tabId : tab.windowId === session.windowId : false;
|
|
471
|
+
if (isDebuggableUrl(tab.url) && matchesSession) return tabId;
|
|
472
|
+
if (session && !matchesSession) console.warn(`[opencli] Tab ${tabId} is not bound to workspace ${workspace}, re-resolving`);
|
|
473
|
+
else if (!isDebuggableUrl(tab.url)) console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
|
|
474
|
+
} catch {
|
|
475
|
+
console.warn(`[opencli] Tab ${tabId} no longer exists, re-resolving`);
|
|
476
|
+
}
|
|
477
|
+
const adoptedTabId = await maybeBindWorkspaceToExistingTab(workspace);
|
|
478
|
+
if (adoptedTabId !== null) return adoptedTabId;
|
|
479
|
+
const existingSession = automationSessions.get(workspace);
|
|
480
|
+
if (existingSession?.preferredTabId !== null) try {
|
|
481
|
+
const preferredTab = await chrome.tabs.get(existingSession.preferredTabId);
|
|
482
|
+
if (isDebuggableUrl(preferredTab.url)) return preferredTab.id;
|
|
483
|
+
} catch {
|
|
484
|
+
automationSessions.delete(workspace);
|
|
485
|
+
}
|
|
486
|
+
const windowId = await getAutomationWindow(workspace);
|
|
487
|
+
const tabs = await chrome.tabs.query({ windowId });
|
|
488
|
+
const debuggableTab = tabs.find((t) => t.id && isDebuggableUrl(t.url));
|
|
489
|
+
if (debuggableTab?.id) return debuggableTab.id;
|
|
490
|
+
const reuseTab = tabs.find((t) => t.id);
|
|
491
|
+
if (reuseTab?.id) {
|
|
492
|
+
await chrome.tabs.update(reuseTab.id, { url: BLANK_PAGE });
|
|
493
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
494
|
+
try {
|
|
495
|
+
const updated = await chrome.tabs.get(reuseTab.id);
|
|
496
|
+
if (isDebuggableUrl(updated.url)) return reuseTab.id;
|
|
497
|
+
console.warn(`[opencli] data: URI was intercepted (${updated.url}), creating fresh tab`);
|
|
498
|
+
} catch {}
|
|
499
|
+
}
|
|
500
|
+
const newTab = await chrome.tabs.create({
|
|
501
|
+
windowId,
|
|
502
|
+
url: BLANK_PAGE,
|
|
503
|
+
active: true
|
|
504
|
+
});
|
|
505
|
+
if (!newTab.id) throw new Error("Failed to create tab in automation window");
|
|
506
|
+
return newTab.id;
|
|
377
507
|
}
|
|
378
508
|
async function listAutomationTabs(workspace) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
509
|
+
const session = automationSessions.get(workspace);
|
|
510
|
+
if (!session) return [];
|
|
511
|
+
if (session.preferredTabId !== null) try {
|
|
512
|
+
return [await chrome.tabs.get(session.preferredTabId)];
|
|
513
|
+
} catch {
|
|
514
|
+
automationSessions.delete(workspace);
|
|
515
|
+
return [];
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
return await chrome.tabs.query({ windowId: session.windowId });
|
|
519
|
+
} catch {
|
|
520
|
+
automationSessions.delete(workspace);
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
387
523
|
}
|
|
388
524
|
async function listAutomationWebTabs(workspace) {
|
|
389
|
-
|
|
390
|
-
return tabs.filter((tab) => isDebuggableUrl(tab.url));
|
|
525
|
+
return (await listAutomationTabs(workspace)).filter((tab) => isDebuggableUrl(tab.url));
|
|
391
526
|
}
|
|
392
527
|
async function handleExec(cmd, workspace) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
528
|
+
if (!cmd.code) return {
|
|
529
|
+
id: cmd.id,
|
|
530
|
+
ok: false,
|
|
531
|
+
error: "Missing code"
|
|
532
|
+
};
|
|
533
|
+
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
534
|
+
try {
|
|
535
|
+
const data = await evaluateAsync(tabId, cmd.code);
|
|
536
|
+
return {
|
|
537
|
+
id: cmd.id,
|
|
538
|
+
ok: true,
|
|
539
|
+
data
|
|
540
|
+
};
|
|
541
|
+
} catch (err) {
|
|
542
|
+
return {
|
|
543
|
+
id: cmd.id,
|
|
544
|
+
ok: false,
|
|
545
|
+
error: err instanceof Error ? err.message : String(err)
|
|
546
|
+
};
|
|
547
|
+
}
|
|
401
548
|
}
|
|
402
549
|
async function handleNavigate(cmd, workspace) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
550
|
+
if (!cmd.url) return {
|
|
551
|
+
id: cmd.id,
|
|
552
|
+
ok: false,
|
|
553
|
+
error: "Missing url"
|
|
554
|
+
};
|
|
555
|
+
if (!isSafeNavigationUrl(cmd.url)) return {
|
|
556
|
+
id: cmd.id,
|
|
557
|
+
ok: false,
|
|
558
|
+
error: "Blocked URL scheme -- only http:// and https:// are allowed"
|
|
559
|
+
};
|
|
560
|
+
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
561
|
+
const beforeTab = await chrome.tabs.get(tabId);
|
|
562
|
+
const beforeNormalized = normalizeUrlForComparison(beforeTab.url);
|
|
563
|
+
const targetUrl = cmd.url;
|
|
564
|
+
if (beforeTab.status === "complete" && isTargetUrl(beforeTab.url, targetUrl)) return {
|
|
565
|
+
id: cmd.id,
|
|
566
|
+
ok: true,
|
|
567
|
+
data: {
|
|
568
|
+
title: beforeTab.title,
|
|
569
|
+
url: beforeTab.url,
|
|
570
|
+
tabId,
|
|
571
|
+
timedOut: false
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
await detach(tabId);
|
|
575
|
+
await chrome.tabs.update(tabId, { url: targetUrl });
|
|
576
|
+
let timedOut = false;
|
|
577
|
+
await new Promise((resolve) => {
|
|
578
|
+
let settled = false;
|
|
579
|
+
let checkTimer = null;
|
|
580
|
+
let timeoutTimer = null;
|
|
581
|
+
const finish = () => {
|
|
582
|
+
if (settled) return;
|
|
583
|
+
settled = true;
|
|
584
|
+
chrome.tabs.onUpdated.removeListener(listener);
|
|
585
|
+
if (checkTimer) clearTimeout(checkTimer);
|
|
586
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
587
|
+
resolve();
|
|
588
|
+
};
|
|
589
|
+
const isNavigationDone = (url) => {
|
|
590
|
+
return isTargetUrl(url, targetUrl) || normalizeUrlForComparison(url) !== beforeNormalized;
|
|
591
|
+
};
|
|
592
|
+
const listener = (id, info, tab) => {
|
|
593
|
+
if (id !== tabId) return;
|
|
594
|
+
if (info.status === "complete" && isNavigationDone(tab.url ?? info.url)) finish();
|
|
595
|
+
};
|
|
596
|
+
chrome.tabs.onUpdated.addListener(listener);
|
|
597
|
+
checkTimer = setTimeout(async () => {
|
|
598
|
+
try {
|
|
599
|
+
const currentTab = await chrome.tabs.get(tabId);
|
|
600
|
+
if (currentTab.status === "complete" && isNavigationDone(currentTab.url)) finish();
|
|
601
|
+
} catch {}
|
|
602
|
+
}, 100);
|
|
603
|
+
timeoutTimer = setTimeout(() => {
|
|
604
|
+
timedOut = true;
|
|
605
|
+
console.warn(`[opencli] Navigate to ${targetUrl} timed out after 15s`);
|
|
606
|
+
finish();
|
|
607
|
+
}, 15e3);
|
|
608
|
+
});
|
|
609
|
+
const tab = await chrome.tabs.get(tabId);
|
|
610
|
+
return {
|
|
611
|
+
id: cmd.id,
|
|
612
|
+
ok: true,
|
|
613
|
+
data: {
|
|
614
|
+
title: tab.title,
|
|
615
|
+
url: tab.url,
|
|
616
|
+
tabId,
|
|
617
|
+
timedOut
|
|
618
|
+
}
|
|
619
|
+
};
|
|
464
620
|
}
|
|
465
621
|
async function handleTabs(cmd, workspace) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
622
|
+
switch (cmd.op) {
|
|
623
|
+
case "list": {
|
|
624
|
+
const data = (await listAutomationWebTabs(workspace)).map((t, i) => ({
|
|
625
|
+
index: i,
|
|
626
|
+
tabId: t.id,
|
|
627
|
+
url: t.url,
|
|
628
|
+
title: t.title,
|
|
629
|
+
active: t.active
|
|
630
|
+
}));
|
|
631
|
+
return {
|
|
632
|
+
id: cmd.id,
|
|
633
|
+
ok: true,
|
|
634
|
+
data
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
case "new": {
|
|
638
|
+
if (cmd.url && !isSafeNavigationUrl(cmd.url)) return {
|
|
639
|
+
id: cmd.id,
|
|
640
|
+
ok: false,
|
|
641
|
+
error: "Blocked URL scheme -- only http:// and https:// are allowed"
|
|
642
|
+
};
|
|
643
|
+
const windowId = await getAutomationWindow(workspace);
|
|
644
|
+
const tab = await chrome.tabs.create({
|
|
645
|
+
windowId,
|
|
646
|
+
url: cmd.url ?? BLANK_PAGE,
|
|
647
|
+
active: true
|
|
648
|
+
});
|
|
649
|
+
return {
|
|
650
|
+
id: cmd.id,
|
|
651
|
+
ok: true,
|
|
652
|
+
data: {
|
|
653
|
+
tabId: tab.id,
|
|
654
|
+
url: tab.url
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
case "close": {
|
|
659
|
+
if (cmd.index !== void 0) {
|
|
660
|
+
const target = (await listAutomationWebTabs(workspace))[cmd.index];
|
|
661
|
+
if (!target?.id) return {
|
|
662
|
+
id: cmd.id,
|
|
663
|
+
ok: false,
|
|
664
|
+
error: `Tab index ${cmd.index} not found`
|
|
665
|
+
};
|
|
666
|
+
await chrome.tabs.remove(target.id);
|
|
667
|
+
await detach(target.id);
|
|
668
|
+
return {
|
|
669
|
+
id: cmd.id,
|
|
670
|
+
ok: true,
|
|
671
|
+
data: { closed: target.id }
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
675
|
+
await chrome.tabs.remove(tabId);
|
|
676
|
+
await detach(tabId);
|
|
677
|
+
return {
|
|
678
|
+
id: cmd.id,
|
|
679
|
+
ok: true,
|
|
680
|
+
data: { closed: tabId }
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
case "select": {
|
|
684
|
+
if (cmd.index === void 0 && cmd.tabId === void 0) return {
|
|
685
|
+
id: cmd.id,
|
|
686
|
+
ok: false,
|
|
687
|
+
error: "Missing index or tabId"
|
|
688
|
+
};
|
|
689
|
+
if (cmd.tabId !== void 0) {
|
|
690
|
+
const session = automationSessions.get(workspace);
|
|
691
|
+
let tab;
|
|
692
|
+
try {
|
|
693
|
+
tab = await chrome.tabs.get(cmd.tabId);
|
|
694
|
+
} catch {
|
|
695
|
+
return {
|
|
696
|
+
id: cmd.id,
|
|
697
|
+
ok: false,
|
|
698
|
+
error: `Tab ${cmd.tabId} no longer exists`
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
if (!session || tab.windowId !== session.windowId) return {
|
|
702
|
+
id: cmd.id,
|
|
703
|
+
ok: false,
|
|
704
|
+
error: `Tab ${cmd.tabId} is not in the automation window`
|
|
705
|
+
};
|
|
706
|
+
await chrome.tabs.update(cmd.tabId, { active: true });
|
|
707
|
+
return {
|
|
708
|
+
id: cmd.id,
|
|
709
|
+
ok: true,
|
|
710
|
+
data: { selected: cmd.tabId }
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
const target = (await listAutomationWebTabs(workspace))[cmd.index];
|
|
714
|
+
if (!target?.id) return {
|
|
715
|
+
id: cmd.id,
|
|
716
|
+
ok: false,
|
|
717
|
+
error: `Tab index ${cmd.index} not found`
|
|
718
|
+
};
|
|
719
|
+
await chrome.tabs.update(target.id, { active: true });
|
|
720
|
+
return {
|
|
721
|
+
id: cmd.id,
|
|
722
|
+
ok: true,
|
|
723
|
+
data: { selected: target.id }
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
default: return {
|
|
727
|
+
id: cmd.id,
|
|
728
|
+
ok: false,
|
|
729
|
+
error: `Unknown tabs op: ${cmd.op}`
|
|
730
|
+
};
|
|
731
|
+
}
|
|
526
732
|
}
|
|
527
733
|
async function handleCookies(cmd) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
734
|
+
if (!cmd.domain && !cmd.url) return {
|
|
735
|
+
id: cmd.id,
|
|
736
|
+
ok: false,
|
|
737
|
+
error: "Cookie scope required: provide domain or url to avoid dumping all cookies"
|
|
738
|
+
};
|
|
739
|
+
const details = {};
|
|
740
|
+
if (cmd.domain) details.domain = cmd.domain;
|
|
741
|
+
if (cmd.url) details.url = cmd.url;
|
|
742
|
+
const data = (await chrome.cookies.getAll(details)).map((c) => ({
|
|
743
|
+
name: c.name,
|
|
744
|
+
value: c.value,
|
|
745
|
+
domain: c.domain,
|
|
746
|
+
path: c.path,
|
|
747
|
+
secure: c.secure,
|
|
748
|
+
httpOnly: c.httpOnly,
|
|
749
|
+
expirationDate: c.expirationDate
|
|
750
|
+
}));
|
|
751
|
+
return {
|
|
752
|
+
id: cmd.id,
|
|
753
|
+
ok: true,
|
|
754
|
+
data
|
|
755
|
+
};
|
|
545
756
|
}
|
|
546
757
|
async function handleScreenshot(cmd, workspace) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
758
|
+
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
759
|
+
try {
|
|
760
|
+
const data = await screenshot(tabId, {
|
|
761
|
+
format: cmd.format,
|
|
762
|
+
quality: cmd.quality,
|
|
763
|
+
fullPage: cmd.fullPage
|
|
764
|
+
});
|
|
765
|
+
return {
|
|
766
|
+
id: cmd.id,
|
|
767
|
+
ok: true,
|
|
768
|
+
data
|
|
769
|
+
};
|
|
770
|
+
} catch (err) {
|
|
771
|
+
return {
|
|
772
|
+
id: cmd.id,
|
|
773
|
+
ok: false,
|
|
774
|
+
error: err instanceof Error ? err.message : String(err)
|
|
775
|
+
};
|
|
776
|
+
}
|
|
558
777
|
}
|
|
559
778
|
async function handleCloseWindow(cmd, workspace) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
779
|
+
const session = automationSessions.get(workspace);
|
|
780
|
+
if (session) {
|
|
781
|
+
if (session.owned) try {
|
|
782
|
+
await chrome.windows.remove(session.windowId);
|
|
783
|
+
} catch {}
|
|
784
|
+
if (session.idleTimer) clearTimeout(session.idleTimer);
|
|
785
|
+
automationSessions.delete(workspace);
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
id: cmd.id,
|
|
789
|
+
ok: true,
|
|
790
|
+
data: { closed: true }
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
async function handleSetFileInput(cmd, workspace) {
|
|
794
|
+
if (!cmd.files || !Array.isArray(cmd.files) || cmd.files.length === 0) return {
|
|
795
|
+
id: cmd.id,
|
|
796
|
+
ok: false,
|
|
797
|
+
error: "Missing or empty files array"
|
|
798
|
+
};
|
|
799
|
+
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
800
|
+
try {
|
|
801
|
+
await setFileInputFiles(tabId, cmd.files, cmd.selector);
|
|
802
|
+
return {
|
|
803
|
+
id: cmd.id,
|
|
804
|
+
ok: true,
|
|
805
|
+
data: { count: cmd.files.length }
|
|
806
|
+
};
|
|
807
|
+
} catch (err) {
|
|
808
|
+
return {
|
|
809
|
+
id: cmd.id,
|
|
810
|
+
ok: false,
|
|
811
|
+
error: err instanceof Error ? err.message : String(err)
|
|
812
|
+
};
|
|
813
|
+
}
|
|
570
814
|
}
|
|
571
815
|
async function handleSessions(cmd) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
816
|
+
const now = Date.now();
|
|
817
|
+
const data = await Promise.all([...automationSessions.entries()].map(async ([workspace, session]) => ({
|
|
818
|
+
workspace,
|
|
819
|
+
windowId: session.windowId,
|
|
820
|
+
tabCount: (await chrome.tabs.query({ windowId: session.windowId })).filter((tab) => isDebuggableUrl(tab.url)).length,
|
|
821
|
+
idleMsRemaining: Math.max(0, session.idleDeadlineAt - now)
|
|
822
|
+
})));
|
|
823
|
+
return {
|
|
824
|
+
id: cmd.id,
|
|
825
|
+
ok: true,
|
|
826
|
+
data
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
async function handleBindCurrent(cmd, workspace) {
|
|
830
|
+
const activeTabs = await chrome.tabs.query({
|
|
831
|
+
active: true,
|
|
832
|
+
lastFocusedWindow: true
|
|
833
|
+
});
|
|
834
|
+
const fallbackTabs = await chrome.tabs.query({ lastFocusedWindow: true });
|
|
835
|
+
const allTabs = await chrome.tabs.query({});
|
|
836
|
+
const boundTab = activeTabs.find((tab) => matchesBindCriteria(tab, cmd)) ?? fallbackTabs.find((tab) => matchesBindCriteria(tab, cmd)) ?? allTabs.find((tab) => matchesBindCriteria(tab, cmd));
|
|
837
|
+
if (!boundTab?.id) return {
|
|
838
|
+
id: cmd.id,
|
|
839
|
+
ok: false,
|
|
840
|
+
error: cmd.matchDomain || cmd.matchPathPrefix ? `No visible tab matching ${cmd.matchDomain ?? "domain"}${cmd.matchPathPrefix ? ` ${cmd.matchPathPrefix}` : ""}` : "No active debuggable tab found"
|
|
841
|
+
};
|
|
842
|
+
setWorkspaceSession(workspace, {
|
|
843
|
+
windowId: boundTab.windowId,
|
|
844
|
+
owned: false,
|
|
845
|
+
preferredTabId: boundTab.id
|
|
846
|
+
});
|
|
847
|
+
resetWindowIdleTimer(workspace);
|
|
848
|
+
console.log(`[opencli] Workspace ${workspace} explicitly bound to tab ${boundTab.id} (${boundTab.url})`);
|
|
849
|
+
return {
|
|
850
|
+
id: cmd.id,
|
|
851
|
+
ok: true,
|
|
852
|
+
data: {
|
|
853
|
+
tabId: boundTab.id,
|
|
854
|
+
windowId: boundTab.windowId,
|
|
855
|
+
url: boundTab.url,
|
|
856
|
+
title: boundTab.title,
|
|
857
|
+
workspace
|
|
858
|
+
}
|
|
859
|
+
};
|
|
580
860
|
}
|
|
861
|
+
//#endregion
|