---
name: pp-youtube
description: "A self-maintained competitor-monitoring machine for YouTube: the read surfaces that matter for market and competitor research plus a local databank of channel histories, snapshots, comments, and packaging assets - market data hours old, not weeks. Trigger phrases: `monitor my youtube competitors`, `which competitor videos are gaining views right now`, `find fresh breakout videos in a niche`, `youtube packaging and thumbnail analysis data`, `mine youtube comments for audience signal`, `new niche research workspace`, `switch youtube api key`, `backfill youtube channel history`, `get the transcript of this youtube video`, `use youtube`, `run youtube`."
author: "Justin"
license: "Apache-2.0"
argument-hint: "<command> [args] | install cli|mcp"
allowed-tools: "Read Bash"
metadata:
  openclaw:
    requires:
      bins:
        - youtube-pp-cli
    install:
      - kind: go
        bins: [youtube-pp-cli]
        module: github.com/mvanhorn/printing-press-library/library/media-and-entertainment/youtube/cmd/youtube-pp-cli
---
<!-- GENERATED FILE — DO NOT EDIT.
     This file is a verbatim mirror of library/media-and-entertainment/youtube/SKILL.md,
     regenerated post-merge by tools/generate-skills/. Hand-edits here are
     silently overwritten on the next regen. Edit the library/ source instead.
     See the repository agent guide, section "Generated artifacts: registry.json, cli-skills/". -->

# YouTube — Printing Press CLI

## Prerequisites: Install the CLI

This skill drives the `youtube-pp-cli` binary. **You must verify the CLI is installed before invoking any command from this skill.** If it is missing, install it first:

1. Install via the Printing Press installer. It defaults binaries to `$HOME/.local/bin` on macOS/Linux and `%LOCALAPPDATA%\Programs\PrintingPress\bin` on Windows:
   ```bash
   npx -y @mvanhorn/printing-press-library install youtube --cli-only
   ```
2. Verify: `youtube-pp-cli --version`
3. Ensure the reported install directory is on `$PATH` for the agent/runtime that will invoke this skill.

If the `npx` install fails (no Node, offline, etc.), fall back to a direct Go install (requires Go 1.26.6 or newer). This installs into `$GOPATH/bin` (default `$HOME/go/bin`), so add that directory to `$PATH` instead:

```bash
go install github.com/mvanhorn/printing-press-library/library/media-and-entertainment/youtube/cmd/youtube-pp-cli@latest
```

If `--version` reports "command not found" after install, the runtime cannot see the binary directory on `$PATH`. Do not proceed with skill commands until verification succeeds.

The YouTube Data API v3 read surfaces that matter for market and competitor research with complete parameter wiring, feeding a local SQLite databank designed for competitor monitoring: `watch` your ~15 competitors, `monitor` refreshes them for ~20-40 quota units per run, and `velocity`, `growth`, `breakouts`, `comments-mine`, and `packaging` turn the accumulated snapshots into current market intelligence that lagging analytics platforms deliver one to two weeks late.

## When to Use This CLI

Reach for this CLI whenever the task is YouTube market or competitor research: tracking a fixed set of competitor channels over time, measuring what is gaining views right now, discovering fresh breakout videos in a niche, mining comments for audience signal, or collecting titles, thumbnails, and hooks for packaging analysis. It is the right tool when the answer should come from a locally owned, regularly refreshed databank instead of a lagging external analytics platform.

## Anti-triggers

Do not use this CLI for:
- Do not use this CLI for your own channel's private analytics (revenue, retention, demographics, traffic sources) - that is the OAuth-only YouTube Analytics API, which this CLI deliberately excludes
- Do not use it to upload, edit, rate, or delete videos or manage a channel - all write operations are out of scope
- Do not use it to download video or audio media - use yt-dlp for media files
- Do not use it for wide-market computed judgments like cross-niche outlier scores or monetization estimates - curated analytics databases own that; this CLI owns fresh data on the channels you track

