youtube-knowledge-mcp 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) hide show
  1. package/README.md +153 -38
  2. package/dist/brain-schemas.d.ts +10 -10
  3. package/dist/brain-schemas.d.ts.map +1 -1
  4. package/dist/brain-schemas.js +0 -4
  5. package/dist/brain-schemas.js.map +1 -1
  6. package/dist/harvest-schemas.d.ts +75 -0
  7. package/dist/harvest-schemas.d.ts.map +1 -0
  8. package/dist/harvest-schemas.js +106 -0
  9. package/dist/harvest-schemas.js.map +1 -0
  10. package/dist/http/app.d.ts +16 -0
  11. package/dist/http/app.d.ts.map +1 -0
  12. package/dist/http/app.js +176 -0
  13. package/dist/http/app.js.map +1 -0
  14. package/dist/http/auth.d.ts +38 -0
  15. package/dist/http/auth.d.ts.map +1 -0
  16. package/dist/http/auth.js +79 -0
  17. package/dist/http/auth.js.map +1 -0
  18. package/dist/http/config.d.ts +41 -0
  19. package/dist/http/config.d.ts.map +1 -0
  20. package/dist/http/config.js +65 -0
  21. package/dist/http/config.js.map +1 -0
  22. package/dist/http/rate-limiter.d.ts +24 -0
  23. package/dist/http/rate-limiter.d.ts.map +1 -0
  24. package/dist/http/rate-limiter.js +55 -0
  25. package/dist/http/rate-limiter.js.map +1 -0
  26. package/dist/http/sessions.d.ts +34 -0
  27. package/dist/http/sessions.d.ts.map +1 -0
  28. package/dist/http/sessions.js +62 -0
  29. package/dist/http/sessions.js.map +1 -0
  30. package/dist/http.d.ts +6 -48
  31. package/dist/http.d.ts.map +1 -1
  32. package/dist/http.js +35 -252
  33. package/dist/http.js.map +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +11 -517
  36. package/dist/index.js.map +1 -1
  37. package/dist/prompts-brain.d.ts +4 -0
  38. package/dist/prompts-brain.d.ts.map +1 -0
  39. package/dist/prompts-brain.js +48 -0
  40. package/dist/prompts-brain.js.map +1 -0
  41. package/dist/prompts-research.d.ts +4 -0
  42. package/dist/prompts-research.d.ts.map +1 -0
  43. package/dist/prompts-research.js +74 -0
  44. package/dist/prompts-research.js.map +1 -0
  45. package/dist/prompts-shared.d.ts +13 -0
  46. package/dist/prompts-shared.d.ts.map +1 -0
  47. package/dist/prompts-shared.js +7 -0
  48. package/dist/prompts-shared.js.map +1 -0
  49. package/dist/prompts.d.ts.map +1 -1
  50. package/dist/prompts.js +5 -100
  51. package/dist/prompts.js.map +1 -1
  52. package/dist/registry/brain-tools.d.ts +4 -0
  53. package/dist/registry/brain-tools.d.ts.map +1 -0
  54. package/dist/registry/brain-tools.js +104 -0
  55. package/dist/registry/brain-tools.js.map +1 -0
  56. package/dist/registry/channel-tools.d.ts +4 -0
  57. package/dist/registry/channel-tools.d.ts.map +1 -0
  58. package/dist/registry/channel-tools.js +53 -0
  59. package/dist/registry/channel-tools.js.map +1 -0
  60. package/dist/registry/harvest-tools.d.ts +10 -0
  61. package/dist/registry/harvest-tools.d.ts.map +1 -0
  62. package/dist/registry/harvest-tools.js +109 -0
  63. package/dist/registry/harvest-tools.js.map +1 -0
  64. package/dist/registry/health-tools.d.ts +4 -0
  65. package/dist/registry/health-tools.d.ts.map +1 -0
  66. package/dist/registry/health-tools.js +39 -0
  67. package/dist/registry/health-tools.js.map +1 -0
  68. package/dist/registry/index.d.ts +8 -0
  69. package/dist/registry/index.d.ts.map +1 -0
  70. package/dist/registry/index.js +29 -0
  71. package/dist/registry/index.js.map +1 -0
  72. package/dist/registry/library-tools.d.ts +4 -0
  73. package/dist/registry/library-tools.d.ts.map +1 -0
  74. package/dist/registry/library-tools.js +116 -0
  75. package/dist/registry/library-tools.js.map +1 -0
  76. package/dist/registry/media-tools.d.ts +4 -0
  77. package/dist/registry/media-tools.d.ts.map +1 -0
  78. package/dist/registry/media-tools.js +100 -0
  79. package/dist/registry/media-tools.js.map +1 -0
  80. package/dist/registry/thumbnail-tools.d.ts +7 -0
  81. package/dist/registry/thumbnail-tools.d.ts.map +1 -0
  82. package/dist/registry/thumbnail-tools.js +72 -0
  83. package/dist/registry/thumbnail-tools.js.map +1 -0
  84. package/dist/registry/transcript-tools.d.ts +4 -0
  85. package/dist/registry/transcript-tools.d.ts.map +1 -0
  86. package/dist/registry/transcript-tools.js +68 -0
  87. package/dist/registry/transcript-tools.js.map +1 -0
  88. package/dist/registry/types.d.ts +41 -0
  89. package/dist/registry/types.d.ts.map +1 -0
  90. package/dist/registry/types.js +17 -0
  91. package/dist/registry/types.js.map +1 -0
  92. package/dist/registry/video-tools.d.ts +4 -0
  93. package/dist/registry/video-tools.d.ts.map +1 -0
  94. package/dist/registry/video-tools.js +101 -0
  95. package/dist/registry/video-tools.js.map +1 -0
  96. package/dist/resources.d.ts.map +1 -1
  97. package/dist/resources.js +13 -10
  98. package/dist/resources.js.map +1 -1
  99. package/dist/schemas.d.ts +250 -2
  100. package/dist/schemas.d.ts.map +1 -1
  101. package/dist/schemas.js +61 -0
  102. package/dist/schemas.js.map +1 -1
  103. package/dist/thumbnail-resource.d.ts +8 -0
  104. package/dist/thumbnail-resource.d.ts.map +1 -0
  105. package/dist/thumbnail-resource.js +38 -0
  106. package/dist/thumbnail-resource.js.map +1 -0
  107. package/dist/thumbnail-schemas.d.ts +512 -0
  108. package/dist/thumbnail-schemas.d.ts.map +1 -0
  109. package/dist/thumbnail-schemas.js +95 -0
  110. package/dist/thumbnail-schemas.js.map +1 -0
  111. package/dist/tools/ask-brain.js +1 -1
  112. package/dist/tools/ask-brain.js.map +1 -1
  113. package/dist/tools/batch.js +1 -1
  114. package/dist/tools/batch.js.map +1 -1
  115. package/dist/tools/brain-info.js +1 -1
  116. package/dist/tools/brain-info.js.map +1 -1
  117. package/dist/tools/build-brain.js +1 -1
  118. package/dist/tools/build-brain.js.map +1 -1
  119. package/dist/tools/check-health.d.ts +61 -0
  120. package/dist/tools/check-health.d.ts.map +1 -1
  121. package/dist/tools/check-health.js +94 -6
  122. package/dist/tools/check-health.js.map +1 -1
  123. package/dist/tools/clips.d.ts +9 -41
  124. package/dist/tools/clips.d.ts.map +1 -1
  125. package/dist/tools/clips.js +52 -128
  126. package/dist/tools/clips.js.map +1 -1
  127. package/dist/tools/fetch-channel-thumbnails.d.ts +179 -0
  128. package/dist/tools/fetch-channel-thumbnails.d.ts.map +1 -0
  129. package/dist/tools/fetch-channel-thumbnails.js +123 -0
  130. package/dist/tools/fetch-channel-thumbnails.js.map +1 -0
  131. package/dist/tools/fetch-videos.d.ts +18 -4
  132. package/dist/tools/fetch-videos.d.ts.map +1 -1
  133. package/dist/tools/fetch-videos.js +64 -20
  134. package/dist/tools/fetch-videos.js.map +1 -1
  135. package/dist/tools/frames-subtitles.d.ts +44 -0
  136. package/dist/tools/frames-subtitles.d.ts.map +1 -0
  137. package/dist/tools/frames-subtitles.js +104 -0
  138. package/dist/tools/frames-subtitles.js.map +1 -0
  139. package/dist/tools/get-channel-info.d.ts +2 -0
  140. package/dist/tools/get-channel-info.d.ts.map +1 -1
  141. package/dist/tools/get-channel-info.js +8 -1
  142. package/dist/tools/get-channel-info.js.map +1 -1
  143. package/dist/tools/get-comments.d.ts +238 -6
  144. package/dist/tools/get-comments.d.ts.map +1 -1
  145. package/dist/tools/get-comments.js +85 -21
  146. package/dist/tools/get-comments.js.map +1 -1
  147. package/dist/tools/get-coverage.d.ts +122 -0
  148. package/dist/tools/get-coverage.d.ts.map +1 -0
  149. package/dist/tools/get-coverage.js +105 -0
  150. package/dist/tools/get-coverage.js.map +1 -0
  151. package/dist/tools/get-playlist-info.d.ts.map +1 -1
  152. package/dist/tools/get-playlist-info.js +1 -0
  153. package/dist/tools/get-playlist-info.js.map +1 -1
  154. package/dist/tools/get-thumbnail.d.ts +38 -0
  155. package/dist/tools/get-thumbnail.d.ts.map +1 -0
  156. package/dist/tools/get-thumbnail.js +152 -0
  157. package/dist/tools/get-thumbnail.js.map +1 -0
  158. package/dist/tools/get-transcript.d.ts.map +1 -1
  159. package/dist/tools/get-transcript.js +2 -14
  160. package/dist/tools/get-transcript.js.map +1 -1
  161. package/dist/tools/harvest-channel.d.ts +92 -0
  162. package/dist/tools/harvest-channel.d.ts.map +1 -0
  163. package/dist/tools/harvest-channel.js +62 -0
  164. package/dist/tools/harvest-channel.js.map +1 -0
  165. package/dist/tools/harvest-comments.d.ts +81 -0
  166. package/dist/tools/harvest-comments.d.ts.map +1 -0
  167. package/dist/tools/harvest-comments.js +115 -0
  168. package/dist/tools/harvest-comments.js.map +1 -0
  169. package/dist/tools/library.d.ts.map +1 -1
  170. package/dist/tools/library.js +14 -4
  171. package/dist/tools/library.js.map +1 -1
  172. package/dist/tools/list-channel-thumbnails.d.ts +235 -0
  173. package/dist/tools/list-channel-thumbnails.d.ts.map +1 -0
  174. package/dist/tools/list-channel-thumbnails.js +79 -0
  175. package/dist/tools/list-channel-thumbnails.js.map +1 -0
  176. package/dist/tools/list-library.js +1 -1
  177. package/dist/tools/list-library.js.map +1 -1
  178. package/dist/tools/prune-harvest.d.ts +59 -0
  179. package/dist/tools/prune-harvest.d.ts.map +1 -0
  180. package/dist/tools/prune-harvest.js +108 -0
  181. package/dist/tools/prune-harvest.js.map +1 -0
  182. package/dist/tools/query-comments.d.ts +125 -0
  183. package/dist/tools/query-comments.d.ts.map +1 -0
  184. package/dist/tools/query-comments.js +106 -0
  185. package/dist/tools/query-comments.js.map +1 -0
  186. package/dist/tools/query-videos.d.ts +118 -0
  187. package/dist/tools/query-videos.d.ts.map +1 -0
  188. package/dist/tools/query-videos.js +97 -0
  189. package/dist/tools/query-videos.js.map +1 -0
  190. package/dist/tools/repair-store.d.ts +34 -0
  191. package/dist/tools/repair-store.d.ts.map +1 -0
  192. package/dist/tools/repair-store.js +58 -0
  193. package/dist/tools/repair-store.js.map +1 -0
  194. package/dist/tools/save-to-library.d.ts.map +1 -1
  195. package/dist/tools/save-to-library.js +5 -1
  196. package/dist/tools/save-to-library.js.map +1 -1
  197. package/dist/tools/search-channels.d.ts +6 -0
  198. package/dist/tools/search-channels.d.ts.map +1 -1
  199. package/dist/tools/search-channels.js +5 -1
  200. package/dist/tools/search-channels.js.map +1 -1
  201. package/dist/tools/search-transcript.d.ts.map +1 -1
  202. package/dist/tools/search-transcript.js +6 -13
  203. package/dist/tools/search-transcript.js.map +1 -1
  204. package/dist/tools/search-videos.d.ts +3 -0
  205. package/dist/tools/search-videos.d.ts.map +1 -1
  206. package/dist/tools/search-videos.js +1 -1
  207. package/dist/tools/search-videos.js.map +1 -1
  208. package/dist/utils/batches.d.ts +15 -0
  209. package/dist/utils/batches.d.ts.map +1 -0
  210. package/dist/utils/batches.js +31 -0
  211. package/dist/utils/batches.js.map +1 -0
  212. package/dist/utils/brain-build.d.ts.map +1 -1
  213. package/dist/utils/brain-build.js +1 -12
  214. package/dist/utils/brain-build.js.map +1 -1
  215. package/dist/utils/brain-index.d.ts.map +1 -1
  216. package/dist/utils/brain-index.js +3 -2
  217. package/dist/utils/brain-index.js.map +1 -1
  218. package/dist/utils/brain-lock.d.ts +2 -18
  219. package/dist/utils/brain-lock.d.ts.map +1 -1
  220. package/dist/utils/brain-lock.js +4 -80
  221. package/dist/utils/brain-lock.js.map +1 -1
  222. package/dist/utils/brain-lookup.d.ts +8 -0
  223. package/dist/utils/brain-lookup.d.ts.map +1 -1
  224. package/dist/utils/brain-lookup.js +5 -31
  225. package/dist/utils/brain-lookup.js.map +1 -1
  226. package/dist/utils/brain-storage.d.ts.map +1 -1
  227. package/dist/utils/brain-storage.js +5 -5
  228. package/dist/utils/brain-storage.js.map +1 -1
  229. package/dist/utils/caption-probe.d.ts +7 -0
  230. package/dist/utils/caption-probe.d.ts.map +1 -0
  231. package/dist/utils/caption-probe.js +36 -0
  232. package/dist/utils/caption-probe.js.map +1 -0
  233. package/dist/utils/channel-images.d.ts +16 -0
  234. package/dist/utils/channel-images.d.ts.map +1 -0
  235. package/dist/utils/channel-images.js +35 -0
  236. package/dist/utils/channel-images.js.map +1 -0
  237. package/dist/utils/channel-lookup.d.ts +5 -0
  238. package/dist/utils/channel-lookup.d.ts.map +1 -0
  239. package/dist/utils/channel-lookup.js +37 -0
  240. package/dist/utils/channel-lookup.js.map +1 -0
  241. package/dist/utils/chapters.d.ts +11 -0
  242. package/dist/utils/chapters.d.ts.map +1 -0
  243. package/dist/utils/chapters.js +24 -0
  244. package/dist/utils/chapters.js.map +1 -0
  245. package/dist/utils/clips.d.ts.map +1 -1
  246. package/dist/utils/clips.js +18 -26
  247. package/dist/utils/clips.js.map +1 -1
  248. package/dist/utils/comment-store.d.ts +71 -0
  249. package/dist/utils/comment-store.d.ts.map +1 -0
  250. package/dist/utils/comment-store.js +129 -0
  251. package/dist/utils/comment-store.js.map +1 -0
  252. package/dist/utils/comment-threads.d.ts +51 -0
  253. package/dist/utils/comment-threads.d.ts.map +1 -0
  254. package/dist/utils/comment-threads.js +76 -0
  255. package/dist/utils/comment-threads.js.map +1 -0
  256. package/dist/utils/coverage-text.d.ts +3 -0
  257. package/dist/utils/coverage-text.d.ts.map +1 -0
  258. package/dist/utils/coverage-text.js +62 -0
  259. package/dist/utils/coverage-text.js.map +1 -0
  260. package/dist/utils/coverage.d.ts +57 -0
  261. package/dist/utils/coverage.d.ts.map +1 -0
  262. package/dist/utils/coverage.js +142 -0
  263. package/dist/utils/coverage.js.map +1 -0
  264. package/dist/utils/env.d.ts +26 -0
  265. package/dist/utils/env.d.ts.map +1 -0
  266. package/dist/utils/env.js +72 -0
  267. package/dist/utils/env.js.map +1 -0
  268. package/dist/utils/errors.d.ts +1 -1
  269. package/dist/utils/errors.d.ts.map +1 -1
  270. package/dist/utils/errors.js +28 -2
  271. package/dist/utils/errors.js.map +1 -1
  272. package/dist/utils/file-lock.d.ts +34 -0
  273. package/dist/utils/file-lock.d.ts.map +1 -0
  274. package/dist/utils/file-lock.js +91 -0
  275. package/dist/utils/file-lock.js.map +1 -0
  276. package/dist/utils/flat-listing.d.ts +89 -0
  277. package/dist/utils/flat-listing.d.ts.map +1 -0
  278. package/dist/utils/flat-listing.js +68 -0
  279. package/dist/utils/flat-listing.js.map +1 -0
  280. package/dist/utils/format.d.ts +18 -4
  281. package/dist/utils/format.d.ts.map +1 -1
  282. package/dist/utils/format.js +2 -6
  283. package/dist/utils/format.js.map +1 -1
  284. package/dist/utils/guard.d.ts +21 -0
  285. package/dist/utils/guard.d.ts.map +1 -0
  286. package/dist/utils/guard.js +105 -0
  287. package/dist/utils/guard.js.map +1 -0
  288. package/dist/utils/harvest-catalog.d.ts +44 -0
  289. package/dist/utils/harvest-catalog.d.ts.map +1 -0
  290. package/dist/utils/harvest-catalog.js +164 -0
  291. package/dist/utils/harvest-catalog.js.map +1 -0
  292. package/dist/utils/harvest-lock.d.ts +3 -0
  293. package/dist/utils/harvest-lock.d.ts.map +1 -0
  294. package/dist/utils/harvest-lock.js +26 -0
  295. package/dist/utils/harvest-lock.js.map +1 -0
  296. package/dist/utils/harvest-receipts.d.ts +14 -0
  297. package/dist/utils/harvest-receipts.d.ts.map +1 -0
  298. package/dist/utils/harvest-receipts.js +110 -0
  299. package/dist/utils/harvest-receipts.js.map +1 -0
  300. package/dist/utils/image-dimensions.d.ts +19 -0
  301. package/dist/utils/image-dimensions.d.ts.map +1 -0
  302. package/dist/utils/image-dimensions.js +93 -0
  303. package/dist/utils/image-dimensions.js.map +1 -0
  304. package/dist/utils/image-fetch.d.ts +31 -0
  305. package/dist/utils/image-fetch.d.ts.map +1 -0
  306. package/dist/utils/image-fetch.js +141 -0
  307. package/dist/utils/image-fetch.js.map +1 -0
  308. package/dist/utils/json-file.d.ts +14 -5
  309. package/dist/utils/json-file.d.ts.map +1 -1
  310. package/dist/utils/json-file.js +24 -9
  311. package/dist/utils/json-file.js.map +1 -1
  312. package/dist/utils/listing-cursor.d.ts +31 -0
  313. package/dist/utils/listing-cursor.d.ts.map +1 -0
  314. package/dist/utils/listing-cursor.js +39 -0
  315. package/dist/utils/listing-cursor.js.map +1 -0
  316. package/dist/utils/paths.d.ts +10 -0
  317. package/dist/utils/paths.d.ts.map +1 -1
  318. package/dist/utils/paths.js +14 -0
  319. package/dist/utils/paths.js.map +1 -1
  320. package/dist/utils/pattern.d.ts +30 -0
  321. package/dist/utils/pattern.d.ts.map +1 -0
  322. package/dist/utils/pattern.js +130 -0
  323. package/dist/utils/pattern.js.map +1 -0
  324. package/dist/utils/pot-preflight.d.ts +53 -0
  325. package/dist/utils/pot-preflight.d.ts.map +1 -0
  326. package/dist/utils/pot-preflight.js +146 -0
  327. package/dist/utils/pot-preflight.js.map +1 -0
  328. package/dist/utils/search-index.d.ts.map +1 -1
  329. package/dist/utils/search-index.js +9 -4
  330. package/dist/utils/search-index.js.map +1 -1
  331. package/dist/utils/sleep.d.ts +9 -0
  332. package/dist/utils/sleep.d.ts.map +1 -0
  333. package/dist/utils/sleep.js +25 -0
  334. package/dist/utils/sleep.js.map +1 -0
  335. package/dist/utils/storage.d.ts.map +1 -1
  336. package/dist/utils/storage.js +14 -8
  337. package/dist/utils/storage.js.map +1 -1
  338. package/dist/utils/store-health.d.ts +37 -0
  339. package/dist/utils/store-health.d.ts.map +1 -0
  340. package/dist/utils/store-health.js +92 -0
  341. package/dist/utils/store-health.js.map +1 -0
  342. package/dist/utils/store-migrations.d.ts +33 -0
  343. package/dist/utils/store-migrations.d.ts.map +1 -0
  344. package/dist/utils/store-migrations.js +41 -0
  345. package/dist/utils/store-migrations.js.map +1 -0
  346. package/dist/utils/store-paths.d.ts +33 -0
  347. package/dist/utils/store-paths.d.ts.map +1 -0
  348. package/dist/utils/store-paths.js +48 -0
  349. package/dist/utils/store-paths.js.map +1 -0
  350. package/dist/utils/store-rows.d.ts +22 -0
  351. package/dist/utils/store-rows.d.ts.map +1 -0
  352. package/dist/utils/store-rows.js +62 -0
  353. package/dist/utils/store-rows.js.map +1 -0
  354. package/dist/utils/store-schema.d.ts +16 -0
  355. package/dist/utils/store-schema.d.ts.map +1 -0
  356. package/dist/utils/store-schema.js +168 -0
  357. package/dist/utils/store-schema.js.map +1 -0
  358. package/dist/utils/store.d.ts +34 -0
  359. package/dist/utils/store.d.ts.map +1 -0
  360. package/dist/utils/store.js +157 -0
  361. package/dist/utils/store.js.map +1 -0
  362. package/dist/utils/subtitles.d.ts +7 -0
  363. package/dist/utils/subtitles.d.ts.map +1 -0
  364. package/dist/utils/subtitles.js +30 -0
  365. package/dist/utils/subtitles.js.map +1 -0
  366. package/dist/utils/thumbnail-entry.d.ts +19 -0
  367. package/dist/utils/thumbnail-entry.d.ts.map +1 -0
  368. package/dist/utils/thumbnail-entry.js +119 -0
  369. package/dist/utils/thumbnail-entry.js.map +1 -0
  370. package/dist/utils/thumbnail-fetch.d.ts +37 -0
  371. package/dist/utils/thumbnail-fetch.d.ts.map +1 -0
  372. package/dist/utils/thumbnail-fetch.js +89 -0
  373. package/dist/utils/thumbnail-fetch.js.map +1 -0
  374. package/dist/utils/thumbnail-ladder.d.ts +44 -0
  375. package/dist/utils/thumbnail-ladder.d.ts.map +1 -0
  376. package/dist/utils/thumbnail-ladder.js +68 -0
  377. package/dist/utils/thumbnail-ladder.js.map +1 -0
  378. package/dist/utils/thumbnail-listing.d.ts +16 -0
  379. package/dist/utils/thumbnail-listing.d.ts.map +1 -0
  380. package/dist/utils/thumbnail-listing.js +26 -0
  381. package/dist/utils/thumbnail-listing.js.map +1 -0
  382. package/dist/utils/thumbnail-paths.d.ts +20 -0
  383. package/dist/utils/thumbnail-paths.d.ts.map +1 -0
  384. package/dist/utils/thumbnail-paths.js +35 -0
  385. package/dist/utils/thumbnail-paths.js.map +1 -0
  386. package/dist/utils/thumbnail-store.d.ts +19 -0
  387. package/dist/utils/thumbnail-store.d.ts.map +1 -0
  388. package/dist/utils/thumbnail-store.js +125 -0
  389. package/dist/utils/thumbnail-store.js.map +1 -0
  390. package/dist/utils/transcript-cache.d.ts +20 -0
  391. package/dist/utils/transcript-cache.d.ts.map +1 -0
  392. package/dist/utils/transcript-cache.js +127 -0
  393. package/dist/utils/transcript-cache.js.map +1 -0
  394. package/dist/utils/transcript-search.d.ts +20 -0
  395. package/dist/utils/transcript-search.d.ts.map +1 -0
  396. package/dist/utils/transcript-search.js +35 -0
  397. package/dist/utils/transcript-search.js.map +1 -0
  398. package/dist/utils/transcript.d.ts +10 -31
  399. package/dist/utils/transcript.d.ts.map +1 -1
  400. package/dist/utils/transcript.js +2 -64
  401. package/dist/utils/transcript.js.map +1 -1
  402. package/dist/utils/validate-youtube.d.ts +35 -0
  403. package/dist/utils/validate-youtube.d.ts.map +1 -0
  404. package/dist/utils/validate-youtube.js +87 -0
  405. package/dist/utils/validate-youtube.js.map +1 -0
  406. package/dist/utils/validate.d.ts +11 -4
  407. package/dist/utils/validate.d.ts.map +1 -1
  408. package/dist/utils/validate.js +43 -36
  409. package/dist/utils/validate.js.map +1 -1
  410. package/dist/utils/version.d.ts +2 -0
  411. package/dist/utils/version.d.ts.map +1 -0
  412. package/dist/utils/version.js +19 -0
  413. package/dist/utils/version.js.map +1 -0
  414. package/dist/utils/video-stats-cache.d.ts +16 -0
  415. package/dist/utils/video-stats-cache.d.ts.map +1 -0
  416. package/dist/utils/video-stats-cache.js +81 -0
  417. package/dist/utils/video-stats-cache.js.map +1 -0
  418. package/dist/utils/video-stats.d.ts +186 -0
  419. package/dist/utils/video-stats.d.ts.map +1 -0
  420. package/dist/utils/video-stats.js +168 -0
  421. package/dist/utils/video-stats.js.map +1 -0
  422. package/dist/utils/video-store.d.ts +69 -0
  423. package/dist/utils/video-store.d.ts.map +1 -0
  424. package/dist/utils/video-store.js +77 -0
  425. package/dist/utils/video-store.js.map +1 -0
  426. package/dist/utils/youtube-channel.d.ts +51 -0
  427. package/dist/utils/youtube-channel.d.ts.map +1 -0
  428. package/dist/utils/youtube-channel.js +91 -0
  429. package/dist/utils/youtube-channel.js.map +1 -0
  430. package/dist/utils/youtube-download.d.ts +27 -0
  431. package/dist/utils/youtube-download.d.ts.map +1 -0
  432. package/dist/utils/youtube-download.js +118 -0
  433. package/dist/utils/youtube-download.js.map +1 -0
  434. package/dist/utils/youtube-schemas.d.ts +492 -0
  435. package/dist/utils/youtube-schemas.d.ts.map +1 -0
  436. package/dist/utils/youtube-schemas.js +148 -0
  437. package/dist/utils/youtube-schemas.js.map +1 -0
  438. package/dist/utils/youtube-search.d.ts +14 -0
  439. package/dist/utils/youtube-search.d.ts.map +1 -0
  440. package/dist/utils/youtube-search.js +39 -0
  441. package/dist/utils/youtube-search.js.map +1 -0
  442. package/dist/utils/youtube-url.d.ts +25 -0
  443. package/dist/utils/youtube-url.d.ts.map +1 -0
  444. package/dist/utils/youtube-url.js +75 -0
  445. package/dist/utils/youtube-url.js.map +1 -0
  446. package/dist/utils/youtube-video.d.ts +68 -0
  447. package/dist/utils/youtube-video.d.ts.map +1 -0
  448. package/dist/utils/youtube-video.js +110 -0
  449. package/dist/utils/youtube-video.js.map +1 -0
  450. package/dist/utils/youtube.d.ts +10 -122
  451. package/dist/utils/youtube.d.ts.map +1 -1
  452. package/dist/utils/youtube.js +11 -486
  453. package/dist/utils/youtube.js.map +1 -1
  454. package/dist/utils/ytdlp-env.d.ts +33 -0
  455. package/dist/utils/ytdlp-env.d.ts.map +1 -0
  456. package/dist/utils/ytdlp-env.js +121 -0
  457. package/dist/utils/ytdlp-env.js.map +1 -0
  458. package/dist/utils/ytdlp-limiter.d.ts +27 -0
  459. package/dist/utils/ytdlp-limiter.d.ts.map +1 -0
  460. package/dist/utils/ytdlp-limiter.js +85 -0
  461. package/dist/utils/ytdlp-limiter.js.map +1 -0
  462. package/dist/utils/ytdlp-pacer.d.ts +64 -0
  463. package/dist/utils/ytdlp-pacer.d.ts.map +1 -0
  464. package/dist/utils/ytdlp-pacer.js +165 -0
  465. package/dist/utils/ytdlp-pacer.js.map +1 -0
  466. package/dist/utils/ytdlp-parse.d.ts +13 -0
  467. package/dist/utils/ytdlp-parse.d.ts.map +1 -0
  468. package/dist/utils/ytdlp-parse.js +48 -0
  469. package/dist/utils/ytdlp-parse.js.map +1 -0
  470. package/dist/utils/ytdlp-stream.d.ts +32 -0
  471. package/dist/utils/ytdlp-stream.d.ts.map +1 -0
  472. package/dist/utils/ytdlp-stream.js +61 -0
  473. package/dist/utils/ytdlp-stream.js.map +1 -0
  474. package/dist/utils/ytdlp.d.ts +23 -18
  475. package/dist/utils/ytdlp.d.ts.map +1 -1
  476. package/dist/utils/ytdlp.js +56 -134
  477. package/dist/utils/ytdlp.js.map +1 -1
  478. package/package.json +3 -2
