A keyboard-first calendar and todo organizer with Svelte 5 (SvelteKit) frontend and Python Flask backend.
- Calendar: Month and day views; subscribe to read-only ICS URL, local folder of .ics files, or CalDAV server
- Todos: From local folder and CalDAV (when used); create, complete, edit, delete
- Keyboard-first: Switch views (1/C calendar, 2/T todos), day/month (D/M), go to today (G/Home), new (N), j/k or arrows to move focus, Enter to open, ? for shortcuts
- Theming: Light, dark, or system
cd backend
uv sync # or: pip install -e .
uv run flask --app app.main run --port 8000Standalone helper script to rewrite local vdirsyncer .ics files for iOS Reminders compatibility:
- rewrites VTODO scheduling date-times (
DUE,DTSTART,RECURRENCE-ID,RDATE,EXDATE) into your system local timezone (TZID=...) - rewrites recurring VEVENT UTC date-times (
DTSTART,DTEND,RECURRENCE-ID,RDATE,EXDATE) into your local timezone so weekly events stay at the same wall-clock time across DST - leaves VEVENT values that already use local
TZIDunchanged
cd backend
uv run python scripts/normalize_vdirsyncer_ics_to_utc.py ~/.vdirsyncerUseful options:
--dry-runto preview changes--backup-ext .bakto keep original copies before rewriting--tz America/New_Yorkto force a specific IANA timezone instead of system local
Shared app config is stored in ~/.config/calslop/settings.json (sources + notifications). To use a different directory (e.g. for development), set CALSLOP_DATA_DIR to that path.
Notification targets:
notify_send(default) for Linux desktop notifications from the backend servicewebhookwith URL and optional headersemailwith recipient in app settings and SMTP credentials from environment variables only
notify-send settings:
notify_send_timeout:5s,15s(default),60s, orpersistent(notify-send -t 0)
Notification message formatting:
time_formatuses Pythonstrftimedirectives (default:%b %d %H:%M %Z)body_templatesupports multiline text and placeholders:{time},{delta},{title},{kind}
Email environment variables (required for email target):
CALSLOP_EMAIL_SMTP_HOSTCALSLOP_EMAIL_SMTP_PORTCALSLOP_EMAIL_SMTP_USERNAMECALSLOP_EMAIL_SMTP_PASSWORDCALSLOP_EMAIL_USE_TLS(1or0, defaults to1)- optional
CALSLOP_EMAIL_FROM
cd frontend
npm install
npm run devOpen http://localhost:5173 . The dev server proxies /api to the backend.
Run the app as one process: Flask serves the built Svelte frontend and the API. Default port is 8765 (chosen to avoid common ports like 3000, 5000, 8000, 8080).
Prerequisites: Python 3.11+, Node.js 18+, and uv (or pip).
Clone the repo, then run the install script. It installs to ~/.local/share/calslop by default (override with a path argument). The systemd user service is installed and enabled by default; pass --no-service to skip it.
After install, a calslop management script is placed at ~/.local/bin/calslop (make sure that directory is in your PATH).
git clone https://github.com/storchdev/calslop.git
cd calslop
./install.sh [INSTALL_DIR] [--no-service]Common management commands:
calslop start # start the service
calslop stop # stop the service
calslop status # check service status
calslop logs -f # follow service logs
calslop update # pull latest code and rebuild
calslop help # show all commandsTo update an existing install (pull and rebuild), run from the install directory or pass the path:
./update.sh [INSTALL_DIR]A PKGBUILD is included for Arch Linux. It installs the app to /opt/calslop, places the management script at /usr/bin/calslop, and installs a systemd user unit to /usr/lib/systemd/user/.
git clone https://github.com/storchdev/calslop.git
cd calslop
makepkg -siAfter install, enable and start the service as your normal user:
systemctl --user enable --now calslopThe following steps are the manual equivalent of the scripts above.
-
Clone and install
git clone https://github.com/storchdev/calslop.git cd calslop -
Backend
cd backend uv sync # or: pip install -e . cd ..
-
Frontend (static build)
cd frontend npm ci npm run build cd ..
-
Run
From the backend directory so Flask can import
app.main. PointCALSLOP_STATIC_DIRat the built frontend (use an absolute path):cd backend CALSLOP_STATIC_DIR="$(realpath ../frontend/build)" uv run flask --app app.main run --port 8765 --host 0.0.0.0
Open http://localhost:8765 (or your machine’s IP if remote).
To use a different port, set the port in the command above and in the systemd unit below.
To run Calslop as a user service (runs as you, no sudo required):
-
Create an install directory in your home, clone the repo, and build. Example using
~/.local/share/calslop:mkdir -p ~/.local/share/calslop git clone https://github.com/storchdev/calslop.git ~/.local/share/calslop cd ~/.local/share/calslop cd backend && uv sync && cd .. cd frontend && npm ci && npm run build && cd ..
-
Create the user service unit. Uses
%hfor your home directory. If you used a different install path, replace~/.local/share/calslopin the unit. If you usedpip install -e .instead ofuv, setExecStartto your venv’spython.mkdir -p ~/.config/systemd/user tee ~/.config/systemd/user/calslop.service << 'EOF' [Unit] Description=Calslop calendar and todo app After=network.target [Service] Type=simple WorkingDirectory=%h/.local/share/calslop/backend Environment="PYTHONPATH=%h/.local/share/calslop/backend" Environment="CALSLOP_STATIC_DIR=%h/.local/share/calslop/frontend/build" Environment="PATH=%h/.local/share/calslop/backend/.venv/bin:/usr/local/bin:/usr/bin:/bin" ExecStart=%h/.local/share/calslop/backend/.venv/bin/python -m flask --app app.main run --port 8765 --host 0.0.0.0 Restart=on-failure RestartSec=5 [Install] WantedBy=default.target EOF
-
Enable and start:
systemctl --user daemon-reload systemctl --user enable calslop systemctl --user start calslop systemctl --user status calslopThe app will be available on port 8765. Sources and notification settings use the default
~/.config/calslop/settings.jsonsince the service runs as you.To have the service start at boot even when you are not logged in, enable lingering:
loginctl enable-linger $USER.
- Open Settings and click Add source.
- Choose type:
- Read-only ICS URL: paste a public .ics URL (e.g. from Google Calendar export).
- Local .ics folder: path to a directory; each
.icsfile is one event (or one calendar). Writable: new events/todos create files there. - CalDAV: server URL, username, password. Events and todos are read/written via CalDAV.
backend/– Flask API: sources CRUD, events/todos aggregation from ICS URL, local folder, and CalDAV; write support for local folder and CalDAV.frontend/– SvelteKit (Svelte 5) app: calendar (month/day), todo list, modals for create/edit, theme switcher, global keyboard shortcuts.
GET/POST/PUT/DELETE /api/sources– calendar/todo source configs (stored in~/.config/calslop/settings.json).GET /api/events?start=&end=– aggregated events.GET/POST/PATCH/DELETE /api/events[/:id]– event CRUD (writable sources only).GET /api/todos– aggregated todos.GET/POST/PATCH/DELETE /api/todos[/:id]– todo CRUD (writable sources only).