## Unique Capabilities

These capabilities aren't available in any other tool for this API.

### Competitor monitoring machine
- **`watch`** — Register the competitor channels your monitoring machine tracks, in a typed watchlist table you own.

  _Defines the tracked market once; every later monitoring command runs against it without re-specifying channels._

  ```bash
  youtube-pp-cli watch add @mkbhd --json
  ```
- **`monitor`** — Refresh every watched channel in one run: stats snapshot, new uploads, re-snapshot of recent video statistics.

  _One command keeps the databank current, so market answers are hours old instead of weeks old._

  ```bash
  youtube-pp-cli monitor --json
  ```
- **`velocity`** — See which tracked videos are gaining views fastest right now, computed from real between-snapshot deltas.

  _Current market movement - what is taking off today, not what took off two weeks ago._

  ```bash
  youtube-pp-cli velocity --json
  ```
- **`growth`** — Channel-level subscriber, view, and upload-count deltas between dated local snapshots.

  _Tells an agent whether a competitor is accelerating without any external history service._

  ```bash
  youtube-pp-cli growth @mkbhd --json
  ```
- **`backfill`** — Pull a channel's complete upload history with statistics into the local databank in one command.

  _Run once per competitor; every later question about that channel is answered offline for free._

  ```bash
  youtube-pp-cli backfill @mkbhd --json
  ```
- **`workspace`** — Named databanks: keep the competitor machine in one database and explore a new niche in another, switching instantly.

  _Lets an agent spin up a clean research sandbox per niche without risking the production watchlist databank._

  ```bash
  youtube-pp-cli workspace list --json
  ```
- **`auth keys`** — Store multiple named YouTube API keys, switch between them instantly, and optionally fail over automatically via --rotate when one runs out of quota.

  _An agent can finish large collection jobs without human intervention when the first key's daily quota is spent._

  ```bash
  youtube-pp-cli auth keys list --json
  ```

### Fresh market discovery
- **`breakouts`** — Chain search filters into a matrix (terms x upload window x duration x region), join results to channel size, and rank fresh high-momentum videos.

  _Finds niche breakouts days after upload, weeks before they reach lagging analytics platforms._

  ```bash
  youtube-pp-cli breakouts "berlin history" --days 14 --json
  ```
- **`comments-mine`** — Sync comments into a typed full-text-searchable table and report top-liked comments, keyword frequencies, and audience questions.

  _Fast audience signal from data you own - what viewers praise, ask, and complain about across a channel._

  ```bash
  youtube-pp-cli comments-mine @mkbhd --json
  ```
- **`packaging`** — Collect titles, thumbnails (downloaded as local image files), and hook text from transcript openings into a packaging table.

  _Hands a multimodal agent everything it needs for thumbnail and hook analysis without any scraping or manual collection._

  ```bash
  youtube-pp-cli packaging @mkbhd --json
  ```

## Command Reference

**youtube** — YouTube Data API v3 (read-only, api-key) for market and competitor analysis

