Skip to main content

Aegis

A macOS background focus tool. Start a focus session with a topic and duration; the Chrome extension reports pages you visit to a local daemon, which uses an LLM (any OpenAI-compatible endpoint) to judge whether the page supports your study topic. Off-task pages are redirected to a local "keep focus" page.

  • No browsing latency. Pages load normally; the check happens in the background and a caught distraction is redirected within ~1s of being judged.
  • Locked sessions. While a session is active it cannot be stopped, reset, or quit through the app — the daemon runs as a launchd KeepAlive agent and session state persists atomically across restarts (crash/restart/corruption cannot silently void the lock). Be honest with yourself: this is a self-imposed commitment aid, not tamper-proof confinement — anyone who owns the machine can still unload the agent, disable the extension, use another browser, or edit the session file. It guarantees the lock is not lost accidentally; it does not prevent leaving intentionally.

Architecture

Chrome (extension: background.js)
  │  tabs.onUpdated / onActivated
  ▼  POST /evaluate  (async, non-blocking)
macOS daemon (aegis/)  ──►  SQLite cache ──► LLM (Groq/NIM/Zen/…)
  │  whitelist  →  cache  →  LLM verdict
  ▼  {allowed: false, confidence >= 0.8}
redirect tab ──►  http://127.0.0.1:8123/blocked  ("Aegis" page)

Install

Aegis is a Python package installed with pipx; the daemon runs as a launchd LaunchAgent managed by the aegis CLI.

brew install pipx
pipx install aegis-focus         # from PyPI
aegis install                    # render the LaunchAgent plist and start the daemon

The package is published on PyPI as aegis-focus (the plain aegis name was already taken by another project); the installed commands are still aegis and aegis-daemon.

The plist points at the pipx venv's python, so the daemon always runs exactly the code the CLI manages. After a new release, upgrade and reload:

pipx upgrade aegis-focus
aegis restart                    # refuses while a focus session is active (--force overrides)

To install from this repository instead (development builds):

pipx install .                   # from the repo (or pipx install /path/to/aegis)

The Chrome extension is not part of the Python package: load it from the extension/ folder of the repository (that folder is also bundled in the PyPI source download).

CLI reference

aegis install     install/repair the daemon agent (re-renders the plist, reloads)
aegis restart     reload the agent; refuses during an active session unless --force
aegis status      agent loaded? daemon healthy? session state?
aegis cleanup     remove logs and the verdict cache (keeps config and the session lock)
aegis uninstall   stop the daemon, remove the agent + logs + ALL state (confirms unless -y)

There is deliberately no aegis stop: the daemon is a KeepAlive agent — you cannot stop it without removing it. aegis uninstall is the removal path. A deliberate user can always escape (see "Locked sessions" above); the commands are honest about it.

Configure the LLM

Create ~/Library/Application Support/Aegis/config.json:

{
  "LLM_BASE_URL": "https://api.groq.com/openai/v1",
  "LLM_API_KEY": "gsk_...",
  "LLM_MODEL": "llama-3.3-70b-versatile",
  "LLM_TIMEOUT": "60",
  "BLOCK_CONFIDENCE": "0.8"
}

or export the same names as environment variables in the terminal you run the daemon from (a launchd agent does not inherit your shell env; the rendered plist only sets the PATH captured at aegis install time). After a pipx ensurepath or a shell change, run aegis restart so the plist is re-rendered with the new PATH (an unchanged plist is hot-restarted with kickstart; a changed one is booted out and re-bootstrapped so launchd reads the new program path). config.json is the reliable route for secrets under launchd. If no key is set the daemon still runs and blocks nothing (every verdict falls back to allow). Invalid values (e.g. a bad AEGIS_PORT) fall back to defaults instead of crashing.

Note: the Chrome extension hardcodes 127.0.0.1:8123 (see extension/background.js), so AEGIS_PORT/AEGIS_HOST should be left at their defaults for the extension to work.

Extra always-allowed domains can be added without touching code:

{ "EXTRA_WHITELIST": "khanacademy.org, brilliant.org" }

Whitelisted entries match subdomains too (e.g. en.wikipedia.org) and are IDNA-normalized, so Unicode domains work. Public-suffix entries (e.g. co.uk, com.au — domains that are really TLDs) are rejected with a warning: you cannot whitelist an entire country's domains, and the rejection keeps a typo from silently widening the allowlist.

Then load the extension:

  1. Open Chrome → chrome://extensions
  2. Enable Developer modeLoad unpacked
  3. Select the extension/ folder

