@zenalexa/unicli 0.220.1 → 0.221.0

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 (471) hide show
  1. package/AGENTS.md +28 -6
  2. package/README.md +8 -8
  3. package/README.zh-CN.md +8 -8
  4. package/dist/adapters/bilibili/comments.js +66 -4
  5. package/dist/adapters/bilibili/comments.js.map +1 -1
  6. package/dist/adapters/bilibili/compat.js +2 -2
  7. package/dist/adapters/bilibili/compat.js.map +1 -1
  8. package/dist/adapters/bilibili/download.js +4 -4
  9. package/dist/adapters/bilibili/download.js.map +1 -1
  10. package/dist/adapters/bilibili/wbi.d.ts.map +1 -1
  11. package/dist/adapters/bilibili/wbi.js +3 -3
  12. package/dist/adapters/bilibili/wbi.js.map +1 -1
  13. package/dist/adapters/cipo/_shared.d.ts +21 -0
  14. package/dist/adapters/cipo/_shared.d.ts.map +1 -0
  15. package/dist/adapters/cipo/_shared.js +67 -0
  16. package/dist/adapters/cipo/_shared.js.map +1 -0
  17. package/dist/adapters/cipo/get.d.ts +19 -0
  18. package/dist/adapters/cipo/get.d.ts.map +1 -0
  19. package/dist/adapters/cipo/get.js +140 -0
  20. package/dist/adapters/cipo/get.js.map +1 -0
  21. package/dist/adapters/cipo/legal-status.d.ts +19 -0
  22. package/dist/adapters/cipo/legal-status.d.ts.map +1 -0
  23. package/dist/adapters/cipo/legal-status.js +111 -0
  24. package/dist/adapters/cipo/legal-status.js.map +1 -0
  25. package/dist/adapters/cipo/search.d.ts +20 -0
  26. package/dist/adapters/cipo/search.d.ts.map +1 -0
  27. package/dist/adapters/cipo/search.js +148 -0
  28. package/dist/adapters/cipo/search.js.map +1 -0
  29. package/dist/adapters/cnipa/_shared.d.ts +47 -0
  30. package/dist/adapters/cnipa/_shared.d.ts.map +1 -0
  31. package/dist/adapters/cnipa/_shared.js +97 -0
  32. package/dist/adapters/cnipa/_shared.js.map +1 -0
  33. package/dist/adapters/cnipa/get.d.ts +19 -0
  34. package/dist/adapters/cnipa/get.d.ts.map +1 -0
  35. package/dist/adapters/cnipa/get.js +149 -0
  36. package/dist/adapters/cnipa/get.js.map +1 -0
  37. package/dist/adapters/cnipa/legal-status.d.ts +19 -0
  38. package/dist/adapters/cnipa/legal-status.d.ts.map +1 -0
  39. package/dist/adapters/cnipa/legal-status.js +119 -0
  40. package/dist/adapters/cnipa/legal-status.js.map +1 -0
  41. package/dist/adapters/cnipa/search.d.ts +21 -0
  42. package/dist/adapters/cnipa/search.d.ts.map +1 -0
  43. package/dist/adapters/cnipa/search.js +170 -0
  44. package/dist/adapters/cnipa/search.js.map +1 -0
  45. package/dist/adapters/espacenet/_shared.d.ts +21 -0
  46. package/dist/adapters/espacenet/_shared.d.ts.map +1 -0
  47. package/dist/adapters/espacenet/_shared.js +67 -0
  48. package/dist/adapters/espacenet/_shared.js.map +1 -0
  49. package/dist/adapters/espacenet/family.d.ts +19 -0
  50. package/dist/adapters/espacenet/family.d.ts.map +1 -0
  51. package/dist/adapters/espacenet/family.js +118 -0
  52. package/dist/adapters/espacenet/family.js.map +1 -0
  53. package/dist/adapters/espacenet/get.d.ts +19 -0
  54. package/dist/adapters/espacenet/get.d.ts.map +1 -0
  55. package/dist/adapters/espacenet/get.js +130 -0
  56. package/dist/adapters/espacenet/get.js.map +1 -0
  57. package/dist/adapters/espacenet/legal-status.d.ts +19 -0
  58. package/dist/adapters/espacenet/legal-status.d.ts.map +1 -0
  59. package/dist/adapters/espacenet/legal-status.js +110 -0
  60. package/dist/adapters/espacenet/legal-status.js.map +1 -0
  61. package/dist/adapters/espacenet/search.d.ts +20 -0
  62. package/dist/adapters/espacenet/search.d.ts.map +1 -0
  63. package/dist/adapters/espacenet/search.js +165 -0
  64. package/dist/adapters/espacenet/search.js.map +1 -0
  65. package/dist/adapters/facebook/subtitles.d.ts +9 -0
  66. package/dist/adapters/facebook/subtitles.d.ts.map +1 -0
  67. package/dist/adapters/facebook/subtitles.js +42 -0
  68. package/dist/adapters/facebook/subtitles.js.map +1 -0
  69. package/dist/adapters/fips/_shared.d.ts +21 -0
  70. package/dist/adapters/fips/_shared.d.ts.map +1 -0
  71. package/dist/adapters/fips/_shared.js +77 -0
  72. package/dist/adapters/fips/_shared.js.map +1 -0
  73. package/dist/adapters/fips/get.d.ts +19 -0
  74. package/dist/adapters/fips/get.d.ts.map +1 -0
  75. package/dist/adapters/fips/get.js +139 -0
  76. package/dist/adapters/fips/get.js.map +1 -0
  77. package/dist/adapters/fips/search.d.ts +20 -0
  78. package/dist/adapters/fips/search.d.ts.map +1 -0
  79. package/dist/adapters/fips/search.js +148 -0
  80. package/dist/adapters/fips/search.js.map +1 -0
  81. package/dist/adapters/freepatentsonline-web/_shared.d.ts +72 -0
  82. package/dist/adapters/freepatentsonline-web/_shared.d.ts.map +1 -0
  83. package/dist/adapters/freepatentsonline-web/_shared.js +216 -0
  84. package/dist/adapters/freepatentsonline-web/_shared.js.map +1 -0
  85. package/dist/adapters/freepatentsonline-web/get.d.ts +21 -0
  86. package/dist/adapters/freepatentsonline-web/get.d.ts.map +1 -0
  87. package/dist/adapters/freepatentsonline-web/get.js +127 -0
  88. package/dist/adapters/freepatentsonline-web/get.js.map +1 -0
  89. package/dist/adapters/freepatentsonline-web/search.d.ts +22 -0
  90. package/dist/adapters/freepatentsonline-web/search.d.ts.map +1 -0
  91. package/dist/adapters/freepatentsonline-web/search.js +149 -0
  92. package/dist/adapters/freepatentsonline-web/search.js.map +1 -0
  93. package/dist/adapters/google-patents-web/_shared.d.ts +110 -0
  94. package/dist/adapters/google-patents-web/_shared.d.ts.map +1 -0
  95. package/dist/adapters/google-patents-web/_shared.js +164 -0
  96. package/dist/adapters/google-patents-web/_shared.js.map +1 -0
  97. package/dist/adapters/google-patents-web/get.d.ts +36 -0
  98. package/dist/adapters/google-patents-web/get.d.ts.map +1 -0
  99. package/dist/adapters/google-patents-web/get.js +187 -0
  100. package/dist/adapters/google-patents-web/get.js.map +1 -0
  101. package/dist/adapters/google-patents-web/search.d.ts +23 -0
  102. package/dist/adapters/google-patents-web/search.d.ts.map +1 -0
  103. package/dist/adapters/google-patents-web/search.js +169 -0
  104. package/dist/adapters/google-patents-web/search.js.map +1 -0
  105. package/dist/adapters/inpi-br/_shared.d.ts +21 -0
  106. package/dist/adapters/inpi-br/_shared.d.ts.map +1 -0
  107. package/dist/adapters/inpi-br/_shared.js +67 -0
  108. package/dist/adapters/inpi-br/_shared.js.map +1 -0
  109. package/dist/adapters/inpi-br/get.d.ts +19 -0
  110. package/dist/adapters/inpi-br/get.d.ts.map +1 -0
  111. package/dist/adapters/inpi-br/get.js +142 -0
  112. package/dist/adapters/inpi-br/get.js.map +1 -0
  113. package/dist/adapters/inpi-br/search.d.ts +20 -0
  114. package/dist/adapters/inpi-br/search.d.ts.map +1 -0
  115. package/dist/adapters/inpi-br/search.js +154 -0
  116. package/dist/adapters/inpi-br/search.js.map +1 -0
  117. package/dist/adapters/instagram/subtitles.d.ts +9 -0
  118. package/dist/adapters/instagram/subtitles.d.ts.map +1 -0
  119. package/dist/adapters/instagram/subtitles.js +42 -0
  120. package/dist/adapters/instagram/subtitles.js.map +1 -0
  121. package/dist/adapters/mastodon/statuses.d.ts +40 -0
  122. package/dist/adapters/mastodon/statuses.d.ts.map +1 -0
  123. package/dist/adapters/mastodon/statuses.js +153 -0
  124. package/dist/adapters/mastodon/statuses.js.map +1 -0
  125. package/dist/adapters/reddit/comments.d.ts +9 -0
  126. package/dist/adapters/reddit/comments.d.ts.map +1 -0
  127. package/dist/adapters/reddit/comments.js +124 -0
  128. package/dist/adapters/reddit/comments.js.map +1 -0
  129. package/dist/adapters/threads/post.d.ts +32 -0
  130. package/dist/adapters/threads/post.d.ts.map +1 -0
  131. package/dist/adapters/threads/post.js +287 -0
  132. package/dist/adapters/threads/post.js.map +1 -0
  133. package/dist/adapters/tiktok/subtitles.d.ts +9 -0
  134. package/dist/adapters/tiktok/subtitles.d.ts.map +1 -0
  135. package/dist/adapters/tiktok/subtitles.js +42 -0
  136. package/dist/adapters/tiktok/subtitles.js.map +1 -0
  137. package/dist/adapters/twitter/accept.js +2 -2
  138. package/dist/adapters/twitter/accept.js.map +1 -1
  139. package/dist/adapters/twitter/browser-fallback.d.ts +26 -0
  140. package/dist/adapters/twitter/browser-fallback.d.ts.map +1 -0
  141. package/dist/adapters/twitter/browser-fallback.js +93 -0
  142. package/dist/adapters/twitter/browser-fallback.js.map +1 -0
  143. package/dist/adapters/twitter/browser-state.d.ts +11 -0
  144. package/dist/adapters/twitter/browser-state.d.ts.map +1 -0
  145. package/dist/adapters/twitter/browser-state.js +46 -0
  146. package/dist/adapters/twitter/browser-state.js.map +1 -0
  147. package/dist/adapters/twitter/client.d.ts.map +1 -1
  148. package/dist/adapters/twitter/client.js +36 -13
  149. package/dist/adapters/twitter/client.js.map +1 -1
  150. package/dist/adapters/twitter/reply-dm.js +2 -2
  151. package/dist/adapters/twitter/reply-dm.js.map +1 -1
  152. package/dist/adapters/twitter/reply.js +1 -0
  153. package/dist/adapters/twitter/reply.js.map +1 -1
  154. package/dist/adapters/twitter/search.js +11 -18
  155. package/dist/adapters/twitter/search.js.map +1 -1
  156. package/dist/adapters/twitter/thread.d.ts +14 -0
  157. package/dist/adapters/twitter/thread.d.ts.map +1 -1
  158. package/dist/adapters/twitter/thread.js +28 -2
  159. package/dist/adapters/twitter/thread.js.map +1 -1
  160. package/dist/adapters/twitter/trending.js +13 -59
  161. package/dist/adapters/twitter/trending.js.map +1 -1
  162. package/dist/adapters/xiaohongshu/browser-state.d.ts +19 -0
  163. package/dist/adapters/xiaohongshu/browser-state.d.ts.map +1 -0
  164. package/dist/adapters/xiaohongshu/browser-state.js +67 -0
  165. package/dist/adapters/xiaohongshu/browser-state.js.map +1 -0
  166. package/dist/adapters/xiaohongshu/comments.js +28 -5
  167. package/dist/adapters/xiaohongshu/comments.js.map +1 -1
  168. package/dist/adapters/xiaohongshu/download.js +49 -11
  169. package/dist/adapters/xiaohongshu/download.js.map +1 -1
  170. package/dist/adapters/xiaohongshu/search.d.ts.map +1 -1
  171. package/dist/adapters/xiaohongshu/search.js +11 -5
  172. package/dist/adapters/xiaohongshu/search.js.map +1 -1
  173. package/dist/adapters/xiaohongshu/trending.d.ts +9 -0
  174. package/dist/adapters/xiaohongshu/trending.d.ts.map +1 -0
  175. package/dist/adapters/xiaohongshu/trending.js +94 -0
  176. package/dist/adapters/xiaohongshu/trending.js.map +1 -0
  177. package/dist/adapters/youtube/comments.d.ts +80 -0
  178. package/dist/adapters/youtube/comments.d.ts.map +1 -1
  179. package/dist/adapters/youtube/comments.js +108 -12
  180. package/dist/adapters/youtube/comments.js.map +1 -1
  181. package/dist/adapters/youtube/subtitles.d.ts +9 -0
  182. package/dist/adapters/youtube/subtitles.d.ts.map +1 -0
  183. package/dist/adapters/youtube/subtitles.js +42 -0
  184. package/dist/adapters/youtube/subtitles.js.map +1 -0
  185. package/dist/adapters/yt-dlp/subtitles.d.ts +9 -0
  186. package/dist/adapters/yt-dlp/subtitles.d.ts.map +1 -0
  187. package/dist/adapters/yt-dlp/subtitles.js +41 -0
  188. package/dist/adapters/yt-dlp/subtitles.js.map +1 -0
  189. package/dist/adapters/zhihu/answer-detail.d.ts +39 -0
  190. package/dist/adapters/zhihu/answer-detail.d.ts.map +1 -0
  191. package/dist/adapters/zhihu/answer-detail.js +204 -0
  192. package/dist/adapters/zhihu/answer-detail.js.map +1 -0
  193. package/dist/adapters/zhihu/comment.d.ts +9 -0
  194. package/dist/adapters/zhihu/comment.d.ts.map +1 -0
  195. package/dist/adapters/zhihu/comment.js +149 -0
  196. package/dist/adapters/zhihu/comment.js.map +1 -0
  197. package/dist/adapters/zhihu/recommend.d.ts +36 -0
  198. package/dist/adapters/zhihu/recommend.d.ts.map +1 -0
  199. package/dist/adapters/zhihu/recommend.js +151 -0
  200. package/dist/adapters/zhihu/recommend.js.map +1 -0
  201. package/dist/browser/bridge.d.ts.map +1 -1
  202. package/dist/browser/bridge.js +14 -3
  203. package/dist/browser/bridge.js.map +1 -1
  204. package/dist/browser/daemon-client.d.ts +6 -0
  205. package/dist/browser/daemon-client.d.ts.map +1 -1
  206. package/dist/browser/daemon-client.js +75 -15
  207. package/dist/browser/daemon-client.js.map +1 -1
  208. package/dist/browser/daemon.js +39 -15
  209. package/dist/browser/daemon.js.map +1 -1
  210. package/dist/browser/protocol.d.ts +1 -0
  211. package/dist/browser/protocol.d.ts.map +1 -1
  212. package/dist/browser/protocol.js +1 -0
  213. package/dist/browser/protocol.js.map +1 -1
  214. package/dist/cli.d.ts.map +1 -1
  215. package/dist/cli.js +6 -0
  216. package/dist/cli.js.map +1 -1
  217. package/dist/commands/approvals.d.ts.map +1 -1
  218. package/dist/commands/approvals.js +1 -37
  219. package/dist/commands/approvals.js.map +1 -1
  220. package/dist/commands/browser/index.d.ts.map +1 -1
  221. package/dist/commands/browser/index.js +7 -2
  222. package/dist/commands/browser/index.js.map +1 -1
  223. package/dist/commands/daemon.d.ts.map +1 -1
  224. package/dist/commands/daemon.js +7 -3
  225. package/dist/commands/daemon.js.map +1 -1
  226. package/dist/commands/dispatch.d.ts.map +1 -1
  227. package/dist/commands/dispatch.js +27 -3
  228. package/dist/commands/dispatch.js.map +1 -1
  229. package/dist/commands/patent-doctor.d.ts +48 -0
  230. package/dist/commands/patent-doctor.d.ts.map +1 -0
  231. package/dist/commands/patent-doctor.js +109 -0
  232. package/dist/commands/patent-doctor.js.map +1 -0
  233. package/dist/commands/patent.d.ts +78 -0
  234. package/dist/commands/patent.d.ts.map +1 -0
  235. package/dist/commands/patent.js +919 -0
  236. package/dist/commands/patent.js.map +1 -0
  237. package/dist/commands/social.d.ts +19 -0
  238. package/dist/commands/social.d.ts.map +1 -0
  239. package/dist/commands/social.js +236 -0
  240. package/dist/commands/social.js.map +1 -0
  241. package/dist/core/registry.d.ts +1 -1
  242. package/dist/core/registry.d.ts.map +1 -1
  243. package/dist/core/registry.js +11 -2
  244. package/dist/core/registry.js.map +1 -1
  245. package/dist/discovery/loader.d.ts.map +1 -1
  246. package/dist/discovery/loader.js +4 -0
  247. package/dist/discovery/loader.js.map +1 -1
  248. package/dist/engine/approval-presenter.d.ts +10 -0
  249. package/dist/engine/approval-presenter.d.ts.map +1 -0
  250. package/dist/engine/approval-presenter.js +45 -0
  251. package/dist/engine/approval-presenter.js.map +1 -0
  252. package/dist/engine/approval-store.d.ts +4 -0
  253. package/dist/engine/approval-store.d.ts.map +1 -1
  254. package/dist/engine/approval-store.js +85 -11
  255. package/dist/engine/approval-store.js.map +1 -1
  256. package/dist/engine/auth/oauth2-cc.d.ts +67 -0
  257. package/dist/engine/auth/oauth2-cc.d.ts.map +1 -0
  258. package/dist/engine/auth/oauth2-cc.js +120 -0
  259. package/dist/engine/auth/oauth2-cc.js.map +1 -0
  260. package/dist/engine/cookies.d.ts +10 -0
  261. package/dist/engine/cookies.d.ts.map +1 -1
  262. package/dist/engine/cookies.js +64 -0
  263. package/dist/engine/cookies.js.map +1 -1
  264. package/dist/engine/download.d.ts +5 -0
  265. package/dist/engine/download.d.ts.map +1 -1
  266. package/dist/engine/download.js +11 -4
  267. package/dist/engine/download.js.map +1 -1
  268. package/dist/engine/executor.d.ts +1 -0
  269. package/dist/engine/executor.d.ts.map +1 -1
  270. package/dist/engine/executor.js +25 -0
  271. package/dist/engine/executor.js.map +1 -1
  272. package/dist/engine/framework.d.ts +5 -5
  273. package/dist/engine/framework.js +5 -5
  274. package/dist/engine/harden.d.ts +1 -1
  275. package/dist/engine/harden.js +1 -1
  276. package/dist/engine/kernel/stages.d.ts.map +1 -1
  277. package/dist/engine/kernel/stages.js +2 -1
  278. package/dist/engine/kernel/stages.js.map +1 -1
  279. package/dist/engine/normalizer/patent-envelope.d.ts +61 -0
  280. package/dist/engine/normalizer/patent-envelope.d.ts.map +1 -0
  281. package/dist/engine/normalizer/patent-envelope.js +132 -0
  282. package/dist/engine/normalizer/patent-envelope.js.map +1 -0
  283. package/dist/engine/research.d.ts +5 -7
  284. package/dist/engine/research.d.ts.map +1 -1
  285. package/dist/engine/research.js +6 -9
  286. package/dist/engine/research.js.map +1 -1
  287. package/dist/engine/steps/browser-helpers.d.ts +2 -2
  288. package/dist/engine/steps/browser-helpers.d.ts.map +1 -1
  289. package/dist/engine/steps/browser-helpers.js +39 -16
  290. package/dist/engine/steps/browser-helpers.js.map +1 -1
  291. package/dist/engine/steps/download.d.ts +1 -0
  292. package/dist/engine/steps/download.d.ts.map +1 -1
  293. package/dist/engine/steps/download.js +3 -1
  294. package/dist/engine/steps/download.js.map +1 -1
  295. package/dist/engine/steps/index.d.ts +2 -0
  296. package/dist/engine/steps/index.d.ts.map +1 -1
  297. package/dist/engine/steps/index.js +2 -0
  298. package/dist/engine/steps/index.js.map +1 -1
  299. package/dist/engine/steps/oauth2-token.d.ts +41 -0
  300. package/dist/engine/steps/oauth2-token.d.ts.map +1 -0
  301. package/dist/engine/steps/oauth2-token.js +115 -0
  302. package/dist/engine/steps/oauth2-token.js.map +1 -0
  303. package/dist/engine/steps/select-xml.d.ts +34 -0
  304. package/dist/engine/steps/select-xml.d.ts.map +1 -0
  305. package/dist/engine/steps/select-xml.js +222 -0
  306. package/dist/engine/steps/select-xml.js.map +1 -0
  307. package/dist/engine/template.d.ts.map +1 -1
  308. package/dist/engine/template.js +7 -0
  309. package/dist/engine/template.js.map +1 -1
  310. package/dist/engine/transport/mcp-browser.d.ts +128 -0
  311. package/dist/engine/transport/mcp-browser.d.ts.map +1 -0
  312. package/dist/engine/transport/mcp-browser.js +120 -0
  313. package/dist/engine/transport/mcp-browser.js.map +1 -0
  314. package/dist/fast-path/handlers/approvals.d.ts +11 -0
  315. package/dist/fast-path/handlers/approvals.d.ts.map +1 -0
  316. package/dist/fast-path/handlers/approvals.js +136 -0
  317. package/dist/fast-path/handlers/approvals.js.map +1 -0
  318. package/dist/fast-path/manifest.d.ts +1 -0
  319. package/dist/fast-path/manifest.d.ts.map +1 -1
  320. package/dist/fast-path/manifest.js.map +1 -1
  321. package/dist/fast-path.d.ts.map +1 -1
  322. package/dist/fast-path.js +3 -0
  323. package/dist/fast-path.js.map +1 -1
  324. package/dist/index.d.ts +23 -0
  325. package/dist/index.d.ts.map +1 -0
  326. package/dist/index.js +38 -0
  327. package/dist/index.js.map +1 -0
  328. package/dist/manifest-compact.txt +3 -3
  329. package/dist/manifest-search.json +1 -1
  330. package/dist/manifest.json +2239 -176
  331. package/dist/output/auth-guidance.d.ts +14 -0
  332. package/dist/output/auth-guidance.d.ts.map +1 -0
  333. package/dist/output/auth-guidance.js +50 -0
  334. package/dist/output/auth-guidance.js.map +1 -0
  335. package/dist/output/error-map.d.ts +1 -1
  336. package/dist/output/error-map.d.ts.map +1 -1
  337. package/dist/output/error-map.js +28 -4
  338. package/dist/output/error-map.js.map +1 -1
  339. package/dist/output/next-actions.d.ts.map +1 -1
  340. package/dist/output/next-actions.js +19 -3
  341. package/dist/output/next-actions.js.map +1 -1
  342. package/dist/registry.d.ts +18 -1
  343. package/dist/registry.d.ts.map +1 -1
  344. package/dist/registry.js +5 -0
  345. package/dist/registry.js.map +1 -1
  346. package/dist/social/browser-errors.d.ts +13 -0
  347. package/dist/social/browser-errors.d.ts.map +1 -0
  348. package/dist/social/browser-errors.js +36 -0
  349. package/dist/social/browser-errors.js.map +1 -0
  350. package/dist/social/capabilities.d.ts +29 -0
  351. package/dist/social/capabilities.d.ts.map +1 -0
  352. package/dist/social/capabilities.js +448 -0
  353. package/dist/social/capabilities.js.map +1 -0
  354. package/dist/social/comments.d.ts +26 -0
  355. package/dist/social/comments.d.ts.map +1 -0
  356. package/dist/social/comments.js +97 -0
  357. package/dist/social/comments.js.map +1 -0
  358. package/dist/social/video-text.d.ts +27 -0
  359. package/dist/social/video-text.d.ts.map +1 -0
  360. package/dist/social/video-text.js +140 -0
  361. package/dist/social/video-text.js.map +1 -0
  362. package/dist/types/patent.d.ts +160 -0
  363. package/dist/types/patent.d.ts.map +1 -0
  364. package/dist/types/patent.js +16 -0
  365. package/dist/types/patent.js.map +1 -0
  366. package/dist/types.d.ts +12 -0
  367. package/dist/types.d.ts.map +1 -1
  368. package/dist/types.js.map +1 -1
  369. package/package.json +9 -4
  370. package/server.json +3 -3
  371. package/skills/unicli/SKILL.md +1 -1
  372. package/skills/unicli-claude-code/SKILL.md +1 -1
  373. package/skills/unicli-hermes/SKILL.md +1 -1
  374. package/src/adapters/bilibili/comments-tree.test.ts +41 -0
  375. package/src/adapters/bilibili/comments.ts +78 -4
  376. package/src/adapters/bilibili/compat.ts +5 -2
  377. package/src/adapters/bilibili/download.ts +7 -4
  378. package/src/adapters/bilibili/wbi.ts +6 -3
  379. package/src/adapters/brave/search.yaml +53 -0
  380. package/src/adapters/cipo/_shared.ts +98 -0
  381. package/src/adapters/cipo/get.ts +188 -0
  382. package/src/adapters/cipo/legal-status.ts +148 -0
  383. package/src/adapters/cipo/search.ts +195 -0
  384. package/src/adapters/cnipa/_shared.ts +138 -0
  385. package/src/adapters/cnipa/get.ts +199 -0
  386. package/src/adapters/cnipa/legal-status.ts +162 -0
  387. package/src/adapters/cnipa/search.ts +229 -0
  388. package/src/adapters/dpma/get.yaml +67 -0
  389. package/src/adapters/dpma/search.yaml +77 -0
  390. package/src/adapters/duckduckgo/search.yaml +54 -0
  391. package/src/adapters/duckduckgo/suggest.yaml +52 -0
  392. package/src/adapters/epo/family.yaml +69 -0
  393. package/src/adapters/epo/get.yaml +74 -0
  394. package/src/adapters/epo/legal-status.yaml +63 -0
  395. package/src/adapters/epo/search.yaml +84 -0
  396. package/src/adapters/espacenet/_shared.ts +98 -0
  397. package/src/adapters/espacenet/family.ts +161 -0
  398. package/src/adapters/espacenet/get.ts +185 -0
  399. package/src/adapters/espacenet/legal-status.ts +151 -0
  400. package/src/adapters/espacenet/search.ts +229 -0
  401. package/src/adapters/facebook/subtitles.ts +44 -0
  402. package/src/adapters/fips/_shared.ts +109 -0
  403. package/src/adapters/fips/get.ts +186 -0
  404. package/src/adapters/fips/search.ts +195 -0
  405. package/src/adapters/freepatentsonline-web/_shared.ts +273 -0
  406. package/src/adapters/freepatentsonline-web/get.ts +144 -0
  407. package/src/adapters/freepatentsonline-web/search.ts +170 -0
  408. package/src/adapters/google-patents-bq/prior-art.yaml +80 -0
  409. package/src/adapters/google-patents-bq/search.yaml +97 -0
  410. package/src/adapters/google-patents-web/_shared.ts +242 -0
  411. package/src/adapters/google-patents-web/get.ts +224 -0
  412. package/src/adapters/google-patents-web/search.ts +196 -0
  413. package/src/adapters/inpi-br/_shared.ts +98 -0
  414. package/src/adapters/inpi-br/get.ts +193 -0
  415. package/src/adapters/inpi-br/search.ts +206 -0
  416. package/src/adapters/inpi-fr/get.yaml +62 -0
  417. package/src/adapters/inpi-fr/search.yaml +74 -0
  418. package/src/adapters/instagram/subtitles.ts +44 -0
  419. package/src/adapters/ipaustralia/get.yaml +67 -0
  420. package/src/adapters/ipaustralia/search.yaml +74 -0
  421. package/src/adapters/jpo/get.yaml +63 -0
  422. package/src/adapters/jpo/search.yaml +76 -0
  423. package/src/adapters/kipris/get.yaml +69 -0
  424. package/src/adapters/kipris/legal-status.yaml +58 -0
  425. package/src/adapters/kipris/search.yaml +79 -0
  426. package/src/adapters/lens/get.yaml +64 -0
  427. package/src/adapters/lens/search.yaml +82 -0
  428. package/src/adapters/mastodon/statuses.test.ts +82 -0
  429. package/src/adapters/mastodon/statuses.ts +208 -0
  430. package/src/adapters/patsnap/get.yaml +65 -0
  431. package/src/adapters/patsnap/search.yaml +77 -0
  432. package/src/adapters/pqai/prior-art.yaml +59 -0
  433. package/src/adapters/pqai/search.yaml +60 -0
  434. package/src/adapters/reddit/comments-tree.test.ts +79 -0
  435. package/src/adapters/reddit/comments.ts +159 -0
  436. package/src/adapters/threads/post.test.ts +64 -0
  437. package/src/adapters/threads/post.ts +366 -0
  438. package/src/adapters/threads/user.yaml +73 -0
  439. package/src/adapters/tiktok/subtitles.ts +44 -0
  440. package/src/adapters/twitter/accept.ts +5 -2
  441. package/src/adapters/twitter/browser-fallback.ts +138 -0
  442. package/src/adapters/twitter/browser-state.ts +74 -0
  443. package/src/adapters/twitter/client.ts +51 -21
  444. package/src/adapters/twitter/reply-dm.ts +5 -2
  445. package/src/adapters/twitter/reply.ts +1 -0
  446. package/src/adapters/twitter/search.ts +12 -38
  447. package/src/adapters/twitter/thread.test.ts +43 -0
  448. package/src/adapters/twitter/thread.ts +44 -2
  449. package/src/adapters/twitter/trending.ts +14 -95
  450. package/src/adapters/ukipo/info.yaml +43 -0
  451. package/src/adapters/uspto/get.yaml +67 -0
  452. package/src/adapters/uspto/legal-status.yaml +58 -0
  453. package/src/adapters/uspto/search.yaml +88 -0
  454. package/src/adapters/wipo-patentscope/info.yaml +43 -0
  455. package/src/adapters/xiaohongshu/browser-state.ts +95 -0
  456. package/src/adapters/xiaohongshu/comments.ts +29 -6
  457. package/src/adapters/xiaohongshu/download.ts +60 -11
  458. package/src/adapters/xiaohongshu/search.ts +18 -6
  459. package/src/adapters/xiaohongshu/trending.ts +112 -0
  460. package/src/adapters/yahoo/search.yaml +52 -0
  461. package/src/adapters/youtube/comments-microformat.test.ts +35 -0
  462. package/src/adapters/youtube/comments-tree.test.ts +74 -0
  463. package/src/adapters/youtube/comments.ts +166 -12
  464. package/src/adapters/youtube/subtitles.ts +44 -0
  465. package/src/adapters/yt-dlp/subtitles.ts +43 -0
  466. package/src/adapters/zhihu/answer-detail.test.ts +83 -0
  467. package/src/adapters/zhihu/answer-detail.ts +275 -0
  468. package/src/adapters/zhihu/comment-tree.test.ts +57 -0
  469. package/src/adapters/zhihu/comment.ts +186 -0
  470. package/src/adapters/zhihu/recommend.test.ts +65 -0
  471. package/src/adapters/zhihu/recommend.ts +207 -0
