@agentrhq/webcmd 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. package/README.md +317 -24
  2. package/dist/src/adapter-shadow.d.ts +11 -0
  3. package/dist/src/adapter-shadow.js +72 -0
  4. package/dist/src/adapter-shadow.test.d.ts +1 -0
  5. package/dist/src/adapter-shadow.test.js +49 -0
  6. package/dist/src/adapter-source.d.ts +11 -0
  7. package/dist/src/adapter-source.js +24 -0
  8. package/dist/src/adapter-source.test.d.ts +1 -0
  9. package/dist/src/adapter-source.test.js +29 -0
  10. package/dist/src/brand.d.ts +9 -0
  11. package/dist/src/brand.js +9 -0
  12. package/dist/src/brand.test.d.ts +1 -0
  13. package/dist/src/brand.test.js +15 -0
  14. package/dist/src/browser/analyze.d.ts +118 -0
  15. package/dist/src/browser/analyze.js +405 -0
  16. package/dist/src/browser/analyze.test.d.ts +1 -0
  17. package/dist/src/browser/analyze.test.js +209 -0
  18. package/dist/src/browser/article-extract.d.ts +57 -0
  19. package/dist/src/browser/article-extract.e2e.test.d.ts +1 -0
  20. package/dist/src/browser/article-extract.e2e.test.js +105 -0
  21. package/dist/src/browser/article-extract.js +169 -0
  22. package/dist/src/browser/article-extract.test.d.ts +1 -0
  23. package/dist/src/browser/article-extract.test.js +94 -0
  24. package/dist/src/browser/ax-snapshot.d.ts +37 -0
  25. package/dist/src/browser/ax-snapshot.js +217 -0
  26. package/dist/src/browser/ax-snapshot.test.d.ts +1 -0
  27. package/dist/src/browser/ax-snapshot.test.js +91 -0
  28. package/dist/src/browser/base-page.d.ts +151 -0
  29. package/dist/src/browser/base-page.js +1060 -0
  30. package/dist/src/browser/base-page.test.d.ts +1 -0
  31. package/dist/src/browser/base-page.test.js +803 -0
  32. package/dist/src/browser/bridge-readiness.d.ts +14 -0
  33. package/dist/src/browser/bridge-readiness.js +24 -0
  34. package/dist/src/browser/bridge-readiness.test.d.ts +1 -0
  35. package/dist/src/browser/bridge-readiness.test.js +58 -0
  36. package/dist/src/browser/bridge.d.ts +26 -0
  37. package/dist/src/browser/bridge.js +58 -0
  38. package/dist/src/browser/cdp-click-fixture.test.d.ts +1 -0
  39. package/dist/src/browser/cdp-click-fixture.test.js +87 -0
  40. package/dist/src/browser/cdp.d.ts +46 -0
  41. package/dist/src/browser/cdp.js +536 -0
  42. package/dist/src/browser/cdp.test.d.ts +1 -0
  43. package/dist/src/browser/cdp.test.js +79 -0
  44. package/dist/src/browser/compound.d.ts +59 -0
  45. package/dist/src/browser/compound.js +112 -0
  46. package/dist/src/browser/compound.test.d.ts +1 -0
  47. package/dist/src/browser/compound.test.js +175 -0
  48. package/dist/src/browser/config.d.ts +2 -0
  49. package/dist/src/browser/config.js +14 -0
  50. package/dist/src/browser/daemon-client.d.ts +32 -0
  51. package/dist/src/browser/daemon-client.js +135 -0
  52. package/dist/src/browser/daemon-client.test.d.ts +1 -0
  53. package/dist/src/browser/daemon-client.test.js +326 -0
  54. package/dist/src/browser/daemon-lifecycle.d.ts +37 -0
  55. package/dist/src/browser/daemon-lifecycle.js +147 -0
  56. package/dist/src/browser/daemon-transport.d.ts +54 -0
  57. package/dist/src/browser/daemon-transport.js +68 -0
  58. package/dist/src/browser/daemon-version.d.ts +4 -0
  59. package/dist/src/browser/daemon-version.js +12 -0
  60. package/dist/src/browser/dom-helpers.d.ts +41 -0
  61. package/dist/src/browser/dom-helpers.js +239 -0
  62. package/dist/src/browser/dom-helpers.test.d.ts +1 -0
  63. package/dist/src/browser/dom-helpers.test.js +106 -0
  64. package/dist/src/browser/dom-snapshot.d.ts +93 -0
  65. package/dist/src/browser/dom-snapshot.js +894 -0
  66. package/dist/src/browser/dom-snapshot.test.d.ts +11 -0
  67. package/dist/src/browser/dom-snapshot.test.js +313 -0
  68. package/dist/src/browser/errors.d.ts +38 -0
  69. package/dist/src/browser/errors.js +79 -0
  70. package/dist/src/browser/errors.test.d.ts +1 -0
  71. package/dist/src/browser/errors.test.js +60 -0
  72. package/dist/src/browser/extract.d.ts +69 -0
  73. package/dist/src/browser/extract.js +132 -0
  74. package/dist/src/browser/extract.test.d.ts +1 -0
  75. package/dist/src/browser/extract.test.js +129 -0
  76. package/dist/src/browser/find.d.ts +84 -0
  77. package/dist/src/browser/find.js +398 -0
  78. package/dist/src/browser/find.test.d.ts +1 -0
  79. package/dist/src/browser/find.test.js +180 -0
  80. package/dist/src/browser/html-tree.d.ts +75 -0
  81. package/dist/src/browser/html-tree.js +112 -0
  82. package/dist/src/browser/html-tree.test.d.ts +1 -0
  83. package/dist/src/browser/html-tree.test.js +181 -0
  84. package/dist/src/browser/index.d.ts +14 -0
  85. package/dist/src/browser/index.js +12 -0
  86. package/dist/src/browser/network-cache.d.ts +49 -0
  87. package/dist/src/browser/network-cache.js +78 -0
  88. package/dist/src/browser/network-cache.test.d.ts +1 -0
  89. package/dist/src/browser/network-cache.test.js +75 -0
  90. package/dist/src/browser/network-interceptor.d.ts +11 -0
  91. package/dist/src/browser/network-interceptor.js +11 -0
  92. package/dist/src/browser/network-key.d.ts +22 -0
  93. package/dist/src/browser/network-key.js +66 -0
  94. package/dist/src/browser/network-key.test.d.ts +1 -0
  95. package/dist/src/browser/network-key.test.js +49 -0
  96. package/dist/src/browser/page.d.ts +83 -0
  97. package/dist/src/browser/page.js +453 -0
  98. package/dist/src/browser/page.test.d.ts +1 -0
  99. package/dist/src/browser/page.test.js +406 -0
  100. package/dist/src/browser/profile.d.ts +14 -0
  101. package/dist/src/browser/profile.js +86 -0
  102. package/dist/src/browser/protocol.d.ts +63 -0
  103. package/dist/src/browser/protocol.js +1 -0
  104. package/dist/src/browser/runtime/local-cloak/actions.d.ts +3 -0
  105. package/dist/src/browser/runtime/local-cloak/actions.js +240 -0
  106. package/dist/src/browser/runtime/local-cloak/downloads.d.ts +3 -0
  107. package/dist/src/browser/runtime/local-cloak/downloads.js +28 -0
  108. package/dist/src/browser/runtime/local-cloak/network.d.ts +28 -0
  109. package/dist/src/browser/runtime/local-cloak/network.js +120 -0
  110. package/dist/src/browser/runtime/local-cloak/network.test.d.ts +1 -0
  111. package/dist/src/browser/runtime/local-cloak/network.test.js +124 -0
  112. package/dist/src/browser/runtime/local-cloak/profiles.d.ts +6 -0
  113. package/dist/src/browser/runtime/local-cloak/profiles.js +17 -0
  114. package/dist/src/browser/runtime/local-cloak/profiles.test.d.ts +1 -0
  115. package/dist/src/browser/runtime/local-cloak/profiles.test.js +19 -0
  116. package/dist/src/browser/runtime/local-cloak/provider.d.ts +15 -0
  117. package/dist/src/browser/runtime/local-cloak/provider.js +27 -0
  118. package/dist/src/browser/runtime/local-cloak/provider.test.d.ts +1 -0
  119. package/dist/src/browser/runtime/local-cloak/provider.test.js +244 -0
  120. package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +77 -0
  121. package/dist/src/browser/runtime/local-cloak/session-manager.js +280 -0
  122. package/dist/src/browser/runtime/local-cloak/session-manager.test.d.ts +1 -0
  123. package/dist/src/browser/runtime/local-cloak/session-manager.test.js +92 -0
  124. package/dist/src/browser/runtime/provider.d.ts +9 -0
  125. package/dist/src/browser/runtime/provider.js +1 -0
  126. package/dist/src/browser/runtime/provider.test.d.ts +2 -0
  127. package/dist/src/browser/runtime/provider.test.js +49 -0
  128. package/dist/src/browser/shape-filter.d.ts +52 -0
  129. package/dist/src/browser/shape-filter.js +101 -0
  130. package/dist/src/browser/shape-filter.test.d.ts +1 -0
  131. package/dist/src/browser/shape-filter.test.js +101 -0
  132. package/dist/src/browser/shape.d.ts +23 -0
  133. package/dist/src/browser/shape.js +95 -0
  134. package/dist/src/browser/shape.test.d.ts +1 -0
  135. package/dist/src/browser/shape.test.js +82 -0
  136. package/dist/src/browser/stealth.d.ts +11 -0
  137. package/dist/src/browser/stealth.js +359 -0
  138. package/dist/src/browser/stealth.test.d.ts +1 -0
  139. package/dist/src/browser/stealth.test.js +134 -0
  140. package/dist/src/browser/tabs.d.ts +13 -0
  141. package/dist/src/browser/tabs.js +70 -0
  142. package/dist/src/browser/target-errors.d.ts +39 -0
  143. package/dist/src/browser/target-errors.js +45 -0
  144. package/dist/src/browser/target-errors.test.d.ts +1 -0
  145. package/dist/src/browser/target-errors.test.js +94 -0
  146. package/dist/src/browser/target-resolver.d.ts +158 -0
  147. package/dist/src/browser/target-resolver.js +570 -0
  148. package/dist/src/browser/target-resolver.test.d.ts +1 -0
  149. package/dist/src/browser/target-resolver.test.js +118 -0
  150. package/dist/src/browser/utils.d.ts +20 -0
  151. package/dist/src/browser/utils.js +64 -0
  152. package/dist/src/browser/utils.test.d.ts +1 -0
  153. package/dist/src/browser/utils.test.js +29 -0
  154. package/dist/src/browser/verify-fixture.d.ts +65 -0
  155. package/dist/src/browser/verify-fixture.js +318 -0
  156. package/dist/src/browser/verify-fixture.test.d.ts +1 -0
  157. package/dist/src/browser/verify-fixture.test.js +219 -0
  158. package/dist/src/browser/visual-refs.d.ts +11 -0
  159. package/dist/src/browser/visual-refs.js +108 -0
  160. package/dist/src/browser.test.d.ts +1 -0
  161. package/dist/src/browser.test.js +297 -0
  162. package/dist/src/build-manifest.d.ts +103 -0
  163. package/dist/src/build-manifest.js +363 -0
  164. package/dist/src/build-manifest.test.d.ts +1 -0
  165. package/dist/src/build-manifest.test.js +341 -0
  166. package/dist/src/capabilityRouting.d.ts +19 -0
  167. package/dist/src/capabilityRouting.js +56 -0
  168. package/dist/src/capabilityRouting.test.d.ts +1 -0
  169. package/dist/src/capabilityRouting.test.js +63 -0
  170. package/dist/src/cli-argv-preprocess.d.ts +63 -0
  171. package/dist/src/cli-argv-preprocess.js +303 -0
  172. package/dist/src/cli-argv-preprocess.test.d.ts +1 -0
  173. package/dist/src/cli-argv-preprocess.test.js +304 -0
  174. package/dist/src/cli.d.ts +79 -0
  175. package/dist/src/cli.js +3350 -0
  176. package/dist/src/cli.test.d.ts +1 -0
  177. package/dist/src/cli.test.js +3041 -0
  178. package/dist/src/commanderAdapter.d.ts +21 -0
  179. package/dist/src/commanderAdapter.js +208 -0
  180. package/dist/src/commanderAdapter.test.d.ts +1 -0
  181. package/dist/src/commanderAdapter.test.js +352 -0
  182. package/dist/src/commands/auth.d.ts +40 -0
  183. package/dist/src/commands/auth.js +434 -0
  184. package/dist/src/commands/auth.test.d.ts +1 -0
  185. package/dist/src/commands/auth.test.js +252 -0
  186. package/dist/src/commands/daemon.d.ts +9 -0
  187. package/dist/src/commands/daemon.js +91 -0
  188. package/dist/src/commands/daemon.test.d.ts +1 -0
  189. package/dist/src/commands/daemon.test.js +293 -0
  190. package/dist/src/completion-fast.d.ts +22 -0
  191. package/dist/src/completion-fast.js +88 -0
  192. package/dist/src/completion-shared.d.ts +13 -0
  193. package/dist/src/completion-shared.js +61 -0
  194. package/dist/src/completion.d.ts +21 -0
  195. package/dist/src/completion.js +68 -0
  196. package/dist/src/completion.test.d.ts +1 -0
  197. package/dist/src/completion.test.js +24 -0
  198. package/dist/src/constants.d.ts +18 -0
  199. package/dist/src/constants.js +41 -0
  200. package/dist/src/convention-audit.d.ts +50 -0
  201. package/dist/src/convention-audit.js +553 -0
  202. package/dist/src/convention-audit.test.d.ts +1 -0
  203. package/dist/src/convention-audit.test.js +255 -0
  204. package/dist/src/daemon/server.d.ts +13 -0
  205. package/dist/src/daemon/server.js +185 -0
  206. package/dist/src/daemon/server.test.d.ts +1 -0
  207. package/dist/src/daemon/server.test.js +132 -0
  208. package/dist/src/daemon-utils.d.ts +18 -0
  209. package/dist/src/daemon-utils.js +37 -0
  210. package/dist/src/daemon.d.ts +1 -0
  211. package/dist/src/daemon.js +27 -0
  212. package/dist/src/daemon.test.d.ts +1 -0
  213. package/dist/src/daemon.test.js +60 -0
  214. package/dist/src/discovery.d.ts +46 -0
  215. package/dist/src/discovery.js +341 -0
  216. package/dist/src/doctor.d.ts +39 -0
  217. package/dist/src/doctor.js +168 -0
  218. package/dist/src/doctor.test.d.ts +1 -0
  219. package/dist/src/doctor.test.js +302 -0
  220. package/dist/src/download/article-download.d.ts +72 -0
  221. package/dist/src/download/article-download.js +305 -0
  222. package/dist/src/download/article-download.test.d.ts +1 -0
  223. package/dist/src/download/article-download.test.js +235 -0
  224. package/dist/src/download/index.d.ts +71 -0
  225. package/dist/src/download/index.js +363 -0
  226. package/dist/src/download/index.test.d.ts +1 -0
  227. package/dist/src/download/index.test.js +174 -0
  228. package/dist/src/download/media-download.d.ts +49 -0
  229. package/dist/src/download/media-download.js +127 -0
  230. package/dist/src/download/media-download.test.d.ts +1 -0
  231. package/dist/src/download/media-download.test.js +112 -0
  232. package/dist/src/download/progress.d.ts +36 -0
  233. package/dist/src/download/progress.js +120 -0
  234. package/dist/src/download/progress.test.d.ts +1 -0
  235. package/dist/src/download/progress.test.js +36 -0
  236. package/dist/src/electron-apps.d.ts +31 -0
  237. package/dist/src/electron-apps.js +91 -0
  238. package/dist/src/electron-apps.test.d.ts +1 -0
  239. package/dist/src/electron-apps.test.js +76 -0
  240. package/dist/src/engine.test.d.ts +1 -0
  241. package/dist/src/engine.test.js +312 -0
  242. package/dist/src/errors.d.ts +114 -0
  243. package/dist/src/errors.js +174 -0
  244. package/dist/src/errors.test.d.ts +1 -0
  245. package/dist/src/errors.test.js +109 -0
  246. package/dist/src/execution.d.ts +24 -0
  247. package/dist/src/execution.js +509 -0
  248. package/dist/src/execution.test.d.ts +1 -0
  249. package/dist/src/execution.test.js +645 -0
  250. package/dist/src/external.d.ts +49 -0
  251. package/dist/src/external.js +211 -0
  252. package/dist/src/external.test.d.ts +1 -0
  253. package/dist/src/external.test.js +110 -0
  254. package/dist/src/help.d.ts +88 -0
  255. package/dist/src/help.js +556 -0
  256. package/dist/src/help.test.d.ts +1 -0
  257. package/dist/src/help.test.js +58 -0
  258. package/dist/src/hooks.d.ts +46 -0
  259. package/dist/src/hooks.js +58 -0
  260. package/dist/src/hooks.test.d.ts +4 -0
  261. package/dist/src/hooks.test.js +92 -0
  262. package/dist/src/interceptor.d.ts +44 -0
  263. package/dist/src/interceptor.js +183 -0
  264. package/dist/src/interceptor.test.d.ts +4 -0
  265. package/dist/src/interceptor.test.js +81 -0
  266. package/dist/src/launcher.d.ts +41 -0
  267. package/dist/src/launcher.js +226 -0
  268. package/dist/src/launcher.test.d.ts +1 -0
  269. package/dist/src/launcher.test.js +153 -0
  270. package/dist/src/logger.d.ts +26 -0
  271. package/dist/src/logger.js +49 -0
  272. package/dist/src/main.d.ts +5 -0
  273. package/dist/src/main.js +177 -0
  274. package/dist/src/manifest-types.d.ts +43 -0
  275. package/dist/src/manifest-types.js +9 -0
  276. package/dist/src/node-network.d.ts +10 -0
  277. package/dist/src/node-network.js +174 -0
  278. package/dist/src/node-network.test.d.ts +1 -0
  279. package/dist/src/node-network.test.js +55 -0
  280. package/dist/src/observation/artifact.d.ts +16 -0
  281. package/dist/src/observation/artifact.js +260 -0
  282. package/dist/src/observation/artifact.test.d.ts +1 -0
  283. package/dist/src/observation/artifact.test.js +121 -0
  284. package/dist/src/observation/events.d.ts +89 -0
  285. package/dist/src/observation/events.js +1 -0
  286. package/dist/src/observation/index.d.ts +7 -0
  287. package/dist/src/observation/index.js +7 -0
  288. package/dist/src/observation/manager.d.ts +9 -0
  289. package/dist/src/observation/manager.js +27 -0
  290. package/dist/src/observation/manager.test.d.ts +1 -0
  291. package/dist/src/observation/manager.test.js +13 -0
  292. package/dist/src/observation/redaction.d.ts +11 -0
  293. package/dist/src/observation/redaction.js +81 -0
  294. package/dist/src/observation/redaction.test.d.ts +1 -0
  295. package/dist/src/observation/redaction.test.js +32 -0
  296. package/dist/src/observation/retention.d.ts +32 -0
  297. package/dist/src/observation/retention.js +160 -0
  298. package/dist/src/observation/retention.test.d.ts +1 -0
  299. package/dist/src/observation/retention.test.js +118 -0
  300. package/dist/src/observation/ring-buffer.d.ts +22 -0
  301. package/dist/src/observation/ring-buffer.js +45 -0
  302. package/dist/src/observation/ring-buffer.test.d.ts +1 -0
  303. package/dist/src/observation/ring-buffer.test.js +22 -0
  304. package/dist/src/observation/session.d.ts +25 -0
  305. package/dist/src/observation/session.js +50 -0
  306. package/dist/src/output.d.ts +14 -0
  307. package/dist/src/output.js +152 -0
  308. package/dist/src/output.test.d.ts +1 -0
  309. package/dist/src/output.test.js +53 -0
  310. package/dist/src/package-exports.test.d.ts +1 -0
  311. package/dist/src/package-exports.test.js +112 -0
  312. package/dist/src/package-paths.d.ts +8 -0
  313. package/dist/src/package-paths.js +41 -0
  314. package/dist/src/pipeline/executor.d.ts +11 -0
  315. package/dist/src/pipeline/executor.js +93 -0
  316. package/dist/src/pipeline/executor.test.d.ts +4 -0
  317. package/dist/src/pipeline/executor.test.js +182 -0
  318. package/dist/src/pipeline/index.d.ts +5 -0
  319. package/dist/src/pipeline/index.js +5 -0
  320. package/dist/src/pipeline/registry.d.ts +28 -0
  321. package/dist/src/pipeline/registry.js +55 -0
  322. package/dist/src/pipeline/steps/browser.d.ts +13 -0
  323. package/dist/src/pipeline/steps/browser.js +86 -0
  324. package/dist/src/pipeline/steps/download.d.ts +18 -0
  325. package/dist/src/pipeline/steps/download.js +252 -0
  326. package/dist/src/pipeline/steps/download.test.d.ts +1 -0
  327. package/dist/src/pipeline/steps/download.test.js +102 -0
  328. package/dist/src/pipeline/steps/fetch.d.ts +5 -0
  329. package/dist/src/pipeline/steps/fetch.js +122 -0
  330. package/dist/src/pipeline/steps/fetch.test.d.ts +1 -0
  331. package/dist/src/pipeline/steps/fetch.test.js +117 -0
  332. package/dist/src/pipeline/steps/intercept.d.ts +5 -0
  333. package/dist/src/pipeline/steps/intercept.js +50 -0
  334. package/dist/src/pipeline/steps/tap.d.ts +12 -0
  335. package/dist/src/pipeline/steps/tap.js +93 -0
  336. package/dist/src/pipeline/steps/transform.d.ts +9 -0
  337. package/dist/src/pipeline/steps/transform.js +70 -0
  338. package/dist/src/pipeline/template.d.ts +17 -0
  339. package/dist/src/pipeline/template.js +344 -0
  340. package/dist/src/pipeline/template.test.d.ts +4 -0
  341. package/dist/src/pipeline/template.test.js +197 -0
  342. package/dist/src/pipeline/transform.test.d.ts +4 -0
  343. package/dist/src/pipeline/transform.test.js +133 -0
  344. package/dist/src/plugin-manifest.d.ts +70 -0
  345. package/dist/src/plugin-manifest.js +160 -0
  346. package/dist/src/plugin-manifest.test.d.ts +4 -0
  347. package/dist/src/plugin-manifest.test.js +179 -0
  348. package/dist/src/plugin-scaffold.d.ts +28 -0
  349. package/dist/src/plugin-scaffold.js +143 -0
  350. package/dist/src/plugin-scaffold.test.d.ts +4 -0
  351. package/dist/src/plugin-scaffold.test.js +83 -0
  352. package/dist/src/plugin.d.ts +146 -0
  353. package/dist/src/plugin.js +1303 -0
  354. package/dist/src/plugin.test.d.ts +4 -0
  355. package/dist/src/plugin.test.js +1363 -0
  356. package/dist/src/registry-api.d.ts +13 -0
  357. package/dist/src/registry-api.js +10 -0
  358. package/dist/src/registry.d.ts +123 -0
  359. package/dist/src/registry.js +125 -0
  360. package/dist/src/registry.test.d.ts +4 -0
  361. package/dist/src/registry.test.js +209 -0
  362. package/dist/src/runtime-copy.test.d.ts +1 -0
  363. package/dist/src/runtime-copy.test.js +29 -0
  364. package/dist/src/runtime-detect.d.ts +31 -0
  365. package/dist/src/runtime-detect.js +47 -0
  366. package/dist/src/runtime-detect.test.d.ts +1 -0
  367. package/dist/src/runtime-detect.test.js +39 -0
  368. package/dist/src/runtime-identity.test.d.ts +1 -0
  369. package/dist/src/runtime-identity.test.js +18 -0
  370. package/dist/src/runtime.d.ts +47 -0
  371. package/dist/src/runtime.js +54 -0
  372. package/dist/src/scripts/framework.d.ts +7 -0
  373. package/dist/src/scripts/framework.js +25 -0
  374. package/dist/src/scripts/interact.d.ts +4 -0
  375. package/dist/src/scripts/interact.js +20 -0
  376. package/dist/src/scripts/store.d.ts +13 -0
  377. package/dist/src/scripts/store.js +48 -0
  378. package/dist/src/serialization.d.ts +42 -0
  379. package/dist/src/serialization.js +103 -0
  380. package/dist/src/serialization.test.d.ts +1 -0
  381. package/dist/src/serialization.test.js +78 -0
  382. package/dist/src/skills.d.ts +14 -0
  383. package/dist/src/skills.js +127 -0
  384. package/dist/src/skills.test.d.ts +1 -0
  385. package/dist/src/skills.test.js +71 -0
  386. package/dist/src/snapshotFormatter.d.ts +11 -0
  387. package/dist/src/snapshotFormatter.js +338 -0
  388. package/dist/src/snapshotFormatter.test.d.ts +7 -0
  389. package/dist/src/snapshotFormatter.test.js +521 -0
  390. package/dist/src/tui.d.ts +28 -0
  391. package/dist/src/tui.js +179 -0
  392. package/dist/src/tui.test.d.ts +1 -0
  393. package/dist/src/tui.test.js +19 -0
  394. package/dist/src/types.d.ts +236 -0
  395. package/dist/src/types.js +7 -0
  396. package/dist/src/update-check.d.ts +66 -0
  397. package/dist/src/update-check.js +206 -0
  398. package/dist/src/update-check.test.d.ts +1 -0
  399. package/dist/src/update-check.test.js +117 -0
  400. package/dist/src/utils.d.ts +57 -0
  401. package/dist/src/utils.js +152 -0
  402. package/dist/src/utils.test.d.ts +1 -0
  403. package/dist/src/utils.test.js +155 -0
  404. package/dist/src/validate.d.ts +21 -0
  405. package/dist/src/validate.js +130 -0
  406. package/dist/src/validate.test.d.ts +9 -0
  407. package/dist/src/validate.test.js +90 -0
  408. package/dist/src/verify.d.ts +26 -0
  409. package/dist/src/verify.js +67 -0
  410. package/dist/src/version.d.ts +4 -0
  411. package/dist/src/version.js +22 -0
  412. package/dist/src/weixin-download.test.d.ts +1 -0
  413. package/dist/src/weixin-download.test.js +45 -0
  414. package/package.json +4 -3
  415. package/scripts/check-package-bin.mjs +95 -0
