@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.
Files changed (523) hide show
  1. package/.github/workflows/build-extension.yml +3 -3
  2. package/.github/workflows/ci.yml +6 -6
  3. package/.github/workflows/doc-check.yml +3 -3
  4. package/.github/workflows/e2e-headed.yml +2 -2
  5. package/.github/workflows/pkg-pr-new.yml +2 -2
  6. package/.github/workflows/release.yml +3 -3
  7. package/.github/workflows/security.yml +2 -2
  8. package/CONTRIBUTING.md +39 -1
  9. package/README.md +13 -10
  10. package/README.zh-CN.md +43 -17
  11. package/SKILL.md +10 -5
  12. package/dist/browser/cdp.d.ts +4 -4
  13. package/dist/browser/cdp.js +39 -16
  14. package/dist/browser/daemon-client.d.ts +4 -2
  15. package/dist/browser/daemon-client.js +17 -4
  16. package/dist/browser/dom-helpers.js +38 -7
  17. package/dist/browser/dom-snapshot.d.ts +86 -0
  18. package/dist/browser/dom-snapshot.js +729 -0
  19. package/dist/browser/dom-snapshot.test.d.ts +11 -0
  20. package/dist/browser/dom-snapshot.test.js +212 -0
  21. package/dist/browser/index.d.ts +2 -0
  22. package/dist/browser/index.js +1 -0
  23. package/dist/browser/mcp.js +3 -1
  24. package/dist/browser/page.d.ts +14 -24
  25. package/dist/browser/page.js +46 -6
  26. package/dist/build-manifest.d.ts +11 -4
  27. package/dist/build-manifest.js +59 -21
  28. package/dist/build-manifest.test.js +58 -2
  29. package/dist/cli-manifest.json +3856 -1509
  30. package/dist/cli.js +66 -0
  31. package/dist/clis/barchart/greeks.js +1 -1
  32. package/dist/clis/barchart/options.js +1 -1
  33. package/dist/clis/barchart/quote.js +1 -1
  34. package/dist/clis/bilibili/download.js +1 -1
  35. package/dist/clis/bilibili/following.js +1 -1
  36. package/dist/clis/bilibili/subtitle.js +1 -1
  37. package/dist/clis/bilibili/user-videos.js +1 -1
  38. package/dist/clis/boss/batchgreet.js +10 -97
  39. package/dist/clis/boss/chatlist.js +8 -25
  40. package/dist/clis/boss/chatmsg.js +11 -42
  41. package/dist/clis/boss/common.d.ts +92 -0
  42. package/dist/clis/boss/common.js +223 -0
  43. package/dist/clis/boss/detail.js +7 -49
  44. package/dist/clis/boss/exchange.js +13 -79
  45. package/dist/clis/boss/greet.js +18 -145
  46. package/dist/clis/boss/invite.js +26 -121
  47. package/dist/clis/boss/joblist.js +6 -31
  48. package/dist/clis/boss/mark.js +12 -85
  49. package/dist/clis/boss/recommend.js +10 -49
  50. package/dist/clis/boss/resume.js +18 -118
  51. package/dist/clis/boss/search.js +12 -60
  52. package/dist/clis/boss/send.js +17 -151
  53. package/dist/clis/boss/stats.js +18 -69
  54. package/dist/clis/coupang/add-to-cart.js +1 -1
  55. package/dist/clis/devto/tag.yaml +34 -0
  56. package/dist/clis/devto/top.yaml +29 -0
  57. package/dist/clis/devto/user.yaml +33 -0
  58. package/dist/clis/douban/book-hot.d.ts +1 -0
  59. package/dist/clis/douban/book-hot.js +14 -0
  60. package/dist/clis/douban/marks.d.ts +1 -0
  61. package/dist/clis/douban/marks.js +115 -0
  62. package/dist/clis/douban/movie-hot.d.ts +1 -0
  63. package/dist/clis/douban/movie-hot.js +14 -0
  64. package/dist/clis/douban/reviews.d.ts +1 -0
  65. package/dist/clis/douban/reviews.js +106 -0
  66. package/dist/clis/douban/search.d.ts +1 -0
  67. package/dist/clis/douban/search.js +16 -0
  68. package/dist/clis/douban/shared.d.ts +4 -0
  69. package/dist/clis/douban/shared.js +155 -0
  70. package/dist/clis/douban/subject.yaml +76 -0
  71. package/dist/clis/douban/top250.yaml +70 -0
  72. package/dist/clis/douban/utils.d.ts +35 -0
  73. package/dist/clis/douban/utils.js +48 -0
  74. package/dist/clis/facebook/add-friend.yaml +43 -0
  75. package/dist/clis/facebook/events.yaml +44 -0
  76. package/dist/clis/facebook/feed.yaml +63 -0
  77. package/dist/clis/facebook/friends.yaml +42 -0
  78. package/dist/clis/facebook/groups.yaml +50 -0
  79. package/dist/clis/facebook/join-group.yaml +44 -0
  80. package/dist/clis/facebook/memories.yaml +39 -0
  81. package/dist/clis/facebook/notifications.yaml +40 -0
  82. package/dist/clis/facebook/profile.yaml +37 -0
  83. package/dist/clis/facebook/search.yaml +46 -0
  84. package/dist/clis/google/news.d.ts +5 -0
  85. package/dist/clis/google/news.js +58 -0
  86. package/dist/clis/google/search.d.ts +10 -0
  87. package/dist/clis/google/search.js +127 -0
  88. package/dist/clis/google/suggest.d.ts +5 -0
  89. package/dist/clis/google/suggest.js +34 -0
  90. package/dist/clis/google/trends.d.ts +5 -0
  91. package/dist/clis/google/trends.js +38 -0
  92. package/dist/clis/google/utils.d.ts +9 -0
  93. package/dist/clis/google/utils.js +23 -0
  94. package/dist/clis/google/utils.test.d.ts +1 -0
  95. package/dist/clis/google/utils.test.js +75 -0
  96. package/dist/clis/grok/ask.d.ts +14 -0
  97. package/dist/clis/grok/ask.js +257 -65
  98. package/dist/clis/grok/ask.test.d.ts +1 -0
  99. package/dist/clis/grok/ask.test.js +36 -0
  100. package/dist/clis/instagram/comment.yaml +52 -0
  101. package/dist/clis/instagram/explore.yaml +43 -0
  102. package/dist/clis/instagram/follow.yaml +41 -0
  103. package/dist/clis/instagram/followers.yaml +51 -0
  104. package/dist/clis/instagram/following.yaml +51 -0
  105. package/dist/clis/instagram/like.yaml +46 -0
  106. package/dist/clis/instagram/profile.yaml +42 -0
  107. package/dist/clis/instagram/save.yaml +46 -0
  108. package/dist/clis/instagram/saved.yaml +40 -0
  109. package/dist/clis/instagram/search.yaml +43 -0
  110. package/dist/clis/instagram/unfollow.yaml +38 -0
  111. package/dist/clis/instagram/unlike.yaml +46 -0
  112. package/dist/clis/instagram/unsave.yaml +46 -0
  113. package/dist/clis/instagram/user.yaml +54 -0
  114. package/dist/clis/jike/repost.js +1 -1
  115. package/dist/clis/jimeng/generate.yaml +1 -0
  116. package/dist/clis/linux-do/category.yaml +1 -0
  117. package/dist/clis/lobsters/active.yaml +29 -0
  118. package/dist/clis/lobsters/hot.yaml +29 -0
  119. package/dist/clis/lobsters/newest.yaml +29 -0
  120. package/dist/clis/lobsters/tag.yaml +34 -0
  121. package/dist/clis/medium/feed.d.ts +1 -0
  122. package/dist/clis/medium/feed.js +15 -0
  123. package/dist/clis/medium/search.d.ts +1 -0
  124. package/dist/clis/medium/search.js +15 -0
  125. package/dist/clis/medium/shared.d.ts +5 -0
  126. package/dist/clis/medium/shared.js +78 -0
  127. package/dist/clis/medium/user.d.ts +1 -0
  128. package/dist/clis/medium/user.js +15 -0
  129. package/dist/clis/reddit/comment.js +1 -1
  130. package/dist/clis/reddit/read.js +1 -1
  131. package/dist/clis/reddit/save.js +1 -1
  132. package/dist/clis/reddit/subreddit.yaml +1 -0
  133. package/dist/clis/reddit/subscribe.js +1 -1
  134. package/dist/clis/reddit/upvote.js +1 -1
  135. package/dist/clis/sinablog/article.d.ts +1 -0
  136. package/dist/clis/sinablog/article.js +14 -0
  137. package/dist/clis/sinablog/hot.d.ts +1 -0
  138. package/dist/clis/sinablog/hot.js +14 -0
  139. package/dist/clis/sinablog/search.d.ts +1 -0
  140. package/dist/clis/sinablog/search.js +51 -0
  141. package/dist/clis/sinablog/shared.d.ts +7 -0
  142. package/dist/clis/sinablog/shared.js +187 -0
  143. package/dist/clis/sinablog/user.d.ts +1 -0
  144. package/dist/clis/sinablog/user.js +15 -0
  145. package/dist/clis/substack/feed.d.ts +1 -0
  146. package/dist/clis/substack/feed.js +15 -0
  147. package/dist/clis/substack/publication.d.ts +1 -0
  148. package/dist/clis/substack/publication.js +15 -0
  149. package/dist/clis/substack/search.d.ts +1 -0
  150. package/dist/clis/substack/search.js +77 -0
  151. package/dist/clis/substack/shared.d.ts +4 -0
  152. package/dist/clis/substack/shared.js +129 -0
  153. package/dist/clis/tiktok/comment.yaml +66 -0
  154. package/dist/clis/tiktok/explore.yaml +39 -0
  155. package/dist/clis/tiktok/follow.yaml +39 -0
  156. package/dist/clis/tiktok/following.yaml +46 -0
  157. package/dist/clis/tiktok/friends.yaml +47 -0
  158. package/dist/clis/tiktok/like.yaml +38 -0
  159. package/dist/clis/tiktok/live.yaml +51 -0
  160. package/dist/clis/tiktok/notifications.yaml +52 -0
  161. package/dist/clis/tiktok/profile.yaml +45 -0
  162. package/dist/clis/tiktok/save.yaml +34 -0
  163. package/dist/clis/tiktok/search.yaml +46 -0
  164. package/dist/clis/tiktok/unfollow.yaml +44 -0
  165. package/dist/clis/tiktok/unlike.yaml +38 -0
  166. package/dist/clis/tiktok/unsave.yaml +36 -0
  167. package/dist/clis/tiktok/user.yaml +44 -0
  168. package/dist/clis/twitter/download.d.ts +1 -1
  169. package/dist/clis/twitter/download.js +3 -3
  170. package/dist/clis/twitter/followers.js +1 -1
  171. package/dist/clis/twitter/following.js +1 -1
  172. package/dist/clis/twitter/thread.js +1 -1
  173. package/dist/clis/twitter/timeline.d.ts +23 -0
  174. package/dist/clis/twitter/timeline.js +42 -14
  175. package/dist/clis/twitter/timeline.test.d.ts +1 -0
  176. package/dist/clis/twitter/timeline.test.js +102 -0
  177. package/dist/clis/wikipedia/random.d.ts +1 -0
  178. package/dist/clis/wikipedia/random.js +19 -0
  179. package/dist/clis/wikipedia/search.js +3 -3
  180. package/dist/clis/wikipedia/summary.js +4 -9
  181. package/dist/clis/wikipedia/trending.d.ts +1 -0
  182. package/dist/clis/wikipedia/trending.js +35 -0
  183. package/dist/clis/wikipedia/utils.d.ts +28 -0
  184. package/dist/clis/wikipedia/utils.js +13 -0
  185. package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
  186. package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
  187. package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
  188. package/dist/clis/xiaohongshu/download.js +1 -1
  189. package/dist/clis/xueqiu/earnings-date.yaml +69 -0
  190. package/dist/clis/xueqiu/search.yaml +2 -1
  191. package/dist/clis/xueqiu/stock.yaml +2 -0
  192. package/dist/clis/yahoo-finance/quote.js +1 -1
  193. package/dist/commanderAdapter.js +13 -7
  194. package/dist/daemon.js +21 -0
  195. package/dist/discovery.d.ts +8 -0
  196. package/dist/discovery.js +105 -19
  197. package/dist/doctor.js +3 -1
  198. package/dist/doctor.test.js +46 -2
  199. package/dist/engine.test.d.ts +0 -3
  200. package/dist/engine.test.js +74 -6
  201. package/dist/execution.d.ts +4 -2
  202. package/dist/execution.js +31 -7
  203. package/dist/explore.d.ts +76 -3
  204. package/dist/explore.js +11 -4
  205. package/dist/generate.d.ts +41 -2
  206. package/dist/generate.js +5 -4
  207. package/dist/main.js +2 -1
  208. package/dist/pipeline/executor.d.ts +4 -2
  209. package/dist/pipeline/executor.js +54 -15
  210. package/dist/pipeline/executor.test.js +33 -6
  211. package/dist/pipeline/registry.d.ts +1 -1
  212. package/dist/pipeline/steps/browser.d.ts +7 -7
  213. package/dist/pipeline/steps/browser.js +15 -7
  214. package/dist/pipeline/steps/fetch.d.ts +1 -1
  215. package/dist/pipeline/steps/fetch.js +11 -7
  216. package/dist/pipeline/steps/transform.d.ts +6 -5
  217. package/dist/pipeline/steps/transform.js +30 -9
  218. package/dist/pipeline/template.d.ts +6 -6
  219. package/dist/pipeline/template.js +43 -5
  220. package/dist/pipeline/template.test.js +18 -0
  221. package/dist/pipeline/transform.test.js +11 -0
  222. package/dist/plugin.d.ts +31 -0
  223. package/dist/plugin.js +216 -0
  224. package/dist/plugin.test.d.ts +4 -0
  225. package/dist/plugin.test.js +76 -0
  226. package/dist/registry-api.d.ts +11 -0
  227. package/dist/registry-api.js +9 -0
  228. package/dist/registry.d.ts +11 -0
  229. package/dist/registry.js +6 -1
  230. package/dist/synthesize.d.ts +94 -4
  231. package/dist/synthesize.js +5 -4
  232. package/dist/types.d.ts +39 -26
  233. package/dist/validate.js +8 -2
  234. package/docs/.vitepress/config.mts +6 -4
  235. package/docs/adapters/browser/barchart.md +6 -5
  236. package/docs/adapters/browser/bilibili.md +9 -0
  237. package/docs/adapters/browser/devto.md +35 -0
  238. package/docs/adapters/browser/douban.md +38 -0
  239. package/docs/adapters/browser/facebook.md +36 -0
  240. package/docs/adapters/browser/google.md +62 -0
  241. package/docs/adapters/browser/grok.md +26 -8
  242. package/docs/adapters/browser/instagram.md +46 -0
  243. package/docs/adapters/browser/lobsters.md +32 -0
  244. package/docs/adapters/browser/medium.md +32 -0
  245. package/docs/adapters/browser/reddit.md +9 -0
  246. package/docs/adapters/browser/sinablog.md +36 -0
  247. package/docs/adapters/browser/substack.md +38 -0
  248. package/docs/adapters/browser/tiktok.md +68 -0
  249. package/docs/adapters/browser/wikipedia.md +11 -2
  250. package/docs/adapters/browser/xueqiu.md +10 -0
  251. package/docs/adapters/browser/yahoo-finance.md +6 -5
  252. package/docs/adapters/desktop/antigravity.md +6 -0
  253. package/docs/adapters/desktop/chatgpt.md +2 -1
  254. package/docs/adapters/desktop/codex.md +5 -1
  255. package/docs/adapters/desktop/cursor.md +4 -0
  256. package/docs/adapters/desktop/discord.md +7 -7
  257. package/docs/adapters/index.md +1 -4
  258. package/docs/guide/getting-started.md +1 -0
  259. package/docs/guide/plugins.md +153 -0
  260. package/docs/zh/guide/plugins.md +107 -0
  261. package/extension/dist/background.js +91 -23
  262. package/extension/src/background.ts +82 -29
  263. package/extension/src/cdp.ts +42 -1
  264. package/package.json +10 -5
  265. package/scripts/clean-dist.cjs +13 -0
  266. package/src/browser/cdp.ts +71 -31
  267. package/src/browser/daemon-client.ts +21 -5
  268. package/src/browser/dom-helpers.ts +38 -7
  269. package/src/browser/dom-snapshot.test.ts +249 -0
  270. package/src/browser/dom-snapshot.ts +770 -0
  271. package/src/browser/index.ts +2 -0
  272. package/src/browser/mcp.ts +3 -1
  273. package/src/browser/page.ts +57 -21
  274. package/src/build-manifest.test.ts +70 -2
  275. package/src/build-manifest.ts +94 -26
  276. package/src/cli.ts +71 -2
  277. package/src/clis/barchart/greeks.ts +1 -1
  278. package/src/clis/barchart/options.ts +1 -1
  279. package/src/clis/barchart/quote.ts +1 -1
  280. package/src/clis/bilibili/download.ts +1 -1
  281. package/src/clis/bilibili/following.ts +1 -1
  282. package/src/clis/bilibili/subtitle.ts +1 -1
  283. package/src/clis/bilibili/user-videos.ts +1 -1
  284. package/src/clis/boss/batchgreet.ts +14 -106
  285. package/src/clis/boss/chatlist.ts +12 -26
  286. package/src/clis/boss/chatmsg.ts +16 -40
  287. package/src/clis/boss/common.ts +287 -0
  288. package/src/clis/boss/detail.ts +8 -54
  289. package/src/clis/boss/exchange.ts +15 -89
  290. package/src/clis/boss/greet.ts +23 -160
  291. package/src/clis/boss/invite.ts +36 -133
  292. package/src/clis/boss/joblist.ts +7 -36
  293. package/src/clis/boss/mark.ts +13 -94
  294. package/src/clis/boss/recommend.ts +12 -57
  295. package/src/clis/boss/resume.ts +19 -124
  296. package/src/clis/boss/search.ts +13 -66
  297. package/src/clis/boss/send.ts +21 -161
  298. package/src/clis/boss/stats.ts +19 -74
  299. package/src/clis/coupang/add-to-cart.ts +1 -1
  300. package/src/clis/devto/tag.yaml +34 -0
  301. package/src/clis/devto/top.yaml +29 -0
  302. package/src/clis/devto/user.yaml +33 -0
  303. package/src/clis/douban/book-hot.ts +15 -0
  304. package/src/clis/douban/marks.ts +135 -0
  305. package/src/clis/douban/movie-hot.ts +15 -0
  306. package/src/clis/douban/reviews.ts +127 -0
  307. package/src/clis/douban/search.ts +17 -0
  308. package/src/clis/douban/shared.ts +165 -0
  309. package/src/clis/douban/subject.yaml +76 -0
  310. package/src/clis/douban/top250.yaml +70 -0
  311. package/src/clis/douban/utils.ts +81 -0
  312. package/src/clis/facebook/add-friend.yaml +43 -0
  313. package/src/clis/facebook/events.yaml +44 -0
  314. package/src/clis/facebook/feed.yaml +63 -0
  315. package/src/clis/facebook/friends.yaml +42 -0
  316. package/src/clis/facebook/groups.yaml +50 -0
  317. package/src/clis/facebook/join-group.yaml +44 -0
  318. package/src/clis/facebook/memories.yaml +39 -0
  319. package/src/clis/facebook/notifications.yaml +40 -0
  320. package/src/clis/facebook/profile.yaml +37 -0
  321. package/src/clis/facebook/search.yaml +46 -0
  322. package/src/clis/google/news.ts +66 -0
  323. package/src/clis/google/search.ts +133 -0
  324. package/src/clis/google/suggest.ts +40 -0
  325. package/src/clis/google/trends.ts +44 -0
  326. package/src/clis/google/utils.test.ts +82 -0
  327. package/src/clis/google/utils.ts +24 -0
  328. package/src/clis/grok/ask.test.ts +53 -0
  329. package/src/clis/grok/ask.ts +300 -69
  330. package/src/clis/instagram/comment.yaml +52 -0
  331. package/src/clis/instagram/explore.yaml +43 -0
  332. package/src/clis/instagram/follow.yaml +41 -0
  333. package/src/clis/instagram/followers.yaml +51 -0
  334. package/src/clis/instagram/following.yaml +51 -0
  335. package/src/clis/instagram/like.yaml +46 -0
  336. package/src/clis/instagram/profile.yaml +42 -0
  337. package/src/clis/instagram/save.yaml +46 -0
  338. package/src/clis/instagram/saved.yaml +40 -0
  339. package/src/clis/instagram/search.yaml +43 -0
  340. package/src/clis/instagram/unfollow.yaml +38 -0
  341. package/src/clis/instagram/unlike.yaml +46 -0
  342. package/src/clis/instagram/unsave.yaml +46 -0
  343. package/src/clis/instagram/user.yaml +54 -0
  344. package/src/clis/jike/repost.ts +1 -1
  345. package/src/clis/jimeng/generate.yaml +1 -0
  346. package/src/clis/linux-do/category.yaml +1 -0
  347. package/src/clis/lobsters/active.yaml +29 -0
  348. package/src/clis/lobsters/hot.yaml +29 -0
  349. package/src/clis/lobsters/newest.yaml +29 -0
  350. package/src/clis/lobsters/tag.yaml +34 -0
  351. package/src/clis/medium/feed.ts +16 -0
  352. package/src/clis/medium/search.ts +16 -0
  353. package/src/clis/medium/shared.ts +83 -0
  354. package/src/clis/medium/user.ts +16 -0
  355. package/src/clis/reddit/comment.ts +1 -1
  356. package/src/clis/reddit/read.ts +1 -1
  357. package/src/clis/reddit/save.ts +1 -1
  358. package/src/clis/reddit/subreddit.yaml +1 -0
  359. package/src/clis/reddit/subscribe.ts +1 -1
  360. package/src/clis/reddit/upvote.ts +1 -1
  361. package/src/clis/sinablog/article.ts +15 -0
  362. package/src/clis/sinablog/hot.ts +15 -0
  363. package/src/clis/sinablog/search.ts +56 -0
  364. package/src/clis/sinablog/shared.ts +198 -0
  365. package/src/clis/sinablog/user.ts +16 -0
  366. package/src/clis/substack/feed.ts +16 -0
  367. package/src/clis/substack/publication.ts +16 -0
  368. package/src/clis/substack/search.ts +91 -0
  369. package/src/clis/substack/shared.ts +132 -0
  370. package/src/clis/tiktok/comment.yaml +66 -0
  371. package/src/clis/tiktok/explore.yaml +39 -0
  372. package/src/clis/tiktok/follow.yaml +39 -0
  373. package/src/clis/tiktok/following.yaml +46 -0
  374. package/src/clis/tiktok/friends.yaml +47 -0
  375. package/src/clis/tiktok/like.yaml +38 -0
  376. package/src/clis/tiktok/live.yaml +51 -0
  377. package/src/clis/tiktok/notifications.yaml +52 -0
  378. package/src/clis/tiktok/profile.yaml +45 -0
  379. package/src/clis/tiktok/save.yaml +34 -0
  380. package/src/clis/tiktok/search.yaml +46 -0
  381. package/src/clis/tiktok/unfollow.yaml +44 -0
  382. package/src/clis/tiktok/unlike.yaml +38 -0
  383. package/src/clis/tiktok/unsave.yaml +36 -0
  384. package/src/clis/tiktok/user.yaml +44 -0
  385. package/src/clis/twitter/download.ts +3 -3
  386. package/src/clis/twitter/followers.ts +1 -1
  387. package/src/clis/twitter/following.ts +1 -1
  388. package/src/clis/twitter/thread.ts +1 -1
  389. package/src/clis/twitter/timeline.test.ts +109 -0
  390. package/src/clis/twitter/timeline.ts +59 -19
  391. package/src/clis/wikipedia/random.ts +19 -0
  392. package/src/clis/wikipedia/search.ts +10 -4
  393. package/src/clis/wikipedia/summary.ts +4 -9
  394. package/src/clis/wikipedia/trending.ts +41 -0
  395. package/src/clis/wikipedia/utils.ts +31 -0
  396. package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
  397. package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
  398. package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
  399. package/src/clis/xiaohongshu/download.ts +1 -1
  400. package/src/clis/xueqiu/earnings-date.yaml +69 -0
  401. package/src/clis/xueqiu/search.yaml +2 -1
  402. package/src/clis/xueqiu/stock.yaml +2 -0
  403. package/src/clis/yahoo-finance/quote.ts +1 -1
  404. package/src/commanderAdapter.ts +17 -10
  405. package/src/daemon.ts +23 -0
  406. package/src/discovery.ts +134 -24
  407. package/src/doctor.test.ts +59 -2
  408. package/src/doctor.ts +4 -2
  409. package/src/engine.test.ts +79 -6
  410. package/src/execution.ts +42 -16
  411. package/src/explore.ts +77 -9
  412. package/src/generate.ts +58 -9
  413. package/src/main.ts +2 -1
  414. package/src/pipeline/executor.test.ts +35 -6
  415. package/src/pipeline/executor.ts +68 -19
  416. package/src/pipeline/registry.ts +3 -3
  417. package/src/pipeline/steps/browser.ts +24 -15
  418. package/src/pipeline/steps/fetch.ts +18 -13
  419. package/src/pipeline/steps/transform.ts +40 -15
  420. package/src/pipeline/template.test.ts +18 -0
  421. package/src/pipeline/template.ts +86 -13
  422. package/src/pipeline/transform.test.ts +15 -2
  423. package/src/plugin.test.ts +86 -0
  424. package/src/plugin.ts +254 -0
  425. package/src/registry-api.ts +12 -0
  426. package/src/registry.ts +19 -1
  427. package/src/synthesize.ts +102 -21
  428. package/src/types.ts +44 -12
  429. package/src/validate.ts +19 -4
  430. package/tests/e2e/browser-public.test.ts +11 -0
  431. package/tests/e2e/public-commands.test.ts +64 -0
  432. package/dist/clis/feishu/new.d.ts +0 -1
  433. package/dist/clis/feishu/new.js +0 -27
  434. package/dist/clis/feishu/read.d.ts +0 -1
  435. package/dist/clis/feishu/read.js +0 -40
  436. package/dist/clis/feishu/search.d.ts +0 -1
  437. package/dist/clis/feishu/search.js +0 -30
  438. package/dist/clis/feishu/send.d.ts +0 -1
  439. package/dist/clis/feishu/send.js +0 -39
  440. package/dist/clis/feishu/status.d.ts +0 -1
  441. package/dist/clis/feishu/status.js +0 -28
  442. package/dist/clis/neteasemusic/like.d.ts +0 -1
  443. package/dist/clis/neteasemusic/like.js +0 -25
  444. package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
  445. package/dist/clis/neteasemusic/lyrics.js +0 -47
  446. package/dist/clis/neteasemusic/next.d.ts +0 -1
  447. package/dist/clis/neteasemusic/next.js +0 -26
  448. package/dist/clis/neteasemusic/play.d.ts +0 -1
  449. package/dist/clis/neteasemusic/play.js +0 -26
  450. package/dist/clis/neteasemusic/playing.d.ts +0 -1
  451. package/dist/clis/neteasemusic/playing.js +0 -59
  452. package/dist/clis/neteasemusic/playlist.d.ts +0 -1
  453. package/dist/clis/neteasemusic/playlist.js +0 -46
  454. package/dist/clis/neteasemusic/prev.d.ts +0 -1
  455. package/dist/clis/neteasemusic/prev.js +0 -25
  456. package/dist/clis/neteasemusic/search.d.ts +0 -1
  457. package/dist/clis/neteasemusic/search.js +0 -52
  458. package/dist/clis/neteasemusic/status.d.ts +0 -1
  459. package/dist/clis/neteasemusic/status.js +0 -16
  460. package/dist/clis/neteasemusic/volume.d.ts +0 -1
  461. package/dist/clis/neteasemusic/volume.js +0 -54
  462. package/dist/clis/wechat/chats.d.ts +0 -1
  463. package/dist/clis/wechat/chats.js +0 -28
  464. package/dist/clis/wechat/contacts.d.ts +0 -1
  465. package/dist/clis/wechat/contacts.js +0 -28
  466. package/dist/clis/wechat/read.d.ts +0 -1
  467. package/dist/clis/wechat/read.js +0 -58
  468. package/dist/clis/wechat/search.d.ts +0 -1
  469. package/dist/clis/wechat/search.js +0 -31
  470. package/dist/clis/wechat/send.d.ts +0 -1
  471. package/dist/clis/wechat/send.js +0 -42
  472. package/dist/clis/wechat/status.d.ts +0 -1
  473. package/dist/clis/wechat/status.js +0 -29
  474. package/dist/pipeline.d.ts +0 -7
  475. package/dist/pipeline.js +0 -7
  476. package/docs/adapters/browser/github.md +0 -26
  477. package/docs/adapters/desktop/feishu.md +0 -20
  478. package/docs/adapters/desktop/neteasemusic.md +0 -31
  479. package/docs/adapters/desktop/wechat.md +0 -28
  480. package/src/clis/antigravity/README.md +0 -5
  481. package/src/clis/antigravity/README.zh-CN.md +0 -51
  482. package/src/clis/chaoxing/README.md +0 -14
  483. package/src/clis/chaoxing/README.zh-CN.md +0 -35
  484. package/src/clis/chatgpt/README.md +0 -5
  485. package/src/clis/chatgpt/README.zh-CN.md +0 -44
  486. package/src/clis/chatwise/README.md +0 -5
  487. package/src/clis/chatwise/README.zh-CN.md +0 -38
  488. package/src/clis/codex/README.md +0 -5
  489. package/src/clis/codex/README.zh-CN.md +0 -33
  490. package/src/clis/cursor/README.md +0 -5
  491. package/src/clis/cursor/README.zh-CN.md +0 -33
  492. package/src/clis/discord-app/README.md +0 -5
  493. package/src/clis/discord-app/README.zh-CN.md +0 -28
  494. package/src/clis/feishu/README.md +0 -5
  495. package/src/clis/feishu/README.zh-CN.md +0 -20
  496. package/src/clis/feishu/new.ts +0 -32
  497. package/src/clis/feishu/read.ts +0 -48
  498. package/src/clis/feishu/search.ts +0 -35
  499. package/src/clis/feishu/send.ts +0 -46
  500. package/src/clis/feishu/status.ts +0 -34
  501. package/src/clis/neteasemusic/README.md +0 -5
  502. package/src/clis/neteasemusic/README.zh-CN.md +0 -31
  503. package/src/clis/neteasemusic/like.ts +0 -28
  504. package/src/clis/neteasemusic/lyrics.ts +0 -53
  505. package/src/clis/neteasemusic/next.ts +0 -30
  506. package/src/clis/neteasemusic/play.ts +0 -30
  507. package/src/clis/neteasemusic/playing.ts +0 -62
  508. package/src/clis/neteasemusic/playlist.ts +0 -51
  509. package/src/clis/neteasemusic/prev.ts +0 -29
  510. package/src/clis/neteasemusic/search.ts +0 -58
  511. package/src/clis/neteasemusic/status.ts +0 -18
  512. package/src/clis/neteasemusic/volume.ts +0 -61
  513. package/src/clis/notion/README.md +0 -5
  514. package/src/clis/notion/README.zh-CN.md +0 -29
  515. package/src/clis/wechat/README.md +0 -5
  516. package/src/clis/wechat/README.zh-CN.md +0 -28
  517. package/src/clis/wechat/chats.ts +0 -33
  518. package/src/clis/wechat/contacts.ts +0 -33
  519. package/src/clis/wechat/read.ts +0 -72
  520. package/src/clis/wechat/search.ts +0 -36
  521. package/src/clis/wechat/send.ts +0 -49
  522. package/src/clis/wechat/status.ts +0 -35
  523. package/src/pipeline.ts +0 -8
