publishport-opencli 1.0.1 → 1.0.2

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 (317) hide show
  1. package/README.md +61 -61
  2. package/README.zh-CN.md +68 -68
  3. package/cli-manifest.json +502 -59
  4. package/clis/1688/shared.js +2 -2
  5. package/clis/36kr/news.js +1 -1
  6. package/clis/_atlassian/shared.js +1 -1
  7. package/clis/_shared/article/auth.js +1 -1
  8. package/clis/_shared/article/publish.js +1 -1
  9. package/clis/_shared/content-guard.js +1 -1
  10. package/clis/_shared/self-hosted-sites.js +2 -0
  11. package/clis/_shared/site-auth.js +1 -1
  12. package/clis/_shared/token-auth.js +2 -2
  13. package/clis/_shared/video-publish.js +1 -1
  14. package/clis/aibase/news.js +1 -1
  15. package/clis/amazon/shared.js +2 -2
  16. package/clis/antigravity/SKILL.md +12 -12
  17. package/clis/antigravity/storage.js +1 -1
  18. package/clis/apple-podcasts/episodes.js +1 -1
  19. package/clis/archive/item.js +1 -1
  20. package/clis/archive/search.js +1 -1
  21. package/clis/archive/snapshots.js +1 -1
  22. package/clis/archive/wayback.js +1 -1
  23. package/clis/arxiv/author.js +1 -1
  24. package/clis/baijiahao/publish.js +1 -1
  25. package/clis/bbc/utils.js +1 -1
  26. package/clis/bloomberg/businessweek.js +1 -1
  27. package/clis/bloomberg/utils.js +1 -1
  28. package/clis/booking/search.js +1 -1
  29. package/clis/boss/utils.js +1 -1
  30. package/clis/chatgpt/ask.js +1 -1
  31. package/clis/chatgpt/detail.js +1 -1
  32. package/clis/chatgpt/history.js +1 -1
  33. package/clis/chatgpt/project-file-add.js +1 -1
  34. package/clis/chatgpt/project-list.js +1 -1
  35. package/clis/chatgpt/utils.js +2 -2
  36. package/clis/chess/utils.js +1 -1
  37. package/clis/claude/ask.js +1 -1
  38. package/clis/claude/history.js +1 -1
  39. package/clis/claude/utils.js +1 -1
  40. package/clis/codex/extract-diff.js +1 -1
  41. package/clis/coingecko/coin.js +1 -1
  42. package/clis/crates/utils.js +1 -1
  43. package/clis/csdn/stats.js +1 -1
  44. package/clis/dblp/utils.js +1 -1
  45. package/clis/defillama/utils.js +1 -1
  46. package/clis/devto/publish.js +1 -1
  47. package/clis/discord-app/utils.js +1 -1
  48. package/clis/dockerhub/utils.js +1 -1
  49. package/clis/douban/utils.js +1 -1
  50. package/clis/douyin/_shared/tos-upload.js +1 -1
  51. package/clis/douyin/hashtag.js +1 -1
  52. package/clis/douyin/publish-image.js +1 -1
  53. package/clis/douyin/publish.js +1 -1
  54. package/clis/eastmoney/announcement.js +1 -1
  55. package/clis/eastmoney/rank.js +1 -1
  56. package/clis/eastmoney/sectors.js +1 -1
  57. package/clis/endoflife/utils.js +1 -1
  58. package/clis/flathub/utils.js +1 -1
  59. package/clis/gemini/detail.js +1 -1
  60. package/clis/gemini/read.js +1 -1
  61. package/clis/geogebra/add-circle.js +1 -1
  62. package/clis/geogebra/add-line.js +1 -1
  63. package/clis/geogebra/add-point.js +1 -1
  64. package/clis/geogebra/add-polygon.js +1 -1
  65. package/clis/geogebra/eval.js +1 -1
  66. package/clis/geogebra/hexagon.js +1 -1
  67. package/clis/geogebra/info.js +1 -1
  68. package/clis/geogebra/triangle.js +1 -1
  69. package/clis/ghost/login.js +1 -0
  70. package/clis/ghost/publish.js +1 -1
  71. package/clis/ghost/shared.js +24 -1
  72. package/clis/ghost/sites.js +1 -0
  73. package/clis/ghost/whoami.js +1 -1
  74. package/clis/gitee/user.js +2 -2
  75. package/clis/github-trending/repos.js +1 -1
  76. package/clis/goproxy/utils.js +1 -1
  77. package/clis/grok/export-all.js +1 -1
  78. package/clis/grok/export.js +1 -1
  79. package/clis/hashnode/publish.js +1 -1
  80. package/clis/hf/datasets.js +1 -1
  81. package/clis/hf/models.js +1 -1
  82. package/clis/hf/paper.js +1 -1
  83. package/clis/hf/spaces.js +1 -1
  84. package/clis/homebrew/utils.js +1 -1
  85. package/clis/instagram/note.js +1 -1
  86. package/clis/jimeng/generate.js +1 -1
  87. package/clis/juejin/utils.js +1 -1
  88. package/clis/kuaishou/publish.js +1 -1
  89. package/clis/lesswrong/tag.js +1 -1
  90. package/clis/lichess/utils.js +1 -1
  91. package/clis/linkedin/search.js +2 -2
  92. package/clis/linux-do/feed.js +2 -2
  93. package/clis/lobsters/domain.js +1 -1
  94. package/clis/mastodon/login.js +1 -0
  95. package/clis/mastodon/post.js +1 -1
  96. package/clis/mastodon/shared.js +10 -10
  97. package/clis/mastodon/sites.js +1 -0
  98. package/clis/mastodon/whoami.js +1 -1
  99. package/clis/maven/utils.js +1 -1
  100. package/clis/mdn/search.js +1 -1
  101. package/clis/medium/tag.js +1 -1
  102. package/clis/notebooklm/current.js +1 -1
  103. package/clis/notebooklm/get.js +1 -1
  104. package/clis/notebooklm/history.js +1 -1
  105. package/clis/notebooklm/note-list.js +1 -1
  106. package/clis/notebooklm/notes-get.js +1 -1
  107. package/clis/notebooklm/open.js +1 -1
  108. package/clis/notebooklm/source-fulltext.js +1 -1
  109. package/clis/notebooklm/source-get.js +1 -1
  110. package/clis/notebooklm/source-guide.js +1 -1
  111. package/clis/notebooklm/source-list.js +1 -1
  112. package/clis/notebooklm/summary.js +1 -1
  113. package/clis/notebooklm/utils.js +1 -1
  114. package/clis/npm/utils.js +1 -1
  115. package/clis/nuget/utils.js +1 -1
  116. package/clis/nvd/cve.js +1 -1
  117. package/clis/oeis/utils.js +1 -1
  118. package/clis/ones/common.js +2 -2
  119. package/clis/ones/me.js +1 -1
  120. package/clis/ones/tasks.js +1 -1
  121. package/clis/ones/token-info.js +1 -1
  122. package/clis/ones/worklog.js +1 -1
  123. package/clis/openalex/utils.js +1 -1
  124. package/clis/oschina/stats.js +1 -1
  125. package/clis/osv/utils.js +1 -1
  126. package/clis/packagist/utils.js +1 -1
  127. package/clis/pixiv/detail.js +1 -1
  128. package/clis/pixiv/user.js +1 -1
  129. package/clis/producthunt/utils.js +1 -1
  130. package/clis/pypi/utils.js +1 -1
  131. package/clis/qiita/gql.js +2 -2
  132. package/clis/rest-countries/utils.js +1 -1
  133. package/clis/rfc/utils.js +1 -1
  134. package/clis/rubygems/utils.js +1 -1
  135. package/clis/semanticscholar/utils.js +1 -1
  136. package/clis/slock/errors.js +1 -1
  137. package/clis/spotify/spotify.js +1 -1
  138. package/clis/spotify/utils.js +1 -1
  139. package/clis/stackoverflow/utils.js +1 -1
  140. package/clis/steam/utils.js +1 -1
  141. package/clis/substack/auth.js +2 -2
  142. package/clis/suno/generate.js +2 -2
  143. package/clis/suno/list.js +3 -3
  144. package/clis/telegram/send.js +1 -1
  145. package/clis/tiktok/creator-videos.js +1 -1
  146. package/clis/tiktok/publish.js +1 -1
  147. package/clis/tiktok/utils.js +1 -1
  148. package/clis/trae-cn/activity.js +1 -1
  149. package/clis/trae-cn/approve.js +1 -1
  150. package/clis/trae-cn/ask.js +1 -1
  151. package/clis/trae-cn/dump.js +1 -1
  152. package/clis/trae-cn/export.js +1 -1
  153. package/clis/trae-cn/model.js +1 -1
  154. package/clis/trae-cn/new.js +1 -1
  155. package/clis/trae-cn/read.js +1 -1
  156. package/clis/trae-cn/screenshot.js +1 -1
  157. package/clis/trae-cn/select-model.js +1 -1
  158. package/clis/trae-cn/send.js +1 -1
  159. package/clis/trae-cn/setup.js +1 -1
  160. package/clis/trae-cn/status.js +1 -1
  161. package/clis/trae-cn/targets.js +1 -1
  162. package/clis/trae-cn/watch.js +1 -1
  163. package/clis/trae-solo/state-fs.js +1 -1
  164. package/clis/tumblr/publish.js +1 -1
  165. package/clis/tvmaze/utils.js +1 -1
  166. package/clis/twitter/bookmark-folder.js +1 -1
  167. package/clis/twitter/download.js +1 -1
  168. package/clis/twitter/followers.js +2 -2
  169. package/clis/twitter/following.js +2 -2
  170. package/clis/twitter/likes.js +1 -1
  171. package/clis/twitter/list-add-batch.js +1 -1
  172. package/clis/twitter/list-add-core.js +1 -1
  173. package/clis/twitter/list-add.js +1 -1
  174. package/clis/twitter/list-create.js +1 -1
  175. package/clis/twitter/list-delete.js +3 -3
  176. package/clis/twitter/list-remove-batch.js +1 -1
  177. package/clis/twitter/list-remove.js +1 -1
  178. package/clis/twitter/list-tweets.js +1 -1
  179. package/clis/twitter/profile.js +1 -1
  180. package/clis/twitter/search.js +1 -1
  181. package/clis/twitter/shared.js +1 -1
  182. package/clis/twitter/tweets.js +1 -1
  183. package/clis/uisdc/news.js +1 -1
  184. package/clis/v2ex/daily.js +2 -2
  185. package/clis/v2ex/me.js +2 -2
  186. package/clis/v2ex/notifications.js +1 -1
  187. package/clis/wikidata/utils.js +1 -1
  188. package/clis/wikipedia/page.js +2 -2
  189. package/clis/wikipedia/summary.js +1 -1
  190. package/clis/wikipedia/utils.js +1 -1
  191. package/clis/wordpress/login.js +1 -1
  192. package/clis/wordpress/publish.js +1 -1
  193. package/clis/wordpress/shared.js +3 -3
  194. package/clis/wordpress/sites.js +1 -0
  195. package/clis/wordpress/whoami.js +1 -1
  196. package/clis/woshipm/stats.js +1 -1
  197. package/clis/xiaoe/content.js +1 -1
  198. package/clis/xiaohongshu/draft-delete.js +1 -1
  199. package/clis/xiaohongshu/draft-open.js +1 -1
  200. package/clis/xiaohongshu/draft-utils.js +2 -2
  201. package/clis/xiaohongshu/publish-video.js +1 -1
  202. package/clis/xiaohongshu/publish.js +1 -1
  203. package/clis/xiaoyuzhou/auth.js +1 -1
  204. package/clis/xiaoyuzhou/transcript.js +1 -1
  205. package/clis/yollomi/background.js +1 -1
  206. package/clis/yollomi/edit.js +1 -1
  207. package/clis/yollomi/generate.js +1 -1
  208. package/clis/yollomi/try-on.js +1 -1
  209. package/clis/youtube/publish.js +1 -1
  210. package/clis/zhihu/answer-comments.js +1 -1
  211. package/clis/zhihu/answer-detail.js +1 -1
  212. package/clis/zhihu/collection.js +2 -2
  213. package/clis/zhihu/collections.js +1 -1
  214. package/clis/zhihu/question.js +1 -1
  215. package/clis/zhihu/search.js +1 -1
  216. package/clis/zhihu/target.js +1 -1
  217. package/clis/zhihu/user-arg.js +1 -1
  218. package/dist/src/adapter-shadow.js +1 -1
  219. package/dist/src/browser/analyze.js +1 -1
  220. package/dist/src/browser/article-extract.d.ts +1 -1
  221. package/dist/src/browser/base-page.js +3 -3
  222. package/dist/src/browser/cdp.js +1 -1
  223. package/dist/src/browser/daemon-client.d.ts +1 -1
  224. package/dist/src/browser/daemon-lifecycle.js +6 -6
  225. package/dist/src/browser/daemon-version.js +1 -1
  226. package/dist/src/browser/dom-snapshot.d.ts +1 -1
  227. package/dist/src/browser/dom-snapshot.js +2 -2
  228. package/dist/src/browser/errors.js +2 -2
  229. package/dist/src/browser/network-cache.js +1 -1
  230. package/dist/src/browser/profile.js +1 -1
  231. package/dist/src/browser/target-resolver.js +4 -4
  232. package/dist/src/browser/verify-fixture.js +1 -1
  233. package/dist/src/browser-session-lock.js +1 -1
  234. package/dist/src/build-manifest.d.ts +1 -1
  235. package/dist/src/build-manifest.js +1 -1
  236. package/dist/src/cli-argv-preprocess.d.ts +4 -4
  237. package/dist/src/cli-argv-preprocess.js +1 -1
  238. package/dist/src/cli.d.ts +1 -1
  239. package/dist/src/cli.js +19 -19
  240. package/dist/src/commanderAdapter.js +1 -1
  241. package/dist/src/commands/auth.js +2 -2
  242. package/dist/src/commands/daemon.d.ts +4 -4
  243. package/dist/src/commands/daemon.js +1 -1
  244. package/dist/src/completion-shared.js +12 -12
  245. package/dist/src/completion.d.ts +2 -2
  246. package/dist/src/constants.d.ts +1 -1
  247. package/dist/src/constants.js +1 -1
  248. package/dist/src/daemon-utils.d.ts +1 -1
  249. package/dist/src/daemon-utils.js +1 -1
  250. package/dist/src/daemon.d.ts +2 -2
  251. package/dist/src/daemon.js +1 -1
  252. package/dist/src/discovery.d.ts +7 -7
  253. package/dist/src/discovery.js +2 -2
  254. package/dist/src/doctor.d.ts +1 -1
  255. package/dist/src/doctor.js +6 -6
  256. package/dist/src/electron-apps.d.ts +1 -1
  257. package/dist/src/electron-apps.js +1 -1
  258. package/dist/src/errors.d.ts +2 -2
  259. package/dist/src/errors.js +1 -1
  260. package/dist/src/execution.js +1 -1
  261. package/dist/src/external.d.ts +1 -1
  262. package/dist/src/external.js +1 -1
  263. package/dist/src/help.d.ts +1 -1
  264. package/dist/src/help.js +3 -3
  265. package/dist/src/hooks.d.ts +1 -1
  266. package/dist/src/launcher.js +2 -2
  267. package/dist/src/logger.d.ts +1 -1
  268. package/dist/src/main.d.ts +1 -1
  269. package/dist/src/main.js +1 -1
  270. package/dist/src/observation/artifact.js +2 -2
  271. package/dist/src/package-paths.js +1 -1
  272. package/dist/src/package-paths.test.d.ts +1 -0
  273. package/dist/src/plugin-manifest.d.ts +12 -3
  274. package/dist/src/plugin-manifest.js +1 -1
  275. package/dist/src/plugin-scaffold.d.ts +2 -2
  276. package/dist/src/plugin-scaffold.js +9 -9
  277. package/dist/src/plugin.d.ts +4 -4
  278. package/dist/src/plugin.js +10 -10
  279. package/dist/src/rate-limit.js +1 -1
  280. package/dist/src/registry-api.d.ts +1 -1
  281. package/dist/src/runtime-detect.d.ts +1 -1
  282. package/dist/src/serialization.js +1 -1
  283. package/dist/src/skills.js +6 -6
  284. package/dist/src/user-runtime.d.ts +8 -0
  285. package/dist/src/user-runtime.js +1 -0
  286. package/dist/src/user-runtime.test.d.ts +1 -0
  287. package/dist/src/validate.js +1 -1
  288. package/dist/src/validate.test.d.ts +1 -1
  289. package/docs/guide/browser-bridge.md +23 -23
  290. package/docs/guide/electron-app-cli.md +3 -3
  291. package/docs/guide/exit-codes.md +3 -3
  292. package/docs/guide/extending-opencli.md +37 -37
  293. package/docs/guide/getting-started.md +19 -19
  294. package/docs/guide/installation.md +6 -6
  295. package/docs/guide/plugins.md +32 -32
  296. package/docs/guide/remote-orchestration.md +5 -5
  297. package/docs/guide/troubleshooting.md +9 -9
  298. package/package.json +3 -2
  299. package/scripts/fetch-adapters.js +6 -6
  300. package/scripts/postinstall.js +14 -14
  301. package/skills/opencli-adapter-author/SKILL.md +24 -24
  302. package/skills/opencli-adapter-author/references/adapter-template.md +13 -13
  303. package/skills/opencli-adapter-author/references/api-discovery.md +25 -25
  304. package/skills/opencli-adapter-author/references/coverage-matrix.md +3 -3
  305. package/skills/opencli-adapter-author/references/field-decode-playbook.md +10 -10
  306. package/skills/opencli-adapter-author/references/jsdom-fixture-pattern.md +2 -2
  307. package/skills/opencli-adapter-author/references/site-memory.md +11 -11
  308. package/skills/opencli-adapter-author/references/site-recon.md +7 -7
  309. package/skills/opencli-adapter-author/references/strategy-selection.md +3 -3
  310. package/skills/opencli-adapter-author/references/success-rate-pitfalls.md +4 -4
  311. package/skills/opencli-adapter-author/references/typed-errors.md +2 -2
  312. package/skills/opencli-autofix/SKILL.md +29 -29
  313. package/skills/opencli-browser/SKILL.md +55 -55
  314. package/skills/opencli-browser-sitemap/SKILL.md +7 -7
  315. package/skills/opencli-sitemap-author/SKILL.md +11 -11
  316. package/skills/opencli-sitemap-author/references/sitemap-schema.md +19 -19
  317. package/skills/opencli-usage/SKILL.md +43 -43
