youtube-knowledge-mcp 2.0.1 → 2.1.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 (130) hide show
  1. package/README.md +110 -12
  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 +113 -0
  5. package/dist/brain-schemas.js.map +1 -0
  6. package/dist/http.d.ts.map +1 -1
  7. package/dist/http.js +20 -5
  8. package/dist/http.js.map +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +86 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/prompts.d.ts.map +1 -1
  13. package/dist/prompts.js +42 -0
  14. package/dist/prompts.js.map +1 -1
  15. package/dist/resources.d.ts.map +1 -1
  16. package/dist/resources.js +66 -0
  17. package/dist/resources.js.map +1 -1
  18. package/dist/schemas.d.ts +67 -56
  19. package/dist/schemas.d.ts.map +1 -1
  20. package/dist/schemas.js +25 -4
  21. package/dist/schemas.js.map +1 -1
  22. package/dist/tools/ask-brain.d.ts +62 -0
  23. package/dist/tools/ask-brain.d.ts.map +1 -0
  24. package/dist/tools/ask-brain.js +70 -0
  25. package/dist/tools/ask-brain.js.map +1 -0
  26. package/dist/tools/batch.d.ts +10 -10
  27. package/dist/tools/batch.js +2 -2
  28. package/dist/tools/batch.js.map +1 -1
  29. package/dist/tools/brain-info.d.ts +180 -0
  30. package/dist/tools/brain-info.d.ts.map +1 -0
  31. package/dist/tools/brain-info.js +108 -0
  32. package/dist/tools/brain-info.js.map +1 -0
  33. package/dist/tools/build-brain.d.ts +120 -0
  34. package/dist/tools/build-brain.d.ts.map +1 -0
  35. package/dist/tools/build-brain.js +123 -0
  36. package/dist/tools/build-brain.js.map +1 -0
  37. package/dist/tools/clips.d.ts +4 -4
  38. package/dist/tools/fetch-videos.d.ts +8 -8
  39. package/dist/tools/get-chapters.d.ts +2 -2
  40. package/dist/tools/get-comments.d.ts +2 -2
  41. package/dist/tools/get-transcript.d.ts +2 -2
  42. package/dist/tools/library.d.ts +9 -7
  43. package/dist/tools/library.d.ts.map +1 -1
  44. package/dist/tools/library.js +10 -4
  45. package/dist/tools/library.js.map +1 -1
  46. package/dist/tools/list-library.d.ts +4 -4
  47. package/dist/tools/manage-brain.d.ts +39 -0
  48. package/dist/tools/manage-brain.d.ts.map +1 -0
  49. package/dist/tools/manage-brain.js +53 -0
  50. package/dist/tools/manage-brain.js.map +1 -0
  51. package/dist/tools/search-channels.d.ts +4 -4
  52. package/dist/tools/search-transcript.d.ts +2 -2
  53. package/dist/tools/search-videos.d.ts +8 -8
  54. package/dist/utils/brain-build.d.ts +40 -0
  55. package/dist/utils/brain-build.d.ts.map +1 -0
  56. package/dist/utils/brain-build.js +211 -0
  57. package/dist/utils/brain-build.js.map +1 -0
  58. package/dist/utils/brain-chunks.d.ts +33 -0
  59. package/dist/utils/brain-chunks.d.ts.map +1 -0
  60. package/dist/utils/brain-chunks.js +87 -0
  61. package/dist/utils/brain-chunks.js.map +1 -0
  62. package/dist/utils/brain-index.d.ts +27 -0
  63. package/dist/utils/brain-index.d.ts.map +1 -0
  64. package/dist/utils/brain-index.js +100 -0
  65. package/dist/utils/brain-index.js.map +1 -0
  66. package/dist/utils/brain-ingest.d.ts +57 -0
  67. package/dist/utils/brain-ingest.d.ts.map +1 -0
  68. package/dist/utils/brain-ingest.js +127 -0
  69. package/dist/utils/brain-ingest.js.map +1 -0
  70. package/dist/utils/brain-lock.d.ts +20 -0
  71. package/dist/utils/brain-lock.d.ts.map +1 -0
  72. package/dist/utils/brain-lock.js +93 -0
  73. package/dist/utils/brain-lock.js.map +1 -0
  74. package/dist/utils/brain-lookup.d.ts +3 -0
  75. package/dist/utils/brain-lookup.d.ts.map +1 -0
  76. package/dist/utils/brain-lookup.js +62 -0
  77. package/dist/utils/brain-lookup.js.map +1 -0
  78. package/dist/utils/brain-paths.d.ts +19 -0
  79. package/dist/utils/brain-paths.d.ts.map +1 -0
  80. package/dist/utils/brain-paths.js +34 -0
  81. package/dist/utils/brain-paths.js.map +1 -0
  82. package/dist/utils/brain-phrases.d.ts +31 -0
  83. package/dist/utils/brain-phrases.d.ts.map +1 -0
  84. package/dist/utils/brain-phrases.js +116 -0
  85. package/dist/utils/brain-phrases.js.map +1 -0
  86. package/dist/utils/brain-stats.d.ts +25 -0
  87. package/dist/utils/brain-stats.d.ts.map +1 -0
  88. package/dist/utils/brain-stats.js +64 -0
  89. package/dist/utils/brain-stats.js.map +1 -0
  90. package/dist/utils/brain-storage.d.ts +28 -0
  91. package/dist/utils/brain-storage.d.ts.map +1 -0
  92. package/dist/utils/brain-storage.js +87 -0
  93. package/dist/utils/brain-storage.js.map +1 -0
  94. package/dist/utils/clips.js +4 -4
  95. package/dist/utils/clips.js.map +1 -1
  96. package/dist/utils/json-file.d.ts +36 -0
  97. package/dist/utils/json-file.d.ts.map +1 -0
  98. package/dist/utils/json-file.js +45 -0
  99. package/dist/utils/json-file.js.map +1 -0
  100. package/dist/utils/paths.d.ts +9 -0
  101. package/dist/utils/paths.d.ts.map +1 -0
  102. package/dist/utils/paths.js +13 -0
  103. package/dist/utils/paths.js.map +1 -0
  104. package/dist/utils/preflight.d.ts.map +1 -1
  105. package/dist/utils/preflight.js +14 -2
  106. package/dist/utils/preflight.js.map +1 -1
  107. package/dist/utils/search-index.d.ts +22 -7
  108. package/dist/utils/search-index.d.ts.map +1 -1
  109. package/dist/utils/search-index.js +17 -4
  110. package/dist/utils/search-index.js.map +1 -1
  111. package/dist/utils/storage.d.ts +14 -44
  112. package/dist/utils/storage.d.ts.map +1 -1
  113. package/dist/utils/storage.js +37 -80
  114. package/dist/utils/storage.js.map +1 -1
  115. package/dist/utils/transcript.d.ts +5 -4
  116. package/dist/utils/transcript.d.ts.map +1 -1
  117. package/dist/utils/transcript.js +3 -0
  118. package/dist/utils/transcript.js.map +1 -1
  119. package/dist/utils/validate.d.ts +1 -0
  120. package/dist/utils/validate.d.ts.map +1 -1
  121. package/dist/utils/validate.js +18 -0
  122. package/dist/utils/validate.js.map +1 -1
  123. package/dist/utils/youtube.d.ts +14 -0
  124. package/dist/utils/youtube.d.ts.map +1 -1
  125. package/dist/utils/youtube.js +19 -13
  126. package/dist/utils/youtube.js.map +1 -1
  127. package/dist/utils/ytdlp.d.ts.map +1 -1
  128. package/dist/utils/ytdlp.js +56 -6
  129. package/dist/utils/ytdlp.js.map +1 -1
  130. package/package.json +2 -2
