@jackwener/opencli 1.3.2 → 1.4.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 (508) hide show
  1. package/.github/pull_request_template.md +3 -1
  2. package/.github/workflows/build-extension.yml +7 -1
  3. package/.github/workflows/ci.yml +29 -3
  4. package/.github/workflows/docs.yml +1 -1
  5. package/.github/workflows/e2e-headed.yml +20 -0
  6. package/.github/workflows/release.yml +1 -1
  7. package/.github/workflows/security.yml +0 -3
  8. package/CHANGELOG.md +55 -0
  9. package/CONTRIBUTING.md +6 -3
  10. package/README.md +37 -10
  11. package/README.zh-CN.md +37 -10
  12. package/SKILL.md +7 -2
  13. package/TESTING.md +1 -0
  14. package/chatwise-opencli.ps1 +82 -0
  15. package/dist/analysis.d.ts +38 -0
  16. package/dist/analysis.js +166 -0
  17. package/dist/browser/cdp.d.ts +0 -4
  18. package/dist/browser/cdp.js +59 -38
  19. package/dist/browser/cdp.test.d.ts +1 -0
  20. package/dist/browser/cdp.test.js +52 -0
  21. package/dist/browser/daemon-client.js +2 -1
  22. package/dist/browser/discover.js +2 -1
  23. package/dist/browser/dom-snapshot.d.ts +2 -2
  24. package/dist/browser/dom-snapshot.js +54 -1
  25. package/dist/browser/dom-snapshot.test.js +36 -0
  26. package/dist/browser/errors.js +2 -1
  27. package/dist/browser/index.d.ts +3 -2
  28. package/dist/browser/index.js +2 -1
  29. package/dist/browser/mcp.d.ts +0 -2
  30. package/dist/browser/mcp.js +2 -3
  31. package/dist/browser/page.d.ts +4 -3
  32. package/dist/browser/page.js +44 -35
  33. package/dist/browser/stealth.d.ts +16 -0
  34. package/dist/browser/stealth.js +155 -0
  35. package/dist/browser.test.js +47 -1
  36. package/dist/build-manifest.js +15 -9
  37. package/dist/build-manifest.test.js +12 -0
  38. package/dist/cascade.js +4 -2
  39. package/dist/cli-manifest.json +639 -258
  40. package/dist/cli.js +57 -29
  41. package/dist/clis/_shared/desktop-commands.d.ts +22 -0
  42. package/dist/clis/_shared/desktop-commands.js +108 -0
  43. package/dist/clis/antigravity/serve.js +5 -2
  44. package/dist/clis/arxiv/search.js +1 -1
  45. package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
  46. package/dist/clis/bilibili/dynamic.test.js +68 -0
  47. package/dist/clis/bilibili/favorite.js +4 -2
  48. package/dist/clis/bilibili/following.js +3 -2
  49. package/dist/clis/bilibili/subtitle.js +8 -7
  50. package/dist/clis/bilibili/utils.js +2 -2
  51. package/dist/clis/boss/batchgreet.js +1 -1
  52. package/dist/clis/boss/chatlist.js +1 -1
  53. package/dist/clis/boss/chatmsg.js +1 -1
  54. package/dist/clis/boss/detail.js +1 -1
  55. package/dist/clis/boss/exchange.js +1 -1
  56. package/dist/clis/boss/greet.js +1 -1
  57. package/dist/clis/boss/invite.js +1 -1
  58. package/dist/clis/boss/joblist.js +1 -1
  59. package/dist/clis/boss/mark.js +4 -3
  60. package/dist/clis/boss/recommend.js +1 -1
  61. package/dist/clis/boss/resume.js +1 -1
  62. package/dist/clis/boss/search.js +1 -1
  63. package/dist/clis/boss/send.js +5 -4
  64. package/dist/clis/boss/stats.js +1 -1
  65. package/dist/clis/chatgpt/ask.js +4 -0
  66. package/dist/clis/chatgpt/new.js +5 -1
  67. package/dist/clis/chatgpt/read.js +5 -1
  68. package/dist/clis/chatgpt/send.js +2 -1
  69. package/dist/clis/chatgpt/status.js +5 -1
  70. package/dist/clis/chatwise/ask.js +8 -2
  71. package/dist/clis/chatwise/export.js +2 -0
  72. package/dist/clis/chatwise/history.js +2 -0
  73. package/dist/clis/chatwise/model.js +8 -3
  74. package/dist/clis/chatwise/new.js +3 -18
  75. package/dist/clis/chatwise/read.js +2 -0
  76. package/dist/clis/chatwise/screenshot.js +3 -27
  77. package/dist/clis/chatwise/send.js +8 -2
  78. package/dist/clis/chatwise/shared.d.ts +2 -0
  79. package/dist/clis/chatwise/shared.js +6 -0
  80. package/dist/clis/chatwise/status.js +3 -22
  81. package/dist/clis/codex/ask.js +6 -2
  82. package/dist/clis/codex/dump.js +2 -25
  83. package/dist/clis/codex/new.js +2 -25
  84. package/dist/clis/codex/screenshot.js +2 -27
  85. package/dist/clis/codex/send.js +6 -4
  86. package/dist/clis/codex/status.js +2 -22
  87. package/dist/clis/cursor/ask.js +2 -1
  88. package/dist/clis/cursor/composer.js +2 -1
  89. package/dist/clis/cursor/dump.js +2 -25
  90. package/dist/clis/cursor/new.js +2 -18
  91. package/dist/clis/cursor/read.js +2 -1
  92. package/dist/clis/cursor/screenshot.js +1 -30
  93. package/dist/clis/cursor/send.js +2 -1
  94. package/dist/clis/cursor/status.js +2 -21
  95. package/dist/clis/dictionary/examples.yaml +25 -0
  96. package/dist/clis/dictionary/search.yaml +27 -0
  97. package/dist/clis/dictionary/synonyms.yaml +25 -0
  98. package/dist/clis/douban/book-hot.js +1 -1
  99. package/dist/clis/douban/movie-hot.js +1 -1
  100. package/dist/clis/douban/search.js +1 -1
  101. package/dist/clis/douban/utils.d.ts +4 -1
  102. package/dist/clis/douban/utils.js +156 -1
  103. package/dist/clis/doubao/ask.js +1 -1
  104. package/dist/clis/doubao/new.js +1 -1
  105. package/dist/clis/doubao/read.js +1 -1
  106. package/dist/clis/doubao/send.js +1 -1
  107. package/dist/clis/doubao/status.js +1 -1
  108. package/dist/clis/doubao-app/ask.js +1 -1
  109. package/dist/clis/doubao-app/new.js +1 -1
  110. package/dist/clis/doubao-app/read.js +1 -1
  111. package/dist/clis/doubao-app/send.js +1 -1
  112. package/dist/clis/grok/ask.d.ts +4 -0
  113. package/dist/clis/grok/ask.js +28 -10
  114. package/dist/clis/grok/ask.test.js +18 -0
  115. package/dist/clis/jd/item.d.ts +1 -0
  116. package/dist/clis/jd/item.js +96 -0
  117. package/dist/clis/jd/item.test.d.ts +1 -0
  118. package/dist/clis/jd/item.test.js +28 -0
  119. package/dist/clis/jike/feed.js +1 -1
  120. package/dist/clis/jike/search.js +1 -1
  121. package/dist/clis/linkedin/search.js +5 -4
  122. package/dist/clis/linkedin/timeline.d.ts +21 -0
  123. package/dist/clis/linkedin/timeline.js +503 -0
  124. package/dist/clis/linkedin/timeline.test.d.ts +1 -0
  125. package/dist/clis/linkedin/timeline.test.js +81 -0
  126. package/dist/clis/medium/feed.js +1 -1
  127. package/dist/clis/medium/search.js +1 -1
  128. package/dist/clis/medium/user.js +1 -1
  129. package/dist/clis/medium/{shared.js → utils.js} +2 -1
  130. package/dist/clis/pixiv/detail.yaml +49 -0
  131. package/dist/clis/pixiv/download.d.ts +7 -0
  132. package/dist/clis/pixiv/download.js +78 -0
  133. package/dist/clis/pixiv/download.test.d.ts +1 -0
  134. package/dist/clis/pixiv/download.test.js +87 -0
  135. package/dist/clis/pixiv/illusts.d.ts +8 -0
  136. package/dist/clis/pixiv/illusts.js +65 -0
  137. package/dist/clis/pixiv/illusts.test.d.ts +1 -0
  138. package/dist/clis/pixiv/illusts.test.js +99 -0
  139. package/dist/clis/pixiv/ranking.yaml +53 -0
  140. package/dist/clis/pixiv/search.d.ts +6 -0
  141. package/dist/clis/pixiv/search.js +43 -0
  142. package/dist/clis/pixiv/search.test.d.ts +1 -0
  143. package/dist/clis/pixiv/search.test.js +83 -0
  144. package/dist/clis/pixiv/test-utils.d.ts +12 -0
  145. package/dist/clis/pixiv/test-utils.js +23 -0
  146. package/dist/clis/pixiv/user.yaml +46 -0
  147. package/dist/clis/pixiv/utils.d.ts +27 -0
  148. package/dist/clis/pixiv/utils.js +49 -0
  149. package/dist/clis/reddit/comment.js +2 -1
  150. package/dist/clis/reddit/read.js +4 -3
  151. package/dist/clis/reddit/read.test.d.ts +1 -0
  152. package/dist/clis/reddit/read.test.js +28 -0
  153. package/dist/clis/reddit/save.js +2 -1
  154. package/dist/clis/reddit/saved.js +7 -3
  155. package/dist/clis/reddit/subscribe.js +2 -1
  156. package/dist/clis/reddit/upvote.js +2 -1
  157. package/dist/clis/reddit/upvoted.js +7 -3
  158. package/dist/clis/sinablog/article.js +1 -1
  159. package/dist/clis/sinablog/hot.js +1 -1
  160. package/dist/clis/sinablog/user.js +1 -1
  161. package/dist/clis/substack/feed.js +1 -1
  162. package/dist/clis/substack/publication.js +1 -1
  163. package/dist/clis/substack/search.js +3 -2
  164. package/dist/clis/substack/{shared.js → utils.js} +3 -2
  165. package/dist/clis/tiktok/search.yaml +2 -1
  166. package/dist/clis/twitter/accept.js +2 -1
  167. package/dist/clis/twitter/article.js +4 -1
  168. package/dist/clis/twitter/block.js +2 -1
  169. package/dist/clis/twitter/bookmark.js +2 -1
  170. package/dist/clis/twitter/bookmarks.js +3 -2
  171. package/dist/clis/twitter/delete.js +2 -1
  172. package/dist/clis/twitter/follow.js +2 -1
  173. package/dist/clis/twitter/followers.js +3 -2
  174. package/dist/clis/twitter/following.js +3 -2
  175. package/dist/clis/twitter/hide-reply.js +2 -1
  176. package/dist/clis/twitter/like.js +2 -1
  177. package/dist/clis/twitter/notifications.js +2 -1
  178. package/dist/clis/twitter/post.js +2 -1
  179. package/dist/clis/twitter/profile.js +5 -2
  180. package/dist/clis/twitter/reply-dm.js +2 -1
  181. package/dist/clis/twitter/reply.js +2 -1
  182. package/dist/clis/twitter/search.js +30 -13
  183. package/dist/clis/twitter/search.test.d.ts +1 -0
  184. package/dist/clis/twitter/search.test.js +104 -0
  185. package/dist/clis/twitter/thread.js +2 -2
  186. package/dist/clis/twitter/timeline.js +3 -2
  187. package/dist/clis/twitter/trending.js +3 -2
  188. package/dist/clis/twitter/unblock.js +2 -1
  189. package/dist/clis/twitter/unbookmark.js +2 -1
  190. package/dist/clis/twitter/unfollow.js +2 -1
  191. package/dist/clis/v2ex/daily.js +3 -2
  192. package/dist/clis/v2ex/me.js +3 -2
  193. package/dist/clis/v2ex/notifications.js +4 -4
  194. package/dist/clis/web/read.d.ts +16 -0
  195. package/dist/clis/web/read.js +202 -0
  196. package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
  197. package/dist/clis/xueqiu/danjuan-utils.js +126 -0
  198. package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
  199. package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
  200. package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
  201. package/dist/clis/xueqiu/fund-holdings.js +28 -0
  202. package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
  203. package/dist/clis/xueqiu/fund-snapshot.js +25 -0
  204. package/dist/clis/youtube/transcript.js +5 -4
  205. package/dist/clis/youtube/video.js +3 -2
  206. package/dist/constants.d.ts +2 -0
  207. package/dist/constants.js +2 -0
  208. package/dist/daemon.js +9 -4
  209. package/dist/discovery.js +11 -10
  210. package/dist/doctor.js +4 -2
  211. package/dist/download/index.d.ts +4 -12
  212. package/dist/download/index.js +33 -12
  213. package/dist/download/index.test.js +79 -2
  214. package/dist/download/media-download.js +4 -2
  215. package/dist/engine.test.js +76 -4
  216. package/dist/execution.d.ts +1 -9
  217. package/dist/execution.js +56 -46
  218. package/dist/explore.js +12 -111
  219. package/dist/external-clis.yaml +0 -8
  220. package/dist/external.js +7 -5
  221. package/dist/external.test.js +4 -0
  222. package/dist/generate.d.ts +0 -9
  223. package/dist/generate.js +4 -20
  224. package/dist/hooks.d.ts +46 -0
  225. package/dist/hooks.js +56 -0
  226. package/dist/hooks.test.d.ts +4 -0
  227. package/dist/hooks.test.js +92 -0
  228. package/dist/interceptor.js +70 -23
  229. package/dist/main.js +2 -0
  230. package/dist/output.js +12 -6
  231. package/dist/pipeline/executor.js +1 -1
  232. package/dist/pipeline/steps/browser.js +1 -3
  233. package/dist/pipeline/steps/download.js +42 -26
  234. package/dist/pipeline/steps/download.test.d.ts +1 -0
  235. package/dist/pipeline/steps/download.test.js +101 -0
  236. package/dist/pipeline/steps/fetch.js +40 -22
  237. package/dist/pipeline/steps/fetch.test.d.ts +1 -0
  238. package/dist/pipeline/steps/fetch.test.js +123 -0
  239. package/dist/pipeline/steps/transform.js +2 -6
  240. package/dist/pipeline/template.js +66 -52
  241. package/dist/pipeline/template.test.js +28 -0
  242. package/dist/pipeline/transform.test.js +18 -0
  243. package/dist/plugin.d.ts +40 -1
  244. package/dist/plugin.js +214 -17
  245. package/dist/plugin.test.d.ts +1 -1
  246. package/dist/plugin.test.js +219 -3
  247. package/dist/record.js +6 -98
  248. package/dist/registry-api.d.ts +2 -0
  249. package/dist/registry-api.js +1 -0
  250. package/dist/registry.d.ts +5 -2
  251. package/dist/registry.js +1 -2
  252. package/dist/runtime.d.ts +0 -1
  253. package/dist/runtime.js +14 -4
  254. package/dist/snapshotFormatter.d.ts +7 -14
  255. package/dist/snapshotFormatter.js +38 -78
  256. package/dist/utils.d.ts +9 -0
  257. package/dist/utils.js +29 -0
  258. package/dist/validate.js +3 -5
  259. package/dist/yaml-schema.d.ts +26 -0
  260. package/dist/yaml-schema.js +5 -0
  261. package/docs/.vitepress/config.mts +3 -0
  262. package/docs/adapters/browser/dictionary.md +27 -0
  263. package/docs/adapters/browser/douban.md +18 -8
  264. package/docs/adapters/browser/jd.md +27 -0
  265. package/docs/adapters/browser/linkedin.md +6 -0
  266. package/docs/adapters/browser/pixiv.md +92 -0
  267. package/docs/adapters/browser/web.md +30 -0
  268. package/docs/adapters/browser/wikipedia.md +0 -9
  269. package/docs/adapters/browser/xueqiu.md +27 -9
  270. package/docs/adapters/desktop/antigravity.md +0 -3
  271. package/docs/adapters/index.md +11 -9
  272. package/docs/comparison.md +125 -0
  273. package/docs/developer/contributing.md +21 -2
  274. package/docs/developer/testing.md +14 -8
  275. package/docs/developer/ts-adapter.md +18 -0
  276. package/docs/developer/yaml-adapter.md +16 -0
  277. package/docs/guide/plugins.md +10 -0
  278. package/docs/zh/guide/plugins.md +10 -0
  279. package/extension/dist/background.js +519 -444
  280. package/extension/manifest.json +1 -1
  281. package/extension/package.json +1 -1
  282. package/extension/src/background.test.ts +46 -1
  283. package/extension/src/background.ts +108 -33
  284. package/extension/src/cdp.ts +9 -9
  285. package/package.json +3 -2
  286. package/scripts/check-doc-coverage.sh +2 -0
  287. package/src/analysis.ts +170 -0
  288. package/src/browser/cdp.test.ts +66 -0
  289. package/src/browser/cdp.ts +64 -41
  290. package/src/browser/daemon-client.ts +4 -3
  291. package/src/browser/discover.ts +2 -1
  292. package/src/browser/dom-snapshot.test.ts +42 -0
  293. package/src/browser/dom-snapshot.ts +56 -3
  294. package/src/browser/errors.ts +2 -1
  295. package/src/browser/index.ts +3 -2
  296. package/src/browser/mcp.ts +2 -4
  297. package/src/browser/page.ts +43 -35
  298. package/src/browser/stealth.ts +156 -0
  299. package/src/browser.test.ts +51 -1
  300. package/src/build-manifest.test.ts +14 -0
  301. package/src/build-manifest.ts +13 -32
  302. package/src/cascade.ts +5 -3
  303. package/src/cli.ts +66 -34
  304. package/src/clis/_shared/desktop-commands.ts +121 -0
  305. package/src/clis/antigravity/serve.ts +6 -3
  306. package/src/clis/arxiv/search.ts +1 -1
  307. package/src/clis/bilibili/dynamic.test.ts +79 -0
  308. package/src/clis/bilibili/favorite.ts +5 -2
  309. package/src/clis/bilibili/following.ts +3 -2
  310. package/src/clis/bilibili/subtitle.ts +8 -7
  311. package/src/clis/bilibili/utils.ts +2 -2
  312. package/src/clis/boss/batchgreet.ts +1 -1
  313. package/src/clis/boss/chatlist.ts +1 -1
  314. package/src/clis/boss/chatmsg.ts +1 -1
  315. package/src/clis/boss/detail.ts +1 -1
  316. package/src/clis/boss/exchange.ts +1 -1
  317. package/src/clis/boss/greet.ts +1 -1
  318. package/src/clis/boss/invite.ts +1 -1
  319. package/src/clis/boss/joblist.ts +1 -1
  320. package/src/clis/boss/mark.ts +4 -3
  321. package/src/clis/boss/recommend.ts +1 -1
  322. package/src/clis/boss/resume.ts +1 -1
  323. package/src/clis/boss/search.ts +1 -1
  324. package/src/clis/boss/send.ts +5 -4
  325. package/src/clis/boss/stats.ts +1 -1
  326. package/src/clis/chatgpt/ask.ts +5 -0
  327. package/src/clis/chatgpt/new.ts +7 -2
  328. package/src/clis/chatgpt/read.ts +7 -2
  329. package/src/clis/chatgpt/send.ts +3 -2
  330. package/src/clis/chatgpt/status.ts +6 -1
  331. package/src/clis/chatwise/ask.ts +7 -2
  332. package/src/clis/chatwise/export.ts +2 -0
  333. package/src/clis/chatwise/history.ts +2 -0
  334. package/src/clis/chatwise/model.ts +7 -3
  335. package/src/clis/chatwise/new.ts +3 -20
  336. package/src/clis/chatwise/read.ts +2 -0
  337. package/src/clis/chatwise/screenshot.ts +3 -32
  338. package/src/clis/chatwise/send.ts +7 -2
  339. package/src/clis/chatwise/shared.ts +8 -0
  340. package/src/clis/chatwise/status.ts +3 -24
  341. package/src/clis/codex/ask.ts +5 -2
  342. package/src/clis/codex/dump.ts +2 -27
  343. package/src/clis/codex/new.ts +2 -28
  344. package/src/clis/codex/screenshot.ts +2 -32
  345. package/src/clis/codex/send.ts +5 -4
  346. package/src/clis/codex/status.ts +2 -24
  347. package/src/clis/cursor/ask.ts +2 -1
  348. package/src/clis/cursor/composer.ts +2 -1
  349. package/src/clis/cursor/dump.ts +2 -27
  350. package/src/clis/cursor/new.ts +2 -20
  351. package/src/clis/cursor/read.ts +2 -1
  352. package/src/clis/cursor/screenshot.ts +1 -36
  353. package/src/clis/cursor/send.ts +2 -1
  354. package/src/clis/cursor/status.ts +2 -22
  355. package/src/clis/dictionary/examples.yaml +25 -0
  356. package/src/clis/dictionary/search.yaml +27 -0
  357. package/src/clis/dictionary/synonyms.yaml +25 -0
  358. package/src/clis/douban/book-hot.ts +1 -1
  359. package/src/clis/douban/movie-hot.ts +1 -1
  360. package/src/clis/douban/search.ts +1 -1
  361. package/src/clis/douban/utils.ts +165 -1
  362. package/src/clis/doubao/ask.ts +1 -1
  363. package/src/clis/doubao/new.ts +1 -1
  364. package/src/clis/doubao/read.ts +1 -1
  365. package/src/clis/doubao/send.ts +1 -1
  366. package/src/clis/doubao/status.ts +1 -1
  367. package/src/clis/doubao-app/ask.ts +1 -1
  368. package/src/clis/doubao-app/new.ts +1 -1
  369. package/src/clis/doubao-app/read.ts +1 -1
  370. package/src/clis/doubao-app/send.ts +1 -1
  371. package/src/clis/grok/ask.test.ts +25 -0
  372. package/src/clis/grok/ask.ts +25 -12
  373. package/src/clis/jd/item.test.ts +35 -0
  374. package/src/clis/jd/item.ts +101 -0
  375. package/src/clis/jike/feed.ts +1 -1
  376. package/src/clis/jike/search.ts +1 -1
  377. package/src/clis/linkedin/search.ts +5 -4
  378. package/src/clis/linkedin/timeline.test.ts +99 -0
  379. package/src/clis/linkedin/timeline.ts +532 -0
  380. package/src/clis/medium/feed.ts +1 -1
  381. package/src/clis/medium/search.ts +1 -1
  382. package/src/clis/medium/user.ts +1 -1
  383. package/src/clis/medium/{shared.ts → utils.ts} +2 -1
  384. package/src/clis/pixiv/detail.yaml +49 -0
  385. package/src/clis/pixiv/download.test.ts +114 -0
  386. package/src/clis/pixiv/download.ts +91 -0
  387. package/src/clis/pixiv/illusts.test.ts +115 -0
  388. package/src/clis/pixiv/illusts.ts +78 -0
  389. package/src/clis/pixiv/ranking.yaml +53 -0
  390. package/src/clis/pixiv/search.test.ts +97 -0
  391. package/src/clis/pixiv/search.ts +53 -0
  392. package/src/clis/pixiv/test-utils.ts +29 -0
  393. package/src/clis/pixiv/user.yaml +46 -0
  394. package/src/clis/pixiv/utils.ts +62 -0
  395. package/src/clis/reddit/comment.ts +2 -1
  396. package/src/clis/reddit/read.test.ts +34 -0
  397. package/src/clis/reddit/read.ts +4 -3
  398. package/src/clis/reddit/save.ts +2 -1
  399. package/src/clis/reddit/saved.ts +6 -2
  400. package/src/clis/reddit/subscribe.ts +2 -1
  401. package/src/clis/reddit/upvote.ts +2 -1
  402. package/src/clis/reddit/upvoted.ts +6 -2
  403. package/src/clis/sinablog/article.ts +1 -1
  404. package/src/clis/sinablog/hot.ts +1 -1
  405. package/src/clis/sinablog/user.ts +1 -1
  406. package/src/clis/substack/feed.ts +1 -1
  407. package/src/clis/substack/publication.ts +1 -1
  408. package/src/clis/substack/search.ts +3 -2
  409. package/src/clis/substack/{shared.ts → utils.ts} +3 -2
  410. package/src/clis/tiktok/search.yaml +2 -1
  411. package/src/clis/twitter/accept.ts +2 -1
  412. package/src/clis/twitter/article.ts +3 -1
  413. package/src/clis/twitter/block.ts +2 -1
  414. package/src/clis/twitter/bookmark.ts +2 -1
  415. package/src/clis/twitter/bookmarks.ts +3 -2
  416. package/src/clis/twitter/delete.ts +2 -1
  417. package/src/clis/twitter/follow.ts +2 -1
  418. package/src/clis/twitter/followers.ts +3 -2
  419. package/src/clis/twitter/following.ts +3 -2
  420. package/src/clis/twitter/hide-reply.ts +2 -1
  421. package/src/clis/twitter/like.ts +2 -1
  422. package/src/clis/twitter/notifications.ts +2 -1
  423. package/src/clis/twitter/post.ts +2 -1
  424. package/src/clis/twitter/profile.ts +4 -2
  425. package/src/clis/twitter/reply-dm.ts +2 -1
  426. package/src/clis/twitter/reply.ts +2 -1
  427. package/src/clis/twitter/search.test.ts +113 -0
  428. package/src/clis/twitter/search.ts +38 -14
  429. package/src/clis/twitter/thread.ts +2 -2
  430. package/src/clis/twitter/timeline.ts +3 -2
  431. package/src/clis/twitter/trending.ts +3 -2
  432. package/src/clis/twitter/unblock.ts +2 -1
  433. package/src/clis/twitter/unbookmark.ts +2 -1
  434. package/src/clis/twitter/unfollow.ts +2 -1
  435. package/src/clis/v2ex/daily.ts +3 -2
  436. package/src/clis/v2ex/me.ts +3 -2
  437. package/src/clis/v2ex/notifications.ts +3 -4
  438. package/src/clis/web/read.ts +210 -0
  439. package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
  440. package/src/clis/xueqiu/danjuan-utils.ts +176 -0
  441. package/src/clis/xueqiu/fund-holdings.ts +32 -0
  442. package/src/clis/xueqiu/fund-snapshot.ts +27 -0
  443. package/src/clis/youtube/transcript.ts +5 -4
  444. package/src/clis/youtube/video.ts +3 -2
  445. package/src/constants.ts +3 -0
  446. package/src/daemon.ts +7 -5
  447. package/src/discovery.ts +12 -34
  448. package/src/doctor.ts +5 -3
  449. package/src/download/index.test.ts +93 -2
  450. package/src/download/index.ts +44 -23
  451. package/src/download/media-download.ts +5 -3
  452. package/src/engine.test.ts +84 -3
  453. package/src/execution.ts +62 -46
  454. package/src/explore.ts +21 -90
  455. package/src/external-clis.yaml +0 -8
  456. package/src/external.test.ts +9 -0
  457. package/src/external.ts +12 -10
  458. package/src/generate.ts +4 -41
  459. package/src/hooks.test.ts +126 -0
  460. package/src/hooks.ts +90 -0
  461. package/src/interceptor.ts +73 -23
  462. package/src/main.ts +2 -0
  463. package/src/output.ts +14 -6
  464. package/src/pipeline/executor.ts +1 -1
  465. package/src/pipeline/steps/browser.ts +1 -3
  466. package/src/pipeline/steps/download.test.ts +136 -0
  467. package/src/pipeline/steps/download.ts +47 -34
  468. package/src/pipeline/steps/fetch.test.ts +179 -0
  469. package/src/pipeline/steps/fetch.ts +39 -23
  470. package/src/pipeline/steps/transform.ts +2 -6
  471. package/src/pipeline/template.test.ts +28 -0
  472. package/src/pipeline/template.ts +67 -79
  473. package/src/pipeline/transform.test.ts +20 -0
  474. package/src/plugin.test.ts +251 -3
  475. package/src/plugin.ts +265 -21
  476. package/src/record.ts +12 -84
  477. package/src/registry-api.ts +2 -0
  478. package/src/registry.ts +7 -4
  479. package/src/runtime.ts +14 -4
  480. package/src/snapshotFormatter.ts +43 -121
  481. package/src/utils.ts +39 -0
  482. package/src/validate.ts +3 -6
  483. package/src/yaml-schema.ts +28 -0
  484. package/tests/e2e/browser-auth.test.ts +25 -0
  485. package/tests/e2e/plugin-management.test.ts +137 -0
  486. package/tests/e2e/public-commands.test.ts +34 -1
  487. package/vitest.config.ts +19 -1
  488. package/.github/workflows/pkg-pr-new.yml +0 -30
  489. package/dist/clis/douban/shared.d.ts +0 -4
  490. package/dist/clis/douban/shared.js +0 -155
  491. package/src/clis/douban/shared.ts +0 -165
  492. /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
  493. /package/dist/clis/boss/{common.js → utils.js} +0 -0
  494. /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
  495. /package/dist/clis/doubao/{common.js → utils.js} +0 -0
  496. /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
  497. /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
  498. /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
  499. /package/dist/clis/jike/{shared.js → utils.js} +0 -0
  500. /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
  501. /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
  502. /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
  503. /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
  504. /package/src/clis/boss/{common.ts → utils.ts} +0 -0
  505. /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
  506. /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
  507. /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
  508. /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
