yt-mem-ai β a local YouTube memory for your AI assistant
Give Claude, Codex, Cursor, or any MCP host the ability to watch YouTube for
you: transcribe videos, remember them, follow your subscriptions, and turn all
of it into summaries, timestamped highlights, Q&A, digests, and video reels.
Everything runs on your machine β no cloud service, no API key.

One command, two questions β here it's wiring the skills into Codex.
Example β "make a presentation from this video"
(Andrej Karpathy: From Vibe Coding to Agentic Engineering, Sequoia, 30 min) β
13 slides, PDF,
every quote timestamped from the transcript. Ingest to deck in one request.
Table of Contents
Features
- π§ Transcribes any video β YouTube captions when they exist (fast, any
language), offline Whisper when they don't.
- π§ Remembers what you watched β every transcript is stored and indexed
locally, so your library stays searchable forever. Nothing leaves your machine.
- π Finds the moment β ask "what did that video say about X" and get the
answer with a timestamp you can jump to.
- π‘ Follows your subscriptions β picks up new uploads and turns the day into
one digest.
- βοΈ Your assistant does the writing β summaries, highlights, Q&A, slide
decks, all in the video's own language, using the model you already pay for.
- β€οΈ Learns your taste β like or dislike videos and get recommendations from
your own library.
- π¬ Makes media too β clickable highlight docs, still frames, and rendered
supercut reels.
- π Works with your tools β Claude Code, Claude Desktop, Codex, Cursor,
Antigravity, OpenClaw, Hermes: skills or MCP, your pick.
How to install
1. Connect your assistant β
curl -LsSf https://raw.githubusercontent.com/dasein108/yt-mem-ai/main/install.sh | sh
An interactive wizard opens. Pick what you want, tick your apps, press enter:
step 1/2 β what (pick one) step 2/2 β where (tick any)
> Plugin skills + CLI [x] Claude Code [ ] Claude Desktop
MCP typed tools [x] Codex [ ] Cursor
[ ] Antigravity [ ] OpenClaw [ ] Hermes
Plugin teaches your assistant to act on plain requests β "summarize this
video". MCP gives it a set of tools instead. Not sure? Start with Plugin;
you can run the wizard again for the other.
It installs everything it needs, ticks what you already have, and removes
anything you untick (it shows a plan and asks first). Then restart the app
and try: summarize 'https://youtu.be/β¦'.
Already know what you want? Skip the questions:
curl -LsSf https://raw.githubusercontent.com/dasein108/yt-mem-ai/main/install.sh \
| sh -s -- --plugin --claude-code --codex
curl -LsSf https://raw.githubusercontent.com/dasein108/yt-mem-ai/main/install.sh \
| sh -s -- --mcp --claude-desktop --cursor
Hosts: --claude-code --claude-desktop --codex --cursor --antigravity
--openclaw --hermes, or --all. Full flag list and uninstall notes:
integrations/README.md. Rather have an agent do it?
Paste integrations/PROMPT.md into any assistant.
2. MCP by hand β one config entry, self-installing
No prior install needed: uvx fetches the package the first time the host
launches the server, and keeps it cached afterwards. Drop this into your host's
MCP config:
{
"mcpServers": {
"yt-mem-ai": {
"command": "uvx",
"args": ["--from", "yt-mem-ai[mcp]", "yt-ai-mcp"]
}
}
}
That's the whole setup β no paths, no env block. Settings live in
~/.yt-mem-ai/config.env and the agent can write them itself with the
config_set tool (or you with yt-ai config set).
| Host | Where that JSON goes |
|---|
| Claude Desktop | macOS ~/Library/Application Support/Claude/claude_desktop_config.json Β· Windows %APPDATA%\Claude\claude_desktop_config.json β restart the app |
| Claude Code | claude mcp add -s user yt-mem-ai -- uvx --from 'yt-mem-ai[mcp]' yt-ai-mcp |
| Cursor | ~/.cursor/mcp.json (reload Cursor) |
| Antigravity | ~/.gemini/config/mcp_config.json (restart) |
| Codex | ~/.codex/config.toml β TOML, see below (or codex mcp add yt-mem-ai -- uvx --from 'yt-mem-ai[mcp]' yt-ai-mcp) |
| OpenClaw | openclaw mcp add yt-mem-ai --command uvx --arg --from --arg 'yt-mem-ai[mcp]' --arg yt-ai-mcp (or ~/.openclaw/openclaw.json β mcp.servers) |
| Hermes | ~/.hermes/config.yaml under mcp_servers: β YAML, see below |
[mcp_servers.yt-mem-ai]
command = "uvx"
args = ["--from", "yt-mem-ai[mcp]", "yt-ai-mcp"]
mcp_servers:
yt-mem-ai:
command: "uvx"
args: ["--from", "yt-mem-ai[mcp]", "yt-ai-mcp"]
enabled: true
Restart the app and the tools show up β see Usage for what they do.
Nothing appeared, or the host timed out? The first launch downloads
dependencies and can outlast the host's startup check. Run
uvx --from 'yt-mem-ai[mcp]' yt-ai-mcp --help once, then reopen the app. If
the host still can't start it, give it absolute paths β uv tool install 'yt-mem-ai[mcp]' and use which yt-ai-mcp as command with "args": []
(GUI apps often don't see ~/.local/bin on their PATH).
3. Claude Desktop β skills (in the app)
Desktop stores plugins on your Claude account, not on disk, so nothing can
install them for you. It takes a minute in the app:
Customize (left sidebar) β Plugins β Personal plugins β + β
Add marketplace β Add from a repository β
https://github.com/dasein108/yt-mem-ai β Add β Install yt-mem-ai
Then ask: summarize 'https://youtu.be/β¦'. Uninstall the same way. The same
plugin also works on claude.ai and Cowork. Prefer tools over skills? The
MCP setup above works for Desktop too β and that one can be scripted.
Skills by hand β Codex, Cursor, Antigravity, OpenClaw, Hermes
Each host loads SKILL.md files from a user-scope directory: Codex
~/.codex/skills/ (CLI and IDE share it, v0.117.0+), Cursor ~/.cursor/skills/,
Antigravity ~/.gemini/skills/, OpenClaw ~/.agents/skills/, Hermes
~/.hermes/skills/ (where they become /yt and /yt-agent).
cp -R skills/yt skills/yt-agent ~/.codex/skills/
for s in yt yt-agent; do
mkdir -p ~/.codex/skills/$s
curl -LsSf "https://raw.githubusercontent.com/dasein108/yt-mem-ai/main/skills/$s/SKILL.md" \
-o ~/.codex/skills/$s/SKILL.md
done
Codex extras: the /yt-* prompts (integrations/codex/prompts/*.md β
~/.codex/prompts/) and integrations/codex/AGENTS.md β ~/.codex/AGENTS.md.
Full guide: skills/README.md.
4. The CLI on its own
The skills drive it, but it's a perfectly good standalone tool:
uvx yt-mem-ai --help
uv tool install yt-mem-ai
Needs Python 3.11+ and uv; ffmpeg only for
supercut / frame.
The desktop UI lives in a separate repo:
yt-mem-ai-desktop β it
depends on this package and runs its own local REST API.
Getting Started
Installed and host restarted? You're ready. Just talk to your assistant β the
skills (or MCP prompts + analyze_video) do the ingesting for you:
"Summarize https://youtu.be/dQw4w9WgXcQ"
β ingests the video (captions β whisper), then writes an executive summary
plus key points, in the video's own language.
"Give me the highlights of that video with timestamps"
β 3β8 deep-linked moments (watch?v=β¦&t=123s) anchored by semantic search.
"What did I watch about retrieval-augmented generation?"
β searches every transcript in your library and quotes the moments.
"Process my subscriptions into today's digest"
β discovers new uploads, ingests them, writes digests/<DATE>.md.
Prefer the terminal? The same first run:
yt-ai fetch 'https://www.youtube.com/watch?v=VIDEO_ID'
yt-ai search "what was said about embeddings"
yt-ai status
Everything lands in ~/.yt-mem-ai/ (library, logs, downloads).
The first run is slow β that's expected. Installing pulls the ML stack
(torch, LanceDB, sentence-transformers β 1 GB on disk), and your first
fetch or search downloads the embedding model on top of that. If a video
has no captions, the Whisper model (small, β 460 MB) downloads too β the
captions path never needs it. All of it is cached, so it happens once, not per
video. On a GUI host the first MCP launch can outlast the app's startup check
for the same reason: run uvx --from 'yt-mem-ai[mcp]' yt-ai-mcp --help once in
a terminal to warm the cache, then reopen the app.
Daily routine
yt-ai discover
yt-ai fetch-pending
then in your assistant: "process subscriptions" β per-video summaries and
digests/<DATE>.md, and optionally
yt-ai compile --out compilations/$(date +%F).md for the day's highlights as
clickable links.
Usage
Talking to your assistant (skills & prompts)
Two skills ship with the plugin. MCP hosts get the same playbooks as prompts
(yt_summarize, yt_highlights, yt_qa, yt_presentation, yt_digest,
yt_review, yt_group), so nothing is lost without skill support.
| Skill | Use it for |
|---|
yt | the entry point β any operation and the full pipelines (daily routine, single video); hands analysis to yt-agent |
yt-agent | the scenarios β one video β summary / highlights / Q&A / presentation; subscriptions β daily digest; a cross-video review; a group of videos |
| What you say | What happens | Where it lands |
|---|
"summarize <url>" | ingest β executive summary + key points | chat, save_summary in the store |
"highlights for <url>" | 3β8 timestamped, deep-linked moments | chat + store |
"Q&A about <url>" | 3β6 grounded question/answer pairs | chat + store |
"make a presentation from <url>" | ----separated slide deck | slides/<id>.md |
| "process subscriptions" | discover β ingest β per-video analysis | digests/<DATE>.md |
| "review what I watched this week" | cross-video themes essay | reviews/<DATE>.md |
"analyze these videos: <ids/urls/channel>" | ingest a set β per-video + synthesis | groups/<label>.md |
Skills never touch the database directly β they call the CLI, so the same
workflow runs on any host. Install or paste them by hand:
skills/README.md.
The yt-ai-mcp server exposes the whole engine as typed tools. analyze_video
is the one-step entry point: it ingests and returns the transcript so the model
can write the summary itself.
| Category | Tool | Description |
|---|
| Analyze | analyze_video | Ingest a video and return its transcript (+ title, channel, language, existing summary). Call this first for any summarize / highlight / Q&A request. |
| Ingest | fetch | Download + transcribe + embed one video (force, captions_only, prefer_whisper). |
| discover | New uploads from your subscription feed (incremental; after, deep, min_duration). |
| fetch_pending | Batch-ingest everything discovered since a date. |
| channel_list | Enumerate a channel's recent uploads (no ingest). |
| Library | search | Hybrid / vector / FTS search over every chunk, with timestamps. |
| show | Metadata + full transcript for one video. |
| list_videos | List stored videos by status / date / channel. |
| status | Counts by lifecycle status. |
| Summaries | save_summary | Persist a summary + highlights + Q&A so compile and supercut can use them. |
| Taste | like / dislike | Record feedback for one video. |
| recommend | Rank unrated videos by similarity to what you liked. |
| Media | compile | Deep-linked highlights doc, budget-bounded by minutes. |
| supercut | Render the highlight selection into one labeled mp4 (needs ffmpeg + network). |
| frame | Grab a still frame at a timestamp. |
| Config | config_list / config_get | Inspect settings, values, and where each came from (secrets masked). |
| config_set / config_unset | Change settings from chat β proxy creds, cookies browser, embedding model. |
| Maintenance | reembed | Re-embed the whole library after changing the embedding model. |
Server details, prompt list, and raw config: integrations/mcp/README.md.
CLI commands
yt-ai fetch <url>
yt-ai fetch <url> --captions-only
yt-ai transcript <url>
yt-ai discover
yt-ai fetch-pending
yt-ai list
yt-ai show <video_id>
yt-ai status
yt-ai search "<query>"
yt-ai save-summary <id> "<summary>" --highlights '<json>' --qa '<json>'
yt-ai like <video_id>
yt-ai dislike <video_id>
yt-ai recommend
yt-ai compile
yt-ai supercut
yt-ai frame <video_id> --at <ts>
yt-ai reembed
yt-ai channel-list <url>
yt-ai config list
Configuration
Nothing is required to start β defaults put the store, logs, and downloads
under ~/.yt-mem-ai/ and use a local embedding model. Tune it when you need to:
| Setting | What it does |
|---|
YT_STORE_PATH | LanceDB directory |
YT_EMBEDDING_BACKEND | local (sentence-transformers) or openai |
YT_EMBEDDING_MODEL | e.g. paraphrase-multilingual-MiniLM-L12-v2 for non-English libraries |
OPENAI_API_KEY | only for the openai embedding backend |
YT_COOKIES_BROWSER | chrome/firefox/β¦ β fixes YouTube's "confirm you're not a bot" |
YT_CAPTION_LANGS | preferred caption languages (default en, falls back to any track) |
WEBSHARE_PROXY_*, YT_USE_WEBSHARE | optional rotating proxy |
YT_CHUNK_TARGET_S | chunk length for embeddings |
Configure from the CLI or an agent β instead of editing .env by hand, use
yt-ai config (or the MCP config_* tools, so an agent can reconfigure itself
from chat):
yt-ai config list
yt-ai config set WEBSHARE_PROXY_USERNAME <user>
yt-ai config set WEBSHARE_PROXY_PASSWORD <pass>
yt-ai config set YT_EMBEDDING_MODEL paraphrase-multilingual-MiniLM-L12-v2
yt-ai config get OPENAI_API_KEY
set writes the global config file (~/.yt-mem-ai/config.env) by default so the
MCP server picks it up regardless of its working directory; --project writes
./.env. Precedence: process env > project .env > global config file, and
config list shows which one each value comes from.
Under the hood
Everything below is optional reading β internals, tuning, and developer notes.
How it works
yt-dlp β captions (or Whisper) β chunks β embeddings β LanceDB
β
your assistant reads + writes summaries back
The CLI does the heavy IO and owns the store; the skills and MCP tools are thin
callers. A video moves through discovered β downloaded β transcribed β summarized; live streams get a terminal stream status and are skipped by batch
ingestion (transcribe one on demand with yt-ai fetch <url>). Storage is an
embedded LanceDB (videos, channels, transcripts, chunks, summaries,
feedback), with per-chunk vectors plus a full-text index β that's what makes
search hybrid.
Command details
Rate & recommend β like/dislike videos you've fetched, then yt-ai recommend ranks the rest by similarity to what you liked (minus what you
disliked), using their transcript embeddings. Before you've liked anything it
falls back to most-recently-published.
discover is incremental β it pulls the newest feed entries in one flat
call (capped by YT_DISCOVER_FEED_LIMIT), stamps each with an approximate
timestamp, and keeps only those newer than the last run's high-water mark minus
a 1h overlap (YT_DISCOVER_OVERLAP_S), so hour-rounded dates never miss a
boundary video. Already-processed videos are filtered out. --after YYYY-MM-DD
overrides the cutoff.
compile vs supercut β compile renders the day's highlights as markdown
deep links (watch?v=ID&t=<start>s), newest-video-first and bounded by
--max-minutes (default 20); it's instant and prints to stdout unless you pass
--out. supercut re-downloads each highlight's section at 720p, burns a label
onto it, and concatenates everything into supercuts/<date>.mp4 plus a
.refs.md sidecar listing sources (and any clips skipped after a failure) β much
slower, needs network + ffmpeg, but shareable.
Embeddings, proxy, and other tuning
Embeddings: YT_EMBEDDING_BACKEND=local|openai. Local uses
sentence-transformers (YT_EMBEDDING_MODEL, default all-MiniLM-L6-v2) β for
non-English libraries set paraphrase-multilingual-MiniLM-L12-v2 (384-d, 50+
languages) so semantic search works cross-language. openai uses
text-embedding-3-small|large (needs OPENAI_API_KEY). After changing the model,
run yt-ai reembed to migrate the existing library (re-embeds all chunks; no
re-fetch).
Proxy / VLESS: YT_USE_WEBSHARE defaults off. If you already run a
system-level proxy/VPN (VLESS/Xray etc.), leave it off β traffic rides that
tunnel. Stacking the Webshare proxy on top breaks the authenticated
subscription feed (its CONNECT tunnel returns 405). Only set
YT_USE_WEBSHARE=true if you have no other proxy and YouTube rate-limits your
raw IP. Discover tuning: YT_DISCOVER_FEED_LIMIT (newest-N cap, default 60),
YT_DISCOVER_OVERLAP_S (incremental overlap, default 3600), YT_DISCOVER_TIMEOUT_S.
Working from a checkout? uv sync --extra dev, then cp .env.example .env
if you'd rather keep settings project-local than in ~/.yt-mem-ai/config.env.
Use as a Python package
yt-mem-ai is a normal library β the CLI is a thin Typer shell over run_*
cores you can call directly. Everything is local: no server, no API key (unless
you pick the openai embedding backend).
from dataclasses import replace
from pathlib import Path
from yt_mem_ai.config import load_config
from yt_mem_ai.cli import open_store, run_fetch, run_search, run_list, run_save_summary
from yt_mem_ai.store import db as store
cfg = replace(load_config(), store_path=Path("~/.yt-mem-ai/lance").expanduser())
db = open_store(cfg)
video_id = run_fetch("https://www.youtube.com/watch?v=dQw4w9WgXcQ", cfg, db=db)
video = store.get_video(db, video_id)
text = store.get_transcript_text(db, video_id)
print(video.title, video.channel, video.duration_s, len(text or ""))
for c in store.list_chunks(db, video_id)[:3]:
print(f"[{c['start_s']:.0f}s] {c['text'][:80]}")
for hit in run_search(cfg, "retrieval augmented generation", mode="hybrid", k=5, db=db):
print(hit["video_id"], hit["start_s"], hit["text"][:100])
summary_md = my_llm(text)
run_save_summary(
cfg, video_id, summary_md,
highlights_json='[{"t": 42, "text": "key moment"}]',
qa_json='[{"q": "What is it about?", "a": "..."}]',
db=db,
)
print(store.get_summary(db, video_id))
for v in run_list(cfg, status="transcribed", since="2026-01-01", db=db):
print(v.video_id, v.published_at, v.title)
Other cores, same shape (run_x(cfg, ..., db=db)): run_discover,
run_fetch_pending, run_channel_list, run_recommend, run_feedback,
run_compile, run_supercut, run_frame, run_reembed. Lower-level pieces
are importable too β yt_mem_ai.download.download, yt_mem_ai.transcript.get_transcript,
yt_mem_ai.store.embeddings.build_embedder / chunk_segments,
yt_mem_ai.store.db (LanceDB CRUD + search_chunks).
The store is plain LanceDB, so you can also open it directly:
import lancedb
from pathlib import Path
tbl = lancedb.connect(Path("~/.yt-mem-ai/lance").expanduser()).open_table("chunks")
df = tbl.to_pandas()
Logging
The CLI writes structured JSON events to logs/common.jsonl (via
obs.log_event/blog) β one object per line, {ts, source, level, event, msg, ...ctx}. Override the path with YT_LOG_FILE; it's gitignored. Inspect with jq:
jq -c 'select(.level=="error")' logs/common.jsonl
tail -f logs/common.jsonl | jq -c '{ts,event,msg}'
Tests
uv run pytest -q
YT_RUN_INTEGRATION=1 uv run pytest -q
Releasing (maintainers)
Version comes from the git tag (hatch-vcs). Tag, build, and publish:
git tag -a vX.Y.Z -m "vX.Y.Z" && git push origin vX.Y.Z
uv build
sh scripts/publish.sh dist/yt_mem_ai-X.Y.Z*
scripts/publish.sh loads UV_PUBLISH_TOKEN from .env (gitignored) on demand,
so you don't export it each time β add UV_PUBLISH_TOKEN=pypi-β¦ to .env once
(see .env.example). Equivalently: set -a; . ./.env; set +a; uv publish dist/*.
Rotate the token on PyPI if it's ever exposed.