package/README.md CHANGED
@@ -39,6 +39,17 @@ Supports both **local** (stdio) and **remote** (Streamable HTTP) transports.
39
39
  - **Read them back**, and **search across all of them** with full-text ranking
40
40
  - **Tag, retag and delete**
41
41
 
42
+ **Build a brain for a channel** (local mode)
43
+
44
+ - **Read a whole channel** into a searchable corpus of timestamped passages, in
45
+ any caption language, resumable and safe to interrupt — a second run continues
46
+ where it stopped and picks up new uploads
47
+ - **Ask what a creator has said** about anything, across every video, and get
48
+ back the moments themselves with links that open the video there
49
+ - **Measure the channel**: how much was readable, its upload rhythm, its
50
+ speaking rate, and the phrases it repeats across videos
51
+ - **Keep a written profile** beside the corpus, grounded in passages you can cite
52
+
42
53
  **Built to stay working**
43
54
 
44
55
  - WebVTT parsed by the W3C reference implementation, not a hand-written matcher
@@ -130,6 +141,12 @@ Restart your client after updating configuration.
130
141
 
131
142
  The server supports Streamable HTTP transport for remote access via Claude's official connectors.
132
143
 
144
+ **Every remote setup is your own deployment.** There is no shared instance to
145
+ point a connector at, by design: every call shells out to yt-dlp, so a single
146
+ host serving other people's traffic is a host YouTube rate-limits for all of
147
+ them. The button below deploys this repository into your own Render account, in
148
+ about two minutes and without cloning anything.
149
+
133
150
  #### Self-hosted
134
151
 
