youtube-knowledge-mcp 2.2.0 → 3.0.1

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 (407) hide show
  1. package/README.md +126 -36
  2. package/dist/brain-schemas.d.ts +10 -0
  3. package/dist/brain-schemas.d.ts.map +1 -1
  4. package/dist/harvest-schemas.d.ts +75 -0
  5. package/dist/harvest-schemas.d.ts.map +1 -0
  6. package/dist/harvest-schemas.js +111 -0
  7. package/dist/harvest-schemas.js.map +1 -0
  8. package/dist/registry/brain-tools.d.ts.map +1 -1
  9. package/dist/registry/brain-tools.js +16 -0
  10. package/dist/registry/brain-tools.js.map +1 -1
  11. package/dist/registry/channel-tools.d.ts.map +1 -1
  12. package/dist/registry/channel-tools.js +16 -0
  13. package/dist/registry/channel-tools.js.map +1 -1
  14. package/dist/registry/harvest-tools.d.ts +10 -0
  15. package/dist/registry/harvest-tools.d.ts.map +1 -0
  16. package/dist/registry/harvest-tools.js +109 -0
  17. package/dist/registry/harvest-tools.js.map +1 -0
  18. package/dist/registry/health-tools.d.ts.map +1 -1
  19. package/dist/registry/health-tools.js +18 -0
  20. package/dist/registry/health-tools.js.map +1 -1
  21. package/dist/registry/index.d.ts.map +1 -1
  22. package/dist/registry/index.js +6 -0
  23. package/dist/registry/index.js.map +1 -1
  24. package/dist/registry/media-tools.d.ts.map +1 -1
  25. package/dist/registry/media-tools.js +31 -0
  26. package/dist/registry/media-tools.js.map +1 -1
  27. package/dist/registry/store-tools.d.ts +9 -0
  28. package/dist/registry/store-tools.d.ts.map +1 -0
  29. package/dist/registry/store-tools.js +59 -0
  30. package/dist/registry/store-tools.js.map +1 -0
  31. package/dist/registry/transcript-tools.d.ts +4 -1
  32. package/dist/registry/transcript-tools.d.ts.map +1 -1
  33. package/dist/registry/transcript-tools.js +20 -1
  34. package/dist/registry/transcript-tools.js.map +1 -1
  35. package/dist/registry/video-tools.d.ts.map +1 -1
  36. package/dist/registry/video-tools.js +49 -1
  37. package/dist/registry/video-tools.js.map +1 -1
  38. package/dist/registry/vision-tools.d.ts +7 -0
  39. package/dist/registry/vision-tools.d.ts.map +1 -0
  40. package/dist/registry/vision-tools.js +55 -0
  41. package/dist/registry/vision-tools.js.map +1 -0
  42. package/dist/schemas.d.ts +245 -0
  43. package/dist/schemas.d.ts.map +1 -1
  44. package/dist/schemas.js +58 -0
  45. package/dist/schemas.js.map +1 -1
  46. package/dist/thumbnail-schemas.d.ts +38 -28
  47. package/dist/thumbnail-schemas.d.ts.map +1 -1
  48. package/dist/tools/ask-brain.js +1 -1
  49. package/dist/tools/ask-brain.js.map +1 -1
  50. package/dist/tools/ask-brains.d.ts +66 -0
  51. package/dist/tools/ask-brains.d.ts.map +1 -0
  52. package/dist/tools/ask-brains.js +111 -0
  53. package/dist/tools/ask-brains.js.map +1 -0
  54. package/dist/tools/brain-info.js +1 -1
  55. package/dist/tools/brain-info.js.map +1 -1
  56. package/dist/tools/check-health.d.ts +75 -6
  57. package/dist/tools/check-health.d.ts.map +1 -1
  58. package/dist/tools/check-health.js +114 -6
  59. package/dist/tools/check-health.js.map +1 -1
  60. package/dist/tools/check-new-uploads.d.ts +55 -0
  61. package/dist/tools/check-new-uploads.d.ts.map +1 -0
  62. package/dist/tools/check-new-uploads.js +74 -0
  63. package/dist/tools/check-new-uploads.js.map +1 -0
  64. package/dist/tools/clips.d.ts +15 -0
  65. package/dist/tools/clips.d.ts.map +1 -1
  66. package/dist/tools/clips.js +5 -5
  67. package/dist/tools/clips.js.map +1 -1
  68. package/dist/tools/detect-scenes.d.ts +38 -0
  69. package/dist/tools/detect-scenes.d.ts.map +1 -0
  70. package/dist/tools/detect-scenes.js +66 -0
  71. package/dist/tools/detect-scenes.js.map +1 -0
  72. package/dist/tools/export-data.d.ts +85 -0
  73. package/dist/tools/export-data.d.ts.map +1 -0
  74. package/dist/tools/export-data.js +99 -0
  75. package/dist/tools/export-data.js.map +1 -0
  76. package/dist/tools/fetch-channel-thumbnails.d.ts +8 -8
  77. package/dist/tools/fetch-videos.d.ts +15 -4
  78. package/dist/tools/fetch-videos.d.ts.map +1 -1
  79. package/dist/tools/fetch-videos.js +63 -22
  80. package/dist/tools/fetch-videos.js.map +1 -1
  81. package/dist/tools/frames-subtitles.d.ts.map +1 -1
  82. package/dist/tools/frames-subtitles.js +16 -8
  83. package/dist/tools/frames-subtitles.js.map +1 -1
  84. package/dist/tools/frames.d.ts +73 -0
  85. package/dist/tools/frames.d.ts.map +1 -0
  86. package/dist/tools/frames.js +136 -0
  87. package/dist/tools/frames.js.map +1 -0
  88. package/dist/tools/get-channel-info.d.ts +2 -0
  89. package/dist/tools/get-channel-info.d.ts.map +1 -1
  90. package/dist/tools/get-channel-info.js +4 -0
  91. package/dist/tools/get-channel-info.js.map +1 -1
  92. package/dist/tools/get-comments.d.ts +238 -6
  93. package/dist/tools/get-comments.d.ts.map +1 -1
  94. package/dist/tools/get-comments.js +85 -21
  95. package/dist/tools/get-comments.js.map +1 -1
  96. package/dist/tools/get-coverage.d.ts +122 -0
  97. package/dist/tools/get-coverage.d.ts.map +1 -0
  98. package/dist/tools/get-coverage.js +105 -0
  99. package/dist/tools/get-coverage.js.map +1 -0
  100. package/dist/tools/get-sponsor-segments.d.ts +49 -0
  101. package/dist/tools/get-sponsor-segments.d.ts.map +1 -0
  102. package/dist/tools/get-sponsor-segments.js +61 -0
  103. package/dist/tools/get-sponsor-segments.js.map +1 -0
  104. package/dist/tools/get-thumbnail.d.ts.map +1 -1
  105. package/dist/tools/get-thumbnail.js +2 -1
  106. package/dist/tools/get-thumbnail.js.map +1 -1
  107. package/dist/tools/get-transcript.d.ts +1 -0
  108. package/dist/tools/get-transcript.d.ts.map +1 -1
  109. package/dist/tools/get-transcript.js +11 -4
  110. package/dist/tools/get-transcript.js.map +1 -1
  111. package/dist/tools/get-video-details.d.ts +118 -0
  112. package/dist/tools/get-video-details.d.ts.map +1 -0
  113. package/dist/tools/get-video-details.js +147 -0
  114. package/dist/tools/get-video-details.js.map +1 -0
  115. package/dist/tools/gif-concat.d.ts +49 -0
  116. package/dist/tools/gif-concat.d.ts.map +1 -0
  117. package/dist/tools/gif-concat.js +96 -0
  118. package/dist/tools/gif-concat.js.map +1 -0
  119. package/dist/tools/harvest-channel.d.ts +92 -0
  120. package/dist/tools/harvest-channel.d.ts.map +1 -0
  121. package/dist/tools/harvest-channel.js +62 -0
  122. package/dist/tools/harvest-channel.js.map +1 -0
  123. package/dist/tools/harvest-comments.d.ts +81 -0
  124. package/dist/tools/harvest-comments.d.ts.map +1 -0
  125. package/dist/tools/harvest-comments.js +115 -0
  126. package/dist/tools/harvest-comments.js.map +1 -0
  127. package/dist/tools/harvest-details.d.ts +84 -0
  128. package/dist/tools/harvest-details.d.ts.map +1 -0
  129. package/dist/tools/harvest-details.js +71 -0
  130. package/dist/tools/harvest-details.js.map +1 -0
  131. package/dist/tools/library.js +1 -1
  132. package/dist/tools/library.js.map +1 -1
  133. package/dist/tools/list-captions.d.ts +17 -0
  134. package/dist/tools/list-captions.d.ts.map +1 -0
  135. package/dist/tools/list-captions.js +60 -0
  136. package/dist/tools/list-captions.js.map +1 -0
  137. package/dist/tools/list-channel-playlists.d.ts +30 -0
  138. package/dist/tools/list-channel-playlists.d.ts.map +1 -0
  139. package/dist/tools/list-channel-playlists.js +44 -0
  140. package/dist/tools/list-channel-playlists.js.map +1 -0
  141. package/dist/tools/list-channel-thumbnails.d.ts +10 -10
  142. package/dist/tools/list-channel-thumbnails.js +1 -1
  143. package/dist/tools/list-channel-thumbnails.js.map +1 -1
  144. package/dist/tools/list-library.js +1 -1
  145. package/dist/tools/list-library.js.map +1 -1
  146. package/dist/tools/prune-harvest.d.ts +59 -0
  147. package/dist/tools/prune-harvest.d.ts.map +1 -0
  148. package/dist/tools/prune-harvest.js +108 -0
  149. package/dist/tools/prune-harvest.js.map +1 -0
  150. package/dist/tools/query-comments.d.ts +125 -0
  151. package/dist/tools/query-comments.d.ts.map +1 -0
  152. package/dist/tools/query-comments.js +106 -0
  153. package/dist/tools/query-comments.js.map +1 -0
  154. package/dist/tools/query-videos.d.ts +118 -0
  155. package/dist/tools/query-videos.d.ts.map +1 -0
  156. package/dist/tools/query-videos.js +97 -0
  157. package/dist/tools/query-videos.js.map +1 -0
  158. package/dist/tools/repair-store.d.ts +34 -0
  159. package/dist/tools/repair-store.d.ts.map +1 -0
  160. package/dist/tools/repair-store.js +58 -0
  161. package/dist/tools/repair-store.js.map +1 -0
  162. package/dist/tools/search-channels.d.ts +6 -0
  163. package/dist/tools/search-channels.d.ts.map +1 -1
  164. package/dist/tools/search-channels.js +5 -1
  165. package/dist/tools/search-channels.js.map +1 -1
  166. package/dist/tools/search-videos.js +1 -1
  167. package/dist/tools/search-videos.js.map +1 -1
  168. package/dist/tools/transcribe-video.d.ts +28 -0
  169. package/dist/tools/transcribe-video.d.ts.map +1 -0
  170. package/dist/tools/transcribe-video.js +52 -0
  171. package/dist/tools/transcribe-video.js.map +1 -0
  172. package/dist/utils/brain-fusion.d.ts +24 -0
  173. package/dist/utils/brain-fusion.d.ts.map +1 -0
  174. package/dist/utils/brain-fusion.js +18 -0
  175. package/dist/utils/brain-fusion.js.map +1 -0
  176. package/dist/utils/channel-playlists.d.ts +21 -0
  177. package/dist/utils/channel-playlists.d.ts.map +1 -0
  178. package/dist/utils/channel-playlists.js +56 -0
  179. package/dist/utils/channel-playlists.js.map +1 -0
  180. package/dist/utils/clips.d.ts.map +1 -1
  181. package/dist/utils/clips.js +3 -17
  182. package/dist/utils/clips.js.map +1 -1
  183. package/dist/utils/comment-store.d.ts +71 -0
  184. package/dist/utils/comment-store.d.ts.map +1 -0
  185. package/dist/utils/comment-store.js +129 -0
  186. package/dist/utils/comment-store.js.map +1 -0
  187. package/dist/utils/comment-threads.d.ts +51 -0
  188. package/dist/utils/comment-threads.d.ts.map +1 -0
  189. package/dist/utils/comment-threads.js +76 -0
  190. package/dist/utils/comment-threads.js.map +1 -0
  191. package/dist/utils/concat.d.ts +19 -0
  192. package/dist/utils/concat.d.ts.map +1 -0
  193. package/dist/utils/concat.js +135 -0
  194. package/dist/utils/concat.js.map +1 -0
  195. package/dist/utils/contact-sheet.d.ts +28 -0
  196. package/dist/utils/contact-sheet.d.ts.map +1 -0
  197. package/dist/utils/contact-sheet.js +65 -0
  198. package/dist/utils/contact-sheet.js.map +1 -0
  199. package/dist/utils/coverage-text.d.ts +3 -0
  200. package/dist/utils/coverage-text.d.ts.map +1 -0
  201. package/dist/utils/coverage-text.js +62 -0
  202. package/dist/utils/coverage-text.js.map +1 -0
  203. package/dist/utils/coverage.d.ts +57 -0
  204. package/dist/utils/coverage.d.ts.map +1 -0
  205. package/dist/utils/coverage.js +142 -0
  206. package/dist/utils/coverage.js.map +1 -0
  207. package/dist/utils/detail-batch.d.ts +25 -0
  208. package/dist/utils/detail-batch.d.ts.map +1 -0
  209. package/dist/utils/detail-batch.js +58 -0
  210. package/dist/utils/detail-batch.js.map +1 -0
  211. package/dist/utils/errors.d.ts +1 -1
  212. package/dist/utils/errors.d.ts.map +1 -1
  213. package/dist/utils/errors.js +19 -1
  214. package/dist/utils/errors.js.map +1 -1
  215. package/dist/utils/export-data.d.ts +41 -0
  216. package/dist/utils/export-data.d.ts.map +1 -0
  217. package/dist/utils/export-data.js +114 -0
  218. package/dist/utils/export-data.js.map +1 -0
  219. package/dist/utils/export-format.d.ts +23 -0
  220. package/dist/utils/export-format.d.ts.map +1 -0
  221. package/dist/utils/export-format.js +59 -0
  222. package/dist/utils/export-format.js.map +1 -0
  223. package/dist/utils/export-load.d.ts +28 -0
  224. package/dist/utils/export-load.d.ts.map +1 -0
  225. package/dist/utils/export-load.js +70 -0
  226. package/dist/utils/export-load.js.map +1 -0
  227. package/dist/utils/export-rows.d.ts +35 -0
  228. package/dist/utils/export-rows.d.ts.map +1 -0
  229. package/dist/utils/export-rows.js +69 -0
  230. package/dist/utils/export-rows.js.map +1 -0
  231. package/dist/utils/ffmpeg.d.ts +26 -0
  232. package/dist/utils/ffmpeg.d.ts.map +1 -0
  233. package/dist/utils/ffmpeg.js +28 -0
  234. package/dist/utils/ffmpeg.js.map +1 -0
  235. package/dist/utils/format.d.ts +23 -1
  236. package/dist/utils/format.d.ts.map +1 -1
  237. package/dist/utils/format.js +5 -1
  238. package/dist/utils/format.js.map +1 -1
  239. package/dist/utils/frame-plan.d.ts +24 -0
  240. package/dist/utils/frame-plan.d.ts.map +1 -0
  241. package/dist/utils/frame-plan.js +58 -0
  242. package/dist/utils/frame-plan.js.map +1 -0
  243. package/dist/utils/frames.d.ts +32 -0
  244. package/dist/utils/frames.d.ts.map +1 -0
  245. package/dist/utils/frames.js +60 -0
  246. package/dist/utils/frames.js.map +1 -0
  247. package/dist/utils/gif.d.ts +31 -0
  248. package/dist/utils/gif.d.ts.map +1 -0
  249. package/dist/utils/gif.js +77 -0
  250. package/dist/utils/gif.js.map +1 -0
  251. package/dist/utils/harvest-catalog.d.ts +54 -0
  252. package/dist/utils/harvest-catalog.d.ts.map +1 -0
  253. package/dist/utils/harvest-catalog.js +173 -0
  254. package/dist/utils/harvest-catalog.js.map +1 -0
  255. package/dist/utils/harvest-details.d.ts +30 -0
  256. package/dist/utils/harvest-details.d.ts.map +1 -0
  257. package/dist/utils/harvest-details.js +98 -0
  258. package/dist/utils/harvest-details.js.map +1 -0
  259. package/dist/utils/harvest-lock.d.ts +3 -0
  260. package/dist/utils/harvest-lock.d.ts.map +1 -0
  261. package/dist/utils/harvest-lock.js +26 -0
  262. package/dist/utils/harvest-lock.js.map +1 -0
  263. package/dist/utils/harvest-receipts.d.ts +14 -0
  264. package/dist/utils/harvest-receipts.d.ts.map +1 -0
  265. package/dist/utils/harvest-receipts.js +110 -0
  266. package/dist/utils/harvest-receipts.js.map +1 -0
  267. package/dist/utils/listing-cursor.d.ts +31 -0
  268. package/dist/utils/listing-cursor.d.ts.map +1 -0
  269. package/dist/utils/listing-cursor.js +39 -0
  270. package/dist/utils/listing-cursor.js.map +1 -0
  271. package/dist/utils/local-transcription.d.ts +14 -0
  272. package/dist/utils/local-transcription.d.ts.map +1 -0
  273. package/dist/utils/local-transcription.js +85 -0
  274. package/dist/utils/local-transcription.js.map +1 -0
  275. package/dist/utils/moment.d.ts +24 -0
  276. package/dist/utils/moment.d.ts.map +1 -0
  277. package/dist/utils/moment.js +22 -0
  278. package/dist/utils/moment.js.map +1 -0
  279. package/dist/utils/most-replayed.d.ts +15 -0
  280. package/dist/utils/most-replayed.d.ts.map +1 -0
  281. package/dist/utils/most-replayed.js +25 -0
  282. package/dist/utils/most-replayed.js.map +1 -0
  283. package/dist/utils/pot-preflight.d.ts +53 -0
  284. package/dist/utils/pot-preflight.d.ts.map +1 -0
  285. package/dist/utils/pot-preflight.js +146 -0
  286. package/dist/utils/pot-preflight.js.map +1 -0
  287. package/dist/utils/scenes.d.ts +39 -0
  288. package/dist/utils/scenes.d.ts.map +1 -0
  289. package/dist/utils/scenes.js +83 -0
  290. package/dist/utils/scenes.js.map +1 -0
  291. package/dist/utils/seconds.d.ts +5 -0
  292. package/dist/utils/seconds.d.ts.map +1 -0
  293. package/dist/utils/seconds.js +7 -0
  294. package/dist/utils/seconds.js.map +1 -0
  295. package/dist/utils/sleep.d.ts +9 -0
  296. package/dist/utils/sleep.d.ts.map +1 -0
  297. package/dist/utils/sleep.js +25 -0
  298. package/dist/utils/sleep.js.map +1 -0
  299. package/dist/utils/sponsorblock.d.ts +24 -0
  300. package/dist/utils/sponsorblock.d.ts.map +1 -0
  301. package/dist/utils/sponsorblock.js +153 -0
  302. package/dist/utils/sponsorblock.js.map +1 -0
  303. package/dist/utils/store-health.d.ts +37 -0
  304. package/dist/utils/store-health.d.ts.map +1 -0
  305. package/dist/utils/store-health.js +92 -0
  306. package/dist/utils/store-health.js.map +1 -0
  307. package/dist/utils/store-migrations.d.ts +33 -0
  308. package/dist/utils/store-migrations.d.ts.map +1 -0
  309. package/dist/utils/store-migrations.js +41 -0
  310. package/dist/utils/store-migrations.js.map +1 -0
  311. package/dist/utils/store-paths.d.ts +33 -0
  312. package/dist/utils/store-paths.d.ts.map +1 -0
  313. package/dist/utils/store-paths.js +48 -0
  314. package/dist/utils/store-paths.js.map +1 -0
  315. package/dist/utils/store-rows.d.ts +22 -0
  316. package/dist/utils/store-rows.d.ts.map +1 -0
  317. package/dist/utils/store-rows.js +62 -0
  318. package/dist/utils/store-rows.js.map +1 -0
  319. package/dist/utils/store-schema.d.ts +16 -0
  320. package/dist/utils/store-schema.d.ts.map +1 -0
  321. package/dist/utils/store-schema.js +168 -0
  322. package/dist/utils/store-schema.js.map +1 -0
  323. package/dist/utils/store.d.ts +34 -0
  324. package/dist/utils/store.d.ts.map +1 -0
  325. package/dist/utils/store.js +157 -0
  326. package/dist/utils/store.js.map +1 -0
  327. package/dist/utils/transcript-cache.d.ts +5 -1
  328. package/dist/utils/transcript-cache.d.ts.map +1 -1
  329. package/dist/utils/transcript-cache.js +31 -50
  330. package/dist/utils/transcript-cache.js.map +1 -1
  331. package/dist/utils/transcript-files.d.ts +24 -0
  332. package/dist/utils/transcript-files.d.ts.map +1 -0
  333. package/dist/utils/transcript-files.js +61 -0
  334. package/dist/utils/transcript-files.js.map +1 -0
  335. package/dist/utils/transcript.d.ts +3 -0
  336. package/dist/utils/transcript.d.ts.map +1 -1
  337. package/dist/utils/transcript.js +2 -0
  338. package/dist/utils/transcript.js.map +1 -1
  339. package/dist/utils/upload-check.d.ts +38 -0
  340. package/dist/utils/upload-check.d.ts.map +1 -0
  341. package/dist/utils/upload-check.js +93 -0
  342. package/dist/utils/upload-check.js.map +1 -0
  343. package/dist/utils/validate.d.ts +2 -0
  344. package/dist/utils/validate.d.ts.map +1 -1
  345. package/dist/utils/validate.js +4 -0
  346. package/dist/utils/validate.js.map +1 -1
  347. package/dist/utils/video-detail-store.d.ts +47 -0
  348. package/dist/utils/video-detail-store.d.ts.map +1 -0
  349. package/dist/utils/video-detail-store.js +89 -0
  350. package/dist/utils/video-detail-store.js.map +1 -0
  351. package/dist/utils/video-stats-cache.d.ts +16 -0
  352. package/dist/utils/video-stats-cache.d.ts.map +1 -0
  353. package/dist/utils/video-stats-cache.js +81 -0
  354. package/dist/utils/video-stats-cache.js.map +1 -0
  355. package/dist/utils/video-stats-schema.d.ts +187 -0
  356. package/dist/utils/video-stats-schema.d.ts.map +1 -0
  357. package/dist/utils/video-stats-schema.js +50 -0
  358. package/dist/utils/video-stats-schema.js.map +1 -0
  359. package/dist/utils/video-stats-storage.d.ts +16 -0
  360. package/dist/utils/video-stats-storage.d.ts.map +1 -0
  361. package/dist/utils/video-stats-storage.js +37 -0
  362. package/dist/utils/video-stats-storage.js.map +1 -0
  363. package/dist/utils/video-stats.d.ts +82 -0
  364. package/dist/utils/video-stats.d.ts.map +1 -0
  365. package/dist/utils/video-stats.js +108 -0
  366. package/dist/utils/video-stats.js.map +1 -0
  367. package/dist/utils/video-store.d.ts +69 -0
  368. package/dist/utils/video-store.d.ts.map +1 -0
  369. package/dist/utils/video-store.js +77 -0
  370. package/dist/utils/video-store.js.map +1 -0
  371. package/dist/utils/video-stream.d.ts +21 -0
  372. package/dist/utils/video-stream.d.ts.map +1 -0
  373. package/dist/utils/video-stream.js +36 -0
  374. package/dist/utils/video-stream.js.map +1 -0
  375. package/dist/utils/whisper.d.ts +39 -0
  376. package/dist/utils/whisper.d.ts.map +1 -0
  377. package/dist/utils/whisper.js +137 -0
  378. package/dist/utils/whisper.js.map +1 -0
  379. package/dist/utils/youtube-channel.d.ts +13 -2
  380. package/dist/utils/youtube-channel.d.ts.map +1 -1
  381. package/dist/utils/youtube-channel.js +39 -3
  382. package/dist/utils/youtube-channel.js.map +1 -1
  383. package/dist/utils/youtube-schemas.d.ts +104 -0
  384. package/dist/utils/youtube-schemas.d.ts.map +1 -1
  385. package/dist/utils/youtube-schemas.js +32 -0
  386. package/dist/utils/youtube-schemas.js.map +1 -1
  387. package/dist/utils/youtube-video.d.ts +22 -1
  388. package/dist/utils/youtube-video.d.ts.map +1 -1
  389. package/dist/utils/youtube-video.js +40 -28
  390. package/dist/utils/youtube-video.js.map +1 -1
  391. package/dist/utils/ytdlp-limiter.d.ts +3 -0
  392. package/dist/utils/ytdlp-limiter.d.ts.map +1 -1
  393. package/dist/utils/ytdlp-limiter.js +19 -3
  394. package/dist/utils/ytdlp-limiter.js.map +1 -1
  395. package/dist/utils/ytdlp-pacer.d.ts +64 -0
  396. package/dist/utils/ytdlp-pacer.d.ts.map +1 -0
  397. package/dist/utils/ytdlp-pacer.js +165 -0
  398. package/dist/utils/ytdlp-pacer.js.map +1 -0
  399. package/dist/utils/ytdlp-stream.d.ts +32 -0
  400. package/dist/utils/ytdlp-stream.d.ts.map +1 -0
  401. package/dist/utils/ytdlp-stream.js +61 -0
  402. package/dist/utils/ytdlp-stream.js.map +1 -0
  403. package/dist/utils/ytdlp.d.ts +10 -0
  404. package/dist/utils/ytdlp.d.ts.map +1 -1
  405. package/dist/utils/ytdlp.js +36 -25
  406. package/dist/utils/ytdlp.js.map +1 -1
  407. package/package.json +10 -4