@@ -0,0 +1,159 @@
1
+ /**
2
+ * @owner Reddit comments adapter.
3
+ * @does Fetches a Reddit post comment listing and emits normalized nested comment rows.
4
+ * @needs Public reddit JSON listing endpoint and a normal web User-Agent.
5
+ * @feeds Social comment tree analysis and Reddit read workflows.
6
+ * @breaks Reddit JSON schema or public endpoint access changes can block unauthenticated reads.
7
+ */
8
+
9
+ import { USER_AGENT } from "../../constants.js";
10
+ import { cli, Strategy } from "../../registry.js";
11
+ import { normalizeCommentRows } from "../../social/comments.js";
12
+
13
+ interface RedditListing {
14
+ data?: {
15
+ children?: RedditThing[];
16
+ };
17
+ }
18
+
19
+ interface RedditThing {
20
+ kind?: string;
21
+ data?: RedditCommentData;
22
+ }
23
+
24
+ interface RedditCommentData {
25
+ id?: string;
26
+ name?: string;
27
+ parent_id?: string;
28
+ author?: string;
29
+ body?: string;
30
+ score?: number;
31
+ created_utc?: number;
32
+ replies?: "" | RedditListing;
33
+ }
34
+
35
+ function normalizePostPath(input: string): string {
36
+ const raw = input.trim();
37
+ if (!raw) throw new Error("Reddit post URL or ID is required");
38
+ if (/^https?:\/\//i.test(raw)) {
39
+ const url = new URL(raw);
40
+ return url.pathname.replace(/^\/+/, "").replace(/\.json$/, "");
41
+ }
42
+ return raw.replace(/^\/+/, "").replace(/\.json$/, "");
43
+ }
44
+
45
+ function collectCommentRows(
46
+ listing: RedditListing,
47
+ contentId: string,
48
+ parentId = "",
49
+ ): Array<Record<string, unknown>> {
50
+ const rows: Array<Record<string, unknown>> = [];
51
+ for (const thing of listing.data?.children ?? []) {
52
+ if (thing.kind !== "t1") continue;
53
+ const data = thing.data;
54
+ if (!data?.id) continue;
55
+ const commentId = data.name ?? `t1_${data.id}`;
56
+ rows.push({
57
+ id: commentId,
58
+ parent_id:
59
+ data.parent_id && data.parent_id.startsWith("t1_")
60
+ ? data.parent_id
61
+ : parentId,
62
+ author: data.author ?? "",
63
+ body: data.body ?? "",
64
+ text: data.body ?? "",
65
+ score: data.score ?? 0,
66
+ replies:
67
+ data.replies && typeof data.replies === "object"
68
+ ? (data.replies.data?.children?.filter((child) => child.kind === "t1")
69
+ .length ?? 0)
70
+ : 0,
71
+ created: data.created_utc
72
+ ? new Date(data.created_utc * 1000).toISOString()
73
+ : "",
74
+ content_id: contentId,
75
+ });
76
+ if (data.replies && typeof data.replies === "object") {
77
+ rows.push(...collectCommentRows(data.replies, contentId, commentId));
78
+ }
79
+ }
80
+ return rows;
81
+ }
82
+
83
+ export function extractRedditCommentRows(
84
+ response: unknown,
85
+ contentId: string,
86
+ ): Array<Record<string, unknown>> {
87
+ if (!Array.isArray(response) || response.length < 2) {
88
+ throw new Error("Unexpected Reddit comments response");
89
+ }
90
+ return collectCommentRows(response[1] as RedditListing, contentId);
91
+ }
92
+
93
+ cli({
94
+ site: "reddit",
95
+ name: "comments",
96
+ description: "Reddit post comments with nested reply hierarchy",
97
+ domain: "www.reddit.com",
98
+ strategy: Strategy.PUBLIC,
99
+ args: [
100
+ {
101
+ name: "url",
102
+ required: true,
103
+ positional: true,
104
+ description: "Reddit post URL, path, or comments ID",
105
+ "x-unicli-accepts": ["url", "id"],
106
+ },
107
+ {
108
+ name: "sort",
109
+ default: "top",
110
+ choices: ["best", "top", "new", "controversial", "old", "qa"],
111
+ description: "Comment sort order",
112
+ },
113
+ {
114
+ name: "limit",
115
+ type: "int",
116
+ default: 20,
117
+ description: "Number of top-level comments",
118
+ },
119
+ ],
120
+ columns: [
121
+ "platform",
122
+ "content_id",
123
+ "comment_id",
124
+ "parent_id",
125
+ "depth",
126
+ "path",
127
+ "author",
128
+ "text",
129
+ "likes",
130
+ "replies",
131
+ "created",
132
+ "body",
133
+ "score",
134
+ ],
135
+ socialCapabilities: ["read", "comments", "comment_replies"],
136
+ func: async (_page, kwargs) => {
137
+ const postPath = normalizePostPath(String(kwargs.url ?? ""));
138
+ const sort = String(kwargs.sort ?? "top");
139
+ const limit = Number(kwargs.limit ?? 20);
140
+ const response = await fetch(
141
+ `https://www.reddit.com/${postPath}.json?sort=${encodeURIComponent(sort)}&limit=${encodeURIComponent(String(limit))}&raw_json=1`,
142
+ {
143
+ headers: {
144
+ "User-Agent": USER_AGENT,
145
+ Accept: "application/json",
146
+ },
147
+ },
148
+ );
149
+ if (!response.ok) {
150
+ throw new Error(`Reddit comments request failed: ${response.status}`);
151
+ }
152
+ const json = await response.json();
153
+ const rows = extractRedditCommentRows(json, postPath);
154
+ return normalizeCommentRows(rows, {
155
+ platform: "reddit",
156
+ contentId: postPath,
157
+ });
158
+ },
159
+ });
@@ -0,0 +1,64 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { parseThreadsPostHtml, parseThreadsProfileHtml } from "./post.js";
4
+
5
+ const postHtml = String.raw`
6
+ <html>
7
+ <head>
8
+ <meta property="og:title" content="Mark Zuckerberg (@zuck) on Threads" />
9
+ <meta property="og:description" content="Today we&#039;re starting to roll out Incognito Chat &amp; private message requests." />
10
+ <meta property="og:url" content="https://www.threads.com/@zuck/post/DYSAIo_FL77" />
11
+ <meta property="og:image" content="https://scontent.cdninstagram.com/v/t51.2885-15/threads-image.jpg" />
12
+ <meta property="og:image:width" content="1920" />
13
+ <meta property="og:image:height" content="1440" />
14
+ <meta property="al:ios:url" content="barcelona://media?shortcode=DYSAIo_FL77" />
15
+ <link rel="alternate" href="https://threads.net/ap/users/17841401746480004/post/17955322232962801/" type="application/activity+json" />
16
+ </head>
17
+ </html>
18
+ `;
19
+
20
+ const profileHtml = String.raw`
21
+ <html>
22
+ <head>
23
+ <meta property="og:title" content="Mark Zuckerberg (@zuck) • Threads, Say more" />
24
+ <meta property="og:description" content="5.5M Followers • 145 Threads • Mostly superintelligence and MMA takes. See the latest conversations with @zuck." />
25
+ <meta property="og:url" content="https://www.threads.com/@zuck" />
26
+ <meta property="og:image" content="https://scontent.cdninstagram.com/v/t51.2885-19/profile.jpg" />
27
+ </head>
28
+ </html>
29
+ `;
30
+
31
+ describe("Threads public page metadata", () => {
32
+ it("extracts a public post from Threads metadata", () => {
33
+ expect(
34
+ parseThreadsPostHtml(
35
+ postHtml,
36
+ "https://www.threads.net/@zuck/post/DYSAIo_FL77",
37
+ ),
38
+ ).toEqual({
39
+ activity_json_url:
40
+ "https://threads.net/ap/users/17841401746480004/post/17955322232962801/",
41
+ author: "Mark Zuckerberg",
42
+ handle: "@zuck",
43
+ image_height: 1440,
44
+ image_url:
45
+ "https://scontent.cdninstagram.com/v/t51.2885-15/threads-image.jpg",
46
+ image_width: 1920,
47
+ shortcode: "DYSAIo_FL77",
48
+ text: "Today we're starting to roll out Incognito Chat & private message requests.",
49
+ url: "https://www.threads.com/@zuck/post/DYSAIo_FL77",
50
+ });
51
+ });
52
+
53
+ it("extracts a public profile from Threads metadata", () => {
54
+ expect(parseThreadsProfileHtml(profileHtml, "zuck")).toEqual({
55
+ avatar_url: "https://scontent.cdninstagram.com/v/t51.2885-19/profile.jpg",
56
+ bio: "Mostly superintelligence and MMA takes.",
57
+ followers: "5.5M",
58
+ handle: "@zuck",
59
+ name: "Mark Zuckerberg",
60
+ threads: "145",
61
+ url: "https://www.threads.com/@zuck",
62
+ });
63
+ });
64
+ });
@@ -0,0 +1,366 @@
1
+ /**
2
+ * @owner Threads public page metadata adapter.
3
+ * @does Register public profile, post, and media readers backed by Threads OG metadata.
4
+ * @needs Threads public HTML pages exposing Open Graph metadata.
5
+ * @feeds Social research workflows and Threads capability coverage.
6
+ * @breaks Threads hiding public metadata or changing canonical post/profile markup.
7
+ */
8
+
9
+ import { USER_AGENT } from "../../constants.js";
10
+ import { cli, Strategy } from "../../registry.js";
11
+
12
+ export interface ThreadsPostRow {
13
+ author: string;
14
+ handle: string;
15
+ text: string;
16
+ url: string;
17
+ image_url: string;
18
+ image_width: number;
19
+ image_height: number;
20
+ shortcode: string;
21
+ activity_json_url: string;
22
+ }
23
+
24
+ export interface ThreadsProfileRow {
25
+ name: string;
26
+ handle: string;
27
+ followers: string;
28
+ threads: string;
29
+ bio: string;
30
+ url: string;
31
+ avatar_url: string;
32
+ }
33
+
34
+ interface Identity {
35
+ name: string;
36
+ handle: string;
37
+ }
38
+
39
+ const THREADS_HOST_RE = /(^|\.)threads\.(net|com)$/i;
40
+
41
+ function decodeHtml(value: string): string {
42
+ return value
43
+ .replace(/&#x([0-9a-f]+);/gi, (_, code: string) =>
44
+ String.fromCodePoint(Number.parseInt(code, 16)),
45
+ )
46
+ .replace(/&#(\d+);/g, (_, code: string) =>
47
+ String.fromCodePoint(Number.parseInt(code, 10)),
48
+ )
49
+ .replace(/&nbsp;/g, " ")
50
+ .replace(/&amp;/g, "&")
51
+ .replace(/&lt;/g, "<")
52
+ .replace(/&gt;/g, ">")
53
+ .replace(/&quot;/g, '"')
54
+ .replace(/&apos;/g, "'")
55
+ .replace(/&#039;/g, "'")
56
+ .replace(/\s+/g, " ")
57
+ .trim();
58
+ }
59
+
60
+ function attributesFor(tag: string): Record<string, string> {
61
+ const attrs: Record<string, string> = {};
62
+ const attrRe = /\s([^\s"'=<>`]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/g;
63
+ for (const match of tag.matchAll(attrRe)) {
64
+ attrs[match[1].toLowerCase()] = decodeHtml(
65
+ match[2] ?? match[3] ?? match[4] ?? "",
66
+ );
67
+ }
68
+ return attrs;
69
+ }
70
+
71
+ function metaContent(html: string, key: string): string {
72
+ for (const match of html.matchAll(/<meta\b[^>]*>/gi)) {
73
+ const attrs = attributesFor(match[0]);
74
+ if (attrs.property === key || attrs.name === key)
75
+ return attrs.content ?? "";
76
+ }
77
+ return "";
78
+ }
79
+
80
+ function linkHref(html: string, rel: string, type: string): string {
81
+ for (const match of html.matchAll(/<link\b[^>]*>/gi)) {
82
+ const attrs = attributesFor(match[0]);
83
+ const rels = new Set(
84
+ (attrs.rel ?? "").toLowerCase().split(/\s+/).filter(Boolean),
85
+ );
86
+ if (rels.has(rel.toLowerCase()) && attrs.type === type)
87
+ return attrs.href ?? "";
88
+ }
89
+ return "";
90
+ }
91
+
92
+ function firstNonEmpty(...values: string[]): string {
93
+ return values.find((value) => value.trim())?.trim() ?? "";
94
+ }
95
+
96
+ function numberFromMeta(value: string): number {
97
+ const number = Number.parseInt(value, 10);
98
+ return Number.isFinite(number) ? number : 0;
99
+ }
100
+
101
+ function cleanHandle(input: string): string {
102
+ const trimmed = input.trim();
103
+ if (!trimmed) throw new Error("Threads handle cannot be empty");
104
+ if (/^https?:\/\//i.test(trimmed)) {
105
+ const url = new URL(trimmed);
106
+ const handle = url.pathname.split("/").find((part) => part.startsWith("@"));
107
+ if (!handle)
108
+ throw new Error(`Threads URL does not contain a handle: ${input}`);
109
+ return handle.replace(/^@/, "");
110
+ }
111
+ return trimmed.replace(/^@/, "").replace(/^\/+/, "").split("/")[0];
112
+ }
113
+
114
+ function handleFromUrl(input: string): string {
115
+ const url = new URL(input);
116
+ const handle = url.pathname.split("/").find((part) => part.startsWith("@"));
117
+ return handle ? handle : "";
118
+ }
119
+
120
+ function identityFromTitle(title: string, fallbackHandle: string): Identity {
121
+ const match = title.match(/^\s*(.*?)\s+\((@[A-Za-z0-9._]+)\)/);
122
+ const handle = match?.[2] ?? fallbackHandle;
123
+ const name = firstNonEmpty(match?.[1] ?? "", handle.replace(/^@/, ""));
124
+ return { name, handle };
125
+ }
126
+
127
+ function ensureThreadsUrl(url: URL): void {
128
+ if (!THREADS_HOST_RE.test(url.hostname)) {
129
+ throw new Error(
130
+ `Expected a threads.net or threads.com URL, got ${url.hostname}`,
131
+ );
132
+ }
133
+ }
134
+
135
+ export function normalizeThreadsProfileUrl(input: string): string {
136
+ const handle = cleanHandle(input);
137
+ return `https://www.threads.net/@${encodeURIComponent(handle)}`;
138
+ }
139
+
140
+ export function normalizeThreadsPostUrl(input: string): string {
141
+ const trimmed = input.trim();
142
+ if (!trimmed) throw new Error("Threads post URL cannot be empty");
143
+ const raw =
144
+ trimmed.startsWith("@") || trimmed.startsWith("/@")
145
+ ? `https://www.threads.net/${trimmed.replace(/^\/+/, "")}`
146
+ : trimmed;
147
+ const url = new URL(raw);
148
+ ensureThreadsUrl(url);
149
+ if (!/^\/@[^/]+\/post\/[^/?#]+/.test(url.pathname)) {
150
+ throw new Error(
151
+ `Expected Threads post URL path /@handle/post/shortcode, got ${url.pathname}`,
152
+ );
153
+ }
154
+ return url.toString();
155
+ }
156
+
157
+ function shortcodeFromPost(url: string, iosUrl: string): string {
158
+ const iosMatch = iosUrl.match(/[?&]shortcode=([^&]+)/);
159
+ if (iosMatch) return decodeURIComponent(iosMatch[1]);
160
+ const urlMatch = url.match(/\/post\/([^/?#]+)/);
161
+ return urlMatch ? decodeURIComponent(urlMatch[1]) : "";
162
+ }
163
+
164
+ export function parseThreadsPostHtml(
165
+ html: string,
166
+ fallbackUrl: string,
167
+ ): ThreadsPostRow {
168
+ const url = firstNonEmpty(metaContent(html, "og:url"), fallbackUrl);
169
+ const title = metaContent(html, "og:title");
170
+ const text = firstNonEmpty(
171
+ metaContent(html, "og:description"),
172
+ metaContent(html, "twitter:description"),
173
+ );
174
+ const imageUrl = metaContent(html, "og:image");
175
+ const fallbackHandle = handleFromUrl(url);
176
+ const identity = identityFromTitle(title, fallbackHandle);
177
+ if (!identity.handle || (!text && !imageUrl)) {
178
+ throw new Error("Threads post page did not expose usable public metadata");
179
+ }
180
+ return {
181
+ activity_json_url: linkHref(html, "alternate", "application/activity+json"),
182
+ author: identity.name,
183
+ handle: identity.handle,
184
+ image_height: numberFromMeta(metaContent(html, "og:image:height")),
185
+ image_url: imageUrl,
186
+ image_width: numberFromMeta(metaContent(html, "og:image:width")),
187
+ shortcode: shortcodeFromPost(url, metaContent(html, "al:ios:url")),
188
+ text,
189
+ url,
190
+ };
191
+ }
192
+
193
+ export function parseThreadsProfileHtml(
194
+ html: string,
195
+ handleInput: string,
196
+ ): ThreadsProfileRow {
197
+ const title = metaContent(html, "og:title");
198
+ const description = metaContent(html, "og:description");
199
+ const url = firstNonEmpty(
200
+ metaContent(html, "og:url"),
201
+ normalizeThreadsProfileUrl(handleInput),
202
+ );
203
+ const identity = identityFromTitle(title, `@${cleanHandle(handleInput)}`);
204
+ const parts = description
205
+ .split("•")
206
+ .map((part) => part.trim())
207
+ .filter(Boolean);
208
+ const followers =
209
+ parts
210
+ .find((part) => /\bFollowers\b/i.test(part))
211
+ ?.replace(/\s*Followers\b/i, "") ?? "";
212
+ const threads =
213
+ parts
214
+ .find((part) => /\bThreads\b/i.test(part))
215
+ ?.replace(/\s*Threads\b/i, "") ?? "";
216
+ const bio = firstNonEmpty(
217
+ ...parts.filter((part) => !/\b(Followers|Threads)\b/i.test(part)),
218
+ ).replace(/\s*See the latest conversations with @[^.]+\.?$/i, "");
219
+ if (!identity.handle || !identity.name) {
220
+ throw new Error(
221
+ "Threads profile page did not expose usable public metadata",
222
+ );
223
+ }
224
+ return {
225
+ avatar_url: metaContent(html, "og:image"),
226
+ bio,
227
+ followers,
228
+ handle: identity.handle,
229
+ name: identity.name,
230
+ threads,
231
+ url,
232
+ };
233
+ }
234
+
235
+ function mediaRowsFromPost(
236
+ post: ThreadsPostRow,
237
+ ): Array<Record<string, string | number>> {
238
+ if (!post.image_url) return [];
239
+ return [
240
+ {
241
+ rank: 1,
242
+ type: "image",
243
+ url: post.image_url,
244
+ width: post.image_width,
245
+ height: post.image_height,
246
+ post_url: post.url,
247
+ shortcode: post.shortcode,
248
+ source: "og:image",
249
+ },
250
+ ];
251
+ }
252
+
253
+ async function fetchThreadsHtml(url: string): Promise<string> {
254
+ const response = await fetch(url, {
255
+ headers: {
256
+ Accept: "text/html,application/xhtml+xml",
257
+ "User-Agent": USER_AGENT,
258
+ },
259
+ });
260
+ if (!response.ok) {
261
+ const preview = (await response.text()).slice(0, 200);
262
+ throw new Error(`Threads HTTP ${response.status} from ${url}: ${preview}`);
263
+ }
264
+ return response.text();
265
+ }
266
+
267
+ cli({
268
+ site: "threads",
269
+ name: "profile",
270
+ description: "Get public Threads profile metadata",
271
+ domain: "threads.net",
272
+ strategy: Strategy.PUBLIC,
273
+ args: [
274
+ {
275
+ name: "handle",
276
+ required: true,
277
+ positional: true,
278
+ description: "Threads handle or profile URL, e.g. zuck",
279
+ },
280
+ ],
281
+ columns: [
282
+ "name",
283
+ "handle",
284
+ "followers",
285
+ "threads",
286
+ "bio",
287
+ "url",
288
+ "avatar_url",
289
+ ],
290
+ socialCapabilities: ["read", "author", "relations"],
291
+ func: async (_page, kwargs) => {
292
+ const url = normalizeThreadsProfileUrl(String(kwargs.handle ?? ""));
293
+ const html = await fetchThreadsHtml(url);
294
+ return [parseThreadsProfileHtml(html, String(kwargs.handle ?? ""))];
295
+ },
296
+ });
297
+
298
+ cli({
299
+ site: "threads",
300
+ name: "post",
301
+ description: "Get a public Threads post from its metadata",
302
+ domain: "threads.net",
303
+ strategy: Strategy.PUBLIC,
304
+ args: [
305
+ {
306
+ name: "url",
307
+ required: true,
308
+ positional: true,
309
+ description:
310
+ "Threads post URL, e.g. https://www.threads.net/@zuck/post/DYSAIo_FL77",
311
+ },
312
+ ],
313
+ columns: [
314
+ "author",
315
+ "handle",
316
+ "text",
317
+ "url",
318
+ "image_url",
319
+ "image_width",
320
+ "image_height",
321
+ "shortcode",
322
+ "activity_json_url",
323
+ ],
324
+ socialCapabilities: ["read", "author", "media"],
325
+ func: async (_page, kwargs) => {
326
+ const url = normalizeThreadsPostUrl(String(kwargs.url ?? ""));
327
+ const html = await fetchThreadsHtml(url);
328
+ return [parseThreadsPostHtml(html, url)];
329
+ },
330
+ });
331
+
332
+ cli({
333
+ site: "threads",
334
+ name: "media",
335
+ description: "List public media exposed by a Threads post",
336
+ domain: "threads.net",
337
+ strategy: Strategy.PUBLIC,
338
+ args: [
339
+ {
340
+ name: "url",
341
+ required: true,
342
+ positional: true,
343
+ description:
344
+ "Threads post URL, e.g. https://www.threads.net/@zuck/post/DYSAIo_FL77",
345
+ },
346
+ ],
347
+ columns: [
348
+ "rank",
349
+ "type",
350
+ "url",
351
+ "width",
352
+ "height",
353
+ "post_url",
354
+ "shortcode",
355
+ "source",
356
+ ],
357
+ socialCapabilities: ["read", "media"],
358
+ func: async (_page, kwargs) => {
359
+ const url = normalizeThreadsPostUrl(String(kwargs.url ?? ""));
360
+ const html = await fetchThreadsHtml(url);
361
+ const rows = mediaRowsFromPost(parseThreadsPostHtml(html, url));
362
+ if (rows.length === 0)
363
+ throw new Error("Threads post metadata did not include media");
364
+ return rows;
365
+ },
366
+ });
@@ -0,0 +1,73 @@
1
+ site: threads
2
+ name: user
3
+ description: Get recent posts from a Threads user profile
4
+ domain: threads.net
5
+ strategy: intercept
6
+ lint_listing_detail: skip
7
+ browser: true
8
+
9
+ args:
10
+ handle:
11
+ type: str
12
+ required: true
13
+ positional: true
14
+ description: Threads handle without leading @
15
+ limit:
16
+ type: int
17
+ default: 20
18
+ description: Number of posts
19
+
20
+ columns: [rank, author, text, likes, replies, url]
21
+
22
+ pipeline:
23
+ - navigate:
24
+ url: https://www.threads.net/@${{ args.handle | urlencode }}
25
+ settleMs: 4000
26
+
27
+ - scroll:
28
+ times: 2
29
+
30
+ - evaluate: |
31
+ (() => {
32
+ const limit = ${{ args.limit }};
33
+ const handle = ${{ args.handle | json }};
34
+ const clean = value => (value || '').replace(/\s+/g, ' ').trim();
35
+ const absolute = href => href?.startsWith('http') ? href : (href ? `https://www.threads.net${href}` : '');
36
+ const cleanPostText = value => {
37
+ const text = clean(value);
38
+ if (text === handle || text === `@${handle}`) return '';
39
+ if (/^(back|home|search|create|notifications|profile|more|like|reply|repost|share)$/i.test(text)) return '';
40
+ if (/返回|首页|搜索|创建|通知|主页|更多/.test(text)) return '';
41
+ return text;
42
+ };
43
+ const cards = Array.from(document.querySelectorAll('[data-pressable-container="true"], article, [class*="FeedItem"]'));
44
+ const rows = cards.slice(0, limit).map((post, index) => {
45
+ const authorEl = post.querySelector('a[href*="/@"] span, a[href*="/@"]');
46
+ const textEl = post.querySelector('[class*="text"], [class*="caption"], [dir="auto"]');
47
+ const likeEl = post.querySelector('[aria-label*="like" i], [class*="like" i]');
48
+ const replyEl = post.querySelector('[aria-label*="repl" i], [class*="reply" i]');
49
+ const linkEl = post.querySelector('a[href*="/post/"]');
50
+ return {
51
+ rank: index + 1,
52
+ author: clean(authorEl?.textContent) || `@${handle}`,
53
+ text: cleanPostText(textEl?.textContent).slice(0, 500),
54
+ likes: clean(likeEl?.textContent),
55
+ replies: clean(replyEl?.textContent),
56
+ url: absolute(linkEl?.getAttribute('href')),
57
+ };
58
+ }).filter(row => row.text || row.url);
59
+
60
+ if (rows.length === 0) {
61
+ throw new Error('threads user: no posts found; login, empty profile, or selector drift may be blocking extraction');
62
+ }
63
+ return rows;
64
+ })()
65
+
66
+ # schema-v2 metadata — injected by `unicli migrate schema-v2`
67
+ capabilities:
68
+ ["cdp-browser.evaluate", "cdp-browser.navigate", "cdp-browser.scroll"]
69
+ minimum_capability: cdp-browser.evaluate
70
+ trust: public
71
+ confidentiality: public
72
+ quarantine: false
73
+ schema_version: v2