# PRODUCT REQUIREMENTS DOCUMENT — AS-BUILT
# Distill: AI Skill Factory from Social Video

---

## 1. Executive Summary

| Field | Detail |
|---|---|
| **Product Name** | Distill |
| **Current Status** | Production — actively used |
| **Product Type** | Local developer tool / personal productivity tool |
| **Primary Objective** | Extract transcripts from social video content and transform them into reusable Claude AI skills |
| **Current Users** | Single user (self-hosted), local machine only |
| **Technical Maturity** | MVP / Production — single-file Flask app, no tests, no CI |

Distill is a locally-hosted web app that lets users paste a YouTube, TikTok, Instagram Reels, or X/Twitter URL and receive the full transcript of that video in seconds. It then lets users convert any transcript into a production-ready Claude Code skill using AI, which is stored in a local Skills Vault and downloadable as a `.md` file.

---

## 2. Problem Statement

**User Problem:**
Content creators and knowledge workers consume hours of video content — tutorials, strategy breakdowns, expert frameworks — but the knowledge stays trapped in video format. It cannot be searched, referenced, or reused in AI workflows. Writing Claude Code skills from scratch is slow, inconsistent, and requires prompt engineering expertise.

**Business Problem:**
There is no lightweight, local-first tool that bridges the gap between social video content and structured AI prompt libraries. Existing solutions require cloud accounts, paid subscriptions, or complex setup.

**Current Impact:**
Without this tool, extracting a transcript requires either watching the full video or using YouTube's built-in transcript viewer (which only works for YouTube, requires manual copying, and offers no AI transformation).

**Evidence:**
- Decision: Built as a local tool specifically to avoid cloud hosting costs and data privacy concerns.
- Assumption: The primary user watches 10–20 videos per week and wants to extract reusable knowledge from them.

**What happens if the problem is not solved:**
Users continue manually copying transcripts and writing skills by hand — slow, inconsistent, and not scalable.

---

## 3. Target Users

**Primary Persona:**
A content creator or AI power user who:
- Watches YouTube, TikTok, and Instagram Reels for learning and inspiration
- Uses Claude Code daily and builds custom AI skills
- Is comfortable running a local Python server
- Wants to convert video content into reusable AI workflows

**Secondary Persona:**
A researcher or writer who needs to extract and reference spoken content from social video without manual transcription.

**Admin/Internal Users:**
None — single-user tool, no admin role.

**User Context:**
- Runs the app locally on their own machine (Windows, Mac, or Linux)
- Accesses it via browser at `http://127.0.0.1:5000`
- Leaves the server running in a background terminal during work sessions

**Jobs to Be Done:**
- "When I find a video with a useful framework, I want to turn it into a reusable Claude skill without doing it manually."
- "When I need to reference something someone said in a video, I want to get the full transcript instantly."
- "When I'm building a content batch, I want to extract transcripts from many videos at once."

---

## 4. Current Product Overview

**Core Capabilities:**
1. Extract transcripts from YouTube, TikTok, Instagram Reels, and X/Twitter URLs
2. Fall back to OpenAI Whisper audio transcription when no captions exist
3. Analyze transcripts with AI and suggest 2 Claude skill directions
4. Generate a complete, production-ready Claude Code skill from any transcript
5. Store all generated skills in a local Skills Vault
6. Bulk download transcripts from a channel or profile

**Main Modules:**

| Module | Description |
|---|---|
| Transcripts tab | Single URL → instant transcript |
| AI Skills tab | Single URL → AI-generated Claude skill |
| Skills Vault | Local library of all generated skills |
| Bulk tab | Channel/profile URL → batch transcript extraction |

**Key Screens:**
- Main app (single-page app): top nav with Transcripts / AI Skills / Skills Vault / Bulk tabs
- Platform selector: YouTube / TikTok / Instagram / X/Twitter
- URL input + Extract button
- Transcript display with copy and download controls
- Skill suggestion panel (2 AI-generated directions)
- Skill generation panel with custom context input
- Skills Vault grid with filter by category
- Bulk video browser with checkbox selection
- Whisper API cost tracker (bottom of page)

