Metadata-Version: 2.4
Name: yoink-yt
Version: 0.3.0
Summary: YouTube downloader for AI assistants (MCP) and humans (TUI). Subtitles + search + persistent history. Minimal CLI core; TUI and MCP are optional extras.
Project-URL: Homepage, https://github.com/JayshKhan/yoink
Project-URL: Repository, https://github.com/JayshKhan/yoink
Project-URL: Issues, https://github.com/JayshKhan/yoink/issues
Author: Jaysh Khan
License: MIT
License-File: LICENSE
Keywords: ai-tools,claude,downloader,mcp,model-context-protocol,tui,youtube,yt-dlp
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Requires-Dist: yt-dlp>=2024.0.0
Provides-Extra: all
Requires-Dist: mcp[cli]>=1.0.0; extra == 'all'
Requires-Dist: textual<8.0.0,>=1.0.0; extra == 'all'
Provides-Extra: dev
Requires-Dist: mcp[cli]>=1.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: textual<8.0.0,>=1.0.0; extra == 'dev'
Provides-Extra: mcp
Requires-Dist: mcp[cli]>=1.0.0; extra == 'mcp'
Provides-Extra: tui
Requires-Dist: textual<8.0.0,>=1.0.0; extra == 'tui'
Description-Content-Type: text/markdown

<p align="center">
  <img src="src/yoink/assets/yoink.png" alt="yoink - MCP YouTube downloader for AI assistants" width="280">
</p>

<h1 align="center">yoink</h1>

<p align="center">
  <b>MCP server that lets AI assistants download YouTube videos.</b><br>
  <sub>Give Claude, Cursor, or any MCP-compatible AI the ability to download YouTube videos, playlists, and audio through natural language. Also ships with a terminal UI for humans.</sub>
</p>

<p align="center">
  <a href="https://modelcontextprotocol.io/"><img src="https://img.shields.io/badge/MCP-enabled-orange?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiPjxwYXRoIGQ9Ik0xMiAyYTEwIDEwIDAgMSAwIDAgMjAgMTAgMTAgMCAwIDAgMC0yMHoiLz48cGF0aCBkPSJNOCAxMmg4Ii8+PHBhdGggZD0iTTEyIDh2OCIvPjwvc3ZnPg==&logoColor=white" alt="MCP Enabled"></a>
  <a href="https://pypi.org/project/yoink-yt/"><img src="https://img.shields.io/pypi/v/yoink-yt?style=for-the-badge&label=PyPI&color=blue" alt="PyPI"></a>
  <a href="#-quick-start"><img src="https://img.shields.io/badge/python-3.10+-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54" alt="Python 3.10+"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=for-the-badge" alt="MIT License"></a>
</p>

<p align="center">
  <code>pip install yoink-yt</code>&nbsp;&nbsp;&middot;&nbsp;&nbsp;<a href="#-mcp-setup-for-ai-assistants">MCP Setup</a>&nbsp;&nbsp;&middot;&nbsp;&nbsp;<a href="#-terminal-ui-for-humans">Terminal UI</a>&nbsp;&nbsp;&middot;&nbsp;&nbsp;<a href="#-mcp-tools-api-reference">API Reference</a>
</p>

---

<br>

## What is yoink?