@@ -1,35 +1,59 @@
1
+ import { existsSync, realpathSync } from 'node:fs';
1
2
  import { homedir } from 'node:os';
2
- import { isAbsolute, normalize, resolve, sep } from 'node:path';
3
+ import { basename, dirname, isAbsolute, join, normalize, resolve, sep } from 'node:path';
3
4
  import { YouTubeError } from './errors.js';
4
5
  /**
5
6
  * Input validation for the values that reach the filesystem or yt-dlp's
6
- * language negotiation. Zod already constrains shapes at the tool boundary;
7
+ * language negotiation. The YouTube-specific checks — ids and URLs — live in
8
+ * validate-youtube.ts and are re-exported here. Zod already constrains shapes at the tool boundary;
7
9
  * these are the checks Zod cannot express.
8
10
  */
9
11
  /**
10
- * Resolve a caller-supplied output directory.
12
+ * Prove that a caller-supplied path lies inside the home directory.
11
13
  *
12
14
  * A tool argument is model-controlled, so it must not be able to write outside
13
- * the user's home directory or reach the filesystem root through `..`.
15
+ * the user's home directory or reach the filesystem root through `..`. The
16
+ * check is made on the real path: a symlink inside home that points outside it
17
+ * passed a purely lexical comparison, and yt-dlp and ffmpeg then wrote through
18
+ * it. The nearest existing ancestor is resolved and the missing tail re-joined,
19
+ * so a directory that does not exist yet is judged by where it would be created.
14
20
  */