**Main User Actions:**
- Paste URL → extract transcript
- Copy transcript to clipboard
- Download transcript as `.txt`
- Click "Turn into AI Skill" → get suggestions → generate skill
- Download skill as `.md`
- Browse channel → select videos → bulk extract
- Filter vault by skill category
- Delete skills from vault

**Current Limitations:**
- No user authentication (single-user, local only)
- No multi-language transcript support beyond English preference
- No cloud sync or backup
- No transcript editing or post-processing UI
- Skills Vault is not searchable
- No automated tests

---

## 5. Value Proposition

**Primary Benefit:**
Turn any video into a Claude AI skill in under 90 seconds — no manual transcription, no prompt engineering.

**Secondary Benefits:**
- Works with 4 major video platforms from one interface
- Completely local — no data leaves the machine except API calls to OpenAI/OpenRouter
- Whisper fallback means even captionless videos can be transcribed
- Skills Vault creates a growing personal library of AI workflows

**Differentiators:**
- Local-first: no account, no subscription, no cloud storage
- Combines transcript extraction AND AI skill generation in one tool
- Multi-platform: YouTube, TikTok, Instagram, X/Twitter
- Persistent local skill library with categories

**Current Alternatives Users Use:**
- YouTube's built-in transcript viewer (YouTube only, no export, no AI transformation)
- Rev.com / Otter.ai (paid, cloud-based, no AI skill output)
- Manually writing prompts from notes

---

## 6. Scope

### 6.1 In Scope Today
- YouTube transcript extraction via `youtube_transcript_api`
- YouTube fallback via `yt-dlp` with cookie authentication
- TikTok transcript via `yt-dlp` captions or Whisper
- Instagram Reel transcript via Whisper (no caption support)
- X/Twitter transcript via Whisper
- AI skill suggestion (2 options) via Claude Haiku
- AI skill generation via Claude Sonnet
- Local Skills Vault (stored as JSON)
- Skill download as `.md`
- Skill deletion
- Bulk transcript extraction (up to 50 videos)
- Monthly Whisper cost tracking with USD display
- ANSI escape code stripping from error messages
- Instagram URL validation (rejects profile pages)

### 6.2 Out of Scope Today
- User authentication / multi-user support
- Cloud hosting or remote access
- Transcript editing UI
- Skills Vault search
- Non-English video transcripts (by preference, not hard limit)
- Podcast / audio-only file support
- PDF or document ingestion
- Direct Claude Code skill upload/integration
- Skill versioning or editing

### 6.3 Future Scope
- Transcript search across vault
- Batch skill generation from multiple videos
- Direct skill upload to Claude Code
- Auto-start on OS boot
- Skill editing and version history
- Podcast RSS feed import

---

## 7. Functional Requirements

### FR-01 — YouTube Transcript Extraction (Fast Path)
**Description:** Extract transcript from a YouTube URL using `youtube_transcript_api` without cookies.
**Current Status:** Implemented
**User Story:** As a user, I want to paste a YouTube URL and receive the full transcript in under 3 seconds.
**Acceptance Criteria:**
- System accepts any valid YouTube URL format (`youtube.com/watch?v=`, `youtu.be/`, embed URLs)
- System extracts transcript in English if available
- System falls back to the first available language if English is not found
- System returns video title alongside transcript
- Response time under 3 seconds for videos with existing captions
**Edge Cases:**
- Video has no transcript → return 422 with clear message
- Transcripts are disabled → return 422 with clear message
- Invalid video ID → return 400

---

