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
@@ -0,0 +1,121 @@
1
+ import { accessSync, constants, statSync } from 'node:fs';
2
+ import { envEnum, envString } from './env.js';
3
+ import { assertInsideHome } from './validate.js';
4
+ /**
5
+ * The session options yt-dlp is started with, read from the environment.
6
+ *
7
+ * Cookies are how yt-dlp reads age-restricted, members-only and sign-in-gated
8
+ * videos, and how it gets past the "confirm you're not a bot" check YouTube
9
+ * sometimes applies to an address. They are also the user's own account, so
10
+ * everything here is careful about two things: the values are validated at
11
+ * boot and never at request time, and neither the cookie path nor its contents
12
+ * ever appear in a log line, a tool result or an error message.
13
+ */
14
+ export const COOKIE_BROWSERS = [
15
+ 'brave',
16
+ 'chrome',
17
+ 'chromium',
18
+ 'edge',
19
+ 'firefox',
20
+ 'opera',
21
+ 'safari',
22
+ 'vivaldi',
23
+ 'whale',
24
+ ];
25
+ const PROXY_SCHEMES = new Set(['http:', 'https:', 'socks4:', 'socks4a:', 'socks5:', 'socks5h:']);
26
+ function readCookiesFile(env) {
27
+ const raw = envString('YOUTUBE_MCP_COOKIES_FILE', env);
28
+ if (raw === undefined)
29
+ return undefined;
30
+ // The message names the variable and never the path: the path is the one
31
+ // thing about a cookies file that should not travel.
32
+ const rejected = (reason) => new Error(`YOUTUBE_MCP_COOKIES_FILE ${reason}. Point it at a readable cookies file in your home directory.`);
33
+ let path;
34
+ try {
35
+ path = assertInsideHome(raw, 'cookies file');
36
+ }
37
+ catch {
38
+ throw rejected('must be inside your home directory');
39
+ }
40
+ try {
41
+ if (!statSync(path).isFile())
42
+ throw rejected('is not a regular file');
43
+ accessSync(path, constants.R_OK);
44
+ }
45
+ catch (error) {
46
+ if (error instanceof Error && error.message.startsWith('YOUTUBE_MCP_COOKIES_FILE'))
47
+ throw error;
48
+ throw rejected('could not be read');
49
+ }
50
+ return path;
51
+ }
52
+ function readProxy(env) {
53
+ const raw = envString('YOUTUBE_MCP_PROXY', env);
54
+ if (raw === undefined)
55
+ return undefined;
56
+ let url;
57
+ try {
58
+ url = new URL(raw);
59
+ }
60
+ catch {
61
+ throw new Error('YOUTUBE_MCP_PROXY must be a URL such as socks5h://127.0.0.1:1080.');
62
+ }
63
+ if (!PROXY_SCHEMES.has(url.protocol)) {
64
+ throw new Error(`YOUTUBE_MCP_PROXY must use one of ${[...PROXY_SCHEMES].map((s) => s.slice(0, -1)).join(', ')}.`);
65
+ }
66
+ return url.toString();
67
+ }
68
+ function readSleep(env) {
69
+ const raw = envString('YOUTUBE_MCP_SLEEP_REQUESTS_S', env);
70
+ if (raw === undefined)
71
+ return undefined;
72
+ const seconds = Number(raw);
73
+ if (!Number.isFinite(seconds) || seconds < 0) {
74
+ throw new Error('YOUTUBE_MCP_SLEEP_REQUESTS_S must be a non-negative number of seconds.');
75
+ }
76
+ return seconds;
77
+ }
78
+ /**
79
+ * Validate the session settings and turn them into yt-dlp flags.
80
+ *
81
+ * Throws a plain `Error` naming the variable: this runs at boot, where a bad
82
+ * setting should stop the server before a transport starts, not surface as a
83
+ * puzzling failure on the first tool call.
84
+ */
85
+ export function readYtDlpEnv(env = process.env) {
86
+ const cookiesFile = readCookiesFile(env);
87
+ const browser = envEnum('YOUTUBE_MCP_COOKIES_FROM_BROWSER', COOKIE_BROWSERS, env);
88
+ const proxy = readProxy(env);
89
+ const sleep = readSleep(env);
90
+ if (cookiesFile !== undefined && browser !== undefined) {
91
+ throw new Error('Set either YOUTUBE_MCP_COOKIES_FILE or YOUTUBE_MCP_COOKIES_FROM_BROWSER, not both.');
92
+ }
93
+ const args = [];
94
+ if (cookiesFile !== undefined)
95
+ args.push('--cookies', cookiesFile);
96
+ if (browser !== undefined)
97
+ args.push('--cookies-from-browser', browser);
98
+ if (proxy !== undefined)
99
+ args.push('--proxy', proxy);
100
+ if (sleep !== undefined)
101
+ args.push('--sleep-requests', String(sleep));
102
+ return {
103
+ args,
104
+ cookies: cookiesFile !== undefined ? 'file' : browser !== undefined ? 'browser' : 'none',
105
+ ...(browser === undefined ? {} : { browser }),
106
+ proxy: proxy !== undefined,
107
+ ...(sleep === undefined ? {} : { sleepRequestsSeconds: sleep }),
108
+ };
109
+ }
110
+ /** One line for the boot log: what is configured, never what it is. */
111
+ export function describeYtDlpEnv(settings) {
112
+ const cookies = settings.cookies === 'browser'
113
+ ? `browser (${settings.browser ?? 'unknown'})`
114
+ : settings.cookies;
115
+ const parts = [`yt-dlp cookies: ${cookies}`, `proxy: ${settings.proxy ? 'configured' : 'none'}`];
116
+ if (settings.sleepRequestsSeconds !== undefined) {
117
+ parts.push(`sleep between requests: ${settings.sleepRequestsSeconds}s`);
118
+ }
119
+ return parts.join(' · ');
120
+ }
121
+ //# sourceMappingURL=ytdlp-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ytdlp-env.js","sourceRoot":"","sources":["../../src/utils/ytdlp-env.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Takes a concurrency slot, waiting for one when the limiter is full.
3
+ *
4
+ * The wait honours the request's abort signal. It used to be a bare promise
5
+ * with no way out, so a client that gave up stayed queued forever — and since a
6
+ * media transfer runs without a wall-clock timeout, three stalled downloads
7
+ * held every slot and every later call queued behind them, uncancellable. The
8
+ * server was not slow, it was wedged, and no tool could run again.
9
+ */
10
+ export declare function acquireSlot(signal: AbortSignal | undefined): Promise<void>;
11
+ /**
12
+ * Hands the slot to the next waiter rather than freeing it and letting them
13
+ * race for it: decrementing first left `active` below the limit for a tick, so
14
+ * a fresh caller could take the slot the queue was already promised and the
15
+ * limiter would run over its own ceiling.
16
+ */
17
+ export declare function releaseSlot(): void;
18
+ /** Test seam: lets the suite assert the limiter without spawning processes. */
19
+ export declare function concurrencyState(): {
20
+ active: number;
21
+ queued: number;
22
+ /** The level in force now, which throttling can lower. */
23
+ limit: number;
24
+ /** The configured maximum the level never exceeds. */
25
+ ceiling: number;
26
+ };
27
+ //# sourceMappingURL=ytdlp-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ytdlp-limiter.d.ts","sourceRoot":"","sources":["../../src/utils/ytdlp-limiter.ts"],"names":[],"mappings":"AAiCA;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhF;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAOlC;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,IAAI;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;CACjB,CAOA"}
@@ -0,0 +1,85 @@
1
+ import { envInt } from './env.js';
2
+ import { YouTubeError } from './errors.js';
3
+ import { effectiveConcurrency } from './ytdlp-pacer.js';
4
+ /**
5
+ * The concurrency limiter every yt-dlp spawn passes through.
6
+ *
7
+ * A cap so batch operations do not look like a scraper to YouTube, with a queue
8
+ * that honours cancellation so an abandoned request never holds a place in it.
9
+ */
10
+ /**
11
+ * The ceiling this server will never exceed.
12
+ *
13
+ * A limit of 0 (or NaN) would queue every call forever, so the floor is 1.
14
+ * Under throttling the pacer lowers the level below this; the setting is the
15
+ * maximum, not the level.
16
+ */
17
+ const MAX_CONCURRENT = envInt('YOUTUBE_MCP_MAX_CONCURRENCY', 3, { min: 1, max: 32 });
18
+ /** The level in force right now: the configured ceiling, or less if throttled. */
19
+ function currentLimit() {
20
+ return Math.max(1, Math.min(MAX_CONCURRENT, effectiveConcurrency()));
21
+ }
22
+ let active = 0;
23
+ const waiting = [];
24
+ /**
25
+ * Takes a concurrency slot, waiting for one when the limiter is full.
26
+ *
27
+ * The wait honours the request's abort signal. It used to be a bare promise
28
+ * with no way out, so a client that gave up stayed queued forever — and since a
29
+ * media transfer runs without a wall-clock timeout, three stalled downloads
30
+ * held every slot and every later call queued behind them, uncancellable. The
31
+ * server was not slow, it was wedged, and no tool could run again.
32
+ */
33
+ export async function acquireSlot(signal) {
34
+ // No already-aborted check here: `runYtDlp` makes it before every attempt and
35
+ // nothing awaits in between, so a second one could never fire — an
36
+ // unreachable guard that reads like a reachable one.
37
+ if (active < currentLimit()) {
38
+ active++;
39
+ return;
40
+ }
41
+ await new Promise((resolve, reject) => {
42
+ const waiter = {
43
+ grant: () => {
44
+ signal?.removeEventListener('abort', onAbort);
45
+ resolve();
46
+ },
47
+ cancel: (error) => {
48
+ signal?.removeEventListener('abort', onAbort);
49
+ reject(error);
50
+ },
51
+ };
52
+ function onAbort() {
53
+ const index = waiting.indexOf(waiter);
54
+ if (index !== -1)
55
+ waiting.splice(index, 1);
56
+ waiter.cancel(new YouTubeError('CANCELLED', 'The request was cancelled.'));
57
+ }
58
+ signal?.addEventListener('abort', onAbort, { once: true });
59
+ waiting.push(waiter);
60
+ });
61
+ }
62
+ /**
63
+ * Hands the slot to the next waiter rather than freeing it and letting them
64
+ * race for it: decrementing first left `active` below the limit for a tick, so
65
+ * a fresh caller could take the slot the queue was already promised and the
66
+ * limiter would run over its own ceiling.
67
+ */
68
+ export function releaseSlot() {
69
+ const next = waiting.shift();
70
+ if (next) {
71
+ next.grant();
72
+ return;
73
+ }
74
+ active--;
75
+ }
76
+ /** Test seam: lets the suite assert the limiter without spawning processes. */
77
+ export function concurrencyState() {
78
+ return {
79
+ active,
80
+ queued: waiting.length,
81
+ limit: currentLimit(),
82
+ ceiling: MAX_CONCURRENT,
83
+ };
84
+ }
85
+ //# sourceMappingURL=ytdlp-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ytdlp-limiter.js","sourceRoot":"","sources":["../../src/utils/ytdlp-limiter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD;;;;;GAKG;AAEH;;;;;;GAMG;AACH,MAAM,cAAc,GAAG,MAAM,CAAC,6BAA6B,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;AAErF,kFAAkF;AAClF,SAAS,YAAY;IACnB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,oBAAoB,EAAE,CAAC,CAAC,CAAC;AACvE,CAAC;AAOD,IAAI,MAAM,GAAG,CAAC,CAAC;AACf,MAAM,OAAO,GAAa,EAAE,CAAC;AAE7B;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAA+B;IAC/D,8EAA8E;IAC9E,mEAAmE;IACnE,qDAAqD;IACrD,IAAI,MAAM,GAAG,YAAY,EAAE,EAAE,CAAC;QAC5B,MAAM,EAAE,CAAC;QACT,OAAO;IACT,CAAC;IAED,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,MAAM,GAAW;YACrB,KAAK,EAAE,GAAG,EAAE;gBACV,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC9C,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;gBAChB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC9C,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;SACF,CAAC;QAEF,SAAS,OAAO;YACd,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACtC,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,WAAW,EAAE,4BAA4B,CAAC,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAC7B,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,OAAO;IACT,CAAC;IACD,MAAM,EAAE,CAAC;AACX,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB;IAQ9B,OAAO;QACL,MAAM;QACN,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,KAAK,EAAE,YAAY,EAAE;QACrB,OAAO,EAAE,cAAc;KACxB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,64 @@
1
+ import type { YouTubeErrorCode } from './errors.js';
2
+ /**
3
+ * How this server reacts to being throttled.
4
+ *
5
+ * The old behaviour was three attempts with at most ~1.5 seconds of jitter and
6
+ * no memory between calls, which is fine for a blip and useless for an
7
+ * hour-long harvest: nothing slowed down, nothing waited, and a hard block
8
+ * produced an hour of identical failures.
9
+ *
10
+ * This adds three things that only make sense across calls — a shared
11
+ * cooldown, an adaptive concurrency ceiling, and a circuit breaker — so the
12
+ * state is deliberately process-global, the same scope as the limiter it
13
+ * cooperates with.
14
+ */
15
+ export declare const RATE_LIMIT_COOLDOWN_BASE_MS = 30000;
16
+ export declare const RATE_LIMIT_COOLDOWN_MAX_MS: number;
17
+ export declare const BOT_CHECK_COOLDOWN_MS: number;
18
+ export declare const MAX_BACKOFF_MS = 120000;
19
+ export declare const BASE_BACKOFF_MS = 750;
20
+ export declare const AIMD_MIN_CONCURRENCY = 1;
21
+ export declare const AIMD_INCREASE_AFTER_SUCCESSES = 20;
22
+ export declare const AIMD_DECREASE_FACTOR = 0.5;
23
+ /** Consecutive throttles before a global cooldown starts. */
24
+ export declare const COOLDOWN_AFTER_STRIKES = 2;
25
+ export declare const CIRCUIT_STRIKES = 5;
26
+ export declare const CIRCUIT_OPEN_MS: number;
27
+ interface RetryPolicy {
28
+ /** Total spawns, including the first. */
29
+ attempts: number;
30
+ backoff: 'none' | 'exponential';
31
+ cooldown: 'none' | 'rate-limit' | 'bot-check';
32
+ concurrency: 'none' | 'decrease' | 'floor';
33
+ }
34
+ export declare function policyFor(code: YouTubeErrorCode): RetryPolicy;
35
+ export declare function backoffDelay(attempt: number, style: RetryPolicy['backoff']): number;
36
+ export interface PacerDecision {
37
+ giveUp: boolean;
38
+ delayMs: number;
39
+ }
40
+ /** Records a failure and says whether to try again, and after how long. */
41
+ export declare function onFailure(code: YouTubeErrorCode, attempt: number, now?: number): PacerDecision;
42
+ /** Records a clean spawn, easing the ceiling back up one step at a time. */
43
+ export declare function onSuccess(): void;
44
+ /** How long a caller should wait before spawning, in milliseconds. */
45
+ export declare function waitMs(now?: number): number;
46
+ export declare function circuitOpen(now?: number): boolean;
47
+ /**
48
+ * The ceiling the limiter should honour right now.
49
+ *
50
+ * Infinite until something throttles us, so an untroubled server runs at
51
+ * whatever YOUTUBE_MCP_MAX_CONCURRENCY allows and the limiter stays the only
52
+ * authority on the normal case.
53
+ */
54
+ export declare function effectiveConcurrency(now?: number): number;
55
+ export declare function pacerState(now?: number): {
56
+ cooldownMs: number;
57
+ circuitOpen: boolean;
58
+ strikes: number;
59
+ concurrency: number;
60
+ };
61
+ /** Test seam; the pacer is process-global in normal use. */
62
+ export declare function resetPacer(): void;
63
+ export {};
64
+ //# sourceMappingURL=ytdlp-pacer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ytdlp-pacer.d.ts","sourceRoot":"","sources":["../../src/utils/ytdlp-pacer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAClD,eAAO,MAAM,0BAA0B,QAAc,CAAC;AACtD,eAAO,MAAM,qBAAqB,QAAc,CAAC;AACjD,eAAO,MAAM,cAAc,SAAU,CAAC;AACtC,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC,eAAO,MAAM,oBAAoB,IAAI,CAAC;AACtC,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAChD,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,6DAA6D;AAC7D,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,eAAe,QAAa,CAAC;AAE1C,UAAU,WAAW;IACnB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC;IAChC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,WAAW,CAAC;IAC9C,WAAW,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC;CAC5C;AAkCD,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,WAAW,CAE7D;AA+BD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC,SAAS,CAAC,GAAG,MAAM,CAMnF;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CACvB,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EACf,GAAG,SAAa,GACf,aAAa,CAuCf;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,IAAI,IAAI,CAQhC;AAED,sEAAsE;AACtE,wBAAgB,MAAM,CAAC,GAAG,SAAa,GAAG,MAAM,CAG/C;AAED,wBAAgB,WAAW,CAAC,GAAG,SAAa,GAAG,OAAO,CAErD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,SAAa,GAAG,MAAM,CAG7D;AAED,wBAAgB,UAAU,CAAC,GAAG,SAAa,GAAG;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB,CAOA;AAED,4DAA4D;AAC5D,wBAAgB,UAAU,IAAI,IAAI,CAQjC"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * How this server reacts to being throttled.
3
+ *
4
+ * The old behaviour was three attempts with at most ~1.5 seconds of jitter and
5
+ * no memory between calls, which is fine for a blip and useless for an
6
+ * hour-long harvest: nothing slowed down, nothing waited, and a hard block
7
+ * produced an hour of identical failures.
8
+ *
9
+ * This adds three things that only make sense across calls — a shared
10
+ * cooldown, an adaptive concurrency ceiling, and a circuit breaker — so the
11
+ * state is deliberately process-global, the same scope as the limiter it
12
+ * cooperates with.
13
+ */
14
+ export const RATE_LIMIT_COOLDOWN_BASE_MS = 30_000;
15
+ export const RATE_LIMIT_COOLDOWN_MAX_MS = 15 * 60_000;
16
+ export const BOT_CHECK_COOLDOWN_MS = 10 * 60_000;
17
+ export const MAX_BACKOFF_MS = 120_000;
18
+ export const BASE_BACKOFF_MS = 750;
19
+ export const AIMD_MIN_CONCURRENCY = 1;
20
+ export const AIMD_INCREASE_AFTER_SUCCESSES = 20;
21
+ export const AIMD_DECREASE_FACTOR = 0.5;
22
+ /** Consecutive throttles before a global cooldown starts. */
23
+ export const COOLDOWN_AFTER_STRIKES = 2;
24
+ export const CIRCUIT_STRIKES = 5;
25
+ export const CIRCUIT_OPEN_MS = 5 * 60_000;
26
+ const TERMINAL = {
27
+ attempts: 1,
28
+ backoff: 'none',
29
+ cooldown: 'none',
30
+ concurrency: 'none',
31
+ };
32
+ /**
33
+ * What to do about each kind of failure.
34
+ *
35
+ * BOT_CHECK does not retry, and that is measured rather than assumed: from a
36
+ * datacenter address, eight player clients, a fifteen-minute cooldown, a PO
37
+ * token provider and curl_cffi TLS impersonation all returned the same answer.
38
+ * Retrying spends requests on a known-dead path; stopping and naming the cause
39
+ * is the useful response. The cooldown still applies, because whatever else is
40
+ * in flight should not keep hammering.
41
+ */
42
+ const POLICIES = {
43
+ RATE_LIMITED: {
44
+ attempts: 5,
45
+ backoff: 'exponential',
46
+ cooldown: 'rate-limit',
47
+ concurrency: 'decrease',
48
+ },
49
+ BOT_CHECK: { attempts: 1, backoff: 'none', cooldown: 'bot-check', concurrency: 'floor' },
50
+ TIMEOUT: { attempts: 3, backoff: 'exponential', cooldown: 'none', concurrency: 'decrease' },
51
+ // Deliberately absent: an unclassified yt-dlp failure carries no evidence
52
+ // that a second attempt would go differently, and `retryable` is false on it,
53
+ // so it falls through to TERMINAL. Listing it here with attempts > 1 would
54
+ // state a policy the retry loop correctly refuses to honour.
55
+ };
56
+ export function policyFor(code) {
57
+ return POLICIES[code] ?? TERMINAL;
58
+ }
59
+ let state = {
60
+ cooldownUntil: 0,
61
+ circuitOpenUntil: 0,
62
+ strikes: 0,
63
+ successes: 0,
64
+ concurrency: Number.POSITIVE_INFINITY,
65
+ };
66
+ /** ±25% so parallel callers do not come back in lockstep. */
67
+ function jitter(ms) {
68
+ return Math.round(ms * (0.75 + Math.random() * 0.5));
69
+ }
70
+ function cooldownFor(kind, strikes) {
71
+ if (kind === 'none')
72
+ return 0;
73
+ if (kind === 'bot-check')
74
+ return BOT_CHECK_COOLDOWN_MS;
75
+ const escalated = RATE_LIMIT_COOLDOWN_BASE_MS * 2 ** Math.max(0, strikes - 1);
76
+ return jitter(Math.min(escalated, RATE_LIMIT_COOLDOWN_MAX_MS));
77
+ }
78
+ export function backoffDelay(attempt, style) {
79
+ if (style === 'none')
80
+ return 0;
81
+ // Exponential with full jitter, capped so a long run cannot stall for hours.
82
+ const ceiling = Math.min(BASE_BACKOFF_MS * 2 ** (attempt - 1), MAX_BACKOFF_MS);
83
+ return Math.round(Math.random() * ceiling);
84
+ }
85
+ /** Records a failure and says whether to try again, and after how long. */
86
+ export function onFailure(code, attempt, now = Date.now()) {
87
+ const policy = policyFor(code);
88
+ const throttled = policy.cooldown !== 'none';
89
+ if (throttled) {
90
+ state.strikes += 1;
91
+ state.successes = 0;
92
+ // A first strike gets the exponential backoff and nothing more: one 429 is
93
+ // a blip, and making a single interactive call wait out a 30-second global
94
+ // cooldown would be worse than the problem. The cooldown starts once the
95
+ // client has been refused twice in a row, which is when it has stopped
96
+ // being a blip. A bot check skips the grace: it was measured not to clear
97
+ // on its own, so there is nothing to be gained by trying again sooner.
98
+ const grace = policy.cooldown === 'bot-check' ? 1 : COOLDOWN_AFTER_STRIKES;
99
+ if (state.strikes >= grace) {
100
+ state.cooldownUntil = Math.max(state.cooldownUntil, now + cooldownFor(policy.cooldown, state.strikes));
101
+ }
102
+ if (policy.concurrency === 'floor') {
103
+ state.concurrency = AIMD_MIN_CONCURRENCY;
104
+ }
105
+ else if (policy.concurrency === 'decrease') {
106
+ state.concurrency = Math.max(AIMD_MIN_CONCURRENCY, Math.floor(effectiveConcurrency(now) * AIMD_DECREASE_FACTOR));
107
+ }
108
+ if (state.strikes >= CIRCUIT_STRIKES) {
109
+ state.circuitOpenUntil = now + CIRCUIT_OPEN_MS;
110
+ }
111
+ }
112
+ return attempt >= policy.attempts
113
+ ? { giveUp: true, delayMs: 0 }
114
+ : { giveUp: false, delayMs: backoffDelay(attempt, policy.backoff) };
115
+ }
116
+ /** Records a clean spawn, easing the ceiling back up one step at a time. */
117
+ export function onSuccess() {
118
+ state.strikes = 0;
119
+ state.successes += 1;
120
+ if (state.successes >= AIMD_INCREASE_AFTER_SUCCESSES) {
121
+ state.successes = 0;
122
+ if (Number.isFinite(state.concurrency))
123
+ state.concurrency += 1;
124
+ }
125
+ }
126
+ /** How long a caller should wait before spawning, in milliseconds. */
127
+ export function waitMs(now = Date.now()) {
128
+ if (state.circuitOpenUntil > now)
129
+ return state.circuitOpenUntil - now;
130
+ return Math.max(0, state.cooldownUntil - now);
131
+ }
132
+ export function circuitOpen(now = Date.now()) {
133
+ return state.circuitOpenUntil > now;
134
+ }
135
+ /**
136
+ * The ceiling the limiter should honour right now.
137
+ *
138
+ * Infinite until something throttles us, so an untroubled server runs at
139
+ * whatever YOUTUBE_MCP_MAX_CONCURRENCY allows and the limiter stays the only
140
+ * authority on the normal case.
141
+ */
142
+ export function effectiveConcurrency(now = Date.now()) {
143
+ if (circuitOpen(now))
144
+ return AIMD_MIN_CONCURRENCY;
145
+ return state.concurrency;
146
+ }
147
+ export function pacerState(now = Date.now()) {
148
+ return {
149
+ cooldownMs: waitMs(now),
150
+ circuitOpen: circuitOpen(now),
151
+ strikes: state.strikes,
152
+ concurrency: effectiveConcurrency(now),
153
+ };
154
+ }
155
+ /** Test seam; the pacer is process-global in normal use. */
156
+ export function resetPacer() {
157
+ state = {
158
+ cooldownUntil: 0,
159
+ circuitOpenUntil: 0,
160
+ strikes: 0,
161
+ successes: 0,
162
+ concurrency: Number.POSITIVE_INFINITY,
163
+ };
164
+ }
165
+ //# sourceMappingURL=ytdlp-pacer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ytdlp-pacer.js","sourceRoot":"","sources":["../../src/utils/ytdlp-pacer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { z } from 'zod';
2
+ /**
3
+ * Parse yt-dlp JSON output against a schema.
4
+ *
5
+ * Every JSON.parse in this codebase used to be unguarded, so a truncated or
6
+ * non-JSON response surfaced as a raw SyntaxError; and the guard that replaced
7
+ * it only checked for an object, so a field of the wrong type still reached
8
+ * the code that read it.
9
+ */
10
+ export declare function parseYtDlpJson<T>(stdout: string, schema: z.ZodType<T, z.ZodTypeDef, unknown>, what: string): T;
11
+ /** Parse newline-delimited JSON, skipping rows that do not parse or do not match. */
12
+ export declare function parseYtDlpJsonLines<T>(stdout: string, schema: z.ZodType<T, z.ZodTypeDef, unknown>): T[];
13
+ //# sourceMappingURL=ytdlp-parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ytdlp-parse.d.ts","sourceRoot":"","sources":["../../src/utils/ytdlp-parse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAa7B;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,EAC3C,IAAI,EAAE,MAAM,GACX,CAAC,CAWH;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,GAC1C,CAAC,EAAE,CAcL"}
@@ -0,0 +1,48 @@
1
+ import { YouTubeError } from './errors.js';
2
+ /** Reading yt-dlp's JSON output without trusting its shape. */
3
+ function outOfDate(what, detail, cause) {
4
+ return new YouTubeError('MALFORMED_RESPONSE', `yt-dlp returned ${detail} for ${what}.`, {
5
+ nextStep: 'This usually means yt-dlp is out of date. Run `yt-dlp -U` and try again.',
6
+ retryable: cause !== undefined,
7
+ cause,
8
+ });
9
+ }
10
+ /**
11
+ * Parse yt-dlp JSON output against a schema.
12
+ *
13
+ * Every JSON.parse in this codebase used to be unguarded, so a truncated or
14
+ * non-JSON response surfaced as a raw SyntaxError; and the guard that replaced
15
+ * it only checked for an object, so a field of the wrong type still reached
16
+ * the code that read it.
17
+ */
18
+ export function parseYtDlpJson(stdout, schema, what) {
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(stdout);
22
+ }
23
+ catch (error) {
24
+ throw outOfDate(what, 'unreadable output', error);
25
+ }
26
+ const result = schema.safeParse(parsed);
27
+ if (!result.success)
28
+ throw outOfDate(what, 'an unexpected shape');
29
+ return result.data;
30
+ }
31
+ /** Parse newline-delimited JSON, skipping rows that do not parse or do not match. */
32
+ export function parseYtDlpJsonLines(stdout, schema) {
33
+ const results = [];
34
+ for (const line of stdout.trim().split('\n')) {
35
+ if (!line.trim())
36
+ continue;
37
+ try {
38
+ const result = schema.safeParse(JSON.parse(line));
39
+ if (result.success)
40
+ results.push(result.data);
41
+ }
42
+ catch {
43
+ // A single malformed row should not lose the whole result set.
44
+ }
45
+ }
46
+ return results;
47
+ }
48
+ //# sourceMappingURL=ytdlp-parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ytdlp-parse.js","sourceRoot":"","sources":["../../src/utils/ytdlp-parse.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,+DAA+D;AAE/D,SAAS,SAAS,CAAC,IAAY,EAAE,MAAc,EAAE,KAAe;IAC9D,OAAO,IAAI,YAAY,CAAC,oBAAoB,EAAE,mBAAmB,MAAM,QAAQ,IAAI,GAAG,EAAE;QACtF,QAAQ,EAAE,0EAA0E;QACpF,SAAS,EAAE,KAAK,KAAK,SAAS;QAC9B,KAAK;KACN,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAc,EACd,MAA2C,EAC3C,IAAY;IAEZ,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,SAAS,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,MAAM,SAAS,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAClE,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,mBAAmB,CACjC,MAAc,EACd,MAA2C;IAE3C,MAAM,OAAO,GAAQ,EAAE,CAAC;IAExB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,SAAS;QAC3B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAClD,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Running yt-dlp and reading its output a line at a time.
3
+ *
4
+ * `runYtDlp` buffers everything into a string, which is right for a metadata
5
+ * read and wrong for a long walk: a 40,000-entry channel listing killed at
6
+ * minute nine yields nothing at all, and the 64MB buffer is a hard ceiling.
7
+ *
8
+ * Here each line is handed over as it arrives, so a caller can checkpoint as
9
+ * it goes and keep what it already had. Paired with `--lazy-playlist`, which
10
+ * makes yt-dlp emit entries while it walks rather than after.
11
+ */
12
+ export interface StreamOptions {
13
+ label?: string;
14
+ timeoutMs?: number;
15
+ target: string;
16
+ /** Called per line, in order. A throw here aborts the walk. */
17
+ onLine: (line: string) => void;
18
+ }
19
+ export interface StreamResult {
20
+ lines: number;
21
+ /** False when the walk was cut short, so the caller knows not to claim completeness. */
22
+ completed: boolean;
23
+ }
24
+ /**
25
+ * Streams stdout line by line.
26
+ *
27
+ * A failure part-way through is not thrown away: the caller keeps every line
28
+ * delivered so far and is told the walk did not complete, which is the whole
29
+ * point of streaming rather than buffering.
30
+ */
31
+ export declare function runYtDlpLines(args: string[], options: StreamOptions): Promise<StreamResult>;
32
+ //# sourceMappingURL=ytdlp-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ytdlp-stream.d.ts","sourceRoot":"","sources":["../../src/utils/ytdlp-stream.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CA+CjG"}