**yoink** is an [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that gives AI assistants like **Claude Desktop**, **Cursor**, **Windsurf**, and other MCP-compatible tools the ability to **download YouTube videos** on your behalf.

It also ships with a standalone **terminal UI (TUI)** for when you want to download videos yourself.

### The problem

Every YouTube downloader makes **you** do the work &mdash; find the URL, open a tool, pick a format, wait for it, rename the file, repeat.

### The yoink way

Just tell your AI assistant what you want:

> *"Download the latest 3Blue1Brown video in 720p"*
>
> *"Grab that playlist as audio-only MP3s"*
>
> *"Download this lecture with subtitles to my Desktop"*

Your AI handles the entire workflow &mdash; resolves the URL, picks the format, starts the download, tracks progress. No copy-pasting. No menus. Just ask.

<br>

## &#9889; Quick Start

Pick your install — yoink ships a tiny CLI core, with TUI and MCP as optional extras:

```bash
pip install yoink-yt              # plain CLI only (just yt-dlp)
pip install 'yoink-yt[tui]'       # + Terminal UI (textual)
pip install 'yoink-yt[mcp]'       # + MCP server for AI assistants
pip install 'yoink-yt[all]'       # everything
```

> [!IMPORTANT]
> **Upgrading from 0.1.x?** As of 0.3.0 the TUI and MCP server are optional extras and download history is persisted to `~/.yoink/state.db`. If `yoink` or `yoink-mcp` errors after upgrading, reinstall with the extra you need: `pip install --upgrade 'yoink-yt[all]'`.

<table>
<tr>
<td width="50%">

**For AI assistants (MCP server)**

```bash
pip install 'yoink-yt[mcp]'
yoink-mcp
```

Add to Claude Desktop, Cursor, or any MCP client &mdash; your AI gets 7 YouTube tools instantly.

</td>
<td width="50%">

**For humans (terminal UI)**

```bash
pip install 'yoink-yt[tui]'
yoink
```

A full terminal UI with progress bars, quality picker, playlist support, and keyboard shortcuts.

</td>
</tr>
</table>

**Just want to grab a video from your shell?** No extras needed:

```bash
pip install yoink-yt
yoink-cli "https://youtu.be/..." -q 720
```

> [!TIP]
> Install **ffmpeg** for best results &mdash; needed to merge video+audio streams and convert to MP3.
> ```
> brew install ffmpeg          # macOS
> sudo apt install ffmpeg      # Ubuntu/Debian
> ```

<br>

## &#129302; MCP Setup for AI Assistants

yoink exposes **7 tools** via the [Model Context Protocol](https://modelcontextprotocol.io/) over STDIO, giving any MCP-compatible AI assistant full YouTube download capabilities.

### Claude Desktop

Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "yoink": {
      "command": "yoink-mcp"
    }
  }
}
```

Restart Claude Desktop. Done &mdash; Claude can now download YouTube videos.

### Claude Code

Add to your `.mcp.json`:

```json
{
  "mcpServers": {
    "yoink": {
      "command": "yoink-mcp",
      "type": "stdio"
    }
  }
}
```

### Cursor / Windsurf / Other MCP Clients

Any MCP client that supports STDIO transport works. Point it at `yoink-mcp` as the command.

<details>
<summary><b>Using uv (from source instead of pip)</b></summary>

<br>

```json
{
  "mcpServers": {
    "yoink": {
      "command": "uv",
      "args": ["--directory", "/path/to/yoink", "run", "yoink-mcp"]
    }
  }
}
```

</details>

### Example prompts for your AI

| What you say | What yoink does |
|-------------|-----------------|
| *"Find me the latest 3Blue1Brown video and summarize it"* | `search_youtube` → `get_subtitles` → AI summarizes from transcript |
| *"Summarize this video: [url]"* | `get_subtitles` → AI works from the transcript text directly |
| *"Download this video: [url]"* | Downloads in best quality to ~/Downloads |
| *"Get the 720p version of [url]"* | Fetches formats, picks 720p, downloads |
| *"Download this playlist as audio"* | Gets playlist info, downloads each as audio |
| *"What formats are available for [url]?"* | Lists all quality options with file sizes |
| *"Show me what I downloaded last week"* | `list_downloads` reads persisted history |
| *"Cancel the download"* | Stops an in-progress download |
| *"What's the progress?"* | Shows status of all active downloads |
| *"Download [url] and let me know when it's done"* | `download_and_wait` streams progress until finished |
| *"Convert [url] to MP3"* | Downloads audio and converts to MP3 |

<br>

## &#128268; MCP Tools API Reference

These are the **10 tools** your AI assistant gets access to when yoink is configured as an MCP server:

| Tool | Description | Key Parameters |
|------|-------------|----------------|
| `search_youtube` | **NEW in 0.3.0.** Search YouTube and return matching videos. Lets the AI find URLs without leaving the conversation. | `query`, `limit` |
| `get_subtitles` | **NEW in 0.3.0.** Fetch a video's transcript as plain text (manual subs preferred, auto-captions fallback). The single biggest enabler for "summarize this video" / Q&A workflows. | `url`, `lang` |
| `get_video_info` | Fetch video metadata (title, duration, uploader, available formats) | `url` |
| `get_playlist_info` | List all videos in a YouTube playlist | `url` |
| `get_formats` | List available download qualities with file sizes | `url` |
| `start_download` | Start downloading a video, returns a tracking ID. Now supports `idempotency_key` for safe retry. | `url`, `format_string`, `output_dir`, `idempotency_key` |
| `download_and_wait` | **NEW in 0.3.0.** Download a video and stream progress notifications until it finishes. Best when the AI needs the file before the next step. | `url`, `format_string`, `output_dir` |
| `list_downloads` | List recent downloads (active + persisted history). Survives MCP server restarts. | `limit` |
| `get_download_progress` | Check status of a specific download by ID | `download_id` |
| `cancel_download` | Cancel an active download | `download_id` |

> [!NOTE]
> **Idempotency & dedup.** Pass `idempotency_key` to `start_download` to make retries safe across restarts &mdash; a second call with the same key returns the existing `download_id`. Active-URL dedup is also built in: a duplicate URL while one is in flight returns `error_code: duplicate_active`.

> [!NOTE]
> **Structured errors.** Every error response includes an `error_code` field (`bot_check`, `rate_limited`, `age_restricted`, `ffmpeg_missing`, ...) so the AI can branch on the failure mode instead of regex-matching messages.

> [!NOTE]
> **Persistent history.** Download state is mirrored to `~/.yoink/state.db` (SQLite) so `list_downloads` returns history across MCP server restarts. Delete the file to reset.

<br>

## &#127916; Terminal UI for Humans

For when you want to download videos yourself. A full-featured TUI powered by [Textual](https://textual.textualize.io/).

```bash
yoink              # default: 3 concurrent downloads
yoink -j 5         # up to 10
```

<table>
<tr>
<td>

**Keyboard shortcuts**

| Key | Action |
|-----|--------|
| <kbd>/</kbd> | Focus URL input |
| <kbd>d</kbd> | Start download |
| <kbd>r</kbd> | Retry last failed |
| <kbd>a</kbd> | Select all (playlist) |
| <kbd>n</kbd> | Select none (playlist) |
| <kbd>q</kbd> | Quit |

</td>
<td>

**Features**

| Feature | Details |
|---------|---------|
| Quality picker | 1080p down to audio-only |
| Playlists | Search, filter, batch download |
| Audio-only | One-click, or convert to MP3 |
| Subtitles | Auto-generated + manual subs |
| Speed limit | e.g. `5M` for 5 MB/s cap |
| Output dir | Editable save path |
| Retry | One click on failed downloads |
| Open folder | One click on finished |

</td>
</tr>
</table>

<br>

## &#128736; Architecture

```
src/yoink/
├── __main__.py        # python -m yoink → CLI
├── cli.py             # Plain argparse CLI (no optional deps)
├── core/              # Shared engine (used by CLI, MCP, TUI)
│   ├── models.py      # Stdlib dataclasses — no pydantic
│   ├── errors.py      # ErrorCode enum + classify_error pattern matcher
│   ├── extractor.py   # YouTube metadata, search, subtitle extraction
│   ├── engine.py      # Single download executor with progress hooks
│   ├── state.py       # SQLite-backed download history (~/.yoink/state.db)
│   └── manager.py     # Concurrent orchestration + dedup + idempotency
├── mcp_server/        # [mcp] extra — MCP interface for AI assistants
│   └── server.py      # FastMCP server with 10 tools over STDIO
└── tui/               # [tui] extra — Terminal UI for humans
    ├── app.py         # Lazy-loading entry point
    ├── _impl.py       # Textual application (only imported when [tui] installed)
    ├── screens/       # Main screen, format picker modal
    ├── widgets/       # URL bar, video panel, playlist panel, download queue
    └── styles/        # Textual CSS styling