### FR-02 — YouTube Transcript Extraction (Cookie Fallback)
**Description:** When the fast path fails (IP blocked), retry using a cached, cookie-authenticated HTTP session.
**Current Status:** Implemented
**User Story:** As a user, I want transcript extraction to succeed even when my IP is temporarily rate-limited by YouTube.
**Acceptance Criteria:**
- Cookie session is loaded once at startup from a Netscape-format `.txt` file
- Cookies are injected into a `requests.Session` passed as `http_client` to `youtube_transcript_api`
- Session is reused across requests (not reloaded per request)
- If no cookie file is configured, this step is skipped
**Edge Cases:**
- Cookie file does not exist → skip gracefully, continue to next fallback
- Cookies are expired → authentication fails silently, fall through to yt-dlp

---

### FR-03 — YouTube Transcript Extraction (yt-dlp Fallback)
**Description:** When both API paths fail, use yt-dlp with cookies to extract subtitles.
**Current Status:** Implemented
**User Story:** As a user, I want a last-resort fallback that uses my browser cookies to authenticate with YouTube.
**Acceptance Criteria:**
- System tries `cookiefile` (Netscape `.txt`) first if configured
- System tries reading from Edge browser cookies if file fails
- System returns transcript if any subtitle track is found
- System returns a user-friendly error if all methods fail
**Edge Cases:**
- Browser is not installed → skip browser cookie attempt
- No subtitles available → return error, do not attempt Whisper for YouTube

---

### FR-04 — TikTok Transcript Extraction
**Description:** Extract captions from a TikTok video URL using yt-dlp.
**Current Status:** Implemented
**User Story:** As a user, I want to paste a TikTok URL and get the spoken transcript.
**Acceptance Criteria:**
- System attempts fast caption extraction (no download) first
- System falls back to full caption download if fast path fails
- System falls back to Whisper audio transcription if no captions exist and OpenAI key is configured
- Video thumbnail and title are returned with transcript
**Edge Cases:**
- Video is private → return error from yt-dlp
- No captions and no OpenAI key → return error message explaining how to add a key

---

### FR-05 — Instagram Reel Transcript Extraction
**Description:** Extract spoken content from an Instagram Reel URL using Whisper.
**Current Status:** Implemented
**User Story:** As a user, I want to paste an Instagram Reel link and get a transcript of the speech.
**Acceptance Criteria:**
- System validates that URL is a specific Reel/post (not a profile page)
- Profile page URLs return a clear error with an example of a valid URL
- System skips caption extraction (Instagram Reels do not have captions)
- System uses Whisper to transcribe audio if OpenAI key is configured
- System uses Instagram cookies file if configured
**Edge Cases:**
- Profile URL (e.g. `instagram.com/username/`) → return 400 with helpful message
- No cookies → yt-dlp may fail with auth error
- No OpenAI key → return error explaining Whisper requirement

---

### FR-06 — X/Twitter Transcript Extraction
**Description:** Extract spoken content from an X/Twitter video URL using Whisper.
**Current Status:** Implemented
**User Story:** As a user, I want to paste an X/Twitter video URL and get a transcript.
**Acceptance Criteria:**
- System downloads lowest-quality audio stream to minimize file size
- System transcribes via Whisper if OpenAI key is configured
- System records Whisper usage to monthly cost tracker
**Edge Cases:**
- Video has no audio → Whisper returns empty text
- File exceeds 25 MB → split into chunks via ffmpeg if available, else return error

---

### FR-07 — Whisper Audio Transcription Fallback
**Description:** When no captions exist, download audio and transcribe using OpenAI Whisper.
**Current Status:** Implemented
**User Story:** As a user, I want videos without captions to still be transcribable.
**Acceptance Criteria:**
- System uses `whisper-1` model via OpenAI API
- System records duration in seconds to `usage.json` keyed by `YYYY-MM`
- Files over 25 MB are split into 23 MB chunks via ffmpeg and transcribed sequentially
- If ffmpeg is not installed and file is too large, system returns a clear error
- Transcription cost is tracked and displayed in the UI
**Edge Cases:**
- OpenAI key not configured → return message instructing user to add key
- Audio file cannot be downloaded → return error
- ffmpeg not installed + large file → return error with install instructions

