youtube-knowledge-mcp 2.0.2 → 2.2.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 (390) hide show
  1. package/README.md +166 -33
  2. package/dist/brain-schemas.d.ts +502 -0
  3. package/dist/brain-schemas.d.ts.map +1 -0
  4. package/dist/brain-schemas.js +109 -0
  5. package/dist/brain-schemas.js.map +1 -0
  6. package/dist/http/app.d.ts +16 -0
  7. package/dist/http/app.d.ts.map +1 -0
  8. package/dist/http/app.js +176 -0
  9. package/dist/http/app.js.map +1 -0
  10. package/dist/http/auth.d.ts +38 -0
  11. package/dist/http/auth.d.ts.map +1 -0
  12. package/dist/http/auth.js +79 -0
  13. package/dist/http/auth.js.map +1 -0
  14. package/dist/http/config.d.ts +41 -0
  15. package/dist/http/config.d.ts.map +1 -0
  16. package/dist/http/config.js +65 -0
  17. package/dist/http/config.js.map +1 -0
  18. package/dist/http/rate-limiter.d.ts +24 -0
  19. package/dist/http/rate-limiter.d.ts.map +1 -0
  20. package/dist/http/rate-limiter.js +55 -0
  21. package/dist/http/rate-limiter.js.map +1 -0
  22. package/dist/http/sessions.d.ts +34 -0
  23. package/dist/http/sessions.d.ts.map +1 -0
  24. package/dist/http/sessions.js +62 -0
  25. package/dist/http/sessions.js.map +1 -0
  26. package/dist/http.d.ts +6 -48
  27. package/dist/http.d.ts.map +1 -1
  28. package/dist/http.js +35 -252
  29. package/dist/http.js.map +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +11 -431
  32. package/dist/index.js.map +1 -1
  33. package/dist/prompts-brain.d.ts +4 -0
  34. package/dist/prompts-brain.d.ts.map +1 -0
  35. package/dist/prompts-brain.js +48 -0
  36. package/dist/prompts-brain.js.map +1 -0
  37. package/dist/prompts-research.d.ts +4 -0
  38. package/dist/prompts-research.d.ts.map +1 -0
  39. package/dist/prompts-research.js +74 -0
  40. package/dist/prompts-research.js.map +1 -0
  41. package/dist/prompts-shared.d.ts +13 -0
  42. package/dist/prompts-shared.d.ts.map +1 -0
  43. package/dist/prompts-shared.js +7 -0
  44. package/dist/prompts-shared.js.map +1 -0
  45. package/dist/prompts.d.ts.map +1 -1
  46. package/dist/prompts.js +5 -58
  47. package/dist/prompts.js.map +1 -1
  48. package/dist/registry/brain-tools.d.ts +4 -0
  49. package/dist/registry/brain-tools.d.ts.map +1 -0
  50. package/dist/registry/brain-tools.js +104 -0
  51. package/dist/registry/brain-tools.js.map +1 -0
  52. package/dist/registry/channel-tools.d.ts +4 -0
  53. package/dist/registry/channel-tools.d.ts.map +1 -0
  54. package/dist/registry/channel-tools.js +53 -0
  55. package/dist/registry/channel-tools.js.map +1 -0
  56. package/dist/registry/health-tools.d.ts +4 -0
  57. package/dist/registry/health-tools.d.ts.map +1 -0
  58. package/dist/registry/health-tools.js +21 -0
  59. package/dist/registry/health-tools.js.map +1 -0
  60. package/dist/registry/index.d.ts +8 -0
  61. package/dist/registry/index.d.ts.map +1 -0
  62. package/dist/registry/index.js +27 -0
  63. package/dist/registry/index.js.map +1 -0
  64. package/dist/registry/library-tools.d.ts +4 -0
  65. package/dist/registry/library-tools.d.ts.map +1 -0
  66. package/dist/registry/library-tools.js +116 -0
  67. package/dist/registry/library-tools.js.map +1 -0
  68. package/dist/registry/media-tools.d.ts +4 -0
  69. package/dist/registry/media-tools.d.ts.map +1 -0
  70. package/dist/registry/media-tools.js +100 -0
  71. package/dist/registry/media-tools.js.map +1 -0
  72. package/dist/registry/thumbnail-tools.d.ts +7 -0
  73. package/dist/registry/thumbnail-tools.d.ts.map +1 -0
  74. package/dist/registry/thumbnail-tools.js +72 -0
  75. package/dist/registry/thumbnail-tools.js.map +1 -0
  76. package/dist/registry/transcript-tools.d.ts +4 -0
  77. package/dist/registry/transcript-tools.d.ts.map +1 -0
  78. package/dist/registry/transcript-tools.js +68 -0
  79. package/dist/registry/transcript-tools.js.map +1 -0
  80. package/dist/registry/types.d.ts +41 -0
  81. package/dist/registry/types.d.ts.map +1 -0
  82. package/dist/registry/types.js +17 -0
  83. package/dist/registry/types.js.map +1 -0
  84. package/dist/registry/video-tools.d.ts +4 -0
  85. package/dist/registry/video-tools.d.ts.map +1 -0
  86. package/dist/registry/video-tools.js +101 -0
  87. package/dist/registry/video-tools.js.map +1 -0
  88. package/dist/resources.d.ts.map +1 -1
  89. package/dist/resources.js +75 -6
  90. package/dist/resources.js.map +1 -1
  91. package/dist/schemas.d.ts +74 -54
  92. package/dist/schemas.d.ts.map +1 -1
  93. package/dist/schemas.js +30 -4
  94. package/dist/schemas.js.map +1 -1
  95. package/dist/thumbnail-resource.d.ts +8 -0
  96. package/dist/thumbnail-resource.d.ts.map +1 -0
  97. package/dist/thumbnail-resource.js +38 -0
  98. package/dist/thumbnail-resource.js.map +1 -0
  99. package/dist/thumbnail-schemas.d.ts +512 -0
  100. package/dist/thumbnail-schemas.d.ts.map +1 -0
  101. package/dist/thumbnail-schemas.js +95 -0
  102. package/dist/thumbnail-schemas.js.map +1 -0
  103. package/dist/tools/ask-brain.d.ts +62 -0
  104. package/dist/tools/ask-brain.d.ts.map +1 -0
  105. package/dist/tools/ask-brain.js +70 -0
  106. package/dist/tools/ask-brain.js.map +1 -0
  107. package/dist/tools/batch.d.ts +10 -10
  108. package/dist/tools/batch.js +3 -3
  109. package/dist/tools/batch.js.map +1 -1
  110. package/dist/tools/brain-info.d.ts +180 -0
  111. package/dist/tools/brain-info.d.ts.map +1 -0
  112. package/dist/tools/brain-info.js +108 -0
  113. package/dist/tools/brain-info.js.map +1 -0
  114. package/dist/tools/build-brain.d.ts +120 -0
  115. package/dist/tools/build-brain.d.ts.map +1 -0
  116. package/dist/tools/build-brain.js +123 -0
  117. package/dist/tools/build-brain.js.map +1 -0
  118. package/dist/tools/check-health.d.ts +2 -0
  119. package/dist/tools/check-health.d.ts.map +1 -1
  120. package/dist/tools/check-health.js +9 -0
  121. package/dist/tools/check-health.js.map +1 -1
  122. package/dist/tools/clips.d.ts +13 -45
  123. package/dist/tools/clips.d.ts.map +1 -1
  124. package/dist/tools/clips.js +52 -128
  125. package/dist/tools/clips.js.map +1 -1
  126. package/dist/tools/fetch-channel-thumbnails.d.ts +179 -0
  127. package/dist/tools/fetch-channel-thumbnails.d.ts.map +1 -0
  128. package/dist/tools/fetch-channel-thumbnails.js +123 -0
  129. package/dist/tools/fetch-channel-thumbnails.js.map +1 -0
  130. package/dist/tools/fetch-videos.d.ts +11 -8
  131. package/dist/tools/fetch-videos.d.ts.map +1 -1
  132. package/dist/tools/fetch-videos.js +3 -0
  133. package/dist/tools/fetch-videos.js.map +1 -1
  134. package/dist/tools/frames-subtitles.d.ts +44 -0
  135. package/dist/tools/frames-subtitles.d.ts.map +1 -0
  136. package/dist/tools/frames-subtitles.js +104 -0
  137. package/dist/tools/frames-subtitles.js.map +1 -0
  138. package/dist/tools/get-channel-info.d.ts +2 -0
  139. package/dist/tools/get-channel-info.d.ts.map +1 -1
  140. package/dist/tools/get-channel-info.js +8 -1
  141. package/dist/tools/get-channel-info.js.map +1 -1
  142. package/dist/tools/get-chapters.d.ts +2 -2
  143. package/dist/tools/get-comments.d.ts +2 -2
  144. package/dist/tools/get-playlist-info.d.ts.map +1 -1
  145. package/dist/tools/get-playlist-info.js +1 -0
  146. package/dist/tools/get-playlist-info.js.map +1 -1
  147. package/dist/tools/get-thumbnail.d.ts +38 -0
  148. package/dist/tools/get-thumbnail.d.ts.map +1 -0
  149. package/dist/tools/get-thumbnail.js +152 -0
  150. package/dist/tools/get-thumbnail.js.map +1 -0
  151. package/dist/tools/get-transcript.d.ts +2 -2
  152. package/dist/tools/get-transcript.d.ts.map +1 -1
  153. package/dist/tools/get-transcript.js +2 -14
  154. package/dist/tools/get-transcript.js.map +1 -1
  155. package/dist/tools/library.d.ts +9 -7
  156. package/dist/tools/library.d.ts.map +1 -1
  157. package/dist/tools/library.js +23 -7
  158. package/dist/tools/library.js.map +1 -1
  159. package/dist/tools/list-channel-thumbnails.d.ts +235 -0
  160. package/dist/tools/list-channel-thumbnails.d.ts.map +1 -0
  161. package/dist/tools/list-channel-thumbnails.js +79 -0
  162. package/dist/tools/list-channel-thumbnails.js.map +1 -0
  163. package/dist/tools/list-library.d.ts +4 -4
  164. package/dist/tools/manage-brain.d.ts +39 -0
  165. package/dist/tools/manage-brain.d.ts.map +1 -0
  166. package/dist/tools/manage-brain.js +53 -0
  167. package/dist/tools/manage-brain.js.map +1 -0
  168. package/dist/tools/save-to-library.d.ts.map +1 -1
  169. package/dist/tools/save-to-library.js +5 -1
  170. package/dist/tools/save-to-library.js.map +1 -1
  171. package/dist/tools/search-channels.d.ts +10 -4
  172. package/dist/tools/search-channels.d.ts.map +1 -1
  173. package/dist/tools/search-transcript.d.ts +2 -2
  174. package/dist/tools/search-transcript.d.ts.map +1 -1
  175. package/dist/tools/search-transcript.js +6 -13
  176. package/dist/tools/search-transcript.js.map +1 -1
  177. package/dist/tools/search-videos.d.ts +11 -8
  178. package/dist/tools/search-videos.d.ts.map +1 -1
  179. package/dist/utils/batches.d.ts +15 -0
  180. package/dist/utils/batches.d.ts.map +1 -0
  181. package/dist/utils/batches.js +31 -0
  182. package/dist/utils/batches.js.map +1 -0
  183. package/dist/utils/brain-build.d.ts +40 -0
  184. package/dist/utils/brain-build.d.ts.map +1 -0
  185. package/dist/utils/brain-build.js +200 -0
  186. package/dist/utils/brain-build.js.map +1 -0
  187. package/dist/utils/brain-chunks.d.ts +33 -0
  188. package/dist/utils/brain-chunks.d.ts.map +1 -0
  189. package/dist/utils/brain-chunks.js +87 -0
  190. package/dist/utils/brain-chunks.js.map +1 -0
  191. package/dist/utils/brain-index.d.ts +27 -0
  192. package/dist/utils/brain-index.d.ts.map +1 -0
  193. package/dist/utils/brain-index.js +101 -0
  194. package/dist/utils/brain-index.js.map +1 -0
  195. package/dist/utils/brain-ingest.d.ts +57 -0
  196. package/dist/utils/brain-ingest.d.ts.map +1 -0
  197. package/dist/utils/brain-ingest.js +127 -0
  198. package/dist/utils/brain-ingest.js.map +1 -0
  199. package/dist/utils/brain-lock.d.ts +4 -0
  200. package/dist/utils/brain-lock.d.ts.map +1 -0
  201. package/dist/utils/brain-lock.js +17 -0
  202. package/dist/utils/brain-lock.js.map +1 -0
  203. package/dist/utils/brain-lookup.d.ts +11 -0
  204. package/dist/utils/brain-lookup.d.ts.map +1 -0
  205. package/dist/utils/brain-lookup.js +36 -0
  206. package/dist/utils/brain-lookup.js.map +1 -0
  207. package/dist/utils/brain-paths.d.ts +19 -0
  208. package/dist/utils/brain-paths.d.ts.map +1 -0
  209. package/dist/utils/brain-paths.js +34 -0
  210. package/dist/utils/brain-paths.js.map +1 -0
  211. package/dist/utils/brain-phrases.d.ts +31 -0
  212. package/dist/utils/brain-phrases.d.ts.map +1 -0
  213. package/dist/utils/brain-phrases.js +116 -0
  214. package/dist/utils/brain-phrases.js.map +1 -0
  215. package/dist/utils/brain-stats.d.ts +25 -0
  216. package/dist/utils/brain-stats.d.ts.map +1 -0
  217. package/dist/utils/brain-stats.js +64 -0
  218. package/dist/utils/brain-stats.js.map +1 -0
  219. package/dist/utils/brain-storage.d.ts +28 -0
  220. package/dist/utils/brain-storage.d.ts.map +1 -0
  221. package/dist/utils/brain-storage.js +87 -0
  222. package/dist/utils/brain-storage.js.map +1 -0
  223. package/dist/utils/caption-probe.d.ts +7 -0
  224. package/dist/utils/caption-probe.d.ts.map +1 -0
  225. package/dist/utils/caption-probe.js +36 -0
  226. package/dist/utils/caption-probe.js.map +1 -0
  227. package/dist/utils/channel-images.d.ts +16 -0
  228. package/dist/utils/channel-images.d.ts.map +1 -0
  229. package/dist/utils/channel-images.js +35 -0
  230. package/dist/utils/channel-images.js.map +1 -0
  231. package/dist/utils/channel-lookup.d.ts +5 -0
  232. package/dist/utils/channel-lookup.d.ts.map +1 -0
  233. package/dist/utils/channel-lookup.js +37 -0
  234. package/dist/utils/channel-lookup.js.map +1 -0
  235. package/dist/utils/chapters.d.ts +11 -0
  236. package/dist/utils/chapters.d.ts.map +1 -0
  237. package/dist/utils/chapters.js +24 -0
  238. package/dist/utils/chapters.js.map +1 -0
  239. package/dist/utils/clips.d.ts.map +1 -1
  240. package/dist/utils/clips.js +22 -30
  241. package/dist/utils/clips.js.map +1 -1
  242. package/dist/utils/env.d.ts +26 -0
  243. package/dist/utils/env.d.ts.map +1 -0
  244. package/dist/utils/env.js +72 -0
  245. package/dist/utils/env.js.map +1 -0
  246. package/dist/utils/errors.d.ts +1 -1
  247. package/dist/utils/errors.d.ts.map +1 -1
  248. package/dist/utils/errors.js +20 -2
  249. package/dist/utils/errors.js.map +1 -1
  250. package/dist/utils/file-lock.d.ts +34 -0
  251. package/dist/utils/file-lock.d.ts.map +1 -0
  252. package/dist/utils/file-lock.js +91 -0
  253. package/dist/utils/file-lock.js.map +1 -0
  254. package/dist/utils/flat-listing.d.ts +89 -0
  255. package/dist/utils/flat-listing.d.ts.map +1 -0
  256. package/dist/utils/flat-listing.js +68 -0
  257. package/dist/utils/flat-listing.js.map +1 -0
  258. package/dist/utils/format.d.ts +1 -3
  259. package/dist/utils/format.d.ts.map +1 -1
  260. package/dist/utils/format.js +1 -5
  261. package/dist/utils/format.js.map +1 -1
  262. package/dist/utils/guard.d.ts +21 -0
  263. package/dist/utils/guard.d.ts.map +1 -0
  264. package/dist/utils/guard.js +105 -0
  265. package/dist/utils/guard.js.map +1 -0
  266. package/dist/utils/image-dimensions.d.ts +19 -0
  267. package/dist/utils/image-dimensions.d.ts.map +1 -0
  268. package/dist/utils/image-dimensions.js +93 -0
  269. package/dist/utils/image-dimensions.js.map +1 -0
  270. package/dist/utils/image-fetch.d.ts +31 -0
  271. package/dist/utils/image-fetch.d.ts.map +1 -0
  272. package/dist/utils/image-fetch.js +141 -0
  273. package/dist/utils/image-fetch.js.map +1 -0
  274. package/dist/utils/json-file.d.ts +45 -0
  275. package/dist/utils/json-file.d.ts.map +1 -0
  276. package/dist/utils/json-file.js +60 -0
  277. package/dist/utils/json-file.js.map +1 -0
  278. package/dist/utils/paths.d.ts +19 -0
  279. package/dist/utils/paths.d.ts.map +1 -0
  280. package/dist/utils/paths.js +27 -0
  281. package/dist/utils/paths.js.map +1 -0
  282. package/dist/utils/pattern.d.ts +30 -0
  283. package/dist/utils/pattern.d.ts.map +1 -0
  284. package/dist/utils/pattern.js +130 -0
  285. package/dist/utils/pattern.js.map +1 -0
  286. package/dist/utils/search-index.d.ts +22 -7
  287. package/dist/utils/search-index.d.ts.map +1 -1
  288. package/dist/utils/search-index.js +26 -8
  289. package/dist/utils/search-index.js.map +1 -1
  290. package/dist/utils/storage.d.ts +14 -44
  291. package/dist/utils/storage.d.ts.map +1 -1
  292. package/dist/utils/storage.js +47 -84
  293. package/dist/utils/storage.js.map +1 -1
  294. package/dist/utils/subtitles.d.ts +7 -0
  295. package/dist/utils/subtitles.d.ts.map +1 -0
  296. package/dist/utils/subtitles.js +30 -0
  297. package/dist/utils/subtitles.js.map +1 -0
  298. package/dist/utils/thumbnail-entry.d.ts +19 -0
  299. package/dist/utils/thumbnail-entry.d.ts.map +1 -0
  300. package/dist/utils/thumbnail-entry.js +119 -0
  301. package/dist/utils/thumbnail-entry.js.map +1 -0
  302. package/dist/utils/thumbnail-fetch.d.ts +37 -0
  303. package/dist/utils/thumbnail-fetch.d.ts.map +1 -0
  304. package/dist/utils/thumbnail-fetch.js +89 -0
  305. package/dist/utils/thumbnail-fetch.js.map +1 -0
  306. package/dist/utils/thumbnail-ladder.d.ts +44 -0
  307. package/dist/utils/thumbnail-ladder.d.ts.map +1 -0
  308. package/dist/utils/thumbnail-ladder.js +68 -0
  309. package/dist/utils/thumbnail-ladder.js.map +1 -0
  310. package/dist/utils/thumbnail-listing.d.ts +16 -0
  311. package/dist/utils/thumbnail-listing.d.ts.map +1 -0
  312. package/dist/utils/thumbnail-listing.js +26 -0
  313. package/dist/utils/thumbnail-listing.js.map +1 -0
  314. package/dist/utils/thumbnail-paths.d.ts +20 -0
  315. package/dist/utils/thumbnail-paths.d.ts.map +1 -0
  316. package/dist/utils/thumbnail-paths.js +35 -0
  317. package/dist/utils/thumbnail-paths.js.map +1 -0
  318. package/dist/utils/thumbnail-store.d.ts +19 -0
  319. package/dist/utils/thumbnail-store.d.ts.map +1 -0
  320. package/dist/utils/thumbnail-store.js +125 -0
  321. package/dist/utils/thumbnail-store.js.map +1 -0
  322. package/dist/utils/transcript-cache.d.ts +20 -0
  323. package/dist/utils/transcript-cache.d.ts.map +1 -0
  324. package/dist/utils/transcript-cache.js +127 -0
  325. package/dist/utils/transcript-cache.js.map +1 -0
  326. package/dist/utils/transcript-search.d.ts +20 -0
  327. package/dist/utils/transcript-search.d.ts.map +1 -0
  328. package/dist/utils/transcript-search.js +35 -0
  329. package/dist/utils/transcript-search.js.map +1 -0
  330. package/dist/utils/transcript.d.ts +14 -34
  331. package/dist/utils/transcript.d.ts.map +1 -1
  332. package/dist/utils/transcript.js +5 -64
  333. package/dist/utils/transcript.js.map +1 -1
  334. package/dist/utils/validate-youtube.d.ts +35 -0
  335. package/dist/utils/validate-youtube.d.ts.map +1 -0
  336. package/dist/utils/validate-youtube.js +87 -0
  337. package/dist/utils/validate-youtube.js.map +1 -0
  338. package/dist/utils/validate.d.ts +11 -3
  339. package/dist/utils/validate.d.ts.map +1 -1
  340. package/dist/utils/validate.js +43 -18
  341. package/dist/utils/validate.js.map +1 -1
  342. package/dist/utils/version.d.ts +2 -0
  343. package/dist/utils/version.d.ts.map +1 -0
  344. package/dist/utils/version.js +19 -0
  345. package/dist/utils/version.js.map +1 -0
  346. package/dist/utils/youtube-channel.d.ts +44 -0
  347. package/dist/utils/youtube-channel.d.ts.map +1 -0
  348. package/dist/utils/youtube-channel.js +61 -0
  349. package/dist/utils/youtube-channel.js.map +1 -0
  350. package/dist/utils/youtube-download.d.ts +27 -0
  351. package/dist/utils/youtube-download.d.ts.map +1 -0
  352. package/dist/utils/youtube-download.js +118 -0
  353. package/dist/utils/youtube-download.js.map +1 -0
  354. package/dist/utils/youtube-schemas.d.ts +394 -0
  355. package/dist/utils/youtube-schemas.d.ts.map +1 -0
  356. package/dist/utils/youtube-schemas.js +118 -0
  357. package/dist/utils/youtube-schemas.js.map +1 -0
  358. package/dist/utils/youtube-search.d.ts +14 -0
  359. package/dist/utils/youtube-search.d.ts.map +1 -0
  360. package/dist/utils/youtube-search.js +39 -0
  361. package/dist/utils/youtube-search.js.map +1 -0
  362. package/dist/utils/youtube-url.d.ts +25 -0
  363. package/dist/utils/youtube-url.d.ts.map +1 -0
  364. package/dist/utils/youtube-url.js +75 -0
  365. package/dist/utils/youtube-url.js.map +1 -0
  366. package/dist/utils/youtube-video.d.ts +47 -0
  367. package/dist/utils/youtube-video.d.ts.map +1 -0
  368. package/dist/utils/youtube-video.js +98 -0
  369. package/dist/utils/youtube-video.js.map +1 -0
  370. package/dist/utils/youtube.d.ts +13 -111
  371. package/dist/utils/youtube.d.ts.map +1 -1
  372. package/dist/utils/youtube.js +11 -480
  373. package/dist/utils/youtube.js.map +1 -1
  374. package/dist/utils/ytdlp-env.d.ts +33 -0
  375. package/dist/utils/ytdlp-env.d.ts.map +1 -0
  376. package/dist/utils/ytdlp-env.js +121 -0
  377. package/dist/utils/ytdlp-env.js.map +1 -0
  378. package/dist/utils/ytdlp-limiter.d.ts +24 -0
  379. package/dist/utils/ytdlp-limiter.d.ts.map +1 -0
  380. package/dist/utils/ytdlp-limiter.js +69 -0
  381. package/dist/utils/ytdlp-limiter.js.map +1 -0
  382. package/dist/utils/ytdlp-parse.d.ts +13 -0
  383. package/dist/utils/ytdlp-parse.d.ts.map +1 -0
  384. package/dist/utils/ytdlp-parse.js +48 -0
  385. package/dist/utils/ytdlp-parse.js.map +1 -0
  386. package/dist/utils/ytdlp.d.ts +13 -18
  387. package/dist/utils/ytdlp.d.ts.map +1 -1
  388. package/dist/utils/ytdlp.js +21 -110
  389. package/dist/utils/ytdlp.js.map +1 -1
  390. package/package.json +2 -1