135
152
  ```bash
@@ -142,28 +159,58 @@ The server listens on `PORT` (default 3000). Set `PORT` environment variable to
142
159
  #### Docker
143
160
 
144
161
  ```bash
145
- npm run build
146
162
  docker build -t youtube-knowledge-mcp .
147
- docker run -p 3000:10000 youtube-knowledge-mcp
163
+
164
+ TOKEN=$(openssl rand -hex 32) && echo "MCP_AUTH_TOKEN=$TOKEN"
165
+ docker run -p 3000:10000 -e MCP_AUTH_TOKEN="$TOKEN" youtube-knowledge-mcp
148
166
  ```
149
167
 
168
+ The token is printed because nothing else will print it: the server logs that a
169
+ token is required, never its value. Send it as `Authorization: Bearer $TOKEN`.
170
+
171
+ The image sets `PORT=10000` and exposes it; publish it on whatever host port you
172
+ like. The build happens inside the image, so no local `npm run build` first.
173
+
150
174
  #### Deploy to Render
151
175
 
152
- 1. Push to GitHub
153
- 2. Create a **Web Service** on Render with **Docker** runtime
154
- 3. Render sets `PORT` automatically
155
- 4. Add the Render URL as a custom connector in Claude.ai > Settings > Connectors
176
+ [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/teobouancheau/youtube-knowledge-mcp)
177
+
178
+ The button opens Render's Blueprint flow against [`render.yaml`](render.yaml) in
179
+ this repository, which builds the Docker image, points the health check at
180
+ `/health` and generates an `MCP_AUTH_TOKEN` for you. No fork, no clone, no
181
+ settings to fill in — the service is yours, on your account.
182
+
183
+ 1. Click the button and confirm. Render builds the image and deploys it.
184
+ 2. Open the service's **Environment** tab and copy the generated
185
+ `MCP_AUTH_TOKEN`. The HTTP transport rejects every request without it, so a
186
+ URL that leaks is not an open server.
187
+ 3. Add `https://<your-service>.onrender.com/mcp` as a custom connector, with
188
+ `Authorization: Bearer <token>`.
189
+
190
+ Worth doing once the service exists: set `MCP_ALLOWED_HOSTS` to your service's
191
+ hostname (`<your-service>.onrender.com`). It cannot be filled in from the
192
+ Blueprint, since the hostname does not exist until the service does, and it
193
+ rejects requests arriving under any other name.
194
+
195
+ Your instance does not follow this repository. The Blueprint sets
196
+ `autoDeployTrigger: off`, because auto-deploying would run code pushed here
197
+ inside your account, under your token, without you reading it first. To take a
198
+ newer version, use **Manual Deploy** on the service.
199
+
200
+ The free plan sleeps after inactivity, so the first call after a pause waits for
201
+ a cold start. Any paid plan removes that.
156
202
 
157
203
  #### Connect via Claude.ai
158
204
 
159
205
  1. Go to **Settings > Connectors**
160
206
  2. Click **Add custom connector**
161
207
  3. Enter your server URL (e.g., `https://your-app.onrender.com/mcp`)
162
- 4. Click **Add**
208
+ 4. Add the `Authorization: Bearer <token>` header if you set `MCP_AUTH_TOKEN`
209
+ 5. Click **Add**
163
210
 
164
211
  ## MCP Tools
165
212
 
166
- 27 tools. The 14 read-only ones work over both transports; the 13 that touch
213
+ 33 tools. The 14 read-only ones work over both transports; the 19 that touch
167
214
  your filesystem are registered only in local (stdio) mode, so a remote
168
215
  deployment cannot reach the host's disk.
169
216
 
@@ -222,22 +269,56 @@ keyframe-aligned cut. All of these require ffmpeg.
222
269
  | `save_to_library` | `videoId`, `title`, `content`, `contentType`, `channel`, `tags` | Path to the saved note |
223
270
  | `list_library` | `tag` | Saved items, newest first |
224
271
  | `get_library_item` | `videoId`, `contentType` | The saved markdown and its metadata |
225
- | `search_library` | `query`, `limit` | Ranked matches with excerpts |
272
+ | `search_library` | `query`, `limit`, `offset` | Ranked matches with excerpts |
226
273
  | `update_library_tags` | `videoId`, `add`, `remove`, `replace` | The updated tags |
227
274
  | `delete_library_item` | `videoId`, `contentType` | What was deleted |
228
275
  | `rebuild_library_index` | — | Number of notes reindexed |
229
276
 
