One terminal inbox for everything that pings you: email, Slack, GitHub, YouTube, Reddit, Twitter/X, calendar, and (through small bridges) WhatsApp, Telegram, Discord and more.
Three panes: accounts and views on the left, a time-grouped feed in the middle, and a daily summary on the right (counts, top sources, what's coming up, quick actions). An AI bar at the bottom can summarize your unread items.
Written in Rust with ratatui. Built for Linux (tested layout on Arch).
OwlSentry only reads from your accounts. Read, archived and starred flags are kept locally in
~/.local/share/owlsentry/state.json.
cargo run --release -- --demo--demo loads sample data so you can look around before connecting anything.
sudo pacman -S --needed rust xdg-utils libnotify # libnotify = desktop alerts (optional)
git clone <this repo> owlsentry && cd owlsentry
cargo install --path . # installs to ~/.cargo/bin/owlsentry
owlsentry --init # writes ~/.config/owlsentry/config.toml
$EDITOR ~/.config/owlsentry/config.toml
owlsentryNo system TLS libraries are needed (everything uses rustls). A terminal with true color and a font with common Unicode symbols is enough.
Every account is a [[source]] block in ~/.config/owlsentry/config.toml. See
config.example.toml for all options. Secrets can be inline (secret = "...")
or fetched from a command (secret_cmd = "pass show slack/token"), which keeps tokens out of the file.
type |
What you get | Needs |
|---|---|---|
email |
Unread mail over IMAP (headers only) | host, user, password (Gmail: app password) |
slack |
Recent messages in channels/DMs you list | user token (xoxp-), channel ids |
github |
Your GitHub notifications | token with notifications scope |
youtube |
New videos from channels | channel ids (public feeds, no key) |
reddit |
New posts in subreddits | nothing (public JSON; Reddit may rate-limit) |
twitter |
Your mentions | X API tier that allows reads (paid) |
calendar |
Next 24 hours of events + reminders | an .ics URL (Google Calendar "secret address in iCal") |
rss |
Any RSS/Atom feed | feed URL |
command |
Anything else | a script that prints JSON lines |
These have no official personal-account API. The supported route is a command
source: any script that prints one JSON object per line:
{"from": "Alex", "body": "are you free tomorrow?", "ts": 1700000000, "count": 2, "url": null}Optional fields: headline, url, count, tag, mention, important, id.
Point a command source at it, with app = "whatsapp" to get the right icon.
examples/command-source.sh is a working template.
WhatsApp ships with a ready-made bridge in examples/whatsapp-web:
it reads your own Chromium profile (chat list, Status names, unread threads)
and prints the JSON lines above — no WhatsApp libraries, decryption happens
inside the official Web client. Easiest path: open OwlSentry, press C,
open WhatsApp, follow the guided setup. Note: this drives the official client
with browser automation, which Meta's terms don't bless — use your own account
at your own discretion.
| Key | Action | Key | Action |
|---|---|---|---|
j k / arrows |
move | o / Enter |
open in browser (marks read) |
h l / Tab |
switch pane | r |
reply (mail opens a draft) |
[ ] |
previous / next account | m / M |
mark read / mark all shown read |
g G |
top / bottom | a |
archive / unarchive |
/ |
search | i |
star / unstar |
f |
cycle tag filter | s |
summarize this conversation (AI) |
u |
unread only / all | : |
ask the AI about your unread items |
t |
newest / oldest first | R |
refresh now |
C |
connect an app (guided setup) | z / Ctrl+Z |
suspend to shell (fg resumes) |
Esc |
clear search and tag filter | e / ? / q |
source errors / help / quit |
Views on the left: Unread, Mentions, Important (starred or tagged important), Today, Archive.
Tags (dev, personal, work, ...) default per app and can be set per source with tag = "...".
Words in handles = [...] mark matching items as mentions.
Press : and ask something ("what needs a reply today?"), or press s on an item to summarize its
conversation. Set ANTHROPIC_API_KEY (or key_cmd under [ai]). The model defaults to
claude-sonnet-5-5 and can be changed with model = "...". Nothing is sent anywhere until you ask,
and only the notification text needed for that question is sent.
- Credentials are read at fetch time and never written to disk by OwlSentry.
Prefer
secret_cmd(e.g. a password manager) over inlinesecret. - Text from remote services is stripped of control characters before it reaches the terminal.
- Links are opened with
xdg-openonly if they start withhttps://,http://ormailto:. - The AI is told to treat notification text as data, not instructions.
- The WhatsApp bridge only ever reads a copy of your browser profile; the live profile is never opened by a second process.
- Email shows unread messages only, and reading them elsewhere removes them here.
- Calendar: one-off events and simple daily/weekly repeats. Events with a time zone ID are read as
local time. Repeats with
INTERVAL,COUNTor exceptions are not expanded. - Slack uses a user token and the channels you list; it does not discover channels for you.
- First run shows recent Slack/feed items as unread. Press
Mto mark everything shown as read.
src/main.rs startup, fetcher thread, event loop
src/app.rs state, filters, keys, actions, summary numbers
src/ui.rs all drawing (plus a layout smoke test)
src/model.rs Notification, Kind, tag colours
src/config.rs config file (+ one-time rename migration)
src/connect.rs guided in-app setup, hot source reload
src/state.rs local read/archive/star state
src/ai.rs Anthropic API call
src/demo.rs sample data for --demo
src/sources/ one file per service
examples/whatsapp-web/ headless WhatsApp bridge (Node + system Chromium)
assets/ OwlSentry icon (SVG) and README hero
Create src/sources/yours.rs with a struct holding a Meta that implements
Source { fn meta(&self) -> &Meta; fn fetch(&self) -> Result<Fetched, String> }, then add one match
arm in sources::build. Use meta.note(id, who, headline, body, time) to create items; stable ids keep
read/archived state across restarts.
cargo test # layout and calendar tests