@@ -0,0 +1,57 @@
1
+ import { type Coverage, type CompletenessReason, type ExpectedSource, type HarvestScope } from '../harvest-schemas.js';
2
+ /**
3
+ * The one constructor for a completeness receipt.
4
+ *
5
+ * A wrong `complete: true` is the worst thing this server can emit: it is the
6
+ * difference between a consumer saying "here is a sample of the comments" and
7
+ * "here is the video's full comment history". So `complete` is not a field a
8
+ * caller sets — it is derived here, and the derivation is guarded by
9
+ * invariants that are asserted in code rather than merely documented.
10
+ *
11
+ * The rule that does the most work: `expected` is what the SOURCE said, and is
12
+ * never derived from `have`. Copying the number we collected into the field
13
+ * that means "how many there are" would turn "we stopped" into "there was
14
+ * nothing more", silently, and every check downstream would agree with it.
15
+ */
16
+ /** A complete receipt is still only as fresh as its harvest; comments accrue. */
17
+ export declare const RECEIPT_STALE_MS: number;
18
+ /** Beyond this, receipts are summarised rather than listed in a tool result. */
19
+ export declare const COVERAGE_IN_RESULT_LIMIT = 100;
20
+ export interface ExpectedTotal {
21
+ value: number;
22
+ source: ExpectedSource;
23
+ }
24
+ export interface CoverageInput {
25
+ scope: HarvestScope;
26
+ targetId: string;
27
+ have: number;
28
+ source: string;
29
+ /**
30
+ * Only ever an explicit pair or nothing. There is deliberately no code path
31
+ * that accepts a bare number, because the number would have to come from
32
+ * somewhere and `have` is the nearest thing to hand.
33
+ */
34
+ expected?: ExpectedTotal;
35
+ ranToExhaustion?: boolean;
36
+ reason?: CompletenessReason;
37
+ limitApplied?: number;
38
+ sortApplied?: 'top' | 'new';
39
+ resumeToken?: string;
40
+ harvestedAt?: Date;
41
+ note?: string;
42
+ }
43
+ /** Thrown for a receipt that contradicts itself. Not a YouTubeError: no caller can fix it. */
44
+ export declare class CoverageInvariantError extends Error {
45
+ constructor(message: string);
46
+ }
47
+ /**
48
+ * Checks a receipt against every invariant.
49
+ *
50
+ * Called inside `coverageOf` and again by handlers before a result goes out,
51
+ * because the cost of the second call is nothing and the cost of a wrong
52
+ * `complete: true` reaching a consumer is the whole point of this module.
53
+ */
54
+ export declare function assertCoverageConsistent(coverage: Coverage): void;
55
+ /** Builds a validated receipt. The only way to make one. */
56
+ export declare function coverageOf(input: CoverageInput): Coverage;
57
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/utils/coverage.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;GAaG;AAEH,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,QAAiB,CAAC;AAE/C,gFAAgF;AAChF,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,8FAA8F;AAC9F,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAwCD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CA0CjE;AAED,4DAA4D;AAC5D,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,QAAQ,CA8BzD"}
@@ -0,0 +1,142 @@
1
+ import { coverageSchema, } from '../harvest-schemas.js';
2
+ /**
3
+ * The one constructor for a completeness receipt.
4
+ *
5
+ * A wrong `complete: true` is the worst thing this server can emit: it is the
6
+ * difference between a consumer saying "here is a sample of the comments" and
7
+ * "here is the video's full comment history". So `complete` is not a field a
8
+ * caller sets — it is derived here, and the derivation is guarded by
9
+ * invariants that are asserted in code rather than merely documented.
10
+ *
11
+ * The rule that does the most work: `expected` is what the SOURCE said, and is
12
+ * never derived from `have`. Copying the number we collected into the field
13
+ * that means "how many there are" would turn "we stopped" into "there was
14
+ * nothing more", silently, and every check downstream would agree with it.
15
+ */
16
+ /** A complete receipt is still only as fresh as its harvest; comments accrue. */
17
+ export const RECEIPT_STALE_MS = 7 * 86_400_000;
18
+ /** Beyond this, receipts are summarised rather than listed in a tool result. */
19
+ export const COVERAGE_IN_RESULT_LIMIT = 100;
20
+ /** Thrown for a receipt that contradicts itself. Not a YouTubeError: no caller can fix it. */
21
+ export class CoverageInvariantError extends Error {
22
+ constructor(message) {
23
+ super(`Coverage invariant violated: ${message}`);
24
+ this.name = 'CoverageInvariantError';
25
+ }
26
+ }
27
+ function capWasBinding(input) {
28
+ return input.limitApplied !== undefined && input.have >= input.limitApplied;
29
+ }
30
+ /**
31
+ * Whether this server can prove it holds everything in scope.
32
+ *
33
+ * Two independent ways to prove it, and a cap overrides both:
34
+ *
35
+ * - the source stated a total and we hold at least that many, or
36
+ * - the source's own iteration ran out (`source-exhausted`).
37
+ *
38
+ * Exhaustion alone is not proof. Measured against yt-dlp 2026.08.19: a run
39
+ * capped at 40 parent comments returned a non-null `comment_count` of 58,
40
+ * because `itertools.islice` ends iteration at the cap exactly as a natural
41
+ * end would. So a binding cap disqualifies completeness even when the source
42
+ * says it finished.
43
+ */
44
+ function isProvablyComplete(input) {
45
+ if (capWasBinding(input))
46
+ return false;
47
+ if (input.expected !== undefined) {
48
+ if (input.expected.source === 'source-exhausted')
49
+ return input.ranToExhaustion !== false;
50
+ return input.have >= input.expected.value;
51
+ }
52
+ return false;
53
+ }
54
+ function derivedReason(input, complete) {
55
+ if (complete)
56
+ return 'COMPLETE';
57
+ if (input.reason !== undefined && input.reason !== 'COMPLETE')
58
+ return input.reason;
59
+ if (capWasBinding(input))
60
+ return 'CAP_REACHED';
61
+ // Nothing said there was more, and nothing proved there was not. That is not
62
+ // completeness; it is the absence of evidence, and it has its own name.
63
+ return 'SOURCE_SILENT';
64
+ }
65
+ /**
66
+ * Checks a receipt against every invariant.
67
+ *
68
+ * Called inside `coverageOf` and again by handlers before a result goes out,
69
+ * because the cost of the second call is nothing and the cost of a wrong
70
+ * `complete: true` reaching a consumer is the whole point of this module.
71
+ */
72
+ export function assertCoverageConsistent(coverage) {
73
+ const fail = (message) => {
74
+ throw new CoverageInvariantError(message);
75
+ };
76
+ if (coverage.complete && coverage.reason !== 'COMPLETE') {
77
+ fail(`complete is true but reason is ${coverage.reason}`);
78
+ }
79
+ if (!coverage.complete && coverage.reason === 'COMPLETE') {
80
+ fail('reason is COMPLETE but complete is false');
81
+ }
82
+ if ((coverage.expected === undefined) !== (coverage.expectedSource === undefined)) {
83
+ fail('expected and expectedSource must be present or absent together');
84
+ }
85
+ if (coverage.complete) {
86
+ const exhausted = coverage.expectedSource === 'source-exhausted';
87
+ const enough = coverage.expected !== undefined && coverage.have >= coverage.expected;
88
+ if (!exhausted && !enough) {
89
+ fail('complete is true without either a met expected total or an exhausted source');
90
+ }
91
+ }
92
+ // There is deliberately no separate "COMPLETE without an expected total"
93
+ // guard. It reads like a necessary invariant and is unreachable: reaching it
94
+ // needs reason COMPLETE, which the checks above already force to pair with
95
+ // complete:true, which in turn already demands either a met total or an
96
+ // exhausted source. An unreachable guard is not defence in depth, it is code
97
+ // no test can justify.
98
+ // One-directional on purpose. "A token exactly when incomplete" is the
99
+ // tempting symmetry, and it is wrong: plenty of incomplete receipts have
100
+ // nothing to resume — SOURCE_REFUSED, COMMENTS_DISABLED, a caller that
101
+ // simply did not supply one. What must never happen is a token on a
102
+ // complete receipt, which would invite a caller to page past the end.
103
+ if (coverage.resumeToken !== undefined && coverage.complete) {
104
+ fail('a complete receipt cannot carry a resumeToken');
105
+ }
106
+ if (coverage.limitApplied !== undefined &&
107
+ coverage.have >= coverage.limitApplied &&
108
+ coverage.reason === 'COMPLETE') {
109
+ fail('a binding cap cannot produce COMPLETE');
110
+ }
111
+ }
112
+ /** Builds a validated receipt. The only way to make one. */
113
+ export function coverageOf(input) {
114
+ const complete = isProvablyComplete(input);
115
+ const reason = derivedReason(input, complete);
116
+ const harvestedAt = input.harvestedAt ?? new Date();
117
+ const coverage = coverageSchema.parse({
118
+ scope: input.scope,
119
+ targetId: input.targetId,
120
+ complete,
121
+ reason,
122
+ have: input.have,
123
+ ...(input.expected === undefined
124
+ ? {}
125
+ : { expected: input.expected.value, expectedSource: input.expected.source }),
126
+ ...(input.ranToExhaustion === undefined ? {} : { ranToExhaustion: input.ranToExhaustion }),
127
+ source: input.source,
128
+ harvestedAt: harvestedAt.toISOString(),
129
+ ...(complete
130
+ ? { staleAfter: new Date(harvestedAt.getTime() + RECEIPT_STALE_MS).toISOString() }
131
+ : {}),
132
+ ...(input.limitApplied === undefined ? {} : { limitApplied: input.limitApplied }),
133
+ ...(input.sortApplied === undefined ? {} : { sortApplied: input.sortApplied }),
134
+ // A resume token is meaningless on a complete receipt, and its absence is
135
+ // how a caller knows there is nothing left to ask for.
136
+ ...(complete || input.resumeToken === undefined ? {} : { resumeToken: input.resumeToken }),
137
+ ...(input.note === undefined ? {} : { note: input.note }),
138
+ });
139
+ assertCoverageConsistent(coverage);
140
+ return coverage;
141
+ }
142
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/utils/coverage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { CompletenessReason } from '../harvest-schemas.js';
3
+ import { type YouTubeErrorCode } from './errors.js';
4
+ import { fetchVideoStatsRow } from './video-stats.js';
5
+ /** Reading one batch of videos and keeping what came back, a batch at a time. */
6
+ export interface DetailFailure {
7
+ videoId: string;
8
+ code: YouTubeErrorCode;
9
+ }
10
+ /** Refusals that say the next video will be refused too: stop rather than hammer. */
11
+ export declare const STOPPING: Partial<Record<YouTubeErrorCode, CompletenessReason>>;
12
+ export type Read = {
13
+ videoId: string;
14
+ row: Awaited<ReturnType<typeof fetchVideoStatsRow>>;
15
+ } | {
16
+ videoId: string;
17
+ code: YouTubeErrorCode;
18
+ };
19
+ export declare function read(videoId: string): Promise<Read>;
20
+ export declare function stoppingCode(batch: Read[]): YouTubeErrorCode | undefined;
21
+ export declare function persist(database: DatabaseSync, batch: Read[]): {
22
+ stored: number;
23
+ failures: DetailFailure[];
24
+ };
25
+ //# sourceMappingURL=detail-batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detail-batch.d.ts","sourceRoot":"","sources":["../../src/utils/detail-batch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAqC,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AASvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,iFAAiF;AAEjF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,qFAAqF;AACrF,eAAO,MAAM,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAK1E,CAAC;AAEF,MAAM,MAAM,IAAI,GACZ;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAA;CAAE,GACxE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAEhD,wBAAsB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAczD;AAUD,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAIxE;AAED,wBAAgB,OAAO,CACrB,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,IAAI,EAAE,GACZ;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,aAAa,EAAE,CAAA;CAAE,CAqB/C"}
@@ -0,0 +1,58 @@
1
+ import { YouTubeError, classifyPlayability } from './errors.js';
2
+ import { inTransaction } from './store.js';
3
+ import { READ_FAILED_AVAILABILITY, UNREADABLE_AVAILABILITY, markUnreadable, saveVideoDetail, } from './video-detail-store.js';
4
+ import { fetchVideoStatsRow } from './video-stats.js';
5
+ /** Refusals that say the next video will be refused too: stop rather than hammer. */
6
+ export const STOPPING = {
7
+ BOT_CHECK: 'SOURCE_REFUSED',
8
+ LOGIN_REQUIRED: 'SOURCE_REFUSED',
9
+ RATE_LIMITED: 'RATE_LIMITED',
10
+ CANCELLED: 'CANCELLED',
11
+ };
12
+ export async function read(videoId) {
13
+ try {
14
+ // Tolerant, so a private or members-only video comes back as a row whose
15
+ // availability says so, not as a failure that asks to sign in and would be taken
16
+ // for a problem with the whole session.
17
+ const row = await fetchVideoStatsRow(videoId, { tolerateRefusal: true });
18
+ const refusal = classifyPlayability({
19
+ availability: row.availability ?? undefined,
20
+ liveStatus: row.live_status ?? undefined,
21
+ });
22
+ return refusal === undefined ? { videoId, row } : { videoId, code: refusal.code };
23
+ }
24
+ catch (error) {
25
+ return { videoId, code: error instanceof YouTubeError ? error.code : 'YTDLP_FAILED' };
26
+ }
27
+ }
28
+ /** What to record for a video that failed, so it is not tried first on every run. */
29
+ function unreadableMarker(code) {
30
+ return (Object.entries(UNREADABLE_AVAILABILITY).find(([key]) => key === code)?.[1] ??
31
+ READ_FAILED_AVAILABILITY);
32
+ }
33
+ export function stoppingCode(batch) {
34
+ return batch
35
+ .flatMap((outcome) => ('code' in outcome ? [outcome.code] : []))
36
+ .find((code) => STOPPING[code] !== undefined);
37
+ }
38
+ export function persist(database, batch) {
39
+ const failures = [];
40
+ let stored = 0;
41
+ inTransaction(database, () => {
42
+ for (const outcome of batch) {
43
+ if ('row' in outcome) {
44
+ saveVideoDetail(database, { ...outcome.row, id: outcome.videoId }, Date.now());
45
+ stored += 1;
46
+ continue;
47
+ }
48
+ failures.push({ videoId: outcome.videoId, code: outcome.code });
49
+ // A refusal that will hit every video is a session problem: the run stops for it
50
+ // below, and marking this video would blame it for something it did not do.
51
+ if (STOPPING[outcome.code] === undefined) {
52
+ markUnreadable(database, outcome.videoId, unreadableMarker(outcome.code));
53
+ }
54
+ }
55
+ });
56
+ return { stored, failures };
57
+ }
58
+ //# sourceMappingURL=detail-batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detail-batch.js","sourceRoot":"","sources":["../../src/utils/detail-batch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAyB,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EAEvB,cAAc,EACd,eAAe,GAChB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAStD,qFAAqF;AACrF,MAAM,CAAC,MAAM,QAAQ,GAA0D;IAC7E,SAAS,EAAE,gBAAgB;IAC3B,cAAc,EAAE,gBAAgB;IAChC,YAAY,EAAE,cAAc;IAC5B,SAAS,EAAE,WAAW;CACvB,CAAC;AAMF,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAe;IACxC,IAAI,CAAC;QACH,yEAAyE;QACzE,iFAAiF;QACjF,wCAAwC;QACxC,MAAM,GAAG,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,mBAAmB,CAAC;YAClC,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,SAAS;YAC3C,UAAU,EAAE,GAAG,CAAC,WAAW,IAAI,SAAS;SACzC,CAAC,CAAC;QACH,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;IACpF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,YAAY,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC;IACxF,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,SAAS,gBAAgB,CAAC,IAAsB;IAC9C,OAAO,CACL,MAAM,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1E,wBAAwB,CACzB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK;SACT,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAC/D,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,QAAsB,EACtB,KAAa;IAEb,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,aAAa,CAAC,QAAQ,EAAE,GAAG,EAAE;QAC3B,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;YAC5B,IAAI,KAAK,IAAI,OAAO,EAAE,CAAC;gBACrB,eAAe,CAAC,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;gBAC/E,MAAM,IAAI,CAAC,CAAC;gBACZ,SAAS;YACX,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YAChE,iFAAiF;YACjF,4EAA4E;YAC5E,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;gBACzC,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
@@ -7,7 +7,7 @@ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
7
7
  * than a wall of yt-dlp stderr it has to guess at — and never a leaked command