@@ -1 +1 @@
1
- import{cli as m,Strategy as s}from"@jackwener/opencli/registry";import{ArgumentError as c}from"@jackwener/opencli/errors";import{ensureApplet as i,ggbEval as l,requireGgbSuccess as u}from"./utils.js";m({site:"geogebra",name:"eval",access:"write",description:"Execute one or more GeoGebra command strings (semicolon-separated)",domain:"www.geogebra.org",strategy:s.PUBLIC,browser:!0,navigateBefore:!1,example:'opencli geogebra eval "A=(0,0);B=(4,0);c=Circle(A,B);d=Circle(B,A);C=Intersect(c,d,1);Polygon(A,B,C)"',args:[{name:"command",positional:!0,required:!0,help:"GeoGebra command string (use ; to chain multiple commands)"}],columns:["command","result"],func:async(o,t)=>{const r=String(t.command).split(";").map((e)=>e.trim()).filter(Boolean);if(r.length===0)throw new c("command must contain at least one GeoGebra command");await i(o);const a=[];for(const e of r){const n=u(await l(o,e),`Failed to execute GeoGebra command: ${e}`);a.push({command:e,result:`ok (${n.label||"no label"})`})}return a}});
1
+ import{cli as m,Strategy as s}from"@jackwener/opencli/registry";import{ArgumentError as c}from"@jackwener/opencli/errors";import{ensureApplet as i,ggbEval as l,requireGgbSuccess as u}from"./utils.js";m({site:"geogebra",name:"eval",access:"write",description:"Execute one or more GeoGebra command strings (semicolon-separated)",domain:"www.geogebra.org",strategy:s.PUBLIC,browser:!0,navigateBefore:!1,example:'ppcli geogebra eval "A=(0,0);B=(4,0);c=Circle(A,B);d=Circle(B,A);C=Intersect(c,d,1);Polygon(A,B,C)"',args:[{name:"command",positional:!0,required:!0,help:"GeoGebra command string (use ; to chain multiple commands)"}],columns:["command","result"],func:async(o,t)=>{const r=String(t.command).split(";").map((e)=>e.trim()).filter(Boolean);if(r.length===0)throw new c("command must contain at least one GeoGebra command");await i(o);const a=[];for(const e of r){const n=u(await l(o,e),`Failed to execute GeoGebra command: ${e}`);a.push({command:e,result:`ok (${n.label||"no label"})`})}return a}});
@@ -1 +1 @@
1
- import{cli as Z,Strategy as _}from"@jackwener/opencli/registry";import{CommandExecutionError as $}from"@jackwener/opencli/errors";import A from"node:os";import D from"node:path";import{ensureApplet as F,ggbEval as Q,ggbListObjects as L,ggbWaitForObjectCount as N,normalizeNumber as W,requireGgbSuccess as R}from"./utils.js";Z({site:"geogebra",name:"hexagon",access:"write",description:"Draw a regular hexagon centered at the origin",domain:"www.geogebra.org",strategy:_.PUBLIC,browser:!0,navigateBefore:!1,example:"opencli geogebra hexagon --size 3",args:[{name:"size",required:!1,default:"2",help:"Radius of the hexagon (default: 2)"}],columns:["step","result"],func:async(H,T)=>{const f=W(T.size,"size",{defaultValue:2,positive:!0});await F(H);const I=[],U=[["V1",`(${f},0)`],["V2",`(${f}*cos(pi/3),${f}*sin(pi/3))`],["V3",`(${f}*cos(2*pi/3),${f}*sin(2*pi/3))`],["V4",`(-${f},0)`],["V5",`(${f}*cos(4*pi/3),${f}*sin(4*pi/3))`],["V6",`(${f}*cos(5*pi/3),${f}*sin(5*pi/3))`]];for(const[B,M]of U){const Y=R(await Q(H,`${B}=${M}`),`Failed to create point ${B}`);I.push({step:`${B}=${M}`,result:`ok (${Y.label||B})`})}const V=R(await Q(H,"Hexagon=Polygon(V1,V2,V3,V4,V5,V6)"),"Failed to create hexagon polygon");I.push({step:"Hexagon=Polygon(V1,V2,V3,V4,V5,V6)",result:`ok (${V.label||"hexagon created"})`});const X=await N(H,7),J=await L(H),K=D.join(A.tmpdir(),"opencli-geogebra-hexagon.png");try{await H.screenshot({path:K})}catch(B){throw new $(`Failed to capture GeoGebra screenshot: ${B?.message||B}`)}if(Array.isArray(J)&&J.length>0)I.push({step:`canvas has ${X} objects`,result:J.map((B)=>`${B.name}(${B.type})`).join(", ")});I.push({step:"screenshot",result:K});return I}});
1
+ import{cli as Z,Strategy as _}from"@jackwener/opencli/registry";import{CommandExecutionError as $}from"@jackwener/opencli/errors";import A from"node:os";import D from"node:path";import{ensureApplet as F,ggbEval as Q,ggbListObjects as L,ggbWaitForObjectCount as N,normalizeNumber as W,requireGgbSuccess as R}from"./utils.js";Z({site:"geogebra",name:"hexagon",access:"write",description:"Draw a regular hexagon centered at the origin",domain:"www.geogebra.org",strategy:_.PUBLIC,browser:!0,navigateBefore:!1,example:"ppcli geogebra hexagon --size 3",args:[{name:"size",required:!1,default:"2",help:"Radius of the hexagon (default: 2)"}],columns:["step","result"],func:async(H,T)=>{const f=W(T.size,"size",{defaultValue:2,positive:!0});await F(H);const I=[],U=[["V1",`(${f},0)`],["V2",`(${f}*cos(pi/3),${f}*sin(pi/3))`],["V3",`(${f}*cos(2*pi/3),${f}*sin(2*pi/3))`],["V4",`(-${f},0)`],["V5",`(${f}*cos(4*pi/3),${f}*sin(4*pi/3))`],["V6",`(${f}*cos(5*pi/3),${f}*sin(5*pi/3))`]];for(const[B,M]of U){const Y=R(await Q(H,`${B}=${M}`),`Failed to create point ${B}`);I.push({step:`${B}=${M}`,result:`ok (${Y.label||B})`})}const V=R(await Q(H,"Hexagon=Polygon(V1,V2,V3,V4,V5,V6)"),"Failed to create hexagon polygon");I.push({step:"Hexagon=Polygon(V1,V2,V3,V4,V5,V6)",result:`ok (${V.label||"hexagon created"})`});const X=await N(H,7),J=await L(H),K=D.join(A.tmpdir(),"opencli-geogebra-hexagon.png");try{await H.screenshot({path:K})}catch(B){throw new $(`Failed to capture GeoGebra screenshot: ${B?.message||B}`)}if(Array.isArray(J)&&J.length>0)I.push({step:`canvas has ${X} objects`,result:J.map((B)=>`${B.name}(${B.type})`).join(", ")});I.push({step:"screenshot",result:K});return I}});
@@ -1,4 +1,4 @@
1
- import{cli as Q,Strategy as T}from"@jackwener/opencli/registry";import{CommandExecutionError as I,EmptyResultError as U}from"@jackwener/opencli/errors";import{ensureApplet as V,ggbGetProperty as J,normalizeLabel as W,unwrapBridgeEnvelope as X}from"./utils.js";Q({site:"geogebra",name:"info",access:"read",description:"Get detailed properties of a GeoGebra object",domain:"www.geogebra.org",strategy:T.PUBLIC,browser:!0,navigateBefore:!1,example:"opencli geogebra info --name A",args:[{name:"name",required:!0,help:"Object label (e.g. A, c1, poly1)"}],columns:["property","value"],func:async(D,M)=>{const h=W(M.name,"name");await V(D);let f;try{f=X(await D.evaluate(`
1
+ import{cli as Q,Strategy as T}from"@jackwener/opencli/registry";import{CommandExecutionError as I,EmptyResultError as U}from"@jackwener/opencli/errors";import{ensureApplet as V,ggbGetProperty as J,normalizeLabel as W,unwrapBridgeEnvelope as X}from"./utils.js";Q({site:"geogebra",name:"info",access:"read",description:"Get detailed properties of a GeoGebra object",domain:"www.geogebra.org",strategy:T.PUBLIC,browser:!0,navigateBefore:!1,example:"ppcli geogebra info --name A",args:[{name:"name",required:!0,help:"Object label (e.g. A, c1, poly1)"}],columns:["property","value"],func:async(D,M)=>{const h=W(M.name,"name");await V(D);let f;try{f=X(await D.evaluate(`
2
2
  (name => {
3
3
  try {
4
4
  if (typeof ggbApplet === 'undefined' || typeof ggbApplet.getObjectType !== 'function') {
@@ -1 +1 @@
1
- import{cli as h,Strategy as w}from"@jackwener/opencli/registry";import{CommandExecutionError as f}from"@jackwener/opencli/errors";import C from"node:os";import B from"node:path";import{ensureApplet as $,ggbEval as r,ggbListObjects as A,ggbWaitForObjectCount as y,normalizeNumber as F,requireGgbSuccess as a}from"./utils.js";h({site:"geogebra",name:"triangle",access:"write",description:"Draw an equilateral triangle from a horizontal base segment",domain:"www.geogebra.org",strategy:w.PUBLIC,browser:!0,navigateBefore:!1,example:"opencli geogebra triangle --size 4",args:[{name:"size",required:!1,default:"2",help:"Side length of the triangle (default: 2)"}],columns:["step","result"],func:async(e,l)=>{const o=F(l.size,"size",{defaultValue:2,positive:!0});await $(e);const t=[],c=a(await r(e,"A=(0,0)"),"Failed to create point A");t.push({step:"base point A=(0,0)",result:`ok (${c.label||"A"})`});const n=a(await r(e,`B=(${o},0)`),"Failed to create point B");t.push({step:`base point B=(${o},0)`,result:`ok (${n.label||"B"})`});const u=a(await r(e,"c=Circle(A,B)"),"Failed to create circle c");t.push({step:"c=Circle(A,B)",result:`ok (${u.label||"c"})`});const p=a(await r(e,"d=Circle(B,A)"),"Failed to create circle d");t.push({step:"d=Circle(B,A)",result:`ok (${p.label||"d"})`});const g=a(await r(e,"C=Intersect(c,d,1)"),"Failed to create point C");t.push({step:"C=Intersect(c,d,1)",result:`ok (${g.label||"C"})`});const m=a(await r(e,"Polygon(A,B,C)"),"Failed to create triangle polygon");t.push({step:"Polygon(A,B,C)",result:`ok (${m.label||"triangle created"})`});const b=await y(e,5),d=await A(e),i=B.join(C.tmpdir(),"opencli-geogebra-triangle.png");try{await e.screenshot({path:i})}catch(s){throw new f(`Failed to capture GeoGebra screenshot: ${s?.message||s}`)}t.push({step:`canvas has ${b} objects`,result:d.map((s)=>`${s.name}(${s.type})`).join(", ")});t.push({step:"screenshot",result:i});return t}});
1
+ import{cli as h,Strategy as w}from"@jackwener/opencli/registry";import{CommandExecutionError as f}from"@jackwener/opencli/errors";import C from"node:os";import B from"node:path";import{ensureApplet as $,ggbEval as r,ggbListObjects as A,ggbWaitForObjectCount as y,normalizeNumber as F,requireGgbSuccess as a}from"./utils.js";h({site:"geogebra",name:"triangle",access:"write",description:"Draw an equilateral triangle from a horizontal base segment",domain:"www.geogebra.org",strategy:w.PUBLIC,browser:!0,navigateBefore:!1,example:"ppcli geogebra triangle --size 4",args:[{name:"size",required:!1,default:"2",help:"Side length of the triangle (default: 2)"}],columns:["step","result"],func:async(e,l)=>{const o=F(l.size,"size",{defaultValue:2,positive:!0});await $(e);const t=[],c=a(await r(e,"A=(0,0)"),"Failed to create point A");t.push({step:"base point A=(0,0)",result:`ok (${c.label||"A"})`});const n=a(await r(e,`B=(${o},0)`),"Failed to create point B");t.push({step:`base point B=(${o},0)`,result:`ok (${n.label||"B"})`});const u=a(await r(e,"c=Circle(A,B)"),"Failed to create circle c");t.push({step:"c=Circle(A,B)",result:`ok (${u.label||"c"})`});const p=a(await r(e,"d=Circle(B,A)"),"Failed to create circle d");t.push({step:"d=Circle(B,A)",result:`ok (${p.label||"d"})`});const g=a(await r(e,"C=Intersect(c,d,1)"),"Failed to create point C");t.push({step:"C=Intersect(c,d,1)",result:`ok (${g.label||"C"})`});const m=a(await r(e,"Polygon(A,B,C)"),"Failed to create triangle polygon");t.push({step:"Polygon(A,B,C)",result:`ok (${m.label||"triangle created"})`});const b=await y(e,5),d=await A(e),i=B.join(C.tmpdir(),"opencli-geogebra-triangle.png");try{await e.screenshot({path:i})}catch(s){throw new f(`Failed to capture GeoGebra screenshot: ${s?.message||s}`)}t.push({step:`canvas has ${b} objects`,result:d.map((s)=>`${s.name}(${s.type})`).join(", ")});t.push({step:"screenshot",result:i});return t}});
@@ -0,0 +1 @@
1
+ import{cli as Q,Strategy as R}from"@jackwener/opencli/registry";import{TimeoutError as V}from"@jackwener/opencli/errors";import{gotoGhostAdmin as X,probeGhostSite as J,resolveGhostSite as Y}from"./shared.js";const K=300;async function z(q){if(typeof q?.closeWindow!=="function")return;try{await q.closeWindow()}catch{}}Q({site:"ghost",name:"login",access:"write",description:"打开指定 Ghost 自部署站点的后台登录页并等待登录完成。用 --site 选择连接名称或站点网址。",domain:"ghost.org",strategy:R.COOKIE,browser:!0,navigateBefore:!1,defaultWindowMode:"foreground",siteSession:"persistent",args:[{name:"site",help:"连接名称或站点网址;省略时使用默认站点"},{name:"site-url",help:"兼容旧版:Ghost 站点网址(推荐改用 --site)"},{name:"timeout",type:"int",default:K,help:"等待用户完成登录的最长秒数"}],columns:["status","logged_in","site","title","connection"],func:async(q,B)=>{const x=Y(B);try{const f=await J(q,x);await z(q);return[{status:"already_logged_in",logged_in:!0,site:f.url??x.baseUrl,title:f.title,connection:x.name}]}catch(f){if(f?.code!=="AUTH_REQUIRED")throw f}await X(q,x.baseUrl);const H=Number(B.timeout??K),P=Date.now()+H*1000;while(Date.now()<P){await q.wait(2);try{const f=await J(q,x,{navigate:!1});await z(q);return[{status:"login_complete",logged_in:!0,site:f.url??x.baseUrl,title:f.title,connection:x.name}]}catch(f){if(f?.code!=="AUTH_REQUIRED")throw f}}await z(q);throw new V("ghost login",H,`请在打开的窗口里完成 ${x.baseUrl}/ghost/ 的登录后重试。`)}});
@@ -1 +1 @@
1
- import{cli as m,Strategy as d}from"@jackwener/opencli/registry";import{CliError as a}from"@jackwener/opencli/errors";import{readFileSync as u}from"node:fs";import{normalizeContent as p}from"../_shared/article/format.js";import{getGhostConfig as f,ghostRequest as c}from"./shared.js";const h=["draft","published"];m({site:"ghost",name:"publish",access:"write",description:"Publish a post to a Ghost site via the Admin API. A Markdown body is converted to HTML automatically (sent with ?source=html). Defaults to a draft for safety — pass --status published to go live.",domain:"ghost.org",strategy:d.LOCAL,browser:!1,args:[{name:"title",required:!0,help:"Post title"},{name:"content",help:"Post body (Markdown or HTML). Or use --file."},{name:"file",help:"Read the post body from a local file instead of --content"},{name:"markup",default:"auto",choices:["auto","markdown","html"],help:"How to interpret the body: auto-detect (default), markdown, or html"},{name:"status",default:"draft",choices:h,help:"Post status (default: draft)"},{name:"excerpt",help:"Optional custom excerpt"},{name:"slug",help:"Optional URL slug"},{name:"tags",help:"Comma-separated tag names (created if missing)"}],columns:["id","status","title","url"],func:async(t)=>{const n=f();let i=typeof t.content==="string"?t.content:"";if(t.file)try{i=u(String(t.file),"utf8")}catch(o){throw new a("CONFIG",`Cannot read --file ${t.file}: ${o?.message??o}`,"Pass a readable UTF-8 text file.")}if(!i.trim())throw new a("CONFIG","Post body is empty",'Pass --content "..." or --file <path>.');const l=p(i,{format:t.markup}).html,e={title:String(t.title),html:l,status:String(t.status)};if(t.excerpt)e.custom_excerpt=String(t.excerpt);if(t.slug)e.slug=String(t.slug);if(t.tags)e.tags=String(t.tags).split(",").map((o)=>o.trim()).filter(Boolean).map((o)=>({name:o}));const r=await c(n,"/posts/?source=html",{method:"POST",body:{posts:[e]},label:"Ghost publish"}),s=Array.isArray(r?.posts)?r.posts[0]:void 0;if(!s?.id)throw new a("PARSE_ERROR","Ghost did not return a created post","The Admin API response was missing posts[0].id.");return[{id:s.id,status:s.status??e.status,title:s.title??e.title,url:s.url??""}]}});
1
+ import{cli as d,Strategy as p}from"@jackwener/opencli/registry";import{ArgumentError as l,CommandExecutionError as u}from"@jackwener/opencli/errors";import{readFileSync as c}from"node:fs";import{normalizeContent as f}from"../_shared/article/format.js";import{ghostPageRequest as h,gotoGhostAdmin as g,resolveGhostSite as w}from"./shared.js";const y=["draft","published"];d({site:"ghost",name:"publish",access:"write",description:"发布文章到指定 Ghost 自部署站点。用 --site 选择连接名称或站点网址;Markdown 自动转 HTML,默认保存草稿。",domain:"ghost.org",strategy:p.COOKIE,browser:!0,navigateBefore:!1,siteSession:"persistent",args:[{name:"site",help:"连接名称或站点网址;省略时使用默认站点"},{name:"site-url",help:"兼容旧版:Ghost 站点网址(推荐改用 --site)"},{name:"title",required:!0,help:"Post title"},{name:"content",help:"Post body (Markdown or HTML). Or use --file."},{name:"file",help:"Read the post body from a local file instead of --content"},{name:"markup",default:"auto",choices:["auto","markdown","html"],help:"How to interpret the body: auto-detect (default), markdown, or html"},{name:"status",default:"draft",choices:y,help:"Post status (default: draft)"},{name:"excerpt",help:"Optional custom excerpt"},{name:"slug",help:"Optional URL slug"},{name:"tags",help:"Comma-separated tag names (created if missing)"}],columns:["id","status","title","url"],func:async(a,t)=>{const r=w(t);let i=typeof t.content==="string"?t.content:"";if(t.file)try{i=c(String(t.file),"utf8")}catch(o){throw new l(`Cannot read --file ${t.file}: ${o?.message??o}`,"Pass a readable UTF-8 text file.")}if(!i.trim())throw new l("Post body is empty",'Pass --content "..." or --file <path>.');const m=f(i,{format:t.markup}).html,e={title:String(t.title),html:m,status:String(t.status)};if(t.excerpt)e.custom_excerpt=String(t.excerpt);if(t.slug)e.slug=String(t.slug);if(t.tags)e.tags=String(t.tags).split(",").map((o)=>o.trim()).filter(Boolean).map((o)=>({name:o}));await g(a,r.baseUrl);const n=await h(a,r.baseUrl,"/posts/?source=html",{method:"POST",body:{posts:[e]},label:"Ghost publish"}),s=Array.isArray(n?.posts)?n.posts[0]:void 0;if(!s?.id)throw new u("Ghost did not return a created post","The Admin API response was missing posts[0].id.");return[{id:s.id,status:s.status??e.status,title:s.title??e.title,url:s.url??""}]}});
@@ -1 +1,24 @@
1
- import{CliError as X}from"@jackwener/opencli/errors";const B="ghost-admin-api-session";function J(Q,L){const D=Q?.trim().replace(/\/+$/,"");if(!D)throw new X("CONFIG",`Missing ${L}`,`Set ${L}, e.g. https://your-site.com (your Ghost site root URL).`);let V;try{V=new URL(D)}catch{throw new X("CONFIG",`Invalid ${L}: ${D}`,"Use an absolute http(s) URL.")}if(V.protocol!=="http:"&&V.protocol!=="https:")throw new X("CONFIG",`Invalid ${L}: ${D}`,"Use an http(s) URL.");return{baseUrl:D,origin:V.origin}}function j(Q){const L=Q?.trim();if(!L)return"";const D=`${B}=`;if(L.startsWith(D))return L.slice(D.length).split(";")[0].trim();return L}export function getGhostConfig(Q=process.env){const{baseUrl:L,origin:D}=J(Q.GHOST_SITE_URL??Q.GHOST_ADMIN_URL,"GHOST_SITE_URL"),V=j(Q.GHOST_SESSION_COOKIE);if(!V)throw new X("AUTH_REQUIRED","Ghost session cookie missing","Connect your Ghost site first: the desktop opens {siteUrl}/ghost/ so you can log in, then captures the session cookie automatically.");return{baseUrl:L,origin:D,session:V}}export async function ghostRequest(Q,L,D={}){const V=L.startsWith("/")?L:`/${L}`,F=`${Q.baseUrl}/ghost/api/admin${V}`,Z=D.label||`Ghost ${D.method||"GET"} ${V}`;let W;try{W=await fetch(F,{method:D.method||"GET",headers:{Cookie:`${B}=${Q.session}`,Origin:Q.origin,Accept:"application/json","Accept-Version":"v5.0",...D.body!==void 0?{"Content-Type":"application/json"}:{},...D.headers||{}},body:D.body!==void 0?JSON.stringify(D.body):void 0})}catch($){throw new X("FETCH_ERROR",`${Z} failed: ${$?.message??$}`,"Check the site URL is reachable and that it is a Ghost site.")}const y=await W.text();let Y=null;try{Y=y?JSON.parse(y):null}catch{Y=null}if(W.status===401||W.status===403)throw new X("AUTH_REQUIRED",`${Z} returned HTTP ${W.status} (unauthorized)`,"The Ghost session has expired or the Origin was rejected. Re-connect the site (log in again through the desktop) and retry.");if(W.status===404)throw new X("NOT_FOUND",`${Z} returned HTTP 404`,"Check the site URL points at a Ghost site (the Admin API lives under /ghost/api/admin).");if(!W.ok){const $=Array.isArray(Y?.errors)&&Y.errors[0]?.message?`: ${Y.errors[0].message}`:W.statusText?`: ${W.statusText}`:"";throw new X("HTTP_ERROR",`${Z} returned HTTP ${W.status}${$}`)}return Y}
1
+ import{AuthRequiredError as f,CommandExecutionError as c}from"@jackwener/opencli/errors";import{loadSiteConnections as h,resolveSiteConnection as d}from"../_shared/self-hosted-sites.js";export const GHOST_SESSION_COOKIE="ghost-admin-api-session";export function resolveGhostSite(t={},e=process.env){const s=d("ghost",t,{selector:"site",legacySelectors:["base-url","site-url"],envUrl:e.GHOST_SITE_URL??e.GHOST_ADMIN_URL});return{...s,baseUrl:s.url}}export function configuredGhostSites(t=process.env){const e=h("ghost").sites;if(e.length>0)return e;if(!(t.GHOST_SITE_URL??t.GHOST_ADMIN_URL))return[];return[{name:"",url:resolveGhostSite({},t).baseUrl}]}export async function readGhostCookieLogin(t,e){if(typeof t?.getCookies!=="function")return!1;const s=await t.getCookies({url:`${e}/ghost/`});return Array.isArray(s)&&s.some((o)=>o?.name===GHOST_SESSION_COOKIE&&o?.value)}export async function gotoGhostAdmin(t,e){await t.goto(`${e}/ghost/`);await t.wait({time:1});return t.evaluate(`(() => ({
2
+ href: location.href,
3
+ onSignin: /\\/ghost\\/#\\/(?:signin|setup)/i.test(location.href)
4
+ || !!document.querySelector('form[action*="signin"], input[type="email"]'),
5
+ }))()`)}export async function ghostPageRequest(t,e,s,o={}){const r=s.startsWith("/")?s:`/${s}`,u=`${e}/ghost/api/admin${r}`,a=o.method??"GET",n=await t.evaluate(`(async () => {
6
+ try {
7
+ const response = await fetch(${JSON.stringify(u)}, {
8
+ method: ${JSON.stringify(a)},
9
+ credentials: 'include',
10
+ headers: {
11
+ 'Accept': 'application/json',
12
+ 'Accept-Version': 'v5.0',
13
+ ${o.body!==void 0?"'Content-Type': 'application/json',":""}
14
+ },
15
+ body: ${o.body!==void 0?JSON.stringify(JSON.stringify(o.body)):"undefined"},
16
+ });
17
+ const text = await response.text();
18
+ let json = null;
19
+ try { json = text ? JSON.parse(text) : null; } catch (e) {}
20
+ return { status: response.status, ok: response.ok, json, text };
21
+ } catch (error) {
22
+ return { status: 0, ok: false, json: null, text: String(error && error.message || error) };
23
+ }
24
+ })()`),i=Number(n?.status??0);if(i===401||i===403)throw new f(e,`Ghost 登录态已失效(HTTP ${i});请重新登录该站点。`);if(!n?.ok){const l=(Array.isArray(n?.json?.errors)?n.json.errors[0]?.message:"")||n?.text?.slice(0,300)||"unknown error";throw new c(`Ghost ${a} ${r} 失败(HTTP ${i}):${l}`)}return n.json}export async function probeGhostSite(t,e,{navigate:s=!0}={}){if(s)await gotoGhostAdmin(t,e.baseUrl);const r=(await ghostPageRequest(t,e.baseUrl,"/site/"))?.site;if(!r?.title)throw new c("Ghost /site/ 响应缺少站点信息");return r}
@@ -0,0 +1 @@
1
+ import{cli as m}from"@jackwener/opencli/registry";import{connectionCommandDefinitions as n}from"../_shared/self-hosted-sites.js";for(const o of n("ghost"))m(o);
@@ -1 +1 @@
1
- import{cli as A,Strategy as B}from"@jackwener/opencli/registry";import{CliError as z}from"@jackwener/opencli/errors";import{getGhostConfig as D,ghostRequest as F}from"./shared.js";A({site:"ghost",name:"whoami",access:"read",description:"Check whether the connected Ghost site session is valid (GET /ghost/api/admin/site). Returns logged_in true when the site info comes back, false when the session is missing/expired.",domain:"ghost.org",strategy:B.LOCAL,browser:!1,columns:["logged_in","site","title"],func:async()=>{let m;try{m=D()}catch(b){if(b instanceof z&&b.code==="AUTH_REQUIRED")return[{logged_in:!1,site:"",title:""}];throw b}try{const v=(await F(m,"/site/",{label:"Ghost whoami"}))?.site??{};return[{logged_in:!!v.title,site:v.url??m.baseUrl,title:v.title??""}]}catch(b){if(b instanceof z&&b.code==="AUTH_REQUIRED")return[{logged_in:!1,site:m.baseUrl,title:""}];throw b}}});
1
+ import{cli as r,Strategy as n}from"@jackwener/opencli/registry";import{AuthRequiredError as s}from"@jackwener/opencli/errors";import{configuredGhostSites as a,probeGhostSite as c,readGhostCookieLogin as l,resolveGhostSite as g}from"./shared.js";async function u(o){for(const i of a())if(await l(o,i.url))return{logged_in:!0};return{logged_in:!1}}r({site:"ghost",name:"whoami",access:"read",description:"检测指定 Ghost 自部署站点的浏览器登录态与站点信息。用 --site 选择连接名称或站点网址。",domain:"ghost.org",strategy:n.COOKIE,browser:!0,navigateBefore:!1,siteSession:"persistent",args:[{name:"site",help:"连接名称或站点网址;省略时使用默认站点"},{name:"site-url",help:"兼容旧版:Ghost 站点网址(推荐改用 --site)"}],columns:["logged_in","site","title","connection"],authStatus:{quickCheck:u},func:async(o,i)=>{const t=g(i);try{const e=await c(o,t);return[{logged_in:!0,site:e.url??t.baseUrl,title:e.title,connection:t.name}]}catch(e){if(e instanceof s||e?.code==="AUTH_REQUIRED")return[{logged_in:!1,site:t.baseUrl,title:"",connection:t.name}];throw e}}});
@@ -1,4 +1,4 @@
1
- import{CliError as a}from"@jackwener/opencli/errors";import{cli as k,Strategy as h}from"@jackwener/opencli/registry";const y="https://gitee.com",A="https://gitee.com/api/v5/users";function _(e){return e.replace(/\s+/g," ").trim()}function C(e){return e.trim().replace(/^@+/,"").replace(/^\/+|\/+$/g,"")}function m(e){if(!e||typeof e!=="object"||Array.isArray(e))return null;return e}function l(e){if(typeof e==="string")return _(e);if(typeof e==="number"&&Number.isFinite(e))return String(e);if(Array.isArray(e))for(const n of e){const t=l(n);if(t)return t}return""}function s(e){const n=l(e);if(!n)return"";const o=n.replace(/,/g,"").match(/\d+(?:[.]\d+)?(?:[kKmMwW]|\u4E07)?/);if(o)return o[0];return""}function d(...e){for(const n of e)if(typeof n==="string"&&n.trim())return n.trim();return""}function I(e){if(!e)return"";const n=["gitee_index","giteeIndex","index","score","contribution_score","contributionScore","contribution_index","contributionIndex"];for(const t of n){const o=s(e[t]);if(o)return o}return""}k({site:"gitee",name:"user",access:"read",description:"Show a Gitee user profile panel",domain:"gitee.com",strategy:h.PUBLIC,browser:!0,args:[{name:"username",positional:!0,required:!0,help:"Gitee username"}],columns:["field","value"],func:async(e,n)=>{const t=C(String(n.username??""));if(!t)throw new a("INVALID_ARGUMENT","Username is required","Use: opencli gitee user <username>");const o=`${y}/${encodeURIComponent(t)}`;await e.goto(o);await e.wait(2);const f=await e.evaluate(`
1
+ import{CliError as a}from"@jackwener/opencli/errors";import{cli as k,Strategy as h}from"@jackwener/opencli/registry";const y="https://gitee.com",A="https://gitee.com/api/v5/users";function _(e){return e.replace(/\s+/g," ").trim()}function C(e){return e.trim().replace(/^@+/,"").replace(/^\/+|\/+$/g,"")}function m(e){if(!e||typeof e!=="object"||Array.isArray(e))return null;return e}function l(e){if(typeof e==="string")return _(e);if(typeof e==="number"&&Number.isFinite(e))return String(e);if(Array.isArray(e))for(const n of e){const t=l(n);if(t)return t}return""}function s(e){const n=l(e);if(!n)return"";const o=n.replace(/,/g,"").match(/\d+(?:[.]\d+)?(?:[kKmMwW]|\u4E07)?/);if(o)return o[0];return""}function d(...e){for(const n of e)if(typeof n==="string"&&n.trim())return n.trim();return""}function I(e){if(!e)return"";const n=["gitee_index","giteeIndex","index","score","contribution_score","contributionScore","contribution_index","contributionIndex"];for(const t of n){const o=s(e[t]);if(o)return o}return""}k({site:"gitee",name:"user",access:"read",description:"Show a Gitee user profile panel",domain:"gitee.com",strategy:h.PUBLIC,browser:!0,args:[{name:"username",positional:!0,required:!0,help:"Gitee username"}],columns:["field","value"],func:async(e,n)=>{const t=C(String(n.username??""));if(!t)throw new a("INVALID_ARGUMENT","Username is required","Use: ppcli gitee user <username>");const o=`${y}/${encodeURIComponent(t)}`;await e.goto(o);await e.wait(2);const f=await e.evaluate(`
2
2
  (() => {
3
3
  const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
4
4
  const extractCount = (value) => {
@@ -68,4 +68,4 @@ import{CliError as a}from"@jackwener/opencli/errors";import{cli as k,Strategy as
68
68
  giteeIndex,
69
69
  };
70
70
  })()
71
- `),r=m(f),i={notFound:r?.notFound===!0,blocked:r?.blocked===!0,nickname:l(r?.nickname),followers:s(r?.followers),publicRepos:s(r?.publicRepos),giteeIndex:s(r?.giteeIndex)};if(i.notFound)throw new a("NOT_FOUND",`Gitee user "${t}" does not exist`,"Check the username and retry: opencli gitee user <username>");if(i.blocked)throw new a("FORBIDDEN",`Gitee user page "${t}" is not accessible`,"The profile may be private/restricted, or the account may be unavailable");const p=`${A}/${encodeURIComponent(t)}`,u=await fetch(p,{headers:{Accept:"application/json","User-Agent":"Mozilla/5.0",Referer:o}});if(u.status===404)throw new a("NOT_FOUND",`Gitee user "${t}" does not exist`,"Check the username and retry: opencli gitee user <username>");if(!u.ok)throw new a("REQUEST_FAILED",`Failed to read Gitee user profile API: ${u.status}`,"Try again later or verify network access to gitee.com");const c=m(await u.json()),x=d(i.nickname,l(c?.name),l(c?.login),t),g=d(i.followers,s(c?.followers),"-"),b=d(i.publicRepos,s(c?.public_repos),"-"),w=d(i.giteeIndex,I(c),"-");return[{field:"Nickname",value:x},{field:"Followers",value:g},{field:"Public Repositories",value:b},{field:"Gitee Index",value:w},{field:"URL",value:o}]}});
71
+ `),r=m(f),i={notFound:r?.notFound===!0,blocked:r?.blocked===!0,nickname:l(r?.nickname),followers:s(r?.followers),publicRepos:s(r?.publicRepos),giteeIndex:s(r?.giteeIndex)};if(i.notFound)throw new a("NOT_FOUND",`Gitee user "${t}" does not exist`,"Check the username and retry: ppcli gitee user <username>");if(i.blocked)throw new a("FORBIDDEN",`Gitee user page "${t}" is not accessible`,"The profile may be private/restricted, or the account may be unavailable");const p=`${A}/${encodeURIComponent(t)}`,u=await fetch(p,{headers:{Accept:"application/json","User-Agent":"Mozilla/5.0",Referer:o}});if(u.status===404)throw new a("NOT_FOUND",`Gitee user "${t}" does not exist`,"Check the username and retry: ppcli gitee user <username>");if(!u.ok)throw new a("REQUEST_FAILED",`Failed to read Gitee user profile API: ${u.status}`,"Try again later or verify network access to gitee.com");const c=m(await u.json()),x=d(i.nickname,l(c?.name),l(c?.login),t),g=d(i.followers,s(c?.followers),"-"),b=d(i.publicRepos,s(c?.public_repos),"-"),w=d(i.giteeIndex,I(c),"-");return[{field:"Nickname",value:x},{field:"Followers",value:g},{field:"Public Repositories",value:b},{field:"Gitee Index",value:w},{field:"URL",value:o}]}});
@@ -1 +1 @@
1
- import{cli as $,Strategy as E}from"@jackwener/opencli/registry";import{ArgumentError as d,CommandExecutionError as c,EmptyResultError as x}from"@jackwener/opencli/errors";const k={daily:"daily",weekly:"weekly",monthly:"monthly"};function f(t){return String(t??"").replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#0?39;/g,"'").replace(/&#x27;/gi,"'").replace(/&nbsp;/g," ")}function g(t){return String(t??"").replace(/<[^>]*>/g,"")}function T(t){if(t==null)return null;const s=String(t).replace(/[,\s]/g,"");if(!/^\d+$/.test(s))return null;return Number(s)}function M(t){return String(t).replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function b(t,s,o){const e=T(t);if(e==null)throw new c(`github-trending parser drift: missing ${s} for ${o}`);return e}function A(t){return/don.t have any trending repositories/i.test(g(t))||/no trending repositories/i.test(g(t))}function C(t,s){const o=Array.from(String(t??"").matchAll(/<article\b[^>]*class="[^"]*\bBox-row\b[^"]*"[^>]*>([\s\S]*?)<\/article>/g)).map((l)=>l[1]),e=[];if(o.length===0){if(A(t))return e;throw new c("github-trending parser drift: no repository rows found")}for(const l of o){const n=l,p=n.match(/<h2\b[\s\S]*?href="\/([^"/?#]+\/[^"/?#]+)"/);if(!p)throw new c("github-trending parser drift: missing repository link");const i=f(p[1]).trim();if(!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(i))throw new c(`github-trending parser drift: invalid repository identity "${i}"`);const a=n.match(/<p class="col-9 color-fg-muted[^"]*">([\s\S]*?)<\/p>/),h=a?f(g(a[1]).replace(/\s+/g," ")).trim():"",u=n.match(/<span itemprop="programmingLanguage">([\s\S]*?)<\/span>/),r=u?f(g(u[1])).trim():null,m=M(i),y=n.match(new RegExp(`<a\\b[^>]*href="/${m}/stargazers"[^>]*>([\\s\\S]*?)</a>`)),w=n.match(new RegExp(`<a\\b[^>]*href="/${m}/forks"[^>]*>([\\s\\S]*?)</a>`)),S=n.match(/([\d,]+)\s+stars\s+(?:today|this week|this month)/i);e.push({repo:i,description:h,language:r,stars:b(y?g(y[1]):null,"stars",i),forks:b(w?g(w[1]):null,"forks",i),starsSince:b(S?.[1],"period stars",i),url:`https://github.com/${i}`});if(e.length>=s)break}return e}$({site:"github-trending",name:"repos",access:"read",description:"GitHub Trending repositories (public, no login). Filter by --language and --since.",domain:"github.com",strategy:E.PUBLIC,browser:!1,args:[{name:"since",type:"string",default:"daily",help:"Time range: daily / weekly / monthly"},{name:"language",type:"string",default:"",help:'Filter by programming language slug, e.g. python, rust, "c++"'},{name:"limit",type:"int",default:25,help:"Number of repositories to return (max 25)"}],columns:["rank","repo","description","language","stars","forks","starsSince","url"],func:async(t)=>{const s=String(t.since??"daily").toLowerCase(),o=k[s];if(!o)throw new d(`Unknown --since "${s}". Valid: ${Object.keys(k).join(", ")}`);const e=Number(t.limit??25);if(!Number.isInteger(e)||e<=0)throw new d("--limit must be a positive integer");if(e>25)throw new d("--limit must be <= 25 (GitHub Trending lists at most 25 repositories)");const l=e,n=String(t.language??"").trim(),p=n?`/trending/${encodeURIComponent(n)}`:"/trending",i=new URL(`https://github.com${p}`);i.searchParams.set("since",o);let a;try{a=await fetch(i,{headers:{"User-Agent":"Mozilla/5.0 (compatible; opencli/github-trending)",Accept:"text/html"}})}catch(r){throw new c(`github-trending request failed: ${r?.message||r}`)}if(!a.ok)throw new c(`github-trending request failed: HTTP ${a.status}`);const h=await a.text(),u=C(h,l);if(u.length===0)throw new x("github-trending",n?`no trending repositories for language "${n}" (${o})`:`no trending repositories (${o})`);return u.map((r,m)=>({rank:m+1,repo:r.repo,description:r.description,language:r.language,stars:r.stars,forks:r.forks,starsSince:r.starsSince,url:r.url}))}});
1
+ import{cli as $,Strategy as E}from"@jackwener/opencli/registry";import{ArgumentError as d,CommandExecutionError as c,EmptyResultError as x}from"@jackwener/opencli/errors";const k={daily:"daily",weekly:"weekly",monthly:"monthly"};function f(t){return String(t??"").replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#0?39;/g,"'").replace(/&#x27;/gi,"'").replace(/&nbsp;/g," ")}function g(t){return String(t??"").replace(/<[^>]*>/g,"")}function T(t){if(t==null)return null;const s=String(t).replace(/[,\s]/g,"");if(!/^\d+$/.test(s))return null;return Number(s)}function M(t){return String(t).replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function b(t,s,o){const e=T(t);if(e==null)throw new c(`github-trending parser drift: missing ${s} for ${o}`);return e}function A(t){return/don.t have any trending repositories/i.test(g(t))||/no trending repositories/i.test(g(t))}function C(t,s){const o=Array.from(String(t??"").matchAll(/<article\b[^>]*class="[^"]*\bBox-row\b[^"]*"[^>]*>([\s\S]*?)<\/article>/g)).map((l)=>l[1]),e=[];if(o.length===0){if(A(t))return e;throw new c("github-trending parser drift: no repository rows found")}for(const l of o){const n=l,p=n.match(/<h2\b[\s\S]*?href="\/([^"/?#]+\/[^"/?#]+)"/);if(!p)throw new c("github-trending parser drift: missing repository link");const i=f(p[1]).trim();if(!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(i))throw new c(`github-trending parser drift: invalid repository identity "${i}"`);const a=n.match(/<p class="col-9 color-fg-muted[^"]*">([\s\S]*?)<\/p>/),h=a?f(g(a[1]).replace(/\s+/g," ")).trim():"",u=n.match(/<span itemprop="programmingLanguage">([\s\S]*?)<\/span>/),r=u?f(g(u[1])).trim():null,m=M(i),y=n.match(new RegExp(`<a\\b[^>]*href="/${m}/stargazers"[^>]*>([\\s\\S]*?)</a>`)),w=n.match(new RegExp(`<a\\b[^>]*href="/${m}/forks"[^>]*>([\\s\\S]*?)</a>`)),S=n.match(/([\d,]+)\s+stars\s+(?:today|this week|this month)/i);e.push({repo:i,description:h,language:r,stars:b(y?g(y[1]):null,"stars",i),forks:b(w?g(w[1]):null,"forks",i),starsSince:b(S?.[1],"period stars",i),url:`https://github.com/${i}`});if(e.length>=s)break}return e}$({site:"github-trending",name:"repos",access:"read",description:"GitHub Trending repositories (public, no login). Filter by --language and --since.",domain:"github.com",strategy:E.PUBLIC,browser:!1,args:[{name:"since",type:"string",default:"daily",help:"Time range: daily / weekly / monthly"},{name:"language",type:"string",default:"",help:'Filter by programming language slug, e.g. python, rust, "c++"'},{name:"limit",type:"int",default:25,help:"Number of repositories to return (max 25)"}],columns:["rank","repo","description","language","stars","forks","starsSince","url"],func:async(t)=>{const s=String(t.since??"daily").toLowerCase(),o=k[s];if(!o)throw new d(`Unknown --since "${s}". Valid: ${Object.keys(k).join(", ")}`);const e=Number(t.limit??25);if(!Number.isInteger(e)||e<=0)throw new d("--limit must be a positive integer");if(e>25)throw new d("--limit must be <= 25 (GitHub Trending lists at most 25 repositories)");const l=e,n=String(t.language??"").trim(),p=n?`/trending/${encodeURIComponent(n)}`:"/trending",i=new URL(`https://github.com${p}`);i.searchParams.set("since",o);let a;try{a=await fetch(i,{headers:{"User-Agent":"Mozilla/5.0 (compatible; ppcli/github-trending)",Accept:"text/html"}})}catch(r){throw new c(`github-trending request failed: ${r?.message||r}`)}if(!a.ok)throw new c(`github-trending request failed: HTTP ${a.status}`);const h=await a.text(),u=C(h,l);if(u.length===0)throw new x("github-trending",n?`no trending repositories for language "${n}" (${o})`:`no trending repositories (${o})`);return u.map((r,m)=>({rank:m+1,repo:r.repo,description:r.description,language:r.language,stars:r.stars,forks:r.forks,starsSince:r.starsSince,url:r.url}))}});
@@ -1 +1 @@
1
- import{ArgumentError as D,CommandExecutionError as H,EmptyResultError as Z}from"@jackwener/opencli/errors";export const GOPROXY_BASE="https://proxy.golang.org";const U="opencli-goproxy-adapter (+https://github.com/jackwener/opencli)",G=/^[A-Za-z0-9][A-Za-z0-9._\/-]{0,199}$/,X=/^v[0-9]+(\.[0-9]+)*([-+][A-Za-z0-9._-]+)?$/;export function requireModulePath(z){const q=String(z??"").trim();if(!q)throw new D('goproxy module path is required (e.g. "github.com/gin-gonic/gin", "golang.org/x/net")',"Use the canonical module path that appears in `go.mod`.");if(!G.test(q)||!q.includes("/"))throw new D(`goproxy module path "${z}" is not a recognised Go module path`,'Module paths look like "github.com/<org>/<repo>" or "golang.org/x/<name>".');return q}export function requireVersionTag(z){const q=String(z??"").trim();if(!q)throw new D("goproxy --version cannot be empty");if(!X.test(q))throw new D(`goproxy --version "${z}" is not a valid Go semver tag`,'Use the GOPROXY canonical form like "v1.2.3" or "v0.0.0-20240101010101-abcdef012345".');return q}export function requireBoundedInt(z,q,J,K="limit"){const Q=z??q,W=typeof Q==="number"?Q:Number(Q);if(!Number.isInteger(W)||W<=0)throw new D(`goproxy ${K} must be a positive integer`);if(W>J)throw new D(`goproxy ${K} must be <= ${J}`);return W}async function Y(z,q){let J;try{J=await fetch(z,{headers:{"user-agent":U}})}catch(K){throw new H(`${q} request failed: ${K?.message??K}`,"Check that proxy.golang.org is reachable from this network.")}if(J.status===404||J.status===410)throw new Z(q,`proxy.golang.org returned ${J.status} for ${z}.`);if(J.status===429)throw new H(`${q} returned HTTP 429 (rate limited)`);if(!J.ok)throw new H(`${q} returned HTTP ${J.status}`);return J}export async function goproxyJson(z,q){const J=await Y(z,q);let K;try{K=await J.json()}catch(Q){throw new H(`${q} returned malformed JSON: ${Q?.message??Q}`)}return K}export async function goproxyText(z,q){return(await Y(z,q)).text()}export function sortVersionsDescending(z){return z.filter((q)=>typeof q==="string"&&X.test(q)).map((q)=>({v:q,parts:T(q)})).sort((q,J)=>_(J.parts,q.parts)).map((q)=>q.v)}function T(z){const q=z.replace(/^v/,""),J=q.indexOf("+"),K=J>=0?q.slice(0,J):q,Q=K.indexOf("-"),W=Q>=0?K.slice(0,Q):K,j=Q>=0?K.slice(Q+1):"";return{numbers:W.split(".").map(($)=>Number.parseInt($,10)).map(($)=>Number.isFinite($)?$:0),pre:j}}function _(z,q){const J=Math.max(z.numbers.length,q.numbers.length);for(let K=0;K<J;K+=1){const Q=z.numbers[K]??0,W=q.numbers[K]??0;if(Q!==W)return Q-W}if(z.pre===""&&q.pre!=="")return 1;if(z.pre!==""&&q.pre==="")return-1;if(z.pre===q.pre)return 0;return B(z.pre,q.pre)}function B(z,q){const J=z.split("."),K=q.split("."),Q=Math.max(J.length,K.length);for(let W=0;W<Q;W+=1){const j=J[W],k=K[W];if(j==null)return-1;if(k==null)return 1;const $=/^\d+$/.test(j),L=/^\d+$/.test(k);if($&&L){const M=Number(j)-Number(k);if(M!==0)return M;continue}if($!==L)return $?-1:1;if(j!==k)return j<k?-1:1}return 0}export function trimDate(z){const q=String(z??"").trim();if(!q)return null;const J=q.replace(/\.\d+/,"");return J.endsWith("Z")?J:q.length>=10?q.slice(0,10):null}
1
+ import{ArgumentError as D,CommandExecutionError as H,EmptyResultError as Z}from"@jackwener/opencli/errors";export const GOPROXY_BASE="https://proxy.golang.org";const U="opencli-goproxy-adapter (+https://github.com/jackwener/OpenCLI)",G=/^[A-Za-z0-9][A-Za-z0-9._\/-]{0,199}$/,X=/^v[0-9]+(\.[0-9]+)*([-+][A-Za-z0-9._-]+)?$/;export function requireModulePath(z){const q=String(z??"").trim();if(!q)throw new D('goproxy module path is required (e.g. "github.com/gin-gonic/gin", "golang.org/x/net")',"Use the canonical module path that appears in `go.mod`.");if(!G.test(q)||!q.includes("/"))throw new D(`goproxy module path "${z}" is not a recognised Go module path`,'Module paths look like "github.com/<org>/<repo>" or "golang.org/x/<name>".');return q}export function requireVersionTag(z){const q=String(z??"").trim();if(!q)throw new D("goproxy --version cannot be empty");if(!X.test(q))throw new D(`goproxy --version "${z}" is not a valid Go semver tag`,'Use the GOPROXY canonical form like "v1.2.3" or "v0.0.0-20240101010101-abcdef012345".');return q}export function requireBoundedInt(z,q,J,K="limit"){const Q=z??q,W=typeof Q==="number"?Q:Number(Q);if(!Number.isInteger(W)||W<=0)throw new D(`goproxy ${K} must be a positive integer`);if(W>J)throw new D(`goproxy ${K} must be <= ${J}`);return W}async function Y(z,q){let J;try{J=await fetch(z,{headers:{"user-agent":U}})}catch(K){throw new H(`${q} request failed: ${K?.message??K}`,"Check that proxy.golang.org is reachable from this network.")}if(J.status===404||J.status===410)throw new Z(q,`proxy.golang.org returned ${J.status} for ${z}.`);if(J.status===429)throw new H(`${q} returned HTTP 429 (rate limited)`);if(!J.ok)throw new H(`${q} returned HTTP ${J.status}`);return J}export async function goproxyJson(z,q){const J=await Y(z,q);let K;try{K=await J.json()}catch(Q){throw new H(`${q} returned malformed JSON: ${Q?.message??Q}`)}return K}export async function goproxyText(z,q){return(await Y(z,q)).text()}export function sortVersionsDescending(z){return z.filter((q)=>typeof q==="string"&&X.test(q)).map((q)=>({v:q,parts:T(q)})).sort((q,J)=>_(J.parts,q.parts)).map((q)=>q.v)}function T(z){const q=z.replace(/^v/,""),J=q.indexOf("+"),K=J>=0?q.slice(0,J):q,Q=K.indexOf("-"),W=Q>=0?K.slice(0,Q):K,j=Q>=0?K.slice(Q+1):"";return{numbers:W.split(".").map(($)=>Number.parseInt($,10)).map(($)=>Number.isFinite($)?$:0),pre:j}}function _(z,q){const J=Math.max(z.numbers.length,q.numbers.length);for(let K=0;K<J;K+=1){const Q=z.numbers[K]??0,W=q.numbers[K]??0;if(Q!==W)return Q-W}if(z.pre===""&&q.pre!=="")return 1;if(z.pre!==""&&q.pre==="")return-1;if(z.pre===q.pre)return 0;return B(z.pre,q.pre)}function B(z,q){const J=z.split("."),K=q.split("."),Q=Math.max(J.length,K.length);for(let W=0;W<Q;W+=1){const j=J[W],k=K[W];if(j==null)return-1;if(k==null)return 1;const $=/^\d+$/.test(j),L=/^\d+$/.test(k);if($&&L){const M=Number(j)-Number(k);if(M!==0)return M;continue}if($!==L)return $?-1:1;if(j!==k)return j<k?-1:1}return 0}export function trimDate(z){const q=String(z??"").trim();if(!q)return null;const J=q.replace(/\.\d+/,"");return J.endsWith("Z")?J:q.length>=10?q.slice(0,10):null}
@@ -167,4 +167,4 @@ import{cli as S,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
167
167
  position += 1;
168
168
  }
169
169
  return { messages };
170
- })()`);u=b(f,"grok export-all conversation reader")}catch(n){return{status:"failed",error:`Conversation reader failed: ${n?.message||n}`,messageCount:0,messagesJson:"[]"}}if(!Array.isArray(u.messages))return{status:"failed",error:"Conversation reader returned malformed message rows.",messageCount:0,messagesJson:"[]"};const m=[];for(let n=0;n<u.messages.length;n+=1){const s=u.messages[n];if(!s||typeof s!=="object"||Array.isArray(s))return{status:"failed",error:`Conversation reader returned malformed message row ${n+1}.`,messageCount:0,messagesJson:"[]"};const g=String(s.messageId||"").trim(),y=String(s.messageText||"").trim();if(!g||!y||s.messageRole!=="assistant"&&s.messageRole!=="user")return{status:"failed",error:`Conversation reader returned malformed message row ${n+1}.`,messageCount:0,messagesJson:"[]"};m.push({messageIndex:Number.isInteger(s.messageIndex)?s.messageIndex:n+1,messageId:g,messageRole:s.messageRole,messageText:y})}if(!m.length)return{status:"empty",error:"Page loaded, but no visible message text was found after scrolling to bottom.",messageCount:0,messagesJson:"[]"};return{status:"ok",error:null,messageCount:m.length,messagesJson:JSON.stringify(m)}}export const grokExportAllCommand=S({site:"grok",name:"export-all",description:"Export Grok conversation history and each conversation transcript",access:"read",example:"opencli grok export-all --limit 5 -f json",domain:w,strategy:v.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"limit",type:"int",default:0,help:"Max conversations to export; 0 means all loaded history"},{name:"offset",type:"int",default:0,help:"Skip this many conversations before exporting"},{name:"manifestPath",type:"string",default:"",help:"Optional grok/export JSON manifest path; skips history dialog and visits listed /c pages directly"},{name:"maxScrolls",type:"int",default:80,help:"Max history-list scroll rounds when limit is 0 (max 500)"},{name:"pageScrolls",type:"int",default:30,help:"Max per-conversation scroll-to-bottom rounds (max 200)"},{name:"pageTimeoutMs",type:"int",default:30000,help:"Max wait for each conversation page to show messages"},{name:"delayMinMs",type:"int",default:0,help:"Minimum polite delay after a conversation page loads"},{name:"delayMaxMs",type:"int",default:5000,help:"Maximum polite delay after a conversation page loads"}],columns:["index","id","title","date","url","status","messageCount","error","messagesJson"],func:async(o,e)=>{const t=d(e.limit,0,"limit",{min:0}),r=d(e.offset,0,"offset",{min:0}),l=d(e.maxScrolls,80,"maxScrolls",{min:1,max:500}),i=d(e.pageScrolls,30,"pageScrolls",{min:1,max:200}),a=d(e.pageTimeoutMs,30000,"pageTimeoutMs",{min:5000,max:180000}),c=d(e.delayMinMs,0,"delayMinMs",{min:0,max:60000}),f=d(e.delayMaxMs,5000,"delayMaxMs",{min:0,max:60000});if(f<c)throw new p("delayMaxMs","must be >= delayMinMs");const u=x(e.manifestPath,{offset:r,limit:t})||await N(o,{offset:r,limit:t,maxScrolls:l}),m=[];for(let n=0;n<u.length;n+=1){const s=u[n],g=await I(o,s,{pageTimeoutMs:a,pageScrolls:i,delayMinMs:c,delayMaxMs:f});m.push({index:r+n+1,id:s.id,title:s.title||null,date:s.date||null,url:s.url,status:g.status,messageCount:g.messageCount,error:g.error,messagesJson:g.messagesJson})}if(!m.length)throw new h("grok export-all","No Grok conversations were exported.");return m}}),__test__={normalizeInteger:d,readManifest:x};
170
+ })()`);u=b(f,"grok export-all conversation reader")}catch(n){return{status:"failed",error:`Conversation reader failed: ${n?.message||n}`,messageCount:0,messagesJson:"[]"}}if(!Array.isArray(u.messages))return{status:"failed",error:"Conversation reader returned malformed message rows.",messageCount:0,messagesJson:"[]"};const m=[];for(let n=0;n<u.messages.length;n+=1){const s=u.messages[n];if(!s||typeof s!=="object"||Array.isArray(s))return{status:"failed",error:`Conversation reader returned malformed message row ${n+1}.`,messageCount:0,messagesJson:"[]"};const g=String(s.messageId||"").trim(),y=String(s.messageText||"").trim();if(!g||!y||s.messageRole!=="assistant"&&s.messageRole!=="user")return{status:"failed",error:`Conversation reader returned malformed message row ${n+1}.`,messageCount:0,messagesJson:"[]"};m.push({messageIndex:Number.isInteger(s.messageIndex)?s.messageIndex:n+1,messageId:g,messageRole:s.messageRole,messageText:y})}if(!m.length)return{status:"empty",error:"Page loaded, but no visible message text was found after scrolling to bottom.",messageCount:0,messagesJson:"[]"};return{status:"ok",error:null,messageCount:m.length,messagesJson:JSON.stringify(m)}}export const grokExportAllCommand=S({site:"grok",name:"export-all",description:"Export Grok conversation history and each conversation transcript",access:"read",example:"ppcli grok export-all --limit 5 -f json",domain:w,strategy:v.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"limit",type:"int",default:0,help:"Max conversations to export; 0 means all loaded history"},{name:"offset",type:"int",default:0,help:"Skip this many conversations before exporting"},{name:"manifestPath",type:"string",default:"",help:"Optional grok/export JSON manifest path; skips history dialog and visits listed /c pages directly"},{name:"maxScrolls",type:"int",default:80,help:"Max history-list scroll rounds when limit is 0 (max 500)"},{name:"pageScrolls",type:"int",default:30,help:"Max per-conversation scroll-to-bottom rounds (max 200)"},{name:"pageTimeoutMs",type:"int",default:30000,help:"Max wait for each conversation page to show messages"},{name:"delayMinMs",type:"int",default:0,help:"Minimum polite delay after a conversation page loads"},{name:"delayMaxMs",type:"int",default:5000,help:"Maximum polite delay after a conversation page loads"}],columns:["index","id","title","date","url","status","messageCount","error","messagesJson"],func:async(o,e)=>{const t=d(e.limit,0,"limit",{min:0}),r=d(e.offset,0,"offset",{min:0}),l=d(e.maxScrolls,80,"maxScrolls",{min:1,max:500}),i=d(e.pageScrolls,30,"pageScrolls",{min:1,max:200}),a=d(e.pageTimeoutMs,30000,"pageTimeoutMs",{min:5000,max:180000}),c=d(e.delayMinMs,0,"delayMinMs",{min:0,max:60000}),f=d(e.delayMaxMs,5000,"delayMaxMs",{min:0,max:60000});if(f<c)throw new p("delayMaxMs","must be >= delayMinMs");const u=x(e.manifestPath,{offset:r,limit:t})||await N(o,{offset:r,limit:t,maxScrolls:l}),m=[];for(let n=0;n<u.length;n+=1){const s=u[n],g=await I(o,s,{pageTimeoutMs:a,pageScrolls:i,delayMinMs:c,delayMaxMs:f});m.push({index:r+n+1,id:s.id,title:s.title||null,date:s.date||null,url:s.url,status:g.status,messageCount:g.messageCount,error:g.error,messagesJson:g.messagesJson})}if(!m.length)throw new h("grok export-all","No Grok conversations were exported.");return m}}),__test__={normalizeInteger:d,readManifest:x};
@@ -1,4 +1,4 @@
1
- import{cli as h,Strategy as g}from"@jackwener/opencli/registry";import{ArgumentError as n,AuthRequiredError as p,EmptyResultError as i,TimeoutError as b}from"@jackwener/opencli/errors";import{normalizeConversationRows as y,requireObjectEvaluateResult as x}from"./export-utils.js";const a="grok.com",w="https://grok.com/";function c(e){const t=Number(e??0);if(!Number.isInteger(t)||t<0)throw new n("limit","must be 0 or a positive integer");return t}function m(e){const t=Number(e??80);if(!Number.isInteger(t)||t<=0)throw new n("maxScrolls","must be a positive integer");if(t>500)throw new n("maxScrolls","must be <= 500");return t}export const grokExportCommand=h({site:"grok",name:"export",description:"Export all visible Grok conversation history metadata",access:"read",example:"opencli grok export -f yaml",domain:a,strategy:g.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"limit",type:"int",default:0,help:"Max conversations to export; 0 means all loaded history"},{name:"maxScrolls",type:"int",default:80,help:"Max history-list scroll rounds when limit is 0 (max 500)"}],columns:["index","id","title","date","url"],func:async(e,o)=>{const t=c(o.limit),u=m(o.maxScrolls);await e.goto(w);await e.wait(2);const d=await e.evaluate(`(async () => {
1
+ import{cli as h,Strategy as g}from"@jackwener/opencli/registry";import{ArgumentError as n,AuthRequiredError as p,EmptyResultError as i,TimeoutError as b}from"@jackwener/opencli/errors";import{normalizeConversationRows as y,requireObjectEvaluateResult as x}from"./export-utils.js";const a="grok.com",w="https://grok.com/";function c(e){const t=Number(e??0);if(!Number.isInteger(t)||t<0)throw new n("limit","must be 0 or a positive integer");return t}function m(e){const t=Number(e??80);if(!Number.isInteger(t)||t<=0)throw new n("maxScrolls","must be a positive integer");if(t>500)throw new n("maxScrolls","must be <= 500");return t}export const grokExportCommand=h({site:"grok",name:"export",description:"Export all visible Grok conversation history metadata",access:"read",example:"ppcli grok export -f yaml",domain:a,strategy:g.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"limit",type:"int",default:0,help:"Max conversations to export; 0 means all loaded history"},{name:"maxScrolls",type:"int",default:80,help:"Max history-list scroll rounds when limit is 0 (max 500)"}],columns:["index","id","title","date","url"],func:async(e,o)=>{const t=c(o.limit),u=m(o.maxScrolls);await e.goto(w);await e.wait(2);const d=await e.evaluate(`(async () => {
2
2
  const targetLimit = ${JSON.stringify(t)};
3
3
  const maxScrolls = ${JSON.stringify(u)};
4
4
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -6,7 +6,7 @@ import*as M from"node:fs";import{cli as I,Strategy as S}from"@jackwener/opencli/
6
6
  var u = j && j.user;
7
7
  return u && u.username ? { ok: true, username: String(u.username) } : { ok: false };
8
8
  } catch (e) { return { drift: true, err: String(e && e.message || e) }; }
9
- })()`;async function W(z,D,{tries:Y=8,label:Z="eval",anchor:$=j}={}){let q;for(let B=0;B<Y;B+=1){let F;try{F=await z.evaluate(D)}catch(G){F={drift:!0,err:String(G&&G.message||G)}}q=F;if(F&&F.drift){await z.goto($);await z.wait(2);continue}return F}throw new V(`Hashnode ${Z}: browser tab kept drifting (last: ${JSON.stringify(q)})`)}I({site:"hashnode",name:"publish",access:"write",description:"Publish a Hashnode post via the web editor (or save a draft with --draft). Markdown body + tags/cover/canonical.",domain:"hashnode.com",strategy:S.COOKIE,browser:!0,defaultWindowMode:"foreground",siteSession:"persistent",args:[{name:"title",type:"string",required:!0,help:"Post title"},{name:"body",type:"string",required:!1,help:"Post body in Markdown (or use --body-file)"},{name:"body-file",type:"string",required:!1,help:"Path to a Markdown file for the body"},{name:"tags",type:"string",required:!1,help:"Comma-separated tag names"},{name:"cover-image",type:"string",required:!1,help:"Cover image URL"},{name:"canonical-url",type:"string",required:!1,help:"Canonical / original article URL"},{name:"draft",type:"boolean",required:!1,default:!1,help:"Save as draft instead of publishing"}],columns:["status","draft_id","url"],func:async(z,D)=>{if(!z)throw new V("Browser session required for hashnode publish");const Y=String(D.title??"").trim();if(!Y)throw new _("hashnode publish: --title is required");const Z=resolveBody(D);if(Z===void 0)throw new _("--body or --body-file is required");const $=resolveTags(D.tags),q=D["canonical-url"]?String(D["canonical-url"]):"",B=D["cover-image"]?String(D["cover-image"]):"";await z.goto(j);const F=await W(z,h,{label:"auth"});if(!F||!F.ok)throw new b("hashnode.com","Not logged in. Run `opencli hashnode login`.");const G=await W(z,`(async () => {
9
+ })()`;async function W(z,D,{tries:Y=8,label:Z="eval",anchor:$=j}={}){let q;for(let B=0;B<Y;B+=1){let F;try{F=await z.evaluate(D)}catch(G){F={drift:!0,err:String(G&&G.message||G)}}q=F;if(F&&F.drift){await z.goto($);await z.wait(2);continue}return F}throw new V(`Hashnode ${Z}: browser tab kept drifting (last: ${JSON.stringify(q)})`)}I({site:"hashnode",name:"publish",access:"write",description:"Publish a Hashnode post via the web editor (or save a draft with --draft). Markdown body + tags/cover/canonical.",domain:"hashnode.com",strategy:S.COOKIE,browser:!0,defaultWindowMode:"foreground",siteSession:"persistent",args:[{name:"title",type:"string",required:!0,help:"Post title"},{name:"body",type:"string",required:!1,help:"Post body in Markdown (or use --body-file)"},{name:"body-file",type:"string",required:!1,help:"Path to a Markdown file for the body"},{name:"tags",type:"string",required:!1,help:"Comma-separated tag names"},{name:"cover-image",type:"string",required:!1,help:"Cover image URL"},{name:"canonical-url",type:"string",required:!1,help:"Canonical / original article URL"},{name:"draft",type:"boolean",required:!1,default:!1,help:"Save as draft instead of publishing"}],columns:["status","draft_id","url"],func:async(z,D)=>{if(!z)throw new V("Browser session required for hashnode publish");const Y=String(D.title??"").trim();if(!Y)throw new _("hashnode publish: --title is required");const Z=resolveBody(D);if(Z===void 0)throw new _("--body or --body-file is required");const $=resolveTags(D.tags),q=D["canonical-url"]?String(D["canonical-url"]):"",B=D["cover-image"]?String(D["cover-image"]):"";await z.goto(j);const F=await W(z,h,{label:"auth"});if(!F||!F.ok)throw new b("hashnode.com","Not logged in. Run `ppcli hashnode login`.");const G=await W(z,`(async () => {
10
10
  if (location.href.indexOf('hashnode.com') < 0) return { drift: true };
11
11
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
12
12
  const w = [...document.querySelectorAll('a,button')].find((e) => (e.innerText || '').trim() === 'Write');
@@ -1 +1 @@
1
- import{cli as y,Strategy as S}from"@jackwener/opencli/registry";import{ArgumentError as n,CommandExecutionError as d,EmptyResultError as k}from"@jackwener/opencli/errors";const c=["downloads","likes","trending","created_at","last_modified"],A={lastmodified:"last_modified",createdat:"created_at"};y({site:"hf",name:"datasets",access:"read",description:"Top Hugging Face datasets (downloads / likes / trending / freshness).",domain:"huggingface.co",strategy:S.PUBLIC,browser:!1,args:[{name:"sort",type:"string",default:"downloads",help:`Sort key: ${c.join(", ")}`},{name:"search",type:"string",required:!1,help:"Optional name/owner substring filter."},{name:"limit",type:"int",default:20,help:"Max datasets (max 100; one API page)."}],columns:["rank","id","author","downloads","likes","tags","lastModified","url"],func:async(r)=>{const l=String(r.sort??"downloads").toLowerCase(),h=A[l]??l;if(!c.includes(h))throw new n(`hf datasets sort must be one of ${c.join(", ")}`);const s=Number(r.limit??20);if(!Number.isInteger(s)||s<=0)throw new n("hf datasets limit must be a positive integer");if(s>100)throw new n("hf datasets limit must be <= 100");const e=new URL("https://huggingface.co/api/datasets");e.searchParams.set("sort",h);e.searchParams.set("direction","-1");e.searchParams.set("limit",String(s));e.searchParams.set("full","true");if(r.search)e.searchParams.set("search",String(r.search));let i;try{i=await fetch(e,{headers:{Accept:"application/json","User-Agent":"opencli/1.0 (+https://github.com/jackwener/opencli)"}})}catch(t){throw new d(`hf datasets request failed: ${t?.message||t}`)}if(!i.ok)throw new d(`hf datasets failed: HTTP ${i.status}`);let o;try{o=await i.json()}catch(t){throw new d(`hf datasets returned malformed JSON: ${t?.message||t}`)}const f=Array.isArray(o)?o:[];if(f.length===0)throw new k("hf datasets","No matching datasets on huggingface.co.");return f.slice(0,s).map((t,g)=>{const a=t.id||"",m=a.indexOf("/"),u=m>0?a.slice(0,m):"",w=Array.isArray(t.tags)?t.tags.filter((p)=>!p.startsWith("license:")).slice(0,10).join(", "):"";return{rank:g+1,id:a,author:u,downloads:t.downloads??0,likes:t.likes??0,tags:w,lastModified:t.lastModified?String(t.lastModified).slice(0,10):"",url:a?`https://huggingface.co/datasets/${a}`:""}})}});
1
+ import{cli as y,Strategy as S}from"@jackwener/opencli/registry";import{ArgumentError as n,CommandExecutionError as d,EmptyResultError as k}from"@jackwener/opencli/errors";const c=["downloads","likes","trending","created_at","last_modified"],A={lastmodified:"last_modified",createdat:"created_at"};y({site:"hf",name:"datasets",access:"read",description:"Top Hugging Face datasets (downloads / likes / trending / freshness).",domain:"huggingface.co",strategy:S.PUBLIC,browser:!1,args:[{name:"sort",type:"string",default:"downloads",help:`Sort key: ${c.join(", ")}`},{name:"search",type:"string",required:!1,help:"Optional name/owner substring filter."},{name:"limit",type:"int",default:20,help:"Max datasets (max 100; one API page)."}],columns:["rank","id","author","downloads","likes","tags","lastModified","url"],func:async(r)=>{const l=String(r.sort??"downloads").toLowerCase(),h=A[l]??l;if(!c.includes(h))throw new n(`hf datasets sort must be one of ${c.join(", ")}`);const s=Number(r.limit??20);if(!Number.isInteger(s)||s<=0)throw new n("hf datasets limit must be a positive integer");if(s>100)throw new n("hf datasets limit must be <= 100");const e=new URL("https://huggingface.co/api/datasets");e.searchParams.set("sort",h);e.searchParams.set("direction","-1");e.searchParams.set("limit",String(s));e.searchParams.set("full","true");if(r.search)e.searchParams.set("search",String(r.search));let i;try{i=await fetch(e,{headers:{Accept:"application/json","User-Agent":"ppcli/1.0 (+https://github.com/jackwener/OpenCLI)"}})}catch(t){throw new d(`hf datasets request failed: ${t?.message||t}`)}if(!i.ok)throw new d(`hf datasets failed: HTTP ${i.status}`);let o;try{o=await i.json()}catch(t){throw new d(`hf datasets returned malformed JSON: ${t?.message||t}`)}const f=Array.isArray(o)?o:[];if(f.length===0)throw new k("hf datasets","No matching datasets on huggingface.co.");return f.slice(0,s).map((t,g)=>{const a=t.id||"",m=a.indexOf("/"),u=m>0?a.slice(0,m):"",w=Array.isArray(t.tags)?t.tags.filter((p)=>!p.startsWith("license:")).slice(0,10).join(", "):"";return{rank:g+1,id:a,author:u,downloads:t.downloads??0,likes:t.likes??0,tags:w,lastModified:t.lastModified?String(t.lastModified).slice(0,10):"",url:a?`https://huggingface.co/datasets/${a}`:""}})}});
package/clis/hf/models.js CHANGED
@@ -1 +1 @@
1
- import{cli as y,Strategy as S}from"@jackwener/opencli/registry";import{ArgumentError as n,CommandExecutionError as l,EmptyResultError as k}from"@jackwener/opencli/errors";const d=["downloads","likes","trending","created_at","last_modified"],b={lastmodified:"last_modified",createdat:"created_at"};y({site:"hf",name:"models",access:"read",description:"Top Hugging Face models (downloads / likes / trending / freshness).",domain:"huggingface.co",strategy:S.PUBLIC,browser:!1,args:[{name:"sort",type:"string",default:"downloads",help:`Sort key: ${d.join(", ")}`},{name:"search",type:"string",required:!1,help:'Optional name/owner substring filter (e.g. "llama", "mistralai/")'},{name:"pipeline",type:"string",required:!1,help:"Filter by pipeline tag (e.g. text-generation, image-classification)"},{name:"limit",type:"int",default:20,help:"Max models (max 100; one API page)."}],columns:["rank","id","author","pipelineTag","downloads","likes","tags","lastModified","url"],func:async(i)=>{const c=String(i.sort??"downloads").toLowerCase(),m=b[c]??c;if(!d.includes(m))throw new n(`hf models sort must be one of ${d.join(", ")}`);const s=Number(i.limit??20);if(!Number.isInteger(s)||s<=0)throw new n("hf models limit must be a positive integer");if(s>100)throw new n("hf models limit must be <= 100");const t=new URL("https://huggingface.co/api/models");t.searchParams.set("sort",m);t.searchParams.set("direction","-1");t.searchParams.set("limit",String(s));t.searchParams.set("full","true");if(i.search)t.searchParams.set("search",String(i.search));if(i.pipeline)t.searchParams.set("pipeline_tag",String(i.pipeline));let r;try{r=await fetch(t,{headers:{Accept:"application/json","User-Agent":"opencli/1.0 (+https://github.com/jackwener/opencli)"}})}catch(e){throw new l(`hf models request failed: ${e?.message||e}`)}if(!r.ok)throw new l(`hf models failed: HTTP ${r.status}`);let o;try{o=await r.json()}catch(e){throw new l(`hf models returned malformed JSON: ${e?.message||e}`)}const g=Array.isArray(o)?o:[];if(g.length===0)throw new k("hf models","No matching models on huggingface.co.");return g.slice(0,s).map((e,f)=>{const a=e.id||e.modelId||"",h=a.indexOf("/"),p=h>0?a.slice(0,h):"",u=Array.isArray(e.tags)?e.tags.filter((w)=>!w.startsWith("license:")).slice(0,10).join(", "):"";return{rank:f+1,id:a,author:p,pipelineTag:e.pipeline_tag||e.pipelineTag||"",downloads:e.downloads??0,likes:e.likes??0,tags:u,lastModified:e.lastModified?String(e.lastModified).slice(0,10):"",url:a?`https://huggingface.co/${a}`:""}})}});
1
+ import{cli as y,Strategy as S}from"@jackwener/opencli/registry";import{ArgumentError as n,CommandExecutionError as l,EmptyResultError as k}from"@jackwener/opencli/errors";const d=["downloads","likes","trending","created_at","last_modified"],b={lastmodified:"last_modified",createdat:"created_at"};y({site:"hf",name:"models",access:"read",description:"Top Hugging Face models (downloads / likes / trending / freshness).",domain:"huggingface.co",strategy:S.PUBLIC,browser:!1,args:[{name:"sort",type:"string",default:"downloads",help:`Sort key: ${d.join(", ")}`},{name:"search",type:"string",required:!1,help:'Optional name/owner substring filter (e.g. "llama", "mistralai/")'},{name:"pipeline",type:"string",required:!1,help:"Filter by pipeline tag (e.g. text-generation, image-classification)"},{name:"limit",type:"int",default:20,help:"Max models (max 100; one API page)."}],columns:["rank","id","author","pipelineTag","downloads","likes","tags","lastModified","url"],func:async(s)=>{const c=String(s.sort??"downloads").toLowerCase(),m=b[c]??c;if(!d.includes(m))throw new n(`hf models sort must be one of ${d.join(", ")}`);const i=Number(s.limit??20);if(!Number.isInteger(i)||i<=0)throw new n("hf models limit must be a positive integer");if(i>100)throw new n("hf models limit must be <= 100");const t=new URL("https://huggingface.co/api/models");t.searchParams.set("sort",m);t.searchParams.set("direction","-1");t.searchParams.set("limit",String(i));t.searchParams.set("full","true");if(s.search)t.searchParams.set("search",String(s.search));if(s.pipeline)t.searchParams.set("pipeline_tag",String(s.pipeline));let r;try{r=await fetch(t,{headers:{Accept:"application/json","User-Agent":"ppcli/1.0 (+https://github.com/jackwener/OpenCLI)"}})}catch(e){throw new l(`hf models request failed: ${e?.message||e}`)}if(!r.ok)throw new l(`hf models failed: HTTP ${r.status}`);let o;try{o=await r.json()}catch(e){throw new l(`hf models returned malformed JSON: ${e?.message||e}`)}const g=Array.isArray(o)?o:[];if(g.length===0)throw new k("hf models","No matching models on huggingface.co.");return g.slice(0,i).map((e,p)=>{const a=e.id||e.modelId||"",h=a.indexOf("/"),f=h>0?a.slice(0,h):"",u=Array.isArray(e.tags)?e.tags.filter((w)=>!w.startsWith("license:")).slice(0,10).join(", "):"";return{rank:p+1,id:a,author:f,pipelineTag:e.pipeline_tag||e.pipelineTag||"",downloads:e.downloads??0,likes:e.likes??0,tags:u,lastModified:e.lastModified?String(e.lastModified).slice(0,10):"",url:a?`https://huggingface.co/${a}`:""}})}});
package/clis/hf/paper.js CHANGED
@@ -1 +1 @@
1
- import{cli as c,Strategy as h}from"@jackwener/opencli/registry";import{ArgumentError as n,CommandExecutionError as i,EmptyResultError as p}from"@jackwener/opencli/errors";const m=/^\d{4}\.\d{4,5}(?:v\d+)?$/;c({site:"hf",name:"paper",access:"read",description:"Hugging Face paper detail by arXiv id (full title / summary / authors / AI keywords)",domain:"huggingface.co",strategy:h.PUBLIC,browser:!1,args:[{name:"id",positional:!0,required:!0,help:'arXiv id (e.g. "1706.03762") — same value HF uses to mirror the paper'}],columns:["id","title","authors","publishedAt","upvotes","aiKeywords","summary","aiSummary","url"],func:async(o)=>{const a=String(o.id??"").trim();if(!a)throw new n("hf paper id cannot be empty","Example: opencli hf paper 1706.03762");if(!m.test(a))throw new n(`hf paper id "${o.id}" is not a valid arXiv id`,"Expected the modern arXiv form `YYMM.NNNNN` (optionally with a version suffix like `v3`).");const s=process.env.HF_ENDPOINT?.replace(/\/+$/,"")||"https://huggingface.co",u=`${s}/api/papers/${encodeURIComponent(a)}`;let t;try{t=await fetch(u,{headers:{accept:"application/json"}})}catch(r){throw new i(`hf paper request failed: ${r?.message??r}`)}if(t.status===404)throw new p("hf paper",`Hugging Face has no paper page for "${a}".`);if(t.status===429)throw new i("hf paper returned HTTP 429 (rate limited)","Hugging Face throttles unauthenticated traffic; wait a few seconds and retry.");if(!t.ok)throw new i(`hf paper returned HTTP ${t.status}`);let e;try{e=await t.json()}catch(r){throw new i(`hf paper returned malformed JSON: ${r?.message??r}`)}if(!e||typeof e!=="object"||!e.id)throw new p("hf paper",`Hugging Face returned no paper data for "${a}".`);const d=Array.isArray(e.authors)?e.authors.map((r)=>typeof r==="object"&&r?r.name||r.fullname||"":String(r)).filter(Boolean):[],l=Array.isArray(e.ai_keywords)?e.ai_keywords.join(", "):"";return[{id:String(e.id),title:String(e.title??""),authors:d.join(", "),publishedAt:String(e.publishedAt??"").slice(0,10),upvotes:e.upvotes!=null?Number(e.upvotes):null,aiKeywords:l,summary:String(e.summary??""),aiSummary:String(e.ai_summary??""),url:`${s}/papers/${e.id}`}]}});
1
+ import{cli as c,Strategy as h}from"@jackwener/opencli/registry";import{ArgumentError as n,CommandExecutionError as i,EmptyResultError as p}from"@jackwener/opencli/errors";const m=/^\d{4}\.\d{4,5}(?:v\d+)?$/;c({site:"hf",name:"paper",access:"read",description:"Hugging Face paper detail by arXiv id (full title / summary / authors / AI keywords)",domain:"huggingface.co",strategy:h.PUBLIC,browser:!1,args:[{name:"id",positional:!0,required:!0,help:'arXiv id (e.g. "1706.03762") — same value HF uses to mirror the paper'}],columns:["id","title","authors","publishedAt","upvotes","aiKeywords","summary","aiSummary","url"],func:async(o)=>{const a=String(o.id??"").trim();if(!a)throw new n("hf paper id cannot be empty","Example: ppcli hf paper 1706.03762");if(!m.test(a))throw new n(`hf paper id "${o.id}" is not a valid arXiv id`,"Expected the modern arXiv form `YYMM.NNNNN` (optionally with a version suffix like `v3`).");const s=process.env.HF_ENDPOINT?.replace(/\/+$/,"")||"https://huggingface.co",u=`${s}/api/papers/${encodeURIComponent(a)}`;let t;try{t=await fetch(u,{headers:{accept:"application/json"}})}catch(r){throw new i(`hf paper request failed: ${r?.message??r}`)}if(t.status===404)throw new p("hf paper",`Hugging Face has no paper page for "${a}".`);if(t.status===429)throw new i("hf paper returned HTTP 429 (rate limited)","Hugging Face throttles unauthenticated traffic; wait a few seconds and retry.");if(!t.ok)throw new i(`hf paper returned HTTP ${t.status}`);let e;try{e=await t.json()}catch(r){throw new i(`hf paper returned malformed JSON: ${r?.message??r}`)}if(!e||typeof e!=="object"||!e.id)throw new p("hf paper",`Hugging Face returned no paper data for "${a}".`);const d=Array.isArray(e.authors)?e.authors.map((r)=>typeof r==="object"&&r?r.name||r.fullname||"":String(r)).filter(Boolean):[],l=Array.isArray(e.ai_keywords)?e.ai_keywords.join(", "):"";return[{id:String(e.id),title:String(e.title??""),authors:d.join(", "),publishedAt:String(e.publishedAt??"").slice(0,10),upvotes:e.upvotes!=null?Number(e.upvotes):null,aiKeywords:l,summary:String(e.summary??""),aiSummary:String(e.ai_summary??""),url:`${s}/papers/${e.id}`}]}});
package/clis/hf/spaces.js CHANGED
@@ -1 +1 @@
1
- import{cli as S,Strategy as y}from"@jackwener/opencli/registry";import{ArgumentError as o,CommandExecutionError as n,EmptyResultError as b}from"@jackwener/opencli/errors";const d=["likes","created_at","last_modified"],P={lastmodified:"last_modified",createdat:"created_at"};S({site:"hf",name:"spaces",access:"read",description:"Top Hugging Face Spaces (likes / created_at / last_modified).",domain:"huggingface.co",strategy:y.PUBLIC,browser:!1,args:[{name:"sort",type:"string",default:"likes",help:`Sort key: ${d.join(", ")}`},{name:"search",type:"string",required:!1,help:'Optional name/owner substring filter (e.g. "stability", "openai/")'},{name:"sdk",type:"string",required:!1,help:"Filter by Space SDK: gradio / streamlit / docker / static"},{name:"limit",type:"int",default:20,help:"Max spaces (max 100; one API page)."}],columns:["rank","id","author","sdk","likes","tags","lastModified","url"],func:async(s)=>{const h=String(s.sort??"likes").toLowerCase(),f=P[h]??h;if(!d.includes(f))throw new o(`hf spaces sort must be one of ${d.join(", ")}`);const a=Number(s.limit??20);if(!Number.isInteger(a)||a<=0)throw new o("hf spaces limit must be a positive integer");if(a>100)throw new o("hf spaces limit must be <= 100");const c=s.sdk==null?"":String(s.sdk).trim().toLowerCase();if(!new Set(["","gradio","streamlit","docker","static"]).has(c))throw new o("hf spaces sdk must be one of gradio / streamlit / docker / static");const t=new URL("https://huggingface.co/api/spaces");t.searchParams.set("sort",f);t.searchParams.set("direction","-1");t.searchParams.set("limit",String(a));t.searchParams.set("full","true");if(s.search)t.searchParams.set("search",String(s.search));if(c)t.searchParams.set("sdk",c);let r;try{r=await fetch(t,{headers:{Accept:"application/json","User-Agent":"opencli/1.0 (+https://github.com/jackwener/opencli)"}})}catch(e){throw new n(`hf spaces request failed: ${e?.message??e}`)}if(r.status===429)throw new n("hf spaces returned HTTP 429 (rate limited)","Hugging Face throttles unauthenticated traffic; wait a few seconds and retry.");if(!r.ok)throw new n(`hf spaces failed: HTTP ${r.status}`);let l;try{l=await r.json()}catch(e){throw new n(`hf spaces returned malformed JSON: ${e?.message??e}`)}const m=Array.isArray(l)?l:[];if(!m.length)throw new b("hf spaces","No matching spaces on huggingface.co.");return m.slice(0,a).map((e,p)=>{const i=String(e.id??e._id??""),g=i.indexOf("/"),u=String(e.author??(g>0?i.slice(0,g):"")),w=Array.isArray(e.tags)?e.tags.filter((k)=>!String(k).startsWith("license:")).slice(0,10).join(", "):"";return{rank:p+1,id:i,author:u,sdk:String(e.sdk??""),likes:e.likes!=null?Number(e.likes):null,tags:w,lastModified:String(e.lastModified??"").slice(0,10),url:i?`https://huggingface.co/spaces/${i}`:""}})}});
1
+ import{cli as S,Strategy as y}from"@jackwener/opencli/registry";import{ArgumentError as o,CommandExecutionError as n,EmptyResultError as b}from"@jackwener/opencli/errors";const d=["likes","created_at","last_modified"],P={lastmodified:"last_modified",createdat:"created_at"};S({site:"hf",name:"spaces",access:"read",description:"Top Hugging Face Spaces (likes / created_at / last_modified).",domain:"huggingface.co",strategy:y.PUBLIC,browser:!1,args:[{name:"sort",type:"string",default:"likes",help:`Sort key: ${d.join(", ")}`},{name:"search",type:"string",required:!1,help:'Optional name/owner substring filter (e.g. "stability", "openai/")'},{name:"sdk",type:"string",required:!1,help:"Filter by Space SDK: gradio / streamlit / docker / static"},{name:"limit",type:"int",default:20,help:"Max spaces (max 100; one API page)."}],columns:["rank","id","author","sdk","likes","tags","lastModified","url"],func:async(s)=>{const h=String(s.sort??"likes").toLowerCase(),f=P[h]??h;if(!d.includes(f))throw new o(`hf spaces sort must be one of ${d.join(", ")}`);const a=Number(s.limit??20);if(!Number.isInteger(a)||a<=0)throw new o("hf spaces limit must be a positive integer");if(a>100)throw new o("hf spaces limit must be <= 100");const c=s.sdk==null?"":String(s.sdk).trim().toLowerCase();if(!new Set(["","gradio","streamlit","docker","static"]).has(c))throw new o("hf spaces sdk must be one of gradio / streamlit / docker / static");const t=new URL("https://huggingface.co/api/spaces");t.searchParams.set("sort",f);t.searchParams.set("direction","-1");t.searchParams.set("limit",String(a));t.searchParams.set("full","true");if(s.search)t.searchParams.set("search",String(s.search));if(c)t.searchParams.set("sdk",c);let r;try{r=await fetch(t,{headers:{Accept:"application/json","User-Agent":"ppcli/1.0 (+https://github.com/jackwener/OpenCLI)"}})}catch(e){throw new n(`hf spaces request failed: ${e?.message??e}`)}if(r.status===429)throw new n("hf spaces returned HTTP 429 (rate limited)","Hugging Face throttles unauthenticated traffic; wait a few seconds and retry.");if(!r.ok)throw new n(`hf spaces failed: HTTP ${r.status}`);let l;try{l=await r.json()}catch(e){throw new n(`hf spaces returned malformed JSON: ${e?.message??e}`)}const m=Array.isArray(l)?l:[];if(!m.length)throw new b("hf spaces","No matching spaces on huggingface.co.");return m.slice(0,a).map((e,p)=>{const i=String(e.id??e._id??""),g=i.indexOf("/"),u=String(e.author??(g>0?i.slice(0,g):"")),w=Array.isArray(e.tags)?e.tags.filter((k)=>!String(k).startsWith("license:")).slice(0,10).join(", "):"";return{rank:p+1,id:i,author:u,sdk:String(e.sdk??""),likes:e.likes!=null?Number(e.likes):null,tags:w,lastModified:String(e.lastModified??"").slice(0,10),url:i?`https://huggingface.co/spaces/${i}`:""}})}});
@@ -1 +1 @@
1
- import{ArgumentError as G,CommandExecutionError as J,EmptyResultError as L}from"@jackwener/opencli/errors";export const BREW_BASE="https://formulae.brew.sh/api";const M="opencli-homebrew-adapter (+https://github.com/jackwener/opencli)",P=/^[A-Za-z0-9][A-Za-z0-9._+@-]*$/;export function requireString(B,z){const q=String(B??"").trim();if(!q)throw new G(`homebrew ${z} cannot be empty`);return q}export function requireBoundedInt(B,z,q,D="limit"){const F=B??z,I=typeof F==="number"?F:Number(F);if(!Number.isInteger(I)||I<=0)throw new G(`homebrew ${D} must be a positive integer`);if(I>q)throw new G(`homebrew ${D} must be <= ${q}`);return I}export function requireToken(B,z){const q=String(B??"").trim();if(!q)throw new G(`homebrew ${z} is required (e.g. "wget", "gcc@13", "firefox")`);if(q.length>100||!P.test(q))throw new G(`homebrew ${z} "${B}" is not a valid token`,'Use letters / digits / "_-.+@", starting with a letter or digit (max 100 chars).');return q}export function requireOneOf(B,z,q){const D=String(B??"").trim().toLowerCase();if(!D)throw new G(`homebrew ${q} is required`);if(!z.includes(D))throw new G(`homebrew ${q} "${B}" is not supported`,`Allowed: ${z.join(", ")}.`);return D}export async function brewFetch(B,z){let q;try{q=await fetch(B,{headers:{"user-agent":M,accept:"application/json"}})}catch(F){throw new J(`${z} request failed: ${F?.message??F}`,"Check that formulae.brew.sh is reachable from this network.")}if(q.status===404)throw new L(z,`Homebrew API returned 404 for ${B}.`);if(q.status===429)throw new J(`${z} returned HTTP 429 (rate limited)`,"Homebrew throttles bursts; wait a few seconds and retry.");if(!q.ok)throw new J(`${z} returned HTTP ${q.status}`);let D;try{D=await q.json()}catch(F){throw new J(`${z} returned malformed JSON: ${F?.message??F}`)}return D}export function parseInstallCount(B){if(B==null)return null;const z=String(B).replace(/,/g,"").trim();if(!z)return null;const q=Number(z);return Number.isFinite(q)?q:null}
1
+ import{ArgumentError as G,CommandExecutionError as J,EmptyResultError as L}from"@jackwener/opencli/errors";export const BREW_BASE="https://formulae.brew.sh/api";const M="opencli-homebrew-adapter (+https://github.com/jackwener/OpenCLI)",P=/^[A-Za-z0-9][A-Za-z0-9._+@-]*$/;export function requireString(B,z){const q=String(B??"").trim();if(!q)throw new G(`homebrew ${z} cannot be empty`);return q}export function requireBoundedInt(B,z,q,D="limit"){const F=B??z,I=typeof F==="number"?F:Number(F);if(!Number.isInteger(I)||I<=0)throw new G(`homebrew ${D} must be a positive integer`);if(I>q)throw new G(`homebrew ${D} must be <= ${q}`);return I}export function requireToken(B,z){const q=String(B??"").trim();if(!q)throw new G(`homebrew ${z} is required (e.g. "wget", "gcc@13", "firefox")`);if(q.length>100||!P.test(q))throw new G(`homebrew ${z} "${B}" is not a valid token`,'Use letters / digits / "_-.+@", starting with a letter or digit (max 100 chars).');return q}export function requireOneOf(B,z,q){const D=String(B??"").trim().toLowerCase();if(!D)throw new G(`homebrew ${q} is required`);if(!z.includes(D))throw new G(`homebrew ${q} "${B}" is not supported`,`Allowed: ${z.join(", ")}.`);return D}export async function brewFetch(B,z){let q;try{q=await fetch(B,{headers:{"user-agent":M,accept:"application/json"}})}catch(F){throw new J(`${z} request failed: ${F?.message??F}`,"Check that formulae.brew.sh is reachable from this network.")}if(q.status===404)throw new L(z,`Homebrew API returned 404 for ${B}.`);if(q.status===429)throw new J(`${z} returned HTTP 429 (rate limited)`,"Homebrew throttles bursts; wait a few seconds and retry.");if(!q.ok)throw new J(`${z} returned HTTP ${q.status}`);let D;try{D=await q.json()}catch(F){throw new J(`${z} returned malformed JSON: ${F?.message??F}`)}return D}export function parseInstallCount(B){if(B==null)return null;const z=String(B).replace(/,/g,"").trim();if(!z)return null;const q=Number(z);return Number.isFinite(q)?q:null}
@@ -1,4 +1,4 @@
1
- import{ArgumentError as s,CommandExecutionError as r}from"@jackwener/opencli/errors";import{cli as d,Strategy as p}from"@jackwener/opencli/registry";const l="https://www.instagram.com/direct/inbox/",_="25155183657506484",a="usePolarisCreateInboxTrayItemSubmitMutation",i="xdt_create_inbox_tray_item";function m(t){if(!t)throw new r("Browser session required for instagram note");return t}function u(t){if(t.content===void 0)throw new s('Argument "content" is required.','Provide a note text, for example: opencli instagram note "hello"')}function f(t){const e=String(t.content??"").trim();if(!e)throw new s("Instagram note content cannot be empty.",'Provide a non-empty note text, for example: opencli instagram note "hello"');if(Array.from(e).length>60)throw new s("Instagram note content must be 60 characters or fewer.","Shorten the note text and try again.");return e}function b(t){return[{status:"✅ Posted",detail:"Instagram note published successfully",noteId:t}]}function y(t){return`
1
+ import{ArgumentError as s,CommandExecutionError as r}from"@jackwener/opencli/errors";import{cli as d,Strategy as p}from"@jackwener/opencli/registry";const l="https://www.instagram.com/direct/inbox/",_="25155183657506484",a="usePolarisCreateInboxTrayItemSubmitMutation",i="xdt_create_inbox_tray_item";function m(t){if(!t)throw new r("Browser session required for instagram note");return t}function u(t){if(t.content===void 0)throw new s('Argument "content" is required.','Provide a note text, for example: ppcli instagram note "hello"')}function f(t){const e=String(t.content??"").trim();if(!e)throw new s("Instagram note content cannot be empty.",'Provide a non-empty note text, for example: ppcli instagram note "hello"');if(Array.from(e).length>60)throw new s("Instagram note content must be 60 characters or fewer.","Shorten the note text and try again.");return e}function b(t){return[{status:"✅ Posted",detail:"Instagram note published successfully",noteId:t}]}function y(t){return`
2
2
  (async () => {
3
3
  const input = ${JSON.stringify({content:t})};
4
4
  const html = document.documentElement?.outerHTML || '';
@@ -57,4 +57,4 @@ import{cli as h,Strategy as G}from"@jackwener/opencli/registry";import{CommandEx
57
57
  if (!done.length && failMsg) return { state: 'failed', error: failMsg };
58
58
  return { state: 'pending', got: done.length, total };
59
59
  })()`;h({site:"jimeng",name:"generate",access:"write",description:"即梦AI 文生图 — 输入 prompt 生成图片",domain:"jimeng.jianying.com",strategy:G.COOKIE,browser:!0,navigateBefore:!1,siteSession:"persistent",args:[{name:"prompt",type:"string",required:!0,positional:!0,help:"图片描述 prompt"},{name:"wait",type:"int",default:60,help:"等待生成完成的最长秒数(每 3 秒轮询一次,超时会返回 pending 并提示用 history 回查)"}],columns:["status","prompt","model","image_count","image_urls"],func:async(q,K)=>{const z=String(K.prompt??"").trim();if(!z)throw new F("prompt is required");const Q=Number(K.wait??60);await q.goto(M);await q.wait(3);const v=await q.evaluate(N(z));if(!v?.ok){const $={editor_not_found:"找不到 prompt 输入框(页面结构可能已变)",button_not_found:"找不到生成按钮(页面结构可能已变)",button_disabled:"生成按钮不可点击(可能未登录、额度用尽或 prompt 为空)"}[v?.error]||`提交失败:${v?.error||"unknown"}`;throw new F($)}const X=Array.isArray(v.existing)?v.existing:[],Z=Date.now()+Q*1000;let f=null;while(Date.now()<Z){await q.wait(3);f=await q.evaluate(j(X));if(f?.state==="success")return[{status:"success",prompt:z.slice(0,80),model:f.model||"",image_count:f.urls.length,image_urls:f.urls.join(`
60
- `)}];if(f?.state==="failed")throw new F(`即梦生成失败:${f.error}`)}return[{status:"pending",prompt:z.slice(0,80),model:f&&f.model||"",image_count:f&&f.got||0,image_urls:`生成仍在进行(已 ${Q}s);稍后用 \`opencli jimeng history\` 回查结果`}]}});
60
+ `)}];if(f?.state==="failed")throw new F(`即梦生成失败:${f.error}`)}return[{status:"pending",prompt:z.slice(0,80),model:f&&f.model||"",image_count:f&&f.got||0,image_urls:`生成仍在进行(已 ${Q}s);稍后用 \`ppcli jimeng history\` 回查结果`}]}});
@@ -1 +1 @@
1
- import{ArgumentError as $,CommandExecutionError as Z,EmptyResultError as D}from"@jackwener/opencli/errors";export const JUEJIN_API_BASE="https://api.juejin.cn",JUEJIN_POST_URL="https://juejin.cn/post",JUEJIN_USER_URL="https://juejin.cn/user";const P="opencli-juejin-adapter (+https://github.com/jackwener/opencli)",B=/^\d{16,20}$/;export const CATEGORY_ALIASES={backend:"6809637769959178254",frontend:"6809637767543259144",android:"6809635626879549454",ios:"6809635626661445640",ai:"6809637773935378440"};export function requireString(z,q){const F=String(z??"").trim();if(!F)throw new $(`juejin ${q} cannot be empty`);return F}export function requireBoundedInt(z,q,F,H="limit"){const W=z??q;let K;if(typeof W==="number")K=W;else if(typeof W==="string"&&/^[1-9]\d*$/.test(W))K=Number(W);else throw new $(`juejin ${H} must be a positive decimal integer`);if(!Number.isSafeInteger(K)||K<=0)throw new $(`juejin ${H} must be a positive integer`);if(K>F)throw new $(`juejin ${H} must be <= ${F}`);return K}export function requireCursor(z){const q=z??"0";if(typeof q==="number"){if(Number.isSafeInteger(q)&&q>=0)return String(q);throw new $("juejin cursor must be a non-negative decimal integer")}if(typeof q==="string"&&/^(0|[1-9]\d*)$/.test(q))return q;throw new $("juejin cursor must be a non-negative decimal integer")}export function resolveCategory(z){if(z==null)return"";const q=String(z).trim();if(!q)return"";if(B.test(q))return q;const F=q.toLowerCase();if(CATEGORY_ALIASES[F])return CATEGORY_ALIASES[F];throw new $(`juejin category "${z}" is not recognised`,`Use a category id (e.g. "${CATEGORY_ALIASES.backend}") or one of: ${Object.keys(CATEGORY_ALIASES).join(", ")}.`)}export async function juejinFetch(z,q,F,H="POST"){const W=`${JUEJIN_API_BASE}${z}`;let K;try{const X={method:H,headers:{"user-agent":P,accept:"application/json"}};if(H==="POST"){X.headers["content-type"]="application/json";X.body=JSON.stringify(q??{})}K=await fetch(W,X)}catch(X){throw new Z(`${F} request failed: ${X?.message??X}`,"Check that api.juejin.cn is reachable from this network.")}if(K.status===429)throw new Z(`${F} returned HTTP 429 (rate limited)`,"Juejin throttles bursty traffic; wait a few seconds and retry.");if(!K.ok)throw new Z(`${F} returned HTTP ${K.status}`);let Q;try{Q=await K.json()}catch(X){throw new Z(`${F} returned malformed JSON: ${X?.message??X}`)}if(!Q||typeof Q!=="object"||Array.isArray(Q)||!Object.hasOwn(Q,"err_no"))throw new Z(`${F} returned a malformed API envelope`);if(Q.err_no!==0)throw new Z(`${F} returned err_no ${Q.err_no}: ${Q.err_msg??""}`);return Q}export function readDataArray(z,q){if(!z||typeof z!=="object"||Array.isArray(z)||!Object.hasOwn(z,"data"))throw new Z(`${q} returned a malformed payload`);if(!Array.isArray(z.data))throw new Z(`${q} returned a non-array data field`);if(z.data.length===0)throw new D(q,`${q} returned no articles.`);return z.data}function M(z,q){const F=String(z??"").trim();if(!B.test(F))throw new Z(`${q} returned a malformed article id`);return F}function j(z,q){if(z==null)return null;const F=Number(z);if(!Number.isFinite(F))throw new Z(`${q} returned a malformed numeric field`);return F}export function mapFeedItem(z,q){const F=z?.item_info??{},H=F.article_info??{},W=F.author_user_info??{},K=Array.isArray(F.tags)?F.tags.map((X)=>X?.tag_name).filter(Boolean).slice(0,6).join(", "):"",Q=M(H.article_id,"juejin recommend");return{rank:q,article_id:Q,title:String(H.title??"").trim(),brief:String(H.brief_content??"").trim(),views:j(H.view_count,"juejin recommend"),likes:j(H.digg_count,"juejin recommend"),comments:j(H.comment_count,"juejin recommend"),author:String(W.user_name??"").trim(),tags:K,url:Q?`${JUEJIN_POST_URL}/${Q}`:""}}export function mapHotItem(z,q){const F=z?.content??{},H=z?.content_counter??{},W=z?.author??{},K=M(F.content_id,"juejin hot");return{rank:q,article_id:K,title:String(F.title??"").trim(),brief:String(F.brief??"").trim(),views:j(H.view,"juejin hot"),likes:j(H.like,"juejin hot"),comments:j(H.comment_count,"juejin hot"),hot_rank:j(H.hot_rank,"juejin hot"),author:String(W.name??"").trim(),url:K?`${JUEJIN_POST_URL}/${K}`:""}}
1
+ import{ArgumentError as $,CommandExecutionError as Z,EmptyResultError as D}from"@jackwener/opencli/errors";export const JUEJIN_API_BASE="https://api.juejin.cn",JUEJIN_POST_URL="https://juejin.cn/post",JUEJIN_USER_URL="https://juejin.cn/user";const P="opencli-juejin-adapter (+https://github.com/jackwener/OpenCLI)",B=/^\d{16,20}$/;export const CATEGORY_ALIASES={backend:"6809637769959178254",frontend:"6809637767543259144",android:"6809635626879549454",ios:"6809635626661445640",ai:"6809637773935378440"};export function requireString(z,q){const F=String(z??"").trim();if(!F)throw new $(`juejin ${q} cannot be empty`);return F}export function requireBoundedInt(z,q,F,H="limit"){const W=z??q;let K;if(typeof W==="number")K=W;else if(typeof W==="string"&&/^[1-9]\d*$/.test(W))K=Number(W);else throw new $(`juejin ${H} must be a positive decimal integer`);if(!Number.isSafeInteger(K)||K<=0)throw new $(`juejin ${H} must be a positive integer`);if(K>F)throw new $(`juejin ${H} must be <= ${F}`);return K}export function requireCursor(z){const q=z??"0";if(typeof q==="number"){if(Number.isSafeInteger(q)&&q>=0)return String(q);throw new $("juejin cursor must be a non-negative decimal integer")}if(typeof q==="string"&&/^(0|[1-9]\d*)$/.test(q))return q;throw new $("juejin cursor must be a non-negative decimal integer")}export function resolveCategory(z){if(z==null)return"";const q=String(z).trim();if(!q)return"";if(B.test(q))return q;const F=q.toLowerCase();if(CATEGORY_ALIASES[F])return CATEGORY_ALIASES[F];throw new $(`juejin category "${z}" is not recognised`,`Use a category id (e.g. "${CATEGORY_ALIASES.backend}") or one of: ${Object.keys(CATEGORY_ALIASES).join(", ")}.`)}export async function juejinFetch(z,q,F,H="POST"){const W=`${JUEJIN_API_BASE}${z}`;let K;try{const X={method:H,headers:{"user-agent":P,accept:"application/json"}};if(H==="POST"){X.headers["content-type"]="application/json";X.body=JSON.stringify(q??{})}K=await fetch(W,X)}catch(X){throw new Z(`${F} request failed: ${X?.message??X}`,"Check that api.juejin.cn is reachable from this network.")}if(K.status===429)throw new Z(`${F} returned HTTP 429 (rate limited)`,"Juejin throttles bursty traffic; wait a few seconds and retry.");if(!K.ok)throw new Z(`${F} returned HTTP ${K.status}`);let Q;try{Q=await K.json()}catch(X){throw new Z(`${F} returned malformed JSON: ${X?.message??X}`)}if(!Q||typeof Q!=="object"||Array.isArray(Q)||!Object.hasOwn(Q,"err_no"))throw new Z(`${F} returned a malformed API envelope`);if(Q.err_no!==0)throw new Z(`${F} returned err_no ${Q.err_no}: ${Q.err_msg??""}`);return Q}export function readDataArray(z,q){if(!z||typeof z!=="object"||Array.isArray(z)||!Object.hasOwn(z,"data"))throw new Z(`${q} returned a malformed payload`);if(!Array.isArray(z.data))throw new Z(`${q} returned a non-array data field`);if(z.data.length===0)throw new D(q,`${q} returned no articles.`);return z.data}function M(z,q){const F=String(z??"").trim();if(!B.test(F))throw new Z(`${q} returned a malformed article id`);return F}function j(z,q){if(z==null)return null;const F=Number(z);if(!Number.isFinite(F))throw new Z(`${q} returned a malformed numeric field`);return F}export function mapFeedItem(z,q){const F=z?.item_info??{},H=F.article_info??{},W=F.author_user_info??{},K=Array.isArray(F.tags)?F.tags.map((X)=>X?.tag_name).filter(Boolean).slice(0,6).join(", "):"",Q=M(H.article_id,"juejin recommend");return{rank:q,article_id:Q,title:String(H.title??"").trim(),brief:String(H.brief_content??"").trim(),views:j(H.view_count,"juejin recommend"),likes:j(H.digg_count,"juejin recommend"),comments:j(H.comment_count,"juejin recommend"),author:String(W.user_name??"").trim(),tags:K,url:Q?`${JUEJIN_POST_URL}/${Q}`:""}}export function mapHotItem(z,q){const F=z?.content??{},H=z?.content_counter??{},W=z?.author??{},K=M(F.content_id,"juejin hot");return{rank:q,article_id:K,title:String(F.title??"").trim(),brief:String(F.brief??"").trim(),views:j(H.view,"juejin hot"),likes:j(H.like,"juejin hot"),comments:j(H.comment_count,"juejin hot"),hot_rank:j(H.hot_rank,"juejin hot"),author:String(W.name??"").trim(),url:K?`${JUEJIN_POST_URL}/${K}`:""}}
@@ -42,7 +42,7 @@ import{cli as V,Strategy as v}from"@jackwener/opencli/registry";import{ArgumentE
42
42
  first.click();
43
43
  return true;
44
44
  })()
