A local-first daily briefing generator that builds a single HTML report (output/today.html) covering:
- Weather — forecast, radar, lightning, satellite imagery (NWS, US locations)
- News — RSS feeds with optional AI-powered summaries and headline rewrites (Ollama)
- HF Propagation — solar indices, band conditions, and signal quality (ham radio)
- Gmail — recent messages grouped by label, with optional AI insights
- Calendar — today's and tomorrow's events (Google Calendar)
- Apple Reminders — incomplete items grouped by list, due-soon callouts (macOS only)
- TV Watchlist — upcoming episodes and poster thumbnails (TMDB)
- Email delivery — optional daily HTML email via Gmail
Every data section can be toggled on/off, and each output mode (local HTML report, email) can be independently enabled or disabled in the config.
| Feature | macOS | Windows | Linux |
|---|---|---|---|
| Core briefing (weather, news, solar, TV) | Yes | Yes | Yes |
| Gmail + Calendar | Yes | Yes | Yes |
| AI news (Ollama) | Yes | Yes | Yes |
| Email delivery | Yes | Yes | Yes |
| Apple Reminders | Yes | No | No |
| Scheduled generation (launchd) | Yes | Via Task Scheduler | Via cron |
Apple Reminders requires macOS EventKit. On Windows/Linux, disable the reminders section in your config and the dependency is skipped automatically.
git clone https://github.com/YOUR_USERNAME/morning-briefing.git
cd morning-briefing
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp config_personal.example.py config_personal.py
# Edit config_personal.py with your location, sections, labels, etc.
# Google OAuth (requires credentials/google_credentials.json — see step 3.3)
python setup/auth.py
# Generate + open
python main.py
open output/today.htmlgit clone https://github.com/YOUR_USERNAME/morning-briefing.git
cd morning-briefing
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt
copy config_personal.example.py config_personal.py
# Edit config_personal.py with your location, sections, labels, etc.
# Set reminders to disabled: "reminders": {"enabled": False, ...}
# Google OAuth (requires credentials\google_credentials.json — see step 3.3)
python setup\auth.py
# Generate + open
python main.py
start output\today.htmlAll personal settings live in config_personal.py, which is gitignored and never committed. The file config.py contains generic shareable defaults.
- Copy the example:
cp config_personal.example.py config_personal.py - Edit
config_personal.pywith your values
Only include variables you want to override. Everything else falls back to config.py defaults.
Key settings to customize:
| Setting | Purpose |
|---|---|
CITY, LAT, LON |
Your location for weather |
NWS_OFFICE, NWS_GRID_X, NWS_GRID_Y |
NWS forecast grid (run python setup/find_grid.py) |
CALLSIGN, GRID_SQUARE |
Ham radio (or disable the solar section) |
SECTIONS |
Toggle/reorder data sections — dict order = tab order |
HTML_REPORT_ENABLED |
Write local HTML file to output/ (default: True) |
EMAIL_ENABLED, EMAIL_TO |
Send daily email via Gmail (default: False) |
GMAIL_LABELS |
Map display names to your Gmail label names |
REMINDER_LISTS |
Which Apple Reminders lists to show (macOS only) |
RADAR_STATION, GOES_SECTOR |
Weather map sources |
- Python 3.10+ (3.11+ recommended)
- Google account (Gmail/Calendar API access)
- TMDB account + API credentials (for TV watchlist/posters)
- Optional: Ollama for free local AI news summaries
macOS:
brew install pythonWindows:
Download from python.org. During install, check "Add Python to PATH".
macOS / Linux:
cd morning-briefing
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtVerify: which python3 should point to .venv/bin/python3.
Windows (PowerShell):
cd morning-briefing
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txtVerify: where python should point to .venv\Scripts\python.exe.
Run the grid finder to get your NWS forecast office and grid coordinates:
python setup/find_grid.pyThen set NWS_OFFICE, NWS_GRID_X, NWS_GRID_Y in your config_personal.py.
- Go to Google Cloud Console.
- Create or select a project.
- Enable these APIs:
- Gmail API
- Google Calendar API
- APIs & Services -> Credentials -> Create Credentials -> OAuth client ID
- Application type: Desktop app
- Download the JSON file
- Save it as
credentials/google_credentials.json
python setup/auth.pyThis opens a browser for Google sign-in and saves credentials/google_token.json with these scopes:
gmail.readonly— read emails by labelgmail.send— send the daily briefing emailcalendar.readonly— read calendar events
Set environment variables (or add to your config_personal.py):
macOS / Linux:
export TMDB_API_KEY="your_tmdb_api_v3_key"
export TMDB_READ_ACCESS_TOKEN="your_tmdb_v4_read_token"
export TMDB_ACCOUNT_ID="your_tmdb_account_id"
export TMDB_USE_WATCHLIST="true"Windows (PowerShell):
$env:TMDB_API_KEY = "your_tmdb_api_v3_key"
$env:TMDB_READ_ACCESS_TOKEN = "your_tmdb_v4_read_token"
$env:TMDB_ACCOUNT_ID = "your_tmdb_account_id"
$env:TMDB_USE_WATCHLIST = "true"Get your API keys at themoviedb.org/settings/api.
Ollama runs locally and is free. It powers headline rewrites and email AI insights.
macOS:
brew install ollama
brew services start ollama
ollama pull phi3:mini
ollama pull llama3.1:8bWindows:
Download the installer from ollama.com/download. Then in a terminal:
ollama pull phi3:mini
ollama pull llama3.1:8bOllama runs as a background service automatically on Windows after install.
Environment variables (or set via config_personal.py):
NEWS_AI_ENABLED=true
NEWS_AI_PROVIDER=ollama
NEWS_AI_PROFILE=fast # fast | quality
NEWS_OLLAMA_MODEL_FAST=phi3:mini
NEWS_OLLAMA_MODEL_QUALITY=llama3.1:8b
NEWS_OLLAMA_URL=http://127.0.0.1:11434
By default, the briefing writes a local HTML report to output/today.html. You can also enable email delivery, or use email-only mode and skip the local file entirely.
In your config_personal.py:
HTML_REPORT_ENABLED = True # write output/today.html (default: True)
EMAIL_ENABLED = True # send email via Gmail (default: False)
EMAIL_TO = "you@example.com"Examples:
- Both enabled — get a local file and a daily email.
- Email only — set
HTML_REPORT_ENABLED = FalseandEMAIL_ENABLED = True. No file is written tooutput/. - Local only — keep defaults (
HTML_REPORT_ENABLED = True,EMAIL_ENABLED = False).
You can also override these per-run with CLI flags (see section 4).
macOS / Linux:
source .venv/bin/activate
python main.py
open output/today.htmlWindows:
.venv\Scripts\Activate.ps1
python main.py
start output\today.htmlGenerate a new briefing:
python main.pyCommon flags:
| Flag | Effect |
|---|---|
| Output modes | |
--html |
Force local HTML report (overrides config) |
--no-html |
Skip local HTML report |
--send-email |
Force email delivery (overrides config) |
--no-email |
Skip email delivery |
| Data sections | |
--no-gmail |
Skip Gmail fetch |
--no-cal |
Skip Calendar fetch |
--no-news |
Skip RSS news |
--no-weather |
Skip weather |
--no-solar |
Skip HF propagation |
--no-tv |
Skip TV watchlist |
--no-reminders |
Skip Apple Reminders |
Switch AI news profile and regenerate:
python setup/set_news_profile.py fast --apply-now --open
python setup/set_news_profile.py quality --apply-now --openShell aliases (macOS/Linux, add to ~/.zshrc or ~/.bashrc):
alias morning="open ~/morning-briefing/output/today.html"
alias briefing-fast="cd ~/morning-briefing && source .venv/bin/activate && python setup/set_news_profile.py fast --apply-now --open"
alias briefing-quality="cd ~/morning-briefing && source .venv/bin/activate && python setup/set_news_profile.py quality --apply-now --open"A launchd plist template is included. It runs the briefing every morning at a configured time.
# Copy and customize the example plist with your paths, API keys, and email
cp setup/com.ward.morning-briefing.plist.example setup/com.ward.morning-briefing.plist
# Edit setup/com.ward.morning-briefing.plist — update ProgramArguments paths,
# EnvironmentVariables (TMDB keys, NEWS_AI settings, EMAIL_TO), etc.
# Install the LaunchAgent
cp setup/com.ward.morning-briefing.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.ward.morning-briefing.plist
# Verify
launchctl list | grep morning-briefingTo update after editing the plist:
launchctl unload ~/Library/LaunchAgents/com.ward.morning-briefing.plist
launchctl load ~/Library/LaunchAgents/com.ward.morning-briefing.plistOptional — wake the machine before the scheduled run:
sudo pmset repeat wakeorpoweron MTWRF 06:50:00- Open Task Scheduler (
taskschd.msc) - Click Create Basic Task
- Set trigger: Daily, at your desired time (e.g. 6:55 AM)
- Set action: Start a program
- Program:
C:\path\to\morning-briefing\.venv\Scripts\python.exe - Arguments:
main.py - Start in:
C:\path\to\morning-briefing
- Program:
- In the task properties, go to the General tab and check "Run whether user is logged on or not"
- Under Settings, check "Wake the computer to run this task" if desired
Set environment variables system-wide via System Properties > Environment Variables, or create a wrapper batch file:
@echo off
cd /d C:\path\to\morning-briefing
set NEWS_AI_ENABLED=true
set NEWS_AI_PROVIDER=ollama
set NEWS_AI_PROFILE=fast
set TMDB_API_KEY=your_key
set TMDB_READ_ACCESS_TOKEN=your_token
set TMDB_ACCOUNT_ID=your_id
set TMDB_USE_WATCHLIST=true
.venv\Scripts\python.exe main.pyPoint the Task Scheduler action at this .bat file instead.
crontab -eAdd a line like:
55 6 * * * cd /path/to/morning-briefing && .venv/bin/python main.py >> logs/cron.log 2>&1
Set environment variables in the crontab or in a wrapper script.
| Mode | Model | Speed | Quality | Headlines Rewritten |
|---|---|---|---|---|
| fast | phi3:mini | Fast | Good | Up to 3 per category |
| quality | llama3.1:8b | Slower | Better | Up to 8 per category |
Switch the default profile:
python setup/set_news_profile.py fast
python setup/set_news_profile.py qualitySwitch and immediately regenerate:
python setup/set_news_profile.py fast --apply-now --open
python setup/set_news_profile.py quality --apply-now --openpip install -r requirements.txtpython setup/auth.pymacOS:
brew services list | grep ollama
ollama listWindows / Linux:
ollama listpython setup/doctor.py
python setup/doctor.py --verboseThe doctor script checks: venv, Google token scopes, LaunchAgent plist, Ollama service, and model availability. It is macOS-specific due to launchd and brew checks.
python sources/weather.py
python sources/solar.py
python sources/news.py
python sources/tv.py
python sources/gmail_source.py
python sources/calendar_source.pyMake sure you're using the virtual environment:
macOS / Linux:
source .venv/bin/activate
python main.pyWindows:
.venv\Scripts\Activate.ps1
python main.pyRe-run the auth script to refresh the token with all required scopes:
python setup/auth.py- Confirm
NEWS_AI_ENABLED=true - Confirm
NEWS_AI_PROVIDER=ollama - Verify Ollama is running:
ollama list - Verify models are pulled:
ollama pull phi3:mini - Confirm
NEWS_OLLAMA_URLis reachable (http://127.0.0.1:11434)
- Confirm
TMDB_API_KEYandTMDB_READ_ACCESS_TOKENare set - Confirm
TMDB_USE_WATCHLIST=true - Confirm your TMDB watchlist is not empty
- Confirm
REMINDER_LISTSinconfig_personal.pymatches your actual list names - Grant Terminal / IDE access to Reminders in System Settings > Privacy & Security > Reminders
Apple Reminders is macOS-only. Set "reminders": {"enabled": False, ...} in your SECTIONS config.
cat logs/run.log
cat logs/error.log
launchctl list | grep morning-briefingReload if needed:
launchctl unload ~/Library/LaunchAgents/com.ward.morning-briefing.plist
launchctl load ~/Library/LaunchAgents/com.ward.morning-briefing.plistmorning-briefing/
├── main.py # Entry point — orchestrates fetch + render
├── config.py # Shared defaults (generic, committed)
├── config_personal.py # YOUR overrides (gitignored)
├── config_personal.example.py # Template for config_personal.py
├── renderer.py # Jinja2 rendering for browser + email HTML
├── requirements.txt
│
├── templates/
│ ├── briefing.html # Browser report (tabbed UI)
│ └── briefing_email.html # Email-friendly flat layout
│
├── sources/
│ ├── weather.py # NWS forecast + maps
│ ├── solar.py # HF propagation / solar indices
│ ├── news.py # RSS feeds + Ollama AI rewrites
│ ├── gmail_source.py # Gmail by label
│ ├── gmail_ai.py # AI insights for Gmail categories
│ ├── calendar_source.py # Google Calendar events
│ ├── reminders.py # Apple Reminders (macOS only)
│ ├── tv.py # TMDB watchlist + posters
│ └── email_sender.py # Gmail API email delivery
│
├── setup/
│ ├── auth.py # Google OAuth flow
│ ├── doctor.py # Health check (macOS)
│ ├── find_grid.py # NWS grid coordinate finder
│ ├── set_news_profile.py # Switch AI profile + regenerate
│ ├── import_tmdb_watchlist.py # Import shows to TMDB watchlist
│ └── com.ward.morning-briefing.plist.example # launchd template
│
├── credentials/ # OAuth tokens (gitignored)
├── output/ # Generated HTML reports
└── logs/ # Run + error logs
credentials/— OAuth tokens, never committed.config_personal.py— your personal settings, gitignored.setup/com.ward.morning-briefing.plist— contains API keys, gitignored.- Google scopes:
gmail.readonly,gmail.send,calendar.readonly. - API keys (TMDB, Ollama) should be set via environment variables or
config_personal.py, never inconfig.py. - To revoke access: remove app permissions in Google Account Security.
- Run a full generation and confirm all sections populate.
- Verify the scheduler is loaded (macOS:
launchctl list | grep morning-briefing). - Verify Ollama is running and models are present (
ollama list). - Check Google token still works (
python setup/auth.pyif needed). - Refresh TMDB watchlist if TV data looks stale.