Free, open-source distraction blocker. Blocks websites and apps at the OS level. Survives force quits, restarts, and reboots.
Free · GPL-3.0 · macOS + Windows · Tauri UI · C# daemon (Windows) · Swift daemon (macOS)
focus-lock/
├── ui/ Tauri + React + TypeScript frontend
│ ├── src/ React pages and components
│ └── src-tauri/ Rust backend (IPC bridge, tray)
├── daemon-win/ C# .NET 8 Worker Service (Windows daemon)
├── daemon-mac/ Swift CLI (macOS daemon)
├── shared/ Shared TypeScript protocol types
├── installer/
│ ├── windows/ PowerShell + WiX installer files
│ └── macos/ Shell scripts + PKG components
├── update-server/ Cloudflare Worker (update manifest server)
├── landing/ Static marketing site
├── scripts/ Dev tooling (icon generation etc.)
└── .github/workflows/ CI/CD pipelines
| Tool | Version | Install |
|---|---|---|
| Node.js | 20+ | nodejs.org |
| Rust | stable | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
| .NET SDK | 8.0 | dot.net (Windows only) |
| Swift | 5.9+ | Xcode (macOS only) |
cd ui
npm install
npm run dev # Vite dev server at http://localhost:1420
# OR
npm run tauri dev # Full Tauri window (requires daemon running)cd daemon-win/FocusLock.Daemon
dotnet run
# Pipe is available at \\.\pipe\focuslockcd daemon-mac
swift run
# Socket is available at /var/run/focuslock.sockThe repository ships under github.com/Relevant47/focus-lock — those URLs throughout
landing/, ui/src/pages/Settings.tsx, and the update server are real and need no
replacement. If you fork this repo under a different account, do a project-wide
find-and-replace on Relevant47 to point links at your fork.
Before shipping a build, supply these secrets:
{
"plugins": {
"updater": {
"pubkey": "REPLACE_WITH_YOUR_TAURI_UPDATER_PUBKEY",
"endpoints": ["https://YOUR_UPDATE_DOMAIN/{{target}}/{{arch}}/{{current_version}}"]
}
}
}[vars]
GITHUB_OWNER = "your-github-username"
GITHUB_REPO = "focus-lock"npm install --save-dev sharp # one-time
node scripts/generate-icons.mjsOr use Tauri's built-in icon generator:
cd ui && npm run tauri icon ../icon.svgcd ui && npm run tauri signer generate -- -w ../updater.key
# Outputs: updater.key (KEEP SECRET) and updater.key.pub (put in tauri.conf.json)# Build daemon
cd daemon-win/FocusLock.Daemon
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish/
# Build Tauri app
cd ../../ui
npm run tauri build -- --target x86_64-pc-windows-msvc
# Output: src-tauri/target/x86_64-pc-windows-msvc/release/bundle/# Build daemon
cd daemon-mac
swift build -c release
# Output: .build/release/FocusLockDaemon
# Build Tauri app
cd ../ui
npm run tauri build -- --target aarch64-apple-darwin # Apple Silicon
npm run tauri build -- --target x86_64-apple-darwin # Intel
# Output: src-tauri/target/{target}/release/bundle/# Install WiX v4
dotnet tool install --global wix
# Copy build artifacts into installer/windows/
# daemon/ ← from daemon-win/FocusLock.Daemon/publish/
# ui/ ← from ui/src-tauri/target/.../release/bundle/
cd installer/windows
wix build FocusLock.wxs -o FocusLock.msicd installer/macos
chmod +x build-pkg.sh scripts/postinstall
./build-pkg.sh
# Output: FocusLock-1.0.0.pkgPush a version tag to trigger the full build + release pipeline:
git tag v1.0.1
git push origin v1.0.1The pipeline (.github/workflows/release.yml) will:
- Build the Windows daemon + Tauri MSI on
windows-latest - Build the macOS daemon + Tauri DMG on
macos-14(both Intel and Apple Silicon) - Create a GitHub Release draft with all artifacts
- Promote the draft to published
| Secret | Description |
|---|---|
TAURI_SIGNING_PRIVATE_KEY |
Contents of updater.key |
TAURI_SIGNING_PRIVATE_KEY_PASSWORD |
Password for the key (if set) |
WINDOWS_CERT_BASE64 |
Base64-encoded PFX code signing certificate |
WINDOWS_CERT_PASSWORD |
PFX certificate password |
APPLE_CERT_BASE64 |
Base64-encoded Apple Developer ID Application certificate (P12) |
APPLE_CERT_PASSWORD |
P12 certificate password |
APPLE_SIGNING_IDENTITY |
Certificate CN, e.g. Developer ID Application: Your Name (TEAM_ID) |
APPLE_ID |
Your Apple ID email |
APPLE_APP_PASSWORD |
App-specific password from appleid.apple.com |
APPLE_TEAM_ID |
Your Apple Developer Team ID |
KEYCHAIN_PASSWORD |
Any random string (used for CI keychain) |
cd update-server
npm install
# Edit wrangler.toml: set GITHUB_OWNER and GITHUB_REPO
npx wrangler deploy
# Then update tauri.conf.json endpoints to point to your worker URLThe site is deployed on Vercel, configured by vercel.json at the repo root:
it serves landing/ as the static output and applies the security headers (HSTS,
X-Frame-Options, etc.). The api/ directory holds the Vercel serverless functions
(download, get-reviews, submit-review, subscribe) that back the site — these
require Vercel, so deploy the whole repo, not just landing/.
npm i -g vercel
vercel # from the repo root; vercel.json handles the restThe static landing/ HTML/CSS/JS can be hosted on any static host, but the review and
subscribe features won't work without the api/ functions running on Vercel.
- Run
node scripts/generate-icons.mjsto create icon assets - Run
npm run tauri signer generateto create updater keypair - Add updater public key to
tauri.conf.json - Deploy update server to Cloudflare Workers
- Update
tauri.conf.jsonendpoints to your update server URL - Add all GitHub Secrets for CI/CD
- Purchase Windows EV code signing certificate (DigiCert/Sectigo, ~$300/yr)
- Enroll in Apple Developer Program ($99/yr), create Developer ID cert
- Register domain (focuslock.app or similar)
- Deploy landing page + privacy/terms pages
- Set up support email
- Push
v1.0.0tag to trigger first release build - Test the full install flow on a clean VM
- Launch
A voluntary, anonymous in-app survey collects demographics, usage, and feedback, with an optional Beehiiv newsletter opt-in, plus a protected admin dashboard.
Desktop app (ui/) Vercel functions (api/survey/*) Supabase
───────────────── ─────────────────────────────── ─────────
SurveyNudge ─┐ submit ──────────┐
SurveyModal ─┼─► surveyApi.ts ───► prompt-event ────┼─► survey_responses
Settings ──┘ (POST JSON) delete │ survey_prompts_shown
└─► newsletter-embed.html (iframe) ──┐ └─► survey_submit_ratelimit
└─────► Beehiiv inline form (no API key)
Admin dashboard (dashboard/) ────► stats / export (admin JWT) ─► survey_stats_daily
- Single source of truth:
shared/survey.tsdefines every question, its option values (which match the Postgres enums), andvalidateSubmission. The UI form, the dashboard labels, and the API validation all import it. - Trigger:
ui/src/lib/surveyTrigger.ts(pure, unit-tested) surfaces the nudge after 5+ completed sessions OR 7+ days, never during an active block, snoozes 7d/60d on dismiss, and hard-caps at 3 prompts ever. State persists inlocalStorage; submissions are anonymous, keyed to a random install id. - Schema:
supabase/migrations/— applied via the Supabase MCP. RLS is deny-by-default; all access goes through the Vercel functions using the service key. - Newsletter: the final step embeds Beehiiv's inline subscribe form via an iframe to
landing/newsletter-embed.html(which carries the dashboard embed<script>). Beehiiv collects the email directly, so we store no newsletter PII and need no Beehiiv API key. The iframe URL carriesutm_source=in-app-surveyso signups are tagged for segmentation.vercel.jsonexempts that one page from the site-wideX-Frame-Options: DENYand setsframe-ancestorsso the desktop webview can frame it. To update the form, edit it in Beehiiv and paste the new embed code between theBEEHIIVmarkers. Newsletter signup numbers live in the Beehiiv dashboard (not ours); the admin dashboard's "Newsletter" card links there. - Privacy: disclosed in
landing/privacy.html. The survey is opt-in, anonymous, and self-deletable (Settings → Feedback → "Delete my response").
| Var | New? | Secret? | Purpose |
|---|---|---|---|
SUPABASE_URL |
existing | no | Supabase REST/auth base URL |
SUPABASE_SECRET_KEY |
existing | yes | Service-role key — server-side reads/writes (bypasses RLS) |
SUPABASE_ANON_KEY |
add | no | Publishable/anon key — dashboard magic-link auth + JWT verification |
BEEHIIV_API_KEY |
optional | yes | Only for the landing-page api/subscribe.js form. The in-app survey uses the Beehiiv embed and needs no key. |
BEEHIIV_PUBLICATION_ID |
optional | no | As above — only used by api/subscribe.js, not the embed. |
ADMIN_EMAILS |
add | no | Comma-separated allowlist for dashboard access (e.g. you@example.com) |
RATELIMIT_SALT |
optional | yes | Salt for hashing submitter IPs (defaults to a constant if unset) |
The Vercel CLI/MCP can't write env vars here — add them in Project → Settings → Environment Variables (or
vercel env add). The dashboard readsSUPABASE_URL+SUPABASE_ANON_KEYat runtime via/api/survey/config, so it needs no build-time env.
Built by dashboard/ into landing/admin/ (via vercel.json buildCommand) and served at
/admin/analytics. Sign in with a magic link sent to an ADMIN_EMAILS address; data
endpoints reject any other account. Locally: cd dashboard && npm install && npm run dev.
- Add/edit the question in
SURVEY_SECTIONSinshared/survey.ts(id,type,prompt,optionsas{ value, label },skippable, etc.). The form and validation update automatically. - Add a matching column to
survey_responsesvia a new migration insupabase/migrations/(apply it through the Supabase MCP). Single-selects use a Postgres enum whose values must equal theoption.values; multi-selects use atext[]column (no enum needed). - For dashboard charts, add the field to
refresh_survey_stats()(migration0002) and a<Card>indashboard/src/App.tsx. - Update the CSV
COLUMNSlist inapi/survey/export.ts.
The dashboard covers day-to-day needs, but for ad-hoc analysis: use the Export CSV button (admin only) to download all responses, then in Looker Studio create a data source via File Upload and point it at the CSV (re-upload to refresh). For a live connection, Looker's PostgreSQL connector can read the Supabase database directly using the connection details in Supabase → Project Settings → Database (use a read-only role).
See ARCHITECTURE.md for full documentation of the IPC protocol, session state format, anti-tamper mechanisms, and security model.
GPL-3.0 — free to use, modify, and distribute under the same license.
Issues and pull requests are welcome. See ARCHITECTURE.md for the full codebase overview before diving in.