@raviolelabs/engram-mcp 0.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 (662) hide show
  1. package/CLAUDE.md +232 -0
  2. package/LICENSE +21 -0
  3. package/README.md +222 -0
  4. package/SKILL.md +299 -0
  5. package/dist/cloud/auth.d.ts +29 -0
  6. package/dist/cloud/auth.d.ts.map +1 -0
  7. package/dist/cloud/auth.js +132 -0
  8. package/dist/cloud/auth.js.map +1 -0
  9. package/dist/cloud/bridge-client.d.ts +10 -0
  10. package/dist/cloud/bridge-client.d.ts.map +1 -0
  11. package/dist/cloud/bridge-client.js +167 -0
  12. package/dist/cloud/bridge-client.js.map +1 -0
  13. package/dist/cloud/crypto.d.ts +42 -0
  14. package/dist/cloud/crypto.d.ts.map +1 -0
  15. package/dist/cloud/crypto.js +146 -0
  16. package/dist/cloud/crypto.js.map +1 -0
  17. package/dist/cloud/endpoints.d.ts +26 -0
  18. package/dist/cloud/endpoints.d.ts.map +1 -0
  19. package/dist/cloud/endpoints.js +26 -0
  20. package/dist/cloud/endpoints.js.map +1 -0
  21. package/dist/cloud/pairing.d.ts +30 -0
  22. package/dist/cloud/pairing.d.ts.map +1 -0
  23. package/dist/cloud/pairing.js +157 -0
  24. package/dist/cloud/pairing.js.map +1 -0
  25. package/dist/cloud/transit-poller.d.ts +35 -0
  26. package/dist/cloud/transit-poller.d.ts.map +1 -0
  27. package/dist/cloud/transit-poller.js +281 -0
  28. package/dist/cloud/transit-poller.js.map +1 -0
  29. package/dist/config/index.d.ts +3 -0
  30. package/dist/config/index.d.ts.map +1 -0
  31. package/dist/config/index.js +24 -0
  32. package/dist/config/index.js.map +1 -0
  33. package/dist/config/schema.d.ts +466 -0
  34. package/dist/config/schema.d.ts.map +1 -0
  35. package/dist/config/schema.js +171 -0
  36. package/dist/config/schema.js.map +1 -0
  37. package/dist/core/db/index.d.ts +7 -0
  38. package/dist/core/db/index.d.ts.map +1 -0
  39. package/dist/core/db/index.js +273 -0
  40. package/dist/core/db/index.js.map +1 -0
  41. package/dist/core/logger.d.ts +19 -0
  42. package/dist/core/logger.d.ts.map +1 -0
  43. package/dist/core/logger.js +223 -0
  44. package/dist/core/logger.js.map +1 -0
  45. package/dist/core/server/http.d.ts +15 -0
  46. package/dist/core/server/http.d.ts.map +1 -0
  47. package/dist/core/server/http.js +76 -0
  48. package/dist/core/server/http.js.map +1 -0
  49. package/dist/core/server/instructions.d.ts +2 -0
  50. package/dist/core/server/instructions.d.ts.map +1 -0
  51. package/dist/core/server/instructions.js +36 -0
  52. package/dist/core/server/instructions.js.map +1 -0
  53. package/dist/core/server/mcp-handler.d.ts +39 -0
  54. package/dist/core/server/mcp-handler.d.ts.map +1 -0
  55. package/dist/core/server/mcp-handler.js +204 -0
  56. package/dist/core/server/mcp-handler.js.map +1 -0
  57. package/dist/core/server/mcp-http.d.ts +4 -0
  58. package/dist/core/server/mcp-http.d.ts.map +1 -0
  59. package/dist/core/server/mcp-http.js +56 -0
  60. package/dist/core/server/mcp-http.js.map +1 -0
  61. package/dist/core/server/tool-router.d.ts +9 -0
  62. package/dist/core/server/tool-router.d.ts.map +1 -0
  63. package/dist/core/server/tool-router.js +25 -0
  64. package/dist/core/server/tool-router.js.map +1 -0
  65. package/dist/core/server/websocket.d.ts +4 -0
  66. package/dist/core/server/websocket.d.ts.map +1 -0
  67. package/dist/core/server/websocket.js +25 -0
  68. package/dist/core/server/websocket.js.map +1 -0
  69. package/dist/db/index.d.ts +2 -0
  70. package/dist/db/index.d.ts.map +1 -0
  71. package/dist/db/index.js +3 -0
  72. package/dist/db/index.js.map +1 -0
  73. package/dist/embeddings/index.d.ts +24 -0
  74. package/dist/embeddings/index.d.ts.map +1 -0
  75. package/dist/embeddings/index.js +86 -0
  76. package/dist/embeddings/index.js.map +1 -0
  77. package/dist/embeddings/providers/engram.d.ts +7 -0
  78. package/dist/embeddings/providers/engram.d.ts.map +1 -0
  79. package/dist/embeddings/providers/engram.js +67 -0
  80. package/dist/embeddings/providers/engram.js.map +1 -0
  81. package/dist/embeddings/providers/ollama.d.ts +3 -0
  82. package/dist/embeddings/providers/ollama.d.ts.map +1 -0
  83. package/dist/embeddings/providers/ollama.js +9 -0
  84. package/dist/embeddings/providers/ollama.js.map +1 -0
  85. package/dist/embeddings/providers/openai-compat.d.ts +7 -0
  86. package/dist/embeddings/providers/openai-compat.d.ts.map +1 -0
  87. package/dist/embeddings/providers/openai-compat.js +27 -0
  88. package/dist/embeddings/providers/openai-compat.js.map +1 -0
  89. package/dist/embeddings/providers/openai.d.ts +3 -0
  90. package/dist/embeddings/providers/openai.d.ts.map +1 -0
  91. package/dist/embeddings/providers/openai.js +12 -0
  92. package/dist/embeddings/providers/openai.js.map +1 -0
  93. package/dist/embeddings/providers/voyage.d.ts +3 -0
  94. package/dist/embeddings/providers/voyage.d.ts.map +1 -0
  95. package/dist/embeddings/providers/voyage.js +12 -0
  96. package/dist/embeddings/providers/voyage.js.map +1 -0
  97. package/dist/index.d.ts +1 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +3 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/ingest/jobs.d.ts +29 -0
  102. package/dist/ingest/jobs.d.ts.map +1 -0
  103. package/dist/ingest/jobs.js +131 -0
  104. package/dist/ingest/jobs.js.map +1 -0
  105. package/dist/logger.d.ts +2 -0
  106. package/dist/logger.d.ts.map +1 -0
  107. package/dist/logger.js +3 -0
  108. package/dist/logger.js.map +1 -0
  109. package/dist/mcp-server/server.d.ts +2 -0
  110. package/dist/mcp-server/server.d.ts.map +1 -0
  111. package/dist/mcp-server/server.js +3 -0
  112. package/dist/mcp-server/server.js.map +1 -0
  113. package/dist/mcp-server/tests/mcp-e2e.test.d.ts +2 -0
  114. package/dist/mcp-server/tests/mcp-e2e.test.d.ts.map +1 -0
  115. package/dist/mcp-server/tests/mcp-e2e.test.js +157 -0
  116. package/dist/mcp-server/tests/mcp-e2e.test.js.map +1 -0
  117. package/dist/mcp-server/tool-router.d.ts +2 -0
  118. package/dist/mcp-server/tool-router.d.ts.map +1 -0
  119. package/dist/mcp-server/tool-router.js +3 -0
  120. package/dist/mcp-server/tool-router.js.map +1 -0
  121. package/dist/memory/admin/tools.d.ts +6 -0
  122. package/dist/memory/admin/tools.d.ts.map +1 -0
  123. package/dist/memory/admin/tools.js +134 -0
  124. package/dist/memory/admin/tools.js.map +1 -0
  125. package/dist/memory/core/chunker.d.ts +6 -0
  126. package/dist/memory/core/chunker.d.ts.map +1 -0
  127. package/dist/memory/core/chunker.js +49 -0
  128. package/dist/memory/core/chunker.js.map +1 -0
  129. package/dist/memory/core/module-interface.d.ts +23 -0
  130. package/dist/memory/core/module-interface.d.ts.map +1 -0
  131. package/dist/memory/core/module-interface.js +2 -0
  132. package/dist/memory/core/module-interface.js.map +1 -0
  133. package/dist/memory/core/module-registry.d.ts +14 -0
  134. package/dist/memory/core/module-registry.d.ts.map +1 -0
  135. package/dist/memory/core/module-registry.js +45 -0
  136. package/dist/memory/core/module-registry.js.map +1 -0
  137. package/dist/memory/core/property-extractor.d.ts +6 -0
  138. package/dist/memory/core/property-extractor.d.ts.map +1 -0
  139. package/dist/memory/core/property-extractor.js +90 -0
  140. package/dist/memory/core/property-extractor.js.map +1 -0
  141. package/dist/memory/core/reindex.d.ts +11 -0
  142. package/dist/memory/core/reindex.d.ts.map +1 -0
  143. package/dist/memory/core/reindex.js +55 -0
  144. package/dist/memory/core/reindex.js.map +1 -0
  145. package/dist/memory/core/source-registry.d.ts +42 -0
  146. package/dist/memory/core/source-registry.d.ts.map +1 -0
  147. package/dist/memory/core/source-registry.js +86 -0
  148. package/dist/memory/core/source-registry.js.map +1 -0
  149. package/dist/memory/core/store.d.ts +40 -0
  150. package/dist/memory/core/store.d.ts.map +1 -0
  151. package/dist/memory/core/store.js +257 -0
  152. package/dist/memory/core/store.js.map +1 -0
  153. package/dist/memory/core/wikilinks.d.ts +13 -0
  154. package/dist/memory/core/wikilinks.d.ts.map +1 -0
  155. package/dist/memory/core/wikilinks.js +25 -0
  156. package/dist/memory/core/wikilinks.js.map +1 -0
  157. package/dist/memory/modules/_custom/generic-module.d.ts +7 -0
  158. package/dist/memory/modules/_custom/generic-module.d.ts.map +1 -0
  159. package/dist/memory/modules/_custom/generic-module.js +108 -0
  160. package/dist/memory/modules/_custom/generic-module.js.map +1 -0
  161. package/dist/memory/modules/_custom/persistence.d.ts +15 -0
  162. package/dist/memory/modules/_custom/persistence.d.ts.map +1 -0
  163. package/dist/memory/modules/_custom/persistence.js +47 -0
  164. package/dist/memory/modules/_custom/persistence.js.map +1 -0
  165. package/dist/memory/modules/_custom/tests/custom-types.test.d.ts +2 -0
  166. package/dist/memory/modules/_custom/tests/custom-types.test.d.ts.map +1 -0
  167. package/dist/memory/modules/_custom/tests/custom-types.test.js +89 -0
  168. package/dist/memory/modules/_custom/tests/custom-types.test.js.map +1 -0
  169. package/dist/memory/modules/_custom/tools.d.ts +7 -0
  170. package/dist/memory/modules/_custom/tools.d.ts.map +1 -0
  171. package/dist/memory/modules/_custom/tools.js +72 -0
  172. package/dist/memory/modules/_custom/tools.js.map +1 -0
  173. package/dist/memory/modules/audio/ingest.d.ts +9 -0
  174. package/dist/memory/modules/audio/ingest.d.ts.map +1 -0
  175. package/dist/memory/modules/audio/ingest.js +32 -0
  176. package/dist/memory/modules/audio/ingest.js.map +1 -0
  177. package/dist/memory/modules/audio/module.d.ts +6 -0
  178. package/dist/memory/modules/audio/module.d.ts.map +1 -0
  179. package/dist/memory/modules/audio/module.js +18 -0
  180. package/dist/memory/modules/audio/module.js.map +1 -0
  181. package/dist/memory/modules/audio/tests/audio.test.d.ts +2 -0
  182. package/dist/memory/modules/audio/tests/audio.test.d.ts.map +1 -0
  183. package/dist/memory/modules/audio/tests/audio.test.js +57 -0
  184. package/dist/memory/modules/audio/tests/audio.test.js.map +1 -0
  185. package/dist/memory/modules/audio/tests/transcriber.test.d.ts +2 -0
  186. package/dist/memory/modules/audio/tests/transcriber.test.d.ts.map +1 -0
  187. package/dist/memory/modules/audio/tests/transcriber.test.js +27 -0
  188. package/dist/memory/modules/audio/tests/transcriber.test.js.map +1 -0
  189. package/dist/memory/modules/audio/tools.d.ts +5 -0
  190. package/dist/memory/modules/audio/tools.d.ts.map +1 -0
  191. package/dist/memory/modules/audio/tools.js +60 -0
  192. package/dist/memory/modules/audio/tools.js.map +1 -0
  193. package/dist/memory/modules/audio/transcriber.d.ts +15 -0
  194. package/dist/memory/modules/audio/transcriber.d.ts.map +1 -0
  195. package/dist/memory/modules/audio/transcriber.js +177 -0
  196. package/dist/memory/modules/audio/transcriber.js.map +1 -0
  197. package/dist/memory/modules/conversations/ingest.d.ts +10 -0
  198. package/dist/memory/modules/conversations/ingest.d.ts.map +1 -0
  199. package/dist/memory/modules/conversations/ingest.js +38 -0
  200. package/dist/memory/modules/conversations/ingest.js.map +1 -0
  201. package/dist/memory/modules/conversations/module.d.ts +6 -0
  202. package/dist/memory/modules/conversations/module.d.ts.map +1 -0
  203. package/dist/memory/modules/conversations/module.js +43 -0
  204. package/dist/memory/modules/conversations/module.js.map +1 -0
  205. package/dist/memory/modules/conversations/tests/conversations.test.d.ts +2 -0
  206. package/dist/memory/modules/conversations/tests/conversations.test.d.ts.map +1 -0
  207. package/dist/memory/modules/conversations/tests/conversations.test.js +70 -0
  208. package/dist/memory/modules/conversations/tests/conversations.test.js.map +1 -0
  209. package/dist/memory/modules/conversations/tools.d.ts +5 -0
  210. package/dist/memory/modules/conversations/tools.d.ts.map +1 -0
  211. package/dist/memory/modules/conversations/tools.js +75 -0
  212. package/dist/memory/modules/conversations/tools.js.map +1 -0
  213. package/dist/memory/modules/drive/connector.d.ts +19 -0
  214. package/dist/memory/modules/drive/connector.d.ts.map +1 -0
  215. package/dist/memory/modules/drive/connector.js +52 -0
  216. package/dist/memory/modules/drive/connector.js.map +1 -0
  217. package/dist/memory/modules/drive/ingest.d.ts +9 -0
  218. package/dist/memory/modules/drive/ingest.d.ts.map +1 -0
  219. package/dist/memory/modules/drive/ingest.js +27 -0
  220. package/dist/memory/modules/drive/ingest.js.map +1 -0
  221. package/dist/memory/modules/drive/module.d.ts +6 -0
  222. package/dist/memory/modules/drive/module.d.ts.map +1 -0
  223. package/dist/memory/modules/drive/module.js +31 -0
  224. package/dist/memory/modules/drive/module.js.map +1 -0
  225. package/dist/memory/modules/drive/oauth.d.ts +14 -0
  226. package/dist/memory/modules/drive/oauth.d.ts.map +1 -0
  227. package/dist/memory/modules/drive/oauth.js +130 -0
  228. package/dist/memory/modules/drive/oauth.js.map +1 -0
  229. package/dist/memory/modules/drive/tests/drive.test.d.ts +2 -0
  230. package/dist/memory/modules/drive/tests/drive.test.d.ts.map +1 -0
  231. package/dist/memory/modules/drive/tests/drive.test.js +66 -0
  232. package/dist/memory/modules/drive/tests/drive.test.js.map +1 -0
  233. package/dist/memory/modules/drive/tools.d.ts +5 -0
  234. package/dist/memory/modules/drive/tools.d.ts.map +1 -0
  235. package/dist/memory/modules/drive/tools.js +131 -0
  236. package/dist/memory/modules/drive/tools.js.map +1 -0
  237. package/dist/memory/modules/drive/watcher.d.ts +5 -0
  238. package/dist/memory/modules/drive/watcher.d.ts.map +1 -0
  239. package/dist/memory/modules/drive/watcher.js +46 -0
  240. package/dist/memory/modules/drive/watcher.js.map +1 -0
  241. package/dist/memory/modules/notes/ingest.d.ts +3 -0
  242. package/dist/memory/modules/notes/ingest.d.ts.map +1 -0
  243. package/dist/memory/modules/notes/ingest.js +30 -0
  244. package/dist/memory/modules/notes/ingest.js.map +1 -0
  245. package/dist/memory/modules/notes/module.d.ts +5 -0
  246. package/dist/memory/modules/notes/module.d.ts.map +1 -0
  247. package/dist/memory/modules/notes/module.js +28 -0
  248. package/dist/memory/modules/notes/module.js.map +1 -0
  249. package/dist/memory/modules/notes/tests/notes.test.d.ts +2 -0
  250. package/dist/memory/modules/notes/tests/notes.test.d.ts.map +1 -0
  251. package/dist/memory/modules/notes/tests/notes.test.js +59 -0
  252. package/dist/memory/modules/notes/tests/notes.test.js.map +1 -0
  253. package/dist/memory/modules/notes/tools.d.ts +5 -0
  254. package/dist/memory/modules/notes/tools.d.ts.map +1 -0
  255. package/dist/memory/modules/notes/tools.js +69 -0
  256. package/dist/memory/modules/notes/tools.js.map +1 -0
  257. package/dist/memory/modules/notion/connector.d.ts +10 -0
  258. package/dist/memory/modules/notion/connector.d.ts.map +1 -0
  259. package/dist/memory/modules/notion/connector.js +112 -0
  260. package/dist/memory/modules/notion/connector.js.map +1 -0
  261. package/dist/memory/modules/notion/ingest.d.ts +9 -0
  262. package/dist/memory/modules/notion/ingest.d.ts.map +1 -0
  263. package/dist/memory/modules/notion/ingest.js +24 -0
  264. package/dist/memory/modules/notion/ingest.js.map +1 -0
  265. package/dist/memory/modules/notion/module.d.ts +6 -0
  266. package/dist/memory/modules/notion/module.d.ts.map +1 -0
  267. package/dist/memory/modules/notion/module.js +31 -0
  268. package/dist/memory/modules/notion/module.js.map +1 -0
  269. package/dist/memory/modules/notion/oauth.d.ts +19 -0
  270. package/dist/memory/modules/notion/oauth.d.ts.map +1 -0
  271. package/dist/memory/modules/notion/oauth.js +117 -0
  272. package/dist/memory/modules/notion/oauth.js.map +1 -0
  273. package/dist/memory/modules/notion/tests/notion.test.d.ts +2 -0
  274. package/dist/memory/modules/notion/tests/notion.test.d.ts.map +1 -0
  275. package/dist/memory/modules/notion/tests/notion.test.js +53 -0
  276. package/dist/memory/modules/notion/tests/notion.test.js.map +1 -0
  277. package/dist/memory/modules/notion/tools.d.ts +5 -0
  278. package/dist/memory/modules/notion/tools.d.ts.map +1 -0
  279. package/dist/memory/modules/notion/tools.js +116 -0
  280. package/dist/memory/modules/notion/tools.js.map +1 -0
  281. package/dist/memory/modules/notion/watcher.d.ts +5 -0
  282. package/dist/memory/modules/notion/watcher.d.ts.map +1 -0
  283. package/dist/memory/modules/notion/watcher.js +41 -0
  284. package/dist/memory/modules/notion/watcher.js.map +1 -0
  285. package/dist/memory/modules/obsidian/ingest.d.ts +9 -0
  286. package/dist/memory/modules/obsidian/ingest.d.ts.map +1 -0
  287. package/dist/memory/modules/obsidian/ingest.js +80 -0
  288. package/dist/memory/modules/obsidian/ingest.js.map +1 -0
  289. package/dist/memory/modules/obsidian/module.d.ts +6 -0
  290. package/dist/memory/modules/obsidian/module.d.ts.map +1 -0
  291. package/dist/memory/modules/obsidian/module.js +31 -0
  292. package/dist/memory/modules/obsidian/module.js.map +1 -0
  293. package/dist/memory/modules/obsidian/tests/obsidian.test.d.ts +2 -0
  294. package/dist/memory/modules/obsidian/tests/obsidian.test.d.ts.map +1 -0
  295. package/dist/memory/modules/obsidian/tests/obsidian.test.js +65 -0
  296. package/dist/memory/modules/obsidian/tests/obsidian.test.js.map +1 -0
  297. package/dist/memory/modules/obsidian/tests/vault-reader.test.d.ts +2 -0
  298. package/dist/memory/modules/obsidian/tests/vault-reader.test.d.ts.map +1 -0
  299. package/dist/memory/modules/obsidian/tests/vault-reader.test.js +37 -0
  300. package/dist/memory/modules/obsidian/tests/vault-reader.test.js.map +1 -0
  301. package/dist/memory/modules/obsidian/tools.d.ts +5 -0
  302. package/dist/memory/modules/obsidian/tools.d.ts.map +1 -0
  303. package/dist/memory/modules/obsidian/tools.js +101 -0
  304. package/dist/memory/modules/obsidian/tools.js.map +1 -0
  305. package/dist/memory/modules/obsidian/vault-reader.d.ts +8 -0
  306. package/dist/memory/modules/obsidian/vault-reader.d.ts.map +1 -0
  307. package/dist/memory/modules/obsidian/vault-reader.js +82 -0
  308. package/dist/memory/modules/obsidian/vault-reader.js.map +1 -0
  309. package/dist/memory/modules/obsidian/watcher.d.ts +5 -0
  310. package/dist/memory/modules/obsidian/watcher.d.ts.map +1 -0
  311. package/dist/memory/modules/obsidian/watcher.js +83 -0
  312. package/dist/memory/modules/obsidian/watcher.js.map +1 -0
  313. package/dist/memory/modules/youtube/ingest.d.ts +20 -0
  314. package/dist/memory/modules/youtube/ingest.d.ts.map +1 -0
  315. package/dist/memory/modules/youtube/ingest.js +49 -0
  316. package/dist/memory/modules/youtube/ingest.js.map +1 -0
  317. package/dist/memory/modules/youtube/module.d.ts +11 -0
  318. package/dist/memory/modules/youtube/module.d.ts.map +1 -0
  319. package/dist/memory/modules/youtube/module.js +26 -0
  320. package/dist/memory/modules/youtube/module.js.map +1 -0
  321. package/dist/memory/modules/youtube/tests/channel.test.d.ts +2 -0
  322. package/dist/memory/modules/youtube/tests/channel.test.d.ts.map +1 -0
  323. package/dist/memory/modules/youtube/tests/channel.test.js +61 -0
  324. package/dist/memory/modules/youtube/tests/channel.test.js.map +1 -0
  325. package/dist/memory/modules/youtube/tests/transcript-fetcher.test.d.ts +2 -0
  326. package/dist/memory/modules/youtube/tests/transcript-fetcher.test.d.ts.map +1 -0
  327. package/dist/memory/modules/youtube/tests/transcript-fetcher.test.js +23 -0
  328. package/dist/memory/modules/youtube/tests/transcript-fetcher.test.js.map +1 -0
  329. package/dist/memory/modules/youtube/tests/youtube.test.d.ts +2 -0
  330. package/dist/memory/modules/youtube/tests/youtube.test.d.ts.map +1 -0
  331. package/dist/memory/modules/youtube/tests/youtube.test.js +52 -0
  332. package/dist/memory/modules/youtube/tests/youtube.test.js.map +1 -0
  333. package/dist/memory/modules/youtube/tools.d.ts +5 -0
  334. package/dist/memory/modules/youtube/tools.d.ts.map +1 -0
  335. package/dist/memory/modules/youtube/tools.js +182 -0
  336. package/dist/memory/modules/youtube/tools.js.map +1 -0
  337. package/dist/memory/modules/youtube/transcript-fetcher.d.ts +17 -0
  338. package/dist/memory/modules/youtube/transcript-fetcher.d.ts.map +1 -0
  339. package/dist/memory/modules/youtube/transcript-fetcher.js +178 -0
  340. package/dist/memory/modules/youtube/transcript-fetcher.js.map +1 -0
  341. package/dist/memory/modules/youtube/watcher.d.ts +30 -0
  342. package/dist/memory/modules/youtube/watcher.d.ts.map +1 -0
  343. package/dist/memory/modules/youtube/watcher.js +198 -0
  344. package/dist/memory/modules/youtube/watcher.js.map +1 -0
  345. package/dist/memory/public/tools.d.ts +5 -0
  346. package/dist/memory/public/tools.d.ts.map +1 -0
  347. package/dist/memory/public/tools.js +1761 -0
  348. package/dist/memory/public/tools.js.map +1 -0
  349. package/dist/private/algorithms/chunker-semantic.d.ts +3 -0
  350. package/dist/private/algorithms/chunker-semantic.d.ts.map +1 -0
  351. package/dist/private/algorithms/chunker-semantic.js +70 -0
  352. package/dist/private/algorithms/chunker-semantic.js.map +1 -0
  353. package/dist/private/algorithms/find-related-smart.d.ts +4 -0
  354. package/dist/private/algorithms/find-related-smart.d.ts.map +1 -0
  355. package/dist/private/algorithms/find-related-smart.js +52 -0
  356. package/dist/private/algorithms/find-related-smart.js.map +1 -0
  357. package/dist/private/algorithms/graph-semantic-edges.d.ts +4 -0
  358. package/dist/private/algorithms/graph-semantic-edges.d.ts.map +1 -0
  359. package/dist/private/algorithms/graph-semantic-edges.js +38 -0
  360. package/dist/private/algorithms/graph-semantic-edges.js.map +1 -0
  361. package/dist/private/algorithms/search-all-smart.d.ts +9 -0
  362. package/dist/private/algorithms/search-all-smart.d.ts.map +1 -0
  363. package/dist/private/algorithms/search-all-smart.js +62 -0
  364. package/dist/private/algorithms/search-all-smart.js.map +1 -0
  365. package/dist/private/index.d.ts +7 -0
  366. package/dist/private/index.d.ts.map +1 -0
  367. package/dist/private/index.js +39 -0
  368. package/dist/private/index.js.map +1 -0
  369. package/dist/private/prompts/extraction-system.d.ts +2 -0
  370. package/dist/private/prompts/extraction-system.d.ts.map +1 -0
  371. package/dist/private/prompts/extraction-system.js +15 -0
  372. package/dist/private/prompts/extraction-system.js.map +1 -0
  373. package/dist/private/prompts/suggest-properties.d.ts +2 -0
  374. package/dist/private/prompts/suggest-properties.d.ts.map +1 -0
  375. package/dist/private/prompts/suggest-properties.js +18 -0
  376. package/dist/private/prompts/suggest-properties.js.map +1 -0
  377. package/dist/private/tests/find-related-smart.test.d.ts +2 -0
  378. package/dist/private/tests/find-related-smart.test.d.ts.map +1 -0
  379. package/dist/private/tests/find-related-smart.test.js +86 -0
  380. package/dist/private/tests/find-related-smart.test.js.map +1 -0
  381. package/dist/private/tests/property-extractor-smart.test.d.ts +2 -0
  382. package/dist/private/tests/property-extractor-smart.test.d.ts.map +1 -0
  383. package/dist/private/tests/property-extractor-smart.test.js +26 -0
  384. package/dist/private/tests/property-extractor-smart.test.js.map +1 -0
  385. package/dist/scripts/install-ollama.d.ts +3 -0
  386. package/dist/scripts/install-ollama.d.ts.map +1 -0
  387. package/dist/scripts/install-ollama.js +78 -0
  388. package/dist/scripts/install-ollama.js.map +1 -0
  389. package/dist/scripts/install.d.ts +3 -0
  390. package/dist/scripts/install.d.ts.map +1 -0
  391. package/dist/scripts/install.js +191 -0
  392. package/dist/scripts/install.js.map +1 -0
  393. package/dist/scripts/pair.d.ts +3 -0
  394. package/dist/scripts/pair.d.ts.map +1 -0
  395. package/dist/scripts/pair.js +78 -0
  396. package/dist/scripts/pair.js.map +1 -0
  397. package/dist/scripts/rebuild.d.ts +20 -0
  398. package/dist/scripts/rebuild.d.ts.map +1 -0
  399. package/dist/scripts/rebuild.js +171 -0
  400. package/dist/scripts/rebuild.js.map +1 -0
  401. package/dist/scripts/reindex.d.ts +3 -0
  402. package/dist/scripts/reindex.d.ts.map +1 -0
  403. package/dist/scripts/reindex.js +23 -0
  404. package/dist/scripts/reindex.js.map +1 -0
  405. package/dist/scripts/serve.d.ts +3 -0
  406. package/dist/scripts/serve.d.ts.map +1 -0
  407. package/dist/scripts/serve.js +57 -0
  408. package/dist/scripts/serve.js.map +1 -0
  409. package/dist/scripts/service.d.ts +19 -0
  410. package/dist/scripts/service.d.ts.map +1 -0
  411. package/dist/scripts/service.js +257 -0
  412. package/dist/scripts/service.js.map +1 -0
  413. package/dist/server/api/daily.d.ts +3 -0
  414. package/dist/server/api/daily.d.ts.map +1 -0
  415. package/dist/server/api/daily.js +44 -0
  416. package/dist/server/api/daily.js.map +1 -0
  417. package/dist/server/api/graph.d.ts +26 -0
  418. package/dist/server/api/graph.d.ts.map +1 -0
  419. package/dist/server/api/graph.js +80 -0
  420. package/dist/server/api/graph.js.map +1 -0
  421. package/dist/server/api/integrations.d.ts +4 -0
  422. package/dist/server/api/integrations.d.ts.map +1 -0
  423. package/dist/server/api/integrations.js +228 -0
  424. package/dist/server/api/integrations.js.map +1 -0
  425. package/dist/server/api/memories.d.ts +4 -0
  426. package/dist/server/api/memories.d.ts.map +1 -0
  427. package/dist/server/api/memories.js +267 -0
  428. package/dist/server/api/memories.js.map +1 -0
  429. package/dist/server/api/reindex.d.ts +3 -0
  430. package/dist/server/api/reindex.d.ts.map +1 -0
  431. package/dist/server/api/reindex.js +18 -0
  432. package/dist/server/api/reindex.js.map +1 -0
  433. package/dist/server/api/settings.d.ts +3 -0
  434. package/dist/server/api/settings.d.ts.map +1 -0
  435. package/dist/server/api/settings.js +24 -0
  436. package/dist/server/api/settings.js.map +1 -0
  437. package/dist/server/api/sources.d.ts +4 -0
  438. package/dist/server/api/sources.d.ts.map +1 -0
  439. package/dist/server/api/sources.js +45 -0
  440. package/dist/server/api/sources.js.map +1 -0
  441. package/dist/server/api/sync-status.d.ts +3 -0
  442. package/dist/server/api/sync-status.d.ts.map +1 -0
  443. package/dist/server/api/sync-status.js +43 -0
  444. package/dist/server/api/sync-status.js.map +1 -0
  445. package/dist/server/api/types.d.ts +3 -0
  446. package/dist/server/api/types.d.ts.map +1 -0
  447. package/dist/server/api/types.js +20 -0
  448. package/dist/server/api/types.js.map +1 -0
  449. package/dist/server/api/views.d.ts +25 -0
  450. package/dist/server/api/views.d.ts.map +1 -0
  451. package/dist/server/api/views.js +54 -0
  452. package/dist/server/api/views.js.map +1 -0
  453. package/dist/server/index.d.ts +2 -0
  454. package/dist/server/index.d.ts.map +1 -0
  455. package/dist/server/index.js +3 -0
  456. package/dist/server/index.js.map +1 -0
  457. package/dist/sync/apply.d.ts +55 -0
  458. package/dist/sync/apply.d.ts.map +1 -0
  459. package/dist/sync/apply.js +277 -0
  460. package/dist/sync/apply.js.map +1 -0
  461. package/dist/sync/channel-client.d.ts +27 -0
  462. package/dist/sync/channel-client.d.ts.map +1 -0
  463. package/dist/sync/channel-client.js +154 -0
  464. package/dist/sync/channel-client.js.map +1 -0
  465. package/dist/sync/cloud-saves.d.ts +49 -0
  466. package/dist/sync/cloud-saves.d.ts.map +1 -0
  467. package/dist/sync/cloud-saves.js +182 -0
  468. package/dist/sync/cloud-saves.js.map +1 -0
  469. package/dist/sync/ed25519.d.ts +54 -0
  470. package/dist/sync/ed25519.d.ts.map +1 -0
  471. package/dist/sync/ed25519.js +136 -0
  472. package/dist/sync/ed25519.js.map +1 -0
  473. package/dist/sync/ops-log.d.ts +43 -0
  474. package/dist/sync/ops-log.d.ts.map +1 -0
  475. package/dist/sync/ops-log.js +153 -0
  476. package/dist/sync/ops-log.js.map +1 -0
  477. package/dist/sync/recovery-setup.d.ts +26 -0
  478. package/dist/sync/recovery-setup.d.ts.map +1 -0
  479. package/dist/sync/recovery-setup.js +113 -0
  480. package/dist/sync/recovery-setup.js.map +1 -0
  481. package/dist/sync/replay.d.ts +19 -0
  482. package/dist/sync/replay.d.ts.map +1 -0
  483. package/dist/sync/replay.js +59 -0
  484. package/dist/sync/replay.js.map +1 -0
  485. package/dist/sync/shamir.d.ts +22 -0
  486. package/dist/sync/shamir.d.ts.map +1 -0
  487. package/dist/sync/shamir.js +109 -0
  488. package/dist/sync/shamir.js.map +1 -0
  489. package/dist/sync/tests/apply.test.d.ts +4 -0
  490. package/dist/sync/tests/apply.test.d.ts.map +1 -0
  491. package/dist/sync/tests/apply.test.js +119 -0
  492. package/dist/sync/tests/apply.test.js.map +1 -0
  493. package/dist/sync/tests/ops-log.test.d.ts +2 -0
  494. package/dist/sync/tests/ops-log.test.d.ts.map +1 -0
  495. package/dist/sync/tests/ops-log.test.js +105 -0
  496. package/dist/sync/tests/ops-log.test.js.map +1 -0
  497. package/dist/sync/tests/two-device-sync.test.d.ts +2 -0
  498. package/dist/sync/tests/two-device-sync.test.d.ts.map +1 -0
  499. package/dist/sync/tests/two-device-sync.test.js +250 -0
  500. package/dist/sync/tests/two-device-sync.test.js.map +1 -0
  501. package/dist/sync/types.d.ts +87 -0
  502. package/dist/sync/types.d.ts.map +1 -0
  503. package/dist/sync/types.js +37 -0
  504. package/dist/sync/types.js.map +1 -0
  505. package/dist/tests/chunker.test.d.ts +2 -0
  506. package/dist/tests/chunker.test.d.ts.map +1 -0
  507. package/dist/tests/chunker.test.js +24 -0
  508. package/dist/tests/chunker.test.js.map +1 -0
  509. package/dist/tests/cloud-auth.test.d.ts +2 -0
  510. package/dist/tests/cloud-auth.test.d.ts.map +1 -0
  511. package/dist/tests/cloud-auth.test.js +75 -0
  512. package/dist/tests/cloud-auth.test.js.map +1 -0
  513. package/dist/tests/cloud-crypto.test.d.ts +2 -0
  514. package/dist/tests/cloud-crypto.test.d.ts.map +1 -0
  515. package/dist/tests/cloud-crypto.test.js +58 -0
  516. package/dist/tests/cloud-crypto.test.js.map +1 -0
  517. package/dist/tests/cloud-integration.test.d.ts +2 -0
  518. package/dist/tests/cloud-integration.test.d.ts.map +1 -0
  519. package/dist/tests/cloud-integration.test.js +193 -0
  520. package/dist/tests/cloud-integration.test.js.map +1 -0
  521. package/dist/tests/cloud-pairing.test.d.ts +2 -0
  522. package/dist/tests/cloud-pairing.test.d.ts.map +1 -0
  523. package/dist/tests/cloud-pairing.test.js +86 -0
  524. package/dist/tests/cloud-pairing.test.js.map +1 -0
  525. package/dist/tests/cloud-saves-integration.test.d.ts +2 -0
  526. package/dist/tests/cloud-saves-integration.test.d.ts.map +1 -0
  527. package/dist/tests/cloud-saves-integration.test.js +92 -0
  528. package/dist/tests/cloud-saves-integration.test.js.map +1 -0
  529. package/dist/tests/cloud-transit.test.d.ts +2 -0
  530. package/dist/tests/cloud-transit.test.d.ts.map +1 -0
  531. package/dist/tests/cloud-transit.test.js +263 -0
  532. package/dist/tests/cloud-transit.test.js.map +1 -0
  533. package/dist/tests/config.test.d.ts +2 -0
  534. package/dist/tests/config.test.d.ts.map +1 -0
  535. package/dist/tests/config.test.js +25 -0
  536. package/dist/tests/config.test.js.map +1 -0
  537. package/dist/tests/db.test.d.ts +2 -0
  538. package/dist/tests/db.test.d.ts.map +1 -0
  539. package/dist/tests/db.test.js +75 -0
  540. package/dist/tests/db.test.js.map +1 -0
  541. package/dist/tests/embeddings-providers.test.d.ts +2 -0
  542. package/dist/tests/embeddings-providers.test.d.ts.map +1 -0
  543. package/dist/tests/embeddings-providers.test.js +62 -0
  544. package/dist/tests/embeddings-providers.test.js.map +1 -0
  545. package/dist/tests/embeddings.test.d.ts +2 -0
  546. package/dist/tests/embeddings.test.d.ts.map +1 -0
  547. package/dist/tests/embeddings.test.js +22 -0
  548. package/dist/tests/embeddings.test.js.map +1 -0
  549. package/dist/tests/integrations-api.test.d.ts +2 -0
  550. package/dist/tests/integrations-api.test.d.ts.map +1 -0
  551. package/dist/tests/integrations-api.test.js +129 -0
  552. package/dist/tests/integrations-api.test.js.map +1 -0
  553. package/dist/tests/memory-store.test.d.ts +2 -0
  554. package/dist/tests/memory-store.test.d.ts.map +1 -0
  555. package/dist/tests/memory-store.test.js +129 -0
  556. package/dist/tests/memory-store.test.js.map +1 -0
  557. package/dist/tests/module-registry.test.d.ts +2 -0
  558. package/dist/tests/module-registry.test.d.ts.map +1 -0
  559. package/dist/tests/module-registry.test.js +44 -0
  560. package/dist/tests/module-registry.test.js.map +1 -0
  561. package/dist/tests/property-extractor.test.d.ts +2 -0
  562. package/dist/tests/property-extractor.test.d.ts.map +1 -0
  563. package/dist/tests/property-extractor.test.js +24 -0
  564. package/dist/tests/property-extractor.test.js.map +1 -0
  565. package/dist/tests/public-tools.test.d.ts +2 -0
  566. package/dist/tests/public-tools.test.d.ts.map +1 -0
  567. package/dist/tests/public-tools.test.js +270 -0
  568. package/dist/tests/public-tools.test.js.map +1 -0
  569. package/dist/tests/reindex.test.d.ts +2 -0
  570. package/dist/tests/reindex.test.d.ts.map +1 -0
  571. package/dist/tests/reindex.test.js +58 -0
  572. package/dist/tests/reindex.test.js.map +1 -0
  573. package/dist/tests/shamir.test.d.ts +2 -0
  574. package/dist/tests/shamir.test.d.ts.map +1 -0
  575. package/dist/tests/shamir.test.js +57 -0
  576. package/dist/tests/shamir.test.js.map +1 -0
  577. package/dist/tests/source-registry.test.d.ts +2 -0
  578. package/dist/tests/source-registry.test.d.ts.map +1 -0
  579. package/dist/tests/source-registry.test.js +58 -0
  580. package/dist/tests/source-registry.test.js.map +1 -0
  581. package/dist/tests/types.test.d.ts +2 -0
  582. package/dist/tests/types.test.d.ts.map +1 -0
  583. package/dist/tests/types.test.js +26 -0
  584. package/dist/tests/types.test.js.map +1 -0
  585. package/dist/tests/vector.test.d.ts +2 -0
  586. package/dist/tests/vector.test.d.ts.map +1 -0
  587. package/dist/tests/vector.test.js +61 -0
  588. package/dist/tests/vector.test.js.map +1 -0
  589. package/dist/tests/wikilinks.test.d.ts +2 -0
  590. package/dist/tests/wikilinks.test.d.ts.map +1 -0
  591. package/dist/tests/wikilinks.test.js +20 -0
  592. package/dist/tests/wikilinks.test.js.map +1 -0
  593. package/dist/tools/index.d.ts +22 -0
  594. package/dist/tools/index.d.ts.map +1 -0
  595. package/dist/tools/index.js +38 -0
  596. package/dist/tools/index.js.map +1 -0
  597. package/dist/types.d.ts +134 -0
  598. package/dist/types.d.ts.map +1 -0
  599. package/dist/types.js +25 -0
  600. package/dist/types.js.map +1 -0
  601. package/dist/vector/store.d.ts +28 -0
  602. package/dist/vector/store.d.ts.map +1 -0
  603. package/dist/vector/store.js +132 -0
  604. package/dist/vector/store.js.map +1 -0
  605. package/dist/webapp/api/daily.d.ts +3 -0
  606. package/dist/webapp/api/daily.d.ts.map +1 -0
  607. package/dist/webapp/api/daily.js +44 -0
  608. package/dist/webapp/api/daily.js.map +1 -0
  609. package/dist/webapp/api/graph.d.ts +26 -0
  610. package/dist/webapp/api/graph.d.ts.map +1 -0
  611. package/dist/webapp/api/graph.js +80 -0
  612. package/dist/webapp/api/graph.js.map +1 -0
  613. package/dist/webapp/api/memories.d.ts +4 -0
  614. package/dist/webapp/api/memories.d.ts.map +1 -0
  615. package/dist/webapp/api/memories.js +70 -0
  616. package/dist/webapp/api/memories.js.map +1 -0
  617. package/dist/webapp/api/reindex.d.ts +3 -0
  618. package/dist/webapp/api/reindex.d.ts.map +1 -0
  619. package/dist/webapp/api/reindex.js +18 -0
  620. package/dist/webapp/api/reindex.js.map +1 -0
  621. package/dist/webapp/api/settings.d.ts +3 -0
  622. package/dist/webapp/api/settings.d.ts.map +1 -0
  623. package/dist/webapp/api/settings.js +24 -0
  624. package/dist/webapp/api/settings.js.map +1 -0
  625. package/dist/webapp/api/sources.d.ts +4 -0
  626. package/dist/webapp/api/sources.d.ts.map +1 -0
  627. package/dist/webapp/api/sources.js +45 -0
  628. package/dist/webapp/api/sources.js.map +1 -0
  629. package/dist/webapp/api/sync-status.d.ts +3 -0
  630. package/dist/webapp/api/sync-status.d.ts.map +1 -0
  631. package/dist/webapp/api/sync-status.js +43 -0
  632. package/dist/webapp/api/sync-status.js.map +1 -0
  633. package/dist/webapp/api/types.d.ts +3 -0
  634. package/dist/webapp/api/types.d.ts.map +1 -0
  635. package/dist/webapp/api/types.js +20 -0
  636. package/dist/webapp/api/types.js.map +1 -0
  637. package/dist/webapp/api/views.d.ts +25 -0
  638. package/dist/webapp/api/views.d.ts.map +1 -0
  639. package/dist/webapp/api/views.js +54 -0
  640. package/dist/webapp/api/views.js.map +1 -0
  641. package/dist/webapp/mcp-http.d.ts +2 -0
  642. package/dist/webapp/mcp-http.d.ts.map +1 -0
  643. package/dist/webapp/mcp-http.js +3 -0
  644. package/dist/webapp/mcp-http.js.map +1 -0
  645. package/dist/webapp/server.d.ts +2 -0
  646. package/dist/webapp/server.d.ts.map +1 -0
  647. package/dist/webapp/server.js +3 -0
  648. package/dist/webapp/server.js.map +1 -0
  649. package/dist/webapp/tests/api.test.d.ts +2 -0
  650. package/dist/webapp/tests/api.test.d.ts.map +1 -0
  651. package/dist/webapp/tests/api.test.js +125 -0
  652. package/dist/webapp/tests/api.test.js.map +1 -0
  653. package/dist/webapp/tests/mcp-http.test.d.ts +2 -0
  654. package/dist/webapp/tests/mcp-http.test.d.ts.map +1 -0
  655. package/dist/webapp/tests/mcp-http.test.js +47 -0
  656. package/dist/webapp/tests/mcp-http.test.js.map +1 -0
  657. package/dist/webapp/websocket.d.ts +2 -0
  658. package/dist/webapp/websocket.d.ts.map +1 -0
  659. package/dist/webapp/websocket.js +3 -0
  660. package/dist/webapp/websocket.js.map +1 -0
  661. package/package.json +128 -0
  662. package/src/private/README.md +49 -0