```

The CLI, MCP server, and TUI are thin wrappers around the same core engine. The core handles all yt-dlp interaction, threading, progress tracking, and error handling — and depends only on `yt-dlp` plus the Python stdlib.

<details>
<summary><b>Design decisions</b></summary>

<br>

- **Threading model:** yt-dlp is synchronous, so each download runs in its own thread via `ThreadPoolExecutor`. A FIFO dispatcher thread ensures downloads start in submission order.
- **Progress reporting:** Hooks are rate-limited to 100ms intervals to avoid callback floods in both MCP and TUI contexts.
- **Playlist optimization:** Uses `extract_flat` to avoid fetching full metadata for large playlists upfront.
- **Error handling:** Raw yt-dlp errors are pattern-matched against 15 common cases and translated to user-friendly messages.
- **Duplicate detection:** The download manager tracks active URLs and rejects duplicates at the engine level, with a `force` bypass for retries.
- **Cancellation:** Uses `threading.Event` checked in every progress hook callback for responsive cancellation.
- **Minimal core:** Data contracts are stdlib `@dataclass` — no pydantic, no third-party validation library. The CLI install pulls only `yt-dlp`.
- **Lazy extras:** Importing `yoink.tui.app` or `yoink.mcp_server.server` succeeds even without the `[tui]` / `[mcp]` extras; missing extras surface a friendly install hint when the entry point runs, not at import time.
- **Durable state:** `~/.yoink/state.db` is a single SQLite file with WAL mode; reads concurrent, writes serialized via lock. Multiple yoink processes share the same history.
- **Subtitle parsing:** Pure-stdlib WebVTT parser strips cue headers and YouTube's inline word-level `<00:00:01.000>` timing tags, deduplicates consecutive identical lines (auto-captions overlap heavily).
- **Search:** `yt-dlp` `ytsearchN:query` extractor with `extract_flat=True` — no per-result fetch.

</details>

<br>

## &#128295; Development

```bash
git clone https://github.com/JayshKhan/yoink.git
cd yoink
pip install -e ".[dev]"    # all extras + pytest
pytest                     # ~130 tests
yoink-cli --help           # plain CLI
yoink                      # TUI
yoink-mcp                  # MCP server
```

<br>

## Frequently Asked Questions

<details>
<summary><b>What AI assistants work with yoink?</b></summary>

<br>

Any AI assistant that supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) over STDIO transport. This includes **Claude Desktop**, **Claude Code**, **Cursor**, **Windsurf**, and any custom MCP client.

</details>

<details>
<summary><b>Does it only work with YouTube?</b></summary>

<br>

yoink uses [yt-dlp](https://github.com/yt-dlp/yt-dlp) under the hood, which supports thousands of sites. However, yoink is optimized and tested for YouTube. Other sites may work but are not officially supported.

</details>

<details>
<summary><b>Can I use the MCP server and TUI at the same time?</b></summary>

<br>

They are separate processes with separate download managers, so yes. They won't share state or conflict with each other.

</details>

<details>
<summary><b>Where do downloads go?</b></summary>

<br>

By default, `~/Downloads`. The MCP `start_download` tool accepts an `output_dir` parameter, and the TUI has an editable path below the URL bar.

</details>

<details>
<summary><b>What if ffmpeg is not installed?</b></summary>

<br>

You can still download videos, but some quality options that require merging separate video and audio streams won't work. MP3 conversion also requires ffmpeg.

</details>

<br>

## &#128196; License

[MIT](LICENSE) &mdash; do whatever you want with it. Yoink responsibly.

<br>

<p align="center">
  <sub>Powered by <a href="https://github.com/yt-dlp/yt-dlp">yt-dlp</a> &middot; UI by <a href="https://textual.textualize.io/">Textual</a> &middot; AI integration via <a href="https://modelcontextprotocol.io/">MCP</a></sub>
</p>
