A local-first YouTube content filter and desktop productivity guard. LockIn uses a locally running LLM (Ollama) to classify YouTube recommendations against your interest profile and block irrelevant content before you see it β with zero cloud dependency and zero API costs.
| Feature | Description |
|---|---|
| YouTube tile filtering | Scrapes every video tile on YouTube, classifies it against your profile using a local LLM, and overlays a frosted-glass block on anything irrelevant |
| Instant re-blocking | A MutationObserver re-applies overlays the moment YouTube re-renders tiles (infinite scroll, SPA navigation) β no flash of unblocked content |
| Profile-based classification | You write a plain-English description of what you want to watch. The LLM uses that description. Your tags are a heuristic fallback when Ollama is down |
| Desktop app control | A JavaFX desktop app lets you enable/disable the extension, manage profiles, and see live stats β all without opening Chrome |
| Profile sync | Profiles created or activated on the desktop app automatically sync to the extension every 30 seconds |
| Desktop file guard | Scans ~/Desktop, classifies every file and folder by name against your profile, and moves irrelevant items to a quarantine folder β restoring them instantly when LockIn is paused or the profile changes |
| Fully local | Everything runs on your machine. Ollama, the extension, and the desktop app communicate over localhost only |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Arc / Chrome β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β YouTube Tab β β
β β scrapeFn() βββΊ tiles[] β β
β β applyOverlayFn() βββΊ frosted overlays on blocked tiles β β
β β MutationObserver βββΊ instant re-block on re-render β β
β ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ β
β β chrome.scripting β
β ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ β
β β background.js (Service Worker) β β
β β classifyTilesLocally() βββΊ Ollama llama3.2:3b β β
β β heuristicIrrelevant() βββΊ profile tags (fallback) β β
β β syncProfilesWithDesktop() βββΊ POST /profiles every 30s β β
β ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HTTP localhost:7432
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
β LockIn Desktop App (JavaFX) β
β β
β ControlServer :7432 βββΊ /status /profiles /stats β
β DesktopGuard βββΊ scan ~/Desktop, quarantine files β
β OllamaMonitor βββΊ poll localhost:11434 every 4s β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
localhost:11434
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
β Ollama β
β llama3.2:3b βββΊ KEEP/BLOCK labels for YouTube tiles β
β βββΊ KEEP/BLOCK labels for Desktop files β
β gemma4:e2b βββΊ tag suggestions for profiles β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
LockIn/
βββ README.md
βββ ARCHITECTURE.md β deep-dive component reference
βββ desktop/ β JavaFX desktop control app
β βββ build.gradle
β βββ gradlew
β βββ src/main/java/com/lockin/
β βββ App.java β main window, all UI panels
β βββ ControlServer.java β HTTP server on :7432
β βββ DesktopGuard.java β ~/Desktop scanner + quarantine
β βββ OllamaMonitor.java β async Ollama health check
βββ tab-scrapper/
βββ build.gradle β legacy Java backend (optional)
βββ extension/ β Chrome Extension (MV3)
β βββ manifest.json
β βββ background.js β service worker, core logic
β βββ popup.html / popup.js β toolbar popup
β βββ options.html / options.js β settings page
βββ src/main/java/com/youtubewatch/
βββ TileReceiver.java β optional archival server :8765
βββ VideoEnricher.java β async metadata fetcher
βββ YouTubeMonitor.java β legacy Selenium monitor
βββ JsonLogger.java
βββ TileRecord.java
βββ VideoMetadata.java
| Requirement | Version |
|---|---|
| Java (JDK) | 21 or higher |
| Ollama | Latest |
| Arc or Chrome | Any recent version |
| macOS | Tested on macOS (Apple Silicon) |
git clone https://github.com/alanturrr-1703/LockIn.git
cd LockIn# Install from https://ollama.com or via Homebrew
brew install ollama
# Pull the models LockIn uses
ollama pull llama3.2:3b # KEEP/BLOCK classification
ollama pull gemma4:e2b # tag suggestion
# Start Ollama with extension CORS enabled
OLLAMA_ORIGINS="*" ollama serve
OLLAMA_ORIGINS="*"is required because the Chrome extension sends requests with achrome-extension://origin, which Ollama blocks by default.To make this permanent on macOS (menu-bar app):
launchctl setenv OLLAMA_ORIGINS "*" # Then quit and reopen Ollama
- Open
arc://extensionsorchrome://extensions - Enable Developer mode
- Click Load unpacked
- Select
LockIn/tab-scrapper/extension - Pin the extension to your toolbar
cd LockIn/desktop
./gradlew runThe window appears in a few seconds. Keep the terminal open.
To launch cleanly if port 7432 is already in use:
lsof -ti :7432 | xargs kill -9 2>/dev/null; ./gradlew run| Control | What it does |
|---|---|
| Mode select | lookahead β scrape 3000 px below fold (default) Β· strict β visible only Β· all β entire DOM |
| Focus topic | Ad-hoc override topic sent to the classifier |
| Scrape now | One-shot scrape + classify + overlay cycle |
| Watch on/off | Starts a repeating alarm (every 30 s) that scrapes automatically |
| Open settings | Opens the full profile management page |
- Profiles β create, rename, activate, delete profiles
- Prompt β plain-English description of what you want to watch (drives the LLM)
- Tags β comma-separated keywords used by the heuristic fallback when Ollama is unreachable
- Suggest tags β asks
gemma4:e2bto generate 40β50 tags from your profile description
For each fresh tile (not in cache):
β
βββ Ollama UP β llama3.2:3b classifies using your profile prompt
β Result cached permanently
β
βββ Ollama DOWN β heuristic: check if title+channel+description contains
any of your profile tags
Result NOT cached (Ollama retries next cycle)
The prompt format is deliberately minimal to keep latency low:
System: You label YouTube videos as KEEP or BLOCK. Reply only with labels,
one per line like "1:KEEP". No other text.
User: User focus: Focus on algorithms, data structures, ML, system design.
1. "Dynamic Programming Explained" β CS Dojo
2. "IPL 2025 Highlights" β Cricket Max
3. "LLM Fine-tuning in Python" β Andrej Karpathy
4. "Bollywood Top 10" β T-Series
Response: 1:KEEP 2:BLOCK 3:KEEP 4:BLOCK
Classification results are stored in chrome.storage.local with a TTL so the same tile is never re-classified on every scrape:
| Result | Cache TTL |
|---|---|
| Ollama verdict (KEEP or BLOCK) | Permanent (until profile changes) |
| Heuristic verdict | Not cached β Ollama retries next cycle |
The JavaFX app binds an HTTP server on http://127.0.0.1:7432 that acts as a bridge between the desktop and the extension.
Runtime β Shows Ollama status (green dot = running, red = stopped), the classify model (llama3.2:3b), and the tags model (gemma4:e2b).
Session Stats β Live counters updated by the extension after every scrape: tiles blocked, scrape cycles, active profile name, last scrape time.
Profiles β Full profile management without opening Chrome:
- See all profiles synced from the extension
- Click Set Active to switch profiles β the extension picks up the change within 30 seconds
- Click οΌ New Profile to create a profile with a name, prompt, and tags β it syncs to the extension automatically
Desktop Guard β Scans ~/Desktop and quarantines irrelevant files:
- Click β³ Scan Now to classify all desktop files against the active profile
- Blocked items (red chips) are moved to
~/.lockin-quarantine/ - Kept items (green chips) stay on the desktop
- Pausing LockIn restores all quarantined files instantly
- Switching profiles restores all files first, then re-classifies from scratch under the new profile
| Method | Path | Purpose |
|---|---|---|
GET |
/status |
Extension polls before every scrape β returns { "enabled": true/false } |
POST |
/profiles |
Extension pushes current profiles; server replies with pending desktop changes |
POST |
/stats |
Extension reports blocked count, profile name, scrape timestamp |
POST |
/toggle |
Flip enabled state programmatically |
GET |
/health |
Liveness check + cumulative stats |
Extension (every 30s, independent of Watch mode)
POST /profiles { profiles: [...], activeProfileId: "..." }
β
βββ desktop made no changes β { dirty: false }
β extension keeps its current state
β
βββ desktop created/activated β { dirty: true, profiles: [...], activeProfileId: "..." }
extension writes to chrome.storage.local
changes take effect on next scrape
When LockIn is active, the Desktop Guard:
- Calls
restoreAllSilently()β returns any previously quarantined files to~/Desktopfirst (ensures a clean slate on every scan, especially after a profile switch) - Lists all non-hidden files and folders in
~/Desktop - Sends them in batches of 10 to
llama3.2:3bwith the active profile's prompt - Moves anything labelled
BLOCKto~/.lockin-quarantine/ - Writes a manifest at
~/.lockin-quarantine/manifest.jsonrecording every moved item
When LockIn is paused (toggle off), all quarantined items are moved back to ~/Desktop instantly.
Active profile: "Computer science student β focus on DSA, ML, system design"
~/Desktop before scan:
Projects/ β KEEP β
Notes.md β KEEP β
LeetCode.py β KEEP β
Photos/ β BLOCK β moved to ~/.lockin-quarantine/Photos/
Netflix Downloads β BLOCK β moved to ~/.lockin-quarantine/Netflix Downloads/
Family Trip 2024/ β BLOCK β moved to ~/.lockin-quarantine/Family Trip 2024/
Switch profile to: "Physics student β focus on quantum mechanics, thermodynamics"
Scan re-runs:
restoreAllSilently() β Photos, Netflix, Family Trip back on Desktop
Re-classify under Physics profile:
Photos/ β KEEP β
(personal life / study breaks β allowed)
Netflix Downloads β BLOCK β
Family Trip 2024/ β KEEP β
| Problem | Fix |
|---|---|
| Extension gets 403 from Ollama | Start Ollama with OLLAMA_ORIGINS="*" ollama serve |
Desktop app fails with Address already in use |
Run lsof -ti :7432 | xargs kill -9 then relaunch |
| Profiles not syncing to extension | Reload the extension in arc://extensions β this re-creates the PROFILE_SYNC_ALARM |
| Nothing is being blocked on YouTube | Check that Ollama is running (curl localhost:11434) and that your profile has a prompt set |
| Desktop guard not restoring files | Check ~/.lockin-quarantine/manifest.json β if it exists, run the app and toggle LockIn off |
./gradlew: Permission denied |
Run chmod +x gradlew once then retry |
| Ollama takes too long per tile | Make sure you're using llama3.2:3b not gemma4:e2b for classification β gemma4 is much slower |
| Model | Role | Why |
|---|---|---|
llama3.2:3b |
YouTube tile classification, Desktop file classification | Fast, accurate KEEP/BLOCK at 3B params (~5β8 s per batch of 4 tiles) |
gemma4:e2b |
Profile tag suggestion | Larger model gives richer, more contextual tag sets |
To change models, edit the constants in background.js (extension) and DesktopGuard.java (desktop):
// background.js
const OLLAMA_MODEL_CLASSIFY = "llama3.2:3b";
const OLLAMA_MODEL_TAGS = "gemma4:e2b";// DesktopGuard.java
private static final String MODEL = "llama3.2:3b";tab-scrapper/src/ contains an older Java HTTP server (TileReceiver) and a Selenium-based monitor (YouTubeMonitor). These are not required for normal use β the extension works entirely without them.
| Command | What it runs |
|---|---|
cd tab-scrapper && ./gradlew runReceiver |
Tile archival server on :8765 |
cd tab-scrapper && ./gradlew run |
Selenium YouTube monitor (opens real Chrome window) |
MIT β see LICENSE.