---

### FR-08 — AI Skill Suggestion
**Description:** Analyze a transcript and suggest 2 Claude Code skill directions.
**Current Status:** Implemented
**User Story:** As a user, I want AI to suggest how I could turn this transcript into a useful skill.
**Acceptance Criteria:**
- System sends the first 6,000 characters of the transcript to Claude Haiku via OpenRouter
- System returns exactly 2 suggestions as short action phrases (10–15 words max)
- If fewer than 2 suggestions are parsed, system fills with a default fallback phrase
- Suggestions are displayed as clickable buttons in the UI
**Edge Cases:**
- OpenRouter key not configured → return 500 with clear message
- API returns malformed response → parse gracefully, fill with fallback

---

### FR-09 — AI Skill Generation
**Description:** Generate a complete Claude Code skill from a transcript and optional user context.
**Current Status:** Implemented
**User Story:** As a user, I want to generate a production-ready Claude Code skill that I can download and use immediately.
**Acceptance Criteria:**
- System sends up to 12,000 characters of transcript plus user context to Claude Sonnet via OpenRouter
- Generated skill follows the exact Claude Code skill format (YAML frontmatter + markdown body)
- Skill includes: `name`, `description`, `category`, `Purpose`, `Instructions`, `Key Behaviors`, `Examples`
- Skill is assigned to one of 13 predefined categories
- Skill is saved to `skills.json` with metadata (id, video url, platform, thumbnail, created_at)
- Skill is returned to frontend immediately after generation
**Edge Cases:**
- Transcript is empty → return 400
- OpenRouter key not configured → return 500
- Skill frontmatter cannot be parsed → use defaults (`untitled-skill`, empty description)

---

### FR-10 — Skills Vault
**Description:** Display all generated skills in a filterable local library.
**Current Status:** Implemented
**User Story:** As a user, I want to browse, filter, and manage all skills I've generated.
**Acceptance Criteria:**
- Skills are displayed as cards showing thumbnail, name, description, category, and platform
- User can filter by category using category buttons
- User can download any skill as a `.md` file
- User can delete any skill (removes from `skills.json`)
- Vault persists across sessions (stored in `skills.json`)
**Edge Cases:**
- `skills.json` does not exist → return empty vault, do not error
- Skill file is corrupt → return empty vault

---

### FR-11 — Bulk Transcript Extraction
**Description:** Browse a channel or profile and extract transcripts from multiple videos at once.
**Current Status:** Implemented
**User Story:** As a user, I want to select multiple videos from a channel and download all their transcripts in one operation.
**Acceptance Criteria:**
- User pastes a channel or profile URL
- System fetches up to 50 videos using `yt-dlp` with `extract_flat: True`
- Videos are shown as cards with thumbnail, title, and checkbox
- User selects any subset and clicks "Download Transcripts"
- System extracts transcripts sequentially with a 1.5-second delay between requests
- Results are returned as a downloadable batch
**Edge Cases:**
- Channel has no videos → return empty list
- Individual video fails → include error in results, continue processing others
- Instagram profile requires cookies → return 422 if no cookie file is configured

---

### FR-12 — Whisper Cost Tracker
**Description:** Track and display monthly OpenAI Whisper API spend.
**Current Status:** Implemented
**User Story:** As a user, I want to know how much I've spent on Whisper transcription this month.
**Acceptance Criteria:**
- Every Whisper transcription records audio duration in seconds to `usage.json`
- Monthly totals show: number of requests, total minutes, total cost in USD
- Rate: $0.006 per minute
- Display updates on page load
- Keyed by `YYYY-MM` to support month-over-month history
**Edge Cases:**
- `usage.json` does not exist → return zeroes, do not error
- File is corrupt → return zeroes

