@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
|
@@ -35,8 +35,12 @@ function createChromeMock() {
|
|
|
35
35
|
{ id: 3, windowId: 1, url: 'chrome://extensions', title: 'chrome', active: false, status: 'complete' },
|
|
36
36
|
];
|
|
37
37
|
|
|
38
|
-
const query = vi.fn(async (queryInfo: { windowId?: number } = {}) => {
|
|
39
|
-
return tabs.filter((tab) =>
|
|
38
|
+
const query = vi.fn(async (queryInfo: { windowId?: number; active?: boolean } = {}) => {
|
|
39
|
+
return tabs.filter((tab) => {
|
|
40
|
+
if (queryInfo.windowId !== undefined && tab.windowId !== queryInfo.windowId) return false;
|
|
41
|
+
if (queryInfo.active !== undefined && !!tab.active !== queryInfo.active) return false;
|
|
42
|
+
return true;
|
|
43
|
+
});
|
|
40
44
|
});
|
|
41
45
|
const create = vi.fn(async ({ windowId, url, active }: { windowId?: number; url?: string; active?: boolean }) => {
|
|
42
46
|
const tab: MockTab = {
|
|
@@ -84,6 +88,8 @@ function createChromeMock() {
|
|
|
84
88
|
runtime: {
|
|
85
89
|
onInstalled: { addListener: vi.fn() } as Listener<() => void>,
|
|
86
90
|
onStartup: { addListener: vi.fn() } as Listener<() => void>,
|
|
91
|
+
onMessage: { addListener: vi.fn() } as Listener<(msg: unknown, sender: unknown, sendResponse: (value: unknown) => void) => void>,
|
|
92
|
+
getManifest: vi.fn(() => ({ version: 'test-version' })),
|
|
87
93
|
},
|
|
88
94
|
cookies: {
|
|
89
95
|
getAll: vi.fn(async () => []),
|
|
@@ -193,4 +199,198 @@ describe('background tab isolation', () => {
|
|
|
193
199
|
expect.objectContaining({ workspace: 'site:zhihu', windowId: 2 }),
|
|
194
200
|
]));
|
|
195
201
|
});
|
|
202
|
+
|
|
203
|
+
it('rebinds site:notebooklm to the active notebook tab instead of a home tab', async () => {
|
|
204
|
+
const { chrome, tabs } = createChromeMock();
|
|
205
|
+
tabs[0].url = 'https://notebooklm.google.com/';
|
|
206
|
+
tabs[0].title = 'NotebookLM Home';
|
|
207
|
+
tabs[1].url = 'https://notebooklm.google.com/notebook/nb-live';
|
|
208
|
+
tabs[1].title = 'Live Notebook';
|
|
209
|
+
vi.stubGlobal('chrome', chrome);
|
|
210
|
+
|
|
211
|
+
const mod = await import('./background');
|
|
212
|
+
mod.__test__.setAutomationWindowId('site:notebooklm', 1);
|
|
213
|
+
|
|
214
|
+
const tabId = await mod.__test__.resolveTabId(undefined, 'site:notebooklm');
|
|
215
|
+
|
|
216
|
+
expect(tabId).toBe(2);
|
|
217
|
+
expect(mod.__test__.getSession('site:notebooklm')).toEqual(expect.objectContaining({
|
|
218
|
+
windowId: 2,
|
|
219
|
+
preferredTabId: 2,
|
|
220
|
+
owned: false,
|
|
221
|
+
}));
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('prefers a notebook tab over an active home tab for site:notebooklm', async () => {
|
|
225
|
+
const { chrome, tabs } = createChromeMock();
|
|
226
|
+
tabs[0].url = 'https://notebooklm.google.com/';
|
|
227
|
+
tabs[0].title = 'NotebookLM Home';
|
|
228
|
+
tabs[0].active = true;
|
|
229
|
+
tabs[1].url = 'https://notebooklm.google.com/notebook/nb-passive';
|
|
230
|
+
tabs[1].title = 'Notebook';
|
|
231
|
+
tabs[1].active = false;
|
|
232
|
+
vi.stubGlobal('chrome', chrome);
|
|
233
|
+
|
|
234
|
+
const mod = await import('./background');
|
|
235
|
+
mod.__test__.setAutomationWindowId('site:notebooklm', 1);
|
|
236
|
+
|
|
237
|
+
const tabId = await mod.__test__.resolveTabId(undefined, 'site:notebooklm');
|
|
238
|
+
|
|
239
|
+
expect(tabId).toBe(2);
|
|
240
|
+
expect(mod.__test__.getSession('site:notebooklm')).toEqual(expect.objectContaining({
|
|
241
|
+
windowId: 2,
|
|
242
|
+
preferredTabId: 2,
|
|
243
|
+
owned: false,
|
|
244
|
+
}));
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('detaches an adopted workspace session on idle instead of closing the user window', async () => {
|
|
248
|
+
const { chrome } = createChromeMock();
|
|
249
|
+
vi.stubGlobal('chrome', chrome);
|
|
250
|
+
vi.useFakeTimers();
|
|
251
|
+
|
|
252
|
+
const mod = await import('./background');
|
|
253
|
+
mod.__test__.setSession('site:notebooklm', {
|
|
254
|
+
windowId: 2,
|
|
255
|
+
preferredTabId: 2,
|
|
256
|
+
owned: false,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
mod.__test__.resetWindowIdleTimer('site:notebooklm');
|
|
260
|
+
await vi.advanceTimersByTimeAsync(30001);
|
|
261
|
+
|
|
262
|
+
expect(chrome.windows.remove).not.toHaveBeenCalled();
|
|
263
|
+
expect(mod.__test__.getSession('site:notebooklm')).toBeNull();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('binds the active NotebookLM tab into the workspace explicitly', async () => {
|
|
267
|
+
const { chrome, tabs } = createChromeMock();
|
|
268
|
+
tabs[1].url = 'https://notebooklm.google.com/notebook/nb-active';
|
|
269
|
+
tabs[1].title = 'Bound Notebook';
|
|
270
|
+
tabs[1].active = true;
|
|
271
|
+
vi.stubGlobal('chrome', chrome);
|
|
272
|
+
|
|
273
|
+
const mod = await import('./background');
|
|
274
|
+
const result = await mod.__test__.handleBindCurrent(
|
|
275
|
+
{
|
|
276
|
+
id: 'bind-current',
|
|
277
|
+
action: 'bind-current',
|
|
278
|
+
workspace: 'site:notebooklm',
|
|
279
|
+
matchDomain: 'notebooklm.google.com',
|
|
280
|
+
matchPathPrefix: '/notebook/',
|
|
281
|
+
},
|
|
282
|
+
'site:notebooklm',
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
expect(result).toEqual({
|
|
286
|
+
id: 'bind-current',
|
|
287
|
+
ok: true,
|
|
288
|
+
data: expect.objectContaining({
|
|
289
|
+
tabId: 2,
|
|
290
|
+
windowId: 2,
|
|
291
|
+
url: 'https://notebooklm.google.com/notebook/nb-active',
|
|
292
|
+
title: 'Bound Notebook',
|
|
293
|
+
workspace: 'site:notebooklm',
|
|
294
|
+
}),
|
|
295
|
+
});
|
|
296
|
+
expect(mod.__test__.getSession('site:notebooklm')).toEqual(expect.objectContaining({
|
|
297
|
+
windowId: 2,
|
|
298
|
+
preferredTabId: 2,
|
|
299
|
+
owned: false,
|
|
300
|
+
}));
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('bind-current falls back to another matching notebook tab in the current window', async () => {
|
|
304
|
+
const { chrome, tabs } = createChromeMock();
|
|
305
|
+
tabs[0].windowId = 2;
|
|
306
|
+
tabs[0].url = 'https://notebooklm.google.com/';
|
|
307
|
+
tabs[0].title = 'NotebookLM Home';
|
|
308
|
+
tabs[0].active = true;
|
|
309
|
+
tabs[1].url = 'https://notebooklm.google.com/notebook/nb-passive';
|
|
310
|
+
tabs[1].title = 'Passive Notebook';
|
|
311
|
+
tabs[1].active = false;
|
|
312
|
+
vi.stubGlobal('chrome', chrome);
|
|
313
|
+
|
|
314
|
+
const mod = await import('./background');
|
|
315
|
+
const result = await mod.__test__.handleBindCurrent(
|
|
316
|
+
{
|
|
317
|
+
id: 'bind-fallback',
|
|
318
|
+
action: 'bind-current',
|
|
319
|
+
workspace: 'site:notebooklm',
|
|
320
|
+
matchDomain: 'notebooklm.google.com',
|
|
321
|
+
matchPathPrefix: '/notebook/',
|
|
322
|
+
},
|
|
323
|
+
'site:notebooklm',
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
expect(result).toEqual({
|
|
327
|
+
id: 'bind-fallback',
|
|
328
|
+
ok: true,
|
|
329
|
+
data: expect.objectContaining({
|
|
330
|
+
tabId: 2,
|
|
331
|
+
windowId: 2,
|
|
332
|
+
url: 'https://notebooklm.google.com/notebook/nb-passive',
|
|
333
|
+
title: 'Passive Notebook',
|
|
334
|
+
}),
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('bind-current falls back to a matching notebook tab in another window of the same profile', async () => {
|
|
339
|
+
const { chrome, tabs } = createChromeMock();
|
|
340
|
+
tabs[0].windowId = 3;
|
|
341
|
+
tabs[0].url = 'https://notebooklm.google.com/';
|
|
342
|
+
tabs[0].title = 'NotebookLM Home';
|
|
343
|
+
tabs[0].active = true;
|
|
344
|
+
tabs[1].windowId = 2;
|
|
345
|
+
tabs[1].url = 'https://notebooklm.google.com/notebook/nb-other-window';
|
|
346
|
+
tabs[1].title = 'Notebook In Other Window';
|
|
347
|
+
tabs[1].active = false;
|
|
348
|
+
vi.stubGlobal('chrome', chrome);
|
|
349
|
+
|
|
350
|
+
const mod = await import('./background');
|
|
351
|
+
const result = await mod.__test__.handleBindCurrent(
|
|
352
|
+
{
|
|
353
|
+
id: 'bind-cross-window',
|
|
354
|
+
action: 'bind-current',
|
|
355
|
+
workspace: 'site:notebooklm',
|
|
356
|
+
matchDomain: 'notebooklm.google.com',
|
|
357
|
+
matchPathPrefix: '/notebook/',
|
|
358
|
+
},
|
|
359
|
+
'site:notebooklm',
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
expect(result).toEqual({
|
|
363
|
+
id: 'bind-cross-window',
|
|
364
|
+
ok: true,
|
|
365
|
+
data: expect.objectContaining({
|
|
366
|
+
tabId: 2,
|
|
367
|
+
windowId: 2,
|
|
368
|
+
url: 'https://notebooklm.google.com/notebook/nb-other-window',
|
|
369
|
+
title: 'Notebook In Other Window',
|
|
370
|
+
}),
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('rejects bind-current when the active tab is not NotebookLM', async () => {
|
|
375
|
+
const { chrome } = createChromeMock();
|
|
376
|
+
vi.stubGlobal('chrome', chrome);
|
|
377
|
+
|
|
378
|
+
const mod = await import('./background');
|
|
379
|
+
const result = await mod.__test__.handleBindCurrent(
|
|
380
|
+
{
|
|
381
|
+
id: 'bind-miss',
|
|
382
|
+
action: 'bind-current',
|
|
383
|
+
workspace: 'site:notebooklm',
|
|
384
|
+
matchDomain: 'notebooklm.google.com',
|
|
385
|
+
matchPathPrefix: '/notebook/',
|
|
386
|
+
},
|
|
387
|
+
'site:notebooklm',
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
expect(result).toEqual({
|
|
391
|
+
id: 'bind-miss',
|
|
392
|
+
ok: false,
|
|
393
|
+
error: 'No visible tab matching notebooklm.google.com /notebook/',
|
|
394
|
+
});
|
|
395
|
+
});
|
|
196
396
|
});
|
|
@@ -117,6 +117,8 @@ type AutomationSession = {
|
|
|
117
117
|
windowId: number;
|
|
118
118
|
idleTimer: ReturnType<typeof setTimeout> | null;
|
|
119
119
|
idleDeadlineAt: number;
|
|
120
|
+
owned: boolean;
|
|
121
|
+
preferredTabId: number | null;
|
|
120
122
|
};
|
|
121
123
|
|
|
122
124
|
const automationSessions = new Map<string, AutomationSession>();
|
|
@@ -134,6 +136,11 @@ function resetWindowIdleTimer(workspace: string): void {
|
|
|
134
136
|
session.idleTimer = setTimeout(async () => {
|
|
135
137
|
const current = automationSessions.get(workspace);
|
|
136
138
|
if (!current) return;
|
|
139
|
+
if (!current.owned) {
|
|
140
|
+
console.log(`[opencli] Borrowed workspace ${workspace} detached from window ${current.windowId} (idle timeout)`);
|
|
141
|
+
automationSessions.delete(workspace);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
137
144
|
try {
|
|
138
145
|
await chrome.windows.remove(current.windowId);
|
|
139
146
|
console.log(`[opencli] Automation window ${current.windowId} (${workspace}) closed (idle timeout)`);
|
|
@@ -173,6 +180,8 @@ async function getAutomationWindow(workspace: string): Promise<number> {
|
|
|
173
180
|
windowId: win.id!,
|
|
174
181
|
idleTimer: null,
|
|
175
182
|
idleDeadlineAt: Date.now() + WINDOW_IDLE_TIMEOUT,
|
|
183
|
+
owned: true,
|
|
184
|
+
preferredTabId: null,
|
|
176
185
|
};
|
|
177
186
|
automationSessions.set(workspace, session);
|
|
178
187
|
console.log(`[opencli] Created automation window ${session.windowId} (${workspace})`);
|
|
@@ -252,6 +261,10 @@ async function handleCommand(cmd: Command): Promise<Result> {
|
|
|
252
261
|
return await handleCloseWindow(cmd, workspace);
|
|
253
262
|
case 'sessions':
|
|
254
263
|
return await handleSessions(cmd);
|
|
264
|
+
case 'set-file-input':
|
|
265
|
+
return await handleSetFileInput(cmd, workspace);
|
|
266
|
+
case 'bind-current':
|
|
267
|
+
return await handleBindCurrent(cmd, workspace);
|
|
255
268
|
default:
|
|
256
269
|
return { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
|
|
257
270
|
}
|
|
@@ -299,6 +312,89 @@ function isTargetUrl(currentUrl: string | undefined, targetUrl: string): boolean
|
|
|
299
312
|
return normalizeUrlForComparison(currentUrl) === normalizeUrlForComparison(targetUrl);
|
|
300
313
|
}
|
|
301
314
|
|
|
315
|
+
function matchesDomain(url: string | undefined, domain: string): boolean {
|
|
316
|
+
if (!url) return false;
|
|
317
|
+
try {
|
|
318
|
+
const parsed = new URL(url);
|
|
319
|
+
return parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`);
|
|
320
|
+
} catch {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function matchesBindCriteria(tab: chrome.tabs.Tab, cmd: Command): boolean {
|
|
326
|
+
if (!tab.id || !isDebuggableUrl(tab.url)) return false;
|
|
327
|
+
if (cmd.matchDomain && !matchesDomain(tab.url, cmd.matchDomain)) return false;
|
|
328
|
+
if (cmd.matchPathPrefix) {
|
|
329
|
+
try {
|
|
330
|
+
const parsed = new URL(tab.url!);
|
|
331
|
+
if (!parsed.pathname.startsWith(cmd.matchPathPrefix)) return false;
|
|
332
|
+
} catch {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function isNotebooklmWorkspace(workspace: string): boolean {
|
|
340
|
+
return workspace === 'site:notebooklm';
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function classifyNotebooklmUrl(url?: string): 'notebook' | 'home' | 'other' {
|
|
344
|
+
if (!url) return 'other';
|
|
345
|
+
try {
|
|
346
|
+
const parsed = new URL(url);
|
|
347
|
+
if (parsed.hostname !== 'notebooklm.google.com') return 'other';
|
|
348
|
+
return parsed.pathname.startsWith('/notebook/') ? 'notebook' : 'home';
|
|
349
|
+
} catch {
|
|
350
|
+
return 'other';
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function scoreWorkspaceTab(workspace: string, tab: chrome.tabs.Tab): number {
|
|
355
|
+
if (!tab.id || !isDebuggableUrl(tab.url)) return -1;
|
|
356
|
+
if (isNotebooklmWorkspace(workspace)) {
|
|
357
|
+
const kind = classifyNotebooklmUrl(tab.url);
|
|
358
|
+
if (kind === 'other') return -1;
|
|
359
|
+
if (kind === 'notebook') return tab.active ? 400 : 300;
|
|
360
|
+
return tab.active ? 200 : 100;
|
|
361
|
+
}
|
|
362
|
+
return -1;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function setWorkspaceSession(workspace: string, session: Omit<AutomationSession, 'idleTimer' | 'idleDeadlineAt'>): void {
|
|
366
|
+
const existing = automationSessions.get(workspace);
|
|
367
|
+
if (existing?.idleTimer) clearTimeout(existing.idleTimer);
|
|
368
|
+
automationSessions.set(workspace, {
|
|
369
|
+
...session,
|
|
370
|
+
idleTimer: null,
|
|
371
|
+
idleDeadlineAt: Date.now() + WINDOW_IDLE_TIMEOUT,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function maybeBindWorkspaceToExistingTab(workspace: string): Promise<number | null> {
|
|
376
|
+
if (!isNotebooklmWorkspace(workspace)) return null;
|
|
377
|
+
const tabs = await chrome.tabs.query({});
|
|
378
|
+
let bestTab: chrome.tabs.Tab | null = null;
|
|
379
|
+
let bestScore = -1;
|
|
380
|
+
for (const tab of tabs) {
|
|
381
|
+
const score = scoreWorkspaceTab(workspace, tab);
|
|
382
|
+
if (score > bestScore) {
|
|
383
|
+
bestScore = score;
|
|
384
|
+
bestTab = tab;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (!bestTab?.id || bestScore < 0) return null;
|
|
388
|
+
setWorkspaceSession(workspace, {
|
|
389
|
+
windowId: bestTab.windowId,
|
|
390
|
+
owned: false,
|
|
391
|
+
preferredTabId: bestTab.id,
|
|
392
|
+
});
|
|
393
|
+
console.log(`[opencli] Workspace ${workspace} bound to existing tab ${bestTab.id} in window ${bestTab.windowId}`);
|
|
394
|
+
resetWindowIdleTimer(workspace);
|
|
395
|
+
return bestTab.id;
|
|
396
|
+
}
|
|
397
|
+
|
|
302
398
|
/**
|
|
303
399
|
* Resolve target tab in the automation window.
|
|
304
400
|
* If explicit tabId is given, use that directly.
|
|
@@ -312,9 +408,12 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
|
|
|
312
408
|
try {
|
|
313
409
|
const tab = await chrome.tabs.get(tabId);
|
|
314
410
|
const session = automationSessions.get(workspace);
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
411
|
+
const matchesSession = session
|
|
412
|
+
? (session.preferredTabId !== null ? session.preferredTabId === tabId : tab.windowId === session.windowId)
|
|
413
|
+
: false;
|
|
414
|
+
if (isDebuggableUrl(tab.url) && matchesSession) return tabId;
|
|
415
|
+
if (session && !matchesSession) {
|
|
416
|
+
console.warn(`[opencli] Tab ${tabId} is not bound to workspace ${workspace}, re-resolving`);
|
|
318
417
|
} else if (!isDebuggableUrl(tab.url)) {
|
|
319
418
|
// Tab exists but URL is not debuggable — fall through to auto-resolve
|
|
320
419
|
console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
|
|
@@ -325,6 +424,19 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
|
|
|
325
424
|
}
|
|
326
425
|
}
|
|
327
426
|
|
|
427
|
+
const adoptedTabId = await maybeBindWorkspaceToExistingTab(workspace);
|
|
428
|
+
if (adoptedTabId !== null) return adoptedTabId;
|
|
429
|
+
|
|
430
|
+
const existingSession = automationSessions.get(workspace);
|
|
431
|
+
if (existingSession?.preferredTabId !== null) {
|
|
432
|
+
try {
|
|
433
|
+
const preferredTab = await chrome.tabs.get(existingSession.preferredTabId);
|
|
434
|
+
if (isDebuggableUrl(preferredTab.url)) return preferredTab.id!;
|
|
435
|
+
} catch {
|
|
436
|
+
automationSessions.delete(workspace);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
328
440
|
// Get (or create) the automation window
|
|
329
441
|
const windowId = await getAutomationWindow(workspace);
|
|
330
442
|
|
|
@@ -357,6 +469,14 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
|
|
|
357
469
|
async function listAutomationTabs(workspace: string): Promise<chrome.tabs.Tab[]> {
|
|
358
470
|
const session = automationSessions.get(workspace);
|
|
359
471
|
if (!session) return [];
|
|
472
|
+
if (session.preferredTabId !== null) {
|
|
473
|
+
try {
|
|
474
|
+
return [await chrome.tabs.get(session.preferredTabId)];
|
|
475
|
+
} catch {
|
|
476
|
+
automationSessions.delete(workspace);
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
}
|
|
360
480
|
try {
|
|
361
481
|
return await chrome.tabs.query({ windowId: session.windowId });
|
|
362
482
|
} catch {
|
|
@@ -568,10 +688,12 @@ async function handleScreenshot(cmd: Command, workspace: string): Promise<Result
|
|
|
568
688
|
async function handleCloseWindow(cmd: Command, workspace: string): Promise<Result> {
|
|
569
689
|
const session = automationSessions.get(workspace);
|
|
570
690
|
if (session) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
691
|
+
if (session.owned) {
|
|
692
|
+
try {
|
|
693
|
+
await chrome.windows.remove(session.windowId);
|
|
694
|
+
} catch {
|
|
695
|
+
// Window may already be closed
|
|
696
|
+
}
|
|
575
697
|
}
|
|
576
698
|
if (session.idleTimer) clearTimeout(session.idleTimer);
|
|
577
699
|
automationSessions.delete(workspace);
|
|
@@ -579,6 +701,19 @@ async function handleCloseWindow(cmd: Command, workspace: string): Promise<Resul
|
|
|
579
701
|
return { id: cmd.id, ok: true, data: { closed: true } };
|
|
580
702
|
}
|
|
581
703
|
|
|
704
|
+
async function handleSetFileInput(cmd: Command, workspace: string): Promise<Result> {
|
|
705
|
+
if (!cmd.files || !Array.isArray(cmd.files) || cmd.files.length === 0) {
|
|
706
|
+
return { id: cmd.id, ok: false, error: 'Missing or empty files array' };
|
|
707
|
+
}
|
|
708
|
+
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
709
|
+
try {
|
|
710
|
+
await executor.setFileInputFiles(tabId, cmd.files, cmd.selector);
|
|
711
|
+
return { id: cmd.id, ok: true, data: { count: cmd.files.length } };
|
|
712
|
+
} catch (err) {
|
|
713
|
+
return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
582
717
|
async function handleSessions(cmd: Command): Promise<Result> {
|
|
583
718
|
const now = Date.now();
|
|
584
719
|
const data = await Promise.all([...automationSessions.entries()].map(async ([workspace, session]) => ({
|
|
@@ -590,11 +725,52 @@ async function handleSessions(cmd: Command): Promise<Result> {
|
|
|
590
725
|
return { id: cmd.id, ok: true, data };
|
|
591
726
|
}
|
|
592
727
|
|
|
728
|
+
async function handleBindCurrent(cmd: Command, workspace: string): Promise<Result> {
|
|
729
|
+
const activeTabs = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
|
|
730
|
+
const fallbackTabs = await chrome.tabs.query({ lastFocusedWindow: true });
|
|
731
|
+
const allTabs = await chrome.tabs.query({});
|
|
732
|
+
const boundTab = activeTabs.find((tab) => matchesBindCriteria(tab, cmd))
|
|
733
|
+
?? fallbackTabs.find((tab) => matchesBindCriteria(tab, cmd))
|
|
734
|
+
?? allTabs.find((tab) => matchesBindCriteria(tab, cmd));
|
|
735
|
+
if (!boundTab?.id) {
|
|
736
|
+
return {
|
|
737
|
+
id: cmd.id,
|
|
738
|
+
ok: false,
|
|
739
|
+
error: cmd.matchDomain || cmd.matchPathPrefix
|
|
740
|
+
? `No visible tab matching ${cmd.matchDomain ?? 'domain'}${cmd.matchPathPrefix ? ` ${cmd.matchPathPrefix}` : ''}`
|
|
741
|
+
: 'No active debuggable tab found',
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
setWorkspaceSession(workspace, {
|
|
746
|
+
windowId: boundTab.windowId,
|
|
747
|
+
owned: false,
|
|
748
|
+
preferredTabId: boundTab.id,
|
|
749
|
+
});
|
|
750
|
+
resetWindowIdleTimer(workspace);
|
|
751
|
+
console.log(`[opencli] Workspace ${workspace} explicitly bound to tab ${boundTab.id} (${boundTab.url})`);
|
|
752
|
+
return {
|
|
753
|
+
id: cmd.id,
|
|
754
|
+
ok: true,
|
|
755
|
+
data: {
|
|
756
|
+
tabId: boundTab.id,
|
|
757
|
+
windowId: boundTab.windowId,
|
|
758
|
+
url: boundTab.url,
|
|
759
|
+
title: boundTab.title,
|
|
760
|
+
workspace,
|
|
761
|
+
},
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
593
765
|
export const __test__ = {
|
|
594
766
|
handleNavigate,
|
|
595
767
|
isTargetUrl,
|
|
596
768
|
handleTabs,
|
|
597
769
|
handleSessions,
|
|
770
|
+
handleBindCurrent,
|
|
771
|
+
resolveTabId,
|
|
772
|
+
resetWindowIdleTimer,
|
|
773
|
+
getSession: (workspace: string = 'default') => automationSessions.get(workspace) ?? null,
|
|
598
774
|
getAutomationWindowId: (workspace: string = 'default') => automationSessions.get(workspace)?.windowId ?? null,
|
|
599
775
|
setAutomationWindowId: (workspace: string, windowId: number | null) => {
|
|
600
776
|
if (windowId === null) {
|
|
@@ -603,10 +779,13 @@ export const __test__ = {
|
|
|
603
779
|
automationSessions.delete(workspace);
|
|
604
780
|
return;
|
|
605
781
|
}
|
|
606
|
-
|
|
782
|
+
setWorkspaceSession(workspace, {
|
|
607
783
|
windowId,
|
|
608
|
-
|
|
609
|
-
|
|
784
|
+
owned: true,
|
|
785
|
+
preferredTabId: null,
|
|
610
786
|
});
|
|
611
787
|
},
|
|
788
|
+
setSession: (workspace: string, session: { windowId: number; owned: boolean; preferredTabId: number | null }) => {
|
|
789
|
+
setWorkspaceSession(workspace, session);
|
|
790
|
+
},
|
|
612
791
|
};
|
package/extension/src/cdp.ts
CHANGED
|
@@ -71,6 +71,18 @@ async function ensureAttached(tabId: number): Promise<void> {
|
|
|
71
71
|
} catch {
|
|
72
72
|
// Some pages may not need explicit enable
|
|
73
73
|
}
|
|
74
|
+
|
|
75
|
+
// Disable breakpoints so that `debugger;` statements in page code don't
|
|
76
|
+
// pause execution. Anti-bot scripts use `debugger;` traps to detect CDP —
|
|
77
|
+
// they measure the time gap caused by the pause. Deactivating breakpoints
|
|
78
|
+
// makes the engine skip `debugger;` entirely, neutralising the timing
|
|
79
|
+
// side-channel without patching page JS.
|
|
80
|
+
try {
|
|
81
|
+
await chrome.debugger.sendCommand({ tabId }, 'Debugger.enable');
|
|
82
|
+
await chrome.debugger.sendCommand({ tabId }, 'Debugger.setBreakpointsActive', { active: false });
|
|
83
|
+
} catch {
|
|
84
|
+
// Non-fatal: best-effort hardening
|
|
85
|
+
}
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
export async function evaluate(tabId: number, expression: string): Promise<unknown> {
|
|
@@ -147,6 +159,48 @@ export async function screenshot(
|
|
|
147
159
|
}
|
|
148
160
|
}
|
|
149
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Set local file paths on a file input element via CDP DOM.setFileInputFiles.
|
|
164
|
+
* This bypasses the need to send large base64 payloads through the message channel —
|
|
165
|
+
* Chrome reads the files directly from the local filesystem.
|
|
166
|
+
*
|
|
167
|
+
* @param tabId - Target tab ID
|
|
168
|
+
* @param files - Array of absolute local file paths
|
|
169
|
+
* @param selector - CSS selector to find the file input (optional, defaults to first file input)
|
|
170
|
+
*/
|
|
171
|
+
export async function setFileInputFiles(
|
|
172
|
+
tabId: number,
|
|
173
|
+
files: string[],
|
|
174
|
+
selector?: string,
|
|
175
|
+
): Promise<void> {
|
|
176
|
+
await ensureAttached(tabId);
|
|
177
|
+
|
|
178
|
+
// Enable DOM domain (required for DOM.querySelector and DOM.setFileInputFiles)
|
|
179
|
+
await chrome.debugger.sendCommand({ tabId }, 'DOM.enable');
|
|
180
|
+
|
|
181
|
+
// Get the document root
|
|
182
|
+
const doc = await chrome.debugger.sendCommand({ tabId }, 'DOM.getDocument') as {
|
|
183
|
+
root: { nodeId: number };
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Find the file input element
|
|
187
|
+
const query = selector || 'input[type="file"]';
|
|
188
|
+
const result = await chrome.debugger.sendCommand({ tabId }, 'DOM.querySelector', {
|
|
189
|
+
nodeId: doc.root.nodeId,
|
|
190
|
+
selector: query,
|
|
191
|
+
}) as { nodeId: number };
|
|
192
|
+
|
|
193
|
+
if (!result.nodeId) {
|
|
194
|
+
throw new Error(`No element found matching selector: ${query}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Set files directly via CDP — Chrome reads from local filesystem
|
|
198
|
+
await chrome.debugger.sendCommand({ tabId }, 'DOM.setFileInputFiles', {
|
|
199
|
+
files,
|
|
200
|
+
nodeId: result.nodeId,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
150
204
|
export async function detach(tabId: number): Promise<void> {
|
|
151
205
|
if (!attached.has(tabId)) return;
|
|
152
206
|
attached.delete(tabId);
|
|
@@ -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';
|
|
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,12 +26,20 @@ 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 */
|
|
32
36
|
quality?: number;
|
|
33
37
|
/** Whether to capture full page (not just viewport) */
|
|
34
38
|
fullPage?: boolean;
|
|
39
|
+
/** Local file paths for set-file-input action */
|
|
40
|
+
files?: string[];
|
|
41
|
+
/** CSS selector for file input element (set-file-input action) */
|
|
42
|
+
selector?: string;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
export interface Result {
|
|
@@ -54,7 +62,5 @@ export const DAEMON_PING_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}/ping`;
|
|
|
54
62
|
|
|
55
63
|
/** Base reconnect delay for extension WebSocket (ms) */
|
|
56
64
|
export const WS_RECONNECT_BASE_DELAY = 2000;
|
|
57
|
-
/** Max reconnect delay (ms) */
|
|
58
|
-
export const WS_RECONNECT_MAX_DELAY =
|
|
59
|
-
/** Idle timeout before daemon auto-exits (ms) */
|
|
60
|
-
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/scripts/postinstall.js
CHANGED
|
@@ -196,6 +196,22 @@ function main() {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
// ── Spotify credentials template ────────────────────────────────────
|
|
200
|
+
const opencliDir = join(home, '.opencli');
|
|
201
|
+
const spotifyEnvFile = join(opencliDir, 'spotify.env');
|
|
202
|
+
ensureDir(opencliDir);
|
|
203
|
+
if (!existsSync(spotifyEnvFile)) {
|
|
204
|
+
writeFileSync(spotifyEnvFile,
|
|
205
|
+
`# Spotify credentials — get them at https://developer.spotify.com/dashboard\n` +
|
|
206
|
+
`# Add http://127.0.0.1:8888/callback as a Redirect URI in your Spotify app\n` +
|
|
207
|
+
`SPOTIFY_CLIENT_ID=your_spotify_client_id_here\n` +
|
|
208
|
+
`SPOTIFY_CLIENT_SECRET=your_spotify_client_secret_here\n`,
|
|
209
|
+
'utf8'
|
|
210
|
+
);
|
|
211
|
+
console.log(`✓ Spotify credentials template created at ${spotifyEnvFile}`);
|
|
212
|
+
console.log(` Edit the file and add your Client ID and Secret, then run: opencli spotify auth`);
|
|
213
|
+
}
|
|
214
|
+
|
|
199
215
|
// ── Browser Bridge setup hint ───────────────────────────────────────
|
|
200
216
|
console.log('');
|
|
201
217
|
console.log(' \x1b[1mNext step — Browser Bridge setup\x1b[0m');
|