@@ -20,24 +20,27 @@ export declare const searchVideosOutputSchema: {
20
20
  channel: z.ZodOptional<z.ZodString>;
21
21
  viewCount: z.ZodOptional<z.ZodNumber>;
22
22
  uploadDate: z.ZodOptional<z.ZodString>;
23
+ thumbnailUrl: z.ZodOptional<z.ZodString>;
23
24
  }, "strip", z.ZodTypeAny, {
24
- url: string;
25
- title: string;
26
25
  id: string;
27
- durationFormatted: string;
26
+ title: string;
28
27
  durationSeconds: number;
28
+ durationFormatted: string;
29
+ url: string;
29
30
  channel?: string | undefined;
30
- uploadDate?: string | undefined;
31
31
  viewCount?: number | undefined;
32
+ uploadDate?: string | undefined;
33
+ thumbnailUrl?: string | undefined;
32
34
  }, {
33
- url: string;
34
- title: string;
35
35
  id: string;
36
- durationFormatted: string;
36
+ title: string;
37
37
  durationSeconds: number;
38
+ durationFormatted: string;
39
+ url: string;
38
40
  channel?: string | undefined;
39
- uploadDate?: string | undefined;
40
41
  viewCount?: number | undefined;
42
+ uploadDate?: string | undefined;
43
+ thumbnailUrl?: string | undefined;
41
44
  }>, "many">;