---

## 8. Non-Functional Requirements

### Performance
- YouTube transcript extraction (fast path, captions available): **under 3 seconds**
- YouTube transcript extraction (cookie fallback path): **under 5 seconds**
- YouTube transcript extraction (yt-dlp fallback): **under 30 seconds**
- TikTok/Instagram caption fast path: **under 10 seconds**
- Whisper transcription for a 5-minute video: **under 60 seconds**
- Skills Vault load time: **under 1 second** for up to 500 skills

### Scalability
- Designed for single-user local use only
- No scalability requirements in current version
- `skills.json` is a flat file — acceptable up to ~1,000 skills before performance degrades

### Security
- No user authentication required (localhost only)
- API keys stored in `.env` file — never committed to version control
- Cookies files stored locally — never transmitted except to the target platform
- No sensitive data logged to console beyond debug output

### Availability
- Runs locally — no uptime SLA
- App restarts if terminal is closed — no process management in current version

### Reliability
- YouTube extraction uses a 3-tier fallback to maximize success rate
- Non-YouTube extraction uses 2-tier fallback (captions → Whisper)
- Each fallback tier fails silently and continues to the next
- ANSI escape codes stripped from all user-facing error messages

### Compliance
- No user data collected or transmitted beyond API calls
- All transcripts and skills stored locally
- No logging of transcript content

### Accessibility
- No formal accessibility audit performed
- Single-page app uses standard HTML elements
- No keyboard navigation optimization

### Observability
- Debug `print()` statements to terminal for yt-dlp method selection and failures
- No structured logging, no log files, no error tracking service

---

## 9. System Architecture

| Layer | Technology |
|---|---|
| **Frontend** | Vanilla JavaScript, single HTML file (`templates/index.html`) |
| **Backend** | Python 3.12, Flask (debug mode, single process) |
| **Database** | None — flat JSON files (`skills.json`, `usage.json`) |
| **Authentication** | None (localhost only) |
| **Transcript extraction** | `youtube_transcript_api` v1.2.4, `yt-dlp` (latest) |
| **Audio transcription** | OpenAI Whisper (`whisper-1`) via OpenAI API |
| **AI skill generation** | Claude Haiku (analysis) + Claude Sonnet (generation) via OpenRouter API |
| **OpenRouter client** | `openai` Python SDK pointed at `https://openrouter.ai/api/v1` |
| **Cookie auth** | Netscape-format `.txt` files, `http.cookiejar.MozillaCookieJar` |
| **Audio splitting** | `ffmpeg` (optional — must be installed separately) |
| **Hosting** | Local machine, `http://127.0.0.1:5000` |
| **Background jobs** | None |
| **File storage** | Local filesystem (`skills.json`, `usage.json`, temp dirs for audio) |

**Key architectural decisions:**
- Single `app.py` file — all backend logic in one place for simplicity
- Single `templates/index.html` — all frontend in one place
- No database — JSON files are sufficient for personal-scale use
- `yt-dlp` used as a universal fallback for all platforms
- OpenRouter used instead of Anthropic API directly — same models, single API key for routing

---

## 10. Data Model Overview

### Entities

**Skill** (stored in `skills.json` → `skills[]`):
```json
{
  "id": "uuid-v4",
  "name": "kebab-case-skill-name",
  "description": "One-line description string",
  "category": "Writing & Content",
  "video_url": "https://...",
  "video_id": "platform-specific-id",
  "platform": "youtube | tiktok | instagram | twitter",
  "thumbnail": "https://...",
  "skill_markdown": "Full .md file content",
  "created_at": "ISO 8601 timestamp"
}
```

**UsageEntry** (stored in `usage.json`, keyed by `YYYY-MM`):
```json
{
  "2025-05": {
    "seconds": 3640,
    "requests": 12,
    "cost": 0.364
  }
}
```

