@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
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI commands for daemon lifecycle management:
|
|
3
|
+
* opencli daemon status — show daemon state
|
|
4
|
+
* opencli daemon stop — graceful shutdown
|
|
5
|
+
* opencli daemon restart — stop + respawn
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
10
|
+
|
|
11
|
+
const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
|
|
12
|
+
const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
|
|
13
|
+
|
|
14
|
+
interface DaemonStatus {
|
|
15
|
+
ok: boolean;
|
|
16
|
+
pid: number;
|
|
17
|
+
uptime: number;
|
|
18
|
+
extensionConnected: boolean;
|
|
19
|
+
pending: number;
|
|
20
|
+
lastCliRequestTime: number;
|
|
21
|
+
memoryMB: number;
|
|
22
|
+
port: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function fetchStatus(): Promise<DaemonStatus | null> {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const timer = setTimeout(() => controller.abort(), 2000);
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(`${DAEMON_URL}/status`, {
|
|
30
|
+
headers: { 'X-OpenCLI': '1' },
|
|
31
|
+
signal: controller.signal,
|
|
32
|
+
});
|
|
33
|
+
if (!res.ok) return null;
|
|
34
|
+
return await res.json() as DaemonStatus;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
} finally {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function requestShutdown(): Promise<boolean> {
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const timer = setTimeout(() => controller.abort(), 5000);
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetch(`${DAEMON_URL}/shutdown`, {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
headers: { 'X-OpenCLI': '1' },
|
|
49
|
+
signal: controller.signal,
|
|
50
|
+
});
|
|
51
|
+
return res.ok;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
} finally {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function formatUptime(seconds: number): string {
|
|
60
|
+
const h = Math.floor(seconds / 3600);
|
|
61
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
62
|
+
if (h > 0) return `${h}h ${m}m`;
|
|
63
|
+
if (m > 0) return `${m}m`;
|
|
64
|
+
return `${Math.floor(seconds)}s`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function formatTimeSince(timestampMs: number): string {
|
|
68
|
+
const seconds = (Date.now() - timestampMs) / 1000;
|
|
69
|
+
if (seconds < 60) return `${Math.floor(seconds)}s ago`;
|
|
70
|
+
const m = Math.floor(seconds / 60);
|
|
71
|
+
if (m < 60) return `${m} min ago`;
|
|
72
|
+
const h = Math.floor(m / 60);
|
|
73
|
+
return `${h}h ${m % 60}m ago`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function daemonStatus(): Promise<void> {
|
|
77
|
+
const status = await fetchStatus();
|
|
78
|
+
if (!status) {
|
|
79
|
+
console.log(`Daemon: ${chalk.dim('not running')}`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log(`Daemon: ${chalk.green('running')} (PID ${status.pid})`);
|
|
84
|
+
console.log(`Uptime: ${formatUptime(status.uptime)}`);
|
|
85
|
+
console.log(`Extension: ${status.extensionConnected ? chalk.green('connected') : chalk.yellow('disconnected')}`);
|
|
86
|
+
console.log(`Last CLI request: ${formatTimeSince(status.lastCliRequestTime)}`);
|
|
87
|
+
console.log(`Memory: ${status.memoryMB} MB`);
|
|
88
|
+
console.log(`Port: ${status.port}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function daemonStop(): Promise<void> {
|
|
92
|
+
const status = await fetchStatus();
|
|
93
|
+
if (!status) {
|
|
94
|
+
console.log(chalk.dim('Daemon is not running.'));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const ok = await requestShutdown();
|
|
99
|
+
if (ok) {
|
|
100
|
+
console.log(chalk.green('Daemon stopped.'));
|
|
101
|
+
} else {
|
|
102
|
+
console.error(chalk.red('Failed to stop daemon.'));
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function daemonRestart(): Promise<void> {
|
|
108
|
+
const status = await fetchStatus();
|
|
109
|
+
if (status) {
|
|
110
|
+
const ok = await requestShutdown();
|
|
111
|
+
if (!ok) {
|
|
112
|
+
console.error(chalk.red('Failed to stop daemon.'));
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Wait for daemon to actually exit (poll until unreachable)
|
|
117
|
+
const deadline = Date.now() + 5000;
|
|
118
|
+
while (Date.now() < deadline) {
|
|
119
|
+
await new Promise(r => setTimeout(r, 200));
|
|
120
|
+
if (!(await fetchStatus())) break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Import BrowserBridge to spawn a new daemon
|
|
125
|
+
const { BrowserBridge } = await import('../browser/mcp.js');
|
|
126
|
+
const bridge = new BrowserBridge();
|
|
127
|
+
try {
|
|
128
|
+
console.log('Starting daemon...');
|
|
129
|
+
await bridge.connect({ timeout: 10 });
|
|
130
|
+
console.log(chalk.green('Daemon restarted.'));
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error(chalk.red(`Failed to restart daemon: ${err instanceof Error ? err.message : err}`));
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
}
|
|
135
|
+
}
|
package/src/completion.ts
CHANGED
|
@@ -57,9 +57,10 @@ export function getCompletions(words: string[], cursor: number): string[] {
|
|
|
57
57
|
for (const [, cmd] of getRegistry()) {
|
|
58
58
|
if (cmd.site === site) {
|
|
59
59
|
subcommands.push(cmd.name);
|
|
60
|
+
if (cmd.aliases?.length) subcommands.push(...cmd.aliases);
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
|
-
return subcommands.sort();
|
|
63
|
+
return [...new Set(subcommands)].sort();
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
// cursor >= 3 → no further completion
|
package/src/constants.ts
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
/** Default daemon port for HTTP/WebSocket communication with browser extension */
|
|
6
6
|
export const DEFAULT_DAEMON_PORT = 19825;
|
|
7
7
|
|
|
8
|
+
/** Default idle timeout before daemon auto-exits (ms). Override via OPENCLI_DAEMON_TIMEOUT env var. */
|
|
9
|
+
export const DEFAULT_DAEMON_IDLE_TIMEOUT = 4 * 60 * 60 * 1000; // 4 hours
|
|
10
|
+
|
|
8
11
|
/** URL query params that are volatile/ephemeral and should be stripped from patterns */
|
|
9
12
|
export const VOLATILE_PARAMS = new Set([
|
|
10
13
|
'w_rid', 'wts', '_', 'callback', 'timestamp', 't', 'nonce', 'sign',
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { IdleManager } from './idle-manager.js';
|
|
3
|
+
|
|
4
|
+
describe('IdleManager', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.useFakeTimers();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.useRealTimers();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('does not start timer when extension is connected', () => {
|
|
14
|
+
const exit = vi.fn();
|
|
15
|
+
const mgr = new IdleManager(300_000, exit);
|
|
16
|
+
|
|
17
|
+
mgr.setExtensionConnected(true);
|
|
18
|
+
mgr.onCliRequest();
|
|
19
|
+
|
|
20
|
+
vi.advanceTimersByTime(300_000 + 1000);
|
|
21
|
+
expect(exit).not.toHaveBeenCalled();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('starts timer when extension disconnects and CLI is idle', () => {
|
|
25
|
+
const exit = vi.fn();
|
|
26
|
+
const mgr = new IdleManager(300_000, exit);
|
|
27
|
+
|
|
28
|
+
mgr.onCliRequest();
|
|
29
|
+
mgr.setExtensionConnected(true);
|
|
30
|
+
mgr.setExtensionConnected(false);
|
|
31
|
+
|
|
32
|
+
expect(exit).not.toHaveBeenCalled();
|
|
33
|
+
|
|
34
|
+
vi.advanceTimersByTime(300_000 + 1000);
|
|
35
|
+
expect(exit).toHaveBeenCalledTimes(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('exits immediately on extension disconnect if CLI has been idle past timeout', () => {
|
|
39
|
+
const exit = vi.fn();
|
|
40
|
+
const mgr = new IdleManager(300_000, exit);
|
|
41
|
+
|
|
42
|
+
mgr.onCliRequest();
|
|
43
|
+
mgr.setExtensionConnected(true); // connect before timeout elapses
|
|
44
|
+
vi.advanceTimersByTime(400_000); // CLI idle time exceeds timeout, but extension is connected so no exit
|
|
45
|
+
|
|
46
|
+
expect(exit).not.toHaveBeenCalled();
|
|
47
|
+
|
|
48
|
+
mgr.setExtensionConnected(false); // disconnect → should exit immediately since CLI idle > timeout
|
|
49
|
+
|
|
50
|
+
expect(exit).toHaveBeenCalledTimes(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('resets timer on new CLI request', () => {
|
|
54
|
+
const exit = vi.fn();
|
|
55
|
+
const mgr = new IdleManager(300_000, exit);
|
|
56
|
+
|
|
57
|
+
mgr.onCliRequest();
|
|
58
|
+
vi.advanceTimersByTime(200_000);
|
|
59
|
+
mgr.onCliRequest();
|
|
60
|
+
|
|
61
|
+
vi.advanceTimersByTime(200_000);
|
|
62
|
+
expect(exit).not.toHaveBeenCalled();
|
|
63
|
+
|
|
64
|
+
vi.advanceTimersByTime(100_001);
|
|
65
|
+
expect(exit).toHaveBeenCalledTimes(1);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('does not exit when timeout is 0 (disabled)', () => {
|
|
69
|
+
const exit = vi.fn();
|
|
70
|
+
const mgr = new IdleManager(0, exit);
|
|
71
|
+
|
|
72
|
+
mgr.onCliRequest();
|
|
73
|
+
vi.advanceTimersByTime(24 * 60 * 60 * 1000);
|
|
74
|
+
expect(exit).not.toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('clears timer when extension connects', () => {
|
|
78
|
+
const exit = vi.fn();
|
|
79
|
+
const mgr = new IdleManager(300_000, exit);
|
|
80
|
+
|
|
81
|
+
mgr.onCliRequest();
|
|
82
|
+
vi.advanceTimersByTime(200_000);
|
|
83
|
+
|
|
84
|
+
mgr.setExtensionConnected(true);
|
|
85
|
+
vi.advanceTimersByTime(200_000);
|
|
86
|
+
expect(exit).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
});
|
package/src/daemon.ts
CHANGED
|
@@ -15,17 +15,18 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Lifecycle:
|
|
17
17
|
* - Auto-spawned by opencli on first browser command
|
|
18
|
-
* - Auto-exits after
|
|
18
|
+
* - Auto-exits after idle timeout (default 4h, configurable via OPENCLI_DAEMON_TIMEOUT)
|
|
19
19
|
* - Listens on localhost:19825
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
23
23
|
import { WebSocketServer, WebSocket, type RawData } from 'ws';
|
|
24
|
-
import { DEFAULT_DAEMON_PORT } from './constants.js';
|
|
24
|
+
import { DEFAULT_DAEMON_PORT, DEFAULT_DAEMON_IDLE_TIMEOUT } from './constants.js';
|
|
25
25
|
import { EXIT_CODES } from './errors.js';
|
|
26
|
+
import { IdleManager } from './idle-manager.js';
|
|
26
27
|
|
|
27
28
|
const PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
|
|
28
|
-
const IDLE_TIMEOUT =
|
|
29
|
+
const IDLE_TIMEOUT = Number(process.env.OPENCLI_DAEMON_TIMEOUT ?? DEFAULT_DAEMON_IDLE_TIMEOUT);
|
|
29
30
|
|
|
30
31
|
// ─── State ───────────────────────────────────────────────────────────
|
|
31
32
|
|
|
@@ -36,8 +37,6 @@ const pending = new Map<string, {
|
|
|
36
37
|
reject: (error: Error) => void;
|
|
37
38
|
timer: ReturnType<typeof setTimeout>;
|
|
38
39
|
}>();
|
|
39
|
-
let idleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
40
|
-
|
|
41
40
|
// Extension log ring buffer
|
|
42
41
|
interface LogEntry { level: string; msg: string; ts: number; }
|
|
43
42
|
const LOG_BUFFER_SIZE = 200;
|
|
@@ -50,13 +49,10 @@ function pushLog(entry: LogEntry): void {
|
|
|
50
49
|
|
|
51
50
|
// ─── Idle auto-exit ──────────────────────────────────────────────────
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
process.exit(EXIT_CODES.SUCCESS);
|
|
58
|
-
}, IDLE_TIMEOUT);
|
|
59
|
-
}
|
|
52
|
+
const idleManager = new IdleManager(IDLE_TIMEOUT, () => {
|
|
53
|
+
console.error('[daemon] Idle timeout (no CLI requests + no Extension), shutting down');
|
|
54
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
55
|
+
});
|
|
60
56
|
|
|
61
57
|
// ─── HTTP Server ─────────────────────────────────────────────────────
|
|
62
58
|
|
|
@@ -128,11 +124,18 @@ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise
|
|
|
128
124
|
}
|
|
129
125
|
|
|
130
126
|
if (req.method === 'GET' && pathname === '/status') {
|
|
127
|
+
const uptime = process.uptime();
|
|
128
|
+
const mem = process.memoryUsage();
|
|
131
129
|
jsonResponse(res, 200, {
|
|
132
130
|
ok: true,
|
|
131
|
+
pid: process.pid,
|
|
132
|
+
uptime,
|
|
133
133
|
extensionConnected: extensionWs?.readyState === WebSocket.OPEN,
|
|
134
134
|
extensionVersion,
|
|
135
135
|
pending: pending.size,
|
|
136
|
+
lastCliRequestTime: idleManager.lastCliRequestTime,
|
|
137
|
+
memoryMB: Math.round(mem.rss / 1024 / 1024 * 10) / 10,
|
|
138
|
+
port: PORT,
|
|
136
139
|
});
|
|
137
140
|
return;
|
|
138
141
|
}
|
|
@@ -153,8 +156,14 @@ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise
|
|
|
153
156
|
return;
|
|
154
157
|
}
|
|
155
158
|
|
|
159
|
+
if (req.method === 'POST' && pathname === '/shutdown') {
|
|
160
|
+
jsonResponse(res, 200, { ok: true, message: 'Shutting down' });
|
|
161
|
+
setTimeout(() => shutdown(), 100);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
156
165
|
if (req.method === 'POST' && url === '/command') {
|
|
157
|
-
|
|
166
|
+
idleManager.onCliRequest();
|
|
158
167
|
try {
|
|
159
168
|
const body = JSON.parse(await readBody(req));
|
|
160
169
|
if (!body.id) {
|
|
@@ -212,6 +221,7 @@ wss.on('connection', (ws: WebSocket) => {
|
|
|
212
221
|
console.error('[daemon] Extension connected');
|
|
213
222
|
extensionWs = ws;
|
|
214
223
|
extensionVersion = null; // cleared until hello message arrives
|
|
224
|
+
idleManager.setExtensionConnected(true);
|
|
215
225
|
|
|
216
226
|
// ── Heartbeat: ping every 15s, close if 2 pongs missed ──
|
|
217
227
|
let missedPongs = 0;
|
|
@@ -270,6 +280,7 @@ wss.on('connection', (ws: WebSocket) => {
|
|
|
270
280
|
if (extensionWs === ws) {
|
|
271
281
|
extensionWs = null;
|
|
272
282
|
extensionVersion = null;
|
|
283
|
+
idleManager.setExtensionConnected(false);
|
|
273
284
|
// Reject all pending requests since the extension is gone
|
|
274
285
|
for (const [id, p] of pending) {
|
|
275
286
|
clearTimeout(p.timer);
|
|
@@ -284,6 +295,7 @@ wss.on('connection', (ws: WebSocket) => {
|
|
|
284
295
|
if (extensionWs === ws) {
|
|
285
296
|
extensionWs = null;
|
|
286
297
|
extensionVersion = null;
|
|
298
|
+
idleManager.setExtensionConnected(false);
|
|
287
299
|
// Reject pending requests in case 'close' does not follow this 'error'
|
|
288
300
|
for (const [, p] of pending) {
|
|
289
301
|
clearTimeout(p.timer);
|
|
@@ -298,7 +310,7 @@ wss.on('connection', (ws: WebSocket) => {
|
|
|
298
310
|
|
|
299
311
|
httpServer.listen(PORT, '127.0.0.1', () => {
|
|
300
312
|
console.error(`[daemon] Listening on http://127.0.0.1:${PORT}`);
|
|
301
|
-
|
|
313
|
+
idleManager.onCliRequest();
|
|
302
314
|
});
|
|
303
315
|
|
|
304
316
|
httpServer.on('error', (err: NodeJS.ErrnoException) => {
|
package/src/discovery.ts
CHANGED
|
@@ -11,15 +11,19 @@
|
|
|
11
11
|
import * as fs from 'node:fs';
|
|
12
12
|
import * as os from 'node:os';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
|
-
import { pathToFileURL } from 'node:url';
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
15
15
|
import yaml from 'js-yaml';
|
|
16
16
|
import { type CliCommand, type InternalCliCommand, type Arg, Strategy, registerCommand } from './registry.js';
|
|
17
17
|
import { getErrorMessage } from './errors.js';
|
|
18
18
|
import { log } from './logger.js';
|
|
19
19
|
import type { ManifestEntry } from './build-manifest.js';
|
|
20
20
|
|
|
21
|
+
/** User runtime directory: ~/.opencli */
|
|
22
|
+
export const USER_OPENCLI_DIR = path.join(os.homedir(), '.opencli');
|
|
23
|
+
/** User CLIs directory: ~/.opencli/clis */
|
|
24
|
+
export const USER_CLIS_DIR = path.join(USER_OPENCLI_DIR, 'clis');
|
|
21
25
|
/** Plugins directory: ~/.opencli/plugins/ */
|
|
22
|
-
export const PLUGINS_DIR = path.join(
|
|
26
|
+
export const PLUGINS_DIR = path.join(USER_OPENCLI_DIR, 'plugins');
|
|
23
27
|
/** Matches files that register commands via cli() or lifecycle hooks */
|
|
24
28
|
const PLUGIN_MODULE_PATTERN = /\b(?:cli|onStartup|onBeforeExecute|onAfterExecute)\s*\(/;
|
|
25
29
|
|
|
@@ -33,6 +37,47 @@ function parseStrategy(rawStrategy: string | undefined, fallback: Strategy = Str
|
|
|
33
37
|
|
|
34
38
|
import { isRecord } from './utils.js';
|
|
35
39
|
|
|
40
|
+
function resolveHostRuntimeModulePath(moduleName: string): string {
|
|
41
|
+
const runtimeDir = path.dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
for (const ext of ['.js', '.ts']) {
|
|
43
|
+
const candidate = path.join(runtimeDir, `${moduleName}${ext}`);
|
|
44
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
45
|
+
}
|
|
46
|
+
return path.join(runtimeDir, `${moduleName}.js`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function writeCompatShimIfNeeded(filePath: string, content: string): Promise<void> {
|
|
50
|
+
try {
|
|
51
|
+
const existing = await fs.promises.readFile(filePath, 'utf-8');
|
|
52
|
+
if (existing === content) return;
|
|
53
|
+
} catch {
|
|
54
|
+
// Fall through to write missing shim
|
|
55
|
+
}
|
|
56
|
+
await fs.promises.writeFile(filePath, content, 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create runtime shim files under ~/.opencli so legacy user TS CLIs can keep
|
|
61
|
+
* importing ../../registry(.js) and ../../errors(.js).
|
|
62
|
+
*/
|
|
63
|
+
export async function ensureUserCliCompatShims(baseDir: string = USER_OPENCLI_DIR): Promise<void> {
|
|
64
|
+
await fs.promises.mkdir(baseDir, { recursive: true });
|
|
65
|
+
|
|
66
|
+
const registryUrl = pathToFileURL(resolveHostRuntimeModulePath('registry-api')).href;
|
|
67
|
+
const errorsUrl = pathToFileURL(resolveHostRuntimeModulePath('errors')).href;
|
|
68
|
+
|
|
69
|
+
await Promise.all([
|
|
70
|
+
writeCompatShimIfNeeded(path.join(baseDir, 'registry'), `export * from '${registryUrl}';\n`),
|
|
71
|
+
writeCompatShimIfNeeded(path.join(baseDir, 'registry.js'), `export * from '${registryUrl}';\n`),
|
|
72
|
+
writeCompatShimIfNeeded(path.join(baseDir, 'errors'), `export * from '${errorsUrl}';\n`),
|
|
73
|
+
writeCompatShimIfNeeded(path.join(baseDir, 'errors.js'), `export * from '${errorsUrl}';\n`),
|
|
74
|
+
writeCompatShimIfNeeded(
|
|
75
|
+
path.join(baseDir, 'package.json'),
|
|
76
|
+
`${JSON.stringify({ name: 'opencli-user-runtime', private: true, type: 'module' }, null, 2)}\n`,
|
|
77
|
+
),
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
|
|
36
81
|
/**
|
|
37
82
|
* Discover and register CLI commands.
|
|
38
83
|
* Uses pre-compiled manifest when available for instant startup.
|
|
@@ -68,6 +113,7 @@ async function loadFromManifest(manifestPath: string, clisDir: string): Promise<
|
|
|
68
113
|
const cmd: CliCommand = {
|
|
69
114
|
site: entry.site,
|
|
70
115
|
name: entry.name,
|
|
116
|
+
aliases: entry.aliases,
|
|
71
117
|
description: entry.description ?? '',
|
|
72
118
|
domain: entry.domain,
|
|
73
119
|
strategy,
|
|
@@ -90,6 +136,7 @@ async function loadFromManifest(manifestPath: string, clisDir: string): Promise<
|
|
|
90
136
|
const cmd: InternalCliCommand = {
|
|
91
137
|
site: entry.site,
|
|
92
138
|
name: entry.name,
|
|
139
|
+
aliases: entry.aliases,
|
|
93
140
|
description: entry.description ?? '',
|
|
94
141
|
domain: entry.domain,
|
|
95
142
|
strategy,
|
|
@@ -163,6 +210,9 @@ async function registerYamlCli(filePath: string, defaultSite: string): Promise<v
|
|
|
163
210
|
const cmd: CliCommand = {
|
|
164
211
|
site,
|
|
165
212
|
name,
|
|
213
|
+
aliases: isRecord(cliDef) && Array.isArray((cliDef as Record<string, unknown>).aliases)
|
|
214
|
+
? ((cliDef as Record<string, unknown>).aliases as unknown[]).filter((value): value is string => typeof value === 'string')
|
|
215
|
+
: undefined,
|
|
166
216
|
description: cliDef.description ?? '',
|
|
167
217
|
domain: cliDef.domain,
|
|
168
218
|
strategy,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getElectronApp, isElectronApp, loadApps } from './electron-apps.js';
|
|
3
|
+
|
|
4
|
+
describe('electron-apps registry', () => {
|
|
5
|
+
it('returns builtin app entry for cursor', () => {
|
|
6
|
+
const app = getElectronApp('cursor');
|
|
7
|
+
expect(app).toBeDefined();
|
|
8
|
+
expect(app!.port).toBe(9226);
|
|
9
|
+
expect(app!.processName).toBe('Cursor');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('returns builtin app entry for codex', () => {
|
|
13
|
+
const app = getElectronApp('codex');
|
|
14
|
+
expect(app).toBeDefined();
|
|
15
|
+
expect(app!.port).toBe(9222);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('returns undefined for non-Electron sites', () => {
|
|
19
|
+
expect(getElectronApp('bilibili')).toBeUndefined();
|
|
20
|
+
expect(getElectronApp('hackernews')).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('isElectronApp returns true for registered apps', () => {
|
|
24
|
+
expect(isElectronApp('cursor')).toBe(true);
|
|
25
|
+
expect(isElectronApp('codex')).toBe(true);
|
|
26
|
+
expect(isElectronApp('chatwise')).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('isElectronApp returns false for non-Electron sites', () => {
|
|
30
|
+
expect(isElectronApp('bilibili')).toBe(false);
|
|
31
|
+
expect(isElectronApp('unknown-app')).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('loadApps merges user config additively', () => {
|
|
35
|
+
const apps = loadApps({
|
|
36
|
+
myapp: { port: 9234, processName: 'MyApp' },
|
|
37
|
+
});
|
|
38
|
+
expect(apps.myapp).toBeDefined();
|
|
39
|
+
expect(apps.myapp.port).toBe(9234);
|
|
40
|
+
// Builtins still present
|
|
41
|
+
expect(apps.cursor).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('loadApps does not override builtin entries', () => {
|
|
45
|
+
const apps = loadApps({
|
|
46
|
+
cursor: { port: 9999, processName: 'FakeCursor' },
|
|
47
|
+
});
|
|
48
|
+
expect(apps.cursor.port).toBe(9226); // Builtin wins
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Electron app registry — maps site names to launch metadata.
|
|
3
|
+
*
|
|
4
|
+
* Builtin apps are defined here. User-defined apps are loaded
|
|
5
|
+
* from ~/.opencli/apps.yaml (additive only, does not override builtins).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import * as os from 'node:os';
|
|
11
|
+
import yaml from 'js-yaml';
|
|
12
|
+
|
|
13
|
+
export interface ElectronAppEntry {
|
|
14
|
+
/** CDP debug port (unique per app) */
|
|
15
|
+
port: number;
|
|
16
|
+
/** macOS process name for detection via pgrep */
|
|
17
|
+
processName: string;
|
|
18
|
+
/** macOS bundle ID for path discovery */
|
|
19
|
+
bundleId?: string;
|
|
20
|
+
/** Human-readable name for prompts */
|
|
21
|
+
displayName?: string;
|
|
22
|
+
/** Additional launch args beyond --remote-debugging-port */
|
|
23
|
+
extraArgs?: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const builtinApps: Record<string, ElectronAppEntry> = {
|
|
27
|
+
cursor: { port: 9226, processName: 'Cursor', bundleId: 'com.todesktop.runtime.Cursor', displayName: 'Cursor' },
|
|
28
|
+
codex: { port: 9222, processName: 'Codex', bundleId: 'com.openai.codex', displayName: 'Codex' },
|
|
29
|
+
chatwise: { port: 9228, processName: 'ChatWise', bundleId: 'com.chatwise.app', displayName: 'ChatWise' },
|
|
30
|
+
notion: { port: 9230, processName: 'Notion', bundleId: 'notion.id', displayName: 'Notion' },
|
|
31
|
+
'discord-app': { port: 9232, processName: 'Discord', bundleId: 'com.discord.app', displayName: 'Discord' },
|
|
32
|
+
'doubao-app': { port: 9225, processName: 'Doubao', bundleId: 'com.volcengine.doubao', displayName: 'Doubao' },
|
|
33
|
+
antigravity: { port: 9234, processName: 'Antigravity', bundleId: 'dev.antigravity.app', displayName: 'Antigravity' },
|
|
34
|
+
chatgpt: { port: 9236, processName: 'ChatGPT', bundleId: 'com.openai.chat', displayName: 'ChatGPT' },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Merge builtin + user-defined apps. User entries are additive only. */
|
|
38
|
+
export function loadApps(
|
|
39
|
+
userApps?: Record<string, Omit<ElectronAppEntry, 'displayName'> & { displayName?: string }>,
|
|
40
|
+
): Record<string, ElectronAppEntry> {
|
|
41
|
+
const merged = { ...builtinApps };
|
|
42
|
+
if (userApps) {
|
|
43
|
+
for (const [name, entry] of Object.entries(userApps)) {
|
|
44
|
+
if (!(name in merged)) {
|
|
45
|
+
merged[name] = entry as ElectronAppEntry;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return merged;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let _apps: Record<string, ElectronAppEntry> | null = null;
|
|
53
|
+
|
|
54
|
+
function ensureLoaded(): Record<string, ElectronAppEntry> {
|
|
55
|
+
if (_apps) return _apps;
|
|
56
|
+
|
|
57
|
+
let userApps: Record<string, ElectronAppEntry> | undefined;
|
|
58
|
+
try {
|
|
59
|
+
const yamlPath = path.join(os.homedir(), '.opencli', 'apps.yaml');
|
|
60
|
+
if (fs.existsSync(yamlPath)) {
|
|
61
|
+
const content = fs.readFileSync(yamlPath, 'utf-8');
|
|
62
|
+
const parsed = yaml.load(content) as { apps?: Record<string, ElectronAppEntry> };
|
|
63
|
+
userApps = parsed?.apps;
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Silently ignore malformed user config
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_apps = loadApps(userApps);
|
|
70
|
+
return _apps;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getElectronApp(site: string): ElectronAppEntry | undefined {
|
|
74
|
+
return ensureLoaded()[site];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isElectronApp(site: string): boolean {
|
|
78
|
+
return site in ensureLoaded();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Get all registered apps (builtin + user-defined). */
|
|
82
|
+
export function getAllElectronApps(): Record<string, ElectronAppEntry> {
|
|
83
|
+
return ensureLoaded();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Reset loaded apps (for testing). */
|
|
87
|
+
export function _resetRegistry(): void {
|
|
88
|
+
_apps = null;
|
|
89
|
+
}
|
package/src/engine.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { discoverClis, discoverPlugins, PLUGINS_DIR } from './discovery.js';
|
|
2
|
+
import { discoverClis, discoverPlugins, ensureUserCliCompatShims, PLUGINS_DIR } from './discovery.js';
|
|
3
3
|
import { executeCommand } from './execution.js';
|
|
4
4
|
import { getRegistry, cli, Strategy } from './registry.js';
|
|
5
5
|
import { clearAllHooks, onAfterExecute } from './hooks.js';
|
|
@@ -78,6 +78,39 @@ cli({
|
|
|
78
78
|
await fs.promises.rm(tempBuildRoot, { recursive: true, force: true });
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
|
+
|
|
82
|
+
it('loads legacy user TS CLI modules via compatibility shims', async () => {
|
|
83
|
+
const tempOpencliRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-user-clis-'));
|
|
84
|
+
const userClisDir = path.join(tempOpencliRoot, 'clis');
|
|
85
|
+
const siteDir = path.join(userClisDir, 'legacy-site');
|
|
86
|
+
const commandPath = path.join(siteDir, 'hello.ts');
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await ensureUserCliCompatShims(tempOpencliRoot);
|
|
90
|
+
await fs.promises.mkdir(siteDir, { recursive: true });
|
|
91
|
+
await fs.promises.writeFile(commandPath, `
|
|
92
|
+
import { cli, Strategy } from '../../registry';
|
|
93
|
+
import { CommandExecutionError } from '../../errors';
|
|
94
|
+
|
|
95
|
+
cli({
|
|
96
|
+
site: 'legacy-site',
|
|
97
|
+
name: 'hello',
|
|
98
|
+
description: 'hello command',
|
|
99
|
+
strategy: Strategy.PUBLIC,
|
|
100
|
+
browser: false,
|
|
101
|
+
func: async () => [{ ok: true, errorName: new CommandExecutionError('boom').name }],
|
|
102
|
+
});
|
|
103
|
+
`);
|
|
104
|
+
|
|
105
|
+
await discoverClis(userClisDir);
|
|
106
|
+
|
|
107
|
+
const cmd = getRegistry().get('legacy-site/hello');
|
|
108
|
+
expect(cmd).toBeDefined();
|
|
109
|
+
await expect(executeCommand(cmd!, {})).resolves.toEqual([{ ok: true, errorName: 'CommandExecutionError' }]);
|
|
110
|
+
} finally {
|
|
111
|
+
await fs.promises.rm(tempOpencliRoot, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
81
114
|
});
|
|
82
115
|
|
|
83
116
|
describe('discoverPlugins', () => {
|
|
@@ -266,22 +299,25 @@ describe('executeCommand', () => {
|
|
|
266
299
|
expect(typeof seen[0].finishedAt).toBe('number');
|
|
267
300
|
});
|
|
268
301
|
|
|
269
|
-
it('
|
|
302
|
+
it('uses launcher for registered Electron apps (chatwise)', async () => {
|
|
303
|
+
// Mock the launcher to return a fake endpoint (avoids real HTTP/process calls)
|
|
304
|
+
const launcher = await import('./launcher.js');
|
|
305
|
+
const spy = vi.spyOn(launcher, 'resolveElectronEndpoint')
|
|
306
|
+
.mockResolvedValue('http://127.0.0.1:9228');
|
|
307
|
+
|
|
270
308
|
const cmd = cli({
|
|
271
309
|
site: 'chatwise',
|
|
272
310
|
name: 'status',
|
|
273
311
|
description: 'chatwise status',
|
|
274
312
|
browser: true,
|
|
275
313
|
strategy: Strategy.PUBLIC,
|
|
276
|
-
requiredEnv: [
|
|
277
|
-
{
|
|
278
|
-
name: 'OPENCLI_CDP_ENDPOINT',
|
|
279
|
-
help: 'Set OPENCLI_CDP_ENDPOINT before running chatwise commands.',
|
|
280
|
-
},
|
|
281
|
-
],
|
|
282
314
|
func: async () => [{ ok: true }],
|
|
283
315
|
});
|
|
284
316
|
|
|
285
|
-
|
|
317
|
+
// CDPBridge.connect() will fail (no actual CDP server), but the launcher
|
|
318
|
+
// should have been called with 'chatwise'.
|
|
319
|
+
await expect(executeCommand(cmd, {})).rejects.toThrow();
|
|
320
|
+
expect(spy).toHaveBeenCalledWith('chatwise');
|
|
321
|
+
spy.mockRestore();
|
|
286
322
|
});
|
|
287
323
|
});
|