- `youtube-pp-cli youtube activities-list` — Retrieves a list of resources, possibly filtered.
- `youtube-pp-cli youtube captions-list` — Retrieves a list of resources, possibly filtered.
- `youtube-pp-cli youtube channel-sections-list` — Retrieves a list of resources, possibly filtered.
- `youtube-pp-cli youtube channels-list` — Retrieves a list of resources, possibly filtered.
- `youtube-pp-cli youtube comment-threads-list` — Retrieves a list of top-level comment threads, filterable by video, channel, or thread id.
- `youtube-pp-cli youtube i18n-languages-list` — Retrieves a list of resources, possibly filtered.
- `youtube-pp-cli youtube i18n-regions-list` — Retrieves a list of resources, possibly filtered.
- `youtube-pp-cli youtube playlist-items-list` — Retrieves a list of resources, possibly filtered.
- `youtube-pp-cli youtube playlists-list` — Retrieves a list of resources, possibly filtered.
- `youtube-pp-cli youtube search-list` — Retrieves a list of search resources
- `youtube-pp-cli youtube video-categories-list` — Retrieves a list of resources, possibly filtered.
- `youtube-pp-cli youtube videos-list` — Retrieves a list of resources, possibly filtered.
- `youtube-pp-cli youtube channel-uploads` — List a channel's most recent uploads (resolves @handle or channelId, then walks the uploads playlist).
- `youtube-pp-cli youtube playlist-enrich` — Resolve a playlist to per-video metadata + transcript + description in one concurrent call.
- `youtube-pp-cli youtube search-bulk` — Search YouTube for multiple terms in one call, return top-N per term.
- `youtube-pp-cli youtube videos-comments` — Fetch top comments for a video, ranked by like count across pages.
- `youtube-pp-cli youtube videos-embed` — Print embed HTML, iframe, or markdown snippet for a video.
- `youtube-pp-cli youtube videos-enrich` — One video's metadata + transcript + description in one call.
- `youtube-pp-cli youtube videos-links` — Extract resource links from a video description (expands short links, skips social noise).
- `youtube-pp-cli youtube videos-related` — Find related videos, shared-topic ranking above same-channel.
- `youtube-pp-cli youtube videos-transcript` — Fetch the transcript without OAuth (timedtext; --format markdown|text|json).


### Finding the right command

When you know what you want to do but not which command does it, ask the CLI directly:

```bash
youtube-pp-cli which "<capability in your own words>"
```

`which` resolves a natural-language capability query to the best matching command from this CLI's curated feature index. Exit code `0` means at least one match; exit code `2` means no confident match — fall back to `--help` or use a narrower query.

## Recipes

### Stand up the monitoring machine

```bash
youtube-pp-cli watch add @mkbhd --json
```

Add each competitor once; backfill seeds history and every monitor run keeps them current

### What moved today

```bash
youtube-pp-cli velocity --agent --select items.title,items.views_per_day
```

Between-snapshot view velocity for tracked videos, narrowed to the fields an agent needs

### Fresh breakouts in a niche

```bash
youtube-pp-cli breakouts "berlin history" --days 14 --json
```

Chained filter matrix joined to channel size - high views-per-subscriber uploads from the last two weeks

### Packaging dossier for the agent

```bash
youtube-pp-cli packaging @mkbhd --json
```

Titles, local thumbnail files, and hook text side by side, ready for multimodal packaging analysis

### What the audience keeps asking

```bash
youtube-pp-cli comments-mine @mkbhd --json
```

Top-liked comments, keyword frequencies, and extracted questions from the synced comment table

## Auth Setup

Set YOUTUBE_API_KEY to a YouTube Data API v3 key (create one at console.cloud.google.com under APIs & Services > Credentials), or store it once with `auth set-token`. A key in the environment overrides the stored one - if `doctor` shows auth_source env and calls fail with HTTP 400 'API key not valid', the environment copy is stale: unset it or update it. Read-only public-data operations only; no OAuth anywhere.

Run `youtube-pp-cli doctor` to verify setup.

## Agent Mode

Add `--agent` to any command. Expands to: `--json --compact --no-input --no-color`.

- **Pipeable** — JSON on stdout, errors on stderr
- **Filterable** — `--select` keeps a subset of fields. Dotted paths descend into nested structures; arrays traverse element-wise. Critical for keeping context small on verbose APIs:

  ```bash
  youtube-pp-cli youtube activities-list --part snippet --agent --select contentDetails,etag,id
  ```
- **Previewable** — `--dry-run` shows the request without sending
- **Offline-friendly** — sync/search commands can use the local SQLite store when available
- **Non-interactive** — never prompts, every input is a flag
- **Read-only** — do not use this CLI for create, update, delete, publish, comment, upvote, invite, order, send, or other mutating requests

### Response envelope

