Skip to main content
Glama
README.md
# yt

> Agent-first YouTube CLI — search, transcripts, metadata, and a local FTS5 cache for AI context retrieval. Wraps `yt-dlp` with JSON envelopes, NDJSON streaming, and an MCP co-binary.

No YouTube Data API key. No quotas. No rate-limit ceiling on the default path.

```
██╗   ██╗████████╗
╚██╗ ██╔╝╚══██╔══╝
 ╚████╔╝    ██║
  ╚██╔╝     ██║
   ██║      ██║
   ╚═╝      ╚═╝
```

## Why

Existing YouTube tooling is either:
- Built for humans (interactive TUIs like `ytfzf`),
- Built for content creators (niche analysis, competitor audits),
- Or locked inside MCP-only servers.

`yt` is built for **agents** — Claude Code, Cursor, Codex, Gemini CLI, and your bash scripts. One call (`yt context <url>`) hands an LLM everything it needs to reason about a video.

## Install

```bash
# Requires yt-dlp on PATH
brew install yt-dlp

# From source
git clone https://github.com/progrmoiz/yt-cli
cd yt-cli
npm install && npm run build && npm link
```

Sanity check:

```bash
yt doctor
```

## Quick start

```bash
# Search
yt search "rust async" --limit 5

# Grok a single video (the 80% agent use case)
yt context https://www.youtube.com/watch?v=dQw4w9WgXcQ

# Fetch transcript
yt transcript https://www.youtube.com/watch?v=dQw4w9WgXcQ --text

# Query everything you've cached
yt find "borrow checker"

# List a channel's recent videos
yt channel @ThePrimeTimeagen --limit 20
```

Piping switches output to JSON automatically (redis-cli pattern):

```bash
yt search "typescript" | jq '.data[0].url'
yt context <url> | jq '.data.transcript.full_text'
```

## Commands

| Command | Description |
|---|---|
| `yt search <query>` | YouTube search, no API key. `--limit`, `--since 7d`. |
| `yt transcript <url>` | Timestamped transcript JSON. Cached. `--lang en`, `--text`. |
| `yt info <url>` | Metadata: title, channel, chapters, duration. Cached. |
| `yt context <url>` | **Headline.** One-shot info + transcript + chapters for agents. |
| `yt find <query>` | FTS5 search over cached transcripts. Line-level hits with timestamps. |
| `yt channel <handle\|id\|url>` | Recent videos from a channel. |
| `yt doctor [--deep]` | Diagnostics: yt-dlp, cache, agent detection. |
| `yt self-test` | Regression suite — catches YouTube endpoint rot. |
| `yt schema <cmd>` | JSON schema for that command's output. |
| `yt mcp serve` | Stdio MCP server. Co-binary. |

## Agent integration

### Claude Code / Cursor / etc — MCP

```jsonc
// .mcp.json
{
  "mcpServers": {
    "yt": { "command": "yt", "args": ["mcp", "serve"] }
  }
}
```

Tools exposed 1:1 with CLI commands: `search`, `info`, `transcript`, `context`, `find`, `channel`, `whoami`.

### Scripts / shell

```bash
# Ingest the top 10 search hits into the local cache
yt search "machine learning ops" --limit 10 --json \
  | jq -r '.data[].url' \
  | xargs -I{} yt transcript {} --json >/dev/null

# Then query across all of them
yt find "feature store" --limit 5
```

## Cache

SQLite database at `~/.cache/yt/yt.db`. Contents:
- `videos` — one row per video (metadata).
- `transcripts` — one row per (video, language) pair.
- `transcript_segments_fts` — FTS5 virtual table over every transcript segment.

Override with `YT_CACHE_DIR=/path/to/dir`.

Every `transcript` / `info` / `context` call cache-checks first. Pass `--refresh` to force a fresh fetch.

## Output shape

Default envelope (v2):

```json
{
  "version": "2",
  "status": "success",
  "command": "context",
  "cli": { "name": "yt", "version": "0.1.0" },
  "data": { /* command payload */ },
  "metadata": {
    "elapsed_ms": 1200,
    "cached": true,
    "has_transcript": true,
    "agent": "CLAUDECODE"
  }
}
```

Use `--raw` for bare data. Full JSON schema via `yt schema <command>`.

## Exit codes

`0` ok · `1` generic · `2` usage · `3` validation · `4` auth · `5` rate_limit · `6` network · `7` not_found · `75` temp_fail

## Environment

| Var | Purpose |
|---|---|
| `YT_CACHE_DIR` | Cache directory (default: `~/.cache/yt`) |
| `YT_DLP_BIN` | Override path to the `yt-dlp` binary |
| `YT_NO_RETRY` | Set to `1` to disable the single auto-retry on RATE_LIMIT/NETWORK/TIMEOUT |
| `YT_JSON=1` | Force JSON output |
| `YT_API_KEY` | Reserved for future Data API extras |
| `NO_COLOR` | Disable ANSI |
| `CI` | Force non-interactive mode |

## License

MIT.