today pulls in all the information you track or which is flowing into your life, and hands it to an AI that helps you work and play. Any AI provider can be used, allowing whatever mixture of purely-local and cloud-based data sharing makes you comfortable.
$ bin/today --no-sync --non-interactive now
[dotenvx@1.51.1] injecting env (30) from .env
[dotenvx@1.51.1] injecting env (0) from .env
🔍 Checking database health...
📊 Starting focused session: What should I do *right now*?
⚠️ Skipping sync (--no-sync flag provided)
📊 Gathering context...
⏳ Time Tracking...
⏳ Diary / Journal...
⏳ Issues & Tickets...
⏳ markdown-plans...
⏳ stages...
⏳ vault-changes...
⏳ weather-context...
⏳ Calendar Events...
⏳ Tasks...
⏳ Habits...
⏳ Email...
⏳ Projects...
⏳ Health Metrics...
✅ Context ready
🤖 Running in non-interactive mode... Based on your current context, here's what you should do right now:
Stop the time tracker - You've been tracking "Answering hosting mail" for 26 minutes, but it sounds like you're done with that task.
Wind down for the evening - It's 7:19 PM and your bedtime is 9:30 PM. Today is an Off Stage (personal time) day, and you've already put in almost 5 hours of tracked work.
Immediate priorities for the next 2 hours:
Evening routine - Your habits show 0/12 steps completed. This includes:
- Lock the doors
- Finish meal tracking
- Start the fasting timer
- Take your pills
- Journal
30 minutes of gentle exercise - This is on your daily plan and your Exercise habit (30 min) is still pending. A short walk or Apple Fitness+ session would check this off.
Read offline - Your plan calls for 45 minutes between events, and your Read Offline habit shows only 2:45 completed. Even 20-30 minutes before bed would help.
Note: Your daily plan mentioned a Prime Gentlemen Mixer at 10 PM, but that's past your 9:30 PM bedtime. You may have already decided to skip it, or adjusted plans.
One quick check: The healthcheck
newsletter-continuous-catchupwent DOWN today. You might want to glance at that, though it can wait until tomorrow (a Back Stage day) if you prefer to keep tonight personal.Want me to stop the time tracker or help with anything specific?
Sessions can be interactive conversations or one-off suggestions like the above. You can run bin/today to start a session with general advice, or bin/today "What do I need to do for my trip to St. Louis?" to work on something particular.
The hope is:
- It pulls in everything you should know about, so nothing slips through the cracks.
- From your instructions about your goals and what's important to you, it suggests what to do next — whether it's "work like hell" or "go to the beach".
- It stays flexible with changing circumstances, helping you get the most out of right now.
- The more information you pour into it, the more it makes connections and thoughtful suggestions. ("Your diary said you're not getting outside enough. And you're up three pounds, and want to lose weight to take the pressure off your bad hip. Bob emailed about pickleball Friday morning. You wrote back that you couldn't, but the meeting you had then was canceled. Should we tell him you'll be there?")
Clone https://github.com/jeffcovey/today/ onto a POSIX system with npm installed. Running bin/today should run npm install if you're missing any dependencies, and should run bin/today configure if you haven't set up your profile and plugins. The more information you provide through your profile and plugins, the more tailored advice the AI can provide.
Your information comes into the system through plugins. They are categorized into several types with matching binaries. Common data types are stored for each (email "From:", event "Location"), with metadata fields for source-specific types.
Plugin types:
- Context (
bin/context): Weather, location, daily plans, day themes, etc. - Diary (
bin/diary): Day One, Obsidian, Journey, etc. - Email (
bin/email): Gmail, iCloud Mail, Fastmail, etc. - Events (
bin/calendar): Google Calendar, Outlook, Airbnb, TripIt, etc. - Habits (
bin/habits): Streaks, Habitica, Loop Habit Tracker, etc. - Health (
bin/health): Apple Health, Fitbit, Oura, Garmin, etc. - Issues (
bin/issues): GitHub, Jira, Linear, Sentry, etc. - Projects (
bin/projects): GitHub Projects, Notion, Basecamp, etc. - Tasks (
bin/tasks): Todoist, Things, Reminders, Obsidian, etc. - Time Logs (
bin/track): Toggl, Clockify, Harvest, RescueTime, etc. - Contacts (
bin/contacts): Address books, birthdays, contact management. - Finance (
bin/finance): Budgets, transactions, account balances. - Utility: Inbox processing, file cleanup, linting, etc.
You can configure multiple sources for each plugin, for example, for a work Gmail account and a personal Gmail account. You can add instructions for each source to tell the AI something about it ("This is my birthdays calendar. Remind me of these events one week in advance, and then the day of."). Only some of the above-listed services already have plugins! Please share your own where you see a gap you want to fill. The Plugin README explains how to create plugins. Reach out at https://github.com/jeffcovey/today/discussions to share your work or ideas or to ask questions.
You can manage your plugins with bin/today configure (which just calls out to bin/plugins configure if you want to go straight there), or edit ./config.toml directly. You can see what will be passed to the AI with bin/today dry-run.
Define reusable focus sessions in config.toml for common workflows:
[focus.inbox]
description = "Process inbox and messages"
instructions = """
Help me process my inbox. Start with highest priority items.
Check Front conversations, then emails, then vault inbox.
"""
[focus.weekly-review]
description = "Weekly planning and review"
instructions = """
Let's do a weekly review. Look at:
1. What I accomplished this week
2. What's carrying over to next week
3. Any projects that need attention
"""Then run with:
bin/today --focus inbox # Run specific preset
bin/today --focus # Show menu to choose preset
bin/today --focus --non-interactive # Automated preset runMany file-based plugins look for a "vault" directory and follow some Obsidian conventions. The path to the vault can be configured, and defaults to vault/ under Today's directory. Plugins automatically create their required directories inside the vault when first used.
Important: The vault/ directory is gitignored because it contains personal data. Initialize it as a separate repository or sync it with your preferred solution. Plugins have permission to read and write from the vault. We strongly suggest you run git init within the vault and monitor its changes to make sure you're happy with any changes today makes.
Multi-device vault sync has two distinct layers:
Working-tree sync (the files themselves) keeps the vault's actual file contents identical across devices. When you edit a file on one device, the change appears on all other devices — uncommitted, unstaged, just the raw bytes. Use any file-level sync tool you prefer: Resilio Sync, Syncthing, iCloud, Dropbox, Unison, etc. Make sure .git, .git.nosync, and node_modules are excluded from the sync — unignored git directories will flood most sync daemons with inotify events.
Committed-state sync (git history) keeps each device's git repository in sync so that commits made on one device are available on all others. This is separate from working-tree sync — git can only push and pull commits, not uncommitted changes. Manage this with your own git workflow on the vault repository (manual git pull --rebase / git push, or your preferred git automation).
Important: committed-state sync does NOT create commits and does NOT sync uncommitted changes. Commits are always manual. Working-tree sync (above) is what carries uncommitted edits between devices.
Today includes a web server for browsing and interacting with your vault through a browser. The web interface provides full Obsidian compatibility and additional features for task and project management.
bin/today web # Start web server (default port 3000)
bin/today web --port 8080 # Start on custom portThen visit http://localhost:3000 to browse your vault.
For remote access, deploy Today to a server using bin/today configure and bin/deploy commands. See the Server Deployment section for details.
- Vault browsing: Navigate your markdown files with Obsidian compatibility
- Task management: Interactive task lists with clickable links and detail pages
- AI chat integration: Built-in AI assistant with access to your context and tools
- Live editing: Edit tasks and markdown files directly in the browser
- Image support: View embedded images and Obsidian-style image syntax
- Wiki links: Full support for
[[internal links]]and relative paths - Table of contents: Auto-generated TOC for long documents
- Responsive design: Works on desktop and mobile devices
The web interface supports Obsidian markdown features:
[[Wiki Links]]and![[Image Embeds]]- Frontmatter properties (YAML)
- Task syntax with priorities and dates
- Callouts and admonitions
- Line breaks and formatting
This makes it easy to use alongside Obsidian or as a standalone interface to your vault.
The vault/inbox/ directory is a drop zone for quick capture. The inbox-processing plugin automatically processes files based on their content:
| File Type | Detection | Action |
|---|---|---|
| Progress notes | First line is # Progress |
Appended to diary file, moved to .trash |
| Concern notes | First line is # Concerns or filename contains "concerns" |
Appended to diary file, moved to .trash |
| Gratitude notes | First line is # Gratitude or filename contains "gratitude" |
Appended to diary file, moved to .trash |
| Task files | Contains only checkbox lines (- [ ] or - [x]) |
Tasks appended to tasks/tasks.md, moved to .trash |
| Other notes | Default | Left in inbox for user review |
Processed files are kept in vault/inbox/.trash/ for 7 days (configurable) before automatic deletion.
The inbox works great with quick-capture apps. If you have a server with the inbox-api service running, you can upload directly via HTTPS. Otherwise, use file sync.
Drafts (iOS/Mac):
- With inbox-api: Copy
scripts/drafts-send-to-inbox.jsinto a Drafts Action to upload directly to your server - With file sync: Create actions that save to your synced vault folder:
# Progress note action
Title: Progress
Body: {{date}} {{time}}
{{draft}}
Save to: vault/inbox/progress-{{timestamp}}.md
# Quick task action
Body: - [ ] {{draft}}
Save to: vault/inbox/task-{{timestamp}}.md
TextExpander - Create snippets for note formats:
# Progress snippet (;prog)
# Progress
%B %e, %Y %H:%M
<cursor>
# Concerns snippet (;concern)
# Concerns
%B %e, %Y %H:%M
<cursor>
The date format December 7, 2025 14:30 is parsed to determine which plan file to append to.
The scheduler (src/scheduler.js) automates daily operations:
- Every 10 minutes: Plugin sync (external sources, task classification, plan updates)
- Every 6 hours: Database maintenance (WAL checkpoint)
- Weekly: Database vacuum
Option 1: Run Locally
# Run scheduler in foreground
node src/scheduler.js
# Or run in background with pm2
npm install -g pm2
pm2 start src/scheduler.js --name today-scheduler
pm2 saveOption 2: System cron (manual setup)
# Edit crontab
crontab -e
# Add entries like:
*/10 * * * * cd /path/to/today && bin/plugins sync
0 */2 * * * cd /path/to/today && bin/today updatebin/plugins sync syncs every enabled source on each run. Some plugins are far
more expensive than others — markdown-plans, for instance, shells out to the
claude CLI to generate plan summaries. To keep such a plugin on the shared
sync schedule without running it every tick, set a per-source floor on how often
it may sync:
[plugins.markdown-plans.default]
min_sync_interval_minutes = 120The source is then skipped on any run where its last successful sync is more
recent than that, regardless of how often the cron fires. --if-stale <minutes>
applies the same check across all sources ad hoc; when both are set, the
stricter of the two wins.
The scheduler modifies files automatically without asking. It will update daily plans, archive tasks, and sync data. To track these changes and recover if needed:
cd vault
git init
git add .
git commit -m "Initial vault"
# After running the scheduler, review changes:
git status
git diffThis lets you see exactly what the scheduler changed and revert if needed.
Today can be deployed to remote servers for scheduled automation and always-on operation. The deployment system supports multiple servers and providers.
- Always-on scheduling: Run the scheduler 24/7 without keeping your laptop open
- Inbox API: Receive files from mobile apps like Drafts directly via HTTPS
- Vault syncing: Keep your vault synchronized between local and remote
- Multiple environments: Deploy to production, staging, or backup servers
Run bin/today configure and select "Deployments" to add servers. The interactive UI handles all configuration including secure storage of server IPs.
bin/deploy --list # Show all deployments
bin/deploy production status # Check server status
bin/deploy production setup # Initial server setup (nginx, SSL, systemd)
bin/deploy production deploy # Deploy code and restart services
bin/deploy production logs # View recent logs
bin/deploy production ssh # Open SSH session
bin/deploy production maintenance # Run cleanup tasksDeployment copies your local configuration to the remote server, making the remote a mirror of your local setup. This means:
- Your
config.tomlplugins run on the server with the same settings - Scheduled jobs execute remotely instead of locally
- Vault changes sync between local and remote
| Provider | Description |
|---|---|
digitalocean |
DigitalOcean Droplets with automated setup |
hetzner |
Hetzner Cloud servers |
generic |
Any VPS with SSH access |
local |
The current machine, managed via docker-compose (e.g. running Today on a Mac) |
A local deployment configures the machine you're on instead of SSHing to a remote. It's designed for running the Today containers via docker-compose on a Mac (or a Linux dev machine) while still using the same config.toml → bin/deploy → scheduler model as the remote droplets.
Example config.toml entry for a Mac:
[deployments.local.macbook]
deploy_path = "/Users/you/today"
enabled = true
[deployments.local.macbook.services]
scheduler = true
[deployments.local.macbook.jobs.plugin-sync]
schedule = "*/10 * * * *"
command = "bin/plugins sync"Then on the Mac:
bin/deploy macbook setup # one-time: docker compose build
bin/deploy macbook # write scheduler-config.json and `docker compose up -d scheduler`
bin/deploy macbook services # show compose service status
bin/deploy macbook logs schedulerA few things to know about local deployments:
- No systemd, no apt. Service management maps to
docker compose up/stop/restart/ps; package install is the image's job. - Vault sync is the user's own git workflow. Committed-state sync (pushing/pulling vault commits between devices) is managed with your preferred git automation on the vault repo, not by a built-in timer. Working-tree sync (uncommitted edits) uses a file-sync tool — Unison is supported via
bin/deploy <name> setup --unison. - Resilio Sync is not supported on local deployments (
--resilioemits a warning and exits). - Git push credentials inside the container: the compose file bind-mounts
~/.sshread-only, so SSH URLs just work. If your vault remote is HTTPS, switch it:cd <vault> && git remote set-url origin git@github.com:<user>/<vault>.git. macOS Keychain credential helpers don't work inside a Linux container. - Running
bin/deployfrom inside the devcontainer works. The devcontainer uses the officialdocker-outside-of-dockerfeature to install Docker CLI + Compose plugin and wire up/var/run/docker.sockwith correct group permissions, sodocker composecommands from inside the devcontainer talk to the host's Docker daemon. The rootdocker-compose.ymluses${HOST_PROJECT_PATH}and${HOST_HOME}(set viacontainerEnvin.devcontainer/devcontainer.json) so bind mounts resolve against the real host filesystem rather than/workspaces/today. If Docker CLI isn't available in your shell,bin/deployfails loudly with a copy-pasteable set ofdocker composecommands to run from a host terminal instead. Rebuild the devcontainer ("Dev Containers: Rebuild Container") after pulling these changes so the feature gets installed. - Services available locally:
scheduler,vault-web,vault-watcher, andinbox-apiare all defined as compose services in the rootdocker-compose.ymland can be toggled per-deployment under[deployments.local.<name>.services]. None of them depend onollama— if you want a local LLM, bring it up explicitly withdocker compose up -d ollama.
Configure which services run and what scheduled jobs execute in bin/today configure under Deployments. Available services:
- scheduler: Runs scheduled jobs (plugin sync, maintenance, custom commands)
- vault-web: Serves your vault as a web site
- inbox-api: Receives files uploaded from mobile apps
Services and jobs are configured per-deployment, so different servers can run different workloads.
The easiest way to develop is using VS Code's devcontainer:
- Install Docker and VS Code
- Install the Dev Containers extension
- Open this folder in VS Code
- Click "Reopen in Container" when prompted
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report- Email Setup Guide - Configure email integration
MIT