tenbrains · design spec
Let tenbrains analyze take a YouTube URL, pull the caption track for free, and run the transcript through the pipeline that already analyses tweets — same topic/summary/intent/concepts, same persistence, same --learn, search, and digest.
Mirrors src/x/client.ts (the tweet oEmbed path): an undocumented-but-free endpoint, pure parsers split from the network layer, structured errors.
ytInitialPlayerResponsefetch + regex/JSON parsing — the same toolkit the oEmbed path uses. Rejected: youtube-transcript (fragile, redundant), yt-dlp (external binary, breaks the self-contained stance), and the YouTube Data API captions.download (needs the owner's OAuth — the same dead end that makes X timelines a paid feature).
analyzeNo parallel command. analyze detects a YouTube URL in --url and routes to the transcript fetcher, exactly as --thread routes differently from a plain tweet. Everything after ingestion runs unchanged.
| Field | Source |
|---|---|
post.text | flattened transcript |
post.externalId | yt:<videoId> — distinct prefix, dedupes re-runs, no tweet-id collision |
post.url | canonical watch?v=<id> |
post.authorUsername | channel |
post.postedAt | upload date |
post.raw | video metadata → existing posts.raw_json (no migration) |
meta.source | "youtube" |
The analyzer prompt is tweet-tuned; pass an optional kind: "tweet" | "transcript" and swap one sentence to "You are analyzing a video transcript." Output schema untouched.
Reuses the existing PROVIDER_* scaffolding. Every "no captions" path points at the manual escape hatch analyze --transcript @file, which also makes the feature testable offline.
| Condition | Code | Exit |
|---|---|---|
| Not found / deleted / no caption tracks | NOT_FOUND | 3 |
| Private / age-restricted / region-blocked | PROVIDER_UNAUTHORIZED | 5 |
| Rate limited | PROVIDER_RATE_LIMITED | 5 |
| Network / timeout | PROVIDER_NETWORK | 5 |
| Markup changed, can't parse | PROVIDER_BAD_OUTPUT | 5 |
posts + analyses; metadata in raw_json — no schema change (stays v3).--summarize-first is a follow-up.--transcript, one live smoke — the same shape as the shipped thread feature.| Risk | Mitigation |
|---|---|
| YouTube changes watch-page markup | Pure, fixture-tested parsers → one-line fix. Same risk class as existing oEmbed HTML parsing. |
| Video genuinely has no captions | Structured NOT_FOUND → --transcript @file. Audio transcription deferred. |
| ToS / gray-area scraping | Same posture as tweet oEmbed + archive paths — public captions, personal research; noted in docs. |
| Token cost on long transcripts | Soft warning in v1; --summarize-first later. |
analyzerecommended vs. a dedicated tenbrains youtube command. Folding in reuses everything and adds no surface; a separate command is only worth it if video-specific features (chapters, timestamps, a viewer) are expected to grow.--transcript @file as the manual fallback — defer audio/Whisper transcription entirely?Closely matches the already-shipped thread feature: a ~200-line client module (pure parsers + orchestration), a ~40-line analyze integration, fixture tests + one offline command test + a live smoke, and docs. One focused PR through the existing branch → PR → CI (DCO + typecheck/lint/test) flow.