**Relationships:** None — entities are independent.

**Sensitive Data:** None stored. API keys exist only in `.env`.

**Data Retention:** Indefinite — no automatic cleanup.

**Privacy:** All data is local. No PII collected.

---

## 11. User Flows

### Flow 1 — Extract YouTube Transcript
**Trigger:** User pastes YouTube URL and clicks Extract
**Steps:**
1. Frontend sends POST `/transcript` with `{url}`
2. Backend validates URL, extracts video ID
3. Attempts `youtube_transcript_api` without cookies
4. On success → returns transcript + title
5. On IP block → retries with cached cookie session
6. On failure → falls back to yt-dlp
7. Frontend displays transcript, thumbnail, title
8. User clicks Copy or Download

**Success:** Transcript displayed in under 3 seconds
**Failure states:** No transcript exists (422), IP blocked + all fallbacks fail (429)

---

### Flow 2 — Generate Claude Skill from Video
**Trigger:** User opens AI Skills tab, pastes URL, clicks Extract
**Steps:**
1. Transcript is extracted (same as Flow 1)
2. System auto-calls `/analyze-transcript`
3. Two skill direction suggestions appear as clickable buttons
4. User clicks a suggestion or types custom context
5. User clicks "Generate Skill"
6. System calls `/generate-skill` with transcript + context
7. Skill is generated, saved to vault, and displayed
8. User clicks Download to save `.md` file

**Success:** Skill downloaded in under 90 seconds total
**Failure states:** No transcript, OpenRouter key missing, API error

---

### Flow 3 — Bulk Transcript Download
**Trigger:** User opens Bulk tab, pastes channel URL, clicks Fetch Videos
**Steps:**
1. System calls `/channel-videos` with channel URL
2. Up to 50 videos returned as cards
3. User checks desired videos
4. User clicks "Download Transcripts"
5. System calls `/bulk-transcript` with selected video list
6. Results returned per video (success or error)
7. User downloads individual transcripts or all at once

**Success:** All selected transcripts extracted
**Failure states:** Rate limited (1.5s delay mitigates), some videos fail (partial results returned)

---

### Flow 4 — Browse and Download from Skills Vault
**Trigger:** User clicks "Skills Vault" button
**Steps:**
1. Vault loads all skills from `/skills`
2. User optionally filters by category
3. User clicks Download on a skill card → downloads `.md` file
4. User clicks Delete → confirms, skill is removed

**Success:** Skill downloaded or deleted
**Failure states:** Vault empty (shows empty state), `skills.json` missing (returns empty)

---

## 12. Edge Cases

| Case | Behavior |
|---|---|
| YouTube URL with no transcript | Return 422: "No transcript found for this video." |
| YouTube transcripts disabled | Return 422: "Transcripts are disabled for this video." |
| YouTube IP blocked, all fallbacks exhausted | Return 429: "Could not retrieve transcript. Try re-exporting your YouTube cookies." |
| Instagram profile URL (not a Reel) | Return 400: "Paste a link to a specific Reel or post, not a profile page." |
| TikTok/Instagram video has no captions + no OpenAI key | Return error: "No captions found. Add an OpenAI API key to enable audio transcription." |
| Audio file over 25 MB + ffmpeg installed | Split into 23 MB chunks, transcribe each, concatenate |
| Audio file over 25 MB + no ffmpeg | Return error with ffmpeg install instructions |
| OpenRouter key not configured | Return 500: "OpenRouter API key not configured." |
| `skills.json` does not exist | Return empty vault `{skills: []}` |
| `usage.json` does not exist | Return `{seconds: 0, requests: 0, cost: 0}` |
| yt-dlp error contains ANSI color codes | Strip codes before returning error to user |
| Bulk extraction: one video fails | Include error in result, continue remaining |
| YouTube cookies file expired | Cookie authentication silently fails, falls through to yt-dlp |

---

## 13. Known Issues and Technical Debt