@@ -51,6 +51,31 @@ describe('evalExpr', () => {
51
51
  it('evaluates || with truthy left', () => {
52
52
  expect(evalExpr("item.name || 'N/A'", { item: { name: 'Alice' } })).toBe('Alice');
53
53
  });
54
+ it('evaluates chained || fallback (issue #303)', () => {
55
+ // When first two are falsy, should evaluate through to the string literal
56
+ expect(evalExpr("item.a || item.b || 'default'", { item: {} })).toBe('default');
57
+ });
58
+ it('evaluates chained || with middle value truthy', () => {
59
+ expect(evalExpr("item.a || item.b || 'default'", { item: { b: 'middle' } })).toBe('middle');
60
+ });
61
+ it('evaluates chained || with first value truthy', () => {
62
+ expect(evalExpr("item.a || item.b || 'default'", { item: { a: 'first', b: 'middle' } })).toBe('first');
63
+ });
64
+ it('evaluates || with 0 as falsy left (JS semantics)', () => {
65
+ expect(evalExpr("item.count || 'N/A'", { item: { count: 0 } })).toBe('N/A');
66
+ });
67
+ it('evaluates || with empty string as falsy left', () => {
68
+ expect(evalExpr("item.name || 'unknown'", { item: { name: '' } })).toBe('unknown');
69
+ });
70
+ it('evaluates || with numeric fallback returning number type', () => {
71
+ expect(evalExpr('item.a || 42', { item: {} })).toBe(42);
72
+ });
73
+ it('evaluates 4-way chained ||', () => {
74
+ expect(evalExpr("item.a || item.b || item.c || 'last'", { item: { c: 'third' } })).toBe('third');
75
+ });
76
+ it('handles || combined with pipe filter', () => {
77
+ expect(evalExpr("item.a || item.b | upper", { item: { b: 'hello' } })).toBe('HELLO');
78
+ });
54
79
  it('resolves simple path', () => {
55
80
  expect(evalExpr('item.title', { item: { title: 'Test' } })).toBe('Test');
56
81
  });
@@ -63,6 +88,9 @@ describe('evalExpr', () => {
63
88
  it('evaluates method calls on values', () => {
64
89
  expect(evalExpr("args.username.startsWith('@') ? args.username : '@' + args.username", { args: { username: 'alice' } })).toBe('@alice');
65
90
  });
91
+ it('rejects constructor-based sandbox escapes', () => {
92
+ expect(evalExpr("args['cons' + 'tructor']['constructor']('return process')()", { args: {} })).toBeUndefined();
93
+ });
66
94
  it('applies join filter', () => {
67
95
  expect(evalExpr('item.tags | join(,)', { item: { tags: ['a', 'b', 'c'] } })).toBe('a,b,c');
68
96
  });
@@ -85,6 +85,24 @@ describe('stepSort', () => {
85
85
  await stepSort(null, 'score', SAMPLE_DATA, {});
86
86
  expect(SAMPLE_DATA).toEqual(original);
87
87
  });
88
+ it('sorts string-encoded numbers naturally by default', async () => {
89
+ const data = [
90
+ { name: 'A', volume: '99' },
91
+ { name: 'B', volume: '1000' },
92
+ { name: 'C', volume: '250' },
93
+ ];
94
+ const result = await stepSort(null, { by: 'volume', order: 'desc' }, data, {});
95
+ expect(result.map((r) => r.name)).toEqual(['B', 'C', 'A']);
96
+ });
97
+ it('handles missing fields gracefully', async () => {
98
+ const data = [
99
+ { name: 'A', value: '10' },
100
+ { name: 'B' },
101
+ { name: 'C', value: '5' },
102
+ ];
103
+ const result = await stepSort(null, { by: 'value', order: 'asc' }, data, {});
104
+ expect(result.map((r) => r.name)).toEqual(['B', 'C', 'A']);
105
+ });
88
106
  });
89
107
  describe('stepLimit', () => {
90
108
  it('limits array to N items', async () => {
package/dist/plugin.d.ts CHANGED
@@ -4,12 +4,37 @@
4
4
  * Plugins live in ~/.opencli/plugins/<name>/.
5
5
  * Install source format: "github:user/repo"
6
6
  */
7
+ /** Path to the lock file that tracks installed plugin versions. */
8
+ export declare function getLockFilePath(): string;
9
+ export declare const LOCK_FILE: string;
10
+ export interface LockEntry {
11
+ source: string;
12
+ commitHash: string;
13
+ installedAt: string;
14
+ updatedAt?: string;
15
+ }
7
16
  export interface PluginInfo {
8
17
  name: string;
9
18
  path: string;
10
19
  commands: string[];
11
20
  source?: string;
21
+ version?: string;
22
+ installedAt?: string;
23
+ }
24
+ export interface ValidationResult {
25
+ valid: boolean;
26
+ errors: string[];
12
27
  }
28
+ export declare function readLockFile(): Record<string, LockEntry>;
29
+ export declare function writeLockFile(lock: Record<string, LockEntry>): void;
30
+ /** Get the HEAD commit hash of a git repo directory. */
31
+ export declare function getCommitHash(dir: string): string | undefined;
32
+ /**
33
+ * Validate that a downloaded plugin directory is a structurally valid plugin.
34
+ * Checks for at least one command file (.yaml, .yml, .ts, .js) and a valid
35
+ * package.json if it contains .ts files.
36
+ */
37
+ export declare function validatePluginStructure(pluginDir: string): ValidationResult;
13
38
  /**
14
39
  * Install a plugin from a source.
15
40
  * Currently supports "github:user/repo" format (git clone wrapper).
@@ -23,6 +48,16 @@ export declare function uninstallPlugin(name: string): void;
23
48
  * Update a plugin by name (git pull + re-install lifecycle).
24
49
  */
25
50
  export declare function updatePlugin(name: string): void;
51
+ export interface UpdateResult {
52
+ name: string;
53
+ success: boolean;
54
+ error?: string;
55
+ }
56
+ /**
57
+ * Update all installed plugins.
58
+ * Continues even if individual plugin updates fail.
59
+ */
60
+ export declare function updateAllPlugins(): UpdateResult[];
26
61
  /**
27
62
  * List all installed plugins.
28
63
  */
@@ -32,4 +67,8 @@ declare function parseSource(source: string): {
32
67
  cloneUrl: string;
33
68
  name: string;
34
69
  } | null;
35
- export { parseSource as _parseSource };
70
+ /**
71
+ * Resolve the path to the esbuild CLI executable with fallback strategies.
72
+ */
73
+ export declare function resolveEsbuildBin(): string | null;
74
+ export { resolveEsbuildBin as _resolveEsbuildBin, getCommitHash as _getCommitHash, parseSource as _parseSource, readLockFile as _readLockFile, updateAllPlugins as _updateAllPlugins, validatePluginStructure as _validatePluginStructure, writeLockFile as _writeLockFile, };
package/dist/plugin.js CHANGED
@@ -5,10 +5,88 @@
5
5
  * Install source format: "github:user/repo"
6
6
  */
7
7
  import * as fs from 'node:fs';
8
+ import * as os from 'node:os';
8
9
  import * as path from 'node:path';
9
10
  import { execSync, execFileSync } from 'node:child_process';
11
+ import { fileURLToPath } from 'node:url';
10
12
  import { PLUGINS_DIR } from './discovery.js';
13
+ import { getErrorMessage } from './errors.js';
11
14
  import { log } from './logger.js';
15
+ const isWindows = process.platform === 'win32';
16
+ /** Get home directory, respecting HOME environment variable for test isolation. */
17
+ function getHomeDir() {
18
+ return process.env.HOME || process.env.USERPROFILE || os.homedir();
19
+ }
20
+ /** Path to the lock file that tracks installed plugin versions. */
21
+ export function getLockFilePath() {
22
+ return path.join(getHomeDir(), '.opencli', 'plugins.lock.json');
23
+ }
24
+ // Legacy const for backward compatibility (computed at load time)
25
+ export const LOCK_FILE = path.join(os.homedir(), '.opencli', 'plugins.lock.json');
26
+ // ── Lock file helpers ───────────────────────────────────────────────────────
27
+ export function readLockFile() {
28
+ try {
29
+ const raw = fs.readFileSync(getLockFilePath(), 'utf-8');
30
+ return JSON.parse(raw);
31
+ }
32
+ catch {
33
+ return {};
34
+ }
35
+ }
36
+ export function writeLockFile(lock) {
37
+ const lockPath = getLockFilePath();
38
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
39
+ fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
40
+ }
41
+ /** Get the HEAD commit hash of a git repo directory. */
42
+ export function getCommitHash(dir) {
43
+ try {
44
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
45
+ cwd: dir,
46
+ encoding: 'utf-8',
47
+ stdio: ['pipe', 'pipe', 'pipe'],
48
+ }).trim();
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ }
54
+ /**
55
+ * Validate that a downloaded plugin directory is a structurally valid plugin.
56
+ * Checks for at least one command file (.yaml, .yml, .ts, .js) and a valid
57
+ * package.json if it contains .ts files.
58
+ */
59
+ export function validatePluginStructure(pluginDir) {
60
+ const errors = [];
61
+ if (!fs.existsSync(pluginDir)) {
62
+ return { valid: false, errors: ['Plugin directory does not exist'] };
63
+ }
64
+ const files = fs.readdirSync(pluginDir);
65
+ const hasYaml = files.some(f => f.endsWith('.yaml') || f.endsWith('.yml'));
66
+ const hasTs = files.some(f => f.endsWith('.ts') && !f.endsWith('.d.ts') && !f.endsWith('.test.ts'));
67
+ const hasJs = files.some(f => f.endsWith('.js') && !f.endsWith('.d.js'));
68
+ if (!hasYaml && !hasTs && !hasJs) {
69
+ errors.push(`No command files found in plugin directory. A plugin must contain at least one .yaml, .ts, or .js command file.`);
70
+ }
71
+ if (hasTs) {
72
+ const pkgJsonPath = path.join(pluginDir, 'package.json');
73
+ if (!fs.existsSync(pkgJsonPath)) {
74
+ errors.push(`Plugin contains .ts files but no package.json. A package.json with "type": "module" and "@jackwener/opencli" peer dependency is required for TS plugins.`);
75
+ }
76
+ else {
77
+ try {
78
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
79
+ if (pkg.type !== 'module') {
80
+ errors.push(`Plugin package.json must have "type": "module" for TypeScript plugins.`);
81
+ }
82
+ }
83
+ catch {
84
+ errors.push(`Plugin package.json is malformed or invalid JSON.`);
85
+ }
86
+ }
87
+ }
88
+ return { valid: errors.length === 0, errors };
89
+ }
12
90
  /**
13
91
  * Shared post-install lifecycle: npm install → host symlink → TS transpile.
14
92
  * Called by both installPlugin() and updatePlugin().
@@ -22,10 +100,11 @@ function postInstallLifecycle(pluginDir) {
22
100
  cwd: pluginDir,
23
101
  encoding: 'utf-8',
24
102
  stdio: ['pipe', 'pipe', 'pipe'],
103
+ ...(isWindows && { shell: true }),
25
104
  });
26
105
  }
27
- catch {
28
- // Non-fatal: npm install may fail if no real deps
106
+ catch (err) {
107
+ console.error(`[plugin] npm install failed in ${pluginDir}: ${err instanceof Error ? err.message : err}`);
29
108
  }
30
109
  // Symlink host opencli so TS plugins resolve '@jackwener/opencli/registry'
31
110
  // against the running host, not a stale npm-published version.
@@ -59,9 +138,25 @@ export function installPlugin(source) {
59
138
  });
60
139
  }
61
140
  catch (err) {
62
- throw new Error(`Failed to clone plugin: ${err.message}`);
141
+ throw new Error(`Failed to clone plugin: ${getErrorMessage(err)}`);
142
+ }
143
+ const validation = validatePluginStructure(targetDir);
144
+ if (!validation.valid) {
145
+ // If validation fails, clean up the cloned directory and abort
146
+ fs.rmSync(targetDir, { recursive: true, force: true });
147
+ throw new Error(`Invalid plugin structure:\n- ${validation.errors.join('\n- ')}`);
63
148
  }
64
149
  postInstallLifecycle(targetDir);
150
+ const commitHash = getCommitHash(targetDir);
151
+ if (commitHash) {
152
+ const lock = readLockFile();
153
+ lock[name] = {
154
+ source: cloneUrl,
155
+ commitHash,
156
+ installedAt: new Date().toISOString(),
157
+ };
158
+ writeLockFile(lock);
159
+ }
65
160
  return name;
66
161
  }
67
162
  /**
@@ -73,6 +168,11 @@ export function uninstallPlugin(name) {
73
168
  throw new Error(`Plugin "${name}" is not installed.`);
74
169
  }
75
170
  fs.rmSync(targetDir, { recursive: true, force: true });
171
+ const lock = readLockFile();
172
+ if (lock[name]) {
173
+ delete lock[name];
174
+ writeLockFile(lock);
175
+ }
76
176
  }
77
177
  /**
78
178
  * Update a plugin by name (git pull + re-install lifecycle).
@@ -90,9 +190,44 @@ export function updatePlugin(name) {
90
190
  });
91
191
  }
92
192
  catch (err) {
93
- throw new Error(`Failed to update plugin: ${err.message}`);
193
+ throw new Error(`Failed to update plugin: ${getErrorMessage(err)}`);
194
+ }
195
+ const validation = validatePluginStructure(targetDir);
196
+ if (!validation.valid) {
197
+ log.warn(`Plugin "${name}" updated, but structure is now invalid:\n- ${validation.errors.join('\n- ')}`);
94
198
  }
95
199
  postInstallLifecycle(targetDir);
200
+ const commitHash = getCommitHash(targetDir);
201
+ if (commitHash) {
202
+ const lock = readLockFile();
203
+ const existing = lock[name];
204
+ lock[name] = {
205
+ source: existing?.source ?? getPluginSource(targetDir) ?? '',
206
+ commitHash,
207
+ installedAt: existing?.installedAt ?? new Date().toISOString(),
208
+ updatedAt: new Date().toISOString(),
209
+ };
210
+ writeLockFile(lock);
211
+ }
212
+ }
213
+ /**
214
+ * Update all installed plugins.
215
+ * Continues even if individual plugin updates fail.
216
+ */
217
+ export function updateAllPlugins() {
218
+ return listPlugins().map((plugin) => {
219
+ try {
220
+ updatePlugin(plugin.name);
221
+ return { name: plugin.name, success: true };
222
+ }
223
+ catch (err) {
224
+ return {
225
+ name: plugin.name,
226
+ success: false,
227
+ error: getErrorMessage(err),
228
+ };
229
+ }
230
+ });
96
231
  }
97
232
  /**
98
233
  * List all installed plugins.
@@ -101,6 +236,7 @@ export function listPlugins() {
101
236
  if (!fs.existsSync(PLUGINS_DIR))
102
237
  return [];
103
238
  const entries = fs.readdirSync(PLUGINS_DIR, { withFileTypes: true });
239
+ const lock = readLockFile();
104
240
  const plugins = [];
105
241
  for (const entry of entries) {
106
242
  if (!entry.isDirectory())
@@ -108,11 +244,14 @@ export function listPlugins() {
108
244
  const pluginDir = path.join(PLUGINS_DIR, entry.name);
109
245
  const commands = scanPluginCommands(pluginDir);
110
246
  const source = getPluginSource(pluginDir);
247
+ const lockEntry = lock[entry.name];
111
248
  plugins.push({
112
249
  name: entry.name,
113
250
  path: pluginDir,
114
251
  commands,
115
252
  source,
253
+ version: lockEntry?.commitHash?.slice(0, 7),
254
+ installedAt: lockEntry?.installedAt,
116
255
  });
117
256
  }
118
257
  return plugins;
@@ -135,7 +274,7 @@ function scanPluginCommands(dir) {
135
274
  /** Get git remote origin URL */
136
275
  function getPluginSource(dir) {
137
276
  try {
138
- return execSync('git config --get remote.origin.url', {
277
+ return execFileSync('git', ['config', '--get', 'remote.origin.url'], {
139
278
  cwd: dir,
140
279
  encoding: 'utf-8',
141
280
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -179,7 +318,7 @@ function linkHostOpencli(pluginDir) {
179
318
  // Determine the host opencli package root from this module's location.
180
319
  // Both dev (tsx src/plugin.ts) and prod (node dist/plugin.js) are one level
181
320
  // deep, so path.dirname + '..' always gives us the package root.
182
- const thisFile = new URL(import.meta.url).pathname;
321
+ const thisFile = fileURLToPath(import.meta.url);
183
322
  const hostRoot = path.resolve(path.dirname(thisFile), '..');
184
323
  const targetLink = path.join(pluginDir, 'node_modules', '@jackwener', 'opencli');
185
324
  // Remove existing (npm-installed copy or stale symlink)
@@ -188,13 +327,73 @@ function linkHostOpencli(pluginDir) {
188
327
  }
189
328
  // Ensure parent directory exists
190
329
  fs.mkdirSync(path.dirname(targetLink), { recursive: true });
191
- // Create symlink
192
- fs.symlinkSync(hostRoot, targetLink, 'dir');
330
+ // Use 'junction' on Windows (doesn't require admin privileges),
331
+ // 'dir' symlink on other platforms.
332
+ const linkType = isWindows ? 'junction' : 'dir';
333
+ fs.symlinkSync(hostRoot, targetLink, linkType);
193
334
  log.debug(`Linked host opencli into plugin: ${targetLink} → ${hostRoot}`);
194
335
  }
195
336
  catch (err) {
196
- log.warn(`Failed to link host opencli into plugin: ${err.message}`);
337
+ log.warn(`Failed to link host opencli into plugin: ${getErrorMessage(err)}`);
338
+ }
339
+ }
340
+ /**
341
+ * Resolve the path to the esbuild CLI executable with fallback strategies.
342
+ */
343
+ export function resolveEsbuildBin() {
344
+ const thisFile = fileURLToPath(import.meta.url);
345
+ const hostRoot = path.resolve(path.dirname(thisFile), '..');
346
+ // Strategy 1 (Windows): prefer the .cmd wrapper which is executable via shell
347
+ if (isWindows) {
348
+ const cmdPath = path.join(hostRoot, 'node_modules', '.bin', 'esbuild.cmd');
349
+ if (fs.existsSync(cmdPath)) {
350
+ return cmdPath;
351
+ }
352
+ }
353
+ // Strategy 2: resolve esbuild binary via import.meta.resolve
354
+ // (On Unix, shebang scripts are directly executable; on Windows they are not,
355
+ // so this strategy is skipped on Windows in favour of the .cmd wrapper above.)
356
+ if (!isWindows) {
357
+ try {
358
+ const pkgUrl = import.meta.resolve('esbuild/package.json');
359
+ if (pkgUrl.startsWith('file://')) {
360
+ const pkgPath = fileURLToPath(pkgUrl);
361
+ const pkgRaw = fs.readFileSync(pkgPath, 'utf8');
362
+ const pkg = JSON.parse(pkgRaw);
363
+ if (pkg.bin && typeof pkg.bin === 'object' && pkg.bin.esbuild) {
364
+ const binPath = path.resolve(path.dirname(pkgPath), pkg.bin.esbuild);
365
+ if (fs.existsSync(binPath))
366
+ return binPath;
367
+ }
368
+ else if (typeof pkg.bin === 'string') {
369
+ const binPath = path.resolve(path.dirname(pkgPath), pkg.bin);
370
+ if (fs.existsSync(binPath))
371
+ return binPath;
372
+ }
373
+ }
374
+ }
375
+ catch {
376
+ // ignore package resolution failures
377
+ }
378
+ }
379
+ // Strategy 3: fallback to node_modules/.bin/esbuild (Unix)
380
+ const binFallback = path.join(hostRoot, 'node_modules', '.bin', 'esbuild');
381
+ if (fs.existsSync(binFallback)) {
382
+ return binFallback;
197
383
  }
384
+ // Strategy 4: global esbuild in PATH
385
+ try {
386
+ const lookupCmd = isWindows ? 'where esbuild' : 'which esbuild';
387
+ // `where` on Windows may return multiple lines; take only the first match.
388
+ const globalBin = execSync(lookupCmd, { encoding: 'utf-8', stdio: 'pipe' }).trim().split('\n')[0].trim();
389
+ if (globalBin && fs.existsSync(globalBin)) {
390
+ return globalBin;
391
+ }
392
+ }
393
+ catch {
394
+ // ignore PATH lookup failures
395
+ }
396
+ return null;
198
397
  }
199
398
  /**
200
399
  * Transpile TS plugin files to JS so they work in production mode.
@@ -202,12 +401,9 @@ function linkHostOpencli(pluginDir) {
202
401
  */
203
402
  function transpilePluginTs(pluginDir) {
204
403
  try {
205
- // Resolve esbuild binary from the host opencli's node_modules
206
- const thisFile = new URL(import.meta.url).pathname;
207
- const hostRoot = path.resolve(path.dirname(thisFile), '..');
208
- const esbuildBin = path.join(hostRoot, 'node_modules', '.bin', 'esbuild');
209
- if (!fs.existsSync(esbuildBin)) {
210
- log.debug('esbuild not found in host node_modules, skipping TS transpilation');
404
+ const esbuildBin = resolveEsbuildBin();
405
+ if (!esbuildBin) {
406
+ log.debug('esbuild not found in host node_modules, via resolve, or in PATH, skipping TS transpilation');
211
407
  return;
212
408
  }
213
409
  const files = fs.readdirSync(pluginDir);
@@ -223,11 +419,12 @@ function transpilePluginTs(pluginDir) {
223
419
  cwd: pluginDir,
224
420
  encoding: 'utf-8',
225
421
  stdio: ['pipe', 'pipe', 'pipe'],
422
+ ...(isWindows && { shell: true }),
226
423
  });
227
424
  log.debug(`Transpiled plugin file: ${tsFile} → ${jsFile}`);
228
425
  }
229
426
  catch (err) {
230
- log.warn(`Failed to transpile ${tsFile}: ${err.message}`);
427
+ log.warn(`Failed to transpile ${tsFile}: ${getErrorMessage(err)}`);
231
428
  }
232
429
  }
233
430
  }
@@ -235,4 +432,4 @@ function transpilePluginTs(pluginDir) {
235
432
  // Non-fatal: skip transpilation if anything goes wrong
236
433
  }
237
434
  }
238
- export { parseSource as _parseSource };
435
+ export { resolveEsbuildBin as _resolveEsbuildBin, getCommitHash as _getCommitHash, parseSource as _parseSource, readLockFile as _readLockFile, updateAllPlugins as _updateAllPlugins, validatePluginStructure as _validatePluginStructure, writeLockFile as _writeLockFile, };
@@ -1,4 +1,4 @@
1
1
  /**
2
- * Tests for plugin management: install, uninstall, list.
2
+ * Tests for plugin management: install, uninstall, list, and lock file support.
3
3
  */
4
4
  export {};