Sisyphus
AI augmented focus tracker and game-launch speed bump
Available at: sisyphusfocus.site
Issue / Solution
The Issue: Digital distraction and impulsive game launches (like League of Legends) are friction-free, often triggered automatically by muscle memory when a user encounters a small mental barrier during work. Standard website blockers or lock-down tools are overly coercive, triggering a defensive reaction where the user simply uninstalls the blocker, rather than training their focus habits.
The Solution: I built Sisyphus, a lightweight Windows utility that acts as a deliberate "speed bump." Using Windows Image File Execution Options (IFEO) registry hooks, Sisyphus intercepts game launches before the process executes. Instead of locking down the PC, it displays a checklist dashboard. The user must check off their actual work items before the game opens, introducing a mindful delay that breaks the automatic loop of distraction.
Overview
Sisyphus is a Windows desktop app (now at v2.0) that puts a deliberate moment
of friction between you and the things that derail focus. When you launch a blocked game or open
a distracting website, a gate appears, and you can't proceed until you've checked
off the work you said you'd do first. It was built for League of Legends, but it can block any
.exe or website.
Beyond the launch gate, Sisyphus works as an AI-augmented focus tracker. It quietly logs where your screen-time actually goes, scores it against goals you set, drafts daily objectives and to-dos with AI assistance, surfaces AI-generated feedback on how each day went, and nudges healthier habits with smart stretch, sleep, and idle reminders. The name is the thesis: focus is a boulder you push up the hill every day, and the app's job is to make the right action the easy one and the distracting action require a conscious push.
Design Philosophy
Sisyphus v2.0 is built as three .NET 8 projects spanning ~50 source files, backed by 128 unit tests, and ships with 5 UI languages and 8 color themes. Four principles shaped every design decision underneath that.
No server, no account
Everything lives in local JSON under %AppData%\Sisyphus. The only network calls
are an optional GitHub update check and the user's own AI provider — the API key never leaves
the machine. Privacy by construction.
Pure core, thin shells
Sisyphus.Core holds UI-free, deterministic domain logic so it can be unit-tested
and shared by both the WPF app and the launcher. The decision logic of the risky features
(version compare, hash verify, streak math, IFEO name validation) is pure and tested.
Best-effort enforcement, never crash
Blocking, tracking, and updating are all "best-effort": a registry hiccup or I/O blip degrades gracefully (open the web page, skip a sample) rather than taking down the app. Stores swallow load errors and return empty rather than throwing into the UI.
Save-and-restart over live mutation
Theme and appearance changes persist to config and relaunch the app rather than trying to re-skin every open window live (which proved unreliable in WPF). A clean restart guarantees every window picks up the new palette, and config is already on disk, so nothing is lost.
Solution Architecture
The codebase splits into a pure domain core and two thin shells: the always-running dashboard and a tiny launcher that the blocked game's IFEO key redirects to.
Version is single-sourced in Directory.Build.props; the installer is built with Inno
Setup via a PowerShell script.
The Blocking Gate — How It Actually Works
Sisyphus blocks in three layers, because no single mechanism covers every kind of program. Rather than running a CPU-heavy background watchdog, the primary layer registers directly under Image File Execution Options (IFEO) in the Windows Registry.
Layer 1 — Process Interception Flow (IFEO Registry Hook)
Layer 1 — Image File Execution Options (IFEO) redirect
For a blocked game.exe, Sisyphus writes a Debugger value under the HKLM
IFEO key for that exe name (requires admin, done via an elevated --set-ifeo relaunch
of the app itself). Windows then launches the Sisyphus launcher instead of the game, passing along
the original command line. The security-critical trick is that the launcher then re-launches the
very exe that has an IFEO debugger pointing back at it. To avoid an infinite loop, it uses
CreateProcess with DEBUG_ONLY_THIS_PROCESS (which bypasses the
debuggee's IFEO key), then immediately detaches and clears kill-on-exit. The exe name is
normalized to a bare *.exe filename before it can become a registry subkey
(path-traversal stripped), validated by dedicated unit tests.
Layer 2 — AppBlocker (running-process kill)
IFEO can't catch packaged/UWP apps or processes that are already running, so a 1 Hz timer enumerates processes by name, kills blocked ones that aren't currently allowed, and raises the gate. After a gate is passed, the exe is written to the allow store with a 12-hour allowance so it isn't instantly re-killed.
Layer 3 — SiteBlocker (browser tab)
A second 1 Hz timer checks whether the foreground window is a known browser whose title contains a blocked brand token; if so, it sends Ctrl+W to close the tab and raises the website gate, with an 8-second per-(browser, pattern) cooldown so it doesn't fight the user.
The allow store is shared by two processes — the launcher writes the allowance, and the app's watcher prunes and reads it — so it is guarded by a cross-process mutex and atomic writes. That guarantees the watcher can't drop the allowance the launcher just wrote and kill the game the gate just launched.
App Lifecycle & Orchestration
App.xaml.cs is the composition root. On startup it handles the elevated
--set-ifeo/--remove-ifeo admin commands and exits if present; takes a
per-session single-instance mutex (a second launch just surfaces the existing window); applies the
saved color theme before any window loads; and registers a class-level
Window.Loaded handler so every window gets accessibility settings applied
automatically. It then wires the long-lived services — activity tracker, site/app blockers,
foreground watcher, the global notepad hotkey, and timers for stretch reminders and a 1-minute
tick (recurrence resets, bedtime, idle prompts) — before branching to either the first-run wizard
or the dashboard.
The app runs with ShutdownMode=OnExplicitShutdown and disposes every service on exit.
Settings changes are applied by persisting config and calling a restart that releases the
single-instance mutex, relaunches via Environment.ProcessPath, and exits.
Data & Persistence
All state is JSON under %AppData%\Sisyphus, each file owned by a small repository
class with a consistent recipe: a cached load, an atomic temp-file write (write
to *.tmp, then File.Move), and a read-time Normalize pass
that trims, defaults, clamps, and migrates old data.
config.json— the whole config graph: settings, blocked games/sites, todos, goals, theme/accessibility, onboarding version.activity.json— per-day foreground/app/task seconds (90-day prune, sub-minute session merge).sessions.json— per-"computer-on" sessions (sequential numbers, 50-session cap).allowed.json— exe → allow-until-UTC, so a just-launched game isn't re-killed (cross-process locked).notes.json— notepad tabs (blank notes dropped).current-task.json— the currently tracked "Go now" task, with staleness detection.
Migrations are config-version aware: the v2.0 release stamps an onboarding version so the "everyone re-runs setup once" upgrade fires exactly one time, and adopts a new notepad default text size for users still on the old default.
Analytics & Goals
The activity tracker samples the foreground window on a timer (driven by the foreground watcher so it doesn't tight-poll), credits elapsed wall-time (capped at 1 hour) to the current app/title unless the user is idle, and persists on a 60-second cadence. A foreground detector resolves the window to a (process, title) pair — with extra effort for terminals, using UI Automation tab titles and a process-tree walk to label CLI tools.
Each app or site is classified into one of six categories, with per-process and per-site user overrides plus custom categories. The Analytics window renders day, week, month, year, and session ranges as bar and circle charts, a category breakdown, and most-used apps, alongside a goals-first section: each goal shows a progress bar and percentage computed from either the apps the user assigned to it or all time in the goal's category. The pure streak and progress math uses a 10-minute "active day" floor. A "Share" button renders a progress-card image, and an optional "AI grade" asks the coach for a letter grade. Sessions shorter than 15 minutes are filtered out of the Session view.
AI Coach & Smart Reminders
Sisyphus is more than a blocker — it doubles as a lightweight focus coach. The AI coach is a
provider-agnostic single-turn chat client (OpenAI / Anthropic / Gemini). It builds a system prompt
from the user's todos and goals plus a structured 7-day context JSON, and the model can return an
upsert_goals JSON plan that is parsed, clamped, and applied back into config. The API
key is sent only in provider headers — never logged, never in a request body. Three modes keep raw
JSON out of the chat surface: Free chat, Coaching (prose only), and
Auto-plan (JSON). The notepad can run a note through the same service to extract todos or
get coaching, so the checklist that gates a game launch reflects real goals instead of busywork.
On top of focus, the app watches for healthy-habit cues and fires smart reminders to stretch, wind down for sleep, and step away after long idle or continuous-use stretches.
Theming & Accessibility
Eight presets (pure data), including three light themes. Applying a theme mutates the shared
color of the seven themed brushes in place, so every consumer repaints, and the brushes carry alpha
for the app's translucency. A hard-won lesson fixed in v2.0: brush references inside the theme's own
styles and templates must use {DynamicResource}, not {StaticResource} — a
static setter resolves at parse time (before the theme applies) and captures the original white
brush, which was invisible on dark themes but unreadable white-on-light on the light themes.
Accessibility settings apply a text scale (a root layout transform), an optional text-color override, and a font family to every window via the class-level loaded handler. The compact dashboard overlay opts out of content scaling and uses its own timer-size control.
Self-Update & Onboarding
The update service checks GitHub's "latest release" endpoint (unauthenticated) and delegates all
parsing and version logic to a pure, tested release-manifest type. On "Yes", the installer is
streamed to temp and its SHA-256 is verified against the release's
SHA256SUMS before it runs elevated and silent, after which the installer relaunches
Sisyphus. If a release publishes sums, a matching hash is mandatory — a missing or mismatched hash
aborts to the releases page rather than running an unverified binary elevated.
Onboarding is a 6-step wizard with a live theme preview; nothing touches the real app until Finish (which persists and restarts). A version gate re-runs it once for everyone upgrading to a major release, without data loss, while new installs always see it on first launch.
Engineering Patterns
- Atomic writes everywhere — temp file plus
File.Move(overwrite), with unique temp names where two processes may write. - Dictionary localization — five locale dictionaries (en/ko/fr/es/ja) at full key parity, with
DynamicResourcefor XAML, a lookup helper for code-behind, and an English-then-key fallback. - Custom window chrome — borderless windows with a shared title-bar drag/min/max helper and themed control templates.
- Win32 interop kept alive correctly — native callback delegates are stored in fields so they aren't garbage-collected; hooks, hotkeys, and mutexes are disposed.
- Async UI safety — windows that run AI calls set a closing flag and bail after the await so a continuation never touches a closed window.
What I Built
- A three-layer blocking system: IFEO redirect for executables, a running-process killer for UWP/already-running apps, and a browser-tab closer for distracting sites.
- A compact WPF dashboard with custom terminal-style windows and popups, plus a tiny launcher gate that receives the original executable path, shows todos, and launches the game only after completion.
- A pure, unit-tested domain core (128 tests) shared by both the app and the launcher.
- Atomic, normalize-on-read JSON persistence with a cross-process lock guarding the launch-allow store.
- Categorized foreground activity logging with day, week, month, year, session, and custom-range analytics, plus a goals-first progress view and shareable progress cards.
- A provider-agnostic AI coach (OpenAI / Anthropic / Gemini) that drafts daily objectives, decomposes them into to-dos, and gives plain-language feedback — with the API key kept on the machine.
- Eight color themes (three light), five UI languages, and per-window accessibility scaling.
- SHA-256-verified self-update from GitHub releases and a 6-step onboarding wizard with a live theme preview.
- Smart reminders for stretching, sleep, and long idle or continuous-use stretches.
Design Choices
The app is intentionally transparent: config is plain JSON under %APPDATA%, blocks
are removable from the UI, and there is no password or anti-tamper mechanism. The goal is friction
and visibility, not coercive control.
Launch & Traction
Sisyphus reached 50+ users within the first week of its V1.0 release through sisyphusfocus.site. I kept an active line of communication with early users and shipped monthly updates driven by their real-time feedback, refining the launch-gate flow, activity analytics, and reminder behavior release over release.
Stack
.NET 8, WPF, Windows Registry, IFEO, Win32 interop, JSON persistence, foreground window tracking, cross-process locking, AI/LLM integration (OpenAI / Anthropic / Gemini), SHA-256-verified updates, Inno Setup, x64 Windows packaging.