|
| 1 | +--- |
| 2 | +name: ingest-youtube |
| 3 | +description: >- |
| 4 | + Pull the transcript from a YouTube link and file distilled insights into the |
| 5 | + vault. Triggers on "/ingest-youtube", a pasted youtube.com or youtu.be link |
| 6 | + with intent to capture, "get the transcript", "extract insights from this |
| 7 | + video", "summarise this YouTube video into the vault", or any request to turn |
| 8 | + a YouTube video into a vault note. Runs in an interactive session |
| 9 | + (auto-caption-only videos need browser cookies, which can prompt the macOS |
| 10 | + keychain). Don't trigger when the user only wants a video summarised in chat |
| 11 | + with no filing, or for non-YouTube media. |
| 12 | +--- |
| 13 | + |
| 14 | +# Ingest YouTube |
| 15 | + |
| 16 | +Acquisition and distillation layer for YouTube videos, a sibling to ingest-inbox |
| 17 | +(email) and ingest-meetings. It fetches a video's captions, distils them into |
| 18 | +useful notes, and files them. It **defers all filing conventions to the |
| 19 | +vault-ingest skill and all tone, folder, tag and privacy rules to the vault |
| 20 | +`CLAUDE.md`**. Do not duplicate that logic here; apply it. |
| 21 | + |
| 22 | +Default to keeping **distilled insights only**, not the raw transcript, because |
| 23 | +the transcript is third-party content. Save the raw transcript alongside a note |
| 24 | +only if the user asks. |
| 25 | + |
| 26 | +## Prerequisites |
| 27 | + |
| 28 | +- `yt-dlp` via Python: check `python3 -m yt_dlp --version`. If missing, install |
| 29 | + with `python3 -m pip install --user --upgrade yt-dlp`. |
| 30 | +- The fetch step needs outbound network, so run it with the Bash sandbox |
| 31 | + disabled. |
| 32 | + |
| 33 | +## Workflow |
| 34 | + |
| 35 | +### 0. Anchor the date |
| 36 | + |
| 37 | +Confirm today's date from session context and state it before stamping files. |
| 38 | + |
| 39 | +### 1. Take the link(s) |
| 40 | + |
| 41 | +Get the YouTube URL(s) from the user. Note the video id (the `v=` value or the |
| 42 | +`youtu.be/<id>` slug). |
| 43 | + |
| 44 | +### 2. Fetch captions (no video download) |
| 45 | + |
| 46 | +First attempt, no cookies (works for videos with manual captions): |
| 47 | + |
| 48 | +``` |
| 49 | +python3 -m yt_dlp --skip-download --write-subs --write-auto-subs \ |
| 50 | + --sub-langs "en.*,en" --sub-format vtt -o "/tmp/ytt.%(ext)s" "<URL>" |
| 51 | +``` |
| 52 | + |
| 53 | +If yt-dlp reports *"a PO token was not provided"* or *"no subtitles for the |
| 54 | +requested languages"*, the video has auto-captions only and YouTube is gating |
| 55 | +them. Retry with the user's browser cookies. **Ask which browser first** |
| 56 | +(Chrome / Safari / Brave / Firefox), then add: |
| 57 | + |
| 58 | +``` |
| 59 | + --cookies-from-browser <browser> |
| 60 | +``` |
| 61 | + |
| 62 | +Warn that Chrome/Brave/Edge may trigger a one-time macOS "Safe Storage" keychain |
| 63 | +prompt the user must approve. Run this in the background so the keychain prompt |
| 64 | +does not block. Do not scan or enumerate browser cookie stores yourself; let |
| 65 | +yt-dlp read only what it needs from the single named browser. |
| 66 | + |
| 67 | +### 3. Flatten the VTT |
| 68 | + |
| 69 | +Strip the WEBVTT headers, cue timings, inline `<...>` tags and the |
| 70 | +rolling-duplicate lines auto-captions emit. This step is local (no network), so |
| 71 | +it runs under the normal sandbox. Use `*.en-orig.vtt` if `*.en.vtt` is absent. |
| 72 | +The helper is inlined so the skill is self-contained in any agent runtime: |
| 73 | + |
| 74 | +``` |
| 75 | +python3 - /tmp/ytt.en.vtt /tmp/ytt.txt <<'PY' |
| 76 | +import re, sys |
| 77 | +raw = open(sys.argv[1], encoding="utf-8").read() |
| 78 | +lines = [] |
| 79 | +for ln in raw.splitlines(): |
| 80 | + s = ln.strip() |
| 81 | + if not s or s.startswith(("WEBVTT", "NOTE", "Kind:", "Language:")) or "-->" in s: |
| 82 | + continue |
| 83 | + s = re.sub(r"<[^>]+>", "", s).replace(" ", " ").strip() |
| 84 | + if s and (not lines or s != lines[-1]): |
| 85 | + lines.append(s) |
| 86 | +out = [] |
| 87 | +for ln in lines: |
| 88 | + if out and (ln == out[-1] or out[-1].endswith(ln)): |
| 89 | + continue |
| 90 | + out.append(ln) |
| 91 | +text = re.sub(r"\s+", " ", " ".join(out)).strip() |
| 92 | +open(sys.argv[2], "w", encoding="utf-8").write(text + "\n") |
| 93 | +sys.stderr.write(f"words={len(text.split())} chars={len(text)}\n") |
| 94 | +PY |
| 95 | +``` |
| 96 | + |
| 97 | +It prints the word and character count to stderr. |
| 98 | + |
| 99 | +### 4. Distil |
| 100 | + |
| 101 | +Read the cleaned transcript. Produce a **reference note**, not a transcript |
| 102 | +dump: the video title and a source link, a one or two line summary, the key |
| 103 | +ideas/practices, and, for coaching or learning material, concrete prompts or |
| 104 | +takeaways the user can reuse. Keep British English, concise and plain per |
| 105 | +`CLAUDE.md`. |
| 106 | + |
| 107 | +### 5. File via vault-ingest conventions |
| 108 | + |
| 109 | +Place per PARA: coaching/learning usually `2-Areas/Personal Development/` or |
| 110 | +`3-Resources/`. Title Case filename. Frontmatter with `title`, `tags` |
| 111 | +(type + domain, e.g. `[reference, personal-development, coaching]`), the `source` |
| 112 | +URL, and `created`. Add `[[wiki links]]` to related notes. Apply the CLAUDE.md |
| 113 | +privacy rules (a private-area topic stays factual and unshared). |
| 114 | + |
| 115 | +### 6. Log |
| 116 | + |
| 117 | +If the vault keeps an ingest log, append a row (newest first, matching the log's |
| 118 | +schema): `date | title | folder | tags | source (YouTube <id>) | private`. No |
| 119 | +manual reindex: new and changed notes (including the log row) are picked up by |
| 120 | +the index on the next search, and the file watcher reindexes live edits; |
| 121 | +`mcp__hebb__reindex_vault` is only an escape hatch for a suspected-stale index. |
| 122 | + |
| 123 | +### 7. Clean up |
| 124 | + |
| 125 | +Remove the `/tmp/ytt.*` artefacts. Do not commit or push: if the vault runs a |
| 126 | +hebb sync job it auto-commits and pushes new content, so a manual commit just |
| 127 | +races it. (Only if a vault has no sync job, tell the user the change is |
| 128 | +uncommitted and let them decide.) |
| 129 | + |
| 130 | +## Notes |
| 131 | + |
| 132 | +- Manual-caption videos need no cookies; auto-caption-only videos do. |
| 133 | +- For several links, loop the same steps; dedupe by video id against the ingest |
| 134 | + log. |
0 commit comments