15
- export function resolveOutputDir(outputDir, fallback) {
16
- if (outputDir === undefined || outputDir.trim() === '')
17
- return fallback;
18
- const expanded = outputDir.startsWith('~')
19
- ? resolve(homedir(), outputDir.slice(1).replace(/^[/\\]/, ''))
20
- : resolve(normalize(outputDir));
21
- if (expanded.includes('\0')) {
22
- throw new YouTubeError('INVALID_INPUT', 'The output directory path is not valid.', {
23
- nextStep: 'Provide a plain directory path, for example ~/Videos.',
21
+ export function assertInsideHome(input, what) {
22
+ if (input.includes('\0')) {
23
+ throw new YouTubeError('INVALID_INPUT', `The ${what} path is not valid.`, {
24
+ nextStep: 'Provide a plain path, for example ~/Videos.',
24
25
  });
25
26
  }
26
- const home = resolve(homedir());
27
- if (!isAbsolute(expanded) || !(expanded === home || expanded.startsWith(home + sep))) {
28
- throw new YouTubeError('INVALID_INPUT', 'Downloads must be written inside your home directory.', {
29
- nextStep: `Choose a path under ${home}, or omit outputDir to use the default location.`,
27
+ const expanded = input.startsWith('~')
28
+ ? resolve(homedir(), input.slice(1).replace(/^[/\\]/, ''))
29
+ : resolve(normalize(input));
30
+ const home = realpathSync(homedir());
31
+ const real = realpathOfNearestAncestor(expanded);
32
+ if (!isAbsolute(real) || !(real === home || real.startsWith(home + sep))) {
33
+ throw new YouTubeError('INVALID_INPUT', `The ${what} must be inside your home directory.`, {
34
+ nextStep: `Choose a path under ${home}, or omit it to use the default location.`,
30
35
  });
31
36
  }
32
- return expanded;
37
+ return real;
38
+ }
39
+ /**
40
+ * The real path of `target`, resolving symlinks in whichever prefix of it
41
+ * exists. The walk stops at the filesystem root, which always exists.
42
+ */
43
+ function realpathOfNearestAncestor(target) {
44
+ const missing = [];
45
+ let probe = target;
46
+ while (dirname(probe) !== probe && !existsSync(probe)) {
47
+ missing.unshift(basename(probe));
48
+ probe = dirname(probe);
49
+ }
50
+ return join(realpathSync(probe), ...missing);
51
+ }
52
+ /** Resolve a caller-supplied output directory, or the fallback when none is given. */
53
+ export function resolveOutputDir(outputDir, fallback) {
54
+ if (outputDir === undefined || outputDir.trim() === '')
55
+ return fallback;
56
+ return assertInsideHome(outputDir, 'output directory');
33
57
  }
34
58
  /** BCP-47-ish: `en`, `pt-BR`, `zh-Hans`, plus yt-dlp's `-orig` suffix. */
35
59
  const LANGUAGE_PATTERN = /^[a-zA-Z]{2,8}(?:-[a-zA-Z0-9]{2,8})*$/;
@@ -41,24 +65,6 @@ export function assertLanguageTag(language) {
41
65
  }
42
66
  return language;
43
67
  }
44
- /**
45
- * YouTube channel ids are `UC` followed by 22 base64url characters.
46
- *
47
- * This is stricter than it looks like it needs to be. A channel id arrives from
48
- * yt-dlp's output and is then used as a directory name under
49
- * `~/.youtube-knowledge/brains`, so a value containing a separator or `..`
50
- * would write outside that tree. Matching the real format exactly is cheaper
51
- * than sanitising a value that should never have been unusual.
52
- */
53
- const CHANNEL_ID_PATTERN = /^UC[A-Za-z0-9_-]{22}$/;
54
- export function assertChannelId(channelId) {
55
- if (!CHANNEL_ID_PATTERN.test(channelId)) {
56
- throw new YouTubeError('INVALID_INPUT', `"${channelId}" is not a valid YouTube channel id.`, {
57
- nextStep: 'Channel ids look like UCxxxxxxxxxxxxxxxxxxxxxx. Call get_channel_info or list_brains to get one.',
58
- });
59
- }
60
- return channelId;
61
- }
62
68
  /** Accepts `90`, `1:30`, `01:02:03`, `1:30.5` and returns seconds. */
63
69
  export function parseTimestamp(value, field) {
64
70
  if (typeof value === 'number') {
@@ -79,4 +85,5 @@ export function parseTimestamp(value, field) {
79
85
  // The hours group is optional, so it is genuinely absent for "MM:SS" input.
80
86
  return Number(match[1] ?? 0) * 3600 + Number(match[2] ?? 0) * 60 + Number(match[3] ?? 0);
81
87
  }
88
+ export * from './validate-youtube.js';
82
89
  //# sourceMappingURL=validate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/utils/validate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/utils/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function serverVersion(): string;
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAkBA,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
@@ -0,0 +1,19 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { z } from 'zod';
5
+ /**
6
+ * The version this build reports.
7
+ *
8
+ * Advertised in the MCP initialize response and on the authenticated health
9
+ * endpoint, so a package.json that has lost it should fail loudly at startup
10
+ * rather than telling every client the server is version `undefined`.
11
+ */
12
+ const here = dirname(fileURLToPath(import.meta.url));
13
+ const pkg = z
14
+ .object({ version: z.string() })
15
+ .parse(JSON.parse(readFileSync(join(here, '..', '..', 'package.json'), 'utf-8')));
16
+ export function serverVersion() {
17
+ return pkg.version;
18
+ }
19
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AACH,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAErD,MAAM,GAAG,GAAG,CAAC;KACV,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;KAC/B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAEpF,MAAM,UAAU,aAAa;IAC3B,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { type VideoStats } from './video-stats.js';
2
+ export interface StatsResult {
3
+ stats: VideoStats;
4
+ /** True when served from disk rather than refetched. */
5
+ cached: boolean;
6
+ }
7
+ /**
8
+ * A video's stats, from disk when a fresh copy is there.
9
+ *
10
+ * A cache write that fails is not allowed to fail the read: the caller already
11
+ * has the answer, and a full disk should cost speed, not correctness.
12
+ */
13
+ export declare function readVideoStats(urlOrId: string, options?: {
14
+ refresh?: boolean;
15
+ }, now?: number): Promise<StatsResult>;
16
+ //# sourceMappingURL=video-stats-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-stats-cache.d.ts","sourceRoot":"","sources":["../../src/utils/video-stats-cache.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAC;AAqD1B,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,UAAU,CAAC;IAClB,wDAAwD;IACxD,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,EACnC,GAAG,SAAa,GACf,OAAO,CAAC,WAAW,CAAC,CAyBtB"}
@@ -0,0 +1,81 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { z } from 'zod';
4
+ import { dataDir, ensurePrivateDir } from './paths.js';
5
+ import { readJsonFile, writeJsonAtomic } from './json-file.js';
6
+ import { envInt } from './env.js';
7
+ import { assertVideoId } from './validate.js';
8
+ import { extractVideoId } from './youtube-url.js';
9
+ import { fetchVideoStatsRow, toVideoStats, videoStatsRowSchema, } from './video-stats.js';
10
+ /**
11
+ * Keeping one video's stats on disk.
12
+ *
13
+ * Every per-video read goes through here, so get_video_info, get_chapters, a
14
+ * caption inventory and a catalogue detail pass share a single request rather
15
+ * than making one each. Requests are what gets a client throttled, so this is
16
+ * the largest reduction available.
17
+ *
18
+ * The cached document is the yt-dlp row, not the derived object: the row is
19
+ * what has a schema, and re-deriving on read means a cached video and a fresh
20
+ * one always come back through the same code path. Shaped after
21
+ * transcript-cache.ts — sharded so no directory grows to 100,000 entries,
22
+ * validated on read, and anything unreadable treated as absent.
23
+ */
24
+ /**
25
+ * Resolved per call, not once at import.
26
+ *
27
+ * A module-level dataDir() reads the home directory the instant the module
28
+ * loads, which is before a test can point HOME anywhere — and it means the
29
+ * path is fixed for the process. store-paths.ts uses functions for the same
30
+ * reason.
31
+ */
32
+ function cacheDir() {
33
+ return dataDir('stats');
34
+ }
35
+ /** Counts move constantly, so a day is already generous. */
36
+ const VIDEO_STATS_TTL_MS = envInt('YOUTUBE_MCP_STATS_TTL_MS', 86_400_000, { min: 0 });
37
+ /** Bumped when the stored shape changes, so old files are refetched, not misread. */
38
+ const CACHE_VERSION = 1;
39
+ const cachedStatsSchema = z.object({
40
+ version: z.literal(CACHE_VERSION),
41
+ fetchedAt: z.number(),
42
+ row: videoStatsRowSchema,
43
+ });
44
+ function shardDir(videoId) {
45
+ return join(cacheDir(), assertVideoId(videoId).slice(0, 2));
46
+ }
47
+ function cachePath(videoId) {
48
+ return join(shardDir(videoId), `${assertVideoId(videoId)}.json`);
49
+ }
50
+ function isFresh(fetchedAt, now) {
51
+ return VIDEO_STATS_TTL_MS === 0 || now - fetchedAt < VIDEO_STATS_TTL_MS;
52
+ }
53
+ /**
54
+ * A video's stats, from disk when a fresh copy is there.
55
+ *
56
+ * A cache write that fails is not allowed to fail the read: the caller already
57
+ * has the answer, and a full disk should cost speed, not correctness.
58
+ */
59
+ export async function readVideoStats(urlOrId, options = {}, now = Date.now()) {
60
+ // The id comes from the URL, not from a fetch: checking the cache after
61
+ // fetching would be no cache at all.
62
+ const videoId = extractVideoId(urlOrId);
63
+ if (options.refresh !== true) {
64
+ const path = cachePath(videoId);
65
+ if (existsSync(path)) {
66
+ const parsed = await readJsonFile(path, cachedStatsSchema);
67
+ if (parsed !== undefined && isFresh(parsed.fetchedAt, now)) {
68
+ return { stats: toVideoStats(parsed.row), cached: true };
69
+ }
70
+ }
71
+ }
72
+ const row = await fetchVideoStatsRow(urlOrId);
73
+ await ensurePrivateDir(shardDir(videoId));
74
+ await writeJsonAtomic(cachePath(videoId), {
75
+ version: CACHE_VERSION,
76
+ fetchedAt: now,
77
+ row,
78
+ }).catch(() => undefined);
79
+ return { stats: toVideoStats(row), cached: false };
80
+ }
81
+ //# sourceMappingURL=video-stats-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-stats-cache.js","sourceRoot":"","sources":["../../src/utils/video-stats-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,186 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * One full read of a video, serving every caller that needs part of it.
4
+ *
5
+ * `getVideoInfo` ran a narrow `--print` and `getVideoDetails` ran a full `-j`
6
+ * and discarded all but three fields, so a single video cost two spawns for
7
+ * data one already had. Requests to YouTube are the thing that gets a client
8
+ * throttled, so halving them is the cheapest reliability win available.
9
+ *
10
+ * What is NOT kept matters as much as what is. Measured on a 637KB payload:
11
+ * `automatic_captions` is 460,965 bytes (72%) of URLs that expire, `formats`
12
+ * another 113,601 (18%), and `heatmap` 6,589 — larger than the whole rest of
13
+ * the record. Stripped, a video is 4,785 bytes. Storing the raw object would
14
+ * cost 6.4GB per 10,000 videos instead of 48MB.
15
+ */
16
+ /** A stored snapshot must stay small enough that a catalogue is not mostly URLs. */
17
+ export declare const MAX_DETAIL_JSON_BYTES = 32768;
18
+ /** Keys dropped before storage: expiring URLs and bulk we can re-fetch. */
19
+ export declare const STRIPPED_KEYS: readonly ["formats", "requested_formats", "automatic_captions", "subtitles", "thumbnails", "comments", "heatmap"];
20
+ export declare const videoStatsRowSchema: z.ZodObject<{
21
+ id: z.ZodString;
22
+ title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
23
+ channel: z.ZodOptional<z.ZodNullable<z.ZodString>>;
24
+ channel_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
25
+ channel_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
26
+ channel_follower_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
27
+ upload_date: z.ZodOptional<z.ZodNullable<z.ZodString>>;
28
+ duration: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
29
+ view_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
30
+ like_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
31
+ comment_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
32
+ concurrent_view_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
33
+ categories: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
34
+ tags: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
35
+ age_limit: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
36
+ availability: z.ZodOptional<z.ZodNullable<z.ZodString>>;
37
+ live_status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
38
+ was_live: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
39
+ is_live: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
40
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
41
+ thumbnail: z.ZodOptional<z.ZodNullable<z.ZodString>>;
42
+ chapters: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
43
+ title: z.ZodString;
44
+ start_time: z.ZodNumber;
45
+ end_time: z.ZodNumber;
46
+ }, "strip", z.ZodTypeAny, {
47
+ title: string;
48
+ start_time: number;
49
+ end_time: number;
50
+ }, {
51
+ title: string;
52
+ start_time: number;
53
+ end_time: number;
54
+ }>, "many">>>;
55
+ heatmap: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
56
+ start_time: z.ZodNumber;
57
+ end_time: z.ZodNumber;
58
+ value: z.ZodNumber;
59
+ }, "strip", z.ZodTypeAny, {
60
+ value: number;
61
+ start_time: number;
62
+ end_time: number;
63
+ }, {
64
+ value: number;
65
+ start_time: number;
66
+ end_time: number;
67
+ }>, "many">>>;
68
+ automatic_captions: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
69
+ subtitles: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
70
+ }, "strip", z.ZodTypeAny, {
71
+ id: string;
72
+ title?: string | null | undefined;
73
+ channel?: string | null | undefined;
74
+ description?: string | null | undefined;
75
+ tags?: string[] | null | undefined;
76
+ duration?: number | null | undefined;
77
+ upload_date?: string | null | undefined;
78
+ thumbnail?: string | null | undefined;
79
+ view_count?: number | null | undefined;
80
+ like_count?: number | null | undefined;
81
+ comment_count?: number | null | undefined;
82
+ availability?: string | null | undefined;
83
+ live_status?: string | null | undefined;
84
+ chapters?: {
85
+ title: string;
86
+ start_time: number;
87
+ end_time: number;
88
+ }[] | null | undefined;
89
+ subtitles?: Record<string, unknown> | null | undefined;
90
+ automatic_captions?: Record<string, unknown> | null | undefined;
91
+ channel_id?: string | null | undefined;
92
+ channel_url?: string | null | undefined;
93
+ channel_follower_count?: number | null | undefined;
94
+ heatmap?: {
95
+ value: number;
96
+ start_time: number;
97
+ end_time: number;
98
+ }[] | null | undefined;
99
+ concurrent_view_count?: number | null | undefined;
100
+ categories?: string[] | null | undefined;
101
+ age_limit?: number | null | undefined;
102
+ was_live?: boolean | null | undefined;
103
+ is_live?: boolean | null | undefined;
104
+ }, {
105
+ id: string;
106
+ title?: string | null | undefined;
107
+ channel?: string | null | undefined;
108
+ description?: string | null | undefined;
109
+ tags?: string[] | null | undefined;
110
+ duration?: number | null | undefined;
111
+ upload_date?: string | null | undefined;
112
+ thumbnail?: string | null | undefined;
113
+ view_count?: number | null | undefined;
114
+ like_count?: number | null | undefined;
115
+ comment_count?: number | null | undefined;
116
+ availability?: string | null | undefined;
117
+ live_status?: string | null | undefined;
118
+ chapters?: {
119
+ title: string;
120
+ start_time: number;
121
+ end_time: number;
122
+ }[] | null | undefined;
123
+ subtitles?: Record<string, unknown> | null | undefined;
124
+ automatic_captions?: Record<string, unknown> | null | undefined;
125
+ channel_id?: string | null | undefined;
126
+ channel_url?: string | null | undefined;
127
+ channel_follower_count?: number | null | undefined;
128
+ heatmap?: {
129
+ value: number;
130
+ start_time: number;
131
+ end_time: number;
132
+ }[] | null | undefined;
133
+ concurrent_view_count?: number | null | undefined;
134
+ categories?: string[] | null | undefined;
135
+ age_limit?: number | null | undefined;
136
+ was_live?: boolean | null | undefined;
137
+ is_live?: boolean | null | undefined;
138
+ }>;
139
+ export interface VideoStats {
140
+ id: string;
141
+ title: string;
142
+ url: string;
143
+ channel: string;
144
+ channelId?: string;
145
+ channelFollowerCount?: number;
146
+ uploadDate: string;
147
+ durationSeconds: number;
148
+ durationFormatted: string;
149
+ viewCount?: number;
150
+ likeCount?: number;
151
+ /** YouTube's own count. Trustworthy here because this read has no --write-comments. */
152
+ commentCount?: number;
153
+ categories: string[];
154
+ tags: string[];
155
+ ageLimit: number;
156
+ availability?: string;
157
+ liveStatus?: string;
158
+ wasLive: boolean;
159
+ chapters: {
160
+ title: string;
161
+ startTime: number;
162
+ endTime: number;
163
+ }[];
164
+ /** Language tags only — never the URL arrays, which are 72% of the payload. */
165
+ captions: {
166
+ manual: string[];
167
+ automatic: string[];
168
+ };
169
+ hasHeatmap: boolean;
170
+ heatmap?: {
171
+ startSeconds: number;
172
+ endSeconds: number;
173
+ value: number;
174
+ }[];
175
+ descriptionChars: number;
176
+ description: string;
177
+ thumbnailUrl?: string;
178
+ }
179
+ export declare const CATALOG_DESCRIPTION_PREVIEW_CHARS = 500;
180
+ /** Drops the bulk keys, so what is stored is the record rather than its attachments. */
181
+ export declare function stripForStorage(raw: Record<string, unknown>): Record<string, unknown>;
182
+ export declare function toVideoStats(row: z.infer<typeof videoStatsRowSchema>): VideoStats;
183
+ /** One `-j` read, serving info, details, chapters, captions and the heatmap. */
184
+ export declare function fetchVideoStatsRow(urlOrId: string): Promise<z.infer<typeof videoStatsRowSchema>>;
185
+ export declare function getVideoStats(urlOrId: string): Promise<VideoStats>;
186
+ //# sourceMappingURL=video-stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-stats.d.ts","sourceRoot":"","sources":["../../src/utils/video-stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;;;;;GAaG;AAEH,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C,2EAA2E;AAC3E,eAAO,MAAM,aAAa,mHAQhB,CAAC;AAQX,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4B9B,CAAC;AAEH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAClE,+EAA+E;IAC/E,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACpD,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACxE,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAErD,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAcrF;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,GAAG,UAAU,CA0EjF;AAED,gFAAgF;AAChF,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAS9C;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAExE"}
@@ -0,0 +1,168 @@
1
+ import { z } from 'zod';
2
+ import { TIMEOUTS, parseYtDlpJson, runYtDlp } from './ytdlp.js';
3
+ import { extractVideoId, formatDuration, watchUrl } from './youtube-url.js';
4
+ import { classifyPlayability } from './errors.js';
5
+ /**
6
+ * One full read of a video, serving every caller that needs part of it.
7
+ *
8
+ * `getVideoInfo` ran a narrow `--print` and `getVideoDetails` ran a full `-j`
9
+ * and discarded all but three fields, so a single video cost two spawns for
10
+ * data one already had. Requests to YouTube are the thing that gets a client
11
+ * throttled, so halving them is the cheapest reliability win available.
12
+ *
13
+ * What is NOT kept matters as much as what is. Measured on a 637KB payload:
14
+ * `automatic_captions` is 460,965 bytes (72%) of URLs that expire, `formats`
15
+ * another 113,601 (18%), and `heatmap` 6,589 — larger than the whole rest of
16
+ * the record. Stripped, a video is 4,785 bytes. Storing the raw object would
17
+ * cost 6.4GB per 10,000 videos instead of 48MB.
18
+ */
19
+ /** A stored snapshot must stay small enough that a catalogue is not mostly URLs. */
20
+ export const MAX_DETAIL_JSON_BYTES = 32_768;
21
+ /** Keys dropped before storage: expiring URLs and bulk we can re-fetch. */
22
+ export const STRIPPED_KEYS = [
23
+ 'formats',
24
+ 'requested_formats',
25
+ 'automatic_captions',
26
+ 'subtitles',
27
+ 'thumbnails',
28
+ 'comments',
29
+ 'heatmap',
30
+ ];
31
+ const heatmapEntrySchema = z.object({
32
+ start_time: z.number(),
33
+ end_time: z.number(),
34
+ value: z.number(),
35
+ });
36
+ export const videoStatsRowSchema = z.object({
37
+ id: z.string(),
38
+ title: z.string().nullish(),
39
+ channel: z.string().nullish(),
40
+ channel_id: z.string().nullish(),
41
+ channel_url: z.string().nullish(),
42
+ channel_follower_count: z.number().nullish(),
43
+ upload_date: z.string().nullish(),
44
+ duration: z.number().nullish(),
45
+ view_count: z.number().nullish(),
46
+ like_count: z.number().nullish(),
47
+ comment_count: z.number().nullish(),
48
+ concurrent_view_count: z.number().nullish(),
49
+ categories: z.array(z.string()).nullish(),
50
+ tags: z.array(z.string()).nullish(),
51
+ age_limit: z.number().nullish(),
52
+ availability: z.string().nullish(),
53
+ live_status: z.string().nullish(),
54
+ was_live: z.boolean().nullish(),
55
+ is_live: z.boolean().nullish(),
56
+ description: z.string().nullish(),
57
+ thumbnail: z.string().nullish(),
58
+ chapters: z
59
+ .array(z.object({ title: z.string(), start_time: z.number(), end_time: z.number() }))
60
+ .nullish(),
61
+ heatmap: z.array(heatmapEntrySchema).nullish(),
62
+ automatic_captions: z.record(z.string(), z.unknown()).nullish(),
63
+ subtitles: z.record(z.string(), z.unknown()).nullish(),
64
+ });
65
+ export const CATALOG_DESCRIPTION_PREVIEW_CHARS = 500;
66
+ /** Drops the bulk keys, so what is stored is the record rather than its attachments. */
67
+ export function stripForStorage(raw) {
68
+ const kept = {};
69
+ for (const [key, value] of Object.entries(raw)) {
70
+ if (STRIPPED_KEYS.some((stripped) => stripped === key))
71
+ continue;
72
+ kept[key] = value;
73
+ }
74
+ const description = kept.description;
75
+ if (typeof description === 'string' && description.length > CATALOG_DESCRIPTION_PREVIEW_CHARS) {
76
+ kept.description = description.slice(0, CATALOG_DESCRIPTION_PREVIEW_CHARS);
77
+ kept.description_truncated = true;
78
+ }
79
+ return kept;
80
+ }
81
+ export function toVideoStats(row) {
82
+ // classifyPlayability RETURNS the error rather than throwing it, so ignoring
83
+ // the result would hand back ordinary-looking stats for a private or
84
+ // members-only video.
85
+ const unplayable = classifyPlayability({
86
+ availability: row.availability ?? undefined,
87
+ liveStatus: row.live_status ?? undefined,
88
+ });
89
+ if (unplayable !== undefined)
90
+ throw unplayable;
91
+ const duration = row.duration ?? 0;
92
+ const heatmap = row.heatmap ?? undefined;
93
+ const description = row.description ?? '';
94
+ return {
95
+ id: row.id,
96
+ title: row.title ?? 'Unknown',
97
+ url: watchUrl(row.id),
98
+ channel: row.channel ?? 'Unknown',
99
+ ...(row.channel_id === null || row.channel_id === undefined
100
+ ? {}
101
+ : { channelId: row.channel_id }),
102
+ ...(row.channel_follower_count === null || row.channel_follower_count === undefined
103
+ ? {}
104
+ : { channelFollowerCount: row.channel_follower_count }),
105
+ uploadDate: row.upload_date ?? '',
106
+ durationSeconds: duration,
107
+ durationFormatted: formatDuration(Math.floor(duration)),
108
+ ...(row.view_count === null || row.view_count === undefined
109
+ ? {}
110
+ : { viewCount: row.view_count }),
111
+ ...(row.like_count === null || row.like_count === undefined
112
+ ? {}
113
+ : { likeCount: row.like_count }),
114
+ ...(row.comment_count === null || row.comment_count === undefined
115
+ ? {}
116
+ : { commentCount: row.comment_count }),
117
+ categories: row.categories ?? [],
118
+ tags: row.tags ?? [],
119
+ ageLimit: row.age_limit ?? 0,
120
+ ...(row.availability === null || row.availability === undefined
121
+ ? {}
122
+ : { availability: row.availability }),
123
+ ...(row.live_status === null || row.live_status === undefined
124
+ ? {}
125
+ : { liveStatus: row.live_status }),
126
+ wasLive: row.was_live ?? false,
127
+ chapters: (row.chapters ?? []).map((chapter) => ({
128
+ title: chapter.title,
129
+ startTime: chapter.start_time,
130
+ endTime: chapter.end_time,
131
+ })),
132
+ // Keys only: the values are arrays of expiring URLs, and the inventory is
133
+ // what a caller actually asks about.
134
+ captions: {
135
+ manual: Object.keys(row.subtitles ?? {}),
136
+ automatic: Object.keys(row.automatic_captions ?? {}),
137
+ },
138
+ hasHeatmap: heatmap !== undefined && heatmap.length > 0,
139
+ ...(heatmap === undefined
140
+ ? {}
141
+ : {
142
+ heatmap: heatmap.map((entry) => ({
143
+ startSeconds: entry.start_time,
144
+ endSeconds: entry.end_time,
145
+ value: entry.value,
146
+ })),
147
+ }),
148
+ descriptionChars: description.length,
149
+ description,
150
+ ...(row.thumbnail === null || row.thumbnail === undefined
151
+ ? {}
152
+ : { thumbnailUrl: row.thumbnail }),
153
+ };
154
+ }
155
+ /** One `-j` read, serving info, details, chapters, captions and the heatmap. */
156
+ export async function fetchVideoStatsRow(urlOrId) {
157
+ const videoId = extractVideoId(urlOrId);
158
+ const stdout = await runYtDlp(['-j', '--skip-download'], {
159
+ label: 'get_video_stats',
160
+ timeoutMs: TIMEOUTS.metadata,
161
+ target: watchUrl(videoId),
162
+ });
163
+ return parseYtDlpJson(stdout, videoStatsRowSchema, 'video stats');
164
+ }
165
+ export async function getVideoStats(urlOrId) {
166
+ return toVideoStats(await fetchVideoStatsRow(urlOrId));
167
+ }
168
+ //# sourceMappingURL=video-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-stats.js","sourceRoot":"","sources":["../../src/utils/video-stats.ts"],"names":[],"mappings":"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"}