@jackwener/opencli 1.1.1 → 1.2.1
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/.github/workflows/build-extension.yml +3 -3
- package/.github/workflows/ci.yml +6 -6
- package/.github/workflows/doc-check.yml +3 -3
- package/.github/workflows/e2e-headed.yml +2 -2
- package/.github/workflows/pkg-pr-new.yml +2 -2
- package/.github/workflows/release.yml +3 -3
- package/.github/workflows/security.yml +2 -2
- package/CONTRIBUTING.md +39 -1
- package/README.md +13 -10
- package/README.zh-CN.md +43 -17
- package/SKILL.md +10 -5
- package/dist/browser/cdp.d.ts +4 -4
- package/dist/browser/cdp.js +39 -16
- package/dist/browser/daemon-client.d.ts +4 -2
- package/dist/browser/daemon-client.js +17 -4
- package/dist/browser/dom-helpers.js +38 -7
- package/dist/browser/dom-snapshot.d.ts +86 -0
- package/dist/browser/dom-snapshot.js +729 -0
- package/dist/browser/dom-snapshot.test.d.ts +11 -0
- package/dist/browser/dom-snapshot.test.js +212 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/mcp.js +3 -1
- package/dist/browser/page.d.ts +14 -24
- package/dist/browser/page.js +46 -6
- package/dist/build-manifest.d.ts +11 -4
- package/dist/build-manifest.js +59 -21
- package/dist/build-manifest.test.js +58 -2
- package/dist/cli-manifest.json +3856 -1509
- package/dist/cli.js +66 -0
- package/dist/clis/barchart/greeks.js +1 -1
- package/dist/clis/barchart/options.js +1 -1
- package/dist/clis/barchart/quote.js +1 -1
- package/dist/clis/bilibili/download.js +1 -1
- package/dist/clis/bilibili/following.js +1 -1
- package/dist/clis/bilibili/subtitle.js +1 -1
- package/dist/clis/bilibili/user-videos.js +1 -1
- package/dist/clis/boss/batchgreet.js +10 -97
- package/dist/clis/boss/chatlist.js +8 -25
- package/dist/clis/boss/chatmsg.js +11 -42
- package/dist/clis/boss/common.d.ts +92 -0
- package/dist/clis/boss/common.js +223 -0
- package/dist/clis/boss/detail.js +7 -49
- package/dist/clis/boss/exchange.js +13 -79
- package/dist/clis/boss/greet.js +18 -145
- package/dist/clis/boss/invite.js +26 -121
- package/dist/clis/boss/joblist.js +6 -31
- package/dist/clis/boss/mark.js +12 -85
- package/dist/clis/boss/recommend.js +10 -49
- package/dist/clis/boss/resume.js +18 -118
- package/dist/clis/boss/search.js +12 -60
- package/dist/clis/boss/send.js +17 -151
- package/dist/clis/boss/stats.js +18 -69
- package/dist/clis/coupang/add-to-cart.js +1 -1
- package/dist/clis/devto/tag.yaml +34 -0
- package/dist/clis/devto/top.yaml +29 -0
- package/dist/clis/devto/user.yaml +33 -0
- package/dist/clis/douban/book-hot.d.ts +1 -0
- package/dist/clis/douban/book-hot.js +14 -0
- package/dist/clis/douban/marks.d.ts +1 -0
- package/dist/clis/douban/marks.js +115 -0
- package/dist/clis/douban/movie-hot.d.ts +1 -0
- package/dist/clis/douban/movie-hot.js +14 -0
- package/dist/clis/douban/reviews.d.ts +1 -0
- package/dist/clis/douban/reviews.js +106 -0
- package/dist/clis/douban/search.d.ts +1 -0
- package/dist/clis/douban/search.js +16 -0
- package/dist/clis/douban/shared.d.ts +4 -0
- package/dist/clis/douban/shared.js +155 -0
- package/dist/clis/douban/subject.yaml +76 -0
- package/dist/clis/douban/top250.yaml +70 -0
- package/dist/clis/douban/utils.d.ts +35 -0
- package/dist/clis/douban/utils.js +48 -0
- package/dist/clis/facebook/add-friend.yaml +43 -0
- package/dist/clis/facebook/events.yaml +44 -0
- package/dist/clis/facebook/feed.yaml +63 -0
- package/dist/clis/facebook/friends.yaml +42 -0
- package/dist/clis/facebook/groups.yaml +50 -0
- package/dist/clis/facebook/join-group.yaml +44 -0
- package/dist/clis/facebook/memories.yaml +39 -0
- package/dist/clis/facebook/notifications.yaml +40 -0
- package/dist/clis/facebook/profile.yaml +37 -0
- package/dist/clis/facebook/search.yaml +46 -0
- package/dist/clis/google/news.d.ts +5 -0
- package/dist/clis/google/news.js +58 -0
- package/dist/clis/google/search.d.ts +10 -0
- package/dist/clis/google/search.js +127 -0
- package/dist/clis/google/suggest.d.ts +5 -0
- package/dist/clis/google/suggest.js +34 -0
- package/dist/clis/google/trends.d.ts +5 -0
- package/dist/clis/google/trends.js +38 -0
- package/dist/clis/google/utils.d.ts +9 -0
- package/dist/clis/google/utils.js +23 -0
- package/dist/clis/google/utils.test.d.ts +1 -0
- package/dist/clis/google/utils.test.js +75 -0
- package/dist/clis/grok/ask.d.ts +14 -0
- package/dist/clis/grok/ask.js +257 -65
- package/dist/clis/grok/ask.test.d.ts +1 -0
- package/dist/clis/grok/ask.test.js +36 -0
- package/dist/clis/instagram/comment.yaml +52 -0
- package/dist/clis/instagram/explore.yaml +43 -0
- package/dist/clis/instagram/follow.yaml +41 -0
- package/dist/clis/instagram/followers.yaml +51 -0
- package/dist/clis/instagram/following.yaml +51 -0
- package/dist/clis/instagram/like.yaml +46 -0
- package/dist/clis/instagram/profile.yaml +42 -0
- package/dist/clis/instagram/save.yaml +46 -0
- package/dist/clis/instagram/saved.yaml +40 -0
- package/dist/clis/instagram/search.yaml +43 -0
- package/dist/clis/instagram/unfollow.yaml +38 -0
- package/dist/clis/instagram/unlike.yaml +46 -0
- package/dist/clis/instagram/unsave.yaml +46 -0
- package/dist/clis/instagram/user.yaml +54 -0
- package/dist/clis/jike/repost.js +1 -1
- package/dist/clis/jimeng/generate.yaml +1 -0
- package/dist/clis/linux-do/category.yaml +1 -0
- package/dist/clis/lobsters/active.yaml +29 -0
- package/dist/clis/lobsters/hot.yaml +29 -0
- package/dist/clis/lobsters/newest.yaml +29 -0
- package/dist/clis/lobsters/tag.yaml +34 -0
- package/dist/clis/medium/feed.d.ts +1 -0
- package/dist/clis/medium/feed.js +15 -0
- package/dist/clis/medium/search.d.ts +1 -0
- package/dist/clis/medium/search.js +15 -0
- package/dist/clis/medium/shared.d.ts +5 -0
- package/dist/clis/medium/shared.js +78 -0
- package/dist/clis/medium/user.d.ts +1 -0
- package/dist/clis/medium/user.js +15 -0
- package/dist/clis/reddit/comment.js +1 -1
- package/dist/clis/reddit/read.js +1 -1
- package/dist/clis/reddit/save.js +1 -1
- package/dist/clis/reddit/subreddit.yaml +1 -0
- package/dist/clis/reddit/subscribe.js +1 -1
- package/dist/clis/reddit/upvote.js +1 -1
- package/dist/clis/sinablog/article.d.ts +1 -0
- package/dist/clis/sinablog/article.js +14 -0
- package/dist/clis/sinablog/hot.d.ts +1 -0
- package/dist/clis/sinablog/hot.js +14 -0
- package/dist/clis/sinablog/search.d.ts +1 -0
- package/dist/clis/sinablog/search.js +51 -0
- package/dist/clis/sinablog/shared.d.ts +7 -0
- package/dist/clis/sinablog/shared.js +187 -0
- package/dist/clis/sinablog/user.d.ts +1 -0
- package/dist/clis/sinablog/user.js +15 -0
- package/dist/clis/substack/feed.d.ts +1 -0
- package/dist/clis/substack/feed.js +15 -0
- package/dist/clis/substack/publication.d.ts +1 -0
- package/dist/clis/substack/publication.js +15 -0
- package/dist/clis/substack/search.d.ts +1 -0
- package/dist/clis/substack/search.js +77 -0
- package/dist/clis/substack/shared.d.ts +4 -0
- package/dist/clis/substack/shared.js +129 -0
- package/dist/clis/tiktok/comment.yaml +66 -0
- package/dist/clis/tiktok/explore.yaml +39 -0
- package/dist/clis/tiktok/follow.yaml +39 -0
- package/dist/clis/tiktok/following.yaml +46 -0
- package/dist/clis/tiktok/friends.yaml +47 -0
- package/dist/clis/tiktok/like.yaml +38 -0
- package/dist/clis/tiktok/live.yaml +51 -0
- package/dist/clis/tiktok/notifications.yaml +52 -0
- package/dist/clis/tiktok/profile.yaml +45 -0
- package/dist/clis/tiktok/save.yaml +34 -0
- package/dist/clis/tiktok/search.yaml +46 -0
- package/dist/clis/tiktok/unfollow.yaml +44 -0
- package/dist/clis/tiktok/unlike.yaml +38 -0
- package/dist/clis/tiktok/unsave.yaml +36 -0
- package/dist/clis/tiktok/user.yaml +44 -0
- package/dist/clis/twitter/download.d.ts +1 -1
- package/dist/clis/twitter/download.js +3 -3
- package/dist/clis/twitter/followers.js +1 -1
- package/dist/clis/twitter/following.js +1 -1
- package/dist/clis/twitter/thread.js +1 -1
- package/dist/clis/twitter/timeline.d.ts +23 -0
- package/dist/clis/twitter/timeline.js +42 -14
- package/dist/clis/twitter/timeline.test.d.ts +1 -0
- package/dist/clis/twitter/timeline.test.js +102 -0
- package/dist/clis/wikipedia/random.d.ts +1 -0
- package/dist/clis/wikipedia/random.js +19 -0
- package/dist/clis/wikipedia/search.js +3 -3
- package/dist/clis/wikipedia/summary.js +4 -9
- package/dist/clis/wikipedia/trending.d.ts +1 -0
- package/dist/clis/wikipedia/trending.js +35 -0
- package/dist/clis/wikipedia/utils.d.ts +28 -0
- package/dist/clis/wikipedia/utils.js +13 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
- package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
- package/dist/clis/xiaohongshu/download.js +1 -1
- package/dist/clis/xueqiu/earnings-date.yaml +69 -0
- package/dist/clis/xueqiu/search.yaml +2 -1
- package/dist/clis/xueqiu/stock.yaml +2 -0
- package/dist/clis/yahoo-finance/quote.js +1 -1
- package/dist/commanderAdapter.js +13 -7
- package/dist/daemon.js +21 -0
- package/dist/discovery.d.ts +8 -0
- package/dist/discovery.js +105 -19
- package/dist/doctor.js +3 -1
- package/dist/doctor.test.js +46 -2
- package/dist/engine.test.d.ts +0 -3
- package/dist/engine.test.js +74 -6
- package/dist/execution.d.ts +4 -2
- package/dist/execution.js +31 -7
- package/dist/explore.d.ts +76 -3
- package/dist/explore.js +11 -4
- package/dist/generate.d.ts +41 -2
- package/dist/generate.js +5 -4
- package/dist/main.js +2 -1
- package/dist/pipeline/executor.d.ts +4 -2
- package/dist/pipeline/executor.js +54 -15
- package/dist/pipeline/executor.test.js +33 -6
- package/dist/pipeline/registry.d.ts +1 -1
- package/dist/pipeline/steps/browser.d.ts +7 -7
- package/dist/pipeline/steps/browser.js +15 -7
- package/dist/pipeline/steps/fetch.d.ts +1 -1
- package/dist/pipeline/steps/fetch.js +11 -7
- package/dist/pipeline/steps/transform.d.ts +6 -5
- package/dist/pipeline/steps/transform.js +30 -9
- package/dist/pipeline/template.d.ts +6 -6
- package/dist/pipeline/template.js +43 -5
- package/dist/pipeline/template.test.js +18 -0
- package/dist/pipeline/transform.test.js +11 -0
- package/dist/plugin.d.ts +31 -0
- package/dist/plugin.js +216 -0
- package/dist/plugin.test.d.ts +4 -0
- package/dist/plugin.test.js +76 -0
- package/dist/registry-api.d.ts +11 -0
- package/dist/registry-api.js +9 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.js +6 -1
- package/dist/synthesize.d.ts +94 -4
- package/dist/synthesize.js +5 -4
- package/dist/types.d.ts +39 -26
- package/dist/validate.js +8 -2
- package/docs/.vitepress/config.mts +6 -4
- package/docs/adapters/browser/barchart.md +6 -5
- package/docs/adapters/browser/bilibili.md +9 -0
- package/docs/adapters/browser/devto.md +35 -0
- package/docs/adapters/browser/douban.md +38 -0
- package/docs/adapters/browser/facebook.md +36 -0
- package/docs/adapters/browser/google.md +62 -0
- package/docs/adapters/browser/grok.md +26 -8
- package/docs/adapters/browser/instagram.md +46 -0
- package/docs/adapters/browser/lobsters.md +32 -0
- package/docs/adapters/browser/medium.md +32 -0
- package/docs/adapters/browser/reddit.md +9 -0
- package/docs/adapters/browser/sinablog.md +36 -0
- package/docs/adapters/browser/substack.md +38 -0
- package/docs/adapters/browser/tiktok.md +68 -0
- package/docs/adapters/browser/wikipedia.md +11 -2
- package/docs/adapters/browser/xueqiu.md +10 -0
- package/docs/adapters/browser/yahoo-finance.md +6 -5
- package/docs/adapters/desktop/antigravity.md +6 -0
- package/docs/adapters/desktop/chatgpt.md +2 -1
- package/docs/adapters/desktop/codex.md +5 -1
- package/docs/adapters/desktop/cursor.md +4 -0
- package/docs/adapters/desktop/discord.md +7 -7
- package/docs/adapters/index.md +1 -4
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +153 -0
- package/docs/zh/guide/plugins.md +107 -0
- package/extension/dist/background.js +91 -23
- package/extension/src/background.ts +82 -29
- package/extension/src/cdp.ts +42 -1
- package/package.json +10 -5
- package/scripts/clean-dist.cjs +13 -0
- package/src/browser/cdp.ts +71 -31
- package/src/browser/daemon-client.ts +21 -5
- package/src/browser/dom-helpers.ts +38 -7
- package/src/browser/dom-snapshot.test.ts +249 -0
- package/src/browser/dom-snapshot.ts +770 -0
- package/src/browser/index.ts +2 -0
- package/src/browser/mcp.ts +3 -1
- package/src/browser/page.ts +57 -21
- package/src/build-manifest.test.ts +70 -2
- package/src/build-manifest.ts +94 -26
- package/src/cli.ts +71 -2
- package/src/clis/barchart/greeks.ts +1 -1
- package/src/clis/barchart/options.ts +1 -1
- package/src/clis/barchart/quote.ts +1 -1
- package/src/clis/bilibili/download.ts +1 -1
- package/src/clis/bilibili/following.ts +1 -1
- package/src/clis/bilibili/subtitle.ts +1 -1
- package/src/clis/bilibili/user-videos.ts +1 -1
- package/src/clis/boss/batchgreet.ts +14 -106
- package/src/clis/boss/chatlist.ts +12 -26
- package/src/clis/boss/chatmsg.ts +16 -40
- package/src/clis/boss/common.ts +287 -0
- package/src/clis/boss/detail.ts +8 -54
- package/src/clis/boss/exchange.ts +15 -89
- package/src/clis/boss/greet.ts +23 -160
- package/src/clis/boss/invite.ts +36 -133
- package/src/clis/boss/joblist.ts +7 -36
- package/src/clis/boss/mark.ts +13 -94
- package/src/clis/boss/recommend.ts +12 -57
- package/src/clis/boss/resume.ts +19 -124
- package/src/clis/boss/search.ts +13 -66
- package/src/clis/boss/send.ts +21 -161
- package/src/clis/boss/stats.ts +19 -74
- package/src/clis/coupang/add-to-cart.ts +1 -1
- package/src/clis/devto/tag.yaml +34 -0
- package/src/clis/devto/top.yaml +29 -0
- package/src/clis/devto/user.yaml +33 -0
- package/src/clis/douban/book-hot.ts +15 -0
- package/src/clis/douban/marks.ts +135 -0
- package/src/clis/douban/movie-hot.ts +15 -0
- package/src/clis/douban/reviews.ts +127 -0
- package/src/clis/douban/search.ts +17 -0
- package/src/clis/douban/shared.ts +165 -0
- package/src/clis/douban/subject.yaml +76 -0
- package/src/clis/douban/top250.yaml +70 -0
- package/src/clis/douban/utils.ts +81 -0
- package/src/clis/facebook/add-friend.yaml +43 -0
- package/src/clis/facebook/events.yaml +44 -0
- package/src/clis/facebook/feed.yaml +63 -0
- package/src/clis/facebook/friends.yaml +42 -0
- package/src/clis/facebook/groups.yaml +50 -0
- package/src/clis/facebook/join-group.yaml +44 -0
- package/src/clis/facebook/memories.yaml +39 -0
- package/src/clis/facebook/notifications.yaml +40 -0
- package/src/clis/facebook/profile.yaml +37 -0
- package/src/clis/facebook/search.yaml +46 -0
- package/src/clis/google/news.ts +66 -0
- package/src/clis/google/search.ts +133 -0
- package/src/clis/google/suggest.ts +40 -0
- package/src/clis/google/trends.ts +44 -0
- package/src/clis/google/utils.test.ts +82 -0
- package/src/clis/google/utils.ts +24 -0
- package/src/clis/grok/ask.test.ts +53 -0
- package/src/clis/grok/ask.ts +300 -69
- package/src/clis/instagram/comment.yaml +52 -0
- package/src/clis/instagram/explore.yaml +43 -0
- package/src/clis/instagram/follow.yaml +41 -0
- package/src/clis/instagram/followers.yaml +51 -0
- package/src/clis/instagram/following.yaml +51 -0
- package/src/clis/instagram/like.yaml +46 -0
- package/src/clis/instagram/profile.yaml +42 -0
- package/src/clis/instagram/save.yaml +46 -0
- package/src/clis/instagram/saved.yaml +40 -0
- package/src/clis/instagram/search.yaml +43 -0
- package/src/clis/instagram/unfollow.yaml +38 -0
- package/src/clis/instagram/unlike.yaml +46 -0
- package/src/clis/instagram/unsave.yaml +46 -0
- package/src/clis/instagram/user.yaml +54 -0
- package/src/clis/jike/repost.ts +1 -1
- package/src/clis/jimeng/generate.yaml +1 -0
- package/src/clis/linux-do/category.yaml +1 -0
- package/src/clis/lobsters/active.yaml +29 -0
- package/src/clis/lobsters/hot.yaml +29 -0
- package/src/clis/lobsters/newest.yaml +29 -0
- package/src/clis/lobsters/tag.yaml +34 -0
- package/src/clis/medium/feed.ts +16 -0
- package/src/clis/medium/search.ts +16 -0
- package/src/clis/medium/shared.ts +83 -0
- package/src/clis/medium/user.ts +16 -0
- package/src/clis/reddit/comment.ts +1 -1
- package/src/clis/reddit/read.ts +1 -1
- package/src/clis/reddit/save.ts +1 -1
- package/src/clis/reddit/subreddit.yaml +1 -0
- package/src/clis/reddit/subscribe.ts +1 -1
- package/src/clis/reddit/upvote.ts +1 -1
- package/src/clis/sinablog/article.ts +15 -0
- package/src/clis/sinablog/hot.ts +15 -0
- package/src/clis/sinablog/search.ts +56 -0
- package/src/clis/sinablog/shared.ts +198 -0
- package/src/clis/sinablog/user.ts +16 -0
- package/src/clis/substack/feed.ts +16 -0
- package/src/clis/substack/publication.ts +16 -0
- package/src/clis/substack/search.ts +91 -0
- package/src/clis/substack/shared.ts +132 -0
- package/src/clis/tiktok/comment.yaml +66 -0
- package/src/clis/tiktok/explore.yaml +39 -0
- package/src/clis/tiktok/follow.yaml +39 -0
- package/src/clis/tiktok/following.yaml +46 -0
- package/src/clis/tiktok/friends.yaml +47 -0
- package/src/clis/tiktok/like.yaml +38 -0
- package/src/clis/tiktok/live.yaml +51 -0
- package/src/clis/tiktok/notifications.yaml +52 -0
- package/src/clis/tiktok/profile.yaml +45 -0
- package/src/clis/tiktok/save.yaml +34 -0
- package/src/clis/tiktok/search.yaml +46 -0
- package/src/clis/tiktok/unfollow.yaml +44 -0
- package/src/clis/tiktok/unlike.yaml +38 -0
- package/src/clis/tiktok/unsave.yaml +36 -0
- package/src/clis/tiktok/user.yaml +44 -0
- package/src/clis/twitter/download.ts +3 -3
- package/src/clis/twitter/followers.ts +1 -1
- package/src/clis/twitter/following.ts +1 -1
- package/src/clis/twitter/thread.ts +1 -1
- package/src/clis/twitter/timeline.test.ts +109 -0
- package/src/clis/twitter/timeline.ts +59 -19
- package/src/clis/wikipedia/random.ts +19 -0
- package/src/clis/wikipedia/search.ts +10 -4
- package/src/clis/wikipedia/summary.ts +4 -9
- package/src/clis/wikipedia/trending.ts +41 -0
- package/src/clis/wikipedia/utils.ts +31 -0
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
- package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
- package/src/clis/xiaohongshu/download.ts +1 -1
- package/src/clis/xueqiu/earnings-date.yaml +69 -0
- package/src/clis/xueqiu/search.yaml +2 -1
- package/src/clis/xueqiu/stock.yaml +2 -0
- package/src/clis/yahoo-finance/quote.ts +1 -1
- package/src/commanderAdapter.ts +17 -10
- package/src/daemon.ts +23 -0
- package/src/discovery.ts +134 -24
- package/src/doctor.test.ts +59 -2
- package/src/doctor.ts +4 -2
- package/src/engine.test.ts +79 -6
- package/src/execution.ts +42 -16
- package/src/explore.ts +77 -9
- package/src/generate.ts +58 -9
- package/src/main.ts +2 -1
- package/src/pipeline/executor.test.ts +35 -6
- package/src/pipeline/executor.ts +68 -19
- package/src/pipeline/registry.ts +3 -3
- package/src/pipeline/steps/browser.ts +24 -15
- package/src/pipeline/steps/fetch.ts +18 -13
- package/src/pipeline/steps/transform.ts +40 -15
- package/src/pipeline/template.test.ts +18 -0
- package/src/pipeline/template.ts +86 -13
- package/src/pipeline/transform.test.ts +15 -2
- package/src/plugin.test.ts +86 -0
- package/src/plugin.ts +254 -0
- package/src/registry-api.ts +12 -0
- package/src/registry.ts +19 -1
- package/src/synthesize.ts +102 -21
- package/src/types.ts +44 -12
- package/src/validate.ts +19 -4
- package/tests/e2e/browser-public.test.ts +11 -0
- package/tests/e2e/public-commands.test.ts +64 -0
- package/dist/clis/feishu/new.d.ts +0 -1
- package/dist/clis/feishu/new.js +0 -27
- package/dist/clis/feishu/read.d.ts +0 -1
- package/dist/clis/feishu/read.js +0 -40
- package/dist/clis/feishu/search.d.ts +0 -1
- package/dist/clis/feishu/search.js +0 -30
- package/dist/clis/feishu/send.d.ts +0 -1
- package/dist/clis/feishu/send.js +0 -39
- package/dist/clis/feishu/status.d.ts +0 -1
- package/dist/clis/feishu/status.js +0 -28
- package/dist/clis/neteasemusic/like.d.ts +0 -1
- package/dist/clis/neteasemusic/like.js +0 -25
- package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
- package/dist/clis/neteasemusic/lyrics.js +0 -47
- package/dist/clis/neteasemusic/next.d.ts +0 -1
- package/dist/clis/neteasemusic/next.js +0 -26
- package/dist/clis/neteasemusic/play.d.ts +0 -1
- package/dist/clis/neteasemusic/play.js +0 -26
- package/dist/clis/neteasemusic/playing.d.ts +0 -1
- package/dist/clis/neteasemusic/playing.js +0 -59
- package/dist/clis/neteasemusic/playlist.d.ts +0 -1
- package/dist/clis/neteasemusic/playlist.js +0 -46
- package/dist/clis/neteasemusic/prev.d.ts +0 -1
- package/dist/clis/neteasemusic/prev.js +0 -25
- package/dist/clis/neteasemusic/search.d.ts +0 -1
- package/dist/clis/neteasemusic/search.js +0 -52
- package/dist/clis/neteasemusic/status.d.ts +0 -1
- package/dist/clis/neteasemusic/status.js +0 -16
- package/dist/clis/neteasemusic/volume.d.ts +0 -1
- package/dist/clis/neteasemusic/volume.js +0 -54
- package/dist/clis/wechat/chats.d.ts +0 -1
- package/dist/clis/wechat/chats.js +0 -28
- package/dist/clis/wechat/contacts.d.ts +0 -1
- package/dist/clis/wechat/contacts.js +0 -28
- package/dist/clis/wechat/read.d.ts +0 -1
- package/dist/clis/wechat/read.js +0 -58
- package/dist/clis/wechat/search.d.ts +0 -1
- package/dist/clis/wechat/search.js +0 -31
- package/dist/clis/wechat/send.d.ts +0 -1
- package/dist/clis/wechat/send.js +0 -42
- package/dist/clis/wechat/status.d.ts +0 -1
- package/dist/clis/wechat/status.js +0 -29
- package/dist/pipeline.d.ts +0 -7
- package/dist/pipeline.js +0 -7
- package/docs/adapters/browser/github.md +0 -26
- package/docs/adapters/desktop/feishu.md +0 -20
- package/docs/adapters/desktop/neteasemusic.md +0 -31
- package/docs/adapters/desktop/wechat.md +0 -28
- package/src/clis/antigravity/README.md +0 -5
- package/src/clis/antigravity/README.zh-CN.md +0 -51
- package/src/clis/chaoxing/README.md +0 -14
- package/src/clis/chaoxing/README.zh-CN.md +0 -35
- package/src/clis/chatgpt/README.md +0 -5
- package/src/clis/chatgpt/README.zh-CN.md +0 -44
- package/src/clis/chatwise/README.md +0 -5
- package/src/clis/chatwise/README.zh-CN.md +0 -38
- package/src/clis/codex/README.md +0 -5
- package/src/clis/codex/README.zh-CN.md +0 -33
- package/src/clis/cursor/README.md +0 -5
- package/src/clis/cursor/README.zh-CN.md +0 -33
- package/src/clis/discord-app/README.md +0 -5
- package/src/clis/discord-app/README.zh-CN.md +0 -28
- package/src/clis/feishu/README.md +0 -5
- package/src/clis/feishu/README.zh-CN.md +0 -20
- package/src/clis/feishu/new.ts +0 -32
- package/src/clis/feishu/read.ts +0 -48
- package/src/clis/feishu/search.ts +0 -35
- package/src/clis/feishu/send.ts +0 -46
- package/src/clis/feishu/status.ts +0 -34
- package/src/clis/neteasemusic/README.md +0 -5
- package/src/clis/neteasemusic/README.zh-CN.md +0 -31
- package/src/clis/neteasemusic/like.ts +0 -28
- package/src/clis/neteasemusic/lyrics.ts +0 -53
- package/src/clis/neteasemusic/next.ts +0 -30
- package/src/clis/neteasemusic/play.ts +0 -30
- package/src/clis/neteasemusic/playing.ts +0 -62
- package/src/clis/neteasemusic/playlist.ts +0 -51
- package/src/clis/neteasemusic/prev.ts +0 -29
- package/src/clis/neteasemusic/search.ts +0 -58
- package/src/clis/neteasemusic/status.ts +0 -18
- package/src/clis/neteasemusic/volume.ts +0 -61
- package/src/clis/notion/README.md +0 -5
- package/src/clis/notion/README.zh-CN.md +0 -29
- package/src/clis/wechat/README.md +0 -5
- package/src/clis/wechat/README.zh-CN.md +0 -28
- package/src/clis/wechat/chats.ts +0 -33
- package/src/clis/wechat/contacts.ts +0 -33
- package/src/clis/wechat/read.ts +0 -72
- package/src/clis/wechat/search.ts +0 -36
- package/src/clis/wechat/send.ts +0 -49
- package/src/clis/wechat/status.ts +0 -35
- package/src/pipeline.ts +0 -8
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google adapter utilities.
|
|
3
|
+
* Shared RSS parser for news and trends commands.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parse RSS XML by splitting into <item> blocks, then extracting fields per block.
|
|
8
|
+
* Handles both plain text and CDATA-wrapped content.
|
|
9
|
+
*/
|
|
10
|
+
export function parseRssItems(xml: string, fields: string[]): Record<string, string>[] {
|
|
11
|
+
const items = xml.match(/<item>([\s\S]*?)<\/item>/g) || [];
|
|
12
|
+
return items.map(block => {
|
|
13
|
+
const record: Record<string, string> = {};
|
|
14
|
+
for (const field of fields) {
|
|
15
|
+
// Escape regex special characters in field name (e.g. ht:approx_traffic is safe, but defensive)
|
|
16
|
+
const escaped = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
17
|
+
// Handle tags with attributes (e.g. <source url="...">text</source>) and CDATA wrapping
|
|
18
|
+
// (?:\s[^>]*)? ensures we don't match prefix tags (e.g. <sourceUrl> when looking for <source>)
|
|
19
|
+
const match = block.match(new RegExp(`<${escaped}(?:\\s[^>]*)?>(?:<!\\[CDATA\\[)?([\\s\\S]*?)(?:\\]\\]>)?</${escaped}>`));
|
|
20
|
+
record[field] = match ? match[1].trim() : '';
|
|
21
|
+
}
|
|
22
|
+
return record;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { __test__ } from './ask.js';
|
|
3
|
+
|
|
4
|
+
describe('grok ask helpers', () => {
|
|
5
|
+
it('normalizes boolean flags for explicit web routing', () => {
|
|
6
|
+
expect(__test__.normalizeBooleanFlag(true)).toBe(true);
|
|
7
|
+
expect(__test__.normalizeBooleanFlag('true')).toBe(true);
|
|
8
|
+
expect(__test__.normalizeBooleanFlag('1')).toBe(true);
|
|
9
|
+
expect(__test__.normalizeBooleanFlag('yes')).toBe(true);
|
|
10
|
+
expect(__test__.normalizeBooleanFlag('on')).toBe(true);
|
|
11
|
+
|
|
12
|
+
expect(__test__.normalizeBooleanFlag(false)).toBe(false);
|
|
13
|
+
expect(__test__.normalizeBooleanFlag('false')).toBe(false);
|
|
14
|
+
expect(__test__.normalizeBooleanFlag(undefined)).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('ignores baseline bubbles and the echoed prompt when choosing the latest assistant candidate', () => {
|
|
18
|
+
const candidate = __test__.pickLatestAssistantCandidate(
|
|
19
|
+
['older assistant answer', 'Prompt text', 'Assistant draft', 'Assistant final'],
|
|
20
|
+
1,
|
|
21
|
+
'Prompt text',
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(candidate).toBe('Assistant final');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns empty when only the echoed prompt appeared after send', () => {
|
|
28
|
+
const candidate = __test__.pickLatestAssistantCandidate(
|
|
29
|
+
['older assistant answer', 'Prompt text'],
|
|
30
|
+
1,
|
|
31
|
+
'Prompt text',
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(candidate).toBe('');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('tracks stabilization by incrementing repeats and resetting on changes', () => {
|
|
38
|
+
expect(__test__.updateStableState('', 0, 'First chunk')).toEqual({
|
|
39
|
+
previousText: 'First chunk',
|
|
40
|
+
stableCount: 0,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(__test__.updateStableState('First chunk', 0, 'First chunk')).toEqual({
|
|
44
|
+
previousText: 'First chunk',
|
|
45
|
+
stableCount: 1,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(__test__.updateStableState('First chunk', 1, 'Second chunk')).toEqual({
|
|
49
|
+
previousText: 'Second chunk',
|
|
50
|
+
stableCount: 0,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
package/src/clis/grok/ask.ts
CHANGED
|
@@ -1,6 +1,290 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import type { IPage } from '../../types.js';
|
|
3
3
|
|
|
4
|
+
const GROK_URL = 'https://grok.com/';
|
|
5
|
+
const RESPONSE_SELECTOR = 'div.message-bubble, [data-testid="message-bubble"]';
|
|
6
|
+
const BLOCKED_PREFIX = '[BLOCKED]';
|
|
7
|
+
const NO_RESPONSE_PREFIX = '[NO RESPONSE]';
|
|
8
|
+
const SESSION_HINT = 'Likely login/auth/challenge/session issue in the existing grok.com browser session.';
|
|
9
|
+
|
|
10
|
+
type GrokSendResult = {
|
|
11
|
+
ok?: boolean;
|
|
12
|
+
msg?: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
detail?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function blocked(message: string) {
|
|
18
|
+
return [{ response: `${BLOCKED_PREFIX} ${message} ${SESSION_HINT}` }];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeBubbleText(value: unknown): string {
|
|
22
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeBooleanFlag(value: unknown): boolean {
|
|
26
|
+
if (typeof value === 'boolean') return value;
|
|
27
|
+
|
|
28
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
29
|
+
return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function pickLatestAssistantCandidate(
|
|
33
|
+
bubbles: unknown[],
|
|
34
|
+
baselineCount: number,
|
|
35
|
+
prompt: string,
|
|
36
|
+
): string {
|
|
37
|
+
const normalizedPrompt = prompt.trim();
|
|
38
|
+
const freshBubbles = bubbles
|
|
39
|
+
.slice(Math.max(0, baselineCount))
|
|
40
|
+
.map(normalizeBubbleText)
|
|
41
|
+
.filter(Boolean);
|
|
42
|
+
|
|
43
|
+
for (let i = freshBubbles.length - 1; i >= 0; i -= 1) {
|
|
44
|
+
if (freshBubbles[i] !== normalizedPrompt) return freshBubbles[i];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function updateStableState(previousText: string, stableCount: number, nextText: string) {
|
|
51
|
+
if (!nextText) return { previousText: '', stableCount: 0 };
|
|
52
|
+
if (nextText === previousText) return { previousText, stableCount: stableCount + 1 };
|
|
53
|
+
return { previousText: nextText, stableCount: 0 };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runDefaultAsk(
|
|
57
|
+
page: IPage,
|
|
58
|
+
prompt: string,
|
|
59
|
+
timeoutMs: number,
|
|
60
|
+
newChat: boolean,
|
|
61
|
+
) {
|
|
62
|
+
if (newChat) {
|
|
63
|
+
await page.goto(GROK_URL);
|
|
64
|
+
await page.wait(2);
|
|
65
|
+
await page.evaluate(`(() => {
|
|
66
|
+
const btn = [...document.querySelectorAll('a, button')].find(b => {
|
|
67
|
+
const t = (b.textContent || '').trim().toLowerCase();
|
|
68
|
+
return t.includes('new') || b.getAttribute('href') === '/';
|
|
69
|
+
});
|
|
70
|
+
if (btn) btn.click();
|
|
71
|
+
})()`);
|
|
72
|
+
await page.wait(2);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await page.goto(GROK_URL);
|
|
76
|
+
await page.wait(3);
|
|
77
|
+
|
|
78
|
+
const promptJson = JSON.stringify(prompt);
|
|
79
|
+
const sendResult = await page.evaluate(`(async () => {
|
|
80
|
+
try {
|
|
81
|
+
const box = document.querySelector('textarea');
|
|
82
|
+
if (!box) return { ok: false, msg: 'no textarea' };
|
|
83
|
+
box.focus(); box.value = '';
|
|
84
|
+
document.execCommand('selectAll');
|
|
85
|
+
document.execCommand('insertText', false, ${promptJson});
|
|
86
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
87
|
+
const btn = document.querySelector('button[aria-label="\\u63d0\\u4ea4"]');
|
|
88
|
+
if (btn && !btn.disabled) { btn.click(); return { ok: true, msg: 'clicked' }; }
|
|
89
|
+
const sub = [...document.querySelectorAll('button[type="submit"]')].find(b => !b.disabled);
|
|
90
|
+
if (sub) { sub.click(); return { ok: true, msg: 'clicked-submit' }; }
|
|
91
|
+
box.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
|
|
92
|
+
return { ok: true, msg: 'enter' };
|
|
93
|
+
} catch (e) { return { ok: false, msg: e.toString() }; }
|
|
94
|
+
})()`) as GrokSendResult;
|
|
95
|
+
|
|
96
|
+
if (!sendResult || !sendResult.ok) {
|
|
97
|
+
return [{ response: '[SEND FAILED] ' + JSON.stringify(sendResult) }];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const startTime = Date.now();
|
|
101
|
+
let lastText = '';
|
|
102
|
+
let stableCount = 0;
|
|
103
|
+
|
|
104
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
105
|
+
await page.wait(3);
|
|
106
|
+
const response = await page.evaluate(`(() => {
|
|
107
|
+
const bubbles = document.querySelectorAll('div.message-bubble, [data-testid="message-bubble"]');
|
|
108
|
+
if (bubbles.length < 2) return '';
|
|
109
|
+
const last = bubbles[bubbles.length - 1];
|
|
110
|
+
const text = (last.innerText || '').trim();
|
|
111
|
+
if (!text || text.length < 2) return '';
|
|
112
|
+
return text;
|
|
113
|
+
})()`);
|
|
114
|
+
|
|
115
|
+
if (response && response.length > 2) {
|
|
116
|
+
if (response === lastText) {
|
|
117
|
+
stableCount++;
|
|
118
|
+
if (stableCount >= 2) return [{ response }];
|
|
119
|
+
} else {
|
|
120
|
+
stableCount = 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
lastText = response || '';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (lastText) return [{ response: lastText }];
|
|
127
|
+
return [{ response: NO_RESPONSE_PREFIX }];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function getBubbleTexts(page: IPage): Promise<string[]> {
|
|
131
|
+
const result = await page.evaluate(`(() => {
|
|
132
|
+
return Array.from(document.querySelectorAll(${JSON.stringify(RESPONSE_SELECTOR)}))
|
|
133
|
+
.map(node => (node instanceof HTMLElement ? node.innerText : node?.textContent || ''))
|
|
134
|
+
.map(text => (typeof text === 'string' ? text.trim() : ''))
|
|
135
|
+
.filter(Boolean);
|
|
136
|
+
})()`);
|
|
137
|
+
|
|
138
|
+
return Array.isArray(result) ? result.map(normalizeBubbleText).filter(Boolean) : [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function tryStartFreshChat(page: IPage): Promise<void> {
|
|
142
|
+
await page.evaluate(`(() => {
|
|
143
|
+
const isVisible = (node) => {
|
|
144
|
+
if (!(node instanceof HTMLElement)) return false;
|
|
145
|
+
const rect = node.getBoundingClientRect();
|
|
146
|
+
const style = window.getComputedStyle(node);
|
|
147
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none';
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const candidates = Array.from(document.querySelectorAll('a, button')).filter(node => {
|
|
151
|
+
if (!isVisible(node)) return false;
|
|
152
|
+
const text = (node.textContent || '').trim().toLowerCase();
|
|
153
|
+
const aria = (node.getAttribute('aria-label') || '').trim().toLowerCase();
|
|
154
|
+
const href = node.getAttribute('href') || '';
|
|
155
|
+
return text.includes('new chat')
|
|
156
|
+
|| text.includes('new conversation')
|
|
157
|
+
|| aria.includes('new chat')
|
|
158
|
+
|| aria.includes('new conversation')
|
|
159
|
+
|| href === '/';
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const target = candidates[0];
|
|
163
|
+
if (target instanceof HTMLElement) target.click();
|
|
164
|
+
})()`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function sendPromptViaExplicitWeb(page: IPage, prompt: string) {
|
|
168
|
+
return page.evaluate(`(async () => {
|
|
169
|
+
const waitFor = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
170
|
+
const composerSelector = '.ProseMirror[contenteditable="true"]';
|
|
171
|
+
let composer = null;
|
|
172
|
+
|
|
173
|
+
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
174
|
+
const candidate = document.querySelector(composerSelector);
|
|
175
|
+
if (candidate instanceof HTMLElement) {
|
|
176
|
+
composer = candidate;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
await waitFor(1000);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!(composer instanceof HTMLElement)) {
|
|
184
|
+
return {
|
|
185
|
+
ok: false,
|
|
186
|
+
reason: 'Grok composer was not found on grok.com.',
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const editor = composer.editor;
|
|
191
|
+
if (!editor?.commands?.focus || !editor?.commands?.insertContent) {
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
reason: 'Grok composer editor API was unavailable.',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const isVisibleEnabledSubmit = (node) => {
|
|
199
|
+
if (!(node instanceof HTMLButtonElement)) return false;
|
|
200
|
+
const rect = node.getBoundingClientRect();
|
|
201
|
+
const style = window.getComputedStyle(node);
|
|
202
|
+
return !node.disabled
|
|
203
|
+
&& rect.width > 0
|
|
204
|
+
&& rect.height > 0
|
|
205
|
+
&& style.visibility !== 'hidden'
|
|
206
|
+
&& style.display !== 'none';
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
if (editor.commands.clearContent) editor.commands.clearContent();
|
|
211
|
+
editor.commands.focus();
|
|
212
|
+
editor.commands.insertContent(${JSON.stringify(prompt)});
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return {
|
|
215
|
+
ok: false,
|
|
216
|
+
reason: 'Failed to insert the prompt into the Grok composer.',
|
|
217
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let submit = null;
|
|
222
|
+
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
223
|
+
const candidate = Array.from(document.querySelectorAll('button[aria-label="Submit"]'))
|
|
224
|
+
.find(isVisibleEnabledSubmit);
|
|
225
|
+
|
|
226
|
+
if (candidate instanceof HTMLButtonElement) {
|
|
227
|
+
submit = candidate;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
await waitFor(500);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!(submit instanceof HTMLButtonElement)) {
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
reason: 'Grok submit button did not reach a clickable ready state after prompt insertion.',
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
submit.click();
|
|
242
|
+
return { ok: true };
|
|
243
|
+
})()`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function runExplicitWebAsk(
|
|
247
|
+
page: IPage,
|
|
248
|
+
prompt: string,
|
|
249
|
+
timeoutMs: number,
|
|
250
|
+
newChat: boolean,
|
|
251
|
+
) {
|
|
252
|
+
await page.goto(GROK_URL, { settleMs: 2000 });
|
|
253
|
+
|
|
254
|
+
if (newChat) {
|
|
255
|
+
await tryStartFreshChat(page);
|
|
256
|
+
await page.wait(2);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const baselineBubbles = await getBubbleTexts(page);
|
|
260
|
+
const sendResult = await sendPromptViaExplicitWeb(page, prompt) as GrokSendResult;
|
|
261
|
+
|
|
262
|
+
if (!sendResult?.ok) {
|
|
263
|
+
const details = sendResult?.detail ? ` ${sendResult.detail}` : '';
|
|
264
|
+
return blocked(`${sendResult?.reason || 'Unable to send the prompt to Grok.'}${details}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const startTime = Date.now();
|
|
268
|
+
let lastText = '';
|
|
269
|
+
let stableCount = 0;
|
|
270
|
+
|
|
271
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
272
|
+
await page.wait(2);
|
|
273
|
+
const bubbleTexts = await getBubbleTexts(page);
|
|
274
|
+
const candidate = pickLatestAssistantCandidate(bubbleTexts, baselineBubbles.length, prompt);
|
|
275
|
+
const nextState = updateStableState(lastText, stableCount, candidate);
|
|
276
|
+
lastText = nextState.previousText;
|
|
277
|
+
stableCount = nextState.stableCount;
|
|
278
|
+
|
|
279
|
+
if (candidate && stableCount >= 2) {
|
|
280
|
+
return [{ response: candidate }];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (lastText) return [{ response: lastText }];
|
|
285
|
+
return [{ response: `${NO_RESPONSE_PREFIX} No new assistant message bubble appeared within ${Math.round(timeoutMs / 1000)}s.` }];
|
|
286
|
+
}
|
|
287
|
+
|
|
4
288
|
export const askCommand = cli({
|
|
5
289
|
site: 'grok',
|
|
6
290
|
name: 'ask',
|
|
@@ -9,82 +293,29 @@ export const askCommand = cli({
|
|
|
9
293
|
strategy: Strategy.COOKIE,
|
|
10
294
|
browser: true,
|
|
11
295
|
args: [
|
|
12
|
-
{ name: 'prompt', type: 'string', required: true },
|
|
13
|
-
{ name: 'timeout', type: 'int', default: 120 },
|
|
14
|
-
{ name: 'new', type: 'boolean', default: false },
|
|
296
|
+
{ name: 'prompt', positional: true, type: 'string', required: true, help: 'Prompt to send to Grok' },
|
|
297
|
+
{ name: 'timeout', type: 'int', default: 120, help: 'Max seconds to wait for response (default: 120)' },
|
|
298
|
+
{ name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending (default: false)' },
|
|
299
|
+
{ name: 'web', type: 'boolean', default: false, help: 'Use the explicit grok.com consumer web flow (default: false)' },
|
|
15
300
|
],
|
|
16
301
|
columns: ['response'],
|
|
17
302
|
func: async (page: IPage, kwargs: Record<string, any>) => {
|
|
18
303
|
const prompt = kwargs.prompt as string;
|
|
19
304
|
const timeoutMs = ((kwargs.timeout as number) || 120) * 1000;
|
|
20
|
-
const newChat = kwargs.new
|
|
21
|
-
|
|
22
|
-
if (newChat) {
|
|
23
|
-
await page.goto('https://grok.com');
|
|
24
|
-
await page.wait(2);
|
|
25
|
-
await page.evaluate(`(() => {
|
|
26
|
-
const btn = [...document.querySelectorAll('a, button')].find(b => {
|
|
27
|
-
const t = (b.textContent || '').trim().toLowerCase();
|
|
28
|
-
return t.includes('new') || b.getAttribute('href') === '/';
|
|
29
|
-
});
|
|
30
|
-
if (btn) btn.click();
|
|
31
|
-
})()`);
|
|
32
|
-
await page.wait(2);
|
|
33
|
-
}
|
|
305
|
+
const newChat = normalizeBooleanFlag(kwargs.new);
|
|
306
|
+
const useExplicitWeb = normalizeBooleanFlag(kwargs.web);
|
|
34
307
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const promptJson = JSON.stringify(prompt);
|
|
39
|
-
|
|
40
|
-
const sendResult = await page.evaluate(`(async () => {
|
|
41
|
-
try {
|
|
42
|
-
const box = document.querySelector('textarea');
|
|
43
|
-
if (!box) return { ok: false, msg: 'no textarea' };
|
|
44
|
-
box.focus(); box.value = '';
|
|
45
|
-
document.execCommand('selectAll');
|
|
46
|
-
document.execCommand('insertText', false, ${promptJson});
|
|
47
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
48
|
-
const btn = document.querySelector('button[aria-label="\\u63d0\\u4ea4"]');
|
|
49
|
-
if (btn && !btn.disabled) { btn.click(); return { ok: true, msg: 'clicked' }; }
|
|
50
|
-
const sub = [...document.querySelectorAll('button[type="submit"]')].find(b => !b.disabled);
|
|
51
|
-
if (sub) { sub.click(); return { ok: true, msg: 'clicked-submit' }; }
|
|
52
|
-
box.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
|
|
53
|
-
return { ok: true, msg: 'enter' };
|
|
54
|
-
} catch (e) { return { ok: false, msg: e.toString() }; }
|
|
55
|
-
})()`);
|
|
56
|
-
|
|
57
|
-
if (!sendResult || !sendResult.ok) {
|
|
58
|
-
return [{ response: '[SEND FAILED] ' + JSON.stringify(sendResult) }];
|
|
308
|
+
if (useExplicitWeb) {
|
|
309
|
+
return runExplicitWebAsk(page, prompt, timeoutMs, newChat);
|
|
59
310
|
}
|
|
60
311
|
|
|
61
|
-
|
|
62
|
-
let lastText = '';
|
|
63
|
-
let stableCount = 0;
|
|
64
|
-
|
|
65
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
66
|
-
await page.wait(3);
|
|
67
|
-
const response = await page.evaluate(`(() => {
|
|
68
|
-
const bubbles = document.querySelectorAll('div.message-bubble, [data-testid="message-bubble"]');
|
|
69
|
-
if (bubbles.length < 2) return '';
|
|
70
|
-
const last = bubbles[bubbles.length - 1];
|
|
71
|
-
const text = (last.innerText || '').trim();
|
|
72
|
-
if (!text || text.length < 2) return '';
|
|
73
|
-
return text;
|
|
74
|
-
})()`);
|
|
75
|
-
|
|
76
|
-
if (response && response.length > 2) {
|
|
77
|
-
if (response === lastText) {
|
|
78
|
-
stableCount++;
|
|
79
|
-
if (stableCount >= 2) return [{ response }];
|
|
80
|
-
} else {
|
|
81
|
-
stableCount = 0;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
lastText = response || '';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (lastText) return [{ response: lastText }];
|
|
88
|
-
return [{ response: '[NO RESPONSE]' }];
|
|
312
|
+
return runDefaultAsk(page, prompt, timeoutMs, newChat);
|
|
89
313
|
},
|
|
90
314
|
});
|
|
315
|
+
|
|
316
|
+
export const __test__ = {
|
|
317
|
+
pickLatestAssistantCandidate,
|
|
318
|
+
updateStableState,
|
|
319
|
+
normalizeBooleanFlag,
|
|
320
|
+
normalizeBubbleText,
|
|
321
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
site: instagram
|
|
2
|
+
name: comment
|
|
3
|
+
description: Comment on an Instagram post
|
|
4
|
+
domain: www.instagram.com
|
|
5
|
+
|
|
6
|
+
args:
|
|
7
|
+
username:
|
|
8
|
+
type: str
|
|
9
|
+
required: true
|
|
10
|
+
positional: true
|
|
11
|
+
description: Username of the post author
|
|
12
|
+
text:
|
|
13
|
+
positional: true
|
|
14
|
+
type: str
|
|
15
|
+
required: true
|
|
16
|
+
description: Comment text
|
|
17
|
+
index:
|
|
18
|
+
type: int
|
|
19
|
+
default: 1
|
|
20
|
+
description: Post index (1 = most recent)
|
|
21
|
+
|
|
22
|
+
pipeline:
|
|
23
|
+
- navigate: https://www.instagram.com
|
|
24
|
+
|
|
25
|
+
- evaluate: |
|
|
26
|
+
(async () => {
|
|
27
|
+
const username = ${{ args.username | json }};
|
|
28
|
+
const commentText = ${{ args.text | json }};
|
|
29
|
+
const idx = ${{ args.index }} - 1;
|
|
30
|
+
const headers = { 'X-IG-App-ID': '936619743392459' };
|
|
31
|
+
const opts = { credentials: 'include', headers };
|
|
32
|
+
|
|
33
|
+
const r1 = await fetch('https://www.instagram.com/api/v1/users/web_profile_info/?username=' + encodeURIComponent(username), opts);
|
|
34
|
+
if (!r1.ok) throw new Error('User not found: ' + username);
|
|
35
|
+
const userId = (await r1.json())?.data?.user?.id;
|
|
36
|
+
|
|
37
|
+
const r2 = await fetch('https://www.instagram.com/api/v1/feed/user/' + userId + '/?count=' + (idx + 1), opts);
|
|
38
|
+
const posts = (await r2.json())?.items || [];
|
|
39
|
+
if (idx >= posts.length) throw new Error('Post index ' + (idx + 1) + ' not found');
|
|
40
|
+
const pk = posts[idx].pk;
|
|
41
|
+
|
|
42
|
+
const csrf = document.cookie.match(/csrftoken=([^;]+)/)?.[1] || '';
|
|
43
|
+
const r3 = await fetch('https://www.instagram.com/api/v1/web/comments/' + pk + '/add/', {
|
|
44
|
+
method: 'POST', credentials: 'include',
|
|
45
|
+
headers: { ...headers, 'X-CSRFToken': csrf, 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
46
|
+
body: 'comment_text=' + encodeURIComponent(commentText),
|
|
47
|
+
});
|
|
48
|
+
if (!r3.ok) throw new Error('Failed to comment: HTTP ' + r3.status);
|
|
49
|
+
return [{ status: 'Commented', user: username, text: commentText }];
|
|
50
|
+
})()
|
|
51
|
+
|
|
52
|
+
columns: [status, user, text]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
site: instagram
|
|
2
|
+
name: explore
|
|
3
|
+
description: Instagram explore/discover trending posts
|
|
4
|
+
domain: www.instagram.com
|
|
5
|
+
|
|
6
|
+
args:
|
|
7
|
+
limit:
|
|
8
|
+
type: int
|
|
9
|
+
default: 20
|
|
10
|
+
description: Number of posts
|
|
11
|
+
|
|
12
|
+
pipeline:
|
|
13
|
+
- navigate: https://www.instagram.com
|
|
14
|
+
|
|
15
|
+
- evaluate: |
|
|
16
|
+
(async () => {
|
|
17
|
+
const limit = ${{ args.limit }};
|
|
18
|
+
const res = await fetch(
|
|
19
|
+
'https://www.instagram.com/api/v1/discover/web/explore_grid/',
|
|
20
|
+
{
|
|
21
|
+
credentials: 'include',
|
|
22
|
+
headers: { 'X-IG-App-ID': '936619743392459' }
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
if (!res.ok) throw new Error('HTTP ' + res.status + ' - make sure you are logged in to Instagram');
|
|
26
|
+
const data = await res.json();
|
|
27
|
+
const posts = [];
|
|
28
|
+
for (const sec of (data?.sectional_items || [])) {
|
|
29
|
+
for (const m of (sec?.layout_content?.medias || [])) {
|
|
30
|
+
const media = m?.media;
|
|
31
|
+
if (media) posts.push({
|
|
32
|
+
user: media.user?.username || '',
|
|
33
|
+
caption: (media.caption?.text || '').replace(/\n/g, ' ').substring(0, 100),
|
|
34
|
+
likes: media.like_count ?? 0,
|
|
35
|
+
comments: media.comment_count ?? 0,
|
|
36
|
+
type: media.media_type === 1 ? 'photo' : media.media_type === 2 ? 'video' : 'carousel',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return posts.slice(0, limit).map((p, i) => ({ rank: i + 1, ...p }));
|
|
41
|
+
})()
|
|
42
|
+
|
|
43
|
+
columns: [rank, user, caption, likes, comments, type]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
site: instagram
|
|
2
|
+
name: follow
|
|
3
|
+
description: Follow an Instagram user
|
|
4
|
+
domain: www.instagram.com
|
|
5
|
+
|
|
6
|
+
args:
|
|
7
|
+
username:
|
|
8
|
+
type: str
|
|
9
|
+
required: true
|
|
10
|
+
positional: true
|
|
11
|
+
description: Instagram username to follow
|
|
12
|
+
|
|
13
|
+
pipeline:
|
|
14
|
+
- navigate: https://www.instagram.com
|
|
15
|
+
|
|
16
|
+
- evaluate: |
|
|
17
|
+
(async () => {
|
|
18
|
+
const username = ${{ args.username | json }};
|
|
19
|
+
const headers = { 'X-IG-App-ID': '936619743392459' };
|
|
20
|
+
const opts = { credentials: 'include', headers };
|
|
21
|
+
|
|
22
|
+
// Get user ID
|
|
23
|
+
const r1 = await fetch('https://www.instagram.com/api/v1/users/web_profile_info/?username=' + encodeURIComponent(username), opts);
|
|
24
|
+
if (!r1.ok) throw new Error('User not found: ' + username);
|
|
25
|
+
const d1 = await r1.json();
|
|
26
|
+
const userId = d1?.data?.user?.id;
|
|
27
|
+
if (!userId) throw new Error('User not found: ' + username);
|
|
28
|
+
|
|
29
|
+
const csrf = document.cookie.match(/csrftoken=([^;]+)/)?.[1] || '';
|
|
30
|
+
const r2 = await fetch('https://www.instagram.com/api/v1/friendships/create/' + userId + '/', {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
credentials: 'include',
|
|
33
|
+
headers: { ...headers, 'X-CSRFToken': csrf, 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
34
|
+
});
|
|
35
|
+
if (!r2.ok) throw new Error('Failed to follow: HTTP ' + r2.status);
|
|
36
|
+
const d2 = await r2.json();
|
|
37
|
+
const status = d2?.friendship_status?.following ? 'Following' : d2?.friendship_status?.outgoing_request ? 'Request sent' : 'Followed';
|
|
38
|
+
return [{ status, username }];
|
|
39
|
+
})()
|
|
40
|
+
|
|
41
|
+
columns: [status, username]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
site: instagram
|
|
2
|
+
name: followers
|
|
3
|
+
description: List followers of an Instagram user
|
|
4
|
+
domain: www.instagram.com
|
|
5
|
+
|
|
6
|
+
args:
|
|
7
|
+
username:
|
|
8
|
+
type: str
|
|
9
|
+
required: true
|
|
10
|
+
positional: true
|
|
11
|
+
description: Instagram username
|
|
12
|
+
limit:
|
|
13
|
+
type: int
|
|
14
|
+
default: 20
|
|
15
|
+
description: Number of followers
|
|
16
|
+
|
|
17
|
+
pipeline:
|
|
18
|
+
- navigate: https://www.instagram.com
|
|
19
|
+
|
|
20
|
+
- evaluate: |
|
|
21
|
+
(async () => {
|
|
22
|
+
const username = ${{ args.username | json }};
|
|
23
|
+
const limit = ${{ args.limit }};
|
|
24
|
+
const headers = { 'X-IG-App-ID': '936619743392459' };
|
|
25
|
+
const opts = { credentials: 'include', headers };
|
|
26
|
+
|
|
27
|
+
const r1 = await fetch(
|
|
28
|
+
'https://www.instagram.com/api/v1/users/web_profile_info/?username=' + encodeURIComponent(username),
|
|
29
|
+
opts
|
|
30
|
+
);
|
|
31
|
+
if (!r1.ok) throw new Error('HTTP ' + r1.status + ' - make sure you are logged in to Instagram');
|
|
32
|
+
const d1 = await r1.json();
|
|
33
|
+
const userId = d1?.data?.user?.id;
|
|
34
|
+
if (!userId) throw new Error('User not found: ' + username);
|
|
35
|
+
|
|
36
|
+
const r2 = await fetch(
|
|
37
|
+
'https://www.instagram.com/api/v1/friendships/' + userId + '/followers/?count=' + limit,
|
|
38
|
+
opts
|
|
39
|
+
);
|
|
40
|
+
if (!r2.ok) throw new Error('Failed to fetch followers: HTTP ' + r2.status);
|
|
41
|
+
const d2 = await r2.json();
|
|
42
|
+
return (d2?.users || []).slice(0, limit).map((u, i) => ({
|
|
43
|
+
rank: i + 1,
|
|
44
|
+
username: u.username || '',
|
|
45
|
+
name: u.full_name || '',
|
|
46
|
+
verified: u.is_verified ? 'Yes' : 'No',
|
|
47
|
+
private: u.is_private ? 'Yes' : 'No',
|
|
48
|
+
}));
|
|
49
|
+
})()
|
|
50
|
+
|
|
51
|
+
columns: [rank, username, name, verified, private]
|