@ncukondo/search-hub 0.12.1 → 0.12.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 (334) hide show
  1. package/dist/_virtual/_commonjsHelpers.js +30 -0
  2. package/dist/_virtual/_commonjsHelpers.js.map +1 -0
  3. package/dist/_virtual/aliases.js +5 -0
  4. package/dist/_virtual/aliases.js.map +1 -0
  5. package/dist/_virtual/attributes.js +5 -0
  6. package/dist/_virtual/attributes.js.map +1 -0
  7. package/dist/_virtual/back.js +5 -0
  8. package/dist/_virtual/back.js.map +1 -0
  9. package/dist/_virtual/comment.js +5 -0
  10. package/dist/_virtual/comment.js.map +1 -0
  11. package/dist/_virtual/compile.js +5 -0
  12. package/dist/_virtual/compile.js.map +1 -0
  13. package/dist/_virtual/compile2.js +5 -0
  14. package/dist/_virtual/compile2.js.map +1 -0
  15. package/dist/_virtual/decode-data-html.js +5 -0
  16. package/dist/_virtual/decode-data-html.js.map +1 -0
  17. package/dist/_virtual/decode-data-xml.js +5 -0
  18. package/dist/_virtual/decode-data-xml.js.map +1 -0
  19. package/dist/_virtual/decode.js +5 -0
  20. package/dist/_virtual/decode.js.map +1 -0
  21. package/dist/_virtual/decode_codepoint.js +5 -0
  22. package/dist/_virtual/decode_codepoint.js.map +1 -0
  23. package/dist/_virtual/encode-html.js +5 -0
  24. package/dist/_virtual/encode-html.js.map +1 -0
  25. package/dist/_virtual/encode.js +5 -0
  26. package/dist/_virtual/encode.js.map +1 -0
  27. package/dist/_virtual/escape.js +5 -0
  28. package/dist/_virtual/escape.js.map +1 -0
  29. package/dist/_virtual/feeds.js +5 -0
  30. package/dist/_virtual/feeds.js.map +1 -0
  31. package/dist/_virtual/filters.js +5 -0
  32. package/dist/_virtual/filters.js.map +1 -0
  33. package/dist/_virtual/foreignNames.js +5 -0
  34. package/dist/_virtual/foreignNames.js.map +1 -0
  35. package/dist/_virtual/general.js +5 -0
  36. package/dist/_virtual/general.js.map +1 -0
  37. package/dist/_virtual/he.js +5 -0
  38. package/dist/_virtual/he.js.map +1 -0
  39. package/dist/_virtual/helpers.js +5 -0
  40. package/dist/_virtual/helpers.js.map +1 -0
  41. package/dist/_virtual/html.js +5 -0
  42. package/dist/_virtual/html.js.map +1 -0
  43. package/dist/_virtual/index.js +6 -0
  44. package/dist/_virtual/index.js.map +1 -0
  45. package/dist/_virtual/index10.js +5 -0
  46. package/dist/_virtual/index10.js.map +1 -0
  47. package/dist/_virtual/index11.js +5 -0
  48. package/dist/_virtual/index11.js.map +1 -0
  49. package/dist/_virtual/index2.js +5 -0
  50. package/dist/_virtual/index2.js.map +1 -0
  51. package/dist/_virtual/index3.js +5 -0
  52. package/dist/_virtual/index3.js.map +1 -0
  53. package/dist/_virtual/index4.js +5 -0
  54. package/dist/_virtual/index4.js.map +1 -0
  55. package/dist/_virtual/index5.js +7 -0
  56. package/dist/_virtual/index5.js.map +1 -0
  57. package/dist/_virtual/index6.js +5 -0
  58. package/dist/_virtual/index6.js.map +1 -0
  59. package/dist/_virtual/index7.js +5 -0
  60. package/dist/_virtual/index7.js.map +1 -0
  61. package/dist/_virtual/index8.js +5 -0
  62. package/dist/_virtual/index8.js.map +1 -0
  63. package/dist/_virtual/index9.js +5 -0
  64. package/dist/_virtual/index9.js.map +1 -0
  65. package/dist/_virtual/legacy.js +5 -0
  66. package/dist/_virtual/legacy.js.map +1 -0
  67. package/dist/_virtual/manipulation.js +5 -0
  68. package/dist/_virtual/manipulation.js.map +1 -0
  69. package/dist/_virtual/matcher.js +5 -0
  70. package/dist/_virtual/matcher.js.map +1 -0
  71. package/dist/_virtual/node.js +5 -0
  72. package/dist/_virtual/node.js.map +1 -0
  73. package/dist/_virtual/node2.js +5 -0
  74. package/dist/_virtual/node2.js.map +1 -0
  75. package/dist/_virtual/parse.js +5 -0
  76. package/dist/_virtual/parse.js.map +1 -0
  77. package/dist/_virtual/parse2.js +5 -0
  78. package/dist/_virtual/parse2.js.map +1 -0
  79. package/dist/_virtual/pseudos.js +5 -0
  80. package/dist/_virtual/pseudos.js.map +1 -0
  81. package/dist/_virtual/querying.js +5 -0
  82. package/dist/_virtual/querying.js.map +1 -0
  83. package/dist/_virtual/sort.js +5 -0
  84. package/dist/_virtual/sort.js.map +1 -0
  85. package/dist/_virtual/stringify.js +5 -0
  86. package/dist/_virtual/stringify.js.map +1 -0
  87. package/dist/_virtual/subselects.js +5 -0
  88. package/dist/_virtual/subselects.js.map +1 -0
  89. package/dist/_virtual/text.js +5 -0
  90. package/dist/_virtual/text.js.map +1 -0
  91. package/dist/_virtual/traversal.js +5 -0
  92. package/dist/_virtual/traversal.js.map +1 -0
  93. package/dist/_virtual/type.js +5 -0
  94. package/dist/_virtual/type.js.map +1 -0
  95. package/dist/_virtual/valid.js +5 -0
  96. package/dist/_virtual/valid.js.map +1 -0
  97. package/dist/_virtual/void-tag.js +5 -0
  98. package/dist/_virtual/void-tag.js.map +1 -0
  99. package/dist/cli/commands/fulltext/attach.js +1 -1
  100. package/dist/cli/commands/fulltext/attach.js.map +1 -1
  101. package/dist/cli/commands/fulltext/check.d.ts +1 -2
  102. package/dist/cli/commands/fulltext/check.d.ts.map +1 -1
  103. package/dist/cli/commands/fulltext/check.js +4 -2
  104. package/dist/cli/commands/fulltext/check.js.map +1 -1
  105. package/dist/cli/commands/fulltext/convert.d.ts.map +1 -1
  106. package/dist/cli/commands/fulltext/convert.js +8 -8
  107. package/dist/cli/commands/fulltext/convert.js.map +1 -1
  108. package/dist/cli/commands/fulltext/fetch.d.ts.map +1 -1
  109. package/dist/cli/commands/fulltext/fetch.js +10 -6
  110. package/dist/cli/commands/fulltext/fetch.js.map +1 -1
  111. package/dist/cli/commands/fulltext/index.d.ts.map +1 -1
  112. package/dist/cli/commands/fulltext/index.js +2 -0
  113. package/dist/cli/commands/fulltext/index.js.map +1 -1
  114. package/dist/cli/commands/fulltext/init.d.ts.map +1 -1
  115. package/dist/cli/commands/fulltext/init.js +6 -5
  116. package/dist/cli/commands/fulltext/init.js.map +1 -1
  117. package/dist/cli/commands/fulltext/pending.d.ts +1 -1
  118. package/dist/cli/commands/fulltext/pending.d.ts.map +1 -1
  119. package/dist/cli/commands/fulltext/pending.js +4 -2
  120. package/dist/cli/commands/fulltext/pending.js.map +1 -1
  121. package/dist/cli/commands/fulltext/status.d.ts.map +1 -1
  122. package/dist/cli/commands/fulltext/status.js +4 -2
  123. package/dist/cli/commands/fulltext/status.js.map +1 -1
  124. package/dist/cli/commands/fulltext/sync.d.ts.map +1 -1
  125. package/dist/cli/commands/fulltext/sync.js +6 -2
  126. package/dist/cli/commands/fulltext/sync.js.map +1 -1
  127. package/dist/cli/commands/review/types.d.ts +1 -1
  128. package/dist/cli/commands/review/types.d.ts.map +1 -1
  129. package/dist/cli/commands/review/types.js.map +1 -1
  130. package/dist/config/schema.d.ts +2 -0
  131. package/dist/config/schema.d.ts.map +1 -1
  132. package/dist/config/schema.js +6 -0
  133. package/dist/config/schema.js.map +1 -1
  134. package/dist/{fulltext → integration}/attach-shared.d.ts +2 -2
  135. package/dist/integration/attach-shared.d.ts.map +1 -0
  136. package/dist/integration/attach-shared.js.map +1 -0
  137. package/dist/integration/fulltext-attach.js +1 -1
  138. package/dist/integration/fulltext-attach.js.map +1 -1
  139. package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/citation-key.js +1 -1
  140. package/dist/node_modules/@ncukondo/academic-fulltext/dist/citation-key.js.map +1 -0
  141. package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/arxiv-html-parser.js +434 -0
  142. package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/arxiv-html-parser.js.map +1 -0
  143. package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/index.js +93 -0
  144. package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/index.js.map +1 -0
  145. package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/jats-parser.js +1060 -0
  146. package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/jats-parser.js.map +1 -0
  147. package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/convert/markdown-writer.js +146 -117
  148. package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/markdown-writer.js.map +1 -0
  149. package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/discovery/arxiv.js +8 -1
  150. package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/arxiv.js.map +1 -0
  151. package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/discovery/core.js +6 -3
  152. package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/core.js.map +1 -0
  153. package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/index.js +139 -0
  154. package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/index.js.map +1 -0
  155. package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/ncbi-id-converter.js +46 -0
  156. package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/ncbi-id-converter.js.map +1 -0
  157. package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/discovery/pmc.js +8 -4
  158. package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/pmc.js.map +1 -0
  159. package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/discovery/unpaywall.js +43 -9
  160. package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/unpaywall.js.map +1 -0
  161. package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/arxiv-html.js +48 -0
  162. package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/arxiv-html.js.map +1 -0
  163. package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/downloader.js +64 -0
  164. package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/downloader.js.map +1 -0
  165. package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/orchestrator.js +236 -0
  166. package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/orchestrator.js.map +1 -0
  167. package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/download/pmc-xml.js +2 -1
  168. package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/pmc-xml.js.map +1 -0
  169. package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/meta.js +15 -10
  170. package/dist/node_modules/@ncukondo/academic-fulltext/dist/meta.js.map +1 -0
  171. package/dist/node_modules/@ncukondo/academic-fulltext/dist/paths.js.map +1 -0
  172. package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/readme.js +8 -4
  173. package/dist/node_modules/@ncukondo/academic-fulltext/dist/readme.js.map +1 -0
  174. package/dist/node_modules/boolbase/index.js +19 -0
  175. package/dist/node_modules/boolbase/index.js.map +1 -0
  176. package/dist/node_modules/css-select/lib/attributes.js +203 -0
  177. package/dist/node_modules/css-select/lib/attributes.js.map +1 -0
  178. package/dist/node_modules/css-select/lib/compile.js +141 -0
  179. package/dist/node_modules/css-select/lib/compile.js.map +1 -0
  180. package/dist/node_modules/css-select/lib/general.js +154 -0
  181. package/dist/node_modules/css-select/lib/general.js.map +1 -0
  182. package/dist/node_modules/css-select/lib/index.js +128 -0
  183. package/dist/node_modules/css-select/lib/index.js.map +1 -0
  184. package/dist/node_modules/css-select/lib/pseudo-selectors/aliases.js +40 -0
  185. package/dist/node_modules/css-select/lib/pseudo-selectors/aliases.js.map +1 -0
  186. package/dist/node_modules/css-select/lib/pseudo-selectors/filters.js +163 -0
  187. package/dist/node_modules/css-select/lib/pseudo-selectors/filters.js.map +1 -0
  188. package/dist/node_modules/css-select/lib/pseudo-selectors/index.js +71 -0
  189. package/dist/node_modules/css-select/lib/pseudo-selectors/index.js.map +1 -0
  190. package/dist/node_modules/css-select/lib/pseudo-selectors/pseudos.js +93 -0
  191. package/dist/node_modules/css-select/lib/pseudo-selectors/pseudos.js.map +1 -0
  192. package/dist/node_modules/css-select/lib/pseudo-selectors/subselects.js +111 -0
  193. package/dist/node_modules/css-select/lib/pseudo-selectors/subselects.js.map +1 -0
  194. package/dist/node_modules/css-select/lib/sort.js +78 -0
  195. package/dist/node_modules/css-select/lib/sort.js.map +1 -0
  196. package/dist/node_modules/css-what/lib/es/index.js +12 -0
  197. package/dist/node_modules/css-what/lib/es/index.js.map +1 -0
  198. package/dist/node_modules/css-what/lib/es/parse.js +349 -0
  199. package/dist/node_modules/css-what/lib/es/parse.js.map +1 -0
  200. package/dist/node_modules/css-what/lib/es/stringify.js +102 -0
  201. package/dist/node_modules/css-what/lib/es/stringify.js.map +1 -0
  202. package/dist/node_modules/css-what/lib/es/types.js +37 -0
  203. package/dist/node_modules/css-what/lib/es/types.js.map +1 -0
  204. package/dist/node_modules/dom-serializer/lib/foreignNames.js +117 -0
  205. package/dist/node_modules/dom-serializer/lib/foreignNames.js.map +1 -0
  206. package/dist/node_modules/dom-serializer/lib/index.js +207 -0
  207. package/dist/node_modules/dom-serializer/lib/index.js.map +1 -0
  208. package/dist/node_modules/dom-serializer/node_modules/entities/lib/decode.js +368 -0
  209. package/dist/node_modules/dom-serializer/node_modules/entities/lib/decode.js.map +1 -0
  210. package/dist/node_modules/dom-serializer/node_modules/entities/lib/decode_codepoint.js +70 -0
  211. package/dist/node_modules/dom-serializer/node_modules/entities/lib/decode_codepoint.js.map +1 -0
  212. package/dist/node_modules/dom-serializer/node_modules/entities/lib/encode.js +61 -0
  213. package/dist/node_modules/dom-serializer/node_modules/entities/lib/encode.js.map +1 -0
  214. package/dist/node_modules/dom-serializer/node_modules/entities/lib/escape.js +79 -0
  215. package/dist/node_modules/dom-serializer/node_modules/entities/lib/escape.js.map +1 -0
  216. package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/decode-data-html.js +18 -0
  217. package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/decode-data-html.js.map +1 -0
  218. package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/decode-data-xml.js +18 -0
  219. package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/decode-data-xml.js.map +1 -0
  220. package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/encode-html.js +19 -0
  221. package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/encode-html.js.map +1 -0
  222. package/dist/node_modules/dom-serializer/node_modules/entities/lib/index.js +139 -0
  223. package/dist/node_modules/dom-serializer/node_modules/entities/lib/index.js.map +1 -0
  224. package/dist/node_modules/domelementtype/lib/index.js +40 -0
  225. package/dist/node_modules/domelementtype/lib/index.js.map +1 -0
  226. package/dist/node_modules/domhandler/lib/index.js +167 -0
  227. package/dist/node_modules/domhandler/lib/index.js.map +1 -0
  228. package/dist/node_modules/domhandler/lib/node.js +439 -0
  229. package/dist/node_modules/domhandler/lib/node.js.map +1 -0
  230. package/dist/node_modules/domutils/lib/feeds.js +146 -0
  231. package/dist/node_modules/domutils/lib/feeds.js.map +1 -0
  232. package/dist/node_modules/domutils/lib/helpers.js +97 -0
  233. package/dist/node_modules/domutils/lib/helpers.js.map +1 -0
  234. package/dist/node_modules/domutils/lib/index.js +65 -0
  235. package/dist/node_modules/domutils/lib/index.js.map +1 -0
  236. package/dist/node_modules/domutils/lib/legacy.js +124 -0
  237. package/dist/node_modules/domutils/lib/legacy.js.map +1 -0
  238. package/dist/node_modules/domutils/lib/manipulation.js +107 -0
  239. package/dist/node_modules/domutils/lib/manipulation.js.map +1 -0
  240. package/dist/node_modules/domutils/lib/querying.js +102 -0
  241. package/dist/node_modules/domutils/lib/querying.js.map +1 -0
  242. package/dist/node_modules/domutils/lib/stringify.js +65 -0
  243. package/dist/node_modules/domutils/lib/stringify.js.map +1 -0
  244. package/dist/node_modules/domutils/lib/traversal.js +69 -0
  245. package/dist/node_modules/domutils/lib/traversal.js.map +1 -0
  246. package/dist/node_modules/he/he.js +256 -0
  247. package/dist/node_modules/he/he.js.map +1 -0
  248. package/dist/node_modules/node-html-parser/dist/back.js +16 -0
  249. package/dist/node_modules/node-html-parser/dist/back.js.map +1 -0
  250. package/dist/node_modules/node-html-parser/dist/index.js +48 -0
  251. package/dist/node_modules/node-html-parser/dist/index.js.map +1 -0
  252. package/dist/node_modules/node-html-parser/dist/matcher.js +112 -0
  253. package/dist/node_modules/node-html-parser/dist/matcher.js.map +1 -0
  254. package/dist/node_modules/node-html-parser/dist/nodes/comment.js +41 -0
  255. package/dist/node_modules/node-html-parser/dist/nodes/comment.js.map +1 -0
  256. package/dist/node_modules/node-html-parser/dist/nodes/html.js +1048 -0
  257. package/dist/node_modules/node-html-parser/dist/nodes/html.js.map +1 -0
  258. package/dist/node_modules/node-html-parser/dist/nodes/node.js +49 -0
  259. package/dist/node_modules/node-html-parser/dist/nodes/node.js.map +1 -0
  260. package/dist/node_modules/node-html-parser/dist/nodes/text.js +106 -0
  261. package/dist/node_modules/node-html-parser/dist/nodes/text.js.map +1 -0
  262. package/dist/node_modules/node-html-parser/dist/nodes/type.js +19 -0
  263. package/dist/node_modules/node-html-parser/dist/nodes/type.js.map +1 -0
  264. package/dist/node_modules/node-html-parser/dist/parse.js +20 -0
  265. package/dist/node_modules/node-html-parser/dist/parse.js.map +1 -0
  266. package/dist/node_modules/node-html-parser/dist/valid.js +19 -0
  267. package/dist/node_modules/node-html-parser/dist/valid.js.map +1 -0
  268. package/dist/node_modules/node-html-parser/dist/void-tag.js +36 -0
  269. package/dist/node_modules/node-html-parser/dist/void-tag.js.map +1 -0
  270. package/dist/node_modules/nth-check/lib/compile.js +76 -0
  271. package/dist/node_modules/nth-check/lib/compile.js.map +1 -0
  272. package/dist/node_modules/nth-check/lib/index.js +36 -0
  273. package/dist/node_modules/nth-check/lib/index.js.map +1 -0
  274. package/dist/node_modules/nth-check/lib/parse.js +69 -0
  275. package/dist/node_modules/nth-check/lib/parse.js.map +1 -0
  276. package/package.json +2 -2
  277. package/dist/fulltext/attach-shared.d.ts.map +0 -1
  278. package/dist/fulltext/attach-shared.js.map +0 -1
  279. package/dist/fulltext/citation-key.d.ts +0 -15
  280. package/dist/fulltext/citation-key.d.ts.map +0 -1
  281. package/dist/fulltext/citation-key.js.map +0 -1
  282. package/dist/fulltext/convert/index.d.ts +0 -20
  283. package/dist/fulltext/convert/index.d.ts.map +0 -1
  284. package/dist/fulltext/convert/index.js +0 -50
  285. package/dist/fulltext/convert/index.js.map +0 -1
  286. package/dist/fulltext/convert/jats-parser.d.ts +0 -36
  287. package/dist/fulltext/convert/jats-parser.d.ts.map +0 -1
  288. package/dist/fulltext/convert/jats-parser.js +0 -887
  289. package/dist/fulltext/convert/jats-parser.js.map +0 -1
  290. package/dist/fulltext/convert/markdown-writer.d.ts +0 -6
  291. package/dist/fulltext/convert/markdown-writer.d.ts.map +0 -1
  292. package/dist/fulltext/convert/markdown-writer.js.map +0 -1
  293. package/dist/fulltext/convert/types.d.ts +0 -141
  294. package/dist/fulltext/convert/types.d.ts.map +0 -1
  295. package/dist/fulltext/discovery/arxiv.d.ts +0 -11
  296. package/dist/fulltext/discovery/arxiv.d.ts.map +0 -1
  297. package/dist/fulltext/discovery/arxiv.js.map +0 -1
  298. package/dist/fulltext/discovery/core.d.ts +0 -11
  299. package/dist/fulltext/discovery/core.d.ts.map +0 -1
  300. package/dist/fulltext/discovery/core.js.map +0 -1
  301. package/dist/fulltext/discovery/index.d.ts +0 -28
  302. package/dist/fulltext/discovery/index.d.ts.map +0 -1
  303. package/dist/fulltext/discovery/index.js +0 -75
  304. package/dist/fulltext/discovery/index.js.map +0 -1
  305. package/dist/fulltext/discovery/pmc.d.ts +0 -19
  306. package/dist/fulltext/discovery/pmc.d.ts.map +0 -1
  307. package/dist/fulltext/discovery/pmc.js.map +0 -1
  308. package/dist/fulltext/discovery/unpaywall.d.ts +0 -11
  309. package/dist/fulltext/discovery/unpaywall.d.ts.map +0 -1
  310. package/dist/fulltext/discovery/unpaywall.js.map +0 -1
  311. package/dist/fulltext/download/downloader.d.ts +0 -21
  312. package/dist/fulltext/download/downloader.d.ts.map +0 -1
  313. package/dist/fulltext/download/downloader.js +0 -59
  314. package/dist/fulltext/download/downloader.js.map +0 -1
  315. package/dist/fulltext/download/orchestrator.d.ts +0 -33
  316. package/dist/fulltext/download/orchestrator.d.ts.map +0 -1
  317. package/dist/fulltext/download/orchestrator.js +0 -125
  318. package/dist/fulltext/download/orchestrator.js.map +0 -1
  319. package/dist/fulltext/download/pmc-xml.d.ts +0 -13
  320. package/dist/fulltext/download/pmc-xml.d.ts.map +0 -1
  321. package/dist/fulltext/download/pmc-xml.js.map +0 -1
  322. package/dist/fulltext/meta.d.ts +0 -25
  323. package/dist/fulltext/meta.d.ts.map +0 -1
  324. package/dist/fulltext/meta.js.map +0 -1
  325. package/dist/fulltext/paths.d.ts +0 -12
  326. package/dist/fulltext/paths.d.ts.map +0 -1
  327. package/dist/fulltext/paths.js.map +0 -1
  328. package/dist/fulltext/readme.d.ts +0 -4
  329. package/dist/fulltext/readme.d.ts.map +0 -1
  330. package/dist/fulltext/readme.js.map +0 -1
  331. package/dist/fulltext/types.d.ts +0 -90
  332. package/dist/fulltext/types.d.ts.map +0 -1
  333. /package/dist/{fulltext → integration}/attach-shared.js +0 -0
  334. /package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/paths.js +0 -0