277
+ ### Channel brains — local only
278
+
279
+ | Tool | Key parameters | Returns |
280
+ | -------------------- | ----------------------------------------------------------------- | ------------------------------------------------ |
281
+ | `build_brain` | `channel`, `maxVideos`, `language`, `since`, `minDurationSeconds` | What was read, what was ruled out, and the stats |
282
+ | `ask_brain` | `channel`, `query`, `limit`, `offset` | Passages with timestamps and `?t=` links |
283
+ | `list_brains` | — | Every brain built locally |
284
+ | `get_brain_info` | `channel`, `includeVideos` | Coverage, statistics and repeated phrases |
285
+ | `save_brain_profile` | `channel`, `content` | Path to the saved profile |
286
+ | `delete_brain` | `channel` | What was removed |
287
+
288
+ `build_brain` is the only one that touches the network. The rest resolve a
289
+ channel from what is already on disk, so they work offline and cost nothing to
290
+ call.
291
+
292
+ A brain holds one caption language; pass `language` to read another, and build a
293
+ separate brain per language.
294
+
295
+ `since` and `minDurationSeconds` describe the brain, not just the call that
296
+ passed them. They are re-applied every time, so narrowing one drops the passages
297
+ of the videos it excludes and widening it reads them back — which is why
298
+ `build_brain` is annotated as destructive. Whether a video qualifies is decided
299
+ from the date and length already recorded, so changing your mind costs no
300
+ requests until there is something new to fetch. Those values come from each
301
+ video's own metadata, never from a guess: a flat channel listing does not carry
302
+ a publication date at all.
303
+
304
+ `build_brain` also repairs. If the passage file is lost or truncated, the videos
305
+ it can no longer account for are read again on the next call rather than being
306
+ skipped forever as already done.
307
+
230
308
  ## Prompts
231
309
 
232
310
  Reusable workflows your client can invoke directly: `summarize_video`,
233
311
  `extract_skill`, `compare_videos`, `research_topic`, `channel_deep_dive`,
234
- `clip_from_quote` (find a phrase, then cut the clip around it), and
235
- `review_library` (local only).
312
+ `clip_from_quote` (find a phrase, then cut the clip around it), and — local
313
+ only — `review_library`, `create_brain` (build a channel's corpus, then write
314
+ its profile from it) and `ask_creator` (answer a question strictly from a
315
+ brain, with citations).
236
316
 
237
317
  ## Resources
238
318
 
239
319
  - `youtube://transcript/{videoId}` — a timestamped transcript, fetched and cached on first read
240
320
  - `youtube://library/{videoId}/{summary|skill}` — a saved note (local only, and enumerable)
321
+ - `youtube://brain/{channelId}/{manifest|profile}` — what a channel brain covers, or the profile written from it (local only, and enumerable)
241
322
 
242
323
  ## Error codes
243
324
 
@@ -265,7 +346,7 @@ All optional.
265
346
  | `MCP_ALLOWED_HOSTS` | unset | Comma-separated Host allowlist; enables DNS-rebinding protection |
266
347
  | `MCP_ALLOWED_ORIGINS` | unset | Comma-separated Origin allowlist |
267
348
  | `MCP_BIND_HOST` | `0.0.0.0` | Interface to bind |
268
- | `PORT` | `3000` | HTTP port |
349
+ | `PORT` | `3000` | HTTP port. The Docker image sets `10000`; Render and similar platforms inject their own |
269
350
  | `MCP_RATE_LIMIT` | `60` | Requests per window, per client |
270
351
  | `MCP_RATE_WINDOW_MS` | `60000` | Rate-limit window |
271
352
  | `MCP_SESSION_IDLE_MS` | `1800000` | Close HTTP sessions idle this long |
@@ -286,6 +367,11 @@ Content is stored in `~/.youtube-knowledge/`:
286
367
  │ ├── metadata.json
287
368
  │ ├── summary.md
288
369
  │ └── skill.md
370
+ ├── brains/ # Channel brains
371
+ │ └── {channel_id}/
372
+ │ ├── manifest.json # What the brain covers, and where a build stopped
373
+ │ ├── chunks.json # The timestamped passages
374
+ │ └── profile.md # The written account, if one was saved
289
375
  ├── downloads/ # Full downloads
290
376
  ├── clips/ # Extracted clips
291
377
  ├── frames/ # Captured stills
@@ -358,6 +444,18 @@ editor can import.
358
444
  `save_to_library` stores it; `search_library` searches across everything saved
359
445
  with full-text ranking.
360
446
 
447
+ ### Build a brain for a creator
448
+
449
+ ```
450
+ "Build a brain for @Fireship, then tell me everything they've said about Rust"
451
+ ```
452
+
453
+ `build_brain` reads the channel into timestamped passages — interrupt it and
454
+ call it again to continue. `ask_brain` then answers from what was actually
455
+ said, returning the moments themselves so every claim can be checked against
456
+ the video. Run `build_brain` again a month later and it reads only the new
457
+ uploads.
458
+
361
459
  ## Testing
