@jackwener/opencli 1.7.12 → 1.7.13
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/README.md +8 -7
- package/README.zh-CN.md +9 -8
- package/cli-manifest.json +12194 -6843
- package/clis/1point3acres/digest.js +35 -0
- package/clis/1point3acres/forum.js +51 -0
- package/clis/1point3acres/forums.js +44 -0
- package/clis/1point3acres/hot.js +35 -0
- package/clis/1point3acres/latest.js +35 -0
- package/clis/1point3acres/notifications.js +64 -0
- package/clis/1point3acres/search.js +71 -0
- package/clis/1point3acres/thread.js +117 -0
- package/clis/1point3acres/user.js +77 -0
- package/clis/1point3acres/utils.js +247 -0
- package/clis/_shared/desktop-commands.js +4 -0
- package/clis/aibase/news.js +110 -0
- package/clis/aibase/news.test.js +59 -0
- package/clis/amazon/discussion.test.js +1 -28
- package/clis/antigravity/watch.js +3 -2
- package/clis/arxiv/author.js +44 -0
- package/clis/baidu-scholar/search.js +0 -1
- package/clis/bbc/topic.js +57 -0
- package/clis/bbc/utils.js +79 -0
- package/clis/chaoxing/assignments.js +1 -1
- package/clis/chaoxing/exams.js +1 -1
- package/clis/chatgpt/ask.js +57 -0
- package/clis/chatgpt/commands.test.js +45 -0
- package/clis/chatgpt/detail.js +46 -0
- package/clis/chatgpt/history.js +39 -0
- package/clis/chatgpt/image.js +12 -11
- package/clis/chatgpt/image.test.js +23 -0
- package/clis/chatgpt/new.js +25 -0
- package/clis/chatgpt/read.js +43 -0
- package/clis/chatgpt/send.js +46 -0
- package/clis/chatgpt/status.js +29 -0
- package/clis/chatgpt/utils.js +294 -4
- package/clis/chatgpt/utils.test.js +13 -0
- package/clis/chatgpt-app/ask.js +6 -3
- package/clis/chatwise/ask.js +16 -43
- package/clis/chatwise/composer.test.js +186 -0
- package/clis/chatwise/send.js +2 -24
- package/clis/chatwise/utils.js +143 -0
- package/clis/claude/ask.js +1 -1
- package/clis/claude/detail.js +1 -0
- package/clis/claude/history.js +1 -0
- package/clis/claude/new.js +1 -0
- package/clis/claude/read.js +1 -0
- package/clis/claude/send.js +1 -0
- package/clis/claude/status.js +1 -0
- package/clis/codex/ask.js +15 -9
- package/clis/codex/history.js +16 -33
- package/clis/codex/projects.js +28 -0
- package/clis/codex/read.js +10 -4
- package/clis/codex/send.js +10 -3
- package/clis/codex/sidebar.js +356 -0
- package/clis/codex/sidebar.test.js +329 -0
- package/clis/coingecko/categories.js +75 -0
- package/clis/coingecko/coin.js +107 -0
- package/clis/coingecko/coingecko.test.js +109 -0
- package/clis/coingecko/derivatives.js +84 -0
- package/clis/coingecko/exchanges.js +74 -0
- package/clis/coingecko/global.js +71 -0
- package/clis/coingecko/top.js +64 -0
- package/clis/coingecko/trending.js +55 -0
- package/clis/coupang/add-to-cart.js +21 -13
- package/clis/coupang/coupang.test.js +159 -0
- package/clis/coupang/product.js +257 -0
- package/clis/coupang/search.js +38 -16
- package/clis/coupang/utils.js +55 -1
- package/clis/crates/crate.js +62 -0
- package/clis/crates/search.js +44 -0
- package/clis/crates/utils.js +72 -0
- package/clis/ctrip/ctrip.test.js +234 -0
- package/clis/ctrip/hotel-suggest.js +45 -0
- package/clis/ctrip/search.js +22 -68
- package/clis/ctrip/utils.js +175 -0
- package/clis/cursor/ask.js +6 -3
- package/clis/dblp/author.js +133 -0
- package/clis/dblp/venue.js +64 -0
- package/clis/deepseek/ask.js +12 -7
- package/clis/deepseek/ask.test.js +13 -13
- package/clis/deepseek/detail.js +38 -0
- package/clis/deepseek/detail.test.js +81 -0
- package/clis/deepseek/history.js +1 -0
- package/clis/deepseek/new.js +1 -0
- package/clis/deepseek/read.js +1 -0
- package/clis/deepseek/send.js +140 -0
- package/clis/deepseek/send.test.js +107 -0
- package/clis/deepseek/status.js +1 -0
- package/clis/deepseek/utils.js +66 -0
- package/clis/deepseek/utils.test.js +107 -1
- package/clis/defillama/defillama.test.js +99 -0
- package/clis/defillama/protocol.js +84 -0
- package/clis/defillama/protocols.js +55 -0
- package/clis/defillama/utils.js +99 -0
- package/clis/devto/latest.js +74 -0
- package/clis/dockerhub/image.js +52 -0
- package/clis/dockerhub/search.js +47 -0
- package/clis/dockerhub/utils.js +100 -0
- package/clis/doubao/ask.js +7 -3
- package/clis/doubao/detail.js +1 -0
- package/clis/doubao/history.js +1 -0
- package/clis/doubao/meeting-summary.js +1 -0
- package/clis/doubao/meeting-transcript.js +1 -0
- package/clis/doubao/new.js +1 -0
- package/clis/doubao/read.js +1 -0
- package/clis/doubao/send.js +1 -0
- package/clis/doubao/status.js +1 -0
- package/clis/douyin/draft.test.js +1 -30
- package/clis/endoflife/endoflife.test.js +51 -0
- package/clis/endoflife/product.js +55 -0
- package/clis/endoflife/utils.js +89 -0
- package/clis/facebook/__fixtures__/notifications-page.html +13 -0
- package/clis/facebook/notifications.js +326 -30
- package/clis/facebook/notifications.test.js +458 -0
- package/clis/flathub/app.js +71 -0
- package/clis/flathub/flathub.test.js +90 -0
- package/clis/flathub/search.js +80 -0
- package/clis/flathub/utils.js +114 -0
- package/clis/gemini/ask.js +7 -3
- package/clis/gemini/ask.test.js +2 -2
- package/clis/gemini/deep-research-result.js +6 -2
- package/clis/gemini/deep-research-result.test.js +15 -14
- package/clis/gemini/deep-research.js +8 -4
- package/clis/gemini/deep-research.test.js +15 -18
- package/clis/gemini/image.js +7 -2
- package/clis/gemini/new.js +1 -0
- package/clis/gemini/utils.js +0 -4
- package/clis/google-scholar/cite.js +0 -1
- package/clis/google-scholar/profile.js +0 -1
- package/clis/google-scholar/search.js +0 -1
- package/clis/goproxy/goproxy.test.js +103 -0
- package/clis/goproxy/module.js +47 -0
- package/clis/goproxy/utils.js +165 -0
- package/clis/goproxy/versions.js +59 -0
- package/clis/gov-law/recent.js +0 -1
- package/clis/gov-law/search.js +0 -1
- package/clis/gov-policy/__fixtures__/recent.html +16 -0
- package/clis/gov-policy/__fixtures__/search.html +41 -0
- package/clis/gov-policy/gov-policy.test.js +224 -0
- package/clis/gov-policy/recent.js +66 -24
- package/clis/gov-policy/search.js +65 -23
- package/clis/gov-policy/utils.js +54 -0
- package/clis/grok/ask.js +49 -265
- package/clis/grok/ask.test.js +21 -46
- package/clis/grok/detail.js +60 -0
- package/clis/grok/history.js +48 -0
- package/clis/grok/{image.ts → image.js} +56 -70
- package/clis/grok/image.test.ts +20 -0
- package/clis/grok/new.js +20 -0
- package/clis/grok/read.js +39 -0
- package/clis/grok/send.js +50 -0
- package/clis/grok/status.js +41 -0
- package/clis/grok/utils.js +326 -0
- package/clis/grok/utils.test.js +103 -0
- package/clis/hf/datasets.js +88 -0
- package/clis/hf/hf.test.js +16 -0
- package/clis/hf/models.js +91 -0
- package/clis/hf/paper.js +79 -0
- package/clis/hf/spaces.js +101 -0
- package/clis/hf/top.js +1 -0
- package/clis/homebrew/cask.js +39 -0
- package/clis/homebrew/formula.js +41 -0
- package/clis/homebrew/popular.js +54 -0
- package/clis/homebrew/utils.js +100 -0
- package/clis/hupu/__fixtures__/hot-home.html +64 -0
- package/clis/hupu/detail.js +0 -1
- package/clis/hupu/hot.js +156 -35
- package/clis/hupu/hot.test.js +224 -0
- package/clis/hupu/search.js +0 -1
- package/clis/instagram/note.js +1 -1
- package/clis/instagram/note.test.js +1 -29
- package/clis/instagram/post.js +1 -1
- package/clis/instagram/post.test.js +1 -1
- package/clis/instagram/reel.js +1 -1
- package/clis/instagram/story.js +1 -1
- package/clis/instagram/story.test.js +1 -34
- package/clis/jd/commands.test.js +1 -24
- package/clis/lichess/lichess.test.js +85 -0
- package/clis/lichess/top.js +46 -0
- package/clis/lichess/user.js +91 -0
- package/clis/lichess/utils.js +97 -0
- package/clis/linkedin/search.js +107 -10
- package/clis/linkedin/search.test.js +222 -0
- package/clis/linux-do/feed.js +2 -5
- package/clis/linux-do/feed.test.js +35 -0
- package/clis/lobsters/domain.js +92 -0
- package/clis/maven/artifact.js +49 -0
- package/clis/maven/search.js +51 -0
- package/clis/maven/utils.js +110 -0
- package/clis/mdn/search.js +97 -0
- package/clis/medium/tag.js +135 -0
- package/clis/npm/downloads.js +59 -0
- package/clis/npm/package.js +70 -0
- package/clis/npm/search.js +49 -0
- package/clis/npm/utils.js +76 -0
- package/clis/nuget/nuget.test.js +111 -0
- package/clis/nuget/package.js +101 -0
- package/clis/nuget/search.js +69 -0
- package/clis/nuget/utils.js +87 -0
- package/clis/nvd/cve.js +121 -0
- package/clis/oeis/oeis.test.js +88 -0
- package/clis/oeis/search.js +63 -0
- package/clis/oeis/sequence.js +71 -0
- package/clis/oeis/utils.js +88 -0
- package/clis/openalex/search.js +69 -0
- package/clis/openalex/utils.js +160 -0
- package/clis/openalex/work.js +65 -0
- package/clis/openfda/drug-label.js +74 -0
- package/clis/openfda/food-recall.js +65 -0
- package/clis/openfda/openfda.test.js +114 -0
- package/clis/openfda/utils.js +67 -0
- package/clis/osv/osv.test.js +97 -0
- package/clis/osv/query.js +72 -0
- package/clis/osv/utils.js +169 -0
- package/clis/osv/vulnerability.js +54 -0
- package/clis/packagist/package.js +49 -0
- package/clis/packagist/search.js +43 -0
- package/clis/packagist/utils.js +113 -0
- package/clis/paperreview/feedback.js +1 -1
- package/clis/paperreview/review.js +1 -1
- package/clis/paperreview/submit.js +1 -1
- package/clis/pixiv/download.test.js +1 -1
- package/clis/pixiv/illusts.test.js +1 -1
- package/clis/pixiv/search.test.js +1 -1
- package/clis/pubmed/article.js +50 -0
- package/clis/pubmed/author.js +64 -0
- package/clis/pubmed/citations.js +36 -0
- package/clis/pubmed/pubmed.test.js +276 -0
- package/clis/pubmed/related.js +45 -0
- package/clis/pubmed/search.js +75 -0
- package/clis/pubmed/utils.js +309 -0
- package/clis/pypi/downloads.js +66 -0
- package/clis/pypi/package.js +79 -0
- package/clis/pypi/utils.js +55 -0
- package/clis/quark/mv.js +1 -1
- package/clis/quark/save.js +1 -1
- package/clis/qwen/ask.js +85 -0
- package/clis/qwen/detail.js +62 -0
- package/clis/qwen/history.js +61 -0
- package/clis/qwen/image.js +179 -0
- package/clis/qwen/new.js +23 -0
- package/clis/qwen/read.js +41 -0
- package/clis/qwen/send.js +55 -0
- package/clis/qwen/status.js +37 -0
- package/clis/qwen/utils.js +409 -0
- package/clis/qwen/utils.test.js +45 -0
- package/clis/rest-countries/country.js +65 -0
- package/clis/rest-countries/region.js +64 -0
- package/clis/rest-countries/rest-countries.test.js +83 -0
- package/clis/rest-countries/utils.js +126 -0
- package/clis/reuters/article-detail.js +53 -0
- package/clis/reuters/reuters.test.js +299 -0
- package/clis/reuters/search.js +45 -34
- package/clis/reuters/utils.js +159 -0
- package/clis/rfc/rfc.js +52 -0
- package/clis/rfc/rfc.test.js +74 -0
- package/clis/rfc/utils.js +72 -0
- package/clis/rubygems/gem.js +42 -0
- package/clis/rubygems/search.js +47 -0
- package/clis/rubygems/utils.js +86 -0
- package/clis/stackoverflow/related.js +66 -0
- package/clis/stackoverflow/stackoverflow.test.js +58 -0
- package/clis/stackoverflow/tag.js +60 -0
- package/clis/stackoverflow/user.js +50 -0
- package/clis/stackoverflow/utils.js +118 -0
- package/clis/steam/app.js +67 -0
- package/clis/steam/search.js +58 -0
- package/clis/steam/steam.test.js +46 -0
- package/clis/steam/utils.js +107 -0
- package/clis/taobao/commands.test.js +1 -24
- package/clis/test-utils.js +61 -0
- package/clis/tieba/hot.js +0 -1
- package/clis/tiktok/comment.js +128 -41
- package/clis/tiktok/creator-videos.js +270 -0
- package/clis/tiktok/creator-videos.test.js +113 -0
- package/clis/tiktok/explore.js +137 -29
- package/clis/tiktok/follow.js +115 -33
- package/clis/tiktok/following.js +157 -36
- package/clis/tiktok/friends.js +139 -37
- package/clis/tiktok/live.js +137 -41
- package/clis/tiktok/notifications.js +141 -38
- package/clis/tiktok/refactor.test.js +389 -0
- package/clis/tiktok/unfollow.js +124 -38
- package/clis/tiktok/user.js +203 -29
- package/clis/tiktok/utils.js +505 -0
- package/clis/tiktok/write-refactor.test.js +370 -0
- package/clis/toutiao/articles.js +36 -62
- package/clis/toutiao/hot.js +63 -0
- package/clis/toutiao/toutiao.test.js +378 -0
- package/clis/toutiao/utils.js +161 -0
- package/clis/tvmaze/search.js +61 -0
- package/clis/tvmaze/show.js +60 -0
- package/clis/tvmaze/tvmaze.test.js +93 -0
- package/clis/tvmaze/utils.js +110 -0
- package/clis/twitter/accept.js +1 -1
- package/clis/twitter/followers.js +134 -69
- package/clis/twitter/reply-dm.js +1 -1
- package/clis/twitter/reply.test.js +1 -29
- package/clis/uisdc/news.js +105 -0
- package/clis/uisdc/news.test.js +66 -0
- package/clis/wanfang/search.js +0 -1
- package/clis/web/read.js +47 -17
- package/clis/web/read.test.js +101 -1
- package/clis/weixin/create-draft.js +1 -1
- package/clis/weixin/drafts.js +1 -1
- package/clis/weixin/drafts.test.js +5 -1
- package/clis/weixin/search.js +157 -0
- package/clis/weixin/search.test.js +227 -0
- package/clis/wikidata/entity.js +60 -0
- package/clis/wikidata/search.js +50 -0
- package/clis/wikidata/utils.js +117 -0
- package/clis/wikidata/wikidata.test.js +83 -0
- package/clis/wikipedia/page.js +95 -0
- package/clis/wttr/current.js +63 -0
- package/clis/wttr/forecast.js +71 -0
- package/clis/wttr/utils.js +50 -0
- package/clis/wttr/wttr.test.js +84 -0
- package/clis/xianyu/chat.js +16 -4
- package/clis/xianyu/chat.test.js +64 -0
- package/clis/xianyu/publish.js +485 -0
- package/clis/xianyu/publish.test.js +220 -0
- package/clis/xiaoe/catalog.js +105 -40
- package/clis/xiaoe/content.js +164 -29
- package/clis/xiaoe/courses.js +86 -29
- package/clis/xiaoe/xiaoe.test.js +486 -0
- package/clis/xiaohongshu/creator-notes-summary.js +1 -1
- package/clis/xiaohongshu/publish.js +16 -3
- package/clis/xiaohongshu/publish.test.js +46 -1
- package/clis/youtube/transcript.js +13 -19
- package/clis/youtube/transcript.test.js +17 -0
- package/clis/yuanbao/ask.js +17 -66
- package/clis/yuanbao/ask.test.js +5 -5
- package/clis/yuanbao/detail.js +65 -0
- package/clis/yuanbao/history.js +51 -0
- package/clis/yuanbao/new.js +1 -0
- package/clis/yuanbao/read.js +38 -0
- package/clis/yuanbao/send.js +57 -0
- package/clis/yuanbao/shared.js +297 -5
- package/clis/yuanbao/shared.test.js +80 -0
- package/clis/yuanbao/status.js +44 -0
- package/clis/zlibrary/commands.test.js +1 -11
- package/dist/src/browser/base-page.d.ts +9 -0
- package/dist/src/browser/base-page.js +44 -1
- package/dist/src/browser/base-page.test.js +66 -0
- package/dist/src/browser/cdp.d.ts +1 -0
- package/dist/src/browser/cdp.js +51 -9
- package/dist/src/browser/daemon-client.d.ts +4 -0
- package/dist/src/browser/errors.js +1 -1
- package/dist/src/browser/page.d.ts +1 -1
- package/dist/src/browser/page.js +3 -1
- package/dist/src/browser/page.test.js +29 -0
- package/dist/src/browser/target-errors.d.ts +2 -1
- package/dist/src/browser/target-errors.js +1 -0
- package/dist/src/browser/target-resolver.d.ts +25 -0
- package/dist/src/browser/target-resolver.js +43 -0
- package/dist/src/build-manifest.js +9 -4
- package/dist/src/build-manifest.test.js +2 -8
- package/dist/src/capabilityRouting.d.ts +16 -1
- package/dist/src/capabilityRouting.js +24 -1
- package/dist/src/capabilityRouting.test.js +19 -1
- package/dist/src/cli.js +76 -11
- package/dist/src/cli.test.js +150 -0
- package/dist/src/commanderAdapter.js +0 -5
- package/dist/src/commanderAdapter.test.js +0 -1
- package/dist/src/discovery.js +2 -5
- package/dist/src/errors.js +1 -1
- package/dist/src/execution.d.ts +1 -1
- package/dist/src/execution.js +111 -27
- package/dist/src/execution.test.js +326 -17
- package/dist/src/help.d.ts +23 -2
- package/dist/src/help.js +41 -19
- package/dist/src/help.test.d.ts +1 -0
- package/dist/src/help.test.js +54 -0
- package/dist/src/main.js +14 -1
- package/dist/src/manifest-types.d.ts +5 -3
- package/dist/src/pipeline/executor.js +1 -1
- package/dist/src/pipeline/executor.test.js +8 -0
- package/dist/src/pipeline/registry.d.ts +9 -0
- package/dist/src/pipeline/registry.js +13 -1
- package/dist/src/pipeline/steps/browser.d.ts +1 -0
- package/dist/src/pipeline/steps/browser.js +10 -0
- package/dist/src/pipeline/steps/download.test.js +1 -0
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry.d.ts +12 -11
- package/dist/src/registry.js +16 -6
- package/dist/src/registry.test.js +2 -2
- package/dist/src/runtime.d.ts +2 -1
- package/dist/src/runtime.js +1 -1
- package/dist/src/serialization.d.ts +2 -2
- package/dist/src/serialization.js +4 -6
- package/dist/src/serialization.test.js +17 -0
- package/dist/src/types.d.ts +17 -0
- package/dist/src/validate.js +15 -11
- package/dist/src/validate.test.d.ts +9 -0
- package/dist/src/validate.test.js +90 -0
- package/package.json +1 -1
- package/scripts/fetch-adapters.js +1 -1
- package/scripts/typed-error-lint-baseline.json +5 -77
- package/clis/ctrip/search.test.js +0 -64
- package/clis/gov-policy/commands.test.js +0 -27
- package/clis/linux-do/category.js +0 -37
- package/clis/linux-do/hot.js +0 -26
- package/clis/linux-do/latest.js +0 -19
- package/clis/pixiv/test-utils.js +0 -23
- package/clis/toutiao/articles.test.js +0 -30
- package/dist/src/analysis.d.ts +0 -40
- package/dist/src/analysis.js +0 -172
package/clis/grok/ask.js
CHANGED
|
@@ -1,263 +1,26 @@
|
|
|
1
1
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { CommandExecutionError, TimeoutError } from '@jackwener/opencli/errors';
|
|
3
|
+
import {
|
|
4
|
+
authRequired,
|
|
5
|
+
ensureOnGrok,
|
|
6
|
+
getMessageBubbles,
|
|
7
|
+
isLoggedIn,
|
|
8
|
+
normalizeBooleanFlag,
|
|
9
|
+
sendMessage,
|
|
10
|
+
startNewChat,
|
|
11
|
+
waitForAnswer,
|
|
12
|
+
} from './utils.js';
|
|
13
|
+
|
|
6
14
|
const SESSION_HINT = 'Likely login/auth/challenge/session issue in the existing grok.com browser session.';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
function normalizeBooleanFlag(value) {
|
|
14
|
-
if (typeof value === 'boolean')
|
|
15
|
-
return value;
|
|
16
|
-
const normalized = String(value ?? '').trim().toLowerCase();
|
|
17
|
-
return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
|
|
18
|
-
}
|
|
19
|
-
function pickLatestAssistantCandidate(bubbles, baselineCount, prompt) {
|
|
20
|
-
const normalizedPrompt = prompt.trim();
|
|
21
|
-
const freshBubbles = bubbles
|
|
22
|
-
.slice(Math.max(0, baselineCount))
|
|
23
|
-
.map(normalizeBubbleText)
|
|
24
|
-
.filter(Boolean);
|
|
25
|
-
for (let i = freshBubbles.length - 1; i >= 0; i -= 1) {
|
|
26
|
-
if (freshBubbles[i] !== normalizedPrompt)
|
|
27
|
-
return freshBubbles[i];
|
|
15
|
+
|
|
16
|
+
async function getBaselineLastAssistantId(page) {
|
|
17
|
+
const bubbles = await getMessageBubbles(page);
|
|
18
|
+
for (let i = bubbles.length - 1; i >= 0; i -= 1) {
|
|
19
|
+
if (bubbles[i].role === 'Assistant') return bubbles[i].id;
|
|
28
20
|
}
|
|
29
21
|
return '';
|
|
30
22
|
}
|
|
31
|
-
function updateStableState(previousText, stableCount, nextText) {
|
|
32
|
-
if (!nextText)
|
|
33
|
-
return { previousText: '', stableCount: 0 };
|
|
34
|
-
if (nextText === previousText)
|
|
35
|
-
return { previousText, stableCount: stableCount + 1 };
|
|
36
|
-
return { previousText: nextText, stableCount: 0 };
|
|
37
|
-
}
|
|
38
|
-
/** Check whether the tab is already on grok.com (any path). */
|
|
39
|
-
async function isOnGrok(page) {
|
|
40
|
-
// catch handles blank tabs (about:blank) or detached pages
|
|
41
|
-
const url = await page.evaluate('window.location.href').catch(() => '');
|
|
42
|
-
if (typeof url !== 'string' || !url)
|
|
43
|
-
return false;
|
|
44
|
-
try {
|
|
45
|
-
const hostname = new URL(url).hostname;
|
|
46
|
-
return hostname === 'grok.com' || hostname.endsWith('.grok.com');
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async function runDefaultAsk(page, prompt, timeoutMs, newChat) {
|
|
53
|
-
if (newChat) {
|
|
54
|
-
// Explicitly start a fresh conversation via the homepage
|
|
55
|
-
await page.goto(GROK_URL);
|
|
56
|
-
await page.wait(2);
|
|
57
|
-
await tryStartFreshChat(page);
|
|
58
|
-
await page.wait(2);
|
|
59
|
-
}
|
|
60
|
-
else if (!(await isOnGrok(page))) {
|
|
61
|
-
// First invocation or tab was recycled — navigate to Grok
|
|
62
|
-
await page.goto(GROK_URL);
|
|
63
|
-
await page.wait(3);
|
|
64
|
-
}
|
|
65
|
-
const promptJson = JSON.stringify(prompt);
|
|
66
|
-
const sendResult = await page.evaluate(`(async () => {
|
|
67
|
-
try {
|
|
68
|
-
const box = document.querySelector('textarea');
|
|
69
|
-
if (!box) return { ok: false, msg: 'no textarea' };
|
|
70
|
-
box.focus(); box.value = '';
|
|
71
|
-
document.execCommand('selectAll');
|
|
72
|
-
document.execCommand('insertText', false, ${promptJson});
|
|
73
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
74
|
-
const btn = document.querySelector('button[aria-label="\\u63d0\\u4ea4"]');
|
|
75
|
-
if (btn && !btn.disabled) { btn.click(); return { ok: true, msg: 'clicked' }; }
|
|
76
|
-
const sub = [...document.querySelectorAll('button[type="submit"]')].find(b => !b.disabled);
|
|
77
|
-
if (sub) { sub.click(); return { ok: true, msg: 'clicked-submit' }; }
|
|
78
|
-
box.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
|
|
79
|
-
return { ok: true, msg: 'enter' };
|
|
80
|
-
} catch (e) { return { ok: false, msg: e.toString() }; }
|
|
81
|
-
})()`);
|
|
82
|
-
if (!sendResult || !sendResult.ok) {
|
|
83
|
-
return [{ response: '[SEND FAILED] ' + JSON.stringify(sendResult) }];
|
|
84
|
-
}
|
|
85
|
-
const startTime = Date.now();
|
|
86
|
-
let lastText = '';
|
|
87
|
-
let stableCount = 0;
|
|
88
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
89
|
-
await page.wait(3);
|
|
90
|
-
const response = await page.evaluate(`(() => {
|
|
91
|
-
const bubbles = document.querySelectorAll('div.message-bubble, [data-testid="message-bubble"]');
|
|
92
|
-
if (bubbles.length < 2) return '';
|
|
93
|
-
const last = bubbles[bubbles.length - 1];
|
|
94
|
-
const text = (last.innerText || '').trim();
|
|
95
|
-
if (!text || text.length < 2) return '';
|
|
96
|
-
return text;
|
|
97
|
-
})()`);
|
|
98
|
-
if (response && response.length > 2) {
|
|
99
|
-
if (response === lastText) {
|
|
100
|
-
stableCount++;
|
|
101
|
-
if (stableCount >= 2)
|
|
102
|
-
return [{ response }];
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
stableCount = 0;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
lastText = response || '';
|
|
109
|
-
}
|
|
110
|
-
if (lastText)
|
|
111
|
-
return [{ response: lastText }];
|
|
112
|
-
return [{ response: NO_RESPONSE_PREFIX }];
|
|
113
|
-
}
|
|
114
|
-
async function getBubbleTexts(page) {
|
|
115
|
-
const result = await page.evaluate(`(() => {
|
|
116
|
-
return Array.from(document.querySelectorAll(${JSON.stringify(RESPONSE_SELECTOR)}))
|
|
117
|
-
.map(node => (node instanceof HTMLElement ? node.innerText : node?.textContent || ''))
|
|
118
|
-
.map(text => (typeof text === 'string' ? text.trim() : ''))
|
|
119
|
-
.filter(Boolean);
|
|
120
|
-
})()`);
|
|
121
|
-
return Array.isArray(result) ? result.map(normalizeBubbleText).filter(Boolean) : [];
|
|
122
|
-
}
|
|
123
|
-
async function tryStartFreshChat(page) {
|
|
124
|
-
await page.evaluate(`(() => {
|
|
125
|
-
const isVisible = (node) => {
|
|
126
|
-
if (!(node instanceof HTMLElement)) return false;
|
|
127
|
-
const rect = node.getBoundingClientRect();
|
|
128
|
-
const style = window.getComputedStyle(node);
|
|
129
|
-
return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none';
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const candidates = Array.from(document.querySelectorAll('a, button')).filter(node => {
|
|
133
|
-
if (!isVisible(node)) return false;
|
|
134
|
-
const text = (node.textContent || '').trim().toLowerCase();
|
|
135
|
-
const aria = (node.getAttribute('aria-label') || '').trim().toLowerCase();
|
|
136
|
-
const href = node.getAttribute('href') || '';
|
|
137
|
-
return text.includes('new chat')
|
|
138
|
-
|| text.includes('new conversation')
|
|
139
|
-
|| aria.includes('new chat')
|
|
140
|
-
|| aria.includes('new conversation')
|
|
141
|
-
|| href === '/';
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
const target = candidates[0];
|
|
145
|
-
if (target instanceof HTMLElement) target.click();
|
|
146
|
-
})()`);
|
|
147
|
-
}
|
|
148
|
-
async function sendPromptViaExplicitWeb(page, prompt) {
|
|
149
|
-
return page.evaluate(`(async () => {
|
|
150
|
-
const waitFor = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
151
|
-
const composerSelector = '.ProseMirror[contenteditable="true"]';
|
|
152
|
-
let composer = null;
|
|
153
|
-
|
|
154
|
-
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
155
|
-
const candidate = document.querySelector(composerSelector);
|
|
156
|
-
if (candidate instanceof HTMLElement) {
|
|
157
|
-
composer = candidate;
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
await waitFor(1000);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (!(composer instanceof HTMLElement)) {
|
|
165
|
-
return {
|
|
166
|
-
ok: false,
|
|
167
|
-
reason: 'Grok composer was not found on grok.com.',
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const editor = composer.editor;
|
|
172
|
-
if (!editor?.commands?.focus || !editor?.commands?.insertContent) {
|
|
173
|
-
return {
|
|
174
|
-
ok: false,
|
|
175
|
-
reason: 'Grok composer editor API was unavailable.',
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
23
|
|
|
179
|
-
const isVisibleEnabledSubmit = (node) => {
|
|
180
|
-
if (!(node instanceof HTMLButtonElement)) return false;
|
|
181
|
-
const rect = node.getBoundingClientRect();
|
|
182
|
-
const style = window.getComputedStyle(node);
|
|
183
|
-
return !node.disabled
|
|
184
|
-
&& rect.width > 0
|
|
185
|
-
&& rect.height > 0
|
|
186
|
-
&& style.visibility !== 'hidden'
|
|
187
|
-
&& style.display !== 'none';
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
try {
|
|
191
|
-
if (editor.commands.clearContent) editor.commands.clearContent();
|
|
192
|
-
editor.commands.focus();
|
|
193
|
-
editor.commands.insertContent(${JSON.stringify(prompt)});
|
|
194
|
-
} catch (error) {
|
|
195
|
-
return {
|
|
196
|
-
ok: false,
|
|
197
|
-
reason: 'Failed to insert the prompt into the Grok composer.',
|
|
198
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
let submit = null;
|
|
203
|
-
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
204
|
-
const candidate = Array.from(document.querySelectorAll('button[aria-label="Submit"]'))
|
|
205
|
-
.find(isVisibleEnabledSubmit);
|
|
206
|
-
|
|
207
|
-
if (candidate instanceof HTMLButtonElement) {
|
|
208
|
-
submit = candidate;
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
await waitFor(500);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (!(submit instanceof HTMLButtonElement)) {
|
|
216
|
-
return {
|
|
217
|
-
ok: false,
|
|
218
|
-
reason: 'Grok submit button did not reach a clickable ready state after prompt insertion.',
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
submit.click();
|
|
223
|
-
return { ok: true };
|
|
224
|
-
})()`);
|
|
225
|
-
}
|
|
226
|
-
async function runExplicitWebAsk(page, prompt, timeoutMs, newChat) {
|
|
227
|
-
if (newChat) {
|
|
228
|
-
// Navigate to homepage and start a fresh conversation
|
|
229
|
-
await page.goto(GROK_URL, { settleMs: 2000 });
|
|
230
|
-
await tryStartFreshChat(page);
|
|
231
|
-
await page.wait(2);
|
|
232
|
-
}
|
|
233
|
-
else if (!(await isOnGrok(page))) {
|
|
234
|
-
// First invocation or tab was recycled — navigate to Grok
|
|
235
|
-
await page.goto(GROK_URL, { settleMs: 2000 });
|
|
236
|
-
}
|
|
237
|
-
const baselineBubbles = await getBubbleTexts(page);
|
|
238
|
-
const sendResult = await sendPromptViaExplicitWeb(page, prompt);
|
|
239
|
-
if (!sendResult?.ok) {
|
|
240
|
-
const details = sendResult?.detail ? ` ${sendResult.detail}` : '';
|
|
241
|
-
return blocked(`${sendResult?.reason || 'Unable to send the prompt to Grok.'}${details}`);
|
|
242
|
-
}
|
|
243
|
-
const startTime = Date.now();
|
|
244
|
-
let lastText = '';
|
|
245
|
-
let stableCount = 0;
|
|
246
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
247
|
-
await page.wait(2);
|
|
248
|
-
const bubbleTexts = await getBubbleTexts(page);
|
|
249
|
-
const candidate = pickLatestAssistantCandidate(bubbleTexts, baselineBubbles.length, prompt);
|
|
250
|
-
const nextState = updateStableState(lastText, stableCount, candidate);
|
|
251
|
-
lastText = nextState.previousText;
|
|
252
|
-
stableCount = nextState.stableCount;
|
|
253
|
-
if (candidate && stableCount >= 2) {
|
|
254
|
-
return [{ response: candidate }];
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (lastText)
|
|
258
|
-
return [{ response: lastText }];
|
|
259
|
-
return [{ response: `${NO_RESPONSE_PREFIX} No new assistant message bubble appeared within ${Math.round(timeoutMs / 1000)}s.` }];
|
|
260
|
-
}
|
|
261
24
|
export const askCommand = cli({
|
|
262
25
|
site: 'grok',
|
|
263
26
|
name: 'ask',
|
|
@@ -266,28 +29,49 @@ export const askCommand = cli({
|
|
|
266
29
|
domain: 'grok.com',
|
|
267
30
|
strategy: Strategy.COOKIE,
|
|
268
31
|
browser: true,
|
|
32
|
+
browserSession: { reuse: 'site' },
|
|
269
33
|
args: [
|
|
270
34
|
{ name: 'prompt', positional: true, type: 'string', required: true, help: 'Prompt to send to Grok' },
|
|
271
35
|
{ name: 'timeout', type: 'int', default: 120, help: 'Max seconds to wait for response (default: 120)' },
|
|
272
36
|
{ name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending (default: false)' },
|
|
273
|
-
{ name: 'web', type: 'boolean', default: false, help: 'Use the explicit grok.com consumer web flow (default: false)' },
|
|
274
37
|
],
|
|
275
38
|
columns: ['response'],
|
|
276
39
|
func: async (page, kwargs) => {
|
|
277
40
|
const prompt = kwargs.prompt;
|
|
278
|
-
const
|
|
41
|
+
const timeoutSeconds = kwargs.timeout || 120;
|
|
279
42
|
const newChat = normalizeBooleanFlag(kwargs.new);
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
|
|
43
|
+
|
|
44
|
+
if (newChat) {
|
|
45
|
+
await startNewChat(page);
|
|
46
|
+
} else {
|
|
47
|
+
await ensureOnGrok(page);
|
|
283
48
|
}
|
|
284
|
-
|
|
49
|
+
|
|
50
|
+
if (!(await isLoggedIn(page))) {
|
|
51
|
+
throw authRequired();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const baselineLastAssistantId = await getBaselineLastAssistantId(page);
|
|
55
|
+
const sendResult = await sendMessage(page, prompt);
|
|
56
|
+
if (!sendResult || !sendResult.ok) {
|
|
57
|
+
const reason = sendResult?.reason || 'Unable to send the prompt to Grok.';
|
|
58
|
+
const detail = sendResult?.detail ? ` ${sendResult.detail}` : '';
|
|
59
|
+
throw new CommandExecutionError(`${reason}${detail}`, SESSION_HINT);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const result = await waitForAnswer(page, prompt, timeoutSeconds, baselineLastAssistantId);
|
|
63
|
+
if (result.status === 'ok') {
|
|
64
|
+
return [{ response: result.assistant.text }];
|
|
65
|
+
}
|
|
66
|
+
// Partial: streaming was seen but did not stabilize; keep the best-effort
|
|
67
|
+
// text rather than throwing — the caller asked us to wait, not to discard.
|
|
68
|
+
if (result.status === 'partial' && result.assistant) {
|
|
69
|
+
return [{ response: result.assistant.text }];
|
|
70
|
+
}
|
|
71
|
+
throw new TimeoutError('grok ask response', timeoutSeconds);
|
|
285
72
|
},
|
|
286
73
|
});
|
|
74
|
+
|
|
287
75
|
export const __test__ = {
|
|
288
|
-
|
|
289
|
-
updateStableState,
|
|
290
|
-
normalizeBooleanFlag,
|
|
291
|
-
normalizeBubbleText,
|
|
292
|
-
isOnGrok,
|
|
76
|
+
getBaselineLastAssistantId,
|
|
293
77
|
};
|
package/clis/grok/ask.test.js
CHANGED
|
@@ -1,54 +1,29 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { __test__ } from './ask.js';
|
|
3
|
+
|
|
3
4
|
describe('grok ask helpers', () => {
|
|
4
|
-
describe('
|
|
5
|
-
const fakePage = (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
describe('getBaselineLastAssistantId', () => {
|
|
6
|
+
const fakePage = (bubbles) => ({
|
|
7
|
+
// getMessageBubbles in utils.js drives a page.evaluate; in tests we
|
|
8
|
+
// route directly to the in-memory bubble array.
|
|
9
|
+
evaluate: () => Promise.resolve(bubbles),
|
|
9
10
|
});
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
it('returns the id of the most recent Assistant bubble, ignoring later User turns', async () => {
|
|
13
|
+
const bubbles = [
|
|
14
|
+
{ id: 'a1', role: 'Assistant', text: 'first answer', html: '' },
|
|
15
|
+
{ id: 'u1', role: 'User', text: 'follow-up', html: '' },
|
|
16
|
+
{ id: 'a2', role: 'Assistant', text: 'second answer', html: '' },
|
|
17
|
+
{ id: 'u2', role: 'User', text: 'newest user turn', html: '' },
|
|
18
|
+
];
|
|
19
|
+
expect(await __test__.getBaselineLastAssistantId(fakePage(bubbles))).toBe('a2');
|
|
12
20
|
});
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
expect(await __test__.
|
|
16
|
-
expect(await __test__.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
expect(await __test__.isOnGrok(fakePage(new Error('detached')))).toBe(false);
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
it('normalizes boolean flags for explicit web routing', () => {
|
|
23
|
-
expect(__test__.normalizeBooleanFlag(true)).toBe(true);
|
|
24
|
-
expect(__test__.normalizeBooleanFlag('true')).toBe(true);
|
|
25
|
-
expect(__test__.normalizeBooleanFlag('1')).toBe(true);
|
|
26
|
-
expect(__test__.normalizeBooleanFlag('yes')).toBe(true);
|
|
27
|
-
expect(__test__.normalizeBooleanFlag('on')).toBe(true);
|
|
28
|
-
expect(__test__.normalizeBooleanFlag(false)).toBe(false);
|
|
29
|
-
expect(__test__.normalizeBooleanFlag('false')).toBe(false);
|
|
30
|
-
expect(__test__.normalizeBooleanFlag(undefined)).toBe(false);
|
|
31
|
-
});
|
|
32
|
-
it('ignores baseline bubbles and the echoed prompt when choosing the latest assistant candidate', () => {
|
|
33
|
-
const candidate = __test__.pickLatestAssistantCandidate(['older assistant answer', 'Prompt text', 'Assistant draft', 'Assistant final'], 1, 'Prompt text');
|
|
34
|
-
expect(candidate).toBe('Assistant final');
|
|
35
|
-
});
|
|
36
|
-
it('returns empty when only the echoed prompt appeared after send', () => {
|
|
37
|
-
const candidate = __test__.pickLatestAssistantCandidate(['older assistant answer', 'Prompt text'], 1, 'Prompt text');
|
|
38
|
-
expect(candidate).toBe('');
|
|
39
|
-
});
|
|
40
|
-
it('tracks stabilization by incrementing repeats and resetting on changes', () => {
|
|
41
|
-
expect(__test__.updateStableState('', 0, 'First chunk')).toEqual({
|
|
42
|
-
previousText: 'First chunk',
|
|
43
|
-
stableCount: 0,
|
|
44
|
-
});
|
|
45
|
-
expect(__test__.updateStableState('First chunk', 0, 'First chunk')).toEqual({
|
|
46
|
-
previousText: 'First chunk',
|
|
47
|
-
stableCount: 1,
|
|
48
|
-
});
|
|
49
|
-
expect(__test__.updateStableState('First chunk', 1, 'Second chunk')).toEqual({
|
|
50
|
-
previousText: 'Second chunk',
|
|
51
|
-
stableCount: 0,
|
|
21
|
+
|
|
22
|
+
it('returns empty string when no Assistant bubble exists yet (fresh chat)', async () => {
|
|
23
|
+
expect(await __test__.getBaselineLastAssistantId(fakePage([]))).toBe('');
|
|
24
|
+
expect(await __test__.getBaselineLastAssistantId(fakePage([
|
|
25
|
+
{ id: 'u1', role: 'User', text: 'hello', html: '' },
|
|
26
|
+
]))).toBe('');
|
|
52
27
|
});
|
|
53
28
|
});
|
|
54
29
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
import { EmptyResultError } from '@jackwener/opencli/errors';
|
|
3
|
+
import {
|
|
4
|
+
GROK_DOMAIN,
|
|
5
|
+
bubbleHtmlToMarkdown,
|
|
6
|
+
getMessageBubbles,
|
|
7
|
+
normalizeBooleanFlag,
|
|
8
|
+
parseGrokSessionId,
|
|
9
|
+
} from './utils.js';
|
|
10
|
+
|
|
11
|
+
cli({
|
|
12
|
+
site: 'grok',
|
|
13
|
+
name: 'detail',
|
|
14
|
+
access: 'read',
|
|
15
|
+
description: 'Open a Grok conversation by ID and read its messages',
|
|
16
|
+
domain: GROK_DOMAIN,
|
|
17
|
+
strategy: Strategy.COOKIE,
|
|
18
|
+
browser: true,
|
|
19
|
+
navigateBefore: false,
|
|
20
|
+
browserSession: { reuse: 'site' },
|
|
21
|
+
args: [
|
|
22
|
+
{ name: 'id', positional: true, required: true, help: 'Session ID (UUID) or full https://grok.com/c/<id> URL' },
|
|
23
|
+
{ name: 'markdown', type: 'boolean', default: false, help: 'Emit assistant replies as markdown' },
|
|
24
|
+
],
|
|
25
|
+
columns: ['Role', 'Text'],
|
|
26
|
+
func: async (page, kwargs) => {
|
|
27
|
+
const sessionId = parseGrokSessionId(kwargs.id);
|
|
28
|
+
const wantMarkdown = normalizeBooleanFlag(kwargs.markdown, false);
|
|
29
|
+
|
|
30
|
+
await page.goto(`https://grok.com/c/${sessionId}`);
|
|
31
|
+
await page.wait(2);
|
|
32
|
+
|
|
33
|
+
// Poll for the conversation transcript to load. Grok renders the chat
|
|
34
|
+
// page shell before fetching message history, so a fixed wait can race
|
|
35
|
+
// the initial empty render. Cap at ~20s so genuinely missing IDs surface
|
|
36
|
+
// an EmptyResultError rather than hanging.
|
|
37
|
+
let bubbles = [];
|
|
38
|
+
const POLL_DEADLINE_MS = 20_000;
|
|
39
|
+
const POLL_INTERVAL_S = 1;
|
|
40
|
+
const startedAt = Date.now();
|
|
41
|
+
while (Date.now() - startedAt < POLL_DEADLINE_MS) {
|
|
42
|
+
bubbles = await getMessageBubbles(page);
|
|
43
|
+
if (bubbles.length > 0) break;
|
|
44
|
+
await page.wait(POLL_INTERVAL_S);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!bubbles.length) {
|
|
48
|
+
throw new EmptyResultError(
|
|
49
|
+
'grok detail',
|
|
50
|
+
`No visible messages found for conversation ${sessionId}. Verify the ID is correct and that the conversation belongs to the signed-in account.`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return bubbles.map((b) => ({
|
|
54
|
+
Role: b.role,
|
|
55
|
+
Text: wantMarkdown && b.role === 'Assistant' && b.html
|
|
56
|
+
? (bubbleHtmlToMarkdown(b.html) || b.text)
|
|
57
|
+
: b.text,
|
|
58
|
+
}));
|
|
59
|
+
},
|
|
60
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
import { ArgumentError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
3
|
+
import {
|
|
4
|
+
GROK_DOMAIN,
|
|
5
|
+
authRequired,
|
|
6
|
+
ensureOnGrok,
|
|
7
|
+
getHistoryFromSidebar,
|
|
8
|
+
isLoggedIn,
|
|
9
|
+
} from './utils.js';
|
|
10
|
+
|
|
11
|
+
cli({
|
|
12
|
+
site: 'grok',
|
|
13
|
+
name: 'history',
|
|
14
|
+
access: 'read',
|
|
15
|
+
description: 'List recent Grok conversations from the sidebar (requires login)',
|
|
16
|
+
domain: GROK_DOMAIN,
|
|
17
|
+
strategy: Strategy.COOKIE,
|
|
18
|
+
browser: true,
|
|
19
|
+
browserSession: { reuse: 'site' },
|
|
20
|
+
navigateBefore: false,
|
|
21
|
+
args: [
|
|
22
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Max conversations to show (default 20, max 100)' },
|
|
23
|
+
],
|
|
24
|
+
columns: ['Index', 'Title', 'Url'],
|
|
25
|
+
func: async (page, kwargs) => {
|
|
26
|
+
const limit = Number(kwargs.limit ?? 20);
|
|
27
|
+
if (!Number.isInteger(limit) || limit <= 0) {
|
|
28
|
+
throw new ArgumentError('limit', 'must be a positive integer');
|
|
29
|
+
}
|
|
30
|
+
if (limit > 100) {
|
|
31
|
+
throw new ArgumentError('limit', 'must be <= 100');
|
|
32
|
+
}
|
|
33
|
+
await ensureOnGrok(page);
|
|
34
|
+
await page.wait(2);
|
|
35
|
+
if (!(await isLoggedIn(page))) {
|
|
36
|
+
throw authRequired();
|
|
37
|
+
}
|
|
38
|
+
const sessions = await getHistoryFromSidebar(page, limit);
|
|
39
|
+
if (!sessions.length) {
|
|
40
|
+
throw new EmptyResultError('grok history', 'No Grok conversations found in the sidebar for the signed-in account.');
|
|
41
|
+
}
|
|
42
|
+
return sessions.slice(0, limit).map((s, i) => ({
|
|
43
|
+
Index: i + 1,
|
|
44
|
+
Title: s.title || '(untitled)',
|
|
45
|
+
Url: `https://grok.com/c/${s.id}`,
|
|
46
|
+
}));
|
|
47
|
+
},
|
|
48
|
+
});
|