@@ -1,8 +1,10 @@
1
1
  import { readFile, access, readdir } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { parse } from "yaml";
4
- import { discoverOA } from "../../../fulltext/discovery/index.js";
5
- import { loadMeta, saveMeta } from "../../../fulltext/meta.js";
4
+ import { discoverOA } from "../../../node_modules/@ncukondo/academic-fulltext/dist/discovery/index.js";
5
+ import { loadMeta, saveMeta } from "../../../node_modules/@ncukondo/academic-fulltext/dist/meta.js";
6
+ import "../../../node_modules/@ncukondo/academic-fulltext/dist/convert/jats-parser.js";
7
+ import "node:crypto";
6
8
  const DEFAULT_CONCURRENCY = 3;
7
9
  async function loadIncludedArticles(sessionDir) {
8
10
  const reviewsPath = join(sessionDir, ".internal", "reviews.yaml");
@@ -1 +1 @@
1
- {"version":3,"file":"check.js","sources":["../../../../src/cli/commands/fulltext/check.ts"],"sourcesContent":["/**\n * Fulltext check command.\n * Checks OA availability for included articles in a session.\n */\n\nimport { readFile, readdir, access } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { discoverOA, type DiscoveryConfig, type DiscoveryArticle } from '../../../fulltext/discovery/index';\nimport { loadMeta, saveMeta } from '../../../fulltext/meta';\nimport type { OAStatus } from '../../../fulltext/types';\nimport type { ReviewFile, ArticleEntry } from '../review/types';\n\n/** Default concurrency for parallel article processing */\nconst DEFAULT_CONCURRENCY = 3;\n\nexport interface FulltextCheckOptions {\n sessionDir: string;\n config: DiscoveryConfig;\n concurrency?: number;\n}\n\nexport interface FulltextCheckArticleResult {\n doi?: string;\n pmid?: string;\n title: string;\n oaStatus: OAStatus;\n locationCount: number;\n}\n\nexport interface FulltextCheckResult {\n summary: {\n total: number;\n open: number;\n closed: number;\n unknown: number;\n };\n articles: FulltextCheckArticleResult[];\n}\n\n/**\n * Load included articles from reviews.yaml\n */\nasync function loadIncludedArticles(sessionDir: string): Promise<ArticleEntry[]> {\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(content) as ReviewFile;\n return reviewFile.articles.filter((a) => a.finalDecision === 'include');\n}\n\n/**\n * Try to find a meta.json matching an article in the fulltext directory.\n * Returns the dirName if found, null otherwise.\n */\nasync function findArticleDir(\n sessionDir: string,\n article: ArticleEntry\n): Promise<string | null> {\n const fulltextDir = join(sessionDir, 'fulltext');\n try {\n await access(fulltextDir);\n } catch {\n return null;\n }\n\n let entries: string[];\n try {\n const dirEntries = await readdir(fulltextDir);\n entries = dirEntries.map(String);\n } catch {\n return null;\n }\n\n for (const entry of entries) {\n try {\n const metaPath = join(fulltextDir, entry, 'meta.json');\n const meta = await loadMeta(metaPath);\n // Match by DOI or PMID\n if (article.doi && meta.doi === article.doi) return entry;\n if (article.pmid && meta.pmid === article.pmid) return entry;\n } catch {\n // Skip entries without valid meta.json\n }\n }\n return null;\n}\n\n/**\n * Process a single article: run OA discovery and optionally update meta.json.\n */\nasync function processArticle(\n article: ArticleEntry,\n sessionDir: string,\n config: DiscoveryConfig\n): Promise<FulltextCheckArticleResult> {\n const discoveryArticle: DiscoveryArticle = {};\n if (article.doi) discoveryArticle.doi = article.doi;\n if (article.pmid) discoveryArticle.pmid = article.pmid;\n if (article.arxivId) discoveryArticle.arxivId = article.arxivId;\n const discoveryResult = await discoverOA(discoveryArticle, config);\n\n const articleResult: FulltextCheckArticleResult = {\n title: article.title,\n oaStatus: discoveryResult.oaStatus,\n locationCount: discoveryResult.locations.length,\n };\n if (article.doi) articleResult.doi = article.doi;\n if (article.pmid) articleResult.pmid = article.pmid;\n\n // Try to update meta.json if a fulltext directory exists for this article\n const dirName = await findArticleDir(sessionDir, article);\n if (dirName) {\n try {\n const metaPath = join(sessionDir, 'fulltext', dirName, 'meta.json');\n const meta = await loadMeta(metaPath);\n meta.oaStatus = discoveryResult.oaStatus;\n meta.oaLocations = discoveryResult.locations;\n meta.checkedAt = new Date().toISOString();\n await saveMeta(metaPath, meta);\n } catch {\n // Meta update is best-effort\n }\n }\n\n return articleResult;\n}\n\n/**\n * Run async tasks with a concurrency limit.\n */\nasync function runWithConcurrency<T>(\n tasks: Array<() => Promise<T>>,\n concurrency: number\n): Promise<PromiseSettledResult<T>[]> {\n const results: PromiseSettledResult<T>[] = new Array(tasks.length);\n let nextIndex = 0;\n\n async function worker(): Promise<void> {\n while (nextIndex < tasks.length) {\n const index = nextIndex++;\n try {\n const value = await tasks[index]!();\n results[index] = { status: 'fulfilled', value };\n } catch (reason) {\n results[index] = { status: 'rejected', reason };\n }\n }\n }\n\n const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker());\n await Promise.all(workers);\n return results;\n}\n\n/**\n * Execute the fulltext check command.\n * Checks OA availability for all included articles in a session,\n * processing articles in parallel with a concurrency limit.\n */\nexport async function executeFulltextCheck(\n options: FulltextCheckOptions\n): Promise<FulltextCheckResult> {\n const { sessionDir, config, concurrency = DEFAULT_CONCURRENCY } = options;\n\n // Load included articles\n const articles = await loadIncludedArticles(sessionDir);\n\n const summary = { total: articles.length, open: 0, closed: 0, unknown: 0 };\n\n const tasks = articles.map(\n (article) => () => processArticle(article, sessionDir, config)\n );\n\n const settled = await runWithConcurrency(tasks, concurrency);\n\n const results: FulltextCheckArticleResult[] = [];\n for (const result of settled) {\n if (result.status === 'fulfilled') {\n results.push(result.value);\n switch (result.value.oaStatus) {\n case 'open':\n summary.open++;\n break;\n case 'closed':\n summary.closed++;\n break;\n case 'unknown':\n summary.unknown++;\n break;\n }\n }\n }\n\n return { summary, articles: results };\n}\n"],"names":["parseYaml"],"mappings":"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"}
1
+ {"version":3,"file":"check.js","sources":["../../../../src/cli/commands/fulltext/check.ts"],"sourcesContent":["/**\n * Fulltext check command.\n * Checks OA availability for included articles in a session.\n */\n\nimport { readFile, readdir, access } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { discoverOA, loadMeta, saveMeta, type DiscoveryConfig, type DiscoveryArticle, type OAStatus } from '@ncukondo/academic-fulltext';\nimport type { ReviewFile, ArticleEntry } from '../review/types';\n\n/** Default concurrency for parallel article processing */\nconst DEFAULT_CONCURRENCY = 3;\n\nexport interface FulltextCheckOptions {\n sessionDir: string;\n config: DiscoveryConfig;\n concurrency?: number;\n}\n\nexport interface FulltextCheckArticleResult {\n doi?: string;\n pmid?: string;\n title: string;\n oaStatus: OAStatus;\n locationCount: number;\n}\n\nexport interface FulltextCheckResult {\n summary: {\n total: number;\n open: number;\n closed: number;\n unknown: number;\n };\n articles: FulltextCheckArticleResult[];\n}\n\n/**\n * Load included articles from reviews.yaml\n */\nasync function loadIncludedArticles(sessionDir: string): Promise<ArticleEntry[]> {\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(content) as ReviewFile;\n return reviewFile.articles.filter((a) => a.finalDecision === 'include');\n}\n\n/**\n * Try to find a meta.json matching an article in the fulltext directory.\n * Returns the dirName if found, null otherwise.\n */\nasync function findArticleDir(\n sessionDir: string,\n article: ArticleEntry\n): Promise<string | null> {\n const fulltextDir = join(sessionDir, 'fulltext');\n try {\n await access(fulltextDir);\n } catch {\n return null;\n }\n\n let entries: string[];\n try {\n const dirEntries = await readdir(fulltextDir);\n entries = dirEntries.map(String);\n } catch {\n return null;\n }\n\n for (const entry of entries) {\n try {\n const metaPath = join(fulltextDir, entry, 'meta.json');\n const meta = await loadMeta(metaPath);\n // Match by DOI or PMID\n if (article.doi && meta.doi === article.doi) return entry;\n if (article.pmid && meta.pmid === article.pmid) return entry;\n } catch {\n // Skip entries without valid meta.json\n }\n }\n return null;\n}\n\n/**\n * Process a single article: run OA discovery and optionally update meta.json.\n */\nasync function processArticle(\n article: ArticleEntry,\n sessionDir: string,\n config: DiscoveryConfig\n): Promise<FulltextCheckArticleResult> {\n const discoveryArticle: DiscoveryArticle = {};\n if (article.doi) discoveryArticle.doi = article.doi;\n if (article.pmid) discoveryArticle.pmid = article.pmid;\n if (article.arxivId) discoveryArticle.arxivId = article.arxivId;\n const discoveryResult = await discoverOA(discoveryArticle, config);\n\n const articleResult: FulltextCheckArticleResult = {\n title: article.title,\n oaStatus: discoveryResult.oaStatus,\n locationCount: discoveryResult.locations.length,\n };\n if (article.doi) articleResult.doi = article.doi;\n if (article.pmid) articleResult.pmid = article.pmid;\n\n // Try to update meta.json if a fulltext directory exists for this article\n const dirName = await findArticleDir(sessionDir, article);\n if (dirName) {\n try {\n const metaPath = join(sessionDir, 'fulltext', dirName, 'meta.json');\n const meta = await loadMeta(metaPath);\n meta.oaStatus = discoveryResult.oaStatus;\n meta.oaLocations = discoveryResult.locations;\n meta.checkedAt = new Date().toISOString();\n await saveMeta(metaPath, meta);\n } catch {\n // Meta update is best-effort\n }\n }\n\n return articleResult;\n}\n\n/**\n * Run async tasks with a concurrency limit.\n */\nasync function runWithConcurrency<T>(\n tasks: Array<() => Promise<T>>,\n concurrency: number\n): Promise<PromiseSettledResult<T>[]> {\n const results: PromiseSettledResult<T>[] = new Array(tasks.length);\n let nextIndex = 0;\n\n async function worker(): Promise<void> {\n while (nextIndex < tasks.length) {\n const index = nextIndex++;\n try {\n const value = await tasks[index]!();\n results[index] = { status: 'fulfilled', value };\n } catch (reason) {\n results[index] = { status: 'rejected', reason };\n }\n }\n }\n\n const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker());\n await Promise.all(workers);\n return results;\n}\n\n/**\n * Execute the fulltext check command.\n * Checks OA availability for all included articles in a session,\n * processing articles in parallel with a concurrency limit.\n */\nexport async function executeFulltextCheck(\n options: FulltextCheckOptions\n): Promise<FulltextCheckResult> {\n const { sessionDir, config, concurrency = DEFAULT_CONCURRENCY } = options;\n\n // Load included articles\n const articles = await loadIncludedArticles(sessionDir);\n\n const summary = { total: articles.length, open: 0, closed: 0, unknown: 0 };\n\n const tasks = articles.map(\n (article) => () => processArticle(article, sessionDir, config)\n );\n\n const settled = await runWithConcurrency(tasks, concurrency);\n\n const results: FulltextCheckArticleResult[] = [];\n for (const result of settled) {\n if (result.status === 'fulfilled') {\n results.push(result.value);\n switch (result.value.oaStatus) {\n case 'open':\n summary.open++;\n break;\n case 'closed':\n summary.closed++;\n break;\n case 'unknown':\n summary.unknown++;\n break;\n }\n }\n }\n\n return { summary, articles: results };\n}\n"],"names":["parseYaml"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/convert.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,oBAAoB,EAAE,CAAC;CAClC;AAwCD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,CAAC,CA6D/B"}
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/convert.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,oBAAoB,EAAE,CAAC;CAClC;AAwCD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,CAAC,CA+D/B"}
@@ -1,7 +1,8 @@
1
1
  import { readdir, stat } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
- import { getArticleDir, getMetaPath, getFulltextDir } from "../../../fulltext/paths.js";
4
- import { convertPmcXmlToMarkdown } from "../../../fulltext/convert/index.js";
3
+ import { getArticleDir, getMetaPath, getFulltextDir } from "../../../node_modules/@ncukondo/academic-fulltext/dist/paths.js";
4
+ import { convertPmcXmlToMarkdown, convertArxivHtmlToMarkdown } from "../../../node_modules/@ncukondo/academic-fulltext/dist/convert/index.js";
5
+ import "node:crypto";
5
6
  async function fileExists(path) {
6
7
  try {
7
8
  await stat(path);
@@ -36,9 +37,12 @@ async function executeFulltextConvert(options, sessionsDir) {
36
37
  for (const dirName of articleDirs) {
37
38
  const articleDir = getArticleDir(sessionDir, dirName);
38
39
  const xmlPath = join(articleDir, "fulltext.xml");
40
+ const htmlPath = join(articleDir, "fulltext.html");
39
41
  const mdPath = join(articleDir, "fulltext.md");
40
42
  const metaPath = getMetaPath(sessionDir, dirName);
41
- if (!await fileExists(xmlPath)) {
43
+ const hasXml = await fileExists(xmlPath);
44
+ const hasHtml = await fileExists(htmlPath);
45
+ if (!hasXml && !hasHtml) {
42
46
  continue;
43
47
  }
44
48
  if (await fileExists(mdPath)) {
@@ -47,11 +51,7 @@ async function executeFulltextConvert(options, sessionsDir) {
47
51
  continue;
48
52
  }
49
53
  const metaPathExists = await fileExists(metaPath);
50
- const result = await convertPmcXmlToMarkdown(
51
- xmlPath,
52
- mdPath,
53
- metaPathExists ? metaPath : void 0
54
- );
54
+ const result = hasXml ? await convertPmcXmlToMarkdown(xmlPath, mdPath, metaPathExists ? metaPath : void 0) : await convertArxivHtmlToMarkdown(htmlPath, mdPath, metaPathExists ? metaPath : void 0);
55
55
  if (result.success) {
56
56
  converted++;
57
57
  articles.push({
@@ -1 +1 @@
1
- {"version":3,"file":"convert.js","sources":["../../../../src/cli/commands/fulltext/convert.ts"],"sourcesContent":["/**\n * Fulltext convert command - converts PMC XML to Markdown.\n */\n\nimport { readdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { getFulltextDir, getArticleDir, getMetaPath } from '../../../fulltext/paths.js';\nimport { convertPmcXmlToMarkdown } from '../../../fulltext/convert/index.js';\n\nexport interface FulltextConvertOptions {\n sessionId: string;\n article?: string;\n}\n\nexport interface ConvertArticleResult {\n dirName: string;\n title: string;\n status: 'converted' | 'skipped' | 'failed';\n error?: string;\n}\n\nexport interface ConvertCommandResult {\n success: boolean;\n converted: number;\n skipped: number;\n failed: number;\n articles: ConvertArticleResult[];\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get list of article directory names to process.\n */\nasync function getArticleDirs(\n sessionDir: string,\n articleFilter?: string,\n): Promise<string[]> {\n const fulltextDir = getFulltextDir(sessionDir);\n\n if (articleFilter) {\n // Filter to specific article\n const articlePath = getArticleDir(sessionDir, articleFilter);\n if (await fileExists(articlePath)) {\n return [articleFilter];\n }\n return [];\n }\n\n // List all directories in fulltext/\n try {\n const entries = await readdir(fulltextDir, { withFileTypes: true });\n return entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nexport async function executeFulltextConvert(\n options: FulltextConvertOptions,\n sessionsDir: string,\n): Promise<ConvertCommandResult> {\n const sessionDir = join(sessionsDir, options.sessionId);\n const articleDirs = await getArticleDirs(sessionDir, options.article);\n\n const articles: ConvertArticleResult[] = [];\n let converted = 0;\n let skipped = 0;\n let failed = 0;\n\n for (const dirName of articleDirs) {\n const articleDir = getArticleDir(sessionDir, dirName);\n const xmlPath = join(articleDir, 'fulltext.xml');\n const mdPath = join(articleDir, 'fulltext.md');\n const metaPath = getMetaPath(sessionDir, dirName);\n\n // Check if XML exists\n if (!(await fileExists(xmlPath))) {\n continue; // No XML to convert, skip silently\n }\n\n // Check if already converted\n if (await fileExists(mdPath)) {\n skipped++;\n articles.push({ dirName, title: dirName, status: 'skipped' });\n continue;\n }\n\n // Convert\n const metaPathExists = await fileExists(metaPath);\n const result = await convertPmcXmlToMarkdown(\n xmlPath,\n mdPath,\n metaPathExists ? metaPath : undefined,\n );\n\n if (result.success) {\n converted++;\n articles.push({\n dirName,\n title: result.title ?? dirName,\n status: 'converted',\n });\n } else {\n failed++;\n const articleResult: ConvertArticleResult = {\n dirName,\n title: dirName,\n status: 'failed',\n };\n if (result.error) articleResult.error = result.error;\n articles.push(articleResult);\n }\n }\n\n return {\n success: failed === 0,\n converted,\n skipped,\n failed,\n articles,\n };\n}\n"],"names":[],"mappings":";;;;AA6BA,eAAe,WAAW,MAAgC;AACxD,MAAI;AACF,UAAM,KAAK,IAAI;AACf,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAe,eACb,YACA,eACmB;AACnB,QAAM,cAAc,eAAe,UAAU;AAE7C,MAAI,eAAe;AAEjB,UAAM,cAAc,cAAc,YAAY,aAAa;AAC3D,QAAI,MAAM,WAAW,WAAW,GAAG;AACjC,aAAO,CAAC,aAAa;AAAA,IACvB;AACA,WAAO,CAAA;AAAA,EACT;AAGA,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,aAAa,EAAE,eAAe,MAAM;AAClE,WAAO,QACJ,OAAO,CAAC,MAAM,EAAE,aAAa,EAC7B,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,EACtB,QAAQ;AACN,WAAO,CAAA;AAAA,EACT;AACF;AAEA,eAAsB,uBACpB,SACA,aAC+B;AAC/B,QAAM,aAAa,KAAK,aAAa,QAAQ,SAAS;AACtD,QAAM,cAAc,MAAM,eAAe,YAAY,QAAQ,OAAO;AAEpE,QAAM,WAAmC,CAAA;AACzC,MAAI,YAAY;AAChB,MAAI,UAAU;AACd,MAAI,SAAS;AAEb,aAAW,WAAW,aAAa;AACjC,UAAM,aAAa,cAAc,YAAY,OAAO;AACpD,UAAM,UAAU,KAAK,YAAY,cAAc;AAC/C,UAAM,SAAS,KAAK,YAAY,aAAa;AAC7C,UAAM,WAAW,YAAY,YAAY,OAAO;AAGhD,QAAI,CAAE,MAAM,WAAW,OAAO,GAAI;AAChC;AAAA,IACF;AAGA,QAAI,MAAM,WAAW,MAAM,GAAG;AAC5B;AACA,eAAS,KAAK,EAAE,SAAS,OAAO,SAAS,QAAQ,WAAW;AAC5D;AAAA,IACF;AAGA,UAAM,iBAAiB,MAAM,WAAW,QAAQ;AAChD,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,MACA,iBAAiB,WAAW;AAAA,IAAA;AAG9B,QAAI,OAAO,SAAS;AAClB;AACA,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,OAAO,OAAO,SAAS;AAAA,QACvB,QAAQ;AAAA,MAAA,CACT;AAAA,IACH,OAAO;AACL;AACA,YAAM,gBAAsC;AAAA,QAC1C;AAAA,QACA,OAAO;AAAA,QACP,QAAQ;AAAA,MAAA;AAEV,UAAI,OAAO,MAAO,eAAc,QAAQ,OAAO;AAC/C,eAAS,KAAK,aAAa;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS,WAAW;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"convert.js","sources":["../../../../src/cli/commands/fulltext/convert.ts"],"sourcesContent":["/**\n * Fulltext convert command - converts PMC XML to Markdown.\n */\n\nimport { readdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { getFulltextDir, getArticleDir, getMetaPath, convertPmcXmlToMarkdown, convertArxivHtmlToMarkdown } from '@ncukondo/academic-fulltext';\n\nexport interface FulltextConvertOptions {\n sessionId: string;\n article?: string;\n}\n\nexport interface ConvertArticleResult {\n dirName: string;\n title: string;\n status: 'converted' | 'skipped' | 'failed';\n error?: string;\n}\n\nexport interface ConvertCommandResult {\n success: boolean;\n converted: number;\n skipped: number;\n failed: number;\n articles: ConvertArticleResult[];\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get list of article directory names to process.\n */\nasync function getArticleDirs(\n sessionDir: string,\n articleFilter?: string,\n): Promise<string[]> {\n const fulltextDir = getFulltextDir(sessionDir);\n\n if (articleFilter) {\n // Filter to specific article\n const articlePath = getArticleDir(sessionDir, articleFilter);\n if (await fileExists(articlePath)) {\n return [articleFilter];\n }\n return [];\n }\n\n // List all directories in fulltext/\n try {\n const entries = await readdir(fulltextDir, { withFileTypes: true });\n return entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nexport async function executeFulltextConvert(\n options: FulltextConvertOptions,\n sessionsDir: string,\n): Promise<ConvertCommandResult> {\n const sessionDir = join(sessionsDir, options.sessionId);\n const articleDirs = await getArticleDirs(sessionDir, options.article);\n\n const articles: ConvertArticleResult[] = [];\n let converted = 0;\n let skipped = 0;\n let failed = 0;\n\n for (const dirName of articleDirs) {\n const articleDir = getArticleDir(sessionDir, dirName);\n const xmlPath = join(articleDir, 'fulltext.xml');\n const htmlPath = join(articleDir, 'fulltext.html');\n const mdPath = join(articleDir, 'fulltext.md');\n const metaPath = getMetaPath(sessionDir, dirName);\n\n const hasXml = await fileExists(xmlPath);\n const hasHtml = await fileExists(htmlPath);\n\n // No convertible source found\n if (!hasXml && !hasHtml) {\n continue;\n }\n\n // Check if already converted\n if (await fileExists(mdPath)) {\n skipped++;\n articles.push({ dirName, title: dirName, status: 'skipped' });\n continue;\n }\n\n // Convert: prefer PMC XML, fall back to arXiv HTML\n const metaPathExists = await fileExists(metaPath);\n const result = hasXml\n ? await convertPmcXmlToMarkdown(xmlPath, mdPath, metaPathExists ? metaPath : undefined)\n : await convertArxivHtmlToMarkdown(htmlPath, mdPath, metaPathExists ? metaPath : undefined);\n\n if (result.success) {\n converted++;\n articles.push({\n dirName,\n title: result.title ?? dirName,\n status: 'converted',\n });\n } else {\n failed++;\n const articleResult: ConvertArticleResult = {\n dirName,\n title: dirName,\n status: 'failed',\n };\n if (result.error) articleResult.error = result.error;\n articles.push(articleResult);\n }\n }\n\n return {\n success: failed === 0,\n converted,\n skipped,\n failed,\n articles,\n };\n}\n"],"names":[],"mappings":";;;;;AA4BA,eAAe,WAAW,MAAgC;AACxD,MAAI;AACF,UAAM,KAAK,IAAI;AACf,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAe,eACb,YACA,eACmB;AACnB,QAAM,cAAc,eAAe,UAAU;AAE7C,MAAI,eAAe;AAEjB,UAAM,cAAc,cAAc,YAAY,aAAa;AAC3D,QAAI,MAAM,WAAW,WAAW,GAAG;AACjC,aAAO,CAAC,aAAa;AAAA,IACvB;AACA,WAAO,CAAA;AAAA,EACT;AAGA,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,aAAa,EAAE,eAAe,MAAM;AAClE,WAAO,QACJ,OAAO,CAAC,MAAM,EAAE,aAAa,EAC7B,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,EACtB,QAAQ;AACN,WAAO,CAAA;AAAA,EACT;AACF;AAEA,eAAsB,uBACpB,SACA,aAC+B;AAC/B,QAAM,aAAa,KAAK,aAAa,QAAQ,SAAS;AACtD,QAAM,cAAc,MAAM,eAAe,YAAY,QAAQ,OAAO;AAEpE,QAAM,WAAmC,CAAA;AACzC,MAAI,YAAY;AAChB,MAAI,UAAU;AACd,MAAI,SAAS;AAEb,aAAW,WAAW,aAAa;AACjC,UAAM,aAAa,cAAc,YAAY,OAAO;AACpD,UAAM,UAAU,KAAK,YAAY,cAAc;AAC/C,UAAM,WAAW,KAAK,YAAY,eAAe;AACjD,UAAM,SAAS,KAAK,YAAY,aAAa;AAC7C,UAAM,WAAW,YAAY,YAAY,OAAO;AAEhD,UAAM,SAAS,MAAM,WAAW,OAAO;AACvC,UAAM,UAAU,MAAM,WAAW,QAAQ;AAGzC,QAAI,CAAC,UAAU,CAAC,SAAS;AACvB;AAAA,IACF;AAGA,QAAI,MAAM,WAAW,MAAM,GAAG;AAC5B;AACA,eAAS,KAAK,EAAE,SAAS,OAAO,SAAS,QAAQ,WAAW;AAC5D;AAAA,IACF;AAGA,UAAM,iBAAiB,MAAM,WAAW,QAAQ;AAChD,UAAM,SAAS,SACX,MAAM,wBAAwB,SAAS,QAAQ,iBAAiB,WAAW,MAAS,IACpF,MAAM,2BAA2B,UAAU,QAAQ,iBAAiB,WAAW,MAAS;AAE5F,QAAI,OAAO,SAAS;AAClB;AACA,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,OAAO,OAAO,SAAS;AAAA,QACvB,QAAQ;AAAA,MAAA,CACT;AAAA,IACH,OAAO;AACL;AACA,YAAM,gBAAsC;AAAA,QAC1C;AAAA,QACA,OAAO;AAAA,QACP,QAAQ;AAAA,MAAA;AAEV,UAAI,OAAO,MAAO,eAAc,QAAQ,OAAO;AAC/C,eAAS,KAAK,aAAa;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS,WAAW;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/fetch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CA8G9B"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/fetch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAkH9B"}
@@ -1,8 +1,10 @@
1
1
  import { join } from "node:path";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
3
  import { parse, stringify } from "yaml";
4
- import { loadMeta } from "../../../fulltext/meta.js";
5
- import { fetchAllFulltexts } from "../../../fulltext/download/orchestrator.js";
4
+ import { fetchAllFulltexts } from "../../../node_modules/@ncukondo/academic-fulltext/dist/download/orchestrator.js";
5
+ import "../../../node_modules/@ncukondo/academic-fulltext/dist/convert/jats-parser.js";
6
+ import { loadMeta } from "../../../node_modules/@ncukondo/academic-fulltext/dist/meta.js";
7
+ import "node:crypto";
6
8
  import { executeFulltextConvert } from "./convert.js";
7
9
  async function executeFulltextFetch(options) {
8
10
  const { sessionId, sessionsDir, source, dryRun, concurrency = 3, retryDelay = 1e3 } = options;
@@ -42,7 +44,8 @@ async function executeFulltextFetch(options) {
42
44
  toFetch.push({
43
45
  dirName,
44
46
  oaLocations: locations,
45
- ...meta.pmcid ? { pmcid: meta.pmcid } : {}
47
+ ...meta.pmcid ? { pmcid: meta.pmcid } : {},
48
+ ...meta.arxivId ? { arxivId: meta.arxivId } : {}
46
49
  });
47
50
  }
48
51
  if (dryRun) {
@@ -65,10 +68,10 @@ async function executeFulltextFetch(options) {
65
68
  const results = await fetchAllFulltexts(toFetch, sessionDir, fetchOpts);
66
69
  const convertMarkdown = options.convertMarkdown !== false;
67
70
  if (convertMarkdown) {
68
- const xmlArticles = results.filter(
69
- (r) => r.status === "downloaded" && r.filesDownloaded?.includes("fulltext.xml")
71
+ const convertibleArticles = results.filter(
72
+ (r) => r.status === "downloaded" && (r.filesDownloaded?.includes("fulltext.xml") || r.filesDownloaded?.includes("fulltext.html"))
70
73
  );
71
- for (const article of xmlArticles) {
74
+ for (const article of convertibleArticles) {
72
75
  await executeFulltextConvert({ sessionId, article: article.dirName }, sessionsDir);
73
76
  }
74
77
  }
@@ -111,6 +114,7 @@ async function updateReviews(sessionDir, results) {
111
114
  article.fulltext.hasFiles = {
112
115
  pdf: article.fulltext.hasFiles.pdf || result.filesDownloaded.includes("fulltext.pdf"),
113
116
  xml: article.fulltext.hasFiles.xml || result.filesDownloaded.includes("fulltext.xml"),
117
+ html: article.fulltext.hasFiles.html || result.filesDownloaded.includes("fulltext.html"),
114
118
  markdown: article.fulltext.hasFiles.markdown || result.filesDownloaded.includes("fulltext.md")
115
119
  };
116
120
  changed = true;
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","sources":["../../../../src/cli/commands/fulltext/fetch.ts"],"sourcesContent":["/**\n * Fulltext fetch command - downloads OA fulltexts for session articles.\n */\n\nimport { join } from 'node:path';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { ReviewFile } from '../review/types.js';\nimport type { FulltextMeta } from '../../../fulltext/types.js';\nimport { loadMeta } from '../../../fulltext/meta.js';\nimport { fetchAllFulltexts, type FetchArticle } from '../../../fulltext/download/orchestrator.js';\nimport { executeFulltextConvert } from './convert.js';\nexport interface FulltextFetchOptions {\n sessionId: string;\n sessionsDir: string;\n source?: string[];\n convertMarkdown?: boolean;\n dryRun?: boolean;\n concurrency?: number;\n retryDelay?: number;\n}\n\nexport interface FulltextFetchArticle {\n dirName: string;\n title: string;\n oaStatus: string;\n locationCount: number;\n}\n\nexport interface FulltextFetchResult {\n summary: {\n total: number;\n downloaded: number;\n failed: number;\n skipped: number;\n };\n articles: FulltextFetchArticle[];\n dryRun?: boolean;\n}\n\n/**\n * Execute the fulltext fetch command.\n * Loads articles from reviews.yaml, checks OA status via meta.json,\n * and downloads PDFs/XMLs from OA sources.\n */\nexport async function executeFulltextFetch(\n options: FulltextFetchOptions,\n): Promise<FulltextFetchResult> {\n const { sessionId, sessionsDir, source, dryRun, concurrency = 3, retryDelay = 1000 } = options;\n const sessionDir = join(sessionsDir, sessionId);\n\n // Load reviews.yaml to find articles with fulltext dirs\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const reviewContent = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(reviewContent) as ReviewFile;\n\n const includedArticles = reviewFile.articles.filter(\n (a) => a.finalDecision === 'include' && a.fulltext?.dirName,\n );\n\n // Load meta.json for each article to check OA status\n const toFetch: FetchArticle[] = [];\n const allArticles: FulltextFetchArticle[] = [];\n let skippedCount = 0;\n\n const fulltextDir = join(sessionDir, 'fulltext');\n for (const article of includedArticles) {\n const dirName = article.fulltext?.dirName;\n if (!dirName) continue;\n\n let meta: FulltextMeta;\n try {\n const metaPath = join(fulltextDir, dirName, 'meta.json');\n meta = await loadMeta(metaPath);\n } catch {\n skippedCount++;\n continue;\n }\n\n const locations = meta.oaLocations ?? [];\n allArticles.push({\n dirName,\n title: meta.title,\n oaStatus: meta.oaStatus,\n locationCount: locations.length,\n });\n\n // Skip if already has PDF or no OA locations\n if (meta.files.pdf || locations.length === 0) {\n skippedCount++;\n continue;\n }\n\n toFetch.push({\n dirName,\n oaLocations: locations,\n ...(meta.pmcid ? { pmcid: meta.pmcid } : {}),\n });\n }\n\n // Dry run: return what would be downloaded\n if (dryRun) {\n return {\n summary: {\n total: includedArticles.length,\n downloaded: 0,\n failed: 0,\n skipped: skippedCount,\n },\n articles: allArticles,\n dryRun: true,\n };\n }\n\n // Execute downloads\n const fetchOpts: Parameters<typeof fetchAllFulltexts>[2] = {\n concurrency,\n retryDelay,\n };\n if (source) fetchOpts.sourceFilter = source;\n const results = await fetchAllFulltexts(toFetch, sessionDir, fetchOpts);\n\n // Auto-convert PMC XML to Markdown (unless --no-convert-markdown)\n const convertMarkdown = options.convertMarkdown !== false;\n if (convertMarkdown) {\n const xmlArticles = results.filter(\n (r) => r.status === 'downloaded' && r.filesDownloaded?.includes('fulltext.xml'),\n );\n for (const article of xmlArticles) {\n await executeFulltextConvert({ sessionId, article: article.dirName }, sessionsDir);\n }\n }\n\n let downloadedCount = 0;\n let failedCount = 0;\n for (const result of results) {\n if (result.status === 'downloaded') {\n downloadedCount++;\n } else if (result.status === 'failed') {\n failedCount++;\n } else if (result.status === 'skipped') {\n skippedCount++;\n }\n }\n\n // Update reviews.yaml with download results\n await updateReviews(sessionDir, results);\n\n return {\n summary: {\n total: includedArticles.length,\n downloaded: downloadedCount,\n failed: failedCount,\n skipped: skippedCount,\n },\n articles: allArticles,\n };\n}\n\n/**\n * Update reviews.yaml fulltext.hasFiles after downloads.\n */\nasync function updateReviews(\n sessionDir: string,\n results: Array<{ dirName: string; status: string; filesDownloaded?: string[] }>,\n): Promise<void> {\n const downloadedDirs = new Set(\n results\n .filter((r) => r.status === 'downloaded')\n .map((r) => r.dirName),\n );\n\n if (downloadedDirs.size === 0) return;\n\n try {\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(content) as ReviewFile;\n let changed = false;\n\n for (const article of reviewFile.articles) {\n if (article.fulltext?.dirName && downloadedDirs.has(article.fulltext.dirName)) {\n const result = results.find((r) => r.dirName === article.fulltext?.dirName);\n if (result?.filesDownloaded) {\n article.fulltext.hasFiles = {\n pdf: article.fulltext.hasFiles.pdf || result.filesDownloaded.includes('fulltext.pdf'),\n xml: article.fulltext.hasFiles.xml || result.filesDownloaded.includes('fulltext.xml'),\n markdown: article.fulltext.hasFiles.markdown || result.filesDownloaded.includes('fulltext.md'),\n };\n changed = true;\n }\n }\n }\n\n if (changed) {\n await writeFile(reviewsPath, stringifyYaml(reviewFile), 'utf-8');\n }\n } catch (e) {\n console.error('Warning: failed to update reviews.yaml:', e);\n }\n}\n"],"names":["parseYaml","stringifyYaml"],"mappings":"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"}
1
+ {"version":3,"file":"fetch.js","sources":["../../../../src/cli/commands/fulltext/fetch.ts"],"sourcesContent":["/**\n * Fulltext fetch command - downloads OA fulltexts for session articles.\n */\n\nimport { join } from 'node:path';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { ReviewFile } from '../review/types.js';\nimport { loadMeta, fetchAllFulltexts, type FulltextMeta, type FetchArticle } from '@ncukondo/academic-fulltext';\nimport { executeFulltextConvert } from './convert.js';\nexport interface FulltextFetchOptions {\n sessionId: string;\n sessionsDir: string;\n source?: string[];\n convertMarkdown?: boolean;\n dryRun?: boolean;\n concurrency?: number;\n retryDelay?: number;\n}\n\nexport interface FulltextFetchArticle {\n dirName: string;\n title: string;\n oaStatus: string;\n locationCount: number;\n}\n\nexport interface FulltextFetchResult {\n summary: {\n total: number;\n downloaded: number;\n failed: number;\n skipped: number;\n };\n articles: FulltextFetchArticle[];\n dryRun?: boolean;\n}\n\n/**\n * Execute the fulltext fetch command.\n * Loads articles from reviews.yaml, checks OA status via meta.json,\n * and downloads PDFs/XMLs from OA sources.\n */\nexport async function executeFulltextFetch(\n options: FulltextFetchOptions,\n): Promise<FulltextFetchResult> {\n const { sessionId, sessionsDir, source, dryRun, concurrency = 3, retryDelay = 1000 } = options;\n const sessionDir = join(sessionsDir, sessionId);\n\n // Load reviews.yaml to find articles with fulltext dirs\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const reviewContent = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(reviewContent) as ReviewFile;\n\n const includedArticles = reviewFile.articles.filter(\n (a) => a.finalDecision === 'include' && a.fulltext?.dirName,\n );\n\n // Load meta.json for each article to check OA status\n const toFetch: FetchArticle[] = [];\n const allArticles: FulltextFetchArticle[] = [];\n let skippedCount = 0;\n\n const fulltextDir = join(sessionDir, 'fulltext');\n for (const article of includedArticles) {\n const dirName = article.fulltext?.dirName;\n if (!dirName) continue;\n\n let meta: FulltextMeta;\n try {\n const metaPath = join(fulltextDir, dirName, 'meta.json');\n meta = await loadMeta(metaPath);\n } catch {\n skippedCount++;\n continue;\n }\n\n const locations = meta.oaLocations ?? [];\n allArticles.push({\n dirName,\n title: meta.title,\n oaStatus: meta.oaStatus,\n locationCount: locations.length,\n });\n\n // Skip if already has PDF or no OA locations\n if (meta.files.pdf || locations.length === 0) {\n skippedCount++;\n continue;\n }\n\n toFetch.push({\n dirName,\n oaLocations: locations,\n ...(meta.pmcid ? { pmcid: meta.pmcid } : {}),\n ...(meta.arxivId ? { arxivId: meta.arxivId } : {}),\n });\n }\n\n // Dry run: return what would be downloaded\n if (dryRun) {\n return {\n summary: {\n total: includedArticles.length,\n downloaded: 0,\n failed: 0,\n skipped: skippedCount,\n },\n articles: allArticles,\n dryRun: true,\n };\n }\n\n // Execute downloads\n const fetchOpts: Parameters<typeof fetchAllFulltexts>[2] = {\n concurrency,\n retryDelay,\n };\n if (source) fetchOpts.sourceFilter = source;\n const results = await fetchAllFulltexts(toFetch, sessionDir, fetchOpts);\n\n // Auto-convert PMC XML / arXiv HTML to Markdown (unless --no-convert-markdown)\n const convertMarkdown = options.convertMarkdown !== false;\n if (convertMarkdown) {\n const convertibleArticles = results.filter(\n (r) => r.status === 'downloaded' && (\n r.filesDownloaded?.includes('fulltext.xml') ||\n r.filesDownloaded?.includes('fulltext.html')\n ),\n );\n for (const article of convertibleArticles) {\n await executeFulltextConvert({ sessionId, article: article.dirName }, sessionsDir);\n }\n }\n\n let downloadedCount = 0;\n let failedCount = 0;\n for (const result of results) {\n if (result.status === 'downloaded') {\n downloadedCount++;\n } else if (result.status === 'failed') {\n failedCount++;\n } else if (result.status === 'skipped') {\n skippedCount++;\n }\n }\n\n // Update reviews.yaml with download results\n await updateReviews(sessionDir, results);\n\n return {\n summary: {\n total: includedArticles.length,\n downloaded: downloadedCount,\n failed: failedCount,\n skipped: skippedCount,\n },\n articles: allArticles,\n };\n}\n\n/**\n * Update reviews.yaml fulltext.hasFiles after downloads.\n */\nasync function updateReviews(\n sessionDir: string,\n results: Array<{ dirName: string; status: string; filesDownloaded?: string[] }>,\n): Promise<void> {\n const downloadedDirs = new Set(\n results\n .filter((r) => r.status === 'downloaded')\n .map((r) => r.dirName),\n );\n\n if (downloadedDirs.size === 0) return;\n\n try {\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(content) as ReviewFile;\n let changed = false;\n\n for (const article of reviewFile.articles) {\n if (article.fulltext?.dirName && downloadedDirs.has(article.fulltext.dirName)) {\n const result = results.find((r) => r.dirName === article.fulltext?.dirName);\n if (result?.filesDownloaded) {\n article.fulltext.hasFiles = {\n pdf: article.fulltext.hasFiles.pdf || result.filesDownloaded.includes('fulltext.pdf'),\n xml: article.fulltext.hasFiles.xml || result.filesDownloaded.includes('fulltext.xml'),\n html: article.fulltext.hasFiles.html || result.filesDownloaded.includes('fulltext.html'),\n markdown: article.fulltext.hasFiles.markdown || result.filesDownloaded.includes('fulltext.md'),\n };\n changed = true;\n }\n }\n }\n\n if (changed) {\n await writeFile(reviewsPath, stringifyYaml(reviewFile), 'utf-8');\n }\n } catch (e) {\n console.error('Warning: failed to update reviews.yaml:', e);\n }\n}\n"],"names":["parseYaml","stringifyYaml"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAMpD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,GACvD,IAAI,CAgVN"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAMpD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,GACvD,IAAI,CAkVN"}
@@ -168,6 +168,8 @@ Fulltext ${prefix.toLowerCase()}:`);
168
168
  config: {
169
169
  unpaywallEmail: config.fulltext?.sources?.unpaywall_email ?? "",
170
170
  coreApiKey: config.fulltext?.sources?.core_api_key ?? "",
171
+ ncbiEmail: config.fulltext?.sources?.ncbi_email ?? "",
172
+ ncbiTool: config.fulltext?.sources?.ncbi_tool ?? "search-hub",
171
173
  preferSources: config.fulltext?.sources?.prefer_sources ?? ["pmc", "arxiv", "unpaywall", "core"]
172
174
  }
173
175
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/cli/commands/fulltext/index.ts"],"sourcesContent":["/**\n * Fulltext command group registration.\n */\n\nimport type { Command } from 'commander';\nimport { executeFulltextInit } from './init.js';\nimport { executeFulltextSync } from './sync.js';\nimport { executeFulltextConvert } from './convert.js';\nimport { executeFulltextCheck } from './check.js';\nimport { executeFulltextFetch } from './fetch.js';\nimport { executeFulltextAttach } from './attach.js';\nimport { executeFulltextStatus, type FulltextStatusResult } from './status.js';\nimport { executeFulltextPending, type PendingArticle } from './pending.js';\nimport { formatInitOutput, formatSyncOutput } from './format.js';\nimport type { GlobalOptions } from '../../index.js';\nimport { EXIT_CODES } from '../../exit-codes.js';\nimport { sessionExists } from '../../../session/manager.js';\nimport { loadConfig } from '../../../config/index.js';\nimport { join } from 'node:path';\n\nexport function registerFulltextCommands(\n program: Command,\n getSessionsDir: (opts: GlobalOptions) => Promise<string>,\n): void {\n const fulltextCommand = program\n .command('fulltext')\n .description('Fulltext management: retrieval, conversion, attachment')\n .addHelpText('after', `\nExamples:\n $ search-hub fulltext init SESSION_ID # Create directories for included articles\n $ search-hub fulltext init SESSION_ID --dry-run # Preview what would be created\n $ search-hub fulltext sync SESSION_ID # Detect and register added files\n $ search-hub fulltext sync SESSION_ID --dry-run # Preview what would be synced\n $ search-hub fulltext convert SESSION_ID # Convert PMC XML to Markdown\n $ search-hub fulltext check --session SESSION_ID # Check OA availability\n $ search-hub fulltext fetch SESSION_ID # Download available OA articles\n $ search-hub fulltext fetch SESSION_ID --dry-run # Preview what would be downloaded\n $ search-hub fulltext fetch SESSION_ID --source pmc # Download from specific sources\n $ search-hub fulltext attach SESSION_ID # Attach fulltexts to ref entries\n $ search-hub fulltext attach SESSION_ID --dry-run # Preview what would be attached\n $ search-hub fulltext status SESSION_ID # Show fulltext retrieval status\n $ search-hub fulltext pending SESSION_ID # List articles needing download`);\n\n fulltextCommand\n .command('init')\n .description('Create directories for included articles with meta.json and README')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be created without creating', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const result = await executeFulltextInit({\n sessionId,\n sessionsDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n console.log(formatInitOutput(result));\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('sync')\n .description('Detect and register manually added fulltext files')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be synced without modifying', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const result = await executeFulltextSync({\n sessionId,\n sessionsDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n console.log(formatSyncOutput(result));\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('convert')\n .description('Convert PMC XML files to Markdown')\n .argument('<session-id>', 'session ID')\n .option('--article <dir>', 'convert specific article directory')\n .action(async (sessionId: string, options?: { article?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const convertOpts: Parameters<typeof executeFulltextConvert>[0] = { sessionId };\n if (options?.article) convertOpts.article = options.article;\n const result = await executeFulltextConvert(convertOpts, sessionsDir);\n\n if (!globalOpts.quiet) {\n console.log(`Converted: ${result.converted} Skipped: ${result.skipped} Failed: ${result.failed}`);\n for (const article of result.articles) {\n const icon = article.status === 'converted' ? '+' : article.status === 'skipped' ? '-' : '!';\n console.log(` [${icon}] ${article.dirName}: ${article.title}`);\n }\n }\n\n process.exitCode = result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.SESSION_ERROR;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('attach')\n .description('Attach fulltext files to reference-manager entries')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be attached without attaching', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const sessionDir = join(sessionsDir, sessionId);\n\n const result = await executeFulltextAttach({\n sessionDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n const prefix = options.dryRun ? 'Would attach' : 'Attached';\n console.log(`\\nFulltext ${prefix.toLowerCase()}:`);\n if (result.summary.attached > 0) {\n const totalFiles = result.attached.reduce((sum, a) => sum + a.files.length, 0);\n console.log(` ✓ ${result.summary.attached} articles (${totalFiles} files)`);\n for (const item of result.attached) {\n console.log(` ${item.refId}: ${item.files.join(', ')}`);\n }\n }\n if (result.summary.skipped > 0) {\n console.log(` ⚠ ${result.summary.skipped} skipped`);\n }\n if (result.summary.failed > 0) {\n console.log(` ✗ ${result.summary.failed} failed`);\n for (const item of result.failed) {\n console.log(` ${item.dirName}: ${item.reason}`);\n }\n }\n if (result.summary.total === 0) {\n console.log(' No fulltext directories found.');\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('check')\n .description('Check Open Access availability for included articles')\n .requiredOption('--session <id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .action(async (options: { session: string; format: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, options.session);\n const config = await loadConfig(\n globalOpts.config ? { globalConfigPath: globalOpts.config } : {}\n );\n const result = await executeFulltextCheck({\n sessionDir,\n config: {\n unpaywallEmail: config.fulltext?.sources?.unpaywall_email ?? '',\n coreApiKey: config.fulltext?.sources?.core_api_key ?? '',\n preferSources: config.fulltext?.sources?.prefer_sources ?? ['pmc', 'arxiv', 'unpaywall', 'core'],\n },\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(`\\nOA Status Summary:`);\n console.log(` Open Access: ${result.summary.open}`);\n console.log(` Closed Access: ${result.summary.closed}`);\n console.log(` Unknown: ${result.summary.unknown}`);\n console.log(` Total: ${result.summary.total}`);\n if (result.summary.open > 0) {\n console.log(`\\nRun \\`fulltext fetch\\` to download available OA articles.`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('fetch')\n .description('Download available OA fulltexts')\n .argument('<session-id>', 'session ID')\n .option('--source <sources>', 'filter by source (comma-separated: pmc,arxiv,unpaywall,core)')\n .option('--no-convert-markdown', 'skip auto-conversion of PMC XML to Markdown')\n .option('--dry-run', 'show what would be downloaded without downloading', false)\n .action(async (sessionId: string, options: { source?: string; convertMarkdown: boolean; dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const fetchOpts: Parameters<typeof executeFulltextFetch>[0] = {\n sessionId,\n sessionsDir,\n convertMarkdown: options.convertMarkdown,\n dryRun: options.dryRun,\n };\n if (options.source) fetchOpts.source = options.source.split(',');\n const result = await executeFulltextFetch(fetchOpts);\n\n if (!globalOpts.quiet) {\n if (result.dryRun) {\n console.log(`\\nDry run: would fetch ${result.articles.length} articles`);\n for (const article of result.articles) {\n console.log(` ${article.dirName}: ${article.title} (${article.locationCount} sources)`);\n }\n } else {\n console.log(`\\nFulltext Fetch Summary:`);\n console.log(` Downloaded: ${result.summary.downloaded}`);\n console.log(` Failed: ${result.summary.failed}`);\n console.log(` Skipped: ${result.summary.skipped}`);\n console.log(` Total: ${result.summary.total}`);\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('status')\n .description('Show overall fulltext retrieval status')\n .argument('<session-id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .action(async (sessionId: string, options: { format: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n const result = await executeFulltextStatus({\n sessionDir,\n format: options.format as 'table' | 'json',\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(formatStatusOutput(result, sessionId));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('pending')\n .description('List articles needing manual fulltext download')\n .argument('<session-id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .option('--export <file>', 'export URLs to file for batch download')\n .action(async (sessionId: string, options: { format: string; export?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n const result = await executeFulltextPending({\n sessionDir,\n format: options.format as 'table' | 'json',\n exportPath: options.export,\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(formatPendingOutput(result.articles, result.totalPending));\n if (options.export) {\n console.log(`\\nExported URLs to ${options.export}`);\n } else if (result.totalPending > 0) {\n console.log(`\\nExport URLs: fulltext pending ${sessionId} --export urls.txt`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n}\n\nfunction formatStatusOutput(result: FulltextStatusResult, sessionId: string): string {\n const lines = [\n `Fulltext Status: ${sessionId}`,\n '',\n ` Included articles: ${result.totalIncluded}`,\n ` With fulltext: ${result.withFulltext}`,\n ` - PDF only: ${result.pdfOnly}`,\n ` - Markdown only: ${result.markdownOnly}`,\n ` - Both: ${result.both}`,\n ` Pending: ${result.pending} (directories created, no files)`,\n ` Not initialized: ${result.notInitialized} (no directory)`,\n ];\n return lines.join('\\n');\n}\n\nfunction formatPendingOutput(articles: PendingArticle[], total: number): string {\n if (total === 0) {\n return 'All included articles have fulltext.';\n }\n\n const lines: string[] = [`${total} articles need fulltext:`, ''];\n\n for (const [i, article] of articles.entries()) {\n const num = i + 1;\n const identifier = article.dirName ?? '(not initialized)';\n lines.push(`${num}. ${identifier} - \"${article.title}\"`);\n if (article.doi) {\n lines.push(` DOI: ${article.doi}`);\n }\n if (article.publisherUrl) {\n lines.push(` Publisher: ${article.publisherUrl}`);\n }\n if (article.oaLocations) {\n for (const loc of article.oaLocations) {\n lines.push(` ${loc.source}: ${loc.url}`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/cli/commands/fulltext/index.ts"],"sourcesContent":["/**\n * Fulltext command group registration.\n */\n\nimport type { Command } from 'commander';\nimport { executeFulltextInit } from './init.js';\nimport { executeFulltextSync } from './sync.js';\nimport { executeFulltextConvert } from './convert.js';\nimport { executeFulltextCheck } from './check.js';\nimport { executeFulltextFetch } from './fetch.js';\nimport { executeFulltextAttach } from './attach.js';\nimport { executeFulltextStatus, type FulltextStatusResult } from './status.js';\nimport { executeFulltextPending, type PendingArticle } from './pending.js';\nimport { formatInitOutput, formatSyncOutput } from './format.js';\nimport type { GlobalOptions } from '../../index.js';\nimport { EXIT_CODES } from '../../exit-codes.js';\nimport { sessionExists } from '../../../session/manager.js';\nimport { loadConfig } from '../../../config/index.js';\nimport { join } from 'node:path';\n\nexport function registerFulltextCommands(\n program: Command,\n getSessionsDir: (opts: GlobalOptions) => Promise<string>,\n): void {\n const fulltextCommand = program\n .command('fulltext')\n .description('Fulltext management: retrieval, conversion, attachment')\n .addHelpText('after', `\nExamples:\n $ search-hub fulltext init SESSION_ID # Create directories for included articles\n $ search-hub fulltext init SESSION_ID --dry-run # Preview what would be created\n $ search-hub fulltext sync SESSION_ID # Detect and register added files\n $ search-hub fulltext sync SESSION_ID --dry-run # Preview what would be synced\n $ search-hub fulltext convert SESSION_ID # Convert PMC XML to Markdown\n $ search-hub fulltext check --session SESSION_ID # Check OA availability\n $ search-hub fulltext fetch SESSION_ID # Download available OA articles\n $ search-hub fulltext fetch SESSION_ID --dry-run # Preview what would be downloaded\n $ search-hub fulltext fetch SESSION_ID --source pmc # Download from specific sources\n $ search-hub fulltext attach SESSION_ID # Attach fulltexts to ref entries\n $ search-hub fulltext attach SESSION_ID --dry-run # Preview what would be attached\n $ search-hub fulltext status SESSION_ID # Show fulltext retrieval status\n $ search-hub fulltext pending SESSION_ID # List articles needing download`);\n\n fulltextCommand\n .command('init')\n .description('Create directories for included articles with meta.json and README')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be created without creating', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const result = await executeFulltextInit({\n sessionId,\n sessionsDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n console.log(formatInitOutput(result));\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('sync')\n .description('Detect and register manually added fulltext files')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be synced without modifying', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const result = await executeFulltextSync({\n sessionId,\n sessionsDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n console.log(formatSyncOutput(result));\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('convert')\n .description('Convert PMC XML files to Markdown')\n .argument('<session-id>', 'session ID')\n .option('--article <dir>', 'convert specific article directory')\n .action(async (sessionId: string, options?: { article?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const convertOpts: Parameters<typeof executeFulltextConvert>[0] = { sessionId };\n if (options?.article) convertOpts.article = options.article;\n const result = await executeFulltextConvert(convertOpts, sessionsDir);\n\n if (!globalOpts.quiet) {\n console.log(`Converted: ${result.converted} Skipped: ${result.skipped} Failed: ${result.failed}`);\n for (const article of result.articles) {\n const icon = article.status === 'converted' ? '+' : article.status === 'skipped' ? '-' : '!';\n console.log(` [${icon}] ${article.dirName}: ${article.title}`);\n }\n }\n\n process.exitCode = result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.SESSION_ERROR;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('attach')\n .description('Attach fulltext files to reference-manager entries')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be attached without attaching', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const sessionDir = join(sessionsDir, sessionId);\n\n const result = await executeFulltextAttach({\n sessionDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n const prefix = options.dryRun ? 'Would attach' : 'Attached';\n console.log(`\\nFulltext ${prefix.toLowerCase()}:`);\n if (result.summary.attached > 0) {\n const totalFiles = result.attached.reduce((sum, a) => sum + a.files.length, 0);\n console.log(` ✓ ${result.summary.attached} articles (${totalFiles} files)`);\n for (const item of result.attached) {\n console.log(` ${item.refId}: ${item.files.join(', ')}`);\n }\n }\n if (result.summary.skipped > 0) {\n console.log(` ⚠ ${result.summary.skipped} skipped`);\n }\n if (result.summary.failed > 0) {\n console.log(` ✗ ${result.summary.failed} failed`);\n for (const item of result.failed) {\n console.log(` ${item.dirName}: ${item.reason}`);\n }\n }\n if (result.summary.total === 0) {\n console.log(' No fulltext directories found.');\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('check')\n .description('Check Open Access availability for included articles')\n .requiredOption('--session <id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .action(async (options: { session: string; format: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, options.session);\n const config = await loadConfig(\n globalOpts.config ? { globalConfigPath: globalOpts.config } : {}\n );\n const result = await executeFulltextCheck({\n sessionDir,\n config: {\n unpaywallEmail: config.fulltext?.sources?.unpaywall_email ?? '',\n coreApiKey: config.fulltext?.sources?.core_api_key ?? '',\n ncbiEmail: config.fulltext?.sources?.ncbi_email ?? '',\n ncbiTool: config.fulltext?.sources?.ncbi_tool ?? 'search-hub',\n preferSources: config.fulltext?.sources?.prefer_sources ?? ['pmc', 'arxiv', 'unpaywall', 'core'],\n },\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(`\\nOA Status Summary:`);\n console.log(` Open Access: ${result.summary.open}`);\n console.log(` Closed Access: ${result.summary.closed}`);\n console.log(` Unknown: ${result.summary.unknown}`);\n console.log(` Total: ${result.summary.total}`);\n if (result.summary.open > 0) {\n console.log(`\\nRun \\`fulltext fetch\\` to download available OA articles.`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('fetch')\n .description('Download available OA fulltexts')\n .argument('<session-id>', 'session ID')\n .option('--source <sources>', 'filter by source (comma-separated: pmc,arxiv,unpaywall,core)')\n .option('--no-convert-markdown', 'skip auto-conversion of PMC XML to Markdown')\n .option('--dry-run', 'show what would be downloaded without downloading', false)\n .action(async (sessionId: string, options: { source?: string; convertMarkdown: boolean; dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const fetchOpts: Parameters<typeof executeFulltextFetch>[0] = {\n sessionId,\n sessionsDir,\n convertMarkdown: options.convertMarkdown,\n dryRun: options.dryRun,\n };\n if (options.source) fetchOpts.source = options.source.split(',');\n const result = await executeFulltextFetch(fetchOpts);\n\n if (!globalOpts.quiet) {\n if (result.dryRun) {\n console.log(`\\nDry run: would fetch ${result.articles.length} articles`);\n for (const article of result.articles) {\n console.log(` ${article.dirName}: ${article.title} (${article.locationCount} sources)`);\n }\n } else {\n console.log(`\\nFulltext Fetch Summary:`);\n console.log(` Downloaded: ${result.summary.downloaded}`);\n console.log(` Failed: ${result.summary.failed}`);\n console.log(` Skipped: ${result.summary.skipped}`);\n console.log(` Total: ${result.summary.total}`);\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('status')\n .description('Show overall fulltext retrieval status')\n .argument('<session-id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .action(async (sessionId: string, options: { format: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n const result = await executeFulltextStatus({\n sessionDir,\n format: options.format as 'table' | 'json',\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(formatStatusOutput(result, sessionId));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('pending')\n .description('List articles needing manual fulltext download')\n .argument('<session-id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .option('--export <file>', 'export URLs to file for batch download')\n .action(async (sessionId: string, options: { format: string; export?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n const result = await executeFulltextPending({\n sessionDir,\n format: options.format as 'table' | 'json',\n exportPath: options.export,\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(formatPendingOutput(result.articles, result.totalPending));\n if (options.export) {\n console.log(`\\nExported URLs to ${options.export}`);\n } else if (result.totalPending > 0) {\n console.log(`\\nExport URLs: fulltext pending ${sessionId} --export urls.txt`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n}\n\nfunction formatStatusOutput(result: FulltextStatusResult, sessionId: string): string {\n const lines = [\n `Fulltext Status: ${sessionId}`,\n '',\n ` Included articles: ${result.totalIncluded}`,\n ` With fulltext: ${result.withFulltext}`,\n ` - PDF only: ${result.pdfOnly}`,\n ` - Markdown only: ${result.markdownOnly}`,\n ` - Both: ${result.both}`,\n ` Pending: ${result.pending} (directories created, no files)`,\n ` Not initialized: ${result.notInitialized} (no directory)`,\n ];\n return lines.join('\\n');\n}\n\nfunction formatPendingOutput(articles: PendingArticle[], total: number): string {\n if (total === 0) {\n return 'All included articles have fulltext.';\n }\n\n const lines: string[] = [`${total} articles need fulltext:`, ''];\n\n for (const [i, article] of articles.entries()) {\n const num = i + 1;\n const identifier = article.dirName ?? '(not initialized)';\n lines.push(`${num}. ${identifier} - \"${article.title}\"`);\n if (article.doi) {\n lines.push(` DOI: ${article.doi}`);\n }\n if (article.publisherUrl) {\n lines.push(` Publisher: ${article.publisherUrl}`);\n }\n if (article.oaLocations) {\n for (const loc of article.oaLocations) {\n lines.push(` ${loc.source}: ${loc.url}`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CA8G7B"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CA8G7B"}
@@ -2,10 +2,11 @@ import { join } from "node:path";
2
2
  import { readFile, mkdir, writeFile } from "node:fs/promises";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { parse, stringify } from "yaml";
5
- import { generateCitationKey, generateDirName } from "../../../fulltext/citation-key.js";
6
- import { createMeta, saveMeta } from "../../../fulltext/meta.js";
7
- import { generateReadme } from "../../../fulltext/readme.js";
8
- import { getArticleDir, getMetaPath, getReadmePath, getFulltextDir } from "../../../fulltext/paths.js";
5
+ import { createMeta, saveMeta } from "../../../node_modules/@ncukondo/academic-fulltext/dist/meta.js";
6
+ import { getArticleDir, getMetaPath, getReadmePath, getFulltextDir } from "../../../node_modules/@ncukondo/academic-fulltext/dist/paths.js";
7
+ import "../../../node_modules/@ncukondo/academic-fulltext/dist/convert/jats-parser.js";
8
+ import { generateCitationKey, generateDirName } from "../../../node_modules/@ncukondo/academic-fulltext/dist/citation-key.js";
9
+ import { generateReadme } from "../../../node_modules/@ncukondo/academic-fulltext/dist/readme.js";
9
10
  async function executeFulltextInit(options) {
10
11
  const { sessionId, sessionsDir, dryRun } = options;
11
12
  const sessionDir = join(sessionsDir, sessionId);
@@ -50,7 +51,7 @@ async function executeFulltextInit(options) {
50
51
  const meta = createMeta(metaOpts);
51
52
  const fulltextRef = {
52
53
  dirName,
53
- hasFiles: { pdf: false, xml: false, markdown: false }
54
+ hasFiles: { pdf: false, xml: false, html: false, markdown: false }
54
55
  };
55
56
  if (!dryRun) {
56
57
  const articleDir = getArticleDir(sessionDir, dirName);
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sources":["../../../../src/cli/commands/fulltext/init.ts"],"sourcesContent":["/**\n * fulltext init command - Create directories for included articles.\n */\n\nimport { join } from 'node:path';\nimport { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { randomUUID } from 'node:crypto';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { ReviewFile } from '../review/types.js';\nimport type { ArticleFulltextRef } from '../../../fulltext/types.js';\nimport { generateCitationKey, generateDirName } from '../../../fulltext/citation-key.js';\nimport { createMeta, saveMeta } from '../../../fulltext/meta.js';\nimport { generateReadme } from '../../../fulltext/readme.js';\nimport { getFulltextDir, getArticleDir, getMetaPath, getReadmePath } from '../../../fulltext/paths.js';\n\nexport interface FulltextInitOptions {\n sessionId: string;\n sessionsDir: string;\n dryRun?: boolean;\n}\n\nexport interface FulltextInitEntry {\n dirName: string;\n citationKey: string;\n title: string;\n doi?: string;\n pmid?: string;\n}\n\nexport interface FulltextInitResult {\n created: number;\n skipped: number;\n entries: FulltextInitEntry[];\n dryRun?: boolean;\n}\n\nexport async function executeFulltextInit(\n options: FulltextInitOptions,\n): Promise<FulltextInitResult> {\n const { sessionId, sessionsDir, dryRun } = options;\n const sessionDir = join(sessionsDir, sessionId);\n\n // Load reviews.yaml\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const reviewContent = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(reviewContent) as ReviewFile;\n\n // Filter to included articles\n const includedArticles = (reviewFile.articles ?? []).filter(\n (a) => a.finalDecision === 'include',\n );\n\n // Collect existing dirNames from reviews for skip detection\n const existingDirNames = new Set<string>();\n for (const article of reviewFile.articles ?? []) {\n if (article.fulltext?.dirName) {\n existingDirNames.add(article.fulltext.dirName);\n }\n }\n\n const entries: FulltextInitEntry[] = [];\n const existingKeys: string[] = [];\n let created = 0;\n let skipped = 0;\n\n // Track which articles in reviewFile to update with fulltext refs\n const fulltextUpdates = new Map<number, ArticleFulltextRef>();\n\n for (let i = 0; i < includedArticles.length; i++) {\n const article = includedArticles[i]!;\n\n // Find the article's index in the full review file (including non-included)\n const reviewIndex = reviewFile.articles.indexOf(article);\n\n // Skip if already has a fulltext directory\n if (article.fulltext?.dirName && existingDirNames.has(article.fulltext.dirName)) {\n skipped++;\n continue;\n }\n\n // Generate citation key and directory name\n const citationKey = generateCitationKey(article.authors, article.year, existingKeys);\n existingKeys.push(citationKey);\n\n const uuid = randomUUID();\n const dirName = generateDirName(citationKey, uuid);\n\n // Create meta\n const metaOpts: Parameters<typeof createMeta>[0] = {\n citationKey,\n uuid,\n title: article.title,\n };\n if (article.doi) metaOpts.doi = article.doi;\n if (article.pmid) metaOpts.pmid = article.pmid;\n if (article.arxivId) metaOpts.arxivId = article.arxivId;\n if (article.authors) metaOpts.authors = article.authors;\n if (article.year) metaOpts.year = article.year;\n const meta = createMeta(metaOpts);\n\n // Build fulltext ref for reviews.yaml\n const fulltextRef: ArticleFulltextRef = {\n dirName,\n hasFiles: { pdf: false, xml: false, markdown: false },\n };\n\n if (!dryRun) {\n // Create directory\n const articleDir = getArticleDir(sessionDir, dirName);\n await mkdir(articleDir, { recursive: true });\n\n // Write meta.json\n await saveMeta(getMetaPath(sessionDir, dirName), meta);\n\n // Write README.md\n const readme = generateReadme(meta);\n await writeFile(getReadmePath(sessionDir, dirName), readme, 'utf-8');\n\n // Track review update\n fulltextUpdates.set(reviewIndex, fulltextRef);\n }\n\n created++;\n const entry: FulltextInitEntry = { dirName, citationKey, title: article.title };\n if (article.doi) entry.doi = article.doi;\n if (article.pmid) entry.pmid = article.pmid;\n entries.push(entry);\n }\n\n if (!dryRun) {\n // Ensure fulltext directory exists\n await mkdir(getFulltextDir(sessionDir), { recursive: true });\n\n // Update reviews.yaml with fulltext references\n if (fulltextUpdates.size > 0) {\n for (const [reviewIdx, ref] of fulltextUpdates) {\n reviewFile.articles[reviewIdx]!.fulltext = ref;\n }\n await writeFile(reviewsPath, stringifyYaml(reviewFile), 'utf-8');\n }\n }\n\n return {\n created,\n skipped,\n entries,\n ...(dryRun && { dryRun: true }),\n };\n}\n"],"names":["parseYaml","stringifyYaml"],"mappings":";;;;;;;;AAoCA,eAAsB,oBACpB,SAC6B;AAC7B,QAAM,EAAE,WAAW,aAAa,OAAA,IAAW;AAC3C,QAAM,aAAa,KAAK,aAAa,SAAS;AAG9C,QAAM,cAAc,KAAK,YAAY,aAAa,cAAc;AAChE,QAAM,gBAAgB,MAAM,SAAS,aAAa,OAAO;AACzD,QAAM,aAAaA,MAAU,aAAa;AAG1C,QAAM,oBAAoB,WAAW,YAAY,CAAA,GAAI;AAAA,IACnD,CAAC,MAAM,EAAE,kBAAkB;AAAA,EAAA;AAI7B,QAAM,uCAAuB,IAAA;AAC7B,aAAW,WAAW,WAAW,YAAY,CAAA,GAAI;AAC/C,QAAI,QAAQ,UAAU,SAAS;AAC7B,uBAAiB,IAAI,QAAQ,SAAS,OAAO;AAAA,IAC/C;AAAA,EACF;AAEA,QAAM,UAA+B,CAAA;AACrC,QAAM,eAAyB,CAAA;AAC/B,MAAI,UAAU;AACd,MAAI,UAAU;AAGd,QAAM,sCAAsB,IAAA;AAE5B,WAAS,IAAI,GAAG,IAAI,iBAAiB,QAAQ,KAAK;AAChD,UAAM,UAAU,iBAAiB,CAAC;AAGlC,UAAM,cAAc,WAAW,SAAS,QAAQ,OAAO;AAGvD,QAAI,QAAQ,UAAU,WAAW,iBAAiB,IAAI,QAAQ,SAAS,OAAO,GAAG;AAC/E;AACA;AAAA,IACF;AAGA,UAAM,cAAc,oBAAoB,QAAQ,SAAS,QAAQ,MAAM,YAAY;AACnF,iBAAa,KAAK,WAAW;AAE7B,UAAM,OAAO,WAAA;AACb,UAAM,UAAU,gBAAgB,aAAa,IAAI;AAGjD,UAAM,WAA6C;AAAA,MACjD;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AAAA,IAAA;AAEjB,QAAI,QAAQ,IAAK,UAAS,MAAM,QAAQ;AACxC,QAAI,QAAQ,KAAM,UAAS,OAAO,QAAQ;AAC1C,QAAI,QAAQ,QAAS,UAAS,UAAU,QAAQ;AAChD,QAAI,QAAQ,QAAS,UAAS,UAAU,QAAQ;AAChD,QAAI,QAAQ,KAAM,UAAS,OAAO,QAAQ;AAC1C,UAAM,OAAO,WAAW,QAAQ;AAGhC,UAAM,cAAkC;AAAA,MACtC;AAAA,MACA,UAAU,EAAE,KAAK,OAAO,KAAK,OAAO,UAAU,MAAA;AAAA,IAAM;AAGtD,QAAI,CAAC,QAAQ;AAEX,YAAM,aAAa,cAAc,YAAY,OAAO;AACpD,YAAM,MAAM,YAAY,EAAE,WAAW,MAAM;AAG3C,YAAM,SAAS,YAAY,YAAY,OAAO,GAAG,IAAI;AAGrD,YAAM,SAAS,eAAe,IAAI;AAClC,YAAM,UAAU,cAAc,YAAY,OAAO,GAAG,QAAQ,OAAO;AAGnE,sBAAgB,IAAI,aAAa,WAAW;AAAA,IAC9C;AAEA;AACA,UAAM,QAA2B,EAAE,SAAS,aAAa,OAAO,QAAQ,MAAA;AACxE,QAAI,QAAQ,IAAK,OAAM,MAAM,QAAQ;AACrC,QAAI,QAAQ,KAAM,OAAM,OAAO,QAAQ;AACvC,YAAQ,KAAK,KAAK;AAAA,EACpB;AAEA,MAAI,CAAC,QAAQ;AAEX,UAAM,MAAM,eAAe,UAAU,GAAG,EAAE,WAAW,MAAM;AAG3D,QAAI,gBAAgB,OAAO,GAAG;AAC5B,iBAAW,CAAC,WAAW,GAAG,KAAK,iBAAiB;AAC9C,mBAAW,SAAS,SAAS,EAAG,WAAW;AAAA,MAC7C;AACA,YAAM,UAAU,aAAaC,UAAc,UAAU,GAAG,OAAO;AAAA,IACjE;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAI,UAAU,EAAE,QAAQ,KAAA;AAAA,EAAK;AAEjC;"}
1
+ {"version":3,"file":"init.js","sources":["../../../../src/cli/commands/fulltext/init.ts"],"sourcesContent":["/**\n * fulltext init command - Create directories for included articles.\n */\n\nimport { join } from 'node:path';\nimport { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { randomUUID } from 'node:crypto';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { ReviewFile } from '../review/types.js';\nimport { generateCitationKey, generateDirName, createMeta, saveMeta, generateReadme, getFulltextDir, getArticleDir, getMetaPath, getReadmePath, type ArticleFulltextRef } from '@ncukondo/academic-fulltext';\n\nexport interface FulltextInitOptions {\n sessionId: string;\n sessionsDir: string;\n dryRun?: boolean;\n}\n\nexport interface FulltextInitEntry {\n dirName: string;\n citationKey: string;\n title: string;\n doi?: string;\n pmid?: string;\n}\n\nexport interface FulltextInitResult {\n created: number;\n skipped: number;\n entries: FulltextInitEntry[];\n dryRun?: boolean;\n}\n\nexport async function executeFulltextInit(\n options: FulltextInitOptions,\n): Promise<FulltextInitResult> {\n const { sessionId, sessionsDir, dryRun } = options;\n const sessionDir = join(sessionsDir, sessionId);\n\n // Load reviews.yaml\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const reviewContent = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(reviewContent) as ReviewFile;\n\n // Filter to included articles\n const includedArticles = (reviewFile.articles ?? []).filter(\n (a) => a.finalDecision === 'include',\n );\n\n // Collect existing dirNames from reviews for skip detection\n const existingDirNames = new Set<string>();\n for (const article of reviewFile.articles ?? []) {\n if (article.fulltext?.dirName) {\n existingDirNames.add(article.fulltext.dirName);\n }\n }\n\n const entries: FulltextInitEntry[] = [];\n const existingKeys: string[] = [];\n let created = 0;\n let skipped = 0;\n\n // Track which articles in reviewFile to update with fulltext refs\n const fulltextUpdates = new Map<number, ArticleFulltextRef>();\n\n for (let i = 0; i < includedArticles.length; i++) {\n const article = includedArticles[i]!;\n\n // Find the article's index in the full review file (including non-included)\n const reviewIndex = reviewFile.articles.indexOf(article);\n\n // Skip if already has a fulltext directory\n if (article.fulltext?.dirName && existingDirNames.has(article.fulltext.dirName)) {\n skipped++;\n continue;\n }\n\n // Generate citation key and directory name\n const citationKey = generateCitationKey(article.authors, article.year, existingKeys);\n existingKeys.push(citationKey);\n\n const uuid = randomUUID();\n const dirName = generateDirName(citationKey, uuid);\n\n // Create meta\n const metaOpts: Parameters<typeof createMeta>[0] = {\n citationKey,\n uuid,\n title: article.title,\n };\n if (article.doi) metaOpts.doi = article.doi;\n if (article.pmid) metaOpts.pmid = article.pmid;\n if (article.arxivId) metaOpts.arxivId = article.arxivId;\n if (article.authors) metaOpts.authors = article.authors;\n if (article.year) metaOpts.year = article.year;\n const meta = createMeta(metaOpts);\n\n // Build fulltext ref for reviews.yaml\n const fulltextRef: ArticleFulltextRef = {\n dirName,\n hasFiles: { pdf: false, xml: false, html: false, markdown: false },\n };\n\n if (!dryRun) {\n // Create directory\n const articleDir = getArticleDir(sessionDir, dirName);\n await mkdir(articleDir, { recursive: true });\n\n // Write meta.json\n await saveMeta(getMetaPath(sessionDir, dirName), meta);\n\n // Write README.md\n const readme = generateReadme(meta);\n await writeFile(getReadmePath(sessionDir, dirName), readme, 'utf-8');\n\n // Track review update\n fulltextUpdates.set(reviewIndex, fulltextRef);\n }\n\n created++;\n const entry: FulltextInitEntry = { dirName, citationKey, title: article.title };\n if (article.doi) entry.doi = article.doi;\n if (article.pmid) entry.pmid = article.pmid;\n entries.push(entry);\n }\n\n if (!dryRun) {\n // Ensure fulltext directory exists\n await mkdir(getFulltextDir(sessionDir), { recursive: true });\n\n // Update reviews.yaml with fulltext references\n if (fulltextUpdates.size > 0) {\n for (const [reviewIdx, ref] of fulltextUpdates) {\n reviewFile.articles[reviewIdx]!.fulltext = ref;\n }\n await writeFile(reviewsPath, stringifyYaml(reviewFile), 'utf-8');\n }\n }\n\n return {\n created,\n skipped,\n entries,\n ...(dryRun && { dryRun: true }),\n };\n}\n"],"names":["parseYaml","stringifyYaml"],"mappings":";;;;;;;;;AAgCA,eAAsB,oBACpB,SAC6B;AAC7B,QAAM,EAAE,WAAW,aAAa,OAAA,IAAW;AAC3C,QAAM,aAAa,KAAK,aAAa,SAAS;AAG9C,QAAM,cAAc,KAAK,YAAY,aAAa,cAAc;AAChE,QAAM,gBAAgB,MAAM,SAAS,aAAa,OAAO;AACzD,QAAM,aAAaA,MAAU,aAAa;AAG1C,QAAM,oBAAoB,WAAW,YAAY,CAAA,GAAI;AAAA,IACnD,CAAC,MAAM,EAAE,kBAAkB;AAAA,EAAA;AAI7B,QAAM,uCAAuB,IAAA;AAC7B,aAAW,WAAW,WAAW,YAAY,CAAA,GAAI;AAC/C,QAAI,QAAQ,UAAU,SAAS;AAC7B,uBAAiB,IAAI,QAAQ,SAAS,OAAO;AAAA,IAC/C;AAAA,EACF;AAEA,QAAM,UAA+B,CAAA;AACrC,QAAM,eAAyB,CAAA;AAC/B,MAAI,UAAU;AACd,MAAI,UAAU;AAGd,QAAM,sCAAsB,IAAA;AAE5B,WAAS,IAAI,GAAG,IAAI,iBAAiB,QAAQ,KAAK;AAChD,UAAM,UAAU,iBAAiB,CAAC;AAGlC,UAAM,cAAc,WAAW,SAAS,QAAQ,OAAO;AAGvD,QAAI,QAAQ,UAAU,WAAW,iBAAiB,IAAI,QAAQ,SAAS,OAAO,GAAG;AAC/E;AACA;AAAA,IACF;AAGA,UAAM,cAAc,oBAAoB,QAAQ,SAAS,QAAQ,MAAM,YAAY;AACnF,iBAAa,KAAK,WAAW;AAE7B,UAAM,OAAO,WAAA;AACb,UAAM,UAAU,gBAAgB,aAAa,IAAI;AAGjD,UAAM,WAA6C;AAAA,MACjD;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AAAA,IAAA;AAEjB,QAAI,QAAQ,IAAK,UAAS,MAAM,QAAQ;AACxC,QAAI,QAAQ,KAAM,UAAS,OAAO,QAAQ;AAC1C,QAAI,QAAQ,QAAS,UAAS,UAAU,QAAQ;AAChD,QAAI,QAAQ,QAAS,UAAS,UAAU,QAAQ;AAChD,QAAI,QAAQ,KAAM,UAAS,OAAO,QAAQ;AAC1C,UAAM,OAAO,WAAW,QAAQ;AAGhC,UAAM,cAAkC;AAAA,MACtC;AAAA,MACA,UAAU,EAAE,KAAK,OAAO,KAAK,OAAO,MAAM,OAAO,UAAU,MAAA;AAAA,IAAM;AAGnE,QAAI,CAAC,QAAQ;AAEX,YAAM,aAAa,cAAc,YAAY,OAAO;AACpD,YAAM,MAAM,YAAY,EAAE,WAAW,MAAM;AAG3C,YAAM,SAAS,YAAY,YAAY,OAAO,GAAG,IAAI;AAGrD,YAAM,SAAS,eAAe,IAAI;AAClC,YAAM,UAAU,cAAc,YAAY,OAAO,GAAG,QAAQ,OAAO;AAGnE,sBAAgB,IAAI,aAAa,WAAW;AAAA,IAC9C;AAEA;AACA,UAAM,QAA2B,EAAE,SAAS,aAAa,OAAO,QAAQ,MAAA;AACxE,QAAI,QAAQ,IAAK,OAAM,MAAM,QAAQ;AACrC,QAAI,QAAQ,KAAM,OAAM,OAAO,QAAQ;AACvC,YAAQ,KAAK,KAAK;AAAA,EACpB;AAEA,MAAI,CAAC,QAAQ;AAEX,UAAM,MAAM,eAAe,UAAU,GAAG,EAAE,WAAW,MAAM;AAG3D,QAAI,gBAAgB,OAAO,GAAG;AAC5B,iBAAW,CAAC,WAAW,GAAG,KAAK,iBAAiB;AAC9C,mBAAW,SAAS,SAAS,EAAG,WAAW;AAAA,MAC7C;AACA,YAAM,UAAU,aAAaC,UAAc,UAAU,GAAG,OAAO;AAAA,IACjE;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAI,UAAU,EAAE,QAAQ,KAAA;AAAA,EAAK;AAEjC;"}
@@ -1,4 +1,4 @@
1
- import { OALocation } from '../../../fulltext/types.js';
1
+ import { OALocation } from '@ncukondo/academic-fulltext';
2
2
  export interface FulltextPendingOptions {
3
3
  sessionDir: string;
4
4
  format?: 'table' | 'json';
@@ -1 +1 @@
1
- {"version":3,"file":"pending.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/pending.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAG7D,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAyDD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CA0BnE;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAuChC"}
1
+ {"version":3,"file":"pending.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/pending.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAGrF,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAyDD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CA0BnE;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAuChC"}
@@ -1,8 +1,10 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { parse } from "yaml";
4
- import { loadMeta } from "../../../fulltext/meta.js";
5
- import { getMetaPath } from "../../../fulltext/paths.js";
4
+ import { loadMeta } from "../../../node_modules/@ncukondo/academic-fulltext/dist/meta.js";
5
+ import { getMetaPath } from "../../../node_modules/@ncukondo/academic-fulltext/dist/paths.js";
6
+ import "../../../node_modules/@ncukondo/academic-fulltext/dist/convert/jats-parser.js";
7
+ import "node:crypto";
6
8
  async function hasFulltextFiles(sessionDir, dirName) {
7
9
  try {
8
10
  const metaPath = getMetaPath(sessionDir, dirName);
@@ -1 +1 @@
1
- {"version":3,"file":"pending.js","sources":["../../../../src/cli/commands/fulltext/pending.ts"],"sourcesContent":["/**\n * Fulltext pending command.\n * Lists articles needing manual download with URLs.\n */\n\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { loadMeta } from '../../../fulltext/meta.js';\nimport { getMetaPath } from '../../../fulltext/paths.js';\nimport type { OALocation } from '../../../fulltext/types.js';\nimport type { ReviewFile, ArticleEntry } from '../review/types.js';\n\nexport interface FulltextPendingOptions {\n sessionDir: string;\n format?: 'table' | 'json';\n exportPath?: string | undefined;\n}\n\nexport interface PendingArticle {\n dirName?: string;\n citationKey?: string;\n title: string;\n doi?: string;\n pmid?: string;\n publisherUrl?: string;\n oaLocations?: OALocation[];\n}\n\nexport interface FulltextPendingResult {\n totalPending: number;\n articles: PendingArticle[];\n}\n\n/**\n * Check if an article has any fulltext files by reading its meta.json.\n */\nasync function hasFulltextFiles(\n sessionDir: string,\n dirName: string,\n): Promise<boolean> {\n try {\n const metaPath = getMetaPath(sessionDir, dirName);\n const meta = await loadMeta(metaPath);\n return (\n meta.files.pdf !== undefined ||\n meta.files.markdown !== undefined\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Build a PendingArticle from an ArticleEntry, enriched with meta.json data.\n */\nasync function buildPendingArticle(\n article: ArticleEntry,\n sessionDir: string,\n): Promise<PendingArticle> {\n const pending: PendingArticle = {\n title: article.title,\n };\n\n if (article.doi) {\n pending.doi = article.doi;\n pending.publisherUrl = `https://doi.org/${article.doi}`;\n }\n if (article.pmid) pending.pmid = article.pmid;\n\n if (article.fulltext?.dirName) {\n pending.dirName = article.fulltext.dirName;\n\n // Try to load meta.json for OA locations and citation key\n try {\n const metaPath = getMetaPath(sessionDir, article.fulltext.dirName);\n const meta = await loadMeta(metaPath);\n pending.citationKey = meta.citationKey;\n if (meta.oaLocations && meta.oaLocations.length > 0) {\n pending.oaLocations = meta.oaLocations;\n }\n } catch {\n // meta.json not readable — skip enrichment\n }\n }\n\n return pending;\n}\n\n/**\n * Format the export file content for batch download.\n * Format: comment line with article identifier, followed by URL lines.\n */\nexport function formatExportFile(articles: PendingArticle[]): string {\n const blocks: string[] = [];\n\n for (const article of articles) {\n const identifier = article.dirName ?? article.title;\n const header = identifier === article.title\n ? `# ${article.title}`\n : `# ${identifier} - ${article.title}`;\n const lines: string[] = [header];\n\n // Publisher URL (DOI link) first\n if (article.publisherUrl) {\n lines.push(article.publisherUrl);\n }\n\n // OA location URLs\n if (article.oaLocations) {\n for (const loc of article.oaLocations) {\n lines.push(loc.url);\n }\n }\n\n blocks.push(lines.join('\\n'));\n }\n\n return blocks.join('\\n\\n') + '\\n';\n}\n\n/**\n * Execute the fulltext pending command.\n * Lists included articles that don't yet have fulltext files.\n */\nexport async function executeFulltextPending(\n options: FulltextPendingOptions,\n): Promise<FulltextPendingResult> {\n const { sessionDir, exportPath } = options;\n\n // Load reviews.yaml\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(content) as ReviewFile;\n\n // Filter to included articles\n const included = (reviewFile.articles ?? []).filter(\n (a) => a.finalDecision === 'include',\n );\n\n const articles: PendingArticle[] = [];\n\n for (const article of included) {\n // Articles with no fulltext ref are not initialized — always pending\n if (!article.fulltext?.dirName) {\n articles.push(await buildPendingArticle(article, sessionDir));\n continue;\n }\n\n // Articles with a directory but no files are pending\n const hasFiles = await hasFulltextFiles(sessionDir, article.fulltext.dirName);\n if (!hasFiles) {\n articles.push(await buildPendingArticle(article, sessionDir));\n }\n }\n\n // Write export file if requested\n if (exportPath && articles.length > 0) {\n const exportContent = formatExportFile(articles);\n await writeFile(exportPath, exportContent, 'utf-8');\n }\n\n return {\n totalPending: articles.length,\n articles,\n };\n}\n"],"names":["parseYaml"],"mappings":";;;;;AAqCA,eAAe,iBACb,YACA,SACkB;AAClB,MAAI;AACF,UAAM,WAAW,YAAY,YAAY,OAAO;AAChD,UAAM,OAAO,MAAM,SAAS,QAAQ;AACpC,WACE,KAAK,MAAM,QAAQ,UACnB,KAAK,MAAM,aAAa;AAAA,EAE5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAe,oBACb,SACA,YACyB;AACzB,QAAM,UAA0B;AAAA,IAC9B,OAAO,QAAQ;AAAA,EAAA;AAGjB,MAAI,QAAQ,KAAK;AACf,YAAQ,MAAM,QAAQ;AACtB,YAAQ,eAAe,mBAAmB,QAAQ,GAAG;AAAA,EACvD;AACA,MAAI,QAAQ,KAAM,SAAQ,OAAO,QAAQ;AAEzC,MAAI,QAAQ,UAAU,SAAS;AAC7B,YAAQ,UAAU,QAAQ,SAAS;AAGnC,QAAI;AACF,YAAM,WAAW,YAAY,YAAY,QAAQ,SAAS,OAAO;AACjE,YAAM,OAAO,MAAM,SAAS,QAAQ;AACpC,cAAQ,cAAc,KAAK;AAC3B,UAAI,KAAK,eAAe,KAAK,YAAY,SAAS,GAAG;AACnD,gBAAQ,cAAc,KAAK;AAAA,MAC7B;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,iBAAiB,UAAoC;AACnE,QAAM,SAAmB,CAAA;AAEzB,aAAW,WAAW,UAAU;AAC9B,UAAM,aAAa,QAAQ,WAAW,QAAQ;AAC9C,UAAM,SAAS,eAAe,QAAQ,QAClC,KAAK,QAAQ,KAAK,KAClB,KAAK,UAAU,MAAM,QAAQ,KAAK;AACtC,UAAM,QAAkB,CAAC,MAAM;AAG/B,QAAI,QAAQ,cAAc;AACxB,YAAM,KAAK,QAAQ,YAAY;AAAA,IACjC;AAGA,QAAI,QAAQ,aAAa;AACvB,iBAAW,OAAO,QAAQ,aAAa;AACrC,cAAM,KAAK,IAAI,GAAG;AAAA,MACpB;AAAA,IACF;AAEA,WAAO,KAAK,MAAM,KAAK,IAAI,CAAC;AAAA,EAC9B;AAEA,SAAO,OAAO,KAAK,MAAM,IAAI;AAC/B;AAMA,eAAsB,uBACpB,SACgC;AAChC,QAAM,EAAE,YAAY,WAAA,IAAe;AAGnC,QAAM,cAAc,KAAK,YAAY,aAAa,cAAc;AAChE,QAAM,UAAU,MAAM,SAAS,aAAa,OAAO;AACnD,QAAM,aAAaA,MAAU,OAAO;AAGpC,QAAM,YAAY,WAAW,YAAY,CAAA,GAAI;AAAA,IAC3C,CAAC,MAAM,EAAE,kBAAkB;AAAA,EAAA;AAG7B,QAAM,WAA6B,CAAA;AAEnC,aAAW,WAAW,UAAU;AAE9B,QAAI,CAAC,QAAQ,UAAU,SAAS;AAC9B,eAAS,KAAK,MAAM,oBAAoB,SAAS,UAAU,CAAC;AAC5D;AAAA,IACF;AAGA,UAAM,WAAW,MAAM,iBAAiB,YAAY,QAAQ,SAAS,OAAO;AAC5E,QAAI,CAAC,UAAU;AACb,eAAS,KAAK,MAAM,oBAAoB,SAAS,UAAU,CAAC;AAAA,IAC9D;AAAA,EACF;AAGA,MAAI,cAAc,SAAS,SAAS,GAAG;AACrC,UAAM,gBAAgB,iBAAiB,QAAQ;AAC/C,UAAM,UAAU,YAAY,eAAe,OAAO;AAAA,EACpD;AAEA,SAAO;AAAA,IACL,cAAc,SAAS;AAAA,IACvB;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"pending.js","sources":["../../../../src/cli/commands/fulltext/pending.ts"],"sourcesContent":["/**\n * Fulltext pending command.\n * Lists articles needing manual download with URLs.\n */\n\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { loadMeta, getMetaPath, type OALocation } from '@ncukondo/academic-fulltext';\nimport type { ReviewFile, ArticleEntry } from '../review/types.js';\n\nexport interface FulltextPendingOptions {\n sessionDir: string;\n format?: 'table' | 'json';\n exportPath?: string | undefined;\n}\n\nexport interface PendingArticle {\n dirName?: string;\n citationKey?: string;\n title: string;\n doi?: string;\n pmid?: string;\n publisherUrl?: string;\n oaLocations?: OALocation[];\n}\n\nexport interface FulltextPendingResult {\n totalPending: number;\n articles: PendingArticle[];\n}\n\n/**\n * Check if an article has any fulltext files by reading its meta.json.\n */\nasync function hasFulltextFiles(\n sessionDir: string,\n dirName: string,\n): Promise<boolean> {\n try {\n const metaPath = getMetaPath(sessionDir, dirName);\n const meta = await loadMeta(metaPath);\n return (\n meta.files.pdf !== undefined ||\n meta.files.markdown !== undefined\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Build a PendingArticle from an ArticleEntry, enriched with meta.json data.\n */\nasync function buildPendingArticle(\n article: ArticleEntry,\n sessionDir: string,\n): Promise<PendingArticle> {\n const pending: PendingArticle = {\n title: article.title,\n };\n\n if (article.doi) {\n pending.doi = article.doi;\n pending.publisherUrl = `https://doi.org/${article.doi}`;\n }\n if (article.pmid) pending.pmid = article.pmid;\n\n if (article.fulltext?.dirName) {\n pending.dirName = article.fulltext.dirName;\n\n // Try to load meta.json for OA locations and citation key\n try {\n const metaPath = getMetaPath(sessionDir, article.fulltext.dirName);\n const meta = await loadMeta(metaPath);\n pending.citationKey = meta.citationKey;\n if (meta.oaLocations && meta.oaLocations.length > 0) {\n pending.oaLocations = meta.oaLocations;\n }\n } catch {\n // meta.json not readable — skip enrichment\n }\n }\n\n return pending;\n}\n\n/**\n * Format the export file content for batch download.\n * Format: comment line with article identifier, followed by URL lines.\n */\nexport function formatExportFile(articles: PendingArticle[]): string {\n const blocks: string[] = [];\n\n for (const article of articles) {\n const identifier = article.dirName ?? article.title;\n const header = identifier === article.title\n ? `# ${article.title}`\n : `# ${identifier} - ${article.title}`;\n const lines: string[] = [header];\n\n // Publisher URL (DOI link) first\n if (article.publisherUrl) {\n lines.push(article.publisherUrl);\n }\n\n // OA location URLs\n if (article.oaLocations) {\n for (const loc of article.oaLocations) {\n lines.push(loc.url);\n }\n }\n\n blocks.push(lines.join('\\n'));\n }\n\n return blocks.join('\\n\\n') + '\\n';\n}\n\n/**\n * Execute the fulltext pending command.\n * Lists included articles that don't yet have fulltext files.\n */\nexport async function executeFulltextPending(\n options: FulltextPendingOptions,\n): Promise<FulltextPendingResult> {\n const { sessionDir, exportPath } = options;\n\n // Load reviews.yaml\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(content) as ReviewFile;\n\n // Filter to included articles\n const included = (reviewFile.articles ?? []).filter(\n (a) => a.finalDecision === 'include',\n );\n\n const articles: PendingArticle[] = [];\n\n for (const article of included) {\n // Articles with no fulltext ref are not initialized — always pending\n if (!article.fulltext?.dirName) {\n articles.push(await buildPendingArticle(article, sessionDir));\n continue;\n }\n\n // Articles with a directory but no files are pending\n const hasFiles = await hasFulltextFiles(sessionDir, article.fulltext.dirName);\n if (!hasFiles) {\n articles.push(await buildPendingArticle(article, sessionDir));\n }\n }\n\n // Write export file if requested\n if (exportPath && articles.length > 0) {\n const exportContent = formatExportFile(articles);\n await writeFile(exportPath, exportContent, 'utf-8');\n }\n\n return {\n totalPending: articles.length,\n articles,\n };\n}\n"],"names":["parseYaml"],"mappings":";;;;;;;AAmCA,eAAe,iBACb,YACA,SACkB;AAClB,MAAI;AACF,UAAM,WAAW,YAAY,YAAY,OAAO;AAChD,UAAM,OAAO,MAAM,SAAS,QAAQ;AACpC,WACE,KAAK,MAAM,QAAQ,UACnB,KAAK,MAAM,aAAa;AAAA,EAE5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAe,oBACb,SACA,YACyB;AACzB,QAAM,UAA0B;AAAA,IAC9B,OAAO,QAAQ;AAAA,EAAA;AAGjB,MAAI,QAAQ,KAAK;AACf,YAAQ,MAAM,QAAQ;AACtB,YAAQ,eAAe,mBAAmB,QAAQ,GAAG;AAAA,EACvD;AACA,MAAI,QAAQ,KAAM,SAAQ,OAAO,QAAQ;AAEzC,MAAI,QAAQ,UAAU,SAAS;AAC7B,YAAQ,UAAU,QAAQ,SAAS;AAGnC,QAAI;AACF,YAAM,WAAW,YAAY,YAAY,QAAQ,SAAS,OAAO;AACjE,YAAM,OAAO,MAAM,SAAS,QAAQ;AACpC,cAAQ,cAAc,KAAK;AAC3B,UAAI,KAAK,eAAe,KAAK,YAAY,SAAS,GAAG;AACnD,gBAAQ,cAAc,KAAK;AAAA,MAC7B;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,iBAAiB,UAAoC;AACnE,QAAM,SAAmB,CAAA;AAEzB,aAAW,WAAW,UAAU;AAC9B,UAAM,aAAa,QAAQ,WAAW,QAAQ;AAC9C,UAAM,SAAS,eAAe,QAAQ,QAClC,KAAK,QAAQ,KAAK,KAClB,KAAK,UAAU,MAAM,QAAQ,KAAK;AACtC,UAAM,QAAkB,CAAC,MAAM;AAG/B,QAAI,QAAQ,cAAc;AACxB,YAAM,KAAK,QAAQ,YAAY;AAAA,IACjC;AAGA,QAAI,QAAQ,aAAa;AACvB,iBAAW,OAAO,QAAQ,aAAa;AACrC,cAAM,KAAK,IAAI,GAAG;AAAA,MACpB;AAAA,IACF;AAEA,WAAO,KAAK,MAAM,KAAK,IAAI,CAAC;AAAA,EAC9B;AAEA,SAAO,OAAO,KAAK,MAAM,IAAI;AAC/B;AAMA,eAAsB,uBACpB,SACgC;AAChC,QAAM,EAAE,YAAY,WAAA,IAAe;AAGnC,QAAM,cAAc,KAAK,YAAY,aAAa,cAAc;AAChE,QAAM,UAAU,MAAM,SAAS,aAAa,OAAO;AACnD,QAAM,aAAaA,MAAU,OAAO;AAGpC,QAAM,YAAY,WAAW,YAAY,CAAA,GAAI;AAAA,IAC3C,CAAC,MAAM,EAAE,kBAAkB;AAAA,EAAA;AAG7B,QAAM,WAA6B,CAAA;AAEnC,aAAW,WAAW,UAAU;AAE9B,QAAI,CAAC,QAAQ,UAAU,SAAS;AAC9B,eAAS,KAAK,MAAM,oBAAoB,SAAS,UAAU,CAAC;AAC5D;AAAA,IACF;AAGA,UAAM,WAAW,MAAM,iBAAiB,YAAY,QAAQ,SAAS,OAAO;AAC5E,QAAI,CAAC,UAAU;AACb,eAAS,KAAK,MAAM,oBAAoB,SAAS,UAAU,CAAC;AAAA,IAC9D;AAAA,EACF;AAGA,MAAI,cAAc,SAAS,SAAS,GAAG;AACrC,UAAM,gBAAgB,iBAAiB,QAAQ;AAC/C,UAAM,UAAU,YAAY,eAAe,OAAO;AAAA,EACpD;AAEA,SAAO;AAAA,IACL,cAAc,SAAS;AAAA,IACvB;AAAA,EAAA;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/status.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AA+BD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAgD/B"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/status.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AA+BD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAgD/B"}
@@ -1,8 +1,10 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { parse } from "yaml";
4
- import { loadMeta } from "../../../fulltext/meta.js";
5
- import { getMetaPath } from "../../../fulltext/paths.js";
4
+ import { loadMeta } from "../../../node_modules/@ncukondo/academic-fulltext/dist/meta.js";
5
+ import { getMetaPath } from "../../../node_modules/@ncukondo/academic-fulltext/dist/paths.js";
6
+ import "../../../node_modules/@ncukondo/academic-fulltext/dist/convert/jats-parser.js";
7
+ import "node:crypto";
6
8
  async function classifyArticle(article, sessionDir) {
7
9
  if (!article.fulltext?.dirName) {
8
10
  return "not-initialized";
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","sources":["../../../../src/cli/commands/fulltext/status.ts"],"sourcesContent":["/**\n * Fulltext status command.\n * Shows overall fulltext retrieval status for a session.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { loadMeta } from '../../../fulltext/meta.js';\nimport { getMetaPath } from '../../../fulltext/paths.js';\nimport type { ReviewFile, ArticleEntry } from '../review/types.js';\n\nexport interface FulltextStatusOptions {\n sessionDir: string;\n format?: 'table' | 'json';\n}\n\nexport interface FulltextStatusResult {\n totalIncluded: number;\n withFulltext: number;\n pdfOnly: number;\n markdownOnly: number;\n both: number;\n pending: number;\n notInitialized: number;\n}\n\n/**\n * Classify an article's fulltext state by reading its meta.json.\n * Returns: 'pdf-only' | 'markdown-only' | 'both' | 'pending' | 'not-initialized'\n */\nasync function classifyArticle(\n article: ArticleEntry,\n sessionDir: string,\n): Promise<'pdf-only' | 'markdown-only' | 'both' | 'pending' | 'not-initialized'> {\n if (!article.fulltext?.dirName) {\n return 'not-initialized';\n }\n\n try {\n const metaPath = getMetaPath(sessionDir, article.fulltext.dirName);\n const meta = await loadMeta(metaPath);\n\n const hasPdf = meta.files.pdf !== undefined;\n const hasMd = meta.files.markdown !== undefined;\n\n if (hasPdf && hasMd) return 'both';\n if (hasPdf) return 'pdf-only';\n if (hasMd) return 'markdown-only';\n return 'pending';\n } catch {\n // Can't read meta.json — treat as pending since directory was assigned\n return 'pending';\n }\n}\n\n/**\n * Execute the fulltext status command.\n * Scans included articles and reports fulltext retrieval status.\n */\nexport async function executeFulltextStatus(\n options: FulltextStatusOptions,\n): Promise<FulltextStatusResult> {\n const { sessionDir } = options;\n\n // Load reviews.yaml\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(content) as ReviewFile;\n\n // Filter to included articles\n const included = (reviewFile.articles ?? []).filter(\n (a) => a.finalDecision === 'include',\n );\n\n const result: FulltextStatusResult = {\n totalIncluded: included.length,\n withFulltext: 0,\n pdfOnly: 0,\n markdownOnly: 0,\n both: 0,\n pending: 0,\n notInitialized: 0,\n };\n\n for (const article of included) {\n const state = await classifyArticle(article, sessionDir);\n switch (state) {\n case 'pdf-only':\n result.pdfOnly++;\n result.withFulltext++;\n break;\n case 'markdown-only':\n result.markdownOnly++;\n result.withFulltext++;\n break;\n case 'both':\n result.both++;\n result.withFulltext++;\n break;\n case 'pending':\n result.pending++;\n break;\n case 'not-initialized':\n result.notInitialized++;\n break;\n }\n }\n\n return result;\n}\n"],"names":["parseYaml"],"mappings":";;;;;AA+BA,eAAe,gBACb,SACA,YACgF;AAChF,MAAI,CAAC,QAAQ,UAAU,SAAS;AAC9B,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,WAAW,YAAY,YAAY,QAAQ,SAAS,OAAO;AACjE,UAAM,OAAO,MAAM,SAAS,QAAQ;AAEpC,UAAM,SAAS,KAAK,MAAM,QAAQ;AAClC,UAAM,QAAQ,KAAK,MAAM,aAAa;AAEtC,QAAI,UAAU,MAAO,QAAO;AAC5B,QAAI,OAAQ,QAAO;AACnB,QAAI,MAAO,QAAO;AAClB,WAAO;AAAA,EACT,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,sBACpB,SAC+B;AAC/B,QAAM,EAAE,eAAe;AAGvB,QAAM,cAAc,KAAK,YAAY,aAAa,cAAc;AAChE,QAAM,UAAU,MAAM,SAAS,aAAa,OAAO;AACnD,QAAM,aAAaA,MAAU,OAAO;AAGpC,QAAM,YAAY,WAAW,YAAY,CAAA,GAAI;AAAA,IAC3C,CAAC,MAAM,EAAE,kBAAkB;AAAA,EAAA;AAG7B,QAAM,SAA+B;AAAA,IACnC,eAAe,SAAS;AAAA,IACxB,cAAc;AAAA,IACd,SAAS;AAAA,IACT,cAAc;AAAA,IACd,MAAM;AAAA,IACN,SAAS;AAAA,IACT,gBAAgB;AAAA,EAAA;AAGlB,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,MAAM,gBAAgB,SAAS,UAAU;AACvD,YAAQ,OAAA;AAAA,MACN,KAAK;AACH,eAAO;AACP,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,IAAA;AAAA,EAEN;AAEA,SAAO;AACT;"}
1
+ {"version":3,"file":"status.js","sources":["../../../../src/cli/commands/fulltext/status.ts"],"sourcesContent":["/**\n * Fulltext status command.\n * Shows overall fulltext retrieval status for a session.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { loadMeta, getMetaPath } from '@ncukondo/academic-fulltext';\nimport type { ReviewFile, ArticleEntry } from '../review/types.js';\n\nexport interface FulltextStatusOptions {\n sessionDir: string;\n format?: 'table' | 'json';\n}\n\nexport interface FulltextStatusResult {\n totalIncluded: number;\n withFulltext: number;\n pdfOnly: number;\n markdownOnly: number;\n both: number;\n pending: number;\n notInitialized: number;\n}\n\n/**\n * Classify an article's fulltext state by reading its meta.json.\n * Returns: 'pdf-only' | 'markdown-only' | 'both' | 'pending' | 'not-initialized'\n */\nasync function classifyArticle(\n article: ArticleEntry,\n sessionDir: string,\n): Promise<'pdf-only' | 'markdown-only' | 'both' | 'pending' | 'not-initialized'> {\n if (!article.fulltext?.dirName) {\n return 'not-initialized';\n }\n\n try {\n const metaPath = getMetaPath(sessionDir, article.fulltext.dirName);\n const meta = await loadMeta(metaPath);\n\n const hasPdf = meta.files.pdf !== undefined;\n const hasMd = meta.files.markdown !== undefined;\n\n if (hasPdf && hasMd) return 'both';\n if (hasPdf) return 'pdf-only';\n if (hasMd) return 'markdown-only';\n return 'pending';\n } catch {\n // Can't read meta.json — treat as pending since directory was assigned\n return 'pending';\n }\n}\n\n/**\n * Execute the fulltext status command.\n * Scans included articles and reports fulltext retrieval status.\n */\nexport async function executeFulltextStatus(\n options: FulltextStatusOptions,\n): Promise<FulltextStatusResult> {\n const { sessionDir } = options;\n\n // Load reviews.yaml\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n const content = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(content) as ReviewFile;\n\n // Filter to included articles\n const included = (reviewFile.articles ?? []).filter(\n (a) => a.finalDecision === 'include',\n );\n\n const result: FulltextStatusResult = {\n totalIncluded: included.length,\n withFulltext: 0,\n pdfOnly: 0,\n markdownOnly: 0,\n both: 0,\n pending: 0,\n notInitialized: 0,\n };\n\n for (const article of included) {\n const state = await classifyArticle(article, sessionDir);\n switch (state) {\n case 'pdf-only':\n result.pdfOnly++;\n result.withFulltext++;\n break;\n case 'markdown-only':\n result.markdownOnly++;\n result.withFulltext++;\n break;\n case 'both':\n result.both++;\n result.withFulltext++;\n break;\n case 'pending':\n result.pending++;\n break;\n case 'not-initialized':\n result.notInitialized++;\n break;\n }\n }\n\n return result;\n}\n"],"names":["parseYaml"],"mappings":";;;;;;;AA8BA,eAAe,gBACb,SACA,YACgF;AAChF,MAAI,CAAC,QAAQ,UAAU,SAAS;AAC9B,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,WAAW,YAAY,YAAY,QAAQ,SAAS,OAAO;AACjE,UAAM,OAAO,MAAM,SAAS,QAAQ;AAEpC,UAAM,SAAS,KAAK,MAAM,QAAQ;AAClC,UAAM,QAAQ,KAAK,MAAM,aAAa;AAEtC,QAAI,UAAU,MAAO,QAAO;AAC5B,QAAI,OAAQ,QAAO;AACnB,QAAI,MAAO,QAAO;AAClB,WAAO;AAAA,EACT,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,sBACpB,SAC+B;AAC/B,QAAM,EAAE,eAAe;AAGvB,QAAM,cAAc,KAAK,YAAY,aAAa,cAAc;AAChE,QAAM,UAAU,MAAM,SAAS,aAAa,OAAO;AACnD,QAAM,aAAaA,MAAU,OAAO;AAGpC,QAAM,YAAY,WAAW,YAAY,CAAA,GAAI;AAAA,IAC3C,CAAC,MAAM,EAAE,kBAAkB;AAAA,EAAA;AAG7B,QAAM,SAA+B;AAAA,IACnC,eAAe,SAAS;AAAA,IACxB,cAAc;AAAA,IACd,SAAS;AAAA,IACT,cAAc;AAAA,IACd,MAAM;AAAA,IACN,SAAS;AAAA,IACT,gBAAgB;AAAA,EAAA;AAGlB,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,MAAM,gBAAgB,SAAS,UAAU;AACvD,YAAQ,OAAA;AAAA,MACN,KAAK;AACH,eAAO;AACP,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,IAAA;AAAA,EAEN;AAEA,SAAO;AACT;"}
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAiBH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAoI7B"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/fulltext/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgBH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAqI7B"}
@@ -1,11 +1,14 @@
1
1
  import { join } from "node:path";
2
2
  import { readdir, stat, readFile, writeFile } from "node:fs/promises";
3
3
  import { parse, stringify } from "yaml";
4
- import { loadMeta, updateMetaFiles, saveMeta } from "../../../fulltext/meta.js";
5
- import { getFulltextDir } from "../../../fulltext/paths.js";
4
+ import { loadMeta, updateMetaFiles, saveMeta } from "../../../node_modules/@ncukondo/academic-fulltext/dist/meta.js";
5
+ import { getFulltextDir } from "../../../node_modules/@ncukondo/academic-fulltext/dist/paths.js";
6
+ import "../../../node_modules/@ncukondo/academic-fulltext/dist/convert/jats-parser.js";
7
+ import "node:crypto";
6
8
  const FULLTEXT_FILES = {
7
9
  "fulltext.pdf": "pdf",
8
10
  "fulltext.xml": "xml",
11
+ "fulltext.html": "html",
9
12
  "fulltext.md": "markdown"
10
13
  };
11
14
  async function executeFulltextSync(options) {
@@ -72,6 +75,7 @@ async function executeFulltextSync(options) {
72
75
  const hasFiles = {
73
76
  pdf: !!updatedMeta.files.pdf,
74
77
  xml: !!updatedMeta.files.xml,
78
+ html: !!updatedMeta.files.html,
75
79
  markdown: !!updatedMeta.files.markdown
76
80
  };
77
81
  hasFilesUpdates.set(dirName, hasFiles);