8
8
  * line or stack trace.
9
9
  */
10
- export type YouTubeErrorCode = 'PRIVATE' | 'AGE_GATED' | 'MEMBERS_ONLY' | 'PREMIUM_ONLY' | 'LOGIN_REQUIRED' | 'BOT_CHECK' | 'NOT_FOUND' | 'NO_CAPTIONS' | 'LIVE_NOT_ENDED' | 'RATE_LIMITED' | 'TIMEOUT' | 'CANCELLED' | 'YTDLP_MISSING' | 'FFMPEG_MISSING' | 'YTDLP_FAILED' | 'FETCH_FAILED' | 'MALFORMED_RESPONSE' | 'INVALID_INPUT';
10
+ export type YouTubeErrorCode = 'PRIVATE' | 'AGE_GATED' | 'MEMBERS_ONLY' | 'PREMIUM_ONLY' | 'LOGIN_REQUIRED' | 'BOT_CHECK' | 'NOT_FOUND' | 'NO_CAPTIONS' | 'LIVE_NOT_ENDED' | 'RATE_LIMITED' | 'TIMEOUT' | 'CANCELLED' | 'YTDLP_MISSING' | 'FFMPEG_MISSING' | 'WHISPER_MISSING' | 'YTDLP_FAILED' | 'FETCH_FAILED' | 'MALFORMED_RESPONSE' | 'INVALID_INPUT' | 'STORE_UNAVAILABLE' | 'STORE_CORRUPT';
11
11
  export interface YouTubeErrorOptions {
12
12
  /** What the caller should try next. Shown to the model verbatim. */
13
13
  nextStep?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,WAAW,GACX,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,WAAW,GACX,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,cAAc,GACd,SAAS,GACT,WAAW,GACX,eAAe,GACf,gBAAgB,GAChB,cAAc,GACd,cAAc,GACd,oBAAoB,GACpB,eAAe,CAAC;AAEpB,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB;IAQtF,iFAAiF;IACjF,aAAa,IAAI,MAAM;CAGxB;AAgGD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,YAAY,CAkBlF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,YAAY,GAAG,SAAS,CA2C3B;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAQ3D;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAM1D"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,WAAW,GACX,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,WAAW,GACX,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,cAAc,GACd,SAAS,GACT,WAAW,GACX,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,cAAc,GACd,cAAc,GACd,oBAAoB,GACpB,eAAe,GACf,mBAAmB,GACnB,eAAe,CAAC;AAEpB,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB;IAQtF,iFAAiF;IACjF,aAAa,IAAI,MAAM;CAGxB;AAkHD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,YAAY,CAkBlF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,YAAY,GAAG,SAAS,CA2C3B;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAQ3D;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAM1D"}