Usage

  • Click the A menu bar icon → Start Focus Session… → enter topic and duration (1–600 min). The session cannot be stopped early through the app (a deliberate user can always remove it — see "Locked sessions" above).
  • When you finish (or the daemon restarts mid-session), the lock is restored automatically from ~/Library/Application Support/Aegis/session.json (a backup copy .bak is kept and used if the lock file is ever corrupt).

Verdict pipeline

  1. No active session → allow.
  2. Whitelisted domains (github.com, wikipedia.org, …) and browser-internal pages (chrome://, about:, edge://, Chrome Web Store, the Aegis daemon itself, …) → allow, no LLM call.
  3. SQLite verdict cache (per URL per session) → instant repeat answers. LLM failures (timeout, transport error, unparseable reply) are never cached — an outage cannot seal a page open (or lock one shut).
  4. LLM call with page title, URL, meta description, and top/middle/bottom text samples (or YouTube title/channel/description for videos) → JSON verdict {"allowed": bool, "confidence": 0-1, "reason": str}. Verdicts are rate-limited per session (up to 30 per 5 s) so a tab-storm cannot burn the LLM budget; a burst of navigations simply waits its turn and gets slower answers, it never makes more calls.
  5. Any LLM failure → allow (never block browsing due to API issues) — meaning a slow or stalled evaluator intentionally lets an off-task page through. The API is loopback-only and rejects every non-extension Origin, so no website can read your session or burn your LLM quota.

The "Aegis" page

Off-task pages are redirected to http://127.0.0.1:8123/blocked, which:

  • Shows the block reason, session topic, and a live countdown.
  • Auto-returns to the original page 5 seconds after the session ends (no input needed).
  • Appeals: a text box + "Appeal" button. The appeal is sent to the LLM, which decides whether your justification warrants access to that page during this session. The page's link is attached automatically — nothing to copy.
    • Granted → the page opens immediately and the allow verdict is cached for the session, so it is not instantly re-blocked.
    • Denied → the reason is shown; you may appeal again (rate-limited to 1 per 5 s to protect the LLM budget).
    • LLM outage → denied (fail-closed — an appeal is not an unlock bypass). Appeals are retried up to 6 times before giving up.
    • Grants are per-session: a new session judges the page fresh.
    • The page also shows an "Aegis daemon unreachable" hint after several failed /status polls, so a dead daemon is visible instead of a silently frozen countdown.

Uninstall

aegis uninstall

Removes the LaunchAgent, ~/Library/Logs/Aegis/, and the entire ~/Library/Application Support/Aegis/ state directory (session lock, cache, config). The pipx package itself is left installed (pipx uninstall aegis removes that too). The project directory is left in place.

Development

python3 -m venv venv
./venv/bin/pip install -e .
python -m aegis.main          # run daemon in foreground (menu bar app)
python3 -m tests.test_evaluator   # offline daemon+CLI unit tests (no LLM needed)
node aegis/static/test_blocked.mjs      # blocked-page UI harness
node extension/test_background.mjs      # extension harness

Logs: ~/Library/Logs/Aegis/daemon.log (auto-rotating, capped at ~1.5 MB total) plus unbounded daemon.out.log / daemon.err.log (launchd's stdout/stderr redirects). The daemon is crash-proof by design: corrupt lock files fall back to the backup copy, a corrupt SQLite cache degrades to an in-memory cache, and any LLM/cache error always degrades to allow.

License

GNU General Public License v3.0 or later — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aegis_focus-1.1.0.tar.gz (90.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aegis_focus-1.1.0-py3-none-any.whl (60.6 kB view details)

Uploaded Python 3

File details

Details for the file aegis_focus-1.1.0.tar.gz.

File metadata

  • Download URL: aegis_focus-1.1.0.tar.gz
  • Upload date:
  • Size: 90.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for aegis_focus-1.1.0.tar.gz
Algorithm Hash digest
SHA256 790015f98ea4474793cc832afd0848b33b8b26f63ae70e459a59efee55b3e33d
MD5 3aeb726c710fc48c6428c2ed0899d2a9
BLAKE2b-256 0c7b5ffa8423590d5986b5358ca7229fe09d9aa0f8a0f821e3f803b2379e022a

See more details on using hashes here.

File details

Details for the file aegis_focus-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: aegis_focus-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 60.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for aegis_focus-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bfb31b45d1d5ad194c2a7d473d8ca10c6235fd3ac124f3c796ce16500b57c016
MD5 c3c893183a4960cee1b9aa0d85380d59
BLAKE2b-256 ba8f99a8cd18acab8e9f330fcae47fa13b8d022efd955e120d7df1d82b27ca38

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page