This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
ClipCap is a Chrome browser extension for downloading streaming videos (primarily m3u8/HLS streams). It consists of three main components:
- Chrome Extension (
extension/) - Browser UI and web request monitoring - Native Host (
native-host/) - Node.js application for executing yt-dlp commands - Host Configuration (
hosts/) - Native messaging manifests for browser integration
Chrome Extension (popup.js/background.js)
↓ chrome.runtime.connectNative()
Native Messaging Protocol (stdio)
↓ JSON messages with length headers
Native Host (index.js)
↓ spawn/execFile
yt-dlp binary (external)
The extension communicates with the native host using Chrome's Native Messaging protocol, which uses 4-byte length-prefixed JSON messages over stdin/stdout.
Extension Background Service Worker (extension/background.js:1)
- Maintains persistent connection to native host via
chrome.runtime.connectNative() - Intercepts
.m3u8requests usingchrome.webRequestAPI - Detects both direct
.m3u8URLs and proxy/wrapped URLs (e.g.,m3u8.gammacdn.com/?u=...) viaextractM3u8Url()(line 181) - Stores intercepted URLs in
chrome.storage.localwith page context, includingeffectiveUrlfor display - Forwards messages between popup and native host
- Manages download progress state in storage for persistence across popup sessions
Extension Popup (extension/popup.js:1)
- Tabbed interface: Current Page, Downloads, Settings, Logs
- Sends
host:ytdlp-checkmessages to verify video availability and get format info - Sends
host:ytdlp-downloadmessages to start downloads with progress tracking - Handles download cancellation via
host:ytdlp-cancel - Persists active downloads in storage to survive popup close/reopen
Native Host (native-host/index.js:1)
- Node.js script that implements Native Messaging protocol
- Reads 4-byte length header + JSON message from stdin (line 30)
- Handles message types:
host:ytdlp-check- Runsyt-dlp --list-formatsand parses output (line 76)host:ytdlp-download- Spawnsyt-dlpwith progress monitoring (line 116)host:ytdlp-cancel- Kills active download process (line 213)
- Maintains
downloadProcessesMap for cancellation support (line 16) - Logs to
host.logfor debugging (line 11) - Parses yt-dlp output to extract best quality format via
parseYtDlpOutput()(line 275) - Falls back to URL-based resolution guessing via
guessResolutionFromUrl()when yt-dlp reports "unknown" (common for HLS streams)
-
Load unpacked extension in Chrome:
- Navigate to
chrome://extensions/ - Enable "Developer mode"
- Click "Load unpacked" and select the
extension/directory
- Navigate to
-
Check extension logs:
- Right-click extension icon → "Inspect popup" (for popup console)
- Go to
chrome://extensions/→ Click "background page" link (for service worker console)
The native host is launched automatically by Chrome, but for manual testing:
node native-host/index.jsThen send test messages via stdin (4-byte little-endian length + JSON):
// Example test message for listing formats
{"type":"host:ytdlp-check","ytdlpPath":"E:\\yt-dlp\\yt-dlp.exe","url":"https://example.com/video.m3u8"}tail -f native-host/host.logThe native host must be registered in the Windows registry for Chrome to discover it.
Registry Location:
HKEY_CURRENT_USER\Software\Google\Chrome\NativeMessagingHosts\com.example.native_host
Registry Value: Path to hosts/windows/com.darkgenius.clipcap.json
The manifest file points to native-host/index.cmd which launches Node.js with index.js.
Important: The allowed_origins in the manifest must match the extension ID.
All messages between extension and native host follow this structure:
Extension → Native Host:
{
"type": "host:ytdlp-check" | "host:ytdlp-download" | "host:ytdlp-cancel",
"ytdlpPath": "E:\\yt-dlp\\yt-dlp.exe",
"url": "https://...",
"formatId": "...", // for downloads
"outputPath": "...", // for downloads
"filename": "..." // for downloads
}Native Host → Extension:
{
"type": "ytdlp-check-result" | "ytdlp-download-progress" | "ytdlp-download-complete",
"ok": true,
"url": "https://...",
"success": true,
"resolution": "1920x1080", // or "1920x1080 (?)" when guessed, or "unknown"
"filesize": "1.68GiB", // null when unknown
"formatId": "6864",
"percent": 45.0, // for progress
"filesize": "1.49GiB", // for progress (estimated, from yt-dlp output)
"filepath": "...", // for completion
"error": "..." // on failure
}chrome.storage.local keys:
-
m3u8Urls- Array of detected m3u8 URLs with metadata:[{ url: "https://...", // original URL (used as storage key) effectiveUrl: "https://...", // extracted inner URL for proxy URLs (used for display) urlWithoutQuery: "https://...without?params", timestamp: "2025-10-25T...", tabId: 123, pageUrl: "https://page-url.com", checkResult: { success: true, formatId: "6864", resolution: "1920x1080", // or "1920x1080 (?)" when guessed from URL filesize: "1.68GiB", // null when unknown, updated from download progress unknownFilesize: false, // true when filesize was not available at check time checkedAt: "2025-10-25T..." } }]
-
activeDownloads- Map of in-progress downloads (persists across popup sessions):{ "https://video.m3u8": { formatId: "6864", resolution: "1920x1080", filename: "video_1080_1730123456.mp4", percent: 45.0, startedAt: "2025-10-25T..." } }
-
ytdlpSettings- User configuration:{ ytdlpPath: "E:\\yt-dlp\\yt-dlp.exe", ytdlpOutput: "E:\\yt-dlp" }
The native host uses yt-dlp (youtube-dl fork) for video downloading.
Format Selection Algorithm (native-host/index.js:275):
- Parses
yt-dlp --list-formatsoutput - Selects highest resolution format by pixel count
- Extracts format ID, resolution, and file size
- Falls back to formats with unknown resolution (common for HLS)
- Guesses resolution from URL patterns (e.g.,
1080p→1920x1080 (?))
Download Progress Parsing (native-host/index.js):
- Monitors stdout/stderr for
[download] XX.X%patterns - Extracts estimated filesize from progress output (e.g.,
of ~ 1.49GiB) - Sends progress updates with filesize to extension in real-time
- Handles both regular and fragmented downloads
Arguments Used:
--list-formats- Get available formats-f <formatId>- Select specific format-o <path>- Output file path--newline- Ensure progress output on separate lines--no-warnings- Suppress warning messages
The background script maintains connection state and forwards messages even when popup is closed. Download progress is stored in chrome.storage to survive popup lifecycle.
When checking URLs, popup.js sets up both a message listener callback and periodic storage polling (line 403) to handle cases where the popup is closed before receiving the response.
The native host stores spawned child processes in a Map (line 131) to enable cancellation and proper cleanup when downloads are stopped.
Always use escapeHtml() function (line 625) when injecting user-controlled data (URLs) into the DOM to prevent XSS.
- Add handler in
native-host/index.jshandleMessage()function - Add message forwarding in
extension/background.jsline 85 condition - Add message handling in
extension/popup.jschrome.runtime.onMessagelistener
Edit the args array in native-host/index.js:123 for downloads or line 81 for format checking.
Modify the filename generation in extension/popup.js:547-549.