362
460
 
363
461
  ```bash
@@ -0,0 +1,502 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The shapes a channel brain is made of.
4
+ *
5
+ * Split from `schemas.ts` only for size; the rule there applies here too — a
6
+ * shape is declared once and used by both the persistence layer and the tools
7
+ * that report it, so the file on disk and the tool result cannot drift apart.
8
+ */
9
+ /** Why a video is, or is not, part of the corpus. */
10
+ export declare const brainVideoStateSchema: z.ZodObject<{
11
+ videoId: z.ZodString;
12
+ title: z.ZodString;
13
+ url: z.ZodString;
14
+ uploadDate: z.ZodString;
15
+ durationSeconds: z.ZodNumber;
16
+ state: z.ZodEnum<["pending", "indexed", "no-captions", "failed", "excluded"]>;
17
+ chunkCount: z.ZodNumber;
18
+ wordCount: z.ZodNumber;
19
+ error: z.ZodOptional<z.ZodString>;
20
+ }, "strip", z.ZodTypeAny, {
21
+ title: string;
22
+ durationSeconds: number;
23
+ url: string;
24
+ uploadDate: string;
25
+ videoId: string;
26
+ state: "pending" | "indexed" | "no-captions" | "failed" | "excluded";
27
+ chunkCount: number;
28
+ wordCount: number;
29
+ error?: string | undefined;
30
+ }, {
31
+ title: string;
32
+ durationSeconds: number;
33
+ url: string;
34
+ uploadDate: string;
35
+ videoId: string;
36
+ state: "pending" | "indexed" | "no-captions" | "failed" | "excluded";
37
+ chunkCount: number;
38
+ wordCount: number;
39
+ error?: string | undefined;
40
+ }>;
41
+ export type BrainVideoState = z.infer<typeof brainVideoStateSchema>;
42
+ /** One passage of speech, addressable by timestamp. */
43
+ export declare const brainChunkSchema: z.ZodObject<{
44
+ id: z.ZodString;
45
+ videoId: z.ZodString;
46
+ title: z.ZodString;
47
+ startSeconds: z.ZodNumber;
48
+ endSeconds: z.ZodNumber;
49
+ text: z.ZodString;
50
+ }, "strip", z.ZodTypeAny, {
51
+ id: string;
52
+ title: string;
53
+ startSeconds: number;
54
+ endSeconds: number;
55
+ text: string;
56
+ videoId: string;
57
+ }, {
58
+ id: string;
59
+ title: string;
60
+ startSeconds: number;
61
+ endSeconds: number;
62
+ text: string;
63
+ videoId: string;
64
+ }>;
65
+ export type BrainChunk = z.infer<typeof brainChunkSchema>;
66
+ export declare const brainChunkFileSchema: z.ZodObject<{
67
+ version: z.ZodNumber;
68
+ chunks: z.ZodArray<z.ZodObject<{
69
+ id: z.ZodString;
70
+ videoId: z.ZodString;
71
+ title: z.ZodString;
72
+ startSeconds: z.ZodNumber;
73
+ endSeconds: z.ZodNumber;
74
+ text: z.ZodString;
75
+ }, "strip", z.ZodTypeAny, {
76
+ id: string;
77
+ title: string;
78
+ startSeconds: number;
79
+ endSeconds: number;
80
+ text: string;
81
+ videoId: string;
82
+ }, {
83
+ id: string;
84
+ title: string;
85
+ startSeconds: number;
86
+ endSeconds: number;
87
+ text: string;
88
+ videoId: string;
89
+ }>, "many">;
90
+ }, "strip", z.ZodTypeAny, {
91
+ version: number;
92
+ chunks: {
93
+ id: string;
94
+ title: string;
95
+ startSeconds: number;
96
+ endSeconds: number;
97
+ text: string;
98
+ videoId: string;
99
+ }[];
100
+ }, {
101
+ version: number;
102
+ chunks: {
103
+ id: string;
104
+ title: string;
105
+ startSeconds: number;
106
+ endSeconds: number;
107
+ text: string;
108
+ videoId: string;
109
+ }[];
110
+ }>;
111
+ export declare const brainPhraseSchema: z.ZodObject<{
112
+ phrase: z.ZodString;
113
+ videoCount: z.ZodNumber;
114
+ occurrences: z.ZodNumber;
115
+ }, "strip", z.ZodTypeAny, {
116
+ videoCount: number;
117
+ phrase: string;
118
+ occurrences: number;
119
+ }, {
120
+ videoCount: number;
121
+ phrase: string;
122
+ occurrences: number;
123
+ }>;
124
+ export declare const brainMonthlyUploadsSchema: z.ZodObject<{
125
+ month: z.ZodString;
126
+ videos: z.ZodNumber;
127
+ }, "strip", z.ZodTypeAny, {
128
+ month: string;
129
+ videos: number;
130
+ }, {
131
+ month: string;
132
+ videos: number;
133
+ }>;
134
+ /**
135
+ * What the server can state about a channel without inferring anything.
136
+ *
137
+ * Everything here is counted or measured. Claims about what a creator thinks
138
+ * belong in the profile, which a model writes from quotes it can cite.
139
+ */
140
+ export declare const brainStatsSchema: z.ZodObject<{
141
+ videoCount: z.ZodNumber;
142
+ excludedCount: z.ZodNumber;
143
+ indexedCount: z.ZodNumber;
144
+ noCaptionsCount: z.ZodNumber;
145
+ failedCount: z.ZodNumber;
146
+ pendingCount: z.ZodNumber;
147
+ chunkCount: z.ZodNumber;
148
+ totalWords: z.ZodNumber;
149
+ medianWordsPerMinute: z.ZodNumber;
150
+ firstUpload: z.ZodOptional<z.ZodString>;
151
+ lastUpload: z.ZodOptional<z.ZodString>;
152
+ uploadsPerMonth: z.ZodArray<z.ZodObject<{
153
+ month: z.ZodString;
154
+ videos: z.ZodNumber;
155
+ }, "strip", z.ZodTypeAny, {
156
+ month: string;
157
+ videos: number;
158
+ }, {
159
+ month: string;
160
+ videos: number;
161
+ }>, "many">;
162
+ recurringPhrases: z.ZodOptional<z.ZodArray<z.ZodObject<{
163
+ phrase: z.ZodString;
164
+ videoCount: z.ZodNumber;
165
+ occurrences: z.ZodNumber;
166
+ }, "strip", z.ZodTypeAny, {
167
+ videoCount: number;
168
+ phrase: string;
169
+ occurrences: number;
170
+ }, {
171
+ videoCount: number;
172
+ phrase: string;
173
+ occurrences: number;
174
+ }>, "many">>;
175
+ }, "strip", z.ZodTypeAny, {
176
+ videoCount: number;
177
+ chunkCount: number;
178
+ excludedCount: number;
179
+ indexedCount: number;
180
+ noCaptionsCount: number;
181
+ failedCount: number;
182
+ pendingCount: number;
183
+ totalWords: number;
184
+ medianWordsPerMinute: number;
185
+ uploadsPerMonth: {
186
+ month: string;
187
+ videos: number;
188
+ }[];
189
+ firstUpload?: string | undefined;
190
+ lastUpload?: string | undefined;
191
+ recurringPhrases?: {
192
+ videoCount: number;
193
+ phrase: string;
194
+ occurrences: number;
195
+ }[] | undefined;
196
+ }, {
197
+ videoCount: number;
198
+ chunkCount: number;
199
+ excludedCount: number;
200
+ indexedCount: number;
201
+ noCaptionsCount: number;
202
+ failedCount: number;
203
+ pendingCount: number;
204
+ totalWords: number;
205
+ medianWordsPerMinute: number;
206
+ uploadsPerMonth: {
207
+ month: string;
208
+ videos: number;
209
+ }[];
210
+ firstUpload?: string | undefined;
211
+ lastUpload?: string | undefined;
212
+ recurringPhrases?: {
213
+ videoCount: number;
214
+ phrase: string;
215
+ occurrences: number;
216
+ }[] | undefined;
217
+ }>;
218
+ export type BrainStats = z.infer<typeof brainStatsSchema>;
219
+ /** The on-disk document. */
220
+ export declare const brainManifestSchema: z.ZodObject<{
221
+ version: z.ZodNumber;
222
+ channel: z.ZodObject<{
223
+ name: z.ZodString;
224
+ channelId: z.ZodString;
225
+ handle: z.ZodString;
226
+ subscriberCount: z.ZodNumber;
227
+ channelUrl: z.ZodString;
228
+ description: z.ZodString;
229
+ }, "strip", z.ZodTypeAny, {
230
+ description: string;
231
+ name: string;
232
+ channelId: string;
233
+ handle: string;
234
+ subscriberCount: number;
235
+ channelUrl: string;
236
+ }, {
237
+ description: string;
238
+ name: string;
239
+ channelId: string;
240
+ handle: string;
241
+ subscriberCount: number;
242
+ channelUrl: string;
243
+ }>;
244
+ /**
245
+ * A brain is built from one caption language. Defaulted rather than required
246
+ * so a manifest written before this existed still reads back.
247
+ */
248
+ language: z.ZodDefault<z.ZodString>;
249
+ createdAt: z.ZodString;
250
+ updatedAt: z.ZodString;
251
+ videos: z.ZodType<Record<string, {
252
+ title: string;
253
+ durationSeconds: number;
254
+ url: string;
255
+ uploadDate: string;
256
+ videoId: string;
257
+ state: "pending" | "indexed" | "no-captions" | "failed" | "excluded";
258
+ chunkCount: number;
259
+ wordCount: number;
260
+ error?: string | undefined;
261
+ }>, z.ZodTypeDef, Record<string, unknown>>;
262
+ stats: z.ZodObject<{
263
+ videoCount: z.ZodNumber;
264
+ excludedCount: z.ZodNumber;
265
+ indexedCount: z.ZodNumber;
266
+ noCaptionsCount: z.ZodNumber;
267
+ failedCount: z.ZodNumber;
268
+ pendingCount: z.ZodNumber;
269
+ chunkCount: z.ZodNumber;
270
+ totalWords: z.ZodNumber;
271
+ medianWordsPerMinute: z.ZodNumber;
272
+ firstUpload: z.ZodOptional<z.ZodString>;
273
+ lastUpload: z.ZodOptional<z.ZodString>;
274
+ uploadsPerMonth: z.ZodArray<z.ZodObject<{
275
+ month: z.ZodString;
276
+ videos: z.ZodNumber;
277
+ }, "strip", z.ZodTypeAny, {
278
+ month: string;
279
+ videos: number;
280
+ }, {
281
+ month: string;
282
+ videos: number;
283
+ }>, "many">;
284
+ recurringPhrases: z.ZodOptional<z.ZodArray<z.ZodObject<{
285
+ phrase: z.ZodString;
286
+ videoCount: z.ZodNumber;
287
+ occurrences: z.ZodNumber;
288
+ }, "strip", z.ZodTypeAny, {
289
+ videoCount: number;
290
+ phrase: string;
291
+ occurrences: number;
292
+ }, {
293
+ videoCount: number;
294
+ phrase: string;
295
+ occurrences: number;
296
+ }>, "many">>;
297
+ }, "strip", z.ZodTypeAny, {
298
+ videoCount: number;
299
+ chunkCount: number;
300
+ excludedCount: number;
301
+ indexedCount: number;
302
+ noCaptionsCount: number;
303
+ failedCount: number;
304
+ pendingCount: number;
305
+ totalWords: number;
306
+ medianWordsPerMinute: number;
307
+ uploadsPerMonth: {
308
+ month: string;
309
+ videos: number;
310
+ }[];
311
+ firstUpload?: string | undefined;
312
+ lastUpload?: string | undefined;
313
+ recurringPhrases?: {
314
+ videoCount: number;
315
+ phrase: string;
316
+ occurrences: number;
317
+ }[] | undefined;
318
+ }, {
319
+ videoCount: number;
320
+ chunkCount: number;
321
+ excludedCount: number;
322
+ indexedCount: number;
323
+ noCaptionsCount: number;
324
+ failedCount: number;
325
+ pendingCount: number;
326
+ totalWords: number;
327
+ medianWordsPerMinute: number;
328
+ uploadsPerMonth: {
329
+ month: string;
330
+ videos: number;
331
+ }[];
332
+ firstUpload?: string | undefined;
333
+ lastUpload?: string | undefined;
334
+ recurringPhrases?: {
335
+ videoCount: number;
336
+ phrase: string;
337
+ occurrences: number;
338
+ }[] | undefined;
339
+ }>;
340
+ }, "strip", z.ZodTypeAny, {
341
+ channel: {
342
+ description: string;
343
+ name: string;
344
+ channelId: string;
345
+ handle: string;
346
+ subscriberCount: number;
347
+ channelUrl: string;
348
+ };
349
+ version: number;
350
+ videos: Record<string, {
351
+ title: string;
352
+ durationSeconds: number;
353
+ url: string;
354
+ uploadDate: string;
355
+ videoId: string;
356
+ state: "pending" | "indexed" | "no-captions" | "failed" | "excluded";
357
+ chunkCount: number;
358
+ wordCount: number;
359
+ error?: string | undefined;
360
+ }>;
361
+ language: string;
362
+ createdAt: string;
363
+ updatedAt: string;
364
+ stats: {
365
+ videoCount: number;
366
+ chunkCount: number;
367
+ excludedCount: number;
368
+ indexedCount: number;
369
+ noCaptionsCount: number;
370
+ failedCount: number;
371
+ pendingCount: number;
372
+ totalWords: number;
373
+ medianWordsPerMinute: number;
374
+ uploadsPerMonth: {
375
+ month: string;
376
+ videos: number;
377
+ }[];
378
+ firstUpload?: string | undefined;
379
+ lastUpload?: string | undefined;
380
+ recurringPhrases?: {
381
+ videoCount: number;
382
+ phrase: string;
383
+ occurrences: number;
384
+ }[] | undefined;
385
+ };
386
+ }, {
387
+ channel: {
388
+ description: string;
389
+ name: string;
390
+ channelId: string;
391
+ handle: string;
392
+ subscriberCount: number;
393
+ channelUrl: string;
394
+ };
395
+ version: number;
396
+ videos: Record<string, unknown>;
397
+ createdAt: string;
398
+ updatedAt: string;
399
+ stats: {
400
+ videoCount: number;
401
+ chunkCount: number;
402
+ excludedCount: number;
403
+ indexedCount: number;
404
+ noCaptionsCount: number;
405
+ failedCount: number;
406
+ pendingCount: number;
407
+ totalWords: number;
408
+ medianWordsPerMinute: number;
409
+ uploadsPerMonth: {
410
+ month: string;
411
+ videos: number;
412
+ }[];
413
+ firstUpload?: string | undefined;
414
+ lastUpload?: string | undefined;
415
+ recurringPhrases?: {
416
+ videoCount: number;
417
+ phrase: string;
418
+ occurrences: number;
419
+ }[] | undefined;
420
+ };
421
+ language?: string | undefined;
422
+ }>;
423
+ export type BrainManifest = z.infer<typeof brainManifestSchema>;
424
+ /** A retrieval result: a moment in a video, with the link that opens it. */
425
+ export declare const brainPassageSchema: z.ZodObject<{
426
+ videoId: z.ZodString;
427
+ title: z.ZodString;
428
+ startSeconds: z.ZodNumber;
429
+ startFormatted: z.ZodString;
430
+ endSeconds: z.ZodNumber;
431
+ score: z.ZodNumber;
432
+ text: z.ZodString;
433
+ url: z.ZodString;
434
+ }, "strip", z.ZodTypeAny, {
435
+ title: string;
436
+ url: string;
437
+ startSeconds: number;
438
+ startFormatted: string;
439
+ endSeconds: number;
440
+ text: string;
441
+ videoId: string;
442
+ score: number;
443
+ }, {
444
+ title: string;
445
+ url: string;
446
+ startSeconds: number;
447
+ startFormatted: string;
448
+ endSeconds: number;
449
+ text: string;
450
+ videoId: string;
451
+ score: number;
452
+ }>;
453
+ export type BrainPassage = z.infer<typeof brainPassageSchema>;
454
+ /** One row of list_brains. */
455
+ export declare const brainSummarySchema: z.ZodObject<{
456
+ channelId: z.ZodString;
457
+ name: z.ZodString;
458
+ handle: z.ZodString;
459
+ channelUrl: z.ZodString;
460
+ language: z.ZodString;
461
+ createdAt: z.ZodString;
462
+ updatedAt: z.ZodString;
463
+ hasProfile: z.ZodBoolean;
464
+ videoCount: z.ZodNumber;
465
+ indexedCount: z.ZodNumber;
466
+ chunkCount: z.ZodNumber;
467
+ }, "strip", z.ZodTypeAny, {
468
+ name: string;
469
+ channelId: string;
470
+ handle: string;
471
+ channelUrl: string;
472
+ videoCount: number;
473
+ chunkCount: number;
474
+ indexedCount: number;
475
+ language: string;
476
+ createdAt: string;
477
+ updatedAt: string;
478
+ hasProfile: boolean;
479
+ }, {
480
+ name: string;
481
+ channelId: string;
482
+ handle: string;
483
+ channelUrl: string;
484
+ videoCount: number;
485
+ chunkCount: number;
486
+ indexedCount: number;
487
+ language: string;
488
+ createdAt: string;
489
+ updatedAt: string;
490
+ hasProfile: boolean;
491
+ }>;
492
+ export declare const brainLockSchema: z.ZodObject<{
493
+ pid: z.ZodNumber;
494
+ startedAt: z.ZodString;
495
+ }, "strip", z.ZodTypeAny, {
496
+ pid: number;
497
+ startedAt: string;
498
+ }, {
499
+ pid: number;
500
+ startedAt: string;
501
+ }>;
502
+ //# sourceMappingURL=brain-schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain-schemas.d.ts","sourceRoot":"","sources":["../src/brain-schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;GAMG;AAEH,qDAAqD;AACrD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAchC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,uDAAuD;AACvD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;EAO3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAG/B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;EAI5B,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;EAGpC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,4BAA4B;AAC5B,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;IAG9B;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMH,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,8BAA8B;AAC9B,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAY7B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;EAG1B,CAAC"}