@@ -31,7 +31,15 @@ const RULES = [
31
31
  emits: 'Sign in to confirm you',
32
32
  source: 'playabilityStatus.reason, re-raised by yt_dlp/extractor/youtube/_video.py',
33
33
  message: 'YouTube is asking this client to prove it is not a bot.',
34
- nextStep: `Per-video reads from this address need a signed-in session. ${COOKIE_HINT}`,
34
+ // Ordered by what was measured on 2026-09-06, not by what sounds most
35
+ // technical. From a datacenter address every account-free lever was
36
+ // installed and tested — a PO token provider (yt-dlp never even reaches
37
+ // the point of requesting one; it fails at the player response first),
38
+ // eight player clients, a 15-minute cooldown, and curl_cffi TLS
39
+ // impersonation — and none of them changed the answer. So the address
40
+ // comes first and the PO token provider comes last: leading with a remedy
41
+ // that was measured not to work sends the reader down a dead end.
42
+ nextStep: `Per-video reads are refused from this address. Datacenter and VPN/exit-node addresses are gated regardless of client settings, so check whether this host egresses through one. Otherwise: ${COOKIE_HINT} On a residential address a missing PO token provider can be the cause instead — check \`check_health\`.`,
35
43
  },
36
44
  {
37
45
  code: 'RATE_LIMITED',
@@ -43,6 +51,16 @@ const RULES = [
43
51
  nextStep: 'Wait a minute before retrying, and request fewer items per call.',
44
52
  retryable: true,
45
53
  },
54
+ {
55
+ // Raised as an expected error, so a harvest that walks every tab of a channel meets
56
+ // it for each tab the channel lacks. It is an answer, not a failure, and has to be
57
+ // recognisable as one: the generic fallback below carries none of yt-dlp's words.
58
+ code: 'NOT_FOUND',
59
+ emits: 'This channel does not have a',
60
+ source: 'yt_dlp/extractor/youtube/_tab.py:2393',
61
+ message: 'This channel does not have that tab.',
62
+ nextStep: 'Not every channel has every tab; this is not an error with the channel.',
63
+ },
46
64
  {
47
65
  code: 'NO_CAPTIONS',
48
66
  emits: 'There are no subtitles for the requested languages',
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { Coverage } from '../harvest-schemas.js';
3
+ /**
4
+ * Taking harvested data out of the store, as a file.
5
+ *
6
+ * What is harvested is only as whole as its receipt says, and a file is read without
7
+ * the tool that made it. So an export carries its receipts with it: inside the file
8
+ * for JSON and Markdown, and beside it for CSV, which has nowhere to put them.
9
+ */
10
+ export declare const DEFAULT_EXPORT_ROWS = 10000;
11
+ export declare const MAX_EXPORT_ROWS = 100000;
12
+ export type ExportDataset = 'videos' | 'comments';
13
+ export type ExportFormat = 'json' | 'csv' | 'markdown';
14
+ export interface ExportRequest {
15
+ dataset: ExportDataset;
16
+ format: ExportFormat;
17
+ /** Restrict to one channel id. */
18
+ channel?: string;
19
+ /** Restrict to one video id; comments only. */
20
+ video?: string;
21
+ limit: number;
22
+ outputDir?: string;
23
+ }
24
+ export interface ExportResult {
25
+ filePath: string;
26
+ /** Present for CSV: where the receipts went, since a CSV cannot hold them. */
27
+ coveragePath?: string;
28
+ dataset: ExportDataset;
29
+ format: ExportFormat;
30
+ rows: number;
31
+ total: number;
32
+ truncated: boolean;
33
+ coverage: Coverage[];
34
+ /** Receipts that should exist for these rows and do not, as `scope:id`. */
35
+ withoutReceipt: string[];
36
+ /** Receipts left out of the list, past the limit a result carries. */
37
+ omittedReceipts: number;
38
+ bytes: number;
39
+ }
40
+ export declare function exportData(database: DatabaseSync, request: ExportRequest): Promise<ExportResult>;
41
+ //# sourceMappingURL=export-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-data.d.ts","sourceRoot":"","sources":["../../src/utils/export-data.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAUtD;;;;;;GAMG;AAEH,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAC1C,eAAO,MAAM,eAAe,SAAU,CAAC;AAOvC,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,CAAC;AAClD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,2EAA2E;IAC3E,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,sEAAsE;IACtE,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAsCD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CA4DvB"}
@@ -0,0 +1,114 @@
1
+ import { mkdir, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { renderCoverage } from './coverage-text.js';
4
+ import { YouTubeError } from './errors.js';
5
+ import { toCsv, toMarkdownTable } from './export-format.js';
6
+ import { loadComments, loadVideos } from './export-load.js';
7
+ import { COMMENT_COLUMNS, VIDEO_COLUMNS } from './export-rows.js';
8
+ import { writeFileAtomic, writeJsonAtomic } from './json-file.js';
9
+ import { dataDir } from './paths.js';
10
+ import { assertChannelId, assertVideoId, resolveOutputDir } from './validate.js';
11
+ /**
12
+ * Taking harvested data out of the store, as a file.
13
+ *
14
+ * What is harvested is only as whole as its receipt says, and a file is read without
15
+ * the tool that made it. So an export carries its receipts with it: inside the file
16
+ * for JSON and Markdown, and beside it for CSV, which has nowhere to put them.
17
+ */
18
+ export const DEFAULT_EXPORT_ROWS = 10_000;
19
+ export const MAX_EXPORT_ROWS = 100_000;
20
+ /** Resolved per call, not at import, so the home directory is the one in force when it runs. */
21
+ function exportsDir() {
22
+ return dataDir('exports');
23
+ }
24
+ function csv(loaded) {
25
+ return loaded.dataset === 'videos'
26
+ ? toCsv(VIDEO_COLUMNS, loaded.rows)
27
+ : toCsv(COMMENT_COLUMNS, loaded.rows);
28
+ }
29
+ function markdownTable(loaded) {
30
+ return loaded.dataset === 'videos'
31
+ ? toMarkdownTable(VIDEO_COLUMNS, loaded.rows)
32
+ : toMarkdownTable(COMMENT_COLUMNS, loaded.rows);
33
+ }
34
+ function markdown(loaded, truncated) {
35
+ const { receipts } = loaded;
36
+ return [
37
+ `# ${loaded.dataset === 'videos' ? 'Videos' : 'Comments'}`,
38
+ '',
39
+ `${String(loaded.rows.length)} of ${String(loaded.total)} rows${truncated ? ' (truncated by the limit)' : ''}.`,
40
+ '',
41
+ '## Completeness',
42
+ '',
43
+ ...(receipts.coverage.length === 0
44
+ ? ['No receipt covers these rows, so how much of the source they are is unknown.']
45
+ : receipts.coverage.flatMap((receipt) => [renderCoverage(receipt), ''])),
46
+ ...(receipts.withoutReceipt.length === 0
47
+ ? []
48
+ : [`No receipt for: ${receipts.withoutReceipt.join(', ')}`, '']),
49
+ ...(receipts.omittedReceipts === 0
50
+ ? []
51
+ : [`${String(receipts.omittedReceipts)} further receipts are not listed.`, '']),
52
+ markdownTable(loaded),
53
+ '',
54
+ ].join('\n');
55
+ }
56
+ export async function exportData(database, request) {
57
+ if (request.channel !== undefined)
58
+ assertChannelId(request.channel);
59
+ if (request.video !== undefined) {
60
+ if (request.dataset !== 'comments') {
61
+ throw new YouTubeError('INVALID_INPUT', 'video only applies when exporting comments.', {
62
+ nextStep: 'Pass channel to restrict a video export, or export dataset comments.',
63
+ });
64
+ }
65
+ assertVideoId(request.video);
66
+ }
67
+ const targetDir = resolveOutputDir(request.outputDir, exportsDir());
68
+ const loaded = request.dataset === 'videos' ? loadVideos(database, request) : loadComments(database, request);
69
+ const truncated = loaded.rows.length < loaded.total;
70
+ const base = `${request.dataset}-${request.video ?? request.channel ?? 'all'}`;
71
+ const extension = request.format === 'markdown' ? 'md' : request.format;
72
+ const filePath = join(targetDir, `${base}.${extension}`);
73
+ const meta = {
74
+ exportedAt: new Date().toISOString(),
75
+ dataset: request.dataset,
76
+ filters: {
77
+ ...(request.channel === undefined ? {} : { channel: request.channel }),
78
+ ...(request.video === undefined ? {} : { video: request.video }),
79
+ },
80
+ total: loaded.total,
81
+ exported: loaded.rows.length,
82
+ truncated,
83
+ coverage: loaded.receipts.coverage,
84
+ withoutReceipt: loaded.receipts.withoutReceipt,
85
+ omittedReceipts: loaded.receipts.omittedReceipts,
86
+ };
87
+ await mkdir(targetDir, { recursive: true });
88
+ let coveragePath;
89
+ if (request.format === 'json') {
90
+ await writeJsonAtomic(filePath, { ...meta, rows: loaded.rows });
91
+ }
92
+ else if (request.format === 'markdown') {
93
+ await writeFileAtomic(filePath, markdown(loaded, truncated));
94
+ }
95
+ else {
96
+ await writeFileAtomic(filePath, csv(loaded));
97
+ coveragePath = join(targetDir, `${base}.coverage.json`);
98
+ await writeJsonAtomic(coveragePath, meta);
99
+ }
100
+ return {
101
+ filePath,
102
+ ...(coveragePath === undefined ? {} : { coveragePath }),
103
+ dataset: request.dataset,
104
+ format: request.format,
105
+ rows: loaded.rows.length,
106
+ total: loaded.total,
107
+ truncated,
108
+ coverage: loaded.receipts.coverage,
109
+ withoutReceipt: loaded.receipts.withoutReceipt,
110
+ omittedReceipts: loaded.receipts.omittedReceipts,
111
+ bytes: (await stat(filePath)).size,
112
+ };
113
+ }
114
+ //# sourceMappingURL=export-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-data.js","sourceRoot":"","sources":["../../src/utils/export-data.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Writing rows out as CSV and Markdown tables.
3
+ *
4
+ * Exported comments are text written by strangers, and a spreadsheet will run a cell
5
+ * that begins like a formula. Every text field is therefore defused on the way out,
6
+ * as OWASP's CSV-injection guidance prescribes, rather than trusted because it came
7
+ * from a platform.
8
+ */
9
+ export type Cell = string | number | boolean | null | undefined;
10
+ export interface Column<T> {
11
+ header: string;
12
+ value: (row: T) => Cell;
13
+ /**
14
+ * Written as it is, never defused. For identifiers, which are validated elsewhere and
15
+ * must come back exact: a YouTube id may begin with `-`, and a leading apostrophe
16
+ * would make it a different id.
17
+ */
18
+ verbatim?: boolean;
19
+ }
20
+ /** RFC 4180: a header row, CRLF line endings, every record ended with one. */
21
+ export declare function toCsv<T>(columns: Column<T>[], rows: T[]): string;
22
+ export declare function toMarkdownTable<T>(columns: Column<T>[], rows: T[]): string;
23
+ //# sourceMappingURL=export-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-format.d.ts","sourceRoot":"","sources":["../../src/utils/export-format.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAEhE,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAmBD,8EAA8E;AAC9E,wBAAgB,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CAMhE;AAwBD,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CAQ1E"}