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
@@ -0,0 +1,57 @@
1
+ import type { BrainChunk, BrainVideoState } from '../brain-schemas.js';
2
+ import { type VideoListItem } from './youtube.js';
3
+ /**
4
+ * Reading one video into passages.
5
+ *
6
+ * Nothing here throws for anything to do with the video itself. A channel with
7
+ * a members-only upload, a live stream still processing, or an episode nobody
8
+ * captioned is still a channel worth having a brain for; which videos those
9
+ * were belongs in the manifest, not in an exception that loses the other two
10
+ * hundred.
11
+ *
12
+ * Metadata is read before the transcript, in that order deliberately: it is the
13
+ * cheaper request, it carries the date and length the filters need, and a video
14
+ * ruled out by them then costs one request instead of two.
15
+ */
16
+ /**
17
+ * A transcript that chunks past this is not a transcript. Recording the video
18
+ * as failed keeps the brain buildable; letting it through does not.
19
+ *
20
+ * The brain-wide ceiling is what bounds memory, and it was chosen by measuring
21
+ * rather than by feel: a corpus of 100,000 passages is 81MB on disk, and the
22
+ * heaviest thing done to it — the phrase pass — peaked at 767MB of heap. Raising
23
+ * it means measuring again.
24
+ */
25
+ export declare const MAX_CHUNKS_PER_VIDEO = 2000;
26
+ export declare const MAX_CHUNKS_PER_BRAIN = 100000;
27
+ /** Recorded against a video the size guards refused, alongside yt-dlp's own codes. */
28
+ export declare const TOO_LARGE = "TOO_LARGE";
29
+ export declare const BRAIN_FULL = "BRAIN_FULL";
30
+ export interface VideoFilters {
31
+ /** Ignore anything published before this date (YYYY-MM-DD). */
32
+ since?: string;
33
+ /** Ignore anything shorter, to leave out shorts and clips. */
34
+ minDurationSeconds: number;
35
+ }
36
+ export interface IngestOptions extends VideoFilters {
37
+ language: string;
38
+ }
39
+ export interface IngestResult {
40
+ state: BrainVideoState;
41
+ chunks: BrainChunk[];
42
+ }
43
+ export declare function ingestVideo(video: VideoListItem, chunksSoFar: number, options: IngestOptions): Promise<IngestResult>;
44
+ /**
45
+ * Whether the filters rule this video out, or `undefined` when its recorded
46
+ * state cannot say.
47
+ *
48
+ * A build asks this of the manifest before deciding what to read, which is what
49
+ * lets a changed filter take effect without re-fetching anything: once a video
50
+ * has been looked at, its real date and length are on disk and the question is
51
+ * answerable from there. `undefined` is the honest answer for a video only ever
52
+ * seen in a flat listing — not a licence to assume it qualifies.
53
+ */
54
+ export declare function isExcluded(state: BrainVideoState, filters: VideoFilters): boolean | undefined;
55
+ export declare function pendingState(video: VideoListItem): BrainVideoState;
56
+ export declare function excluded(state: BrainVideoState): BrainVideoState;
57
+ //# sourceMappingURL=brain-ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-ingest.d.ts","sourceRoot":"","sources":["../../src/utils/brain-ingest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAKvE,OAAO,EAAkC,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAElF;;;;;;;;;;;;GAYG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AACzC,eAAO,MAAM,oBAAoB,SAAU,CAAC;AAE5C,sFAAsF;AACtF,eAAO,MAAM,SAAS,cAAc,CAAC;AACrC,eAAO,MAAM,UAAU,eAAe,CAAC;AAEvC,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,wBAAsB,WAAW,CAC/B,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAoDvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,GAAG,SAAS,CAe7F;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,eAAe,CAWlE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,eAAe,CAEhE"}
@@ -0,0 +1,127 @@
1
+ import { chunkTranscript } from './brain-chunks.js';
2
+ import { throwIfAborted } from './context.js';
3
+ import { asYouTubeError } from './errors.js';
4
+ import { countWords } from './transcript.js';
5
+ import { getTranscript, getVideoDetails } from './youtube.js';
6
+ /**
7
+ * Reading one video into passages.
8
+ *
9
+ * Nothing here throws for anything to do with the video itself. A channel with
10
+ * a members-only upload, a live stream still processing, or an episode nobody
11
+ * captioned is still a channel worth having a brain for; which videos those
12
+ * were belongs in the manifest, not in an exception that loses the other two
13
+ * hundred.
14
+ *
15
+ * Metadata is read before the transcript, in that order deliberately: it is the
16
+ * cheaper request, it carries the date and length the filters need, and a video
17
+ * ruled out by them then costs one request instead of two.
18
+ */
19
+ /**
20
+ * A transcript that chunks past this is not a transcript. Recording the video
21
+ * as failed keeps the brain buildable; letting it through does not.
22
+ *
23
+ * The brain-wide ceiling is what bounds memory, and it was chosen by measuring
24
+ * rather than by feel: a corpus of 100,000 passages is 81MB on disk, and the
25
+ * heaviest thing done to it — the phrase pass — peaked at 767MB of heap. Raising
26
+ * it means measuring again.
27
+ */
28
+ export const MAX_CHUNKS_PER_VIDEO = 2000;
29
+ export const MAX_CHUNKS_PER_BRAIN = 100_000;
30
+ /** Recorded against a video the size guards refused, alongside yt-dlp's own codes. */
31
+ export const TOO_LARGE = 'TOO_LARGE';
32
+ export const BRAIN_FULL = 'BRAIN_FULL';
33
+ export async function ingestVideo(video, chunksSoFar, options) {
34
+ const listed = pendingState(video);
35
+ try {
36
+ // The listing is flat, which is what makes enumerating a thousand videos one
37
+ // request rather than a thousand — and flat entries carry no date. These are
38
+ // the real values, and the chapters come back in the same request.
39
+ const details = await getVideoDetails(video.id);
40
+ const known = {
41
+ ...listed,
42
+ uploadDate: details.uploadDate,
43
+ durationSeconds: details.durationSeconds,
44
+ };
45
+ if (isExcluded(known, options) === true)
46
+ return { state: excluded(known), chunks: [] };
47
+ const transcript = await getTranscript(video.id, { language: options.language });
48
+ const chunks = chunkTranscript({
49
+ videoId: video.id,
50
+ title: video.title,
51
+ segments: transcript.segments,
52
+ chapters: details.chapters,
53
+ });
54
+ if (chunks.length > MAX_CHUNKS_PER_VIDEO)
55
+ return refused(known, TOO_LARGE);
56
+ if (chunksSoFar + chunks.length > MAX_CHUNKS_PER_BRAIN)
57
+ return refused(known, BRAIN_FULL);
58
+ return {
59
+ state: {
60
+ ...known,
61
+ state: 'indexed',
62
+ chunkCount: chunks.length,
63
+ wordCount: chunks.reduce((total, chunk) => total + countWords(chunk.text), 0),
64
+ },
65
+ chunks,
66
+ };
67
+ }
68
+ catch (error) {
69
+ // Cancellation is the client's decision, not this video's problem. Asking
70
+ // the signal rather than classifying the error catches it however it was
71
+ // raised — an aborted spawn, a rejected read, or the check between videos.
72
+ throwIfAborted();
73
+ const failure = asYouTubeError(error);
74
+ return {
75
+ state: failure.code === 'NO_CAPTIONS'
76
+ ? { ...listed, state: 'no-captions' }
77
+ : { ...listed, state: 'failed', error: failure.code },
78
+ chunks: [],
79
+ };
80
+ }
81
+ }
82
+ /**
83
+ * Whether the filters rule this video out, or `undefined` when its recorded
84
+ * state cannot say.
85
+ *
86
+ * A build asks this of the manifest before deciding what to read, which is what
87
+ * lets a changed filter take effect without re-fetching anything: once a video
88
+ * has been looked at, its real date and length are on disk and the question is
89
+ * answerable from there. `undefined` is the honest answer for a video only ever
90
+ * seen in a flat listing — not a licence to assume it qualifies.
91
+ */
92
+ export function isExcluded(state, filters) {
93
+ const { since, minDurationSeconds } = filters;
94
+ let undecided = false;
95
+ if (minDurationSeconds > 0) {
96
+ if (state.durationSeconds <= 0)
97
+ undecided = true;
98
+ else if (state.durationSeconds < minDurationSeconds)
99
+ return true;
100
+ }
101
+ if (since !== undefined) {
102
+ if (state.uploadDate === '')
103
+ undecided = true;
104
+ else if (state.uploadDate < since)
105
+ return true;
106
+ }
107
+ return undecided ? undefined : false;
108
+ }
109
+ export function pendingState(video) {
110
+ return {
111
+ videoId: video.id,
112
+ title: video.title,
113
+ url: video.url,
114
+ uploadDate: video.uploadDate,
115
+ durationSeconds: video.duration,
116
+ state: 'pending',
117
+ chunkCount: 0,
118
+ wordCount: 0,
119
+ };
120
+ }
121
+ export function excluded(state) {
122
+ return { ...state, state: 'excluded', chunkCount: 0, wordCount: 0 };
123
+ }
124
+ function refused(state, reason) {
125
+ return { state: { ...state, state: 'failed', error: reason }, chunks: [] };
126
+ }
127
+ //# sourceMappingURL=brain-ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-ingest.js","sourceRoot":"","sources":["../../src/utils/brain-ingest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { LOCK_STALE_MS } from './file-lock.js';
2
+ /** Exclusive access to a brain while it is being built. See `withFileLock`. */
3
+ export declare function withBuildLock<T>(channelId: string, build: () => Promise<T>): Promise<T>;
4
+ //# sourceMappingURL=brain-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-lock.d.ts","sourceRoot":"","sources":["../../src/utils/brain-lock.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,+EAA+E;AAC/E,wBAAsB,aAAa,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAK7F"}
@@ -0,0 +1,17 @@
1
+ import { ensureBrainDir } from './brain-storage.js';
2
+ import { lockPath } from './brain-paths.js';
3
+ import { YouTubeError } from './errors.js';
4
+ import { withFileLock } from './file-lock.js';
5
+ export { LOCK_STALE_MS } from './file-lock.js';
6
+ /** Exclusive access to a brain while it is being built. See `withFileLock`. */
7
+ export async function withBuildLock(channelId, build) {
8
+ await ensureBrainDir(channelId);
9
+ return withFileLock(lockPath(channelId), build, (existing) => alreadyRunning(channelId, existing));
10
+ }
11
+ function alreadyRunning(channelId, existing) {
12
+ const owner = existing === undefined ? '' : `Started at ${existing.startedAt} by process ${existing.pid}. `;
13
+ return new YouTubeError('INVALID_INPUT', `A build for ${channelId} is already running.`, {
14
+ nextStep: `${owner}Wait for it to finish, then call build_brain again to continue where it stopped.`,
15
+ });
16
+ }
17
+ //# sourceMappingURL=brain-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-lock.js","sourceRoot":"","sources":["../../src/utils/brain-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAmB,MAAM,gBAAgB,CAAC;AAE/D,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAI,SAAiB,EAAE,KAAuB;IAC/E,MAAM,cAAc,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,CAC3D,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,CACpC,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,SAAiB,EAAE,QAAgC;IACzE,MAAM,KAAK,GACT,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,QAAQ,CAAC,SAAS,eAAe,QAAQ,CAAC,GAAG,IAAI,CAAC;IAEhG,OAAO,IAAI,YAAY,CAAC,eAAe,EAAE,eAAe,SAAS,sBAAsB,EAAE;QACvF,QAAQ,EAAE,GAAG,KAAK,kFAAkF;KACrG,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { BrainManifest } from '../brain-schemas.js';
2
+ /**
3
+ * Find a brain from whatever the caller called the channel.
4
+ *
5
+ * Every brain tool except `build_brain` answers from disk, so none of them
6
+ * should need the network to work out which brain is meant. The matching
7
+ * itself is shared with every other channel-keyed store; what is specific here
8
+ * is what to say when there is no brain, or more than one.
9
+ */
10
+ export declare function resolveBrain(channel: string): Promise<BrainManifest>;
11
+ //# sourceMappingURL=brain-lookup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-lookup.d.ts","sourceRoot":"","sources":["../../src/utils/brain-lookup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAKzD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAU1E"}
@@ -0,0 +1,36 @@
1
+ import { listManifests } from './brain-storage.js';
2
+ import { findByChannel } from './channel-lookup.js';
3
+ import { YouTubeError } from './errors.js';
4
+ /**
5
+ * Find a brain from whatever the caller called the channel.
6
+ *
7
+ * Every brain tool except `build_brain` answers from disk, so none of them
8
+ * should need the network to work out which brain is meant. The matching
9
+ * itself is shared with every other channel-keyed store; what is specific here
10
+ * is what to say when there is no brain, or more than one.
11
+ */
12
+ export async function resolveBrain(channel) {
13
+ const manifests = await listManifests();
14
+ const matches = findByChannel(manifests, channel);
15
+ const [first, second] = matches;
16
+ if (first === undefined)
17
+ throw noBrain(channel, manifests);
18
+ if (second !== undefined)
19
+ throw ambiguous(channel, matches);
20
+ return first;
21
+ }
22
+ function noBrain(channel, manifests) {
23
+ const built = manifests.map((manifest) => manifest.channel.handle || manifest.channel.name);
24
+ return new YouTubeError('NOT_FOUND', `No brain has been built for "${channel}".`, {
25
+ nextStep: built.length === 0
26
+ ? 'Call build_brain with the channel URL or handle to build one.'
27
+ : `Call build_brain to build it. Brains that exist: ${built.join(', ')}.`,
28
+ });
29
+ }
30
+ function ambiguous(channel, matches) {
31
+ const names = matches.map((manifest) => manifest.channel.channelId);
32
+ return new YouTubeError('INVALID_INPUT', `"${channel}" matches more than one brain.`, {
33
+ nextStep: `Use the channel id instead: ${names.join(', ')}.`,
34
+ });
35
+ }
36
+ //# sourceMappingURL=brain-lookup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-lookup.js","sourceRoot":"","sources":["../../src/utils/brain-lookup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAe;IAChD,MAAM,SAAS,GAAG,MAAM,aAAa,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAElD,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;IAEhC,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC3D,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAE5D,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,OAAO,CAAC,OAAe,EAAE,SAA0B;IAC1D,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5F,OAAO,IAAI,YAAY,CAAC,WAAW,EAAE,gCAAgC,OAAO,IAAI,EAAE;QAChF,QAAQ,EACN,KAAK,CAAC,MAAM,KAAK,CAAC;YAChB,CAAC,CAAC,+DAA+D;YACjE,CAAC,CAAC,oDAAoD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;KAC9E,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,OAAe,EAAE,OAAwB;IAC1D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEpE,OAAO,IAAI,YAAY,CAAC,eAAe,EAAE,IAAI,OAAO,gCAAgC,EAAE;QACpF,QAAQ,EAAE,+BAA+B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;KAC7D,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Every file a brain is made of.
3
+ *
4
+ * Separate from the modules that read and write them so that the manifest layer
5
+ * and the index layer can each know where things live without importing each
6
+ * other.
7
+ *
8
+ * A brain is keyed on the channel id rather than the handle: handles get
9
+ * renamed, and a renamed handle would orphan the brain built under the old one.
10
+ * The id is also what reaches the filesystem, so it is validated here — once,
11
+ * at the only place that turns one into a path.
12
+ */
13
+ export declare function brainsDir(): string;
14
+ export declare function brainDir(channelId: string): string;
15
+ export declare function manifestPath(channelId: string): string;
16
+ export declare function chunksPath(channelId: string): string;
17
+ export declare function profilePath(channelId: string): string;
18
+ export declare function lockPath(channelId: string): string;
19
+ //# sourceMappingURL=brain-paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-paths.d.ts","sourceRoot":"","sources":["../../src/utils/brain-paths.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAElD"}
@@ -0,0 +1,34 @@
1
+ import { join } from 'node:path';
2
+ import { dataDir } from './paths.js';
3
+ import { assertChannelId } from './validate.js';
4
+ /**
5
+ * Every file a brain is made of.
6
+ *
7
+ * Separate from the modules that read and write them so that the manifest layer
8
+ * and the index layer can each know where things live without importing each
9
+ * other.
10
+ *
11
+ * A brain is keyed on the channel id rather than the handle: handles get
12
+ * renamed, and a renamed handle would orphan the brain built under the old one.
13
+ * The id is also what reaches the filesystem, so it is validated here — once,
14
+ * at the only place that turns one into a path.
15
+ */
16
+ export function brainsDir() {
17
+ return dataDir('brains');
18
+ }
19
+ export function brainDir(channelId) {
20
+ return join(brainsDir(), assertChannelId(channelId));
21
+ }
22
+ export function manifestPath(channelId) {
23
+ return join(brainDir(channelId), 'manifest.json');
24
+ }
25
+ export function chunksPath(channelId) {
26
+ return join(brainDir(channelId), 'chunks.json');
27
+ }
28
+ export function profilePath(channelId) {
29
+ return join(brainDir(channelId), 'profile.md');
30
+ }
31
+ export function lockPath(channelId) {
32
+ return join(brainDir(channelId), 'build.lock');
33
+ }
34
+ //# sourceMappingURL=brain-paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-paths.js","sourceRoot":"","sources":["../../src/utils/brain-paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD;;;;;;;;;;;GAWG;AAEH,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,SAAiB;IACxC,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,SAAiB;IAC3C,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,YAAY,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,SAAiB;IACxC,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,YAAY,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { BrainChunk } from '../brain-schemas.js';
2
+ /**
3
+ * Phrases a creator repeats across videos.
4
+ *
5
+ * This is the one statistic here that is not a plain count, so it is worth
6
+ * saying what it does and does not claim. A phrase appearing in several
7
+ * different videos is repeated; that is a fact about the corpus. Whether it is
8
+ * a catchphrase, a verbal tic or a segment title is a judgement, and belongs to
9
+ * whoever reads the list.
10
+ *
11
+ * Counting every 3-to-6-word window at once would hold millions of keys in
12
+ * memory. Instead each length is counted only where the length below it
13
+ * survived — a phrase cannot recur more often than its own prefix does — so all
14
+ * but the first pass work on a small candidate set.
15
+ *
16
+ * It is still the most expensive thing done to a corpus: 12.6s over 100,000
17
+ * passages, which is why a build runs it once at the end rather than at every
18
+ * checkpoint.
19
+ */
20
+ export declare const PHRASE_MIN_WORDS = 3;
21
+ export declare const PHRASE_MAX_WORDS = 6;
22
+ /** Repetition inside one video is a habit of that video, not of the channel. */
23
+ export declare const PHRASE_MIN_VIDEOS = 3;
24
+ export declare const PHRASE_LIMIT = 20;
25
+ export interface RecurringPhrase {
26
+ phrase: string;
27
+ videoCount: number;
28
+ occurrences: number;
29
+ }
30
+ export declare function findRecurringPhrases(chunks: BrainChunk[]): RecurringPhrase[];
31
+ //# sourceMappingURL=brain-phrases.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-phrases.d.ts","sourceRoot":"","sources":["../../src/utils/brain-phrases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,YAAY,KAAK,CAAC;AAE/B,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAaD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,eAAe,EAAE,CAuB5E"}
@@ -0,0 +1,116 @@
1
+ import { isStopWord } from './search-index.js';
2
+ /**
3
+ * Phrases a creator repeats across videos.
4
+ *
5
+ * This is the one statistic here that is not a plain count, so it is worth
6
+ * saying what it does and does not claim. A phrase appearing in several
7
+ * different videos is repeated; that is a fact about the corpus. Whether it is
8
+ * a catchphrase, a verbal tic or a segment title is a judgement, and belongs to
9
+ * whoever reads the list.
10
+ *
11
+ * Counting every 3-to-6-word window at once would hold millions of keys in
12
+ * memory. Instead each length is counted only where the length below it
13
+ * survived — a phrase cannot recur more often than its own prefix does — so all
14
+ * but the first pass work on a small candidate set.
15
+ *
16
+ * It is still the most expensive thing done to a corpus: 12.6s over 100,000
17
+ * passages, which is why a build runs it once at the end rather than at every
18
+ * checkpoint.
19
+ */
20
+ export const PHRASE_MIN_WORDS = 3;
21
+ export const PHRASE_MAX_WORDS = 6;
22
+ /** Repetition inside one video is a habit of that video, not of the channel. */
23
+ export const PHRASE_MIN_VIDEOS = 3;
24
+ export const PHRASE_LIMIT = 20;
25
+ export function findRecurringPhrases(chunks) {
26
+ const corpus = wordsByVideo(chunks);
27
+ const kept = [];
28
+ let candidates = new Set();
29
+ for (let length = PHRASE_MIN_WORDS; length <= PHRASE_MAX_WORDS; length++) {
30
+ const tallies = countPhrases(corpus, length, length === PHRASE_MIN_WORDS ? undefined : candidates);
31
+ candidates = new Set();
32
+ for (const [phrase, tally] of tallies) {
33
+ if (tally.videoCount < PHRASE_MIN_VIDEOS)
34
+ continue;
35
+ candidates.add(phrase);
36
+ kept.push({ phrase, videoCount: tally.videoCount, occurrences: tally.occurrences });
37
+ }
38
+ if (candidates.size === 0)
39
+ break;
40
+ }
41
+ return rank(kept);
42
+ }
43
+ /**
44
+ * The corpus as one word list per video, in a stable order.
45
+ *
46
+ * Grouping is what lets distinct-video counts be kept as a single field per
47
+ * phrase rather than a set of video ids per phrase, which for a large channel
48
+ * is the difference between tens of megabytes and hundreds.
49
+ */
50
+ function wordsByVideo(chunks) {
51
+ const byVideo = new Map();
52
+ for (const chunk of chunks) {
53
+ const words = byVideo.get(chunk.videoId) ?? [];
54
+ words.push(...phraseWords(chunk.text));
55
+ byVideo.set(chunk.videoId, words);
56
+ }
57
+ return [...byVideo.values()];
58
+ }
59
+ /** Lowercased words, apostrophes kept because "don't" is one word. */
60
+ function phraseWords(text) {
61
+ return text
62
+ .toLowerCase()
63
+ .split(/[^\p{L}\p{N}']+/u)
64
+ .filter(Boolean);
65
+ }
66
+ function countPhrases(corpus, length, candidatePrefixes) {
67
+ const tallies = new Map();
68
+ for (const [videoIndex, words] of corpus.entries()) {
69
+ for (let start = 0; start + length <= words.length; start++) {
70
+ const window = words.slice(start, start + length);
71
+ // A longer phrase only recurs where its prefix already did. A phrase
72
+ // whose opening words are all filler is therefore never reached, which is
73
+ // the intended trade: it costs "and then i went on" to drop "and then i".
74
+ if (candidatePrefixes !== undefined) {
75
+ if (!candidatePrefixes.has(window.slice(0, length - 1).join(' ')))
76
+ continue;
77
+ }
78
+ else if (window.every(isStopWord)) {
79
+ continue;
80
+ }
81
+ const phrase = window.join(' ');
82
+ const tally = tallies.get(phrase);
83
+ if (tally === undefined) {
84
+ tallies.set(phrase, { occurrences: 1, videoCount: 1, lastVideoIndex: videoIndex });
85
+ continue;
86
+ }
87
+ tally.occurrences++;
88
+ if (tally.lastVideoIndex !== videoIndex) {
89
+ tally.videoCount++;
90
+ tally.lastVideoIndex = videoIndex;
91
+ }
92
+ }
93
+ }
94
+ return tallies;
95
+ }
96
+ /**
97
+ * Drop the truncations, then order by reach.
98
+ *
99
+ * A phrase contained in a longer kept phrase that is just as widespread says
100
+ * nothing the longer one does not — otherwise every long catchphrase drags in
101
+ * three truncations of itself. Subsumption has to consider the longest first,
102
+ * which is not the order worth reporting, so the two happen in separate passes.
103
+ */
104
+ function rank(phrases) {
105
+ const longestFirst = [...phrases].sort((a, b) => b.phrase.split(' ').length - a.phrase.split(' ').length || b.videoCount - a.videoCount);
106
+ const distinct = [];
107
+ for (const candidate of longestFirst) {
108
+ const subsumed = distinct.some((existing) => existing.phrase.includes(candidate.phrase) && existing.videoCount >= candidate.videoCount);
109
+ if (!subsumed)
110
+ distinct.push(candidate);
111
+ }
112
+ return distinct
113
+ .sort((a, b) => b.videoCount - a.videoCount || b.occurrences - a.occurrences)
114
+ .slice(0, PHRASE_LIMIT);
115
+ }
116
+ //# sourceMappingURL=brain-phrases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-phrases.js","sourceRoot":"","sources":["../../src/utils/brain-phrases.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { BrainChunk, BrainStats, BrainVideoState } from '../brain-schemas.js';
2
+ /**
3
+ * What the server can say about a channel without inferring anything.
4
+ *
5
+ * Everything here is counted or measured from the corpus. Deliberately absent:
6
+ * named entities, because auto-generated captions arrive lowercase and
7
+ * unpunctuated so capitalisation heuristics produce noise; and distinctive
8
+ * vocabulary by TF-IDF, because there is no honest background corpus to compare
9
+ * a channel against and inventing one would produce confident nonsense.
10
+ *
11
+ * Anything the numbers cannot support belongs in the profile, which a model
12
+ * writes from passages it can cite.
13
+ */
14
+ export interface StatsOptions {
15
+ /**
16
+ * Whether to run the phrase pass, which reads every word of the corpus. It is
17
+ * measured in seconds at the sizes a brain is allowed to reach — 12.6s over
18
+ * 100,000 passages on the machine this was written on — so a build that
19
+ * checkpoints every ten videos must not pay for it fifty times over. Omitted
20
+ * rather than guessed at when it has not been run.
21
+ */
22
+ phrases: boolean;
23
+ }
24
+ export declare function computeStats(all: BrainVideoState[], chunks: BrainChunk[], options: StatsOptions): BrainStats;
25
+ //# sourceMappingURL=brain-stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-stats.d.ts","sourceRoot":"","sources":["../../src/utils/brain-stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGnF;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,eAAe,EAAE,EACtB,MAAM,EAAE,UAAU,EAAE,EACpB,OAAO,EAAE,YAAY,GACpB,UAAU,CAyBZ"}
@@ -0,0 +1,64 @@
1
+ import { findRecurringPhrases } from './brain-phrases.js';
2
+ export function computeStats(all, chunks, options) {
3
+ // A video the filters ruled out is not part of this brain, so it does not get
4
+ // to move the brain's numbers — its date must not stretch the upload range,
5
+ // and its length must not enter the speaking rate. It is reported as its own
6
+ // count instead, because "we chose not to read 40 videos" is worth knowing.
7
+ const videos = all.filter((video) => video.state !== 'excluded');
8
+ const uploadDates = videos
9
+ .map((video) => video.uploadDate)
10
+ .filter(Boolean)
11
+ .sort();
12
+ return {
13
+ videoCount: videos.length,
14
+ excludedCount: all.length - videos.length,
15
+ indexedCount: countState(videos, 'indexed'),
16
+ noCaptionsCount: countState(videos, 'no-captions'),
17
+ failedCount: countState(videos, 'failed'),
18
+ pendingCount: countState(videos, 'pending'),
19
+ chunkCount: chunks.length,
20
+ totalWords: videos.reduce((total, video) => total + video.wordCount, 0),
21
+ medianWordsPerMinute: medianWordsPerMinute(videos),
22
+ ...firstAndLast(uploadDates),
23
+ uploadsPerMonth: uploadsPerMonth(uploadDates),
24
+ ...(options.phrases ? { recurringPhrases: findRecurringPhrases(chunks) } : {}),
25
+ };
26
+ }
27
+ function countState(videos, state) {
28
+ return videos.filter((video) => video.state === state).length;
29
+ }
30
+ /**
31
+ * The median rather than the mean: one mis-timed transcript, or a ten-hour
32
+ * ambience upload with forty words in it, would drag an average anywhere.
33
+ */
34
+ function medianWordsPerMinute(videos) {
35
+ const rates = videos
36
+ .filter((video) => video.durationSeconds > 0 && video.wordCount > 0)
37
+ .map((video) => (video.wordCount / video.durationSeconds) * 60)
38
+ .sort((a, b) => a - b);
39
+ if (rates.length === 0)
40
+ return 0;
41
+ const middle = Math.floor(rates.length / 2);
42
+ const median = rates.length % 2 === 0
43
+ ? ((rates[middle - 1] ?? 0) + (rates[middle] ?? 0)) / 2
44
+ : (rates[middle] ?? 0);
45
+ return Math.round(median);
46
+ }
47
+ function firstAndLast(sortedDates) {
48
+ const first = sortedDates[0];
49
+ const last = sortedDates[sortedDates.length - 1];
50
+ return {
51
+ ...(first === undefined ? {} : { firstUpload: first }),
52
+ ...(last === undefined ? {} : { lastUpload: last }),
53
+ };
54
+ }
55
+ /** Months with no uploads are absent rather than zero: this is a tally, not a series. */
56
+ function uploadsPerMonth(sortedDates) {
57
+ const months = new Map();
58
+ for (const date of sortedDates) {
59
+ const month = date.slice(0, 7);
60
+ months.set(month, (months.get(month) ?? 0) + 1);
61
+ }
62
+ return [...months.entries()].map(([month, videos]) => ({ month, videos }));
63
+ }
64
+ //# sourceMappingURL=brain-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-stats.js","sourceRoot":"","sources":["../../src/utils/brain-stats.ts"],"names":[],"mappings":"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"}