@@ -0,0 +1,302 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ const { mockGetDaemonHealth, mockConnect, mockClose, mockFindShadowedUserAdapters } = vi.hoisted(() => ({
3
+ mockGetDaemonHealth: vi.fn(),
4
+ mockConnect: vi.fn(),
5
+ mockClose: vi.fn(),
6
+ mockFindShadowedUserAdapters: vi.fn(),
7
+ }));
8
+ vi.mock('./browser/daemon-transport.js', () => ({
9
+ getDaemonHealth: mockGetDaemonHealth,
10
+ }));
11
+ vi.mock('./browser/index.js', () => ({
12
+ BrowserBridge: class {
13
+ connect = mockConnect;
14
+ close = mockClose;
15
+ },
16
+ }));
17
+ vi.mock('./adapter-shadow.js', async () => {
18
+ const actual = await vi.importActual('./adapter-shadow.js');
19
+ return {
20
+ ...actual,
21
+ findShadowedUserAdapters: mockFindShadowedUserAdapters,
22
+ };
23
+ });
24
+ import { renderBrowserDoctorReport, runBrowserDoctor } from './doctor.js';
25
+ describe('doctor report rendering', () => {
26
+ const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ mockFindShadowedUserAdapters.mockReturnValue([]);
30
+ // Doctor always runs live connectivity. Tests that want connect to fail override.
31
+ mockConnect.mockResolvedValue({
32
+ evaluate: vi.fn().mockResolvedValue(2),
33
+ closeWindow: vi.fn().mockResolvedValue(undefined),
34
+ });
35
+ mockClose.mockResolvedValue(undefined);
36
+ });
37
+ it('renders OK-style report when daemon and runtime connected', () => {
38
+ const text = strip(renderBrowserDoctorReport({
39
+ cliVersion: '1.7.9',
40
+ daemonRunning: true,
41
+ daemonVersion: '1.7.9',
42
+ runtimeConnected: true,
43
+ runtimeName: 'Cloak',
44
+ runtimeVersion: '1.6.8',
45
+ issues: [],
46
+ }));
47
+ expect(text).toContain('[OK] Daemon: running on port 19825');
48
+ expect(text).toContain('(v1.7.9)');
49
+ expect(text).toContain('[OK] Runtime: Cloak connected (v1.6.8)');
50
+ expect(text).toContain('Everything looks good!');
51
+ expect(text).not.toContain('webcmd browser analyze <url>');
52
+ });
53
+ it('renders a warning when daemon version is stale', () => {
54
+ const text = strip(renderBrowserDoctorReport({
55
+ cliVersion: '1.7.9',
56
+ daemonRunning: true,
57
+ daemonVersion: '1.7.6',
58
+ daemonStale: true,
59
+ runtimeConnected: true,
60
+ runtimeName: 'Cloak',
61
+ runtimeVersion: '1.0.3',
62
+ issues: ['Stale daemon detected: daemon v1.7.6 != CLI v1.7.9.\n Run: webcmd daemon restart'],
63
+ }));
64
+ expect(text).toContain('[WARN] Daemon: running on port 19825 (v1.7.6, stale; CLI v1.7.9)');
65
+ expect(text).toContain('Run: webcmd daemon restart');
66
+ expect(text).not.toContain('Everything looks good!');
67
+ });
68
+ it('renders MISSING when daemon not running', () => {
69
+ const text = strip(renderBrowserDoctorReport({
70
+ daemonRunning: false,
71
+ runtimeConnected: false,
72
+ issues: ['Daemon is not running.'],
73
+ }));
74
+ expect(text).toContain('[MISSING] Daemon: not running');
75
+ expect(text).toContain('[MISSING] Runtime: Cloak not connected');
76
+ expect(text).toContain('Daemon is not running.');
77
+ });
78
+ it('renders runtime not connected when daemon is running', () => {
79
+ const text = strip(renderBrowserDoctorReport({
80
+ daemonRunning: true,
81
+ runtimeConnected: false,
82
+ issues: ['Daemon is running but the Cloak runtime is not connected.'],
83
+ }));
84
+ expect(text).toContain('[OK] Daemon: running on port 19825');
85
+ expect(text).toContain('[MISSING] Runtime: Cloak not connected');
86
+ });
87
+ it('renders OK when the connected Cloak runtime version is unknown', () => {
88
+ const text = strip(renderBrowserDoctorReport({
89
+ daemonRunning: true,
90
+ runtimeConnected: true,
91
+ runtimeName: 'Cloak',
92
+ issues: [],
93
+ }));
94
+ expect(text).toContain('[OK] Runtime: Cloak connected (version unknown)');
95
+ expect(text).not.toContain('Cloak runtime is connected but did not report a version.');
96
+ expect(text).toContain('Everything looks good!');
97
+ });
98
+ it('renders connectivity OK when live test succeeds', () => {
99
+ const text = strip(renderBrowserDoctorReport({
100
+ daemonRunning: true,
101
+ runtimeConnected: true,
102
+ connectivity: { ok: true, durationMs: 1234 },
103
+ issues: [],
104
+ }));
105
+ expect(text).toContain('[OK] Connectivity: connected in 1.2s');
106
+ });
107
+ it('renders connected profiles when multiple are present', () => {
108
+ const text = strip(renderBrowserDoctorReport({
109
+ daemonRunning: true,
110
+ runtimeConnected: false,
111
+ profiles: [
112
+ { contextId: 'work', runtimeConnected: true, runtimeVersion: '1.2.3', pending: 0 },
113
+ { contextId: 'personal', runtimeConnected: true, runtimeVersion: '1.2.3', pending: 0 },
114
+ ],
115
+ issues: [],
116
+ }));
117
+ expect(text).toContain('Profiles:');
118
+ expect(text).toContain('work: connected v1.2.3');
119
+ expect(text).toContain('personal: connected v1.2.3');
120
+ });
121
+ it('renders unstable runtime state when live connectivity and status disagree', () => {
122
+ const text = strip(renderBrowserDoctorReport({
123
+ daemonRunning: true,
124
+ runtimeConnected: true,
125
+ runtimeFlaky: true,
126
+ runtimeName: 'Cloak',
127
+ connectivity: { ok: true, durationMs: 1234 },
128
+ issues: ['Cloak runtime connection is unstable.'],
129
+ }));
130
+ expect(text).toContain('[WARN] Runtime: Cloak unstable');
131
+ expect(text).toContain('Cloak runtime connection is unstable.');
132
+ });
133
+ it('renders unstable daemon state when live connectivity and status disagree', () => {
134
+ const text = strip(renderBrowserDoctorReport({
135
+ daemonRunning: false,
136
+ daemonFlaky: true,
137
+ runtimeConnected: false,
138
+ connectivity: { ok: true, durationMs: 1234 },
139
+ issues: ['Daemon connectivity is unstable.'],
140
+ }));
141
+ expect(text).toContain('[WARN] Daemon: unstable');
142
+ expect(text).toContain('Daemon connectivity is unstable.');
143
+ });
144
+ it('reports daemon not running when connectivity fails and daemon stays stopped', async () => {
145
+ mockConnect.mockRejectedValueOnce(new Error('Could not start daemon'));
146
+ mockGetDaemonHealth.mockResolvedValueOnce({ state: 'stopped', status: null });
147
+ const report = await runBrowserDoctor();
148
+ expect(report.daemonRunning).toBe(false);
149
+ expect(report.runtimeConnected).toBe(false);
150
+ expect(report.connectivity?.ok).toBe(false);
151
+ expect(report.issues).toEqual(expect.arrayContaining([
152
+ expect.stringContaining('Daemon is not running'),
153
+ ]));
154
+ });
155
+ it('reports flapping when live check succeeds but final status shows runtime disconnected', async () => {
156
+ mockGetDaemonHealth.mockResolvedValueOnce({ state: 'no-runtime', status: { runtimeConnected: false, runtimeName: 'Cloak' } });
157
+ const report = await runBrowserDoctor();
158
+ expect(report.daemonRunning).toBe(true);
159
+ expect(report.runtimeConnected).toBe(false);
160
+ expect(report.runtimeFlaky).toBe(true);
161
+ expect(report.issues).toEqual(expect.arrayContaining([
162
+ expect.stringContaining('Cloak runtime connection is unstable'),
163
+ ]));
164
+ });
165
+ it('uses runtime-neutral readiness hints when the runtime is disconnected', async () => {
166
+ mockConnect.mockRejectedValueOnce(new Error('runtime unavailable'));
167
+ mockGetDaemonHealth.mockResolvedValueOnce({
168
+ state: 'no-runtime',
169
+ status: { runtimeConnected: false, runtimeName: 'Cloak' },
170
+ });
171
+ const report = await runBrowserDoctor();
172
+ const issues = report.issues.join('\n');
173
+ expect(issues).toContain('Cloak runtime is not connected');
174
+ expect(issues).toContain('Make sure Chrome/Chromium is open and Cloak is enabled');
175
+ expect(issues).not.toContain(`Webcmd Browser ${'Bridge'}`);
176
+ expect(issues).not.toContain(`Load ${'unpacked'}`);
177
+ expect(issues).not.toContain('Download the latest extension');
178
+ });
179
+ it('reports daemon flapping when live check succeeds but daemon disappears afterward', async () => {
180
+ mockGetDaemonHealth.mockResolvedValueOnce({ state: 'stopped', status: null });
181
+ const report = await runBrowserDoctor();
182
+ expect(report.daemonRunning).toBe(false);
183
+ expect(report.daemonFlaky).toBe(true);
184
+ expect(report.runtimeConnected).toBe(false);
185
+ expect(report.issues).toEqual(expect.arrayContaining([
186
+ expect.stringContaining('Daemon connectivity is unstable'),
187
+ ]));
188
+ });
189
+ it('uses the fast default timeout for live connectivity checks', async () => {
190
+ let timeoutSeen;
191
+ const closeWindow = vi.fn().mockResolvedValue(undefined);
192
+ mockConnect.mockImplementationOnce(async (opts) => {
193
+ timeoutSeen = opts?.timeout;
194
+ expect(opts?.session).toBe('__doctor__');
195
+ expect(opts?.surface).toBe('browser');
196
+ return {
197
+ evaluate: vi.fn().mockResolvedValue(2),
198
+ closeWindow,
199
+ };
200
+ });
201
+ mockGetDaemonHealth.mockResolvedValueOnce({ state: 'ready', status: { runtimeConnected: true, runtimeName: 'Cloak' } });
202
+ await runBrowserDoctor();
203
+ expect(timeoutSeen).toBe(8);
204
+ expect(closeWindow).toHaveBeenCalledTimes(1);
205
+ });
206
+ it('does not report an issue when the connected Cloak runtime does not report a version', async () => {
207
+ const status = {
208
+ state: 'ready',
209
+ status: {
210
+ runtimeConnected: true,
211
+ runtimeName: 'Cloak',
212
+ runtimeVersion: undefined,
213
+ },
214
+ };
215
+ mockGetDaemonHealth.mockResolvedValue(status);
216
+ const report = await runBrowserDoctor();
217
+ expect(report.runtimeConnected).toBe(true);
218
+ expect(report.runtimeVersion).toBeUndefined();
219
+ expect(report.issues.join('\n')).not.toContain('did not report a version');
220
+ });
221
+ it('does not compare runtime version to CLI version or cached extension updates', async () => {
222
+ const status = {
223
+ state: 'ready',
224
+ status: {
225
+ daemonVersion: '1.7.9',
226
+ runtimeConnected: true,
227
+ runtimeName: 'Cloak',
228
+ runtimeVersion: '99.0.0',
229
+ },
230
+ };
231
+ mockGetDaemonHealth.mockResolvedValue(status);
232
+ const report = await runBrowserDoctor({ cliVersion: '1.7.9' });
233
+ expect(report.runtimeVersion).toBe('99.0.0');
234
+ expect(report.issues.join('\n')).not.toContain('Extension major version mismatch');
235
+ expect(report.issues.join('\n')).not.toContain('Extension update available');
236
+ expect(report.issues.join('\n')).not.toContain('Download the latest extension');
237
+ });
238
+ it('reports an issue when daemon version differs from CLI version', async () => {
239
+ const status = {
240
+ state: 'ready',
241
+ status: {
242
+ daemonVersion: '1.7.6',
243
+ runtimeConnected: true,
244
+ runtimeName: 'Cloak',
245
+ runtimeVersion: '1.0.3',
246
+ },
247
+ };
248
+ mockGetDaemonHealth.mockResolvedValue(status);
249
+ const report = await runBrowserDoctor({ cliVersion: '1.7.9' });
250
+ expect(report.daemonStale).toBe(true);
251
+ expect(report.issues).toEqual(expect.arrayContaining([
252
+ expect.stringContaining('Stale daemon detected: daemon v1.7.6 != CLI v1.7.9'),
253
+ ]));
254
+ });
255
+ it('reports local adapter shadows as a warning issue', async () => {
256
+ const status = {
257
+ state: 'ready',
258
+ status: {
259
+ daemonVersion: '1.7.9',
260
+ runtimeConnected: true,
261
+ runtimeName: 'Cloak',
262
+ runtimeVersion: '1.0.3',
263
+ },
264
+ };
265
+ mockGetDaemonHealth.mockResolvedValue(status);
266
+ mockFindShadowedUserAdapters.mockReturnValueOnce([
267
+ {
268
+ name: 'instagram/saved',
269
+ userPath: '/home/me/.webcmd/clis/instagram/saved.js',
270
+ builtinPath: '/pkg/clis/instagram/saved.js',
271
+ },
272
+ ]);
273
+ const report = await runBrowserDoctor({ cliVersion: '1.7.9' });
274
+ expect(report.adapterShadows).toHaveLength(1);
275
+ expect(report.issues).toEqual(expect.arrayContaining([
276
+ expect.stringContaining('Local adapter overrides shadow packaged adapters'),
277
+ ]));
278
+ });
279
+ it('reports profile-required when multiple profiles are connected without a selection', async () => {
280
+ const status = {
281
+ state: 'profile-required',
282
+ status: {
283
+ runtimeConnected: false,
284
+ runtimeName: 'Cloak',
285
+ profileRequired: true,
286
+ profiles: [
287
+ { contextId: 'work', runtimeConnected: true, pending: 0 },
288
+ { contextId: 'personal', runtimeConnected: true, pending: 0 },
289
+ ],
290
+ },
291
+ };
292
+ mockGetDaemonHealth.mockResolvedValue(status);
293
+ // Real connectivity would fail in profile-required state; force it here so
294
+ // the test exercises the profile-required issue path, not the flaky path.
295
+ mockConnect.mockRejectedValueOnce(new Error('profile required'));
296
+ const report = await runBrowserDoctor();
297
+ expect(report.profiles).toHaveLength(2);
298
+ expect(report.issues).toEqual(expect.arrayContaining([
299
+ expect.stringContaining('Multiple Chrome profiles are connected'),
300
+ ]));
301
+ });
302
+ });
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Article download helper — shared logic for downloading articles as Markdown.
3
+ *
4
+ * Used by: zhihu/download, weixin/download, and future article adapters.
5
+ *
6
+ * Flow: ArticleData → TurndownService → image download → frontmatter → .md file
7
+ */
8
+ import TurndownService from 'turndown';
9
+ export interface ArticleData {
10
+ title: string;
11
+ author?: string;
12
+ publishTime?: string;
13
+ sourceUrl?: string;
14
+ contentHtml: string;
15
+ /** Pre-extracted code blocks to restore after Markdown conversion */
16
+ codeBlocks?: Array<{
17
+ lang: string;
18
+ code: string;
19
+ }>;
20
+ /** Image URLs found in the article (pre-collected from DOM) */
21
+ imageUrls?: string[];
22
+ }
23
+ export interface FrontmatterLabels {
24
+ author?: string;
25
+ publishTime?: string;
26
+ sourceUrl?: string;
27
+ }
28
+ export interface ArticleDownloadOptions {
29
+ output: string;
30
+ downloadImages?: boolean;
31
+ /** Extra headers for image downloads (e.g. { Referer: '...' }) */
32
+ imageHeaders?: Record<string, string>;
33
+ maxTitleLength?: number;
34
+ /** Custom TurndownService configuration callback */
35
+ configureTurndown?: (td: TurndownService) => void;
36
+ /** Custom image extension detector (default: infer from URL extension) */
37
+ detectImageExt?: (url: string) => string;
38
+ /** Custom frontmatter labels (default: Chinese labels) */
39
+ frontmatterLabels?: FrontmatterLabels;
40
+ /**
41
+ * Extra CSS selectors removed from the article before Turndown conversion.
42
+ * Use this to drop site-specific noise the adapter can't always trim upstream
43
+ * (e.g. zhihu 折叠卡, weixin 赞赏栏, wiki infobox).
44
+ */
45
+ cleanSelectors?: string[];
46
+ /**
47
+ * Write the markdown to `process.stdout` instead of a file on disk. Image
48
+ * download and directory creation are skipped — remote image URLs are kept
49
+ * as-is so the output is self-contained when piped.
50
+ */
51
+ stdout?: boolean;
52
+ }
53
+ export interface ArticleDownloadResult {
54
+ title: string;
55
+ author: string;
56
+ publish_time: string;
57
+ status: string;
58
+ size: string;
59
+ saved: string;
60
+ }
61
+ /**
62
+ * Download an article to Markdown with optional image localization.
63
+ *
64
+ * Handles the full pipeline:
65
+ * 1. HTML → Markdown (via TurndownService)
66
+ * 2. Code block placeholder restoration
67
+ * 3. Batch image downloading with concurrency + deduplication
68
+ * 4. Image URL replacement in Markdown
69
+ * 5. Frontmatter generation (customizable labels)
70
+ * 6. File write
71
+ */
72
+ export declare function downloadArticle(data: ArticleData, options: ArticleDownloadOptions): Promise<ArticleDownloadResult[]>;
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Article download helper — shared logic for downloading articles as Markdown.
3
+ *
4
+ * Used by: zhihu/download, weixin/download, and future article adapters.
5
+ *
6
+ * Flow: ArticleData → TurndownService → image download → frontmatter → .md file
7
+ */
8
+ import * as fs from 'node:fs';
9
+ import * as path from 'node:path';
10
+ import TurndownService from 'turndown';
11
+ import { gfm } from 'turndown-plugin-gfm';
12
+ import { httpDownload, sanitizeFilename } from './index.js';
13
+ import { formatBytes } from './progress.js';
14
+ const IMAGE_CONCURRENCY = 5;
15
+ const DEFAULT_LABELS = {
16
+ author: '作者',
17
+ publishTime: '发布时间',
18
+ sourceUrl: '原文链接',
19
+ };
20
+ // ============================================================
21
+ // Markdown Conversion
22
+ // ============================================================
23
+ // Nodes that never carry article content. Turndown keeps them by default — if an
24
+ // adapter's contentHtml extraction misses one, CSS / scripts / widget markup
25
+ // ends up inline in the .md. Strip them unconditionally at the converter level.
26
+ // `svg` is not in HTMLElementTagNameMap, so we type-narrow manually.
27
+ // `header/footer/nav/aside` cover page chrome that adapters occasionally
28
+ // forget to trim — the article's own title/author/publishTime are supplied
29
+ // as separate fields on ArticleData, so duplicated nodes are redundant.
30
+ // `iframe` is NOT in this set — it's handled by a dedicated rule below that
31
+ // degrades to a link so embedded content (YouTube, Twitter, CodePen …) keeps
32
+ // a reachable URL in the exported markdown.
33
+ const STRIPPED_TAGS = [
34
+ 'script', 'style', 'noscript',
35
+ 'canvas',
36
+ 'form', 'button', 'dialog',
37
+ 'header', 'footer', 'nav', 'aside',
38
+ ];
39
+ function createTurndown(configure, cleanSelectors) {
40
+ const td = new TurndownService({
41
+ headingStyle: 'atx',
42
+ codeBlockStyle: 'fenced',
43
+ bulletListMarker: '-',
44
+ });
45
+ td.use(gfm);
46
+ td.remove(STRIPPED_TAGS);
47
+ // turndown-plugin-gfm@1.0.2 emits single-tilde strikethrough (`~x~`), which
48
+ // is not the canonical GFM form. Override it so exported markdown is
49
+ // portable across common renderers.
50
+ td.addRule('canonicalStrikethrough', {
51
+ filter: (node) => ['DEL', 'S', 'STRIKE'].includes(node.nodeName),
52
+ replacement: (content) => `~~${content}~~`,
53
+ });
54
+ // SVG isn't in the static HTML tag map; match by name with a custom filter.
55
+ td.addRule('stripSvg', {
56
+ filter: (node) => node.nodeName === 'svg' || node.nodeName === 'SVG',
57
+ replacement: () => '',
58
+ });
59
+ td.addRule('linebreak', {
60
+ filter: 'br',
61
+ replacement: () => '\n',
62
+ });
63
+ // Inline base64 images would land as huge `![](data:image/...;base64,...)`
64
+ // strings that the image downloader can't localize. Drop them.
65
+ td.addRule('ignoreBase64Images', {
66
+ filter: (node) => {
67
+ if (node.nodeName !== 'IMG')
68
+ return false;
69
+ const src = node.getAttribute?.('src') ?? '';
70
+ return src.startsWith('data:');
71
+ },
72
+ replacement: () => '',
73
+ });
74
+ // Markdown has no native video/audio primitive. Emit inline HTML so
75
+ // renderers that support it (GitHub, VS Code preview …) still play the
76
+ // media; viewers that don't simply show the tag as text, which is still
77
+ // more information than dropping the node outright.
78
+ td.addRule('videoElement', {
79
+ filter: (node) => node.nodeName === 'VIDEO',
80
+ replacement: (_content, node) => {
81
+ const el = node;
82
+ const src = el.getAttribute('src')
83
+ || el.querySelector('source')?.getAttribute('src')
84
+ || '';
85
+ if (!src)
86
+ return '';
87
+ const poster = el.getAttribute('poster') || '';
88
+ return `\n<video src="${src}" controls${poster ? ` poster="${poster}"` : ''}></video>\n`;
89
+ },
90
+ });
91
+ td.addRule('audioElement', {
92
+ filter: (node) => node.nodeName === 'AUDIO',
93
+ replacement: (_content, node) => {
94
+ const el = node;
95
+ const src = el.getAttribute('src')
96
+ || el.querySelector('source')?.getAttribute('src')
97
+ || '';
98
+ return src ? `\n<audio src="${src}" controls></audio>\n` : '';
99
+ },
100
+ });
101
+ // Iframes (YouTube, Twitter, CodePen …) degrade to a markdown link so the
102
+ // embedded resource is still reachable from the exported file.
103
+ td.addRule('iframeToLink', {
104
+ filter: (node) => node.nodeName === 'IFRAME',
105
+ replacement: (_content, node) => {
106
+ const el = node;
107
+ const src = el.getAttribute('src') || '';
108
+ if (!src)
109
+ return '';
110
+ const title = el.getAttribute('title') || 'Embedded content';
111
+ return `\n[${title}](${src})\n`;
112
+ },
113
+ });
114
+ // Per-adapter dirty-node removal. Adapters know their site's specific noise
115
+ // (zhihu 折叠卡, weixin 赞赏栏, wiki 折叠 infobox …); we keep the default set
116
+ // empty so the generic converter stays untouched.
117
+ const selectorRules = (cleanSelectors ?? [])
118
+ .map(sel => sel.trim())
119
+ .filter(Boolean);
120
+ if (selectorRules.length > 0) {
121
+ td.addRule('cleanSelectors', {
122
+ filter: (node) => {
123
+ const match = node.matches;
124
+ if (typeof match !== 'function')
125
+ return false;
126
+ return selectorRules.some((sel) => {
127
+ try {
128
+ return match.call(node, sel);
129
+ }
130
+ catch {
131
+ return false;
132
+ }
133
+ });
134
+ },
135
+ replacement: () => '',
136
+ });
137
+ }
138
+ if (configure)
139
+ configure(td);
140
+ return td;
141
+ }
142
+ function convertToMarkdown(contentHtml, codeBlocks, configure, cleanSelectors) {
143
+ const td = createTurndown(configure, cleanSelectors);
144
+ let md = td.turndown(contentHtml);
145
+ // Restore code block placeholders
146
+ codeBlocks.forEach((block, i) => {
147
+ const placeholder = `CODEBLOCK-PLACEHOLDER-${i}`;
148
+ const fenced = `\n\`\`\`${block.lang}\n${block.code}\n\`\`\`\n`;
149
+ md = md.replace(placeholder, fenced);
150
+ });
151
+ // Clean up
152
+ md = md.replace(/\u00a0/g, ' ');
153
+ // Turndown leaves behind lone dashes / middle dots when list bullets or
154
+ // decorative separators lose their surrounding inline context.
155
+ md = md.replace(/^[ \t]*[-·][ \t]*$/gm, '');
156
+ md = md.replace(/^[ \t]+$/gm, '');
157
+ md = md.replace(/[ \t]+$/gm, '');
158
+ md = md.replace(/\n{3,}/g, '\n\n');
159
+ return md;
160
+ }
161
+ function replaceImageUrls(md, urlMap) {
162
+ return md.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, imgUrl) => {
163
+ const local = urlMap[imgUrl];
164
+ return local ? `![${alt}](${local})` : match;
165
+ });
166
+ }
167
+ // ============================================================
168
+ // Image Downloading
169
+ // ============================================================
170
+ function defaultDetectImageExt(url) {
171
+ const extMatch = url.match(/\.(\w{3,4})(?:\?|$)/);
172
+ return extMatch ? extMatch[1] : 'jpg';
173
+ }
174
+ async function downloadImages(imgUrls, imgDir, headers, detectExt) {
175
+ const urlMap = {};
176
+ if (imgUrls.length === 0)
177
+ return urlMap;
178
+ const detect = detectExt || defaultDetectImageExt;
179
+ // Deduplicate image URLs
180
+ const seen = new Set();
181
+ const uniqueUrls = imgUrls.filter(url => {
182
+ if (seen.has(url))
183
+ return false;
184
+ seen.add(url);
185
+ return true;
186
+ });
187
+ for (let i = 0; i < uniqueUrls.length; i += IMAGE_CONCURRENCY) {
188
+ const batch = uniqueUrls.slice(i, i + IMAGE_CONCURRENCY);
189
+ const results = await Promise.all(batch.map(async (rawUrl, j) => {
190
+ const index = i + j + 1;
191
+ let imgUrl = rawUrl;
192
+ if (imgUrl.startsWith('//'))
193
+ imgUrl = `https:${imgUrl}`;
194
+ const ext = detect(imgUrl);
195
+ const filename = `img_${String(index).padStart(3, '0')}.${ext}`;
196
+ const filepath = path.join(imgDir, filename);
197
+ try {
198
+ const result = await httpDownload(imgUrl, filepath, {
199
+ headers,
200
+ timeout: 15000,
201
+ });
202
+ if (result.success) {
203
+ return { remoteUrl: rawUrl, localPath: `images/${filename}` };
204
+ }
205
+ }
206
+ catch {
207
+ // Skip failed downloads
208
+ }
209
+ return null;
210
+ }));
211
+ for (const r of results) {
212
+ if (r)
213
+ urlMap[r.remoteUrl] = r.localPath;
214
+ }
215
+ }
216
+ return urlMap;
217
+ }
218
+ // ============================================================
219
+ // Main API
220
+ // ============================================================
221
+ /**
222
+ * Download an article to Markdown with optional image localization.
223
+ *
224
+ * Handles the full pipeline:
225
+ * 1. HTML → Markdown (via TurndownService)
226
+ * 2. Code block placeholder restoration
227
+ * 3. Batch image downloading with concurrency + deduplication
228
+ * 4. Image URL replacement in Markdown
229
+ * 5. Frontmatter generation (customizable labels)
230
+ * 6. File write
231
+ */
232
+ export async function downloadArticle(data, options) {
233
+ const { output, downloadImages: shouldDownloadImages = true, imageHeaders, maxTitleLength = 80, configureTurndown, detectImageExt, frontmatterLabels, cleanSelectors, stdout = false, } = options;
234
+ const labels = { ...DEFAULT_LABELS, ...frontmatterLabels };
235
+ if (!data.title) {
236
+ return [{
237
+ title: 'Error',
238
+ author: '-',
239
+ publish_time: '-',
240
+ status: 'failed — no title',
241
+ size: '-',
242
+ saved: '-',
243
+ }];
244
+ }
245
+ if (!data.contentHtml) {
246
+ return [{
247
+ title: data.title,
248
+ author: data.author || '-',
249
+ publish_time: data.publishTime || '-',
250
+ status: 'failed — no content',
251
+ size: '-',
252
+ saved: '-',
253
+ }];
254
+ }
255
+ // Convert HTML to Markdown
256
+ let markdown = convertToMarkdown(data.contentHtml, data.codeBlocks || [], configureTurndown, cleanSelectors);
257
+ const safeTitle = sanitizeFilename(data.title, maxTitleLength);
258
+ // Download images only when writing to disk. In stdout mode remote URLs
259
+ // stay intact so the piped output is self-contained.
260
+ if (!stdout && shouldDownloadImages && data.imageUrls && data.imageUrls.length > 0) {
261
+ const articleDir = path.join(output, safeTitle);
262
+ fs.mkdirSync(articleDir, { recursive: true });
263
+ const imagesDir = path.join(articleDir, 'images');
264
+ fs.mkdirSync(imagesDir, { recursive: true });
265
+ const urlMap = await downloadImages(data.imageUrls, imagesDir, imageHeaders, detectImageExt);
266
+ markdown = replaceImageUrls(markdown, urlMap);
267
+ }
268
+ // Build frontmatter with customizable labels.
269
+ // Shape: `# Title\n[> meta\n...]\n---\n\n<markdown>` — exactly one blank
270
+ // line separates every section, so we never produce ≥3 consecutive newlines.
271
+ const headerLines = [`# ${data.title}`];
272
+ if (data.author)
273
+ headerLines.push(`> ${labels.author}: ${data.author}`);
274
+ if (data.publishTime)
275
+ headerLines.push(`> ${labels.publishTime}: ${data.publishTime}`);
276
+ if (data.sourceUrl)
277
+ headerLines.push(`> ${labels.sourceUrl}: ${data.sourceUrl}`);
278
+ const frontmatter = headerLines.join('\n') + '\n\n---\n\n';
279
+ const fullContent = frontmatter + markdown;
280
+ const size = Buffer.byteLength(fullContent, 'utf-8');
281
+ if (stdout) {
282
+ process.stdout.write(fullContent.endsWith('\n') ? fullContent : fullContent + '\n');
283
+ return [{
284
+ title: data.title,
285
+ author: data.author || '-',
286
+ publish_time: data.publishTime || '-',
287
+ status: 'success',
288
+ size: formatBytes(size),
289
+ saved: '-',
290
+ }];
291
+ }
292
+ const articleDir = path.join(output, safeTitle);
293
+ fs.mkdirSync(articleDir, { recursive: true });
294
+ const filename = `${safeTitle}.md`;
295
+ const filePath = path.join(articleDir, filename);
296
+ fs.writeFileSync(filePath, fullContent, 'utf-8');
297
+ return [{
298
+ title: data.title,
299
+ author: data.author || '-',
300
+ publish_time: data.publishTime || '-',
301
+ status: 'success',
302
+ size: formatBytes(size),
303
+ saved: filePath,
304
+ }];
305
+ }
@@ -0,0 +1 @@
1
+ export {};