Commands that read from the local store or the API wrap output in a provenance envelope:

```json
{
  "meta": {"source": "live" | "local", "synced_at": "...", "reason": "..."},
  "results": <data>
}
```

Parse `.results` for data and `.meta.source` to know whether it's live or local. A human-readable `N results (live)` summary is printed to stderr only when stdout is a terminal AND no machine-format flag (`--json`, `--csv`, `--compact`, `--quiet`, `--plain`, `--select`) is set — piped/agent consumers and explicit-format runs get pure JSON on stdout.

## Paths and state

Agents should treat the CLI's path resolver as part of the runtime contract:

- Use `--home <dir>` for one invocation, or set `YOUTUBE_HOME=<dir>` to relocate all four path kinds under one root.
- Use per-kind env vars only when a specific kind must diverge: `YOUTUBE_CONFIG_DIR`, `YOUTUBE_DATA_DIR`, `YOUTUBE_STATE_DIR`, `YOUTUBE_CACHE_DIR`.
- Resolution order is per-kind env var, `--home`, `YOUTUBE_HOME`, XDG (`XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`, `XDG_CACHE_HOME`), then platform defaults.
- `config` contains settings like `config.toml` and profiles. `data` contains `credentials.toml`, `data.db`, cookies, and auth sidecars. `state` contains persisted queries, jobs, and `teach.log`. `cache` contains regenerable HTTP/cache files.
- Stored secrets live in `credentials.toml` under the data dir. Existing legacy `config.toml` secrets are read for compatibility and leave `config.toml` on the first auth write.
- Run `youtube-pp-cli doctor --fail-on warn` to surface path and credential-location warnings. `agent-context` exposes a schema v4 `paths` block for agents that need the resolved dirs.
- For MCP, pass relocation through the MCP host config. The MCP binary does not inherit CLI flags:

  ```json
  {
    "mcpServers": {
      "youtube": {
        "command": "youtube-pp-mcp",
        "env": {
          "YOUTUBE_HOME": "/srv/youtube"
        }
      }
    }
  }
  ```

⚠️ Two files deliberately live OUTSIDE the relocatable tree, in the platform config dir (`~/Library/Application Support/youtube-pp-cli/` on macOS): `workspaces.json` (the workspace registry must sit outside workspace homes or switching becomes self-referential) and `keyring.json` (quota is per key, not per workspace). Consequence: `--home`/`YOUTUBE_HOME` does NOT isolate the key ring or workspace registry — `keys add/use` and `workspace create/use` mutate shared state even under an isolated home.

Fleet precedence: an inherited per-kind env var overrides an explicit `--home` for that kind. Use `YOUTUBE_HOME` or per-kind vars as durable fleet levers, and use `--home` only for a single invocation. Relocation is not reversible by unsetting env vars; move files manually before clearing `YOUTUBE_HOME`, or `doctor` will not find credentials left under the former root.

## The analyst databank (SQL schema)

Every analyst command writes into one local SQLite databank — the file `doctor --json` reports
as the store path. The filename is scoped by the active API key (`data-<hash>.db`); `workspace`
switches between entirely separate databank files. Agents query it two ways: the MCP `sql` tool
(read-only, validated) and the MCP `search` / CLI `search` full-text surface.

| Table | One row per | Key columns |
|---|---|---|
| `yt_watchlist` | tracked competitor channel | `channel_id`, `handle`, `title`, `note`, `added_at`, `last_monitored_at` |
| `yt_channel_snapshots` | channel per capture time | `channel_id`, `captured_at`, `subscriber_count`, `view_count`, `video_count` |
| `yt_videos` | known video (dimension table) | `video_id`, `channel_id`, `title`, `published_at`, `duration_seconds`, `is_short`, `description` |
| `yt_video_snapshots` | video per capture time | `video_id`, `captured_at`, `view_count`, `like_count`, `comment_count` |
| `yt_comments` | synced comment | `comment_id`, `video_id`, `channel_id`, `author`, `text`, `like_count`, `published_at`, `is_reply` |
| `yt_comments_fts` | FTS5 index over `yt_comments.text` | `MATCH` queries; kept in sync by insert/update/delete triggers |
| `yt_packaging` | collected packaging asset | `video_id`, `channel_id`, `title`, `thumb_url`, `thumb_path`, `hook_text`, `view_count`, `captured_at`, `hook_error`, `thumb_error` |
| `yt_monitor_runs` | one `monitor` run | `run_id`, `started_at`, `finished_at`, `channels`, `new_videos`, `video_snapshots`, `comments_synced`, `quota_units_est` |