@@ -1,4 +1,246 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ const GROK_URL = 'https://grok.com/';
3
+ const RESPONSE_SELECTOR = 'div.message-bubble, [data-testid="message-bubble"]';
4
+ const BLOCKED_PREFIX = '[BLOCKED]';
5
+ const NO_RESPONSE_PREFIX = '[NO RESPONSE]';
6
+ const SESSION_HINT = 'Likely login/auth/challenge/session issue in the existing grok.com browser session.';
7
+ function blocked(message) {
8
+ return [{ response: `${BLOCKED_PREFIX} ${message} ${SESSION_HINT}` }];
9
+ }
10
+ function normalizeBubbleText(value) {
11
+ return typeof value === 'string' ? value.trim() : '';
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];
28
+ }
29
+ return '';
30
+ }
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
+ async function runDefaultAsk(page, prompt, timeoutMs, newChat) {
39
+ if (newChat) {
40
+ await page.goto(GROK_URL);
41
+ await page.wait(2);
42
+ await page.evaluate(`(() => {
43
+ const btn = [...document.querySelectorAll('a, button')].find(b => {
44
+ const t = (b.textContent || '').trim().toLowerCase();
45
+ return t.includes('new') || b.getAttribute('href') === '/';
46
+ });
47
+ if (btn) btn.click();
48
+ })()`);
49
+ await page.wait(2);
50
+ }
51
+ await page.goto(GROK_URL);
52
+ await page.wait(3);
53
+ const promptJson = JSON.stringify(prompt);
54
+ const sendResult = await page.evaluate(`(async () => {
55
+ try {
56
+ const box = document.querySelector('textarea');
57
+ if (!box) return { ok: false, msg: 'no textarea' };
58
+ box.focus(); box.value = '';
59
+ document.execCommand('selectAll');
60
+ document.execCommand('insertText', false, ${promptJson});
61
+ await new Promise(r => setTimeout(r, 1500));
62
+ const btn = document.querySelector('button[aria-label="\\u63d0\\u4ea4"]');
63
+ if (btn && !btn.disabled) { btn.click(); return { ok: true, msg: 'clicked' }; }
64
+ const sub = [...document.querySelectorAll('button[type="submit"]')].find(b => !b.disabled);
65
+ if (sub) { sub.click(); return { ok: true, msg: 'clicked-submit' }; }
66
+ box.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
67
+ return { ok: true, msg: 'enter' };
68
+ } catch (e) { return { ok: false, msg: e.toString() }; }
69
+ })()`);
70
+ if (!sendResult || !sendResult.ok) {
71
+ return [{ response: '[SEND FAILED] ' + JSON.stringify(sendResult) }];
72
+ }
73
+ const startTime = Date.now();
74
+ let lastText = '';
75
+ let stableCount = 0;
76
+ while (Date.now() - startTime < timeoutMs) {
77
+ await page.wait(3);
78
+ const response = await page.evaluate(`(() => {
79
+ const bubbles = document.querySelectorAll('div.message-bubble, [data-testid="message-bubble"]');
80
+ if (bubbles.length < 2) return '';
81
+ const last = bubbles[bubbles.length - 1];
82
+ const text = (last.innerText || '').trim();
83
+ if (!text || text.length < 2) return '';
84
+ return text;
85
+ })()`);
86
+ if (response && response.length > 2) {
87
+ if (response === lastText) {
88
+ stableCount++;
89
+ if (stableCount >= 2)
90
+ return [{ response }];
91
+ }
92
+ else {
93
+ stableCount = 0;
94
+ }
95
+ }
96
+ lastText = response || '';
97
+ }
98
+ if (lastText)
99
+ return [{ response: lastText }];
100
+ return [{ response: NO_RESPONSE_PREFIX }];
101
+ }
102
+ async function getBubbleTexts(page) {
103
+ const result = await page.evaluate(`(() => {
104
+ return Array.from(document.querySelectorAll(${JSON.stringify(RESPONSE_SELECTOR)}))
105
+ .map(node => (node instanceof HTMLElement ? node.innerText : node?.textContent || ''))
106
+ .map(text => (typeof text === 'string' ? text.trim() : ''))
107
+ .filter(Boolean);
108
+ })()`);
109
+ return Array.isArray(result) ? result.map(normalizeBubbleText).filter(Boolean) : [];
110
+ }
111
+ async function tryStartFreshChat(page) {
112
+ await page.evaluate(`(() => {
113
+ const isVisible = (node) => {
114
+ if (!(node instanceof HTMLElement)) return false;
115
+ const rect = node.getBoundingClientRect();
116
+ const style = window.getComputedStyle(node);
117
+ return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none';
118
+ };
119
+
120
+ const candidates = Array.from(document.querySelectorAll('a, button')).filter(node => {
121
+ if (!isVisible(node)) return false;
122
+ const text = (node.textContent || '').trim().toLowerCase();
123
+ const aria = (node.getAttribute('aria-label') || '').trim().toLowerCase();
124
+ const href = node.getAttribute('href') || '';
125
+ return text.includes('new chat')
126
+ || text.includes('new conversation')
127
+ || aria.includes('new chat')
128
+ || aria.includes('new conversation')
129
+ || href === '/';
130
+ });
131
+
132
+ const target = candidates[0];
133
+ if (target instanceof HTMLElement) target.click();
134
+ })()`);
135
+ }
136
+ async function sendPromptViaExplicitWeb(page, prompt) {
137
+ return page.evaluate(`(async () => {
138
+ const waitFor = (ms) => new Promise(resolve => setTimeout(resolve, ms));
139
+ const composerSelector = '.ProseMirror[contenteditable="true"]';
140
+ let composer = null;
141
+
142
+ for (let attempt = 0; attempt < 12; attempt += 1) {
143
+ const candidate = document.querySelector(composerSelector);
144
+ if (candidate instanceof HTMLElement) {
145
+ composer = candidate;
146
+ break;
147
+ }
148
+
149
+ await waitFor(1000);
150
+ }
151
+
152
+ if (!(composer instanceof HTMLElement)) {
153
+ return {
154
+ ok: false,
155
+ reason: 'Grok composer was not found on grok.com.',
156
+ };
157
+ }
158
+
159
+ const editor = composer.editor;
160
+ if (!editor?.commands?.focus || !editor?.commands?.insertContent) {
161
+ return {
162
+ ok: false,
163
+ reason: 'Grok composer editor API was unavailable.',
164
+ };
165
+ }
166
+
167
+ const isVisibleEnabledSubmit = (node) => {
168
+ if (!(node instanceof HTMLButtonElement)) return false;
169
+ const rect = node.getBoundingClientRect();
170
+ const style = window.getComputedStyle(node);
171
+ return !node.disabled
172
+ && rect.width > 0
173
+ && rect.height > 0
174
+ && style.visibility !== 'hidden'
175
+ && style.display !== 'none';
176
+ };
177
+
178
+ try {
179
+ if (editor.commands.clearContent) editor.commands.clearContent();
180
+ editor.commands.focus();
181
+ editor.commands.insertContent(${JSON.stringify(prompt)});
182
+ } catch (error) {
183
+ return {
184
+ ok: false,
185
+ reason: 'Failed to insert the prompt into the Grok composer.',
186
+ detail: error instanceof Error ? error.message : String(error),
187
+ };
188
+ }
189
+
190
+ let submit = null;
191
+ for (let attempt = 0; attempt < 6; attempt += 1) {
192
+ const candidate = Array.from(document.querySelectorAll('button[aria-label="Submit"]'))
193
+ .find(isVisibleEnabledSubmit);
194
+
195
+ if (candidate instanceof HTMLButtonElement) {
196
+ submit = candidate;
197
+ break;
198
+ }
199
+
200
+ await waitFor(500);
201
+ }
202
+
203
+ if (!(submit instanceof HTMLButtonElement)) {
204
+ return {
205
+ ok: false,
206
+ reason: 'Grok submit button did not reach a clickable ready state after prompt insertion.',
207
+ };
208
+ }
209
+
210
+ submit.click();
211
+ return { ok: true };
212
+ })()`);
213
+ }
214
+ async function runExplicitWebAsk(page, prompt, timeoutMs, newChat) {
215
+ await page.goto(GROK_URL, { settleMs: 2000 });
216
+ if (newChat) {
217
+ await tryStartFreshChat(page);
218
+ await page.wait(2);
219
+ }
220
+ const baselineBubbles = await getBubbleTexts(page);
221
+ const sendResult = await sendPromptViaExplicitWeb(page, prompt);
222
+ if (!sendResult?.ok) {
223
+ const details = sendResult?.detail ? ` ${sendResult.detail}` : '';
224
+ return blocked(`${sendResult?.reason || 'Unable to send the prompt to Grok.'}${details}`);
225
+ }
226
+ const startTime = Date.now();
227
+ let lastText = '';
228
+ let stableCount = 0;
229
+ while (Date.now() - startTime < timeoutMs) {
230
+ await page.wait(2);
231
+ const bubbleTexts = await getBubbleTexts(page);
232
+ const candidate = pickLatestAssistantCandidate(bubbleTexts, baselineBubbles.length, prompt);
233
+ const nextState = updateStableState(lastText, stableCount, candidate);
234
+ lastText = nextState.previousText;
235
+ stableCount = nextState.stableCount;
236
+ if (candidate && stableCount >= 2) {
237
+ return [{ response: candidate }];
238
+ }
239
+ }
240
+ if (lastText)
241
+ return [{ response: lastText }];
242
+ return [{ response: `${NO_RESPONSE_PREFIX} No new assistant message bubble appeared within ${Math.round(timeoutMs / 1000)}s.` }];
243
+ }
2
244
  export const askCommand = cli({
3
245
  site: 'grok',
4
246
  name: 'ask',
@@ -7,76 +249,26 @@ export const askCommand = cli({
7
249
  strategy: Strategy.COOKIE,
8
250
  browser: true,
9
251
  args: [
10
- { name: 'prompt', type: 'string', required: true },
11
- { name: 'timeout', type: 'int', default: 120 },
12
- { name: 'new', type: 'boolean', default: false },
252
+ { name: 'prompt', positional: true, type: 'string', required: true, help: 'Prompt to send to Grok' },
253
+ { name: 'timeout', type: 'int', default: 120, help: 'Max seconds to wait for response (default: 120)' },
254
+ { name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending (default: false)' },
255
+ { name: 'web', type: 'boolean', default: false, help: 'Use the explicit grok.com consumer web flow (default: false)' },
13
256
  ],
14
257
  columns: ['response'],
15
258
  func: async (page, kwargs) => {
16
259
  const prompt = kwargs.prompt;
17
260
  const timeoutMs = (kwargs.timeout || 120) * 1000;
18
- const newChat = kwargs.new;
19
- if (newChat) {
20
- await page.goto('https://grok.com');
21
- await page.wait(2);
22
- await page.evaluate(`(() => {
23
- const btn = [...document.querySelectorAll('a, button')].find(b => {
24
- const t = (b.textContent || '').trim().toLowerCase();
25
- return t.includes('new') || b.getAttribute('href') === '/';
26
- });
27
- if (btn) btn.click();
28
- })()`);
29
- await page.wait(2);
30
- }
31
- await page.goto('https://grok.com');
32
- await page.wait(3);
33
- const promptJson = JSON.stringify(prompt);
34
- const sendResult = await page.evaluate(`(async () => {
35
- try {
36
- const box = document.querySelector('textarea');
37
- if (!box) return { ok: false, msg: 'no textarea' };
38
- box.focus(); box.value = '';
39
- document.execCommand('selectAll');
40
- document.execCommand('insertText', false, ${promptJson});
41
- await new Promise(r => setTimeout(r, 1500));
42
- const btn = document.querySelector('button[aria-label="\\u63d0\\u4ea4"]');
43
- if (btn && !btn.disabled) { btn.click(); return { ok: true, msg: 'clicked' }; }
44
- const sub = [...document.querySelectorAll('button[type="submit"]')].find(b => !b.disabled);
45
- if (sub) { sub.click(); return { ok: true, msg: 'clicked-submit' }; }
46
- box.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
47
- return { ok: true, msg: 'enter' };
48
- } catch (e) { return { ok: false, msg: e.toString() }; }
49
- })()`);
50
- if (!sendResult || !sendResult.ok) {
51
- return [{ response: '[SEND FAILED] ' + JSON.stringify(sendResult) }];
52
- }
53
- const startTime = Date.now();
54
- let lastText = '';
55
- let stableCount = 0;
56
- while (Date.now() - startTime < timeoutMs) {
57
- await page.wait(3);
58
- const response = await page.evaluate(`(() => {
59
- const bubbles = document.querySelectorAll('div.message-bubble, [data-testid="message-bubble"]');
60
- if (bubbles.length < 2) return '';
61
- const last = bubbles[bubbles.length - 1];
62
- const text = (last.innerText || '').trim();
63
- if (!text || text.length < 2) return '';
64
- return text;
65
- })()`);
66
- if (response && response.length > 2) {
67
- if (response === lastText) {
68
- stableCount++;
69
- if (stableCount >= 2)
70
- return [{ response }];
71
- }
72
- else {
73
- stableCount = 0;
74
- }
75
- }
76
- lastText = response || '';
261
+ const newChat = normalizeBooleanFlag(kwargs.new);
262
+ const useExplicitWeb = normalizeBooleanFlag(kwargs.web);
263
+ if (useExplicitWeb) {
264
+ return runExplicitWebAsk(page, prompt, timeoutMs, newChat);
77
265
  }
78
- if (lastText)
79
- return [{ response: lastText }];
80
- return [{ response: '[NO RESPONSE]' }];
266
+ return runDefaultAsk(page, prompt, timeoutMs, newChat);
81
267
  },
82
268
  });
269
+ export const __test__ = {
270
+ pickLatestAssistantCandidate,
271
+ updateStableState,
272
+ normalizeBooleanFlag,
273
+ normalizeBubbleText,
274
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { __test__ } from './ask.js';
3
+ describe('grok ask helpers', () => {
4
+ it('normalizes boolean flags for explicit web routing', () => {
5
+ expect(__test__.normalizeBooleanFlag(true)).toBe(true);
6
+ expect(__test__.normalizeBooleanFlag('true')).toBe(true);
7
+ expect(__test__.normalizeBooleanFlag('1')).toBe(true);
8
+ expect(__test__.normalizeBooleanFlag('yes')).toBe(true);
9
+ expect(__test__.normalizeBooleanFlag('on')).toBe(true);
10
+ expect(__test__.normalizeBooleanFlag(false)).toBe(false);
11
+ expect(__test__.normalizeBooleanFlag('false')).toBe(false);
12
+ expect(__test__.normalizeBooleanFlag(undefined)).toBe(false);
13
+ });
14
+ it('ignores baseline bubbles and the echoed prompt when choosing the latest assistant candidate', () => {
15
+ const candidate = __test__.pickLatestAssistantCandidate(['older assistant answer', 'Prompt text', 'Assistant draft', 'Assistant final'], 1, 'Prompt text');
16
+ expect(candidate).toBe('Assistant final');
17
+ });
18
+ it('returns empty when only the echoed prompt appeared after send', () => {
19
+ const candidate = __test__.pickLatestAssistantCandidate(['older assistant answer', 'Prompt text'], 1, 'Prompt text');
20
+ expect(candidate).toBe('');
21
+ });
22
+ it('tracks stabilization by incrementing repeats and resetting on changes', () => {
23
+ expect(__test__.updateStableState('', 0, 'First chunk')).toEqual({
24
+ previousText: 'First chunk',
25
+ stableCount: 0,
26
+ });
27
+ expect(__test__.updateStableState('First chunk', 0, 'First chunk')).toEqual({
28
+ previousText: 'First chunk',
29
+ stableCount: 1,
30
+ });
31
+ expect(__test__.updateStableState('First chunk', 1, 'Second chunk')).toEqual({
32
+ previousText: 'Second chunk',
33
+ stableCount: 0,
34
+ });
35
+ });
36
+ });
@@ -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]
@@ -0,0 +1,51 @@
1
+ site: instagram
2
+ name: following
3
+ description: List accounts an Instagram user is following
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 accounts
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 + '/following/?count=' + limit,
38
+ opts
39
+ );
40
+ if (!r2.ok) throw new Error('Failed to fetch following: 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]
@@ -0,0 +1,46 @@
1
+ site: instagram
2
+ name: like
3
+ description: Like 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
+ index:
13
+ type: int
14
+ default: 1
15
+ description: Post index (1 = most recent)
16
+
17
+ pipeline:
18
+ - navigate: https://www.instagram.com
19
+
20
+ - evaluate: |
21
+ (async () => {
22
+ const username = ${{ args.username | json }};
23
+ const idx = ${{ args.index }} - 1;
24
+ const headers = { 'X-IG-App-ID': '936619743392459' };
25
+ const opts = { credentials: 'include', headers };
26
+
27
+ const r1 = await fetch('https://www.instagram.com/api/v1/users/web_profile_info/?username=' + encodeURIComponent(username), opts);
28
+ if (!r1.ok) throw new Error('User not found: ' + username);
29
+ const userId = (await r1.json())?.data?.user?.id;
30
+
31
+ const r2 = await fetch('https://www.instagram.com/api/v1/feed/user/' + userId + '/?count=' + (idx + 1), opts);
32
+ const posts = (await r2.json())?.items || [];
33
+ if (idx >= posts.length) throw new Error('Post index ' + (idx + 1) + ' not found, user has ' + posts.length + ' recent posts');
34
+ const pk = posts[idx].pk;
35
+ const caption = (posts[idx].caption?.text || '').substring(0, 60);
36
+
37
+ const csrf = document.cookie.match(/csrftoken=([^;]+)/)?.[1] || '';
38
+ const r3 = await fetch('https://www.instagram.com/api/v1/web/likes/' + pk + '/like/', {
39
+ method: 'POST', credentials: 'include',
40
+ headers: { ...headers, 'X-CSRFToken': csrf, 'Content-Type': 'application/x-www-form-urlencoded' },
41
+ });
42
+ if (!r3.ok) throw new Error('Failed to like: HTTP ' + r3.status);
43
+ return [{ status: 'Liked', user: username, post: caption || '(post #' + (idx+1) + ')' }];
44
+ })()
45
+
46
+ columns: [status, user, post]