| Issue | Severity | Notes |
|---|---|---|
| No automated tests | High | All testing is manual |
| No error tracking / logging | Medium | Errors visible only in terminal during session |
| `skills.json` is a flat file — no indexing | Low | Acceptable under ~1,000 skills |
| Skills Vault has no search | Medium | Must scroll or filter by category |
| Cookie session loaded at startup — not refreshed | Low | Requires server restart if cookies expire during session |
| `APP_PASSWORD` variable exists but is not enforced | Low | Password gate was removed but variable remains |
| `try_captions_fast` and `try_captions` both print debug logs to stdout | Low | Should be removed or converted to structured logging before sharing |
| No graceful shutdown or process management | Low | App dies when terminal is closed |
| Frontend has no loading states for bulk extraction progress | Medium | Users cannot see per-video progress during bulk runs |
| yt-dlp must be manually updated | Medium | Instagram/TikTok extraction breaks when platforms change their APIs |

---

## 14. Risks and Mitigations

| Risk | Impact | Likelihood | Mitigation |
|---|---|---|---|
| YouTube changes transcript API | High — core feature breaks | Medium | 3-tier fallback (API → cookies → yt-dlp). Keep `yt-dlp` updated. |
| Instagram/TikTok blocks yt-dlp | High — those platforms break | High | Keep `yt-dlp` updated (`pip install --upgrade yt-dlp`). Whisper fallback covers Instagram. |
| YouTube cookie file expires | Medium — cookie fallback stops working | High | Re-export cookies monthly. Document the process. |
| OpenRouter API changes model IDs | Medium — skill generation breaks | Low | Update model strings in `app.py`. Use model aliases when available. |
| OpenAI Whisper price increase | Low — cost increases | Low | Cost tracker provides visibility. Whisper is last resort only. |
| `skills.json` grows too large | Low — slow vault load | Low | Acceptable for personal use. Add pagination in future. |
| User runs bulk job and gets IP blocked | Medium — bulk extraction breaks mid-run | Medium | 1.5s delay between requests reduces likelihood. |

---

## 15. Assumptions

- The app runs on the same machine as the user's browser (cookie files are local)
- The user has Python 3.8+ installed
- The user has `pip` and can install packages from `requirements.txt`
- The user is comfortable running a terminal command to start the server
- All video content is in English (or the user is OK with the first available language)
- The user has an OpenRouter account for AI features (not required for basic transcript extraction)
- The user has an OpenAI account for Whisper fallback (not required if videos have captions)
- The platform-specific cookies (YouTube, Instagram) are in Netscape `.txt` format as exported by browser extensions
- ffmpeg is optional — large audio files simply cannot be split without it
- The Skills Vault is for personal use and does not need to be shared or synced

---

## 16. Decision Log

| Decision | Reason | Alternatives Considered |
|---|---|---|
| Single `app.py` file | Simplicity for a personal tool; easy to read and modify | Separate modules/blueprints |
| Flat JSON storage (`skills.json`) | No database setup required; portable; readable | SQLite, PostgreSQL |
| OpenRouter instead of direct Anthropic API | Single API key for model routing; easier to switch models | Direct Anthropic API |
| `youtube_transcript_api` as primary path | Fastest method (~1s), no download needed | yt-dlp for all platforms |
| yt-dlp as universal fallback | Handles all 4 platforms with consistent interface | Platform-specific APIs |
| Whisper as last resort (costs money) | Most videos have captions; Whisper is expensive at scale | Require captions only |
| No authentication | Single-user, localhost-only tool | Basic password gate (was tried and removed) |
| Cookie session cached at startup | Avoid reloading cookies on every request | Reload per request |
| Instagram reels → skip captions, go direct to Whisper | Instagram Reels do not support captions via yt-dlp | Try captions anyway (adds latency for no benefit) |

---

## 17. Success Metrics