`monitor`, `backfill`, `breakouts`, `comments-mine`, and `packaging` feed these tables
automatically (write-through); `velocity` and `growth` are computed from consecutive
`yt_video_snapshots` / `yt_channel_snapshots` rows — two runs on different days are the
minimum for a non-empty answer.

Example queries (all verified against a live-populated store):

```sql
-- What moved: views per video from the latest snapshots
SELECT s.video_id, v.title, s.view_count, s.captured_at
FROM yt_video_snapshots s JOIN yt_videos v USING(video_id)
ORDER BY s.captured_at DESC, s.view_count DESC LIMIT 20;

-- Audience signal: most-liked comments mentioning a topic (FTS5)
SELECT c.like_count, c.author, c.text
FROM yt_comments_fts f JOIN yt_comments c ON c.rowid = f.rowid
WHERE yt_comments_fts MATCH 'gemini' ORDER BY c.like_count DESC LIMIT 10;

-- Growth rate per tracked channel between first and last snapshot
SELECT channel_id,
       MAX(subscriber_count) - MIN(subscriber_count) AS subs_delta,
       MIN(captured_at) AS first_seen, MAX(captured_at) AS last_seen
FROM yt_channel_snapshots GROUP BY channel_id;
```

## Automatic learning

This CLI ships a self-capturing learning loop. The CLI does its own bookkeeping: every invocation is journaled locally, a failed flag followed by a corrected retry auto-derives a `flag_alias` candidate, and a `teach` on a query family without a playbook auto-synthesizes a `playbook_candidate` from the session's journal. Your job is judgment only: `recall` first, act on surfaced candidates, `teach` the final answer, `playbook amend` when you observe a correction. You never record failures by hand.

### Step 1: `recall` before any discovery

Before list/search/drill commands on a new user question, run:

```bash
youtube-pp-cli recall "<user's question>" --agent
```

The response envelope:

```json
{
  "query": "...",
  "normalized": "<normalized form>",
  "query_entities": ["..."],
  "found": true | false,
  "match_score": 0.0,
  "results": [
    { "resource_id": "...", "resource_type": "...", "venue": "...",
      "confidence": 2, "entity_match": "exact|partial|unknown",
      "source": "taught|preseed|pattern", "warnings": ["..."] }
  ],
  "mismatches": [ /* only when --debug-mismatches */ ],
  "warnings": [ /* top-level */ ],
  "candidates": [
    { "id": 12, "class": "flag_alias | playbook_candidate",
      "summary": "...", "sightings": 3, "last_seen": "...",
      "rationale": "...",
      "next_action": ["<trial command>", "youtube-pp-cli learnings confirm 12"] }
  ],
  "playbook": {
    "query_family": "...",
    "playbook": {
      "steps": [ { "cmd": "<command with {slot} substitution>", "purpose": "..." } ],
      "entity_slots": ["$ENTITY"],
      "expected_tool_calls": 3
    },
    "slots_resolved": { "$ENTITY": { "token": "<live token>", "canonical": "<canonical>" } },
    "notes": "<workarounds + gotchas for this query family>"
  },
  "notes": "<duplicate surface for non-playbook callers>"
}
```

Empty-store short-circuit: if the store has no learnings, playbooks, or candidates yet (recall finds n