42
45
  };
43
46
  export declare function searchVideosHandler({ query, limit, }: {
@@ -1 +1 @@
1
- {"version":3,"file":"search-videos.d.ts","sourceRoot":"","sources":["../../src/tools/search-videos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,eAAO,MAAM,kBAAkB;;;CAU9B,CAAC;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAIpC,CAAC;AAEF,wBAAsB,mBAAmB,CAAC,EACxC,KAAK,EACL,KAAK,GACN,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,cAAc,CAAC,CA6B1B"}
1
+ {"version":3,"file":"search-videos.d.ts","sourceRoot":"","sources":["../../src/tools/search-videos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,eAAO,MAAM,kBAAkB;;;CAU9B,CAAC;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAIpC,CAAC;AAEF,wBAAsB,mBAAmB,CAAC,EACxC,KAAK,EACL,KAAK,GACN,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,cAAc,CAAC,CA6B1B"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Run `work` over the items, `size` at a time.
3
+ *
4
+ * Deliberately not a rolling window: a batch boundary is a natural place for a
5
+ * checkpoint and a cancellation check, and the limiter inside yt-dlp is what
6
+ * actually paces the network. Results come back in item order, whichever
7
+ * finished first.
8
+ *
9
+ * A batch always settles before a failure propagates. `Promise.all` would
10
+ * reject the moment one item did — on a cancelled request, before the items
11
+ * already in flight had finished — and a checkpoint taken in that instant
12
+ * would record none of them.
13
+ */
14
+ export declare function inBatches<T, R>(items: T[], size: number, work: (item: T, index: number) => Promise<R>): Promise<R[]>;
15
+ //# sourceMappingURL=batches.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batches.d.ts","sourceRoot":"","sources":["../../src/utils/batches.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,CAAC,EAClC,KAAK,EAAE,CAAC,EAAE,EACV,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAC3C,OAAO,CAAC,CAAC,EAAE,CAAC,CAiBd"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Run `work` over the items, `size` at a time.
3
+ *
4
+ * Deliberately not a rolling window: a batch boundary is a natural place for a
5
+ * checkpoint and a cancellation check, and the limiter inside yt-dlp is what
6
+ * actually paces the network. Results come back in item order, whichever
7
+ * finished first.
8
+ *
9
+ * A batch always settles before a failure propagates. `Promise.all` would
10
+ * reject the moment one item did — on a cancelled request, before the items
11
+ * already in flight had finished — and a checkpoint taken in that instant
12
+ * would record none of them.
13
+ */
14
+ export async function inBatches(items, size, work) {
15
+ const results = [];
16
+ const width = Math.max(1, size);
17
+ for (let start = 0; start < items.length; start += width) {
18
+ const slice = items.slice(start, start + width);
19
+ const settled = await Promise.allSettled(slice.map((item, offset) => work(item, start + offset)));
20
+ for (const outcome of settled) {
21
+ if (outcome.status === 'rejected')
22
+ throw asError(outcome.reason);
23
+ results.push(outcome.value);
24
+ }
25
+ }
26
+ return results;
27
+ }
28
+ function asError(reason) {
29
+ return reason instanceof Error ? reason : new Error(String(reason));
30
+ }
31
+ //# sourceMappingURL=batches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batches.js","sourceRoot":"","sources":["../../src/utils/batches.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,KAAU,EACV,IAAY,EACZ,IAA4C;IAE5C,MAAM,OAAO,GAAQ,EAAE,CAAC;IACxB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAEhC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,KAAK,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACtC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC,CACxD,CAAC;QAEF,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;YAC9B,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU;gBAAE,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,OAAO,CAAC,MAAe;IAC9B,OAAO,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AACtE,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { BrainManifest } from '../brain-schemas.js';
2
+ import { type VideoFilters } from './brain-ingest.js';
3
+ import type { ChannelInfo, VideoListItem } from './youtube.js';
4
+ /**
5
+ * Reading a channel into a brain.
6
+ *
7
+ * The shape of this is dictated by how long it takes. Several hundred videos is
8
+ * several hundred yt-dlp invocations, so a build is something that gets
9
+ * interrupted: cancelled by the client, throttled by YouTube, or killed along
10
+ * with the editor it was started from. Every choice below follows from that —
11
+ * per-video state rather than one "built" flag, checkpoints rather than a
12
+ * single write at the end, and a failed video costing only itself.
13
+ *
14
+ * Which is also why there is no separate refresh: a second build sees what it
15
+ * already has, skips it, and reads whatever is new.
16
+ */
17
+ /** Frequent enough that little work is lost, rare enough not to dominate. */
18
+ export declare const CHECKPOINT_EVERY_VIDEOS = 10;
19
+ /** Past this many throttled videos in a row, YouTube means it. */
20
+ export declare const RATE_LIMIT_TOLERANCE = 3;
21
+ export interface BuildBrainOptions extends VideoFilters {
22
+ channel: ChannelInfo;
23
+ videos: VideoListItem[];
24
+ existing: BrainManifest | undefined;
25
+ /** Caption language to read. A brain is built from one language at a time. */
26
+ language: string;
27
+ }
28
+ export interface BuildBrainResult {
29
+ manifest: BrainManifest;
30
+ /** Videos the filters kept, whether or not this call had to read them. */
31
+ considered: number;
32
+ /** Videos read during this call, as opposed to carried over from an earlier one. */
33
+ processed: number;
34
+ skipped: number;
35
+ excluded: number;
36
+ stoppedEarly: boolean;
37
+ stopReason?: string;
38
+ }
39
+ export declare function buildBrain(options: BuildBrainOptions): Promise<BuildBrainResult>;
40
+ //# sourceMappingURL=brain-build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-build.d.ts","sourceRoot":"","sources":["../../src/utils/brain-build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,aAAa,EAAmB,MAAM,qBAAqB,CAAC;AAEtF,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAM3B,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE/D;;;;;;;;;;;;GAYG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAc1C,kEAAkE;AAClE,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,MAAM,WAAW,iBAAkB,SAAQ,YAAY;IACrD,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,QAAQ,EAAE,aAAa,GAAG,SAAS,CAAC;IACpC,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,aAAa,CAAC;IACxB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA4EtF"}
@@ -0,0 +1,200 @@
1
+ import { readChunks, writeChunks } from './brain-index.js';
2
+ import { excluded, ingestVideo, isExcluded, pendingState, } from './brain-ingest.js';
3
+ import { computeStats } from './brain-stats.js';
4
+ import { BRAIN_MANIFEST_VERSION, writeManifest } from './brain-storage.js';
5
+ import { reportProgress, throwIfAborted } from './context.js';
6
+ import { concurrencyState } from './ytdlp.js';
7
+ import { inBatches } from './batches.js';
8
+ /**
9
+ * Reading a channel into a brain.
10
+ *
11
+ * The shape of this is dictated by how long it takes. Several hundred videos is
12
+ * several hundred yt-dlp invocations, so a build is something that gets
13
+ * interrupted: cancelled by the client, throttled by YouTube, or killed along
14
+ * with the editor it was started from. Every choice below follows from that —
15
+ * per-video state rather than one "built" flag, checkpoints rather than a
16
+ * single write at the end, and a failed video costing only itself.
17
+ *
18
+ * Which is also why there is no separate refresh: a second build sees what it
19
+ * already has, skips it, and reads whatever is new.
20
+ */
21
+ /** Frequent enough that little work is lost, rare enough not to dominate. */
22
+ export const CHECKPOINT_EVERY_VIDEOS = 10;
23
+ /**
24
+ * How many videos to read at once: whatever the yt-dlp limiter is actually set
25
+ * to, which `YOUTUBE_MCP_MAX_CONCURRENCY` can change.
26
+ *
27
+ * Read rather than copied. A second number that merely happened to match would
28
+ * stop matching the moment someone tuned the first, and would then either queue
29
+ * work behind a limit it could not see or batch below one it was allowed.
30
+ */
31
+ function buildConcurrency() {
32
+ return concurrencyState().limit;
33
+ }
34
+ /** Past this many throttled videos in a row, YouTube means it. */
35
+ export const RATE_LIMIT_TOLERANCE = 3;
36
+ export async function buildBrain(options) {
37
+ const { channel, videos, existing, language, since, minDurationSeconds } = options;
38
+ const filters = { minDurationSeconds, ...(since === undefined ? {} : { since }) };
39
+ const createdAt = existing?.createdAt ?? new Date().toISOString();
40
+ const chunksByVideo = groupByVideo(await readChunks(channel.channelId));
41
+ const states = reconciled(existing, videos, chunksByVideo, filters);
42
+ const outstanding = videos.filter((video) => isOutstanding(states.get(video.id)));
43
+ /**
44
+ * `phrases` is the difference between a checkpoint and the end of a build:
45
+ * the phrase pass reads the whole corpus and takes seconds at the sizes a
46
+ * brain can reach, so it runs once, when there is a finished corpus to run it
47
+ * over. A checkpoint records what has been read, which is all it is for.
48
+ */
49
+ const save = async (phrases) => {
50
+ const passages = flatten(states, chunksByVideo);
51
+ const manifest = toManifest(channel, states, passages, createdAt, language, phrases);
52
+ await writeChunks(channel.channelId, passages);
53
+ await writeManifest(manifest);
54
+ return manifest;
55
+ };
56
+ let attempted = 0;
57
+ let processed = 0;
58
+ let consecutiveThrottles = 0;
59
+ let stopReason;
60
+ try {
61
+ await inBatches(outstanding, buildConcurrency(), async (video) => {
62
+ if (stopReason !== undefined)
63
+ return;
64
+ throwIfAborted();
65
+ const { state, chunks } = await ingestVideo(video, countChunks(chunksByVideo), {
66
+ language,
67
+ ...filters,
68
+ });
69
+ states.set(video.id, state);
70
+ if (chunks.length > 0)
71
+ chunksByVideo.set(video.id, chunks);
72
+ else
73
+ chunksByVideo.delete(video.id);
74
+ attempted++;
75
+ // A video the filters ruled out was looked at, not read.
76
+ if (state.state !== 'excluded')
77
+ processed++;
78
+ consecutiveThrottles = state.error === 'RATE_LIMITED' ? consecutiveThrottles + 1 : 0;
79
+ if (consecutiveThrottles >= RATE_LIMIT_TOLERANCE) {
80
+ stopReason = 'YouTube is rate limiting this client.';
81
+ }
82
+ reportProgress(attempted, outstanding.length, `Read ${attempted} of ${outstanding.length}`);
83
+ if (attempted % CHECKPOINT_EVERY_VIDEOS === 0)
84
+ await save(false);
85
+ });
86
+ }
87
+ catch (error) {
88
+ // A cancelled build must not throw away the videos read since the last
89
+ // checkpoint. Saving first is what makes "interrupt it and call it again"
90
+ // true rather than nearly true — and it is a checkpoint, not a finished
91
+ // corpus, so it does not pay for the phrase pass either.
92
+ await save(false);
93
+ throw error;
94
+ }
95
+ const manifest = await save(true);
96
+ const excludedCount = videos.filter((video) => states.get(video.id)?.state === 'excluded').length;
97
+ return {
98
+ manifest,
99
+ considered: videos.length - excludedCount,
100
+ processed,
101
+ skipped: videos.length - excludedCount - processed,
102
+ excluded: excludedCount,
103
+ stoppedEarly: stopReason !== undefined,
104
+ ...(stopReason === undefined ? {} : { stopReason }),
105
+ };
106
+ }
107
+ /**
108
+ * Per-video state, reconciled against the passages on disk and re-tested
109
+ * against the filters in force right now.
110
+ *
111
+ * Two corrections happen here, both for the same reason: what the manifest says
112
+ * was true when it was written, and neither the corpus nor the caller's filters
113
+ * are obliged to have stayed the same.
114
+ *
115
+ * The manifest and the passage file are two documents, and only one can be
116
+ * written first. A crash between them, a half-restored backup, or a
117
+ * `chunks.json` truncated by a full disk all end the same way — the manifest
118
+ * says a video was read and there is nothing to show for it. Left alone that
119
+ * brain is stranded for good: every build skips the video as already done while
120
+ * every search returns nothing. So the passages win, and a video the corpus
121
+ * cannot account for goes back to pending.
122
+ *
123
+ * A filter is re-applied rather than remembered. Narrowing one excludes videos
124
+ * already read; widening one brings excluded videos back. Both are decided from
125
+ * the dates and lengths already recorded, so changing your mind costs nothing
126
+ * until there is something new to fetch.
127
+ */
128
+ function reconciled(existing, videos, chunksByVideo, filters) {
129
+ const states = new Map();
130
+ for (const [videoId, recorded] of Object.entries(existing?.videos ?? {})) {
131
+ const lost = recorded.state === 'indexed' && (chunksByVideo.get(videoId)?.length ?? 0) === 0;
132
+ const state = lost
133
+ ? { ...recorded, state: 'pending', chunkCount: 0, wordCount: 0 }
134
+ : recorded;
135
+ states.set(videoId, refiltered(state, filters));
136
+ }
137
+ for (const video of videos) {
138
+ // The listing's own length is real data, so a filter that can be decided
139
+ // from it is decided here — before spending a request to learn what the
140
+ // listing already said.
141
+ if (!states.has(video.id))
142
+ states.set(video.id, refiltered(pendingState(video), filters));
143
+ }
144
+ return states;
145
+ }
146
+ /**
147
+ * `undefined` — the filters cannot be decided from what is recorded — leaves the
148
+ * state alone, so the video is read and the question answered from the metadata
149
+ * that read fetches.
150
+ */
151
+ function refiltered(state, filters) {
152
+ const ruledOut = isExcluded(state, filters);
153
+ if (ruledOut === true)
154
+ return excluded(state);
155
+ if (ruledOut === false && state.state === 'excluded') {
156
+ return { ...state, state: 'pending' };
157
+ }
158
+ return state;
159
+ }
160
+ /** Anything not already read: never attempted, or attempted and failed. */
161
+ function isOutstanding(state) {
162
+ return state === undefined || state.state === 'pending' || state.state === 'failed';
163
+ }
164
+ function groupByVideo(chunks) {
165
+ const grouped = new Map();
166
+ for (const chunk of chunks) {
167
+ grouped.set(chunk.videoId, [...(grouped.get(chunk.videoId) ?? []), chunk]);
168
+ }
169
+ return grouped;
170
+ }
171
+ function countChunks(chunksByVideo) {
172
+ let total = 0;
173
+ for (const chunks of chunksByVideo.values())
174
+ total += chunks.length;
175
+ return total;
176
+ }
177
+ /**
178
+ * The corpus: every passage of every video the brain holds, in manifest order so
179
+ * the file changes as little as possible between builds.
180
+ *
181
+ * A video the filters exclude contributes nothing, which is what makes the
182
+ * filters mean something. Leaving its passages in would let `ask_brain` quote a
183
+ * video that `get_brain_info` says is not part of this brain, and no wording
184
+ * makes that defensible.
185
+ */
186
+ function flatten(states, chunksByVideo) {
187
+ return [...states.entries()].flatMap(([videoId, state]) => state.state === 'excluded' ? [] : (chunksByVideo.get(videoId) ?? []));
188
+ }
189
+ function toManifest(channel, states, passages, createdAt, language, phrases) {
190
+ return {
191
+ version: BRAIN_MANIFEST_VERSION,
192
+ channel,
193
+ language,
194
+ createdAt,
195
+ updatedAt: new Date().toISOString(),
196
+ videos: Object.fromEntries(states),
197
+ stats: computeStats([...states.values()], passages, { phrases }),
198
+ };
199
+ }
200
+ //# sourceMappingURL=brain-build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-build.js","sourceRoot":"","sources":["../../src/utils/brain-build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { BrainChunk } from '../brain-schemas.js';
2
+ import { type TranscriptSegment } from './transcript.js';
3
+ import type { Chapter } from './youtube.js';
4
+ /**
5
+ * Transcripts cut into retrievable passages.
6
+ *
7
+ * A brain is searched, not read: a whole transcript is too long to rank
8
+ * usefully and far too long to return, while a single caption cue is too short
9
+ * to carry a thought. A passage of a few sentences is what BM25 scores well and
10
+ * what a reader can check against the video in one click.
11
+ *
12
+ * Chunks do not overlap. Overlap is worth its cost with embeddings, where a
13
+ * concept split across a boundary can be missed entirely; BM25 matches terms,
14
+ * and a duplicated passage would only mean two results saying the same thing.
15
+ */
16
+ /** Where a passage stops being short enough to keep growing. */
17
+ export declare const CHUNK_TARGET_CHARS = 800;
18
+ /**
19
+ * Where it stops regardless. Speech without a pause for this long is rare, and
20
+ * an unbroken passage is worse than one broken in an awkward place.
21
+ */
22
+ export declare const CHUNK_MAX_CHARS = 1200;
23
+ /** A pause this long is a sentence or a thought ending. */
24
+ export declare const CHUNK_BREAK_GAP_SECONDS = 2;
25
+ export interface ChunkTranscriptOptions {
26
+ videoId: string;
27
+ title: string;
28
+ segments: TranscriptSegment[];
29
+ /** Chapter starts are always passage boundaries: a new chapter is a new subject. */
30
+ chapters?: Chapter[];
31
+ }
32
+ export declare function chunkTranscript(options: ChunkTranscriptOptions): BrainChunk[];
33
+ //# sourceMappingURL=brain-chunks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-chunks.d.ts","sourceRoot":"","sources":["../../src/utils/brain-chunks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAkB,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;;;;;;;GAWG;AAEH,gEAAgE;AAChE,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC;;;GAGG;AACH,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,2DAA2D;AAC3D,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,oFAAoF;IACpF,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,UAAU,EAAE,CAwC7E"}
@@ -0,0 +1,87 @@
1
+ import { segmentsToText } from './transcript.js';
2
+ /**
3
+ * Transcripts cut into retrievable passages.
4
+ *
5
+ * A brain is searched, not read: a whole transcript is too long to rank
6
+ * usefully and far too long to return, while a single caption cue is too short
7
+ * to carry a thought. A passage of a few sentences is what BM25 scores well and
8
+ * what a reader can check against the video in one click.
9
+ *
10
+ * Chunks do not overlap. Overlap is worth its cost with embeddings, where a
11
+ * concept split across a boundary can be missed entirely; BM25 matches terms,
12
+ * and a duplicated passage would only mean two results saying the same thing.
13
+ */
14
+ /** Where a passage stops being short enough to keep growing. */
15
+ export const CHUNK_TARGET_CHARS = 800;
16
+ /**
17
+ * Where it stops regardless. Speech without a pause for this long is rare, and
18
+ * an unbroken passage is worse than one broken in an awkward place.
19
+ */
20
+ export const CHUNK_MAX_CHARS = 1200;
21
+ /** A pause this long is a sentence or a thought ending. */
22
+ export const CHUNK_BREAK_GAP_SECONDS = 2;
23
+ export function chunkTranscript(options) {
24
+ const { videoId, title, segments, chapters = [] } = options;
25
+ const chapterStarts = chapters.map((chapter) => chapter.startTime).sort((a, b) => a - b);
26
+ const chunks = [];
27
+ let current = [];
28
+ let currentChars = 0;
29
+ let nextChapter = 0;
30
+ const flush = () => {
31
+ const chunk = toChunk(videoId, title, current, chunks.length);
32
+ if (chunk !== undefined)
33
+ chunks.push(chunk);
34
+ current = [];
35
+ currentChars = 0;
36
+ };
37
+ for (const segment of segments) {
38
+ // Consume every chapter that starts at or before this segment. More than
39
+ // one can land in the same gap when a chapter is shorter than a cue.
40
+ let startsChapter = false;
41
+ while (nextChapter < chapterStarts.length) {
42
+ const start = chapterStarts[nextChapter];
43
+ if (start === undefined || start > segment.start)
44
+ break;
45
+ nextChapter++;
46
+ startsChapter = true;
47
+ }
48
+ const previous = current[current.length - 1];
49
+ if (previous !== undefined && shouldBreak(previous, segment, currentChars, startsChapter)) {
50
+ flush();
51
+ }
52
+ current.push(segment);
53
+ // Plus one for the space this segment will be joined with.
54
+ currentChars += segment.text.length + 1;
55
+ }
56
+ flush();
57
+ return chunks;
58
+ }
59
+ function shouldBreak(previous, segment, currentChars, startsChapter) {
60
+ if (startsChapter)
61
+ return true;
62
+ if (currentChars >= CHUNK_MAX_CHARS)
63
+ return true;
64
+ return (currentChars >= CHUNK_TARGET_CHARS && segment.start - previous.end >= CHUNK_BREAK_GAP_SECONDS);
65
+ }
66
+ /** Undefined for an empty run, and for one that turned out to be only whitespace. */
67
+ function toChunk(videoId, title, segments, ordinal) {
68
+ const first = segments[0];
69
+ const last = segments[segments.length - 1];
70
+ if (first === undefined || last === undefined)
71
+ return undefined;
72
+ const text = segmentsToText(segments);
73
+ if (text === '')
74
+ return undefined;
75
+ return {
76
+ // Ordinal rather than start time: two passages can begin in the same second
77
+ // when cues are short, and a colliding id would silently drop one of them
78
+ // from the index. The start time is a field of its own.
79
+ id: `${videoId}:${ordinal}`,
80
+ videoId,
81
+ title,
82
+ startSeconds: first.start,
83
+ endSeconds: last.end,
84
+ text,
85
+ };
86
+ }
87
+ //# sourceMappingURL=brain-chunks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-chunks.js","sourceRoot":"","sources":["../../src/utils/brain-chunks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { type BrainChunk, type BrainPassage } from '../brain-schemas.js';
2
+ /**
3
+ * The searchable half of a brain: the passages, and a BM25 index over them.
4
+ *
5
+ * The passages are the only thing persisted. An index file alongside them would
6
+ * hold a second copy of every word — the serialised index carries the text it
7
+ * scored — and would then be free to disagree with them. It is built in memory
8
+ * on first use instead and kept until the passages change: measured at 1.5s for
9
+ * a corpus at this brain's 100,000-passage ceiling, once, against a class of
10
+ * bugs forever. A search over that corpus then costs 73ms.
11
+ *
12
+ * Retrieval is the only way a corpus reaches a model. That ceiling is 81MB of
13
+ * transcript, so there is no "give me everything" path here: `readChunks`
14
+ * exists for statistics and rebuilding, not for answering.
15
+ */
16
+ export declare const BRAIN_CHUNK_FILE_VERSION = 1;
17
+ export declare function forgetBrainCorpus(channelId: string): void;
18
+ export declare function readChunks(channelId: string): Promise<BrainChunk[]>;
19
+ export declare function writeChunks(channelId: string, chunks: BrainChunk[]): Promise<void>;
20
+ export interface BrainSearchResults {
21
+ passages: BrainPassage[];
22
+ /** Passages matching the query, not passages returned. */
23
+ total: number;
24
+ }
25
+ /** Passages ranked against the query, each with the link that opens it. */
26
+ export declare function searchBrain(channelId: string, query: string, limit: number, offset?: number): Promise<BrainSearchResults>;
27
+ //# sourceMappingURL=brain-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-index.d.ts","sourceRoot":"","sources":["../../src/utils/brain-index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAO/F;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAoB1C,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAGzE;AAED,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CASxF;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,2EAA2E;AAC3E,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,SAAI,GACT,OAAO,CAAC,kBAAkB,CAAC,CAuB7B"}
@@ -0,0 +1,101 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { stat } from 'node:fs/promises';
3
+ import { brainChunkFileSchema } from '../brain-schemas.js';
4
+ import { brainDir, chunksPath } from './brain-paths.js';
5
+ import { ensurePrivateDir } from './paths.js';
6
+ import { readJsonFile, writeJsonAtomic } from './json-file.js';
7
+ import { SearchIndex } from './search-index.js';
8
+ import { deepLink, formatTimestamp } from './transcript.js';
9
+ /**
10
+ * The searchable half of a brain: the passages, and a BM25 index over them.
11
+ *
12
+ * The passages are the only thing persisted. An index file alongside them would
13
+ * hold a second copy of every word — the serialised index carries the text it
14
+ * scored — and would then be free to disagree with them. It is built in memory
15
+ * on first use instead and kept until the passages change: measured at 1.5s for
16
+ * a corpus at this brain's 100,000-passage ceiling, once, against a class of
17
+ * bugs forever. A search over that corpus then costs 73ms.
18
+ *
19
+ * Retrieval is the only way a corpus reaches a model. That ceiling is 81MB of
20
+ * transcript, so there is no "give me everything" path here: `readChunks`
21
+ * exists for statistics and rebuilding, not for answering.
22
+ */
23
+ export const BRAIN_CHUNK_FILE_VERSION = 1;
24
+ /**
25
+ * Rebuilding the index on every query would re-read and re-tokenise the whole
26
+ * corpus each time. The passage file's modification time decides when the
27
+ * cached copy is wrong, so a build running in another process is picked up
28
+ * without any coordination between the two.
29
+ */
30
+ const cache = new Map();
31
+ export function forgetBrainCorpus(channelId) {
32
+ cache.delete(channelId);
33
+ }
34
+ export async function readChunks(channelId) {
35
+ const file = await readJsonFile(chunksPath(channelId), brainChunkFileSchema);
36
+ return file?.chunks ?? [];
37
+ }
38
+ export async function writeChunks(channelId, chunks) {
39
+ await ensurePrivateDir(brainDir(channelId));
40
+ // Not pretty-printed: indentation would be a fifth of a file nobody reads.
41
+ await writeJsonAtomic(chunksPath(channelId), { version: BRAIN_CHUNK_FILE_VERSION, chunks }, { pretty: false });
42
+ forgetBrainCorpus(channelId);
43
+ }
44
+ /** Passages ranked against the query, each with the link that opens it. */
45
+ export async function searchBrain(channelId, query, limit, offset = 0) {
46
+ const { index, byId } = await corpusOf(channelId);
47
+ const { hits, total } = index.search(query, limit, offset);
48
+ const passages = hits.flatMap((hit) => {
49
+ const chunk = byId.get(hit.id);
50
+ if (chunk === undefined)
51
+ return [];
52
+ return [
53
+ {
54
+ videoId: chunk.videoId,
55
+ title: chunk.title,
56
+ startSeconds: chunk.startSeconds,
57
+ startFormatted: formatTimestamp(chunk.startSeconds),
58
+ endSeconds: chunk.endSeconds,
59
+ score: hit.score,
60
+ text: chunk.text,
61
+ url: deepLink(chunk.videoId, chunk.startSeconds),
62
+ },
63
+ ];
64
+ });
65
+ return { passages, total };
66
+ }
67
+ async function corpusOf(channelId) {
68
+ const path = chunksPath(channelId);
69
+ if (!existsSync(path))
70
+ return emptyCorpus();
71
+ const { mtimeMs } = await stat(path);
72
+ const cached = cache.get(channelId);
73
+ if (cached?.mtimeMs === mtimeMs)
74
+ return cached.corpus;
75
+ const corpus = buildCorpus(await readChunks(channelId));
76
+ cache.set(channelId, { corpus, mtimeMs });
77
+ return corpus;
78
+ }
79
+ function emptyCorpus() {
80
+ return { index: new SearchIndex(), byId: new Map() };
81
+ }
82
+ /**
83
+ * The shared index is used unchanged: a passage already has an id, a video, a
84
+ * title and text, which is everything BM25 scores on. `kind` distinguishes
85
+ * these rows from the library's summaries and skills.
86
+ */
87
+ function buildCorpus(chunks) {
88
+ const corpus = emptyCorpus();
89
+ for (const chunk of chunks) {
90
+ corpus.index.add({
91
+ id: chunk.id,
92
+ videoId: chunk.videoId,
93
+ title: chunk.title,
94
+ kind: 'chunk',
95
+ text: chunk.text,
96
+ });
97
+ corpus.byId.set(chunk.id, chunk);
98
+ }
99
+ return corpus;
100
+ }
101
+ //# sourceMappingURL=brain-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-index.js","sourceRoot":"","sources":["../../src/utils/brain-index.ts"],"names":[],"mappings":"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"}