### Product Metrics
- Time from YouTube URL to transcript displayed: **< 3 seconds** (fast path)
- Time from URL to generated skill: **< 90 seconds**
- Transcript extraction success rate: **> 95%** for YouTube with captions
- Skill generation success rate: **> 99%** (only fails on API errors)

### Technical Metrics
- YouTube fast path success rate: **> 80%** under normal conditions
- Whisper cost per month: **< $5** at personal use volume
- App startup time: **< 3 seconds**
- Skills Vault load time: **< 1 second** up to 500 skills

### Business Metrics (personal tool — proxy metrics)
- Skills generated per week
- Platforms used (YouTube vs TikTok vs Instagram)
- Whisper cost per month (tracked in-app)

---

## 18. Roadmap

### Phase 1 — Stabilization
- [ ] Remove leftover `APP_PASSWORD` variable from code
- [ ] Convert `print()` debug logs to proper logging with log levels
- [ ] Add graceful error boundary in frontend for failed API calls
- [ ] Add per-video progress indicator in bulk extraction UI
- [ ] Document cookie export process in README

### Phase 2 — Product Improvements
- [ ] Add search to Skills Vault
- [ ] Add bulk skill generation (select multiple videos → generate skills for each)
- [ ] Support non-English transcripts (language selection UI)
- [ ] Add transcript preview before skill generation
- [ ] Auto-refresh cookie session without server restart

### Phase 3 — Scaling
- [ ] Direct Claude Code skill upload via API (when available)
- [ ] Skill editing and version history in vault
- [ ] Export entire vault as a zip of `.md` files
- [ ] Auto-start on OS boot (platform-specific scripts)
- [ ] Optional: convert to installable desktop app (Electron or Tauri wrapper)

---

## 19. Open Questions

1. Should Whisper be disabled by default and require explicit opt-in to avoid unexpected costs?
2. What is the maximum acceptable vault size before pagination is required?
3. Should the app support non-English transcripts, and how should language preference be exposed in the UI?
4. Is there value in supporting podcast RSS feeds as an input source?
5. Should bulk extraction show per-video real-time progress (requires streaming response)?
6. Should skills be taggable beyond the 13 fixed categories?
7. Should the cookie session be refreshed automatically on a timer, or manually via a UI button?
8. Is the 50-video limit for bulk extraction the right cap, or should it be user-configurable?

---

## Appendix A — Required Environment Variables

| Variable | Required | Purpose |
|---|---|---|
| `OPENROUTER_API_KEY` | For AI features | Skill analysis and generation via Claude models |
| `OPENAI_API_KEY` | For Whisper fallback | Audio transcription for captionless videos |
| `YOUTUBE_COOKIES_FILE` | Optional | Path to Netscape cookies file — bypasses YouTube IP blocks |
| `INSTAGRAM_COOKIES_FILE` | Optional | Path to Netscape cookies file — required for Instagram |

---

## Appendix B — Python Dependencies

```
flask
python-dotenv
youtube_transcript_api>=1.2.4
yt-dlp
openai
requests
```

**Optional system dependencies:**
- `ffmpeg` — required only for audio files over 25 MB (long videos without captions)

---

## Appendix C — API Endpoints

| Method | Path | Description |
|---|---|---|
| GET | `/` | Serve the single-page app |
| POST | `/transcript` | Extract transcript from a URL |
| POST | `/analyze-transcript` | Suggest 2 skill directions from transcript |
| POST | `/generate-skill` | Generate and save a Claude Code skill |
| GET | `/skills` | Return all skills from vault |
| DELETE | `/skills/<id>` | Delete a skill by ID |
| GET | `/skills/<id>/download` | Download skill as `.md` file |
| POST | `/channel-videos` | Fetch video list from a channel/profile URL |
| POST | `/bulk-transcript` | Extract transcripts for a list of videos |
| GET | `/usage` | Return current month Whisper API usage and cost |