45
- `))))throw new H("找不到快手「封面设置」入口");if(!await A(q,'div[role="document"].ant-modal',30000))throw new H("封面设置弹窗未出现");await M(q,"上传封面",{exact:!0,timeoutMs:1e4});await q.wait({time:0.5});await Z(q,['div[role="document"].ant-modal input[type="file"]','input[type="file"]'],[z]);await q.wait({time:1});await M(q,"确认",{exact:!0,timeoutMs:1e4});const J=Date.now()+30000;while(Date.now()<J){if(!Boolean(W(await q.evaluate(`(() => { const m = document.querySelector('div[role="document"].ant-modal'); return !!(m && m.getBoundingClientRect().width > 0); })()`))))break;await q.wait({time:0.5})}}V({site:"kuaishou",name:"publish",access:"write",description:"发布视频到快手(一条命令完成上传+填写+发布;可选封面/定时/话题)",domain:"cp.kuaishou.com",strategy:v.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"视频文件路径"},{name:"title",required:!0,help:"作品标题(无 --desc 时作为描述)"},{name:"desc",default:"",help:"视频描述(默认用标题)"},{name:"tags",default:"",help:"话题标签,逗号分隔,最多取前 3 个(不含 #)"},{name:"cover",default:"",help:"封面图片路径(可选)"},{name:"schedule",default:"",help:"定时发布时间(ISO8601 或 Unix 秒;不填即立即发布)"}],columns:["status","title","url"],func:async(q,z)=>{const G=String(z.title??"").trim();if(!G)throw new O("快手视频上传时,title 是必须的");const J=_(z.video),Q=z.cover?U(z.cover):"",X=String(z.desc||"").trim()||G,R=z.tags?String(z.tags).split(",").map((K)=>K.trim()).filter(Boolean):[],Y=C(z.schedule);await q.goto(D);if(!await j(q,"article/publish/video",30000))throw new H("未能进入快手上传页(登录态可能失效,请先 opencli kuaishou login)");if(!await A(q,S,15000))throw new H("快手上传按钮未出现");await Z(q,N,[J]);await q.wait({time:2});if(await I(q,"我知道了",{exact:!0}))await M(q,"我知道了",{exact:!0});await q.evaluate(`
45
+ `))))throw new H("找不到快手「封面设置」入口");if(!await A(q,'div[role="document"].ant-modal',30000))throw new H("封面设置弹窗未出现");await M(q,"上传封面",{exact:!0,timeoutMs:1e4});await q.wait({time:0.5});await Z(q,['div[role="document"].ant-modal input[type="file"]','input[type="file"]'],[z]);await q.wait({time:1});await M(q,"确认",{exact:!0,timeoutMs:1e4});const J=Date.now()+30000;while(Date.now()<J){if(!Boolean(W(await q.evaluate(`(() => { const m = document.querySelector('div[role="document"].ant-modal'); return !!(m && m.getBoundingClientRect().width > 0); })()`))))break;await q.wait({time:0.5})}}V({site:"kuaishou",name:"publish",access:"write",description:"发布视频到快手(一条命令完成上传+填写+发布;可选封面/定时/话题)",domain:"cp.kuaishou.com",strategy:v.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"视频文件路径"},{name:"title",required:!0,help:"作品标题(无 --desc 时作为描述)"},{name:"desc",default:"",help:"视频描述(默认用标题)"},{name:"tags",default:"",help:"话题标签,逗号分隔,最多取前 3 个(不含 #)"},{name:"cover",default:"",help:"封面图片路径(可选)"},{name:"schedule",default:"",help:"定时发布时间(ISO8601 或 Unix 秒;不填即立即发布)"}],columns:["status","title","url"],func:async(q,z)=>{const G=String(z.title??"").trim();if(!G)throw new O("快手视频上传时,title 是必须的");const J=_(z.video),Q=z.cover?U(z.cover):"",X=String(z.desc||"").trim()||G,R=z.tags?String(z.tags).split(",").map((K)=>K.trim()).filter(Boolean):[],Y=C(z.schedule);await q.goto(D);if(!await j(q,"article/publish/video",30000))throw new H("未能进入快手上传页(登录态可能失效,请先 ppcli kuaishou login)");if(!await A(q,S,15000))throw new H("快手上传按钮未出现");await Z(q,N,[J]);await q.wait({time:2});if(await I(q,"我知道了",{exact:!0}))await M(q,"我知道了",{exact:!0});await q.evaluate(`
46
46
  (() => {
47
47
  const tip = document.querySelector('div[id^="react-joyride-step"] div[role="alertdialog"]');
48
48
  if (!tip) return false;
@@ -1,4 +1,4 @@
1
- import{cli as n,Strategy as u}from"@jackwener/opencli/registry";import{EmptyResultError as m}from"@jackwener/opencli/errors";import{DOMAIN as r,SITE as g,gqlRequest as p,resolveTagId as c}from"./_helpers.js";n({site:g,name:"tag",access:"read",description:"Posts by tag",domain:r,strategy:u.PUBLIC,browser:!1,args:[{name:"tag",type:"string",required:!0,positional:!0,help:"Tag slug or name"},{name:"limit",type:"int",default:10,help:"Number of results"}],columns:["rank","title","author","karma","comments","url"],func:async(s)=>{const e=String(s.tag),o=Number(s.limit??10),a=await c(e);if(!a?._id)throw new m(`lesswrong tag ${e}`,'Use "opencli lesswrong tags" to list available tags');const i=`query PostsByTag {
1
+ import{cli as n,Strategy as u}from"@jackwener/opencli/registry";import{EmptyResultError as m}from"@jackwener/opencli/errors";import{DOMAIN as r,SITE as g,gqlRequest as p,resolveTagId as c}from"./_helpers.js";n({site:g,name:"tag",access:"read",description:"Posts by tag",domain:r,strategy:u.PUBLIC,browser:!1,args:[{name:"tag",type:"string",required:!0,positional:!0,help:"Tag slug or name"},{name:"limit",type:"int",default:10,help:"Number of results"}],columns:["rank","title","author","karma","comments","url"],func:async(s)=>{const e=String(s.tag),o=Number(s.limit??10),a=await c(e);if(!a?._id)throw new m(`lesswrong tag ${e}`,'Use "ppcli lesswrong tags" to list available tags');const i=`query PostsByTag {
2
2
  posts(input: {terms: {view: "tagRelevance", tagId: "${a._id}", limit: ${o}}}) {
3
3
  results { _id title user { displayName } baseScore commentCount slug postedAt }
4
4
  }
@@ -1 +1 @@
1
- import{ArgumentError as J,CommandExecutionError as W,EmptyResultError as X}from"@jackwener/opencli/errors";export const LICHESS_BASE="https://lichess.org";const Z="opencli-lichess-adapter/1.0 (+https://github.com/jackwener/opencli; mailto:opencli@example.com)",$=/^[A-Za-z0-9_-]{2,30}$/;export const LICHESS_PERFS=new Set(["ultraBullet","bullet","blitz","rapid","classical","chess960","crazyhouse","antichess","atomic","horde","kingOfTheHill","racingKings","threeCheck"]);export function requireUsername(z){const q=String(z??"").trim();if(!q)throw new J("lichess username is required");if(!$.test(q))throw new J(`lichess username "${z}" is not a valid handle`,"Allowed: letters, digits, underscore, dash; length 2-30.");return q}export function requirePerf(z){const q=String(z??"").trim();if(!q)throw new J('lichess perf is required (e.g. "blitz", "bullet", "rapid")');if(!LICHESS_PERFS.has(q))throw new J(`lichess perf "${z}" is not recognised`,`Allowed values: ${[...LICHESS_PERFS].join(", ")}.`);return q}export function requireBoundedInt(z,q,B,K="limit"){const G=z??q,Q=typeof G==="number"?G:Number(G);if(!Number.isInteger(Q)||Q<=0)throw new J(`lichess ${K} must be a positive integer`);if(Q>B)throw new J(`lichess ${K} must be <= ${B}`);return Q}export async function lichessFetch(z,q){let B;try{B=await fetch(z,{headers:{"user-agent":Z,accept:"application/json"}})}catch(G){throw new W(`${q} request failed: ${G?.message??G}`,"Check that lichess.org is reachable from this network.")}if(B.status===404)throw new X(q,`Lichess returned 404 for ${z}.`);if(B.status===429)throw new W(`${q} returned HTTP 429 (rate limited)`,"Lichess throttles anonymous traffic at ~60 req/min; back off and retry.");if(!B.ok)throw new W(`${q} returned HTTP ${B.status}`);let K;try{K=await B.json()}catch(G){throw new W(`${q} returned malformed JSON: ${G?.message??G}`)}return K}export function formatTimestamp(z){if(typeof z!=="number"||!Number.isFinite(z)||z<=0)return null;const q=new Date(z);if(Number.isNaN(q.getTime()))return null;return q.toISOString()}
1
+ import{ArgumentError as J,CommandExecutionError as W,EmptyResultError as X}from"@jackwener/opencli/errors";export const LICHESS_BASE="https://lichess.org";const Z="opencli-lichess-adapter/1.0 (+https://github.com/jackwener/OpenCLI; mailto:ppcli@example.com)",$=/^[A-Za-z0-9_-]{2,30}$/;export const LICHESS_PERFS=new Set(["ultraBullet","bullet","blitz","rapid","classical","chess960","crazyhouse","antichess","atomic","horde","kingOfTheHill","racingKings","threeCheck"]);export function requireUsername(z){const q=String(z??"").trim();if(!q)throw new J("lichess username is required");if(!$.test(q))throw new J(`lichess username "${z}" is not a valid handle`,"Allowed: letters, digits, underscore, dash; length 2-30.");return q}export function requirePerf(z){const q=String(z??"").trim();if(!q)throw new J('lichess perf is required (e.g. "blitz", "bullet", "rapid")');if(!LICHESS_PERFS.has(q))throw new J(`lichess perf "${z}" is not recognised`,`Allowed values: ${[...LICHESS_PERFS].join(", ")}.`);return q}export function requireBoundedInt(z,q,B,K="limit"){const G=z??q,Q=typeof G==="number"?G:Number(G);if(!Number.isInteger(Q)||Q<=0)throw new J(`lichess ${K} must be a positive integer`);if(Q>B)throw new J(`lichess ${K} must be <= ${B}`);return Q}export async function lichessFetch(z,q){let B;try{B=await fetch(z,{headers:{"user-agent":Z,accept:"application/json"}})}catch(G){throw new W(`${q} request failed: ${G?.message??G}`,"Check that lichess.org is reachable from this network.")}if(B.status===404)throw new X(q,`Lichess returned 404 for ${z}.`);if(B.status===429)throw new W(`${q} returned HTTP 429 (rate limited)`,"Lichess throttles anonymous traffic at ~60 req/min; back off and retry.");if(!B.ok)throw new W(`${q} returned HTTP ${B.status}`);let K;try{K=await B.json()}catch(G){throw new W(`${q} returned malformed JSON: ${G?.message??G}`)}return K}export function formatTimestamp(z){if(typeof z!=="number"||!Number.isFinite(z)||z<=0)return null;const q=new Date(z);if(Number.isNaN(q.getTime()))return null;return q.toISOString()}
@@ -22,7 +22,7 @@ import{cli as w,Strategy as A}from"@jackwener/opencli/registry";import{ArgumentE
22
22
  return { error: 'LinkedIn API error: HTTP ' + res.status + ' ' + text.slice(0, 200) };
23
23
  }
24
24
  return res.json();
25
- })()`);if(!X||X.error){if(X?.authRequired)throw new V(O,X.error);throw new I(X?.error||"LinkedIn search returned an unexpected response")}const C=Array.isArray(X?.elements)?X.elements:[];if(C.length===0)break;for(const j of C){const K=j?.jobCardUnion?.jobPostingCard;if(!K)continue;const y=[K.jobPostingUrn,K.jobPosting?.entityUrn,K.entityUrn].filter(Boolean).map((J)=>String(J).match(/(\d+)/)?.[1]).find(Boolean)??"",M=(K.footerItems||[]).find((J)=>J?.type==="LISTED_DATE"&&J?.timeAt),h=M?.timeAt?new Date(M.timeAt).toISOString().slice(0,10):"";$.push({title:K.jobPostingTitle||K.title?.text||"",company:K.primaryDescription?.text||"",location:K.secondaryDescription?.text||"",listed:h,salary:K.tertiaryDescription?.text||"",url:y?"https://www.linkedin.com/jobs/view/"+y:""})}if(C.length<B)break;H+=C.length}return $.slice(0,Z.limit).map((B,Y)=>({rank:Z.start+Y+1,...B}))}async function f(G,Z){const z=[];for(let $=0;$<Z.length;$++){const H=Z[$];console.error(`[opencli:linkedin] Fetching details ${$+1}/${Z.length}: ${H.title}`);if(!H.url){const Q="no url";console.error(`[opencli:linkedin] Skipping detail for "${H.title}": ${Q}`);z.push({...H,description:null,apply_url:null,detail_error:Q});continue}try{await G.goto(H.url);await x(G,"LinkedIn job detail");await G.wait({text:"About the job",timeout:8});await G.evaluate(`(() => {
25
+ })()`);if(!X||X.error){if(X?.authRequired)throw new V(O,X.error);throw new I(X?.error||"LinkedIn search returned an unexpected response")}const C=Array.isArray(X?.elements)?X.elements:[];if(C.length===0)break;for(const j of C){const K=j?.jobCardUnion?.jobPostingCard;if(!K)continue;const y=[K.jobPostingUrn,K.jobPosting?.entityUrn,K.entityUrn].filter(Boolean).map((J)=>String(J).match(/(\d+)/)?.[1]).find(Boolean)??"",M=(K.footerItems||[]).find((J)=>J?.type==="LISTED_DATE"&&J?.timeAt),h=M?.timeAt?new Date(M.timeAt).toISOString().slice(0,10):"";$.push({title:K.jobPostingTitle||K.title?.text||"",company:K.primaryDescription?.text||"",location:K.secondaryDescription?.text||"",listed:h,salary:K.tertiaryDescription?.text||"",url:y?"https://www.linkedin.com/jobs/view/"+y:""})}if(C.length<B)break;H+=C.length}return $.slice(0,Z.limit).map((B,Y)=>({rank:Z.start+Y+1,...B}))}async function f(G,Z){const z=[];for(let $=0;$<Z.length;$++){const H=Z[$];console.error(`[ppcli:linkedin] Fetching details ${$+1}/${Z.length}: ${H.title}`);if(!H.url){const Q="no url";console.error(`[ppcli:linkedin] Skipping detail for "${H.title}": ${Q}`);z.push({...H,description:null,apply_url:null,detail_error:Q});continue}try{await G.goto(H.url);await x(G,"LinkedIn job detail");await G.wait({text:"About the job",timeout:8});await G.evaluate(`(() => {
26
26
  const norm = (v) => (v || '').replace(/\\s+/g, ' ').trim().toLowerCase();
27
27
  const section = [...document.querySelectorAll('div, section, article')]
28
28
  .find(el => norm(el.querySelector('h1,h2,h3,h4')?.textContent || '') === 'about the job');
@@ -47,4 +47,4 @@ import{cli as w,Strategy as A}from"@jackwener/opencli/registry";import{ArgumentE
47
47
  .find(a => /apply/i.test(a.text) || /apply/i.test(a.aria));
48
48
 
49
49
  return { description, applyUrl: applyLink?.href || '' };
50
- })()`),U=b(Q?.description),W=T(String(Q?.applyUrl??"")),B=U?null:"missing description";z.push({...H,description:U||null,apply_url:W||null,detail_error:B})}catch(Q){if(Q instanceof V)throw Q;const U=`fetch failed: ${Q?.message||Q}`;console.error(`[opencli:linkedin] Detail fetch failed for ${H.url}: ${U}`);z.push({...H,description:null,apply_url:null,detail_error:U})}}return z}w({site:"linkedin",name:"search",access:"read",description:"Search LinkedIn jobs",domain:"www.linkedin.com",strategy:A.COOKIE,browser:!0,args:[{name:"query",type:"string",required:!0,positional:!0,help:"Job search keywords"},{name:"location",type:"string",required:!1,help:"Location text such as San Francisco Bay Area"},{name:"limit",type:"int",default:10,help:"Number of jobs to return (max 100)"},{name:"start",type:"int",default:0,help:"Result offset for pagination"},{name:"details",type:"bool",default:!1,help:"Include full job description and apply URL (slower)"},{name:"company",type:"string",required:!1,help:"Comma-separated company names or LinkedIn company IDs"},{name:"experience-level",type:"string",required:!1,help:"Comma-separated: internship, entry, associate, mid-senior, director, executive"},{name:"job-type",type:"string",required:!1,help:"Comma-separated: full-time, part-time, contract, temporary, volunteer, internship, other"},{name:"date-posted",type:"string",required:!1,help:"One of: any, month, week, 24h"},{name:"remote",type:"string",required:!1,help:"Comma-separated: on-site, hybrid, remote"}],columns:["rank","title","company","location","listed","salary","url"],func:async(G,Z)=>{const z=R(Z.limit,"--limit",10,v,k),$=R(Z.start,"--start",0,E),H=Boolean(Z.details),Q=(Z.location??"").trim(),U=String(Z.query??"").trim();if(!U)throw new q("query is required");await G.goto("https://www.linkedin.com/jobs/");await G.wait(2);const W=d(Z.company),B={keywords:U,location:Q,limit:z,start:$,companyIds:W,experienceLevels:F(Z["experience-level"],S,"experience_level"),jobTypes:F(Z["job-type"],N,"job_type"),datePostedValues:F(Z["date-posted"],D,"date_posted"),remoteTypes:F(Z.remote,L,"remote")},Y=await o(G,B);if(!H)return Y;return f(G,Y)}});export const __test__={parseCsvArg:P,parseIntegerArg:R,mapFilterValues:F,decodeLinkedinRedirect:T,looksLinkedInAuthWallText:_,assertLinkedInAuthenticated:x,enrichJobDetails:f,EXPERIENCE_LEVELS:S,JOB_TYPES:N,DATE_POSTED:D,REMOTE_TYPES:L};
50
+ })()`),U=b(Q?.description),W=T(String(Q?.applyUrl??"")),B=U?null:"missing description";z.push({...H,description:U||null,apply_url:W||null,detail_error:B})}catch(Q){if(Q instanceof V)throw Q;const U=`fetch failed: ${Q?.message||Q}`;console.error(`[ppcli:linkedin] Detail fetch failed for ${H.url}: ${U}`);z.push({...H,description:null,apply_url:null,detail_error:U})}}return z}w({site:"linkedin",name:"search",access:"read",description:"Search LinkedIn jobs",domain:"www.linkedin.com",strategy:A.COOKIE,browser:!0,args:[{name:"query",type:"string",required:!0,positional:!0,help:"Job search keywords"},{name:"location",type:"string",required:!1,help:"Location text such as San Francisco Bay Area"},{name:"limit",type:"int",default:10,help:"Number of jobs to return (max 100)"},{name:"start",type:"int",default:0,help:"Result offset for pagination"},{name:"details",type:"bool",default:!1,help:"Include full job description and apply URL (slower)"},{name:"company",type:"string",required:!1,help:"Comma-separated company names or LinkedIn company IDs"},{name:"experience-level",type:"string",required:!1,help:"Comma-separated: internship, entry, associate, mid-senior, director, executive"},{name:"job-type",type:"string",required:!1,help:"Comma-separated: full-time, part-time, contract, temporary, volunteer, internship, other"},{name:"date-posted",type:"string",required:!1,help:"One of: any, month, week, 24h"},{name:"remote",type:"string",required:!1,help:"Comma-separated: on-site, hybrid, remote"}],columns:["rank","title","company","location","listed","salary","url"],func:async(G,Z)=>{const z=R(Z.limit,"--limit",10,v,k),$=R(Z.start,"--start",0,E),H=Boolean(Z.details),Q=(Z.location??"").trim(),U=String(Z.query??"").trim();if(!U)throw new q("query is required");await G.goto("https://www.linkedin.com/jobs/");await G.wait(2);const W=d(Z.company),B={keywords:U,location:Q,limit:z,start:$,companyIds:W,experienceLevels:F(Z["experience-level"],S,"experience_level"),jobTypes:F(Z["job-type"],N,"job_type"),datePostedValues:F(Z["date-posted"],D,"date_posted"),remoteTypes:F(Z.remote,L,"remote")},Y=await o(G,B);if(!H)return Y;return f(G,Y)}});export const __test__={parseCsvArg:P,parseIntegerArg:R,mapFilterValues:F,decodeLinkedinRedirect:T,looksLinkedInAuthWallText:_,assertLinkedInAuthenticated:x,enrichJobDetails:f,EXPERIENCE_LEVELS:S,JOB_TYPES:N,DATE_POSTED:D,REMOTE_TYPES:L};
@@ -1,4 +1,4 @@
1
- import*as q from"node:fs";import*as k from"node:os";import*as V from"node:path";import{ArgumentError as A,AuthRequiredError as h,CommandExecutionError as I}from"@jackwener/opencli/errors";import{cli as C,Strategy as w}from"@jackwener/opencli/registry";const v="https://linux.do",y=86400000;let G=null,N=null,b=null,R=null,M=null;function F(Q){return Q.trim().replace(/\s+/g," ").toLowerCase()}function m(){return process.env.HOME||process.env.USERPROFILE||k.homedir()}function O(){return M??V.join(m(),".opencli","cache","linux-do")}function S(Q){return V.join(O(),`${Q}.json`)}async function x(Q){try{const W=await q.promises.readFile(S(Q),"utf-8"),Y=JSON.parse(W);if(!Y||!Array.isArray(Y.data)||typeof Y.fetchedAt!=="string")return null;const $=new Date(Y.fetchedAt).getTime(),Z=Number.isFinite($)&&Date.now()-$<y;return{data:Y.data,fresh:Z}}catch{return null}}async function E(Q,W){try{const Y=O();await q.promises.mkdir(Y,{recursive:!0});const $={fetchedAt:new Date().toISOString(),data:W};await q.promises.writeFile(S(Q),JSON.stringify($,null,2)+`
1
+ import*as q from"node:fs";import*as k from"node:os";import*as V from"node:path";import{ArgumentError as A,AuthRequiredError as h,CommandExecutionError as I}from"@jackwener/opencli/errors";import{cli as C,Strategy as w}from"@jackwener/opencli/registry";const v="https://linux.do",y=86400000;let G=null,N=null,b=null,R=null,M=null;function F(Q){return Q.trim().replace(/\s+/g," ").toLowerCase()}function m(){return process.env.HOME||process.env.USERPROFILE||k.homedir()}function O(){return M??V.join(m(),".ppcli","cache","linux-do")}function S(Q){return V.join(O(),`${Q}.json`)}async function x(Q){try{const W=await q.promises.readFile(S(Q),"utf-8"),Y=JSON.parse(W);if(!Y||!Array.isArray(Y.data)||typeof Y.fetchedAt!=="string")return null;const $=new Date(Y.fetchedAt).getTime(),Z=Number.isFinite($)&&Date.now()-$<y;return{data:Y.data,fresh:Z}}catch{return null}}async function E(Q,W){try{const Y=O();await q.promises.mkdir(Y,{recursive:!0});const $={fetchedAt:new Date().toISOString(),data:W};await q.promises.writeFile(S(Q),JSON.stringify($,null,2)+`
2
2
  `)}catch{}}async function f(Q){if(!Q)throw new I("Browser page required");await Q.goto(v);await Q.wait(2)}export async function fetchLinuxDoJson(Q,W,Y={}){if(!Y.skipNavigate)await f(Q);if(!Q)throw new I("Browser page required");const $=JSON.stringify(W),Z=await Q.evaluate(`(async () => {
3
3
  try {
4
4
  const res = await fetch(${$}, { credentials: 'include' });
@@ -16,4 +16,4 @@ import*as q from"node:fs";import*as k from"node:os";import*as V from"node:path";
16
16
  error: error instanceof Error ? error.message : String(error),
17
17
  };
18
18
  }
19
- })()`);if(!Z)throw new I("linux.do returned an empty browser response");if(Z.status===401||Z.status===403)throw new h("linux.do","linux.do requires an active signed-in browser session");if(!Z.ok)throw new I(Z.error||`linux.do request failed: HTTP ${Z.status??"unknown"}`);if(Z.error)throw new I(Z.error,"Please verify your linux.do session is still valid");return Z.data}function n(Q,W){const Y=W.trim(),$=F(W);return/^\d+$/.test(Y)?Q.find((Z)=>Z.id===Number(Y))??null:Q.find((Z)=>F(Z.name)===$)??Q.find((Z)=>F(Z.slug)===$)??null}function p(Q,W){const Y=W.trim(),$=F(W);return/^\d+$/.test(Y)?Q.find((Z)=>Z.id===Number(Y))??null:Q.find((Z)=>d(Z).includes($))??null}function d(Q){const W=[Q.name,Q.slug];if(Q.parent)W.push(`${Q.parent.name} / ${Q.name}`,`${Q.parent.name}/${Q.name}`,`${Q.parent.name}, ${Q.name}`);return W.map(F)}function P(Q,W){return{id:Q.id,name:Q.name??"",description:Q.description_text??Q.description??"",slug:Q.slug??"",parentCategoryId:W?.id??null,parent:W}}async function l(Q){if(b)return b;if(!G)G=(async()=>{const W=await x("tags");if(W?.fresh)return W.data;try{const Y=await fetchLinuxDoJson(Q,"/tags.json",{skipNavigate:!0}),$=(Array.isArray(Y?.tags)?Y.tags:[]).filter((Z)=>!!Z&&typeof Z.id==="number").map((Z)=>({id:Z.id,slug:Z.slug??`${Z.id}-tag`,name:Z.name??String(Z.id)}));await E("tags",$);return $}catch(Y){if(W)return W.data;G=null;throw Y}})().catch((W)=>{G=null;throw W});return G}async function u(Q){if(R)return R;if(!N)N=(async()=>{const W=await x("categories");if(W?.fresh)return W.data;try{const Y=await fetchLinuxDoJson(Q,"/categories.json",{skipNavigate:!0}),$=Array.isArray(Y?.category_list?.categories)?Y.category_list.categories:[],Z=$.map((B)=>P(B,null)),U=new Map(Z.map((B)=>[B.id,B])),X=await Promise.allSettled($.filter((B)=>Array.isArray(B.subcategory_ids)&&B.subcategory_ids.length>0).map(async(B)=>{const j=await fetchLinuxDoJson(Q,`/categories.json?parent_category_id=${B.id}`,{skipNavigate:!0}),J=Array.isArray(j?.category_list?.categories)?j.category_list.categories:[],z=U.get(B.id)??null;return J.map((H)=>P(H,z))})),K=[...Z,...X.flatMap((B)=>B.status==="fulfilled"?B.value:[])];await E("categories",K);return K}catch(Y){if(W)return W.data;throw Y}})().catch((W)=>{N=null;throw W});return N}function c(Q){if(!Q)return"";const W=new Date(Q);if(isNaN(W.getTime()))return Q;return W.toLocaleString()}function i(Q){return Math.max(0,(typeof Q==="number"?Q:1)-1)}function s(Q,W){return(Q?.topic_list?.topics??[]).slice(0,W).map(($)=>({title:$.fancy_title??$.title??"",replies:i($.posts_count),created:c($.created_at),likes:$.like_count??0,views:$.views??0,url:`https://linux.do/t/topic/${$.id}`}))}async function g(Q,W){const Y=n(await l(Q),W);if(Y)return Y;throw new A(`Unknown tag: ${W}`,'Use "opencli linux-do tags" to list available tags')}async function o(Q,W){const Y=p(await u(Q),W);if(Y)return Y;throw new A(`Unknown category: ${W}`,'Use "opencli linux-do categories" to list available categories')}async function D(Q,W){const Y=W.view||"latest",$=W.period||"weekly";if(W.period&&Y!=="top")throw new A("--period is only valid with --view top");const Z=new URLSearchParams;if(W.order&&W.order!=="default")Z.set("order",W.order);if(W.ascending)Z.set("ascending","true");if(W.limit)Z.set("per_page",String(W.limit));const U=typeof W.tag==="string"?W.tag.trim():"",X=typeof W.category==="string"?W.category.trim():"";if(!U&&!X){const _=new URLSearchParams(Z);if(Y==="top")_.set("period",$);const T=_.toString()?`?${_.toString()}`:"";return{url:`${Y==="latest"?"/latest.json":Y==="hot"?"/hot.json":"/top.json"}${T}`}}const K=U?await g(Q,U):null,B=X?await o(Q,X):null,j=B?(B.parent?[B.parent.slug,B.slug,String(B.id)]:[B.slug,String(B.id)]).map(encodeURIComponent).join("/"):"",J=K?`${encodeURIComponent(K.slug||`${K.id}-tag`)}/${K.id}`:"",z=B&&K?`/tags/c/${j}/${J}`:B?`/c/${j}`:`/tag/${J}`,H=new URLSearchParams(Z);if(Y==="top")H.set("period",$);const L=H.toString()?`?${H.toString()}`:"";return{url:`${z}${Y==="latest"?".json":`/l/${Y}.json`}${L}`}}export const LINUX_DO_FEED_ARGS=[{name:"view",type:"str",default:"latest",help:"View type",choices:["latest","hot","top"]},{name:"tag",type:"str",help:"Tag name, slug, or id"},{name:"category",type:"str",help:"Category name, slug, id, or parent/name path"},{name:"limit",type:"int",default:20,help:"Number of items (per_page)"},{name:"order",type:"str",default:"default",help:"Sort order",choices:["default","created","activity","views","posts","category","likes","op_likes","posters"]},{name:"ascending",type:"boolean",default:!1,help:"Sort ascending (default: desc)"},{name:"period",type:"str",help:"Time period (only for --view top)",choices:["all","daily","weekly","monthly","quarterly","yearly"]}];async function t(Q,W){const Y=W.limit||20;await f(Q);const $=await D(Q,W),Z=await fetchLinuxDoJson(Q,$.url,{skipNavigate:!0});return s(Z,Y)}C({site:"linux-do",name:"feed",access:"read",description:"linux.do 话题列表(需登录;支持全站、标签、分类)",domain:"linux.do",strategy:w.COOKIE,browser:!0,columns:["title","replies","created","likes","views","url"],args:LINUX_DO_FEED_ARGS,func:t});export const __test__={resetMetadataCaches(){G=null;N=null;b=null;R=null;M=null},setLiveMetadataForTests({tags:Q,categories:W}){G=null;N=null;b=Q??null;R=W??null},setCacheDirForTests(Q){M=Q},resolveFeedRequest:D};
19
+ })()`);if(!Z)throw new I("linux.do returned an empty browser response");if(Z.status===401||Z.status===403)throw new h("linux.do","linux.do requires an active signed-in browser session");if(!Z.ok)throw new I(Z.error||`linux.do request failed: HTTP ${Z.status??"unknown"}`);if(Z.error)throw new I(Z.error,"Please verify your linux.do session is still valid");return Z.data}function n(Q,W){const Y=W.trim(),$=F(W);return/^\d+$/.test(Y)?Q.find((Z)=>Z.id===Number(Y))??null:Q.find((Z)=>F(Z.name)===$)??Q.find((Z)=>F(Z.slug)===$)??null}function p(Q,W){const Y=W.trim(),$=F(W);return/^\d+$/.test(Y)?Q.find((Z)=>Z.id===Number(Y))??null:Q.find((Z)=>d(Z).includes($))??null}function d(Q){const W=[Q.name,Q.slug];if(Q.parent)W.push(`${Q.parent.name} / ${Q.name}`,`${Q.parent.name}/${Q.name}`,`${Q.parent.name}, ${Q.name}`);return W.map(F)}function P(Q,W){return{id:Q.id,name:Q.name??"",description:Q.description_text??Q.description??"",slug:Q.slug??"",parentCategoryId:W?.id??null,parent:W}}async function l(Q){if(b)return b;if(!G)G=(async()=>{const W=await x("tags");if(W?.fresh)return W.data;try{const Y=await fetchLinuxDoJson(Q,"/tags.json",{skipNavigate:!0}),$=(Array.isArray(Y?.tags)?Y.tags:[]).filter((Z)=>!!Z&&typeof Z.id==="number").map((Z)=>({id:Z.id,slug:Z.slug??`${Z.id}-tag`,name:Z.name??String(Z.id)}));await E("tags",$);return $}catch(Y){if(W)return W.data;G=null;throw Y}})().catch((W)=>{G=null;throw W});return G}async function u(Q){if(R)return R;if(!N)N=(async()=>{const W=await x("categories");if(W?.fresh)return W.data;try{const Y=await fetchLinuxDoJson(Q,"/categories.json",{skipNavigate:!0}),$=Array.isArray(Y?.category_list?.categories)?Y.category_list.categories:[],Z=$.map((B)=>P(B,null)),U=new Map(Z.map((B)=>[B.id,B])),X=await Promise.allSettled($.filter((B)=>Array.isArray(B.subcategory_ids)&&B.subcategory_ids.length>0).map(async(B)=>{const j=await fetchLinuxDoJson(Q,`/categories.json?parent_category_id=${B.id}`,{skipNavigate:!0}),J=Array.isArray(j?.category_list?.categories)?j.category_list.categories:[],z=U.get(B.id)??null;return J.map((H)=>P(H,z))})),K=[...Z,...X.flatMap((B)=>B.status==="fulfilled"?B.value:[])];await E("categories",K);return K}catch(Y){if(W)return W.data;throw Y}})().catch((W)=>{N=null;throw W});return N}function c(Q){if(!Q)return"";const W=new Date(Q);if(isNaN(W.getTime()))return Q;return W.toLocaleString()}function i(Q){return Math.max(0,(typeof Q==="number"?Q:1)-1)}function s(Q,W){return(Q?.topic_list?.topics??[]).slice(0,W).map(($)=>({title:$.fancy_title??$.title??"",replies:i($.posts_count),created:c($.created_at),likes:$.like_count??0,views:$.views??0,url:`https://linux.do/t/topic/${$.id}`}))}async function g(Q,W){const Y=n(await l(Q),W);if(Y)return Y;throw new A(`Unknown tag: ${W}`,'Use "ppcli linux-do tags" to list available tags')}async function o(Q,W){const Y=p(await u(Q),W);if(Y)return Y;throw new A(`Unknown category: ${W}`,'Use "ppcli linux-do categories" to list available categories')}async function D(Q,W){const Y=W.view||"latest",$=W.period||"weekly";if(W.period&&Y!=="top")throw new A("--period is only valid with --view top");const Z=new URLSearchParams;if(W.order&&W.order!=="default")Z.set("order",W.order);if(W.ascending)Z.set("ascending","true");if(W.limit)Z.set("per_page",String(W.limit));const U=typeof W.tag==="string"?W.tag.trim():"",X=typeof W.category==="string"?W.category.trim():"";if(!U&&!X){const _=new URLSearchParams(Z);if(Y==="top")_.set("period",$);const T=_.toString()?`?${_.toString()}`:"";return{url:`${Y==="latest"?"/latest.json":Y==="hot"?"/hot.json":"/top.json"}${T}`}}const K=U?await g(Q,U):null,B=X?await o(Q,X):null,j=B?(B.parent?[B.parent.slug,B.slug,String(B.id)]:[B.slug,String(B.id)]).map(encodeURIComponent).join("/"):"",J=K?`${encodeURIComponent(K.slug||`${K.id}-tag`)}/${K.id}`:"",z=B&&K?`/tags/c/${j}/${J}`:B?`/c/${j}`:`/tag/${J}`,H=new URLSearchParams(Z);if(Y==="top")H.set("period",$);const L=H.toString()?`?${H.toString()}`:"";return{url:`${z}${Y==="latest"?".json":`/l/${Y}.json`}${L}`}}export const LINUX_DO_FEED_ARGS=[{name:"view",type:"str",default:"latest",help:"View type",choices:["latest","hot","top"]},{name:"tag",type:"str",help:"Tag name, slug, or id"},{name:"category",type:"str",help:"Category name, slug, id, or parent/name path"},{name:"limit",type:"int",default:20,help:"Number of items (per_page)"},{name:"order",type:"str",default:"default",help:"Sort order",choices:["default","created","activity","views","posts","category","likes","op_likes","posters"]},{name:"ascending",type:"boolean",default:!1,help:"Sort ascending (default: desc)"},{name:"period",type:"str",help:"Time period (only for --view top)",choices:["all","daily","weekly","monthly","quarterly","yearly"]}];async function t(Q,W){const Y=W.limit||20;await f(Q);const $=await D(Q,W),Z=await fetchLinuxDoJson(Q,$.url,{skipNavigate:!0});return s(Z,Y)}C({site:"linux-do",name:"feed",access:"read",description:"linux.do 话题列表(需登录;支持全站、标签、分类)",domain:"linux.do",strategy:w.COOKIE,browser:!0,columns:["title","replies","created","likes","views","url"],args:LINUX_DO_FEED_ARGS,func:t});export const __test__={resetMetadataCaches(){G=null;N=null;b=null;R=null;M=null},setLiveMetadataForTests({tags:Q,categories:W}){G=null;N=null;b=Q??null;R=W??null},setCacheDirForTests(Q){M=Q},resolveFeedRequest:D};
@@ -1 +1 @@
1
- import{cli as d,Strategy as b}from"@jackwener/opencli/registry";import{ArgumentError as i,CommandExecutionError as m,EmptyResultError as u}from"@jackwener/opencli/errors";const h=/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/i;function g(o){const e=String(o??"").trim().toLowerCase();if(!e)throw new i('lobsters domain is required (e.g. "github.com" or "arxiv.org")');if(!h.test(e))throw new i(`lobsters domain "${o}" is not a valid hostname`);return e}function f(o,e,n){const s=o??e,r=typeof s==="number"?s:Number(s);if(!Number.isInteger(r)||r<=0)throw new i("lobsters limit must be a positive integer");if(r>n)throw new i(`lobsters limit must be <= ${n}`);return r}d({site:"lobsters",name:"domain",access:"read",description:"Lobste.rs stories submitted from a specific domain",domain:"lobste.rs",strategy:b.PUBLIC,browser:!1,args:[{name:"domain",positional:!0,required:!0,help:"Source domain (e.g. github.com, arxiv.org, blog.cloudflare.com)"},{name:"limit",type:"int",default:20,help:"Number of stories (1-25 — single page)"}],columns:["rank","id","title","score","author","comments","created_at","tags","submission_url","comments_url"],func:async(o)=>{const e=g(o.domain),n=f(o.limit,20,25),s=`https://lobste.rs/domains/${encodeURIComponent(e)}.json`;let r;try{r=await fetch(s,{headers:{"user-agent":"opencli-lobsters-adapter (+https://github.com/jackwener/opencli)"}})}catch(t){throw new m(`lobsters domain request failed: ${t?.message??t}`,"Check that lobste.rs is reachable from this network.")}if(r.status===404)throw new u("lobsters domain",`No Lobste.rs stories found for domain "${e}".`);if(!r.ok)throw new m(`lobsters domain returned HTTP ${r.status}`);let a;try{a=await r.json()}catch(t){throw new m(`lobsters domain returned malformed JSON: ${t?.message??t}`)}const l=Array.isArray(a)?a:[];if(!l.length)throw new u("lobsters domain",`No Lobste.rs stories found for domain "${e}".`);return l.slice(0,n).map((t,c)=>({rank:c+1,id:String(t.short_id??""),title:String(t.title??""),score:t.score!=null?Number(t.score):null,author:String(t.submitter_user??""),comments:t.comment_count!=null?Number(t.comment_count):null,created_at:String(t.created_at??"").slice(0,10),tags:Array.isArray(t.tags)?t.tags.join(", "):"",submission_url:String(t.url??""),comments_url:String(t.comments_url??"")}))}});
1
+ import{cli as d,Strategy as b}from"@jackwener/opencli/registry";import{ArgumentError as i,CommandExecutionError as m,EmptyResultError as u}from"@jackwener/opencli/errors";const h=/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/i;function g(s){const e=String(s??"").trim().toLowerCase();if(!e)throw new i('lobsters domain is required (e.g. "github.com" or "arxiv.org")');if(!h.test(e))throw new i(`lobsters domain "${s}" is not a valid hostname`);return e}function f(s,e,n){const o=s??e,r=typeof o==="number"?o:Number(o);if(!Number.isInteger(r)||r<=0)throw new i("lobsters limit must be a positive integer");if(r>n)throw new i(`lobsters limit must be <= ${n}`);return r}d({site:"lobsters",name:"domain",access:"read",description:"Lobste.rs stories submitted from a specific domain",domain:"lobste.rs",strategy:b.PUBLIC,browser:!1,args:[{name:"domain",positional:!0,required:!0,help:"Source domain (e.g. github.com, arxiv.org, blog.cloudflare.com)"},{name:"limit",type:"int",default:20,help:"Number of stories (1-25 — single page)"}],columns:["rank","id","title","score","author","comments","created_at","tags","submission_url","comments_url"],func:async(s)=>{const e=g(s.domain),n=f(s.limit,20,25),o=`https://lobste.rs/domains/${encodeURIComponent(e)}.json`;let r;try{r=await fetch(o,{headers:{"user-agent":"opencli-lobsters-adapter (+https://github.com/jackwener/OpenCLI)"}})}catch(t){throw new m(`lobsters domain request failed: ${t?.message??t}`,"Check that lobste.rs is reachable from this network.")}if(r.status===404)throw new u("lobsters domain",`No Lobste.rs stories found for domain "${e}".`);if(!r.ok)throw new m(`lobsters domain returned HTTP ${r.status}`);let a;try{a=await r.json()}catch(t){throw new m(`lobsters domain returned malformed JSON: ${t?.message??t}`)}const l=Array.isArray(a)?a:[];if(!l.length)throw new u("lobsters domain",`No Lobste.rs stories found for domain "${e}".`);return l.slice(0,n).map((t,c)=>({rank:c+1,id:String(t.short_id??""),title:String(t.title??""),score:t.score!=null?Number(t.score):null,author:String(t.submitter_user??""),comments:t.comment_count!=null?Number(t.comment_count):null,created_at:String(t.created_at??"").slice(0,10),tags:Array.isArray(t.tags)?t.tags.join(", "):"",submission_url:String(t.url??""),comments_url:String(t.comments_url??"")}))}});
@@ -0,0 +1 @@
1
+ import{cli as K,Strategy as P}from"@jackwener/opencli/registry";import{TimeoutError as Q}from"@jackwener/opencli/errors";import{readAccessToken as R,readCurrentAccessToken as V,resolveInstance as X}from"./shared.js";const H=300;async function z(f){if(typeof f?.closeWindow!=="function")return;try{await f.closeWindow()}catch{}}K({site:"mastodon",name:"login",access:"write",description:"打开指定 Mastodon 自部署实例的登录页并等待登录完成。用 --site 选择连接名称或实例网址。",domain:"mastodon.social",strategy:P.UI,browser:!0,navigateBefore:!1,defaultWindowMode:"foreground",siteSession:"persistent",args:[{name:"site",required:!1,help:"连接名称或实例网址;省略时使用默认站点"},{name:"instance",required:!1,help:"兼容旧版:实例网址(推荐改用 --site)"},{name:"timeout",type:"int",default:H,help:"等待用户完成登录的最长秒数"}],columns:["status","logged_in","site","instance"],func:async(f,B)=>{const q=X(B);try{await R(f,q.url);await z(f);return[{status:"already_logged_in",logged_in:!0,site:q.name,instance:q.url}]}catch(x){if(x?.code!=="AUTH_REQUIRED")throw x}await f.goto(`${q.url}/auth/sign_in`);const G=Number(B.timeout??H),J=Date.now()+G*1000;while(Date.now()<J){await f.wait(2);const x=await V(f);if(x?.ok&&x.token){await z(f);return[{status:"login_complete",logged_in:!0,site:q.name,instance:q.url}]}}await z(f);throw new Q("mastodon login",G,`请在打开的窗口里完成 ${q.url} 的登录后重试。`)}});