I wanted MPV for playback, Trakt for tracking, AniList for anime, subtitles when I needed them, and exactly zero browser tabs.
So I built Kari.
Built with Bubble Tea.
git clone https://github.com/Dhairya3391/kari.git
cd kari
go build -o kari ./cmd/kari
./kariType a query, press Space, pick a result.
- Multi-source search — Searches 5+ providers at once (Miruro, VidNest, VidKing, WCO, Jellyfin). Results grouped by source.
- 5 media modes — Anime, Movies, TV Shows, Cartoons, Jellyfin. Switch with Tab.
- Episode browsing — Season/episode lists with filler badges and sub/dub toggles.
- Parallel source resolution — Queries every provider at once, shows all available qualities.
- External player support — MPV (any OS), IINA (macOS), VLC (Windows), MX Player (Android).
- IPC position tracking — MPV reports back via Unix socket. Close mid-episode, resume where you left off.
- Auto-skip intro/outro — Pulls timestamps from AniSkip, generates an MPV Lua script on the fly.
- Scrobbling — Syncs watch status to Trakt.tv (movies/TV) via device auth (no tokens to copy) and AniList (anime) via OAuth.
- Watch history — Local JSON store, grouped by series, remembers your position.
- Downloading — Built-in HLS segment downloader (Miruro) and HTTP range-request downloader (WCO), with yt-dlp as fallback for everything else.
- Subtitles — Pulls from OpenSubtitles automatically, Yify as fallback.
- Self-update —
./kari -ufetches the latest release from GitHub. - Cross-platform — Linux, macOS, Windows, Android (Termux).
- Go 1.26+ — Install Go
At least one media player in $PATH:
| Player | Platforms | Notes |
|---|---|---|
| MPV | Linux, macOS, Windows, Android | Full support |
| IINA | macOS | MPV-based; position tracking works |
| VLC | Windows | No position tracking |
| MX Player | Android | Launched via am start; no position tracking |
Optional tools:
| Tool | Used for | In $PATH |
|---|---|---|
| yt-dlp | Downloading non-Miruro/WCO sources | Yes |
| aria2c | Speeds up yt-dlp downloads | Yes |
| curl | WCO cookie bootstrap + MPV pipe playback | Yes |
| upx | Compressing build artifacts (build script only) | No |
git clone https://github.com/Dhairya3391/kari.git
cd kari
go build -o kari ./cmd/kariThe binary lands in the current directory as ./kari.
If you don't want to install Go, grab a binary from the releases page.
All config is through environment variables. No config files to wrangle.
| Variable | Description | Default |
|---|---|---|
KARI_PLAYER |
Preferred player (mpv, iina, vlc) |
Auto-detected |
| Variable | Description |
|---|---|
TMDB_API_KEY |
Your own TMDB key. If unset, a pool of built-in keys is rotated through. |
OPENSUBTITLES_API_KEY |
OpenSubtitles API key. If unset, falls back to Yify. |
OPENSUBTITLES_USERNAME |
Required if you set the API key. |
OPENSUBTITLES_PASSWORD |
Same. |
JELLYFIN_URL |
Your Jellyfin server URL (e.g. http://192.168.1.100:8096). When set, a JELLYFIN mode appears. |
JELLYFIN_API_KEY |
Jellyfin API key. Required if JELLYFIN_URL is set. Generate one in Dashboard → API Keys. |
TRAKT_CLIENT_ID / TRAKT_ID |
Trakt.tv OAuth client ID |
TRAKT_CLIENT_SECRET / TRAKT_SECRET |
Trakt.tv OAuth client secret |
ANILIST_CLIENT_ID / ANILIST_ID |
AniList OAuth client ID |
ANILIST_CLIENT_SECRET / ANILIST_SECRET |
AniList OAuth client secret |
The binary ships with fallback keys for Trakt, AniList, and TMDB. They work fine for casual use. If you hit rate limits, set your own.
| Variable | Description | Default |
|---|---|---|
KARI_DOWNLOAD_DIR |
Where downloaded episodes land | ./downloads |
KARI_LOG_FILE |
Log file path | ~/.config/kari/kari.log |
KARI_LOG_DEBUG |
Enable debug logging (1, true) |
false |
KARI_LOG_STDERR |
Also write logs to stderr (1, true) |
false |
./kari # Opens the TUI. Start typing to search.
./kari "one piece" # Skips straight to search results.
./kari -v --version # Print version.
./kari -u --update # Self-update from GitHub releases.| Key | Action |
|---|---|
↑/↓ or j/k |
Move through lists |
Enter |
Select series / episode / play |
Esc |
Go back |
/ |
Filter list items |
| Key | Action |
|---|---|
Space |
Start search |
Tab / Shift+Tab |
Cycle media mode (Anime → Movies → TV → Cartoon → Jellyfin) |
Ctrl+H |
Go to home/search |
| Key | Action |
|---|---|
n |
Play next episode |
A |
Toggle autoplay |
a |
Toggle sub/dub (anime only) |
r |
Restart episode from beginning |
Ctrl+P |
Switch players |
| Key | Action |
|---|---|
d |
Download selected episode |
x |
Stop active download / cancel |
| Key | Action |
|---|---|
h / H |
Open watch history |
s / S |
Settings (Trakt/AniList auth) |
Ctrl+D |
Toggle debug panel |
q / Ctrl+C |
Quit |
- Search — Type a query, hit Space. Results come back grouped by provider.
- Episodes — Season/episode list with filler badges. Sub/dub toggle is there for anime.
- Preview — Shows resolved sources, subtitles, and any saved position. Hit Enter to play.
- History — Grouped by series. Resume or delete.
- Settings — Authenticate with Trakt or AniList via device auth (no manual token fiddling).
| Provider | Mode | Method | Priority |
|---|---|---|---|
| Miruro | Anime | API | 1 |
| VidNest | Movies, TV | API (via TMDB) | 1 |
| VidKing | Movies, TV | API (via TMDB) | disabled |
| WCO | Cartoons, Anime | HTML scraping | 2 |
| Jellyfin | Movies, TV | Jellyfin API | 1 |
Lower priority = queried first. All providers are queried in parallel regardless — priority only affects result ordering when multiple providers return the same content.
Want to add another provider? See docs/PROVIDERS.md and PROVIDER_GUIDE.md.
Android support is a bit hacky (MPV Android doesn't expose a normal config directory), but it works:
-
Install Termux from F-Droid (not Play Store — the Play Store version is abandoned)
-
Install dependencies:
pkg install golang curl termux-api yt-dlp
Package Why golangBuild kari curlCookie bootstrap + pipe-to-mpv playback termux-apiProvides termux-am-starterto launch MPV/MX Player via Android intents (the bareambinary is broken on newer Android)yt-dlpRequired for downloads (optional — skip if you only stream) -
Grant storage access (needed to write mpv.conf to the MPV Android config dir):
termux-setup-storage
Then allow the storage permission when prompted. Without this, kari falls back to
~/.config/mpv/mpv.conf(MPV Android may not read it). -
Clone and build:
git clone https://github.com/Dhairya3391/kari.git cd kari go build -o kari ./cmd/kari -
Install MPV Android from Play Store
-
Create a
mpv.confat/storage/emulated/0/Android/media/is.xyz.mpv/mpv.conf:include=/storage/emulated/0/Android/media/is.xyz.mpv/.mpv.conf -
Run
./kari
Kari launches MPV via Android am start intents. The mpv.conf redirect lets it write playback scripts where they can actually be read. See docs/PLAYERS.md for the details of this hack.
Note: On some Android versions, DNS resolution may fail for downloads. Kari includes a built-in fallback to Cloudflare (1.1.1.1) and Google (8.8.8.8) DNS for Android builds.
Kari is split into three layers. Providers fetch data from streaming sites (API calls or HTML scraping). Players send media URLs to external apps (MPV, IINA, VLC, MX Player) and track playback position via IPC. Between them sits a Bubble Tea TUI that handles search, episode browsing, and download management. Each provider and player is a self-contained package — adding a new one doesn't touch anything outside its own directory.
cmd/kari/main.go
|
internal/
├── app/ — Wires everything together
├── config/ — Environment config + API constants
├── service/ — Media, download, subtitle orchestration
├── provider/ — One package per streaming site
├── player/ — Platform-specific player backends
├── tui/ — Bubble Tea model-view-update
├── scrobble/ — Trakt.tv + AniList sync
├── history/ — Local JSON watch storage
├── downloader/ — HLS segment downloader + yt-dlp wrapper
├── subtitles/ — OpenSubtitles + Yify clients
├── aniskip/ — Fetches intro/outro timestamps
├── tmdb/ — Key pool with rotation
├── httpclient/ — Shared retryable HTTP client
└── logging/ — Structured slog wrapper
See docs/ARCHITECTURE.md for more detail.
Same layout as above. cmd/kari/main.go is the entry point. Everything else lives under internal/ — one package per concern.
chmod +x build.sh
./build.sh all # linux/{amd64,arm64}, windows/{amd64,arm64},
# darwin/{amd64,arm64}, android/arm64Or pick one:
./build.sh target linux amd64Or build for whatever machine you're on:
./build.shVersion is derived from git tags and baked in via -ldflags:
| Scenario | Example |
|---|---|
Tagged release (v1.0.0) |
1.0.0 |
| 5 commits after tag | 1.0.5 (patch increments) |
| No tags yet | 1.0.42 (commit count) |
| Uncommitted changes | 1.0.0-dirty |
go run / go build (no ldflags) |
0.0.0-dev |
To cut a release:
git tag v1.0.0
git push origin v1.0.0The CI builds all platforms, creates a GitHub Release, and auto-generates release notes from commits since the last tag.
go build ./...
go vet ./...
go test ./...See docs/CONVENTIONS.md and AGENTS.md (AI assistant rules for this repo) for the full list. Key rules:
- No global state — pass dependencies explicitly
- Wire everything in
internal/app/app.go - Use
internal/httpclientinstead of rawhttp.Client - Log through
internal/logging - Wrap errors with
%w
Pull requests are welcome. The best place to start is adding a new media provider — see PROVIDER_GUIDE.md for the full walkthrough.
A few ground rules:
- Run
go build ./... && go vet ./... && go test ./...before opening a PR - Don't add global state. Wire new components in
internal/app/app.go - Wrap errors with
%w - Kari does not host or distribute copyrighted content. Contributions that add DRM circumvention or direct content hosting will not be accepted.
Downloaded binaries get flagged by Gatekeeper. Clear the quarantine attribute:
xattr -d com.apple.quarantine ./kariIf you built from source, this doesn't apply.
chmod +x ./kariClick More info → Run anyway. SmartScreen flags unsigned binaries — the binary is clean, it just isn't code-signed.
Ensure termux-api is installed (pkg install termux-api) and that you've run termux-setup-storage. MPV Android must be installed from the Play Store, not from Termux packages. On some devices, the am binary is blocked by SELinux — kari will automatically fall back to termux-am or termux-am-starter if available.
Kari uses a built-in DNS fallback (Cloudflare/Google) on Android. If downloads still fail, check that your network connection is active. The system DNS resolver in Termux can be unreliable — this is a known Termux limitation.
Make sure your player is in $PATH. Run which mpv (or which iina, which vlc) to verify.
Written to ~/.config/kari/kari.log. Set KARI_LOG_DEBUG=true for verbose output.
MIT