@@ -0,0 +1,182 @@
1
+ // src/memory/modules/youtube/tools.ts
2
+ import { createLogger } from '../../../logger.js';
3
+ import { fetchTranscript } from './transcript-fetcher.js';
4
+ import { buildYoutubeItem } from './ingest.js';
5
+ import { sourceRegistry } from '../../core/source-registry.js';
6
+ const log = createLogger('youtube:tools');
7
+ export function buildYoutubeTools(store, config) {
8
+ const embeddingModel = `${config.embeddings.provider}/${config.embeddings.model}`;
9
+ return [
10
+ {
11
+ name: 'add_youtube_url',
12
+ description: 'Fetch the transcript of a YouTube video and add it to memory. Works for any public video with captions (auto-generated or human). The video title from the page is used automatically as the title field. You (the calling LLM) should provide additional context like tags when calling suggest_properties afterwards.',
13
+ inputSchema: {
14
+ type: 'object',
15
+ properties: {
16
+ url: {
17
+ type: 'string',
18
+ description: 'YouTube watch URL or video id.',
19
+ },
20
+ },
21
+ required: ['url'],
22
+ },
23
+ handler: async (args) => {
24
+ const url = args.url;
25
+ const transcript = await fetchTranscript(url, config.youtube);
26
+ const item = buildYoutubeItem({ transcript, embeddingModel });
27
+ await store.insert(item);
28
+ log.info(`Ingested YouTube ${transcript.title} (${transcript.segments.length} segments) as ${item.id}`);
29
+ return {
30
+ id: item.id,
31
+ title: transcript.title,
32
+ channel: transcript.channel,
33
+ segments: transcript.segments.length,
34
+ };
35
+ },
36
+ },
37
+ {
38
+ name: 'search_youtube',
39
+ description: 'Search ingested YouTube transcripts by semantic similarity. Returns video metadata + transcript snippets. Use this to find previously ingested YouTube videos by content.',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {
43
+ query: { type: 'string', description: 'Natural language query to search YouTube transcripts.' },
44
+ limit: { type: 'number', default: 10, description: 'Maximum number of results to return.' },
45
+ },
46
+ required: ['query'],
47
+ },
48
+ handler: async (args) => {
49
+ const hits = await store.search('youtube', args.query, args.limit ?? 10);
50
+ return hits.map((h) => ({
51
+ id: h.memory.id,
52
+ score: h.score,
53
+ snippet: h.snippet,
54
+ title: h.memory.properties.title,
55
+ channel: h.memory.properties.author,
56
+ source_url: h.memory.properties.source_url,
57
+ }));
58
+ },
59
+ },
60
+ // ── watch_youtube_channel ───────────────────────────────────────────────
61
+ {
62
+ name: 'watch_youtube_channel',
63
+ description: 'Subscribe to a YouTube channel. EngramMCP will poll the channel RSS feed periodically ' +
64
+ '(default 6h, configurable via youtube.channelPollIntervalMs in ~/.engram/config.json) ' +
65
+ 'and automatically ingest new videos as YouTube memories. ' +
66
+ 'Call with the channel URL (e.g. https://www.youtube.com/@handle) or channel ID (UCxxx). ' +
67
+ 'Use this when a user says "watch this channel" or "keep track of new videos from X". ' +
68
+ 'Providing a descriptive channelName improves search retrieval.',
69
+ inputSchema: {
70
+ type: 'object',
71
+ properties: {
72
+ channelId: {
73
+ type: 'string',
74
+ description: 'YouTube channel ID (starts with UC…) OR full channel URL (e.g. https://www.youtube.com/@handle). Required.',
75
+ },
76
+ channelName: {
77
+ type: 'string',
78
+ description: 'Human-readable channel name. Used as a tag on ingested memories.',
79
+ },
80
+ },
81
+ required: ['channelId'],
82
+ },
83
+ handler: async (args) => {
84
+ const { channelId: rawChannelId, channelName: rawChannelName } = args;
85
+ const { resolveChannelId } = await import('./watcher.js');
86
+ const channelId = await resolveChannelId(rawChannelId);
87
+ const existing = sourceRegistry.listEnabled('youtube').find((s) => s.external_id === channelId);
88
+ if (existing) {
89
+ return {
90
+ content: [
91
+ { type: 'text', text: `Channel ${channelId} is already being watched.` },
92
+ ],
93
+ };
94
+ }
95
+ sourceRegistry.add({
96
+ module_id: 'youtube',
97
+ external_id: channelId,
98
+ display_name: rawChannelName ?? channelId,
99
+ config: { channelId, channelName: rawChannelName ?? channelId },
100
+ });
101
+ return {
102
+ content: [
103
+ {
104
+ type: 'text',
105
+ text: `Now watching channel ${rawChannelName ?? channelId} (${channelId}). New videos will be ingested automatically.`,
106
+ },
107
+ ],
108
+ };
109
+ },
110
+ },
111
+ // ── unwatch_youtube_channel ─────────────────────────────────────────────
112
+ {
113
+ name: 'unwatch_youtube_channel',
114
+ description: 'Stop watching a YouTube channel. Future videos will not be ingested. Existing memories are kept.',
115
+ inputSchema: {
116
+ type: 'object',
117
+ properties: {
118
+ channelId: { type: 'string', description: 'Channel ID to unwatch (UCxxx).' },
119
+ },
120
+ required: ['channelId'],
121
+ },
122
+ handler: async (args) => {
123
+ const { channelId } = args;
124
+ const sources = sourceRegistry.list('youtube');
125
+ const found = sources.find((s) => s.external_id === channelId);
126
+ if (found) {
127
+ sourceRegistry.remove(found.id);
128
+ return {
129
+ content: [
130
+ { type: 'text', text: `Stopped watching channel ${channelId}.` },
131
+ ],
132
+ };
133
+ }
134
+ return {
135
+ content: [
136
+ {
137
+ type: 'text',
138
+ text: `Channel ${channelId} was not in the watch list.`,
139
+ },
140
+ ],
141
+ };
142
+ },
143
+ },
144
+ // ── import_watch_later ──────────────────────────────────────────────────
145
+ {
146
+ name: 'import_watch_later',
147
+ description: 'Bulk-import a YouTube playlist (paste the playlist URL). Fetches video IDs from the playlist page HTML and ingests each video as a YouTube memory. ' +
148
+ 'Note: Watch Later (WL) is a private playlist — the user must make it temporarily public or use a different playlist URL. ' +
149
+ 'Use this when a user says "import my watch later" or "ingest this playlist".',
150
+ inputSchema: {
151
+ type: 'object',
152
+ properties: {
153
+ playlistUrl: {
154
+ type: 'string',
155
+ description: 'Full YouTube playlist URL, e.g. https://www.youtube.com/playlist?list=PLxxxxxx',
156
+ },
157
+ limit: {
158
+ type: 'number',
159
+ description: 'Maximum number of videos to import (default 50, max 200). Older videos are skipped first.',
160
+ },
161
+ },
162
+ required: ['playlistUrl'],
163
+ },
164
+ handler: async (args) => {
165
+ const { playlistUrl, limit: rawLimit } = args;
166
+ const { importPlaylist } = await import('./watcher.js');
167
+ const result = await importPlaylist(playlistUrl, store, config.embeddings, config.youtube, rawLimit ?? 50);
168
+ return {
169
+ content: [
170
+ {
171
+ type: 'text',
172
+ text: result.imported > 0
173
+ ? `Imported ${result.imported} videos from the playlist. ${result.skipped} skipped (already ingested or failed).`
174
+ : `No new videos found in the playlist. Either the playlist is empty, private, or all videos were already ingested.`,
175
+ },
176
+ ],
177
+ };
178
+ },
179
+ },
180
+ ];
181
+ }
182
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../../src/memory/modules/youtube/tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { YoutubeConfig } from '../../../config/schema.js';
2
+ export interface YoutubeTranscriptSegment {
3
+ start: number;
4
+ duration: number;
5
+ text: string;
6
+ }
7
+ export interface YoutubeTranscriptResult {
8
+ video_id: string;
9
+ title: string;
10
+ channel: string;
11
+ language: string;
12
+ segments: YoutubeTranscriptSegment[];
13
+ full_text: string;
14
+ }
15
+ export declare function extractVideoId(url: string): string;
16
+ export declare function fetchTranscript(url: string, config: YoutubeConfig): Promise<YoutubeTranscriptResult>;
17
+ //# sourceMappingURL=transcript-fetcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-fetcher.d.ts","sourceRoot":"","sources":["../../../../src/memory/modules/youtube/transcript-fetcher.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAI/D,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,wBAAwB,EAAE,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAkBlD;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,uBAAuB,CAAC,CAmFlC"}
@@ -0,0 +1,178 @@
1
+ // src/memory/modules/youtube/transcript-fetcher.ts
2
+ /**
3
+ * YouTube transcript fetcher.
4
+ *
5
+ * Strategy:
6
+ * 1. Fetch the video page HTML.
7
+ * 2. Extract the player config containing `captionTracks`.
8
+ * 3. Pick the preferred language track (or first available).
9
+ * 4. Download the timedtext XML and parse into segments.
10
+ *
11
+ * Fallback: shell out to `yt-dlp --write-auto-sub --skip-download --sub-format vtt`
12
+ * if step 2/3 fails AND `yt-dlp` is on PATH AND config.fallbackToYtdlp is true.
13
+ */
14
+ import { spawn } from 'child_process';
15
+ import { createLogger } from '../../../logger.js';
16
+ const log = createLogger('youtube:transcript');
17
+ export function extractVideoId(url) {
18
+ // Handle multiple URL shapes:
19
+ // https://www.youtube.com/watch?v=ID
20
+ // https://youtu.be/ID
21
+ // https://www.youtube.com/embed/ID
22
+ // https://www.youtube.com/shorts/ID
23
+ const patterns = [
24
+ /[?&]v=([A-Za-z0-9_-]{11})/,
25
+ /youtu\.be\/([A-Za-z0-9_-]{11})/,
26
+ /embed\/([A-Za-z0-9_-]{11})/,
27
+ /shorts\/([A-Za-z0-9_-]{11})/,
28
+ ];
29
+ for (const p of patterns) {
30
+ const m = url.match(p);
31
+ if (m)
32
+ return m[1];
33
+ }
34
+ if (/^[A-Za-z0-9_-]{11}$/.test(url))
35
+ return url; // raw id
36
+ throw new Error(`Could not extract YouTube video id from: ${url}`);
37
+ }
38
+ export async function fetchTranscript(url, config) {
39
+ const videoId = extractVideoId(url);
40
+ const watchUrl = `https://www.youtube.com/watch?v=${videoId}`;
41
+ // Step 1: fetch watch page
42
+ const resp = await fetch(watchUrl, {
43
+ headers: {
44
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17 Safari/605.1.15',
45
+ 'Accept-Language': `${config.preferLanguage},en;q=0.9`,
46
+ },
47
+ });
48
+ if (!resp.ok)
49
+ throw new Error(`YouTube watch page returned ${resp.status}`);
50
+ const html = await resp.text();
51
+ // Step 2: extract title + channel + captionTracks
52
+ const titleMatch = html.match(/<title>([^<]+)<\/title>/);
53
+ const title = titleMatch ? titleMatch[1].replace(/ - YouTube$/, '').trim() : videoId;
54
+ const channelMatch = html.match(/"ownerChannelName":"([^"]+)"/);
55
+ const channel = channelMatch ? channelMatch[1] : 'unknown';
56
+ const captionsMatch = html.match(/"captionTracks":(\[[^\]]+\])/);
57
+ if (!captionsMatch) {
58
+ if (config.fallbackToYtdlp) {
59
+ log.info(`No captionTracks in HTML for ${videoId} — falling back to yt-dlp`);
60
+ return await fetchViaYtdlp(videoId, watchUrl, title, channel, config);
61
+ }
62
+ throw new Error(`No captions available for video ${videoId}`);
63
+ }
64
+ const tracks = JSON.parse(captionsMatch[1]);
65
+ const preferred = tracks.find((t) => t.languageCode === config.preferLanguage && t.kind !== 'asr') ??
66
+ tracks.find((t) => t.languageCode === config.preferLanguage) ??
67
+ tracks.find((t) => t.kind !== 'asr') ??
68
+ tracks[0];
69
+ if (!preferred)
70
+ throw new Error('No usable caption track');
71
+ // Step 3: download timedtext
72
+ const xmlResp = await fetch(preferred.baseUrl, {
73
+ headers: {
74
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17 Safari/605.1.15',
75
+ 'Referer': 'https://www.youtube.com/',
76
+ },
77
+ });
78
+ if (!xmlResp.ok)
79
+ throw new Error(`Timedtext fetch failed: ${xmlResp.status}`);
80
+ const xml = await xmlResp.text();
81
+ // Step 4: parse <text start="X" dur="Y">content</text>
82
+ const segments = [];
83
+ const re = /<text start="([\d.]+)" dur="([\d.]+)"[^>]*>([\s\S]*?)<\/text>/g;
84
+ let m;
85
+ while ((m = re.exec(xml)) !== null) {
86
+ segments.push({
87
+ start: parseFloat(m[1]),
88
+ duration: parseFloat(m[2]),
89
+ text: decodeHtmlEntities(m[3]).replace(/\n/g, ' ').trim(),
90
+ });
91
+ }
92
+ // YouTube's timedtext API may return empty XML even for videos with captions.
93
+ // If we got 0 segments and yt-dlp fallback is enabled, use it.
94
+ if (segments.length === 0 && config.fallbackToYtdlp) {
95
+ log.info(`Timedtext returned 0 segments for ${videoId} — falling back to yt-dlp`);
96
+ return await fetchViaYtdlp(videoId, watchUrl, title, channel, config);
97
+ }
98
+ if (segments.length === 0) {
99
+ throw new Error(`No transcript segments found for video ${videoId}. Enable fallbackToYtdlp or check if the video has captions.`);
100
+ }
101
+ return {
102
+ video_id: videoId,
103
+ title,
104
+ channel,
105
+ language: preferred.languageCode,
106
+ segments,
107
+ full_text: segments.map((s) => s.text).join(' '),
108
+ };
109
+ }
110
+ function decodeHtmlEntities(s) {
111
+ return s
112
+ .replace(/&amp;/g, '&')
113
+ .replace(/&lt;/g, '<')
114
+ .replace(/&gt;/g, '>')
115
+ .replace(/&quot;/g, '"')
116
+ .replace(/&#39;/g, "'")
117
+ .replace(/&#x27;/g, "'")
118
+ .replace(/&nbsp;/g, ' ');
119
+ }
120
+ async function fetchViaYtdlp(videoId, url, title, channel, config) {
121
+ return new Promise((resolve, reject) => {
122
+ const args = [
123
+ '--write-auto-sub',
124
+ '--write-sub',
125
+ '--sub-lang',
126
+ `${config.preferLanguage},en`,
127
+ '--skip-download',
128
+ '--sub-format',
129
+ 'vtt',
130
+ '--output',
131
+ `/tmp/engram-yt-%(id)s.%(ext)s`,
132
+ url,
133
+ ];
134
+ const child = spawn('yt-dlp', args);
135
+ let stderr = '';
136
+ child.stderr.on('data', (b) => (stderr += b.toString()));
137
+ child.on('error', (e) => reject(new Error(`yt-dlp not available: ${e.message}. Install via 'brew install yt-dlp'.`)));
138
+ child.on('exit', async (code) => {
139
+ if (code !== 0) {
140
+ reject(new Error(`yt-dlp exit ${code}: ${stderr.slice(-300)}`));
141
+ return;
142
+ }
143
+ try {
144
+ const fs = await import('fs');
145
+ // Find the produced VTT file
146
+ const candidates = fs.readdirSync('/tmp').filter((n) => n.startsWith(`engram-yt-${videoId}`));
147
+ const vttFile = candidates.find((n) => n.endsWith('.vtt'));
148
+ if (!vttFile)
149
+ throw new Error('yt-dlp produced no .vtt');
150
+ const vtt = fs.readFileSync(`/tmp/${vttFile}`, 'utf-8');
151
+ const segments = parseVtt(vtt);
152
+ resolve({
153
+ video_id: videoId,
154
+ title,
155
+ channel,
156
+ language: config.preferLanguage,
157
+ segments,
158
+ full_text: segments.map((s) => s.text).join(' '),
159
+ });
160
+ }
161
+ catch (e) {
162
+ reject(e);
163
+ }
164
+ });
165
+ });
166
+ }
167
+ function parseVtt(vtt) {
168
+ const segments = [];
169
+ const re = /(\d+):(\d{2}):(\d{2})[.,](\d+) --> (\d+):(\d{2}):(\d{2})[.,](\d+)\n([^\n]+)/g;
170
+ let m;
171
+ while ((m = re.exec(vtt)) !== null) {
172
+ const start = Number(m[1]) * 3600 + Number(m[2]) * 60 + Number(m[3]) + Number(m[4]) / 1000;
173
+ const end = Number(m[5]) * 3600 + Number(m[6]) * 60 + Number(m[7]) + Number(m[8]) / 1000;
174
+ segments.push({ start, duration: end - start, text: m[9].trim() });
175
+ }
176
+ return segments;
177
+ }
178
+ //# sourceMappingURL=transcript-fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-fetcher.js","sourceRoot":"","sources":["../../../../src/memory/modules/youtube/transcript-fetcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { EmbeddingsConfig, YoutubeConfig } from '../../../config/schema.js';
2
+ import type { MemoryStore } from '../../core/store.js';
3
+ /**
4
+ * Accepts a channel ID (UCxxx…) or a full URL and returns the channel ID.
5
+ * For handle-based URLs, fetches the channel page and extracts the canonical
6
+ * channel ID from the embedded JSON.
7
+ */
8
+ export declare function resolveChannelId(input: string): Promise<string>;
9
+ /**
10
+ * Fetches the RSS feed for a channel, diffs against the last known video,
11
+ * and ingests any new videos (up to 15 per poll tick — the RSS feed max).
12
+ */
13
+ export declare function pollChannel(channelId: string, channelName: string, store: MemoryStore, embeddingsConfig: EmbeddingsConfig, youtubeConfig: YoutubeConfig): Promise<{
14
+ ingested: number;
15
+ }>;
16
+ /**
17
+ * Fetches a YouTube playlist page (public only), extracts video IDs from
18
+ * ytInitialData JSON blob embedded in the HTML, and ingests new ones.
19
+ */
20
+ export declare function importPlaylist(playlistUrl: string, store: MemoryStore, embeddingsConfig: EmbeddingsConfig, youtubeConfig: YoutubeConfig, limit?: number): Promise<{
21
+ imported: number;
22
+ skipped: number;
23
+ }>;
24
+ /**
25
+ * Starts the channel polling loop for all watched YouTube channels.
26
+ * Called once at server startup from the YouTube module's onBoot().
27
+ * The interval is configurable via config.youtube.channelPollIntervalMs (default 6h).
28
+ */
29
+ export declare function startChannelCron(store: MemoryStore, embeddingsConfig: EmbeddingsConfig, youtubeConfig: YoutubeConfig): void;
30
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../../../src/memory/modules/youtube/watcher.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AACjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AASvD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBrE;AAGD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,WAAW,EAClB,gBAAgB,EAAE,gBAAgB,EAClC,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA6E/B;AAGD;;;GAGG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,WAAW,EAClB,gBAAgB,EAAE,gBAAgB,EAClC,aAAa,EAAE,aAAa,EAC5B,KAAK,SAAK,GACT,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA6DhD;AAGD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAClB,gBAAgB,EAAE,gBAAgB,EAClC,aAAa,EAAE,aAAa,GAC3B,IAAI,CA8CN"}
@@ -0,0 +1,198 @@
1
+ // src/memory/modules/youtube/watcher.ts
2
+ import { XMLParser } from 'fast-xml-parser';
3
+ import { createLogger } from '../../../logger.js';
4
+ import { getDb } from '../../../db/index.js';
5
+ import { ingestYouTube } from './ingest.js';
6
+ const log = createLogger('youtube:watcher');
7
+ const FEED_BASE = 'https://www.youtube.com/feeds/videos.xml?channel_id=';
8
+ const CHANNEL_ID_RE = /^UC[A-Za-z0-9_-]{22}$/;
9
+ const CHANNEL_URL_RE = /(?:youtube\.com\/(?:channel\/|@))([A-Za-z0-9_.-]+)/;
10
+ // ── resolveChannelId ───────────────────────────────────────────────────────
11
+ /**
12
+ * Accepts a channel ID (UCxxx…) or a full URL and returns the channel ID.
13
+ * For handle-based URLs, fetches the channel page and extracts the canonical
14
+ * channel ID from the embedded JSON.
15
+ */
16
+ export async function resolveChannelId(input) {
17
+ if (CHANNEL_ID_RE.test(input))
18
+ return input;
19
+ // Try to extract handle or channel path from URL
20
+ const match = CHANNEL_URL_RE.exec(input);
21
+ if (match) {
22
+ const candidate = match[1];
23
+ if (CHANNEL_ID_RE.test(candidate))
24
+ return candidate;
25
+ // It's a handle — fetch the channel page to get the real channel ID
26
+ const channelUrl = input.startsWith('http') ? input : `https://www.youtube.com/${input}`;
27
+ const html = await fetchText(channelUrl);
28
+ const idMatch = /"channelId":"(UC[A-Za-z0-9_-]{22})"/.exec(html);
29
+ if (idMatch)
30
+ return idMatch[1];
31
+ }
32
+ throw new Error(`Cannot resolve channel ID from "${input}". Provide a full channel URL or a channel ID starting with UC.`);
33
+ }
34
+ // ── pollChannel ────────────────────────────────────────────────────────────
35
+ /**
36
+ * Fetches the RSS feed for a channel, diffs against the last known video,
37
+ * and ingests any new videos (up to 15 per poll tick — the RSS feed max).
38
+ */
39
+ export async function pollChannel(channelId, channelName, store, embeddingsConfig, youtubeConfig) {
40
+ const feedUrl = `${FEED_BASE}${channelId}`;
41
+ let xml;
42
+ try {
43
+ xml = await fetchText(feedUrl);
44
+ }
45
+ catch (e) {
46
+ log.warn(`Failed to fetch RSS for channel ${channelId}: ${e instanceof Error ? e.message : String(e)}`);
47
+ return { ingested: 0 };
48
+ }
49
+ const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '_' });
50
+ const parsed = parser.parse(xml);
51
+ const entries = parsed.feed?.entry
52
+ ? Array.isArray(parsed.feed.entry)
53
+ ? parsed.feed.entry
54
+ : [parsed.feed.entry]
55
+ : [];
56
+ const db = getDb();
57
+ const lastSyncedRow = db
58
+ .prepare(`SELECT last_synced_at FROM watched_sources
59
+ WHERE module_id = 'youtube' AND external_id = ?`)
60
+ .get(channelId);
61
+ const lastSyncedAt = lastSyncedRow?.last_synced_at ?? 0;
62
+ let ingested = 0;
63
+ for (const entry of entries) {
64
+ const videoId = entry['yt:videoId'];
65
+ const publishedStr = entry.published;
66
+ if (!videoId || !publishedStr)
67
+ continue;
68
+ const publishedAt = new Date(publishedStr).getTime();
69
+ if (publishedAt <= lastSyncedAt)
70
+ continue;
71
+ const videoUrl = `https://www.youtube.com/watch?v=${videoId}`;
72
+ try {
73
+ await ingestYouTube(videoUrl, store, embeddingsConfig, youtubeConfig, {
74
+ tags: [channelName, 'channel-watch'],
75
+ sourceContext: `YouTube channel: ${channelName}`,
76
+ });
77
+ ingested++;
78
+ log.info(`Ingested video ${videoId} from channel ${channelId}`);
79
+ }
80
+ catch (e) {
81
+ log.warn(`Failed to ingest video ${videoId} from channel ${channelId}: ${e instanceof Error ? e.message : String(e)}`);
82
+ }
83
+ }
84
+ // Update last_synced_at
85
+ db.prepare(`UPDATE watched_sources SET last_synced_at = ? WHERE module_id = 'youtube' AND external_id = ?`).run(Date.now(), channelId);
86
+ return { ingested };
87
+ }
88
+ // ── importPlaylist ─────────────────────────────────────────────────────────
89
+ /**
90
+ * Fetches a YouTube playlist page (public only), extracts video IDs from
91
+ * ytInitialData JSON blob embedded in the HTML, and ingests new ones.
92
+ */
93
+ export async function importPlaylist(playlistUrl, store, embeddingsConfig, youtubeConfig, limit = 50) {
94
+ const html = await fetchText(playlistUrl);
95
+ // Extract ytInitialData JSON
96
+ const match = /var ytInitialData = ({.+?});<\/script>/s.exec(html);
97
+ if (!match) {
98
+ throw new Error('Could not parse playlist page. The playlist may be private or YouTube has changed its page structure.');
99
+ }
100
+ let data;
101
+ try {
102
+ data = JSON.parse(match[1]);
103
+ }
104
+ catch {
105
+ throw new Error('ytInitialData JSON parse failed — YouTube may have changed page format.');
106
+ }
107
+ // Walk the deeply nested structure to extract video IDs
108
+ const videoIds = extractVideoIds(data);
109
+ const unique = [...new Set(videoIds)].slice(0, limit);
110
+ if (!unique.length) {
111
+ return { imported: 0, skipped: 0 };
112
+ }
113
+ // Check which are already ingested
114
+ const db = getDb();
115
+ const existing = new Set(db
116
+ .prepare(`SELECT source_id FROM memories WHERE type = 'youtube' AND source_id IN (${unique.map(() => '?').join(',')})`)
117
+ .all(...unique).map((r) => r.source_id));
118
+ let imported = 0;
119
+ let skipped = 0;
120
+ for (const videoId of unique) {
121
+ if (existing.has(`youtube:${videoId}`)) {
122
+ skipped++;
123
+ continue;
124
+ }
125
+ const url = `https://www.youtube.com/watch?v=${videoId}`;
126
+ try {
127
+ await ingestYouTube(url, store, embeddingsConfig, youtubeConfig, {
128
+ tags: ['watch-later', 'playlist-import'],
129
+ });
130
+ imported++;
131
+ }
132
+ catch (e) {
133
+ log.warn(`Failed to ingest ${videoId}: ${e instanceof Error ? e.message : String(e)}`);
134
+ skipped++;
135
+ }
136
+ }
137
+ return { imported, skipped };
138
+ }
139
+ // ── startChannelCron ───────────────────────────────────────────────────────
140
+ /**
141
+ * Starts the channel polling loop for all watched YouTube channels.
142
+ * Called once at server startup from the YouTube module's onBoot().
143
+ * The interval is configurable via config.youtube.channelPollIntervalMs (default 6h).
144
+ */
145
+ export function startChannelCron(store, embeddingsConfig, youtubeConfig) {
146
+ const intervalMs = youtubeConfig.channelPollIntervalMs ?? 21_600_000; // 6h default
147
+ const db = getDb();
148
+ async function tick() {
149
+ const channels = db
150
+ .prepare(`SELECT external_id, display_name FROM watched_sources
151
+ WHERE module_id = 'youtube' AND enabled = 1`)
152
+ .all();
153
+ if (!channels.length)
154
+ return;
155
+ log.info(`YouTube channel cron: polling ${channels.length} channel(s)`);
156
+ for (const ch of channels) {
157
+ try {
158
+ const { ingested } = await pollChannel(ch.external_id, ch.display_name, store, embeddingsConfig, youtubeConfig);
159
+ if (ingested > 0)
160
+ log.info(` ${ch.display_name}: ingested ${ingested} new video(s)`);
161
+ }
162
+ catch (e) {
163
+ log.error(`Channel poll error for ${ch.external_id}: ${e instanceof Error ? e.message : String(e)}`);
164
+ }
165
+ }
166
+ }
167
+ // Run once immediately at startup, then on interval
168
+ tick().catch((e) => log.error(`Initial channel poll failed: ${e instanceof Error ? e.message : String(e)}`));
169
+ setInterval(() => tick().catch((e) => log.error(`Channel poll error: ${e instanceof Error ? e.message : String(e)}`)), intervalMs);
170
+ log.info(`YouTube channel cron started (${intervalMs / 3_600_000}h interval)`);
171
+ }
172
+ // ── helpers ────────────────────────────────────────────────────────────────
173
+ async function fetchText(url) {
174
+ const res = await fetch(url, {
175
+ headers: {
176
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/124 Safari/537.36',
177
+ 'Accept-Language': 'en-US,en;q=0.9',
178
+ },
179
+ signal: AbortSignal.timeout(15_000),
180
+ });
181
+ if (!res.ok)
182
+ throw new Error(`HTTP ${res.status} for ${url}`);
183
+ return res.text();
184
+ }
185
+ function extractVideoIds(obj) {
186
+ const ids = [];
187
+ JSON.stringify(obj, (_key, value) => {
188
+ if (typeof value === 'object' &&
189
+ value !== null &&
190
+ 'videoId' in value &&
191
+ typeof value.videoId === 'string') {
192
+ ids.push(value.videoId);
193
+ }
194
+ return value;
195
+ });
196
+ return ids;
197
+ }
198
+ //# sourceMappingURL=watcher.js.map