For context on how my brain works, see Working with an AuDHD Brain.
Here is the smallest version of my entire productivity system:
task add "Write the intro" +next
task 1 start
That is it. The task is recorded, the clock is running, and I can see both in under a second without touching a mouse. Everything else in this post builds on top of those two lines. The stack is taskwarrior for task management, timewarrior for time tracking, and vit as a vim-style interface on top. Keyboard-first, terminal-native, plain text on disk.
No Electron apps. No cloud sync shipping my data off to a vendor I can’t inspect. The files live in ~/.task and I own them.
Why This Stack?
I fought with task management for years. Todoist, Things, Notion, a pile of Obsidian plugins. I tried all of them and abandoned all of them, always for the same reason: too much friction to get a thought out of my head and into the system before it evaporated.
For my brain that friction is the whole problem. If capturing a task costs me three clicks and a loading spinner, the task never gets captured. Taskwarrior removed the friction:
- Speed: add a task in under a second
- Keyboard-only: no mouse needed
- Scriptable: it plugs into everything
- Offline: works always, everywhere, no server required
- Plain text: the data is mine
The trade-off is real and I’ll say it up front: there’s a learning curve, and the first week feels clumsy. I think it pays for itself within days. The rest of this post is the layered version of that opening snippet, from task add to a self-hosted sync server.
Installation
macOS
brew install task timewarrior vit
Linux (Debian/Ubuntu)
sudo apt install taskwarrior timewarrior
pip install vit # or via package manager
Arch Linux
sudo pacman -S task timew vit
After installation, run task once to create the config:
task version
# Creates ~/.taskrc and ~/.task/
Taskwarrior Basics
Adding Tasks
# Simple task
task add "Review pull request"
# With tags
task add "Fix login bug" +urgent +backend
# With due date
task add "Report delivery" due:friday
# With project
task add "Write API endpoint" project:acme.api
# All together
task add "Deploy to production" project:acme.api +urgent due:tomorrow priority:H
Viewing Tasks
task list # All pending tasks
task # Same as task list (or task next)
task all # Including completed and deleted
task completed # Only finished tasks
task +urgent # Filtered by tag
task project:acme # Filtered by project
Managing Tasks
task 1 done # Mark as done
task 1 delete # Delete
task 1 modify +tag # Add tag
task 1 modify due:monday # Change due date
task 1 edit # Open in $EDITOR
task 1 info # All details
Starting/Stopping Tasks
task 1 start # Mark as "active"
task 1 stop # No longer active
task +ACTIVE # View active tasks
Projects
Projects in taskwarrior use hierarchical notation with dots:
# Project structure
task add "Something" project:client.project.subproject
# Examples
task add "Design API" project:acme.api
task add "Write tests" project:acme.api.backend
task add "Build UI" project:acme.api.frontend
Viewing Projects
# All tasks in a project (including subprojects)
task project:acme list
# Only specific project
task project:acme.api list
# Project summary
task projects
Context (Project Focus)
Contexts automatically filter your view:
# Define context
task context define work "project:acme or project:byteherder"
task context define acme "project:acme"
# Activate context
task context acme
# Now 'task list' only shows acme tasks
# View context
task context show
# Disable context
task context none
This is exactly what my p function does. See my time-system scripts for the full implementation.
Dependencies
Tasks can depend on each other:
# Task 2 depends on task 1
task 2 modify depends:1
# Multiple dependencies
task 3 modify depends:1,2
# When creating
task add "Deploy" depends:1,2
Blocked Tasks
# View what's blocked
task blocked
# View what's unblocked (ready to do)
task unblocked
# View the dependency chain
task 1 info
# Shows: "blocking: 2, 3" and "blocked by: (none)"
Example Workflow
task add "Design database schema" # Task 1
task add "Build API endpoints" depends:1 # Task 2
task add "Frontend integration" depends:2 # Task 3
task add "Write tests" depends:1 # Task 4
task add "Documentation" depends:2,3,4 # Task 5
task list
# ID Dep Description
# 1 Design database schema
# 2 1 Build API endpoints [blocked]
# 3 2 Frontend integration [blocked]
# 4 1 Write tests [blocked]
# 5 2-4 Documentation [blocked]
task 1 done
# Now tasks 2 and 4 are unblocked
task list
# ID Dep Description
# 2 Build API endpoints
# 3 2 Frontend integration [blocked]
# 4 Write tests
# 5 2-4 Documentation [blocked]
Tags
Tags are flexible labels:
# Adding
task add "Something" +urgent +backend +review
# Filtering
task +urgent list
task +backend +review list # AND
task +backend or +frontend # OR
task -urgent # NOT (exclude)
# Remove tag
task 1 modify -urgent
Useful Tag Conventions
+next # Next to do
+urgent # High priority
+waiting # Waiting on someone else
+review # Needs review
+blocked # Manually blocked (besides depends)
+quick # < 15 minutes
Priorities
# Three levels: H(igh), M(edium), L(ow)
task add "Critical fix" priority:H
task add "Nice to have" priority:L
# Modify
task 1 modify priority:M
# Filter
task priority:H list
Due Dates
Taskwarrior understands natural language:
# Specific
task add "Meeting" due:2026-01-30
task add "Deadline" due:2026-01-30T14:00
# Relative
task add "Soon" due:tomorrow
task add "This week" due:friday
task add "In a week" due:1week
task add "End of month" due:eom
task add "In 3 days" due:3d
# Recurring
task add "Weekly review" due:monday recur:weekly
task add "Standup" due:tomorrow recur:daily until:2026-12-31
Viewing by Due Date
task due:today list # Today
task due.before:friday # Before friday
task overdue # Past due
task +OVERDUE # Same
Reports
Taskwarrior has built-in reports:
task list # Default list
task next # Next tasks (smartly sorted)
task ready # Ready to do (unblocked, not waiting)
task blocked # Blocked by dependencies
task waiting # Waiting on something
task recurring # Recurring tasks
task completed # Finished
task all # Everything
Custom Reports
In ~/.taskrc:
# Minimal list
report.minimal.columns=id,description
report.minimal.labels=ID,Description
report.minimal.filter=status:pending
# Then: task minimal
Timewarrior Integration
Timewarrior automatically tracks time when you start/stop tasks.
Manual Tracking
timew start "coding" +projectX # Start tracking
timew stop # Stop
timew # Current status
timew summary # Today
timew summary :week # This week
timew summary :month # This month
Taskwarrior Hook
Create automatic integration:
# ~/.task/hooks/on-modify.timewarrior
#!/usr/bin/env python3
import json
import subprocess
import sys
old = json.loads(sys.stdin.readline())
new = json.loads(sys.stdin.readline())
if 'start' in new and 'start' not in old:
project = new.get('project', 'untagged')
tags = new.get('tags', [])
subprocess.run(['timew', 'start', project] + tags)
if 'start' in old and 'start' not in new:
subprocess.run(['timew', 'stop'])
print(json.dumps(new))
chmod +x ~/.task/hooks/on-modify.timewarrior
Now task 1 start automatically starts time tracking!
Timewarrior Reports
timew summary :day # Today
timew summary :week # This week
timew summary 2026-01-20 - today # Range
timew tags # All tags
timew summary :week project:acme # Filtered
Correcting Time
# Adjust current tracking
timew modify start -30min # Started 30 min earlier
# Adjust historical entry
timew summary :ids # Find ID
timew modify @1 start 09:00 # Adjust start
timew delete @1 # Delete entry
Vit: Vim Interface for Taskwarrior
Vit is a TUI with vim keybindings. Perfect if you have vim muscle memory.
Starting
vit # Default view
vit project:acme # Filtered view
Navigation
| Key | Action |
|---|---|
j / k | Up / down |
gg | Go to beginning |
G | Go to end |
Ctrl+d / Ctrl+u | Half page down/up |
/ | Search |
n / N | Next/previous match |
Task Actions
| Key | Action |
|---|---|
a | Add task |
e | Edit task (in $EDITOR) |
m | Modify (inline) |
d | Done |
D | Delete |
s | Start/stop |
u | Undo |
Enter | Task info |
Modify Examples
Press m and type:
project:acme # Set project
+urgent # Add tag
-waiting # Remove tag
due:friday # Set due date
depends:1 # Add dependency
priority:H # Set priority
Switching Views
| Key | View |
|---|---|
1 | next |
2 | list |
3 | completed |
4 | blocked |
5-9 | Custom reports |
Command Mode
| Key | Action |
|---|---|
: | Type task command directly |
:q | Quit |
:!command | Shell command |
Example: :project:acme +urgent filters live.
Vit Config
~/.vit/config.ini:
[vit]
default_keybindings = vi
[report]
default_report = next
[color]
color = on
My Daily Workflow
I’ve packaged all my aliases, functions, and scripts into a reusable collection: time-system
# Install
git clone https://github.com/kapott/time-system.git
cd time-system && ./install.sh
source ~/.zshrc
Project-Based Workflow
The scripts expect a two-level directory structure for projects:
~/Documents/Git/ # TASKFUNC_PROJECTS_DIR
├── clientA/
│ ├── project1/
│ └── project2/
├── clientB/
│ └── projectX/
Daily Flow
# Morning
p # Select client/project with fzf
# -> cd's to directory
# -> sets taskwarrior context
# -> shows pending tasks
vit # View tasks in TUI
# Working
tstart # Pick task with fzf, starts timer
# ... work ...
tstop # Stop task + timer
# Throughout the day
tadd "New task" # Adds to current project (or inbox if no scope)
tpl # List tasks for current project
tnext # What's next?
# End of day
dayend # Summary: completed, time tracked, still active
Key Commands
| Command | Description |
|---|---|
p | Select project, cd to dir, set scope |
tstart [id] | Start task + timer (fzf if no id) |
tstop | Stop current task + timer |
tadd <desc> | Add task to current project |
tdone [id] | Complete task (fzf if no id) |
tpl | List tasks for current project |
tctxn | Clear project scope |
weekstart | Weekly review |
dayreview | Daily review |
dayend | End of day summary |
twcurrent | Current tracking status |
See the full command reference for all aliases and functions.
Backup
Tasks live in ~/.task/:
# Simple backup
cp -r ~/.task ~/.task-backup-$(date +%Y%m%d)
# Or with git
cd ~/.task && git init && git add -A && git commit -m "Backup"
Advanced Features
UDAs (User Defined Attributes)
Custom fields:
# In ~/.taskrc
uda.estimate.type=duration
uda.estimate.label=Est
# Usage
task add "Big job" estimate:2h
task estimate.over:1h list
Urgency Tuning
Taskwarrior calculates urgency automatically. Tune it:
# ~/.taskrc
urgency.user.tag.urgent.coefficient=5.0
urgency.user.tag.quick.coefficient=2.0
urgency.blocking.coefficient=8.0
urgency.blocked.coefficient=-5.0
Taskd: Sync Server
If you use taskwarrior on multiple machines (laptop, desktop, server), you want synchronization. That’s what taskd is for: the official taskwarrior sync server. Self-hosted, so the data never leaves infrastructure I control.
Why Taskd?
- Multi-device sync: Same tasks on all your machines
- Conflict resolution: Taskwarrior handles merge conflicts
- Offline-first: Works locally, syncs when possible
- Self-hosted: Your data stays yours
Alternatives
Before setting up taskd, consider the alternatives:
| Option | Complexity | Advantage |
|---|---|---|
| Syncthing | Low | Simple, sync entire ~/.task folder |
| Git | Low | Version control, but no real sync |
| Taskd | Medium | Official, proper merge handling |
| Taskchampion Sync Server | Medium | Newer, for Taskwarrior 3.x |
For most people Syncthing is the easiest option. Taskd is for when you want proper sync with conflict resolution.
Installing Taskd Server
Via Package Manager
# Debian/Ubuntu
sudo apt install taskd
# Arch
sudo pacman -S taskd
# macOS (as server, unusual)
brew install taskd
Docker (recommended)
# docker-compose.yml
version: '3'
services:
taskd:
image: andir/taskd
ports:
- "53589:53589"
volumes:
- ./taskd-data:/var/taskd
environment:
- CERT_CN=taskd.example.com
- CERT_ORGANIZATION=MyOrg
docker-compose up -d
Server Configuration
If you install taskd manually:
# Create data directory
export TASKDDATA=/var/taskd
sudo mkdir -p $TASKDDATA
sudo chown $USER:$USER $TASKDDATA
# Initialize
taskd init
# PKI setup (certificates)
cd /usr/share/taskd/pki # or where your pki scripts are
./generate
# Copy certificates
cp client.cert.pem $TASKDDATA
cp client.key.pem $TASKDDATA
cp server.cert.pem $TASKDDATA
cp server.key.pem $TASKDDATA
cp server.crl.pem $TASKDDATA
cp ca.cert.pem $TASKDDATA
# Configure server
taskd config --force client.cert $TASKDDATA/client.cert.pem
taskd config --force client.key $TASKDDATA/client.key.pem
taskd config --force server.cert $TASKDDATA/server.cert.pem
taskd config --force server.key $TASKDDATA/server.key.pem
taskd config --force server.crl $TASKDDATA/server.crl.pem
taskd config --force ca.cert $TASKDDATA/ca.cert.pem
taskd config --force server localhost:53589
taskd config --force log /var/log/taskd.log
taskd config --force pid.file /var/run/taskd.pid
Organizations, Groups and Users
Taskd has a hierarchical structure:
# Create organizations
taskd add org Work
taskd add org Home
taskd add org Freelance
# Groups within organizations (optional)
taskd add group Work backend-team
taskd add group Work devops-team
# Users per organization
taskd add user Work tom
taskd add user Work jan
taskd add user Home tom
# Output: New user key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# SAVE THIS KEY!
Important Nuance: No Shared Tasks
Here’s a common misunderstanding: taskd is not meant for sharing tasks between users.
Each user has their own, separate task database. Tom’s tasks are invisible to Jan, even if they’re in the same organization.
What taskd IS for:
- Tom syncs his own tasks between laptop, desktop and phone
- Jan does the same with his own devices
- They share the server infrastructure, not the data
What taskd is NOT for:
- Shared projects
- Team task management
- Assigning tasks to colleagues
Workarounds for team tasks:
| Option | How | Downside |
|---|---|---|
| Shared account | Everyone uses same credentials | No audit trail, who did what? |
| Export/import | task export | ssh colleague task import | Manual, no real sync |
| Different tool | Vikunja, Kanboard, Plane | Not taskwarrior |
My advice: Use taskwarrior for personal tasks, and a dedicated tool for teamwork. Don’t try to make taskd do something it wasn’t designed for.
Generating Client Certificates
cd /usr/share/taskd/pki
./generate.client username
# This creates:
# - username.cert.pem
# - username.key.pem
Copy to your client machine:
ca.cert.pemusername.cert.pemusername.key.pem
Configuring Taskwarrior Client
On every machine where you want to sync:
# Place certificates in ~/.task
cp ca.cert.pem ~/.task/
cp username.cert.pem ~/.task/
cp username.key.pem ~/.task/
# Configure in ~/.taskrc
task config taskd.certificate ~/.task/username.cert.pem
task config taskd.key ~/.task/username.key.pem
task config taskd.ca ~/.task/ca.cert.pem
task config taskd.server taskd.example.com:53589
task config taskd.credentials 'MyOrg/username/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
Starting Server
# Foreground (for testing)
taskdctl start
# As systemd service
sudo systemctl enable taskd
sudo systemctl start taskd
Synchronizing
# First sync (push everything to server)
task sync init
# Normal sync
task sync
Automatic Sync
You can automate sync with hooks or cron:
# ~/.task/hooks/on-exit-sync (automatically after every task action)
#!/bin/bash
task sync
chmod +x ~/.task/hooks/on-exit-sync
Or more conservatively with cron:
# Sync every 30 minutes
*/30 * * * * /usr/bin/task sync >> /tmp/tasksync.log 2>&1
Checking Sync Status
task sync # Sync and show status
task diagnostics | grep -A5 Sync # View sync config
Troubleshooting Taskd
“Certificate fails validation”
# Check if certificates are correct
openssl verify -CAfile ~/.task/ca.cert.pem ~/.task/username.cert.pem
“Could not connect to server”
# Check if server is running
nc -zv taskd.example.com 53589
# Check firewall
sudo ufw allow 53589/tcp
“Sync conflict”
Taskwarrior handles most conflicts automatically. If there are problems:
task sync # View conflict messages
task list # Check if data is correct
Syncthing as Alternative
If taskd is too complex, Syncthing works fine:
# Install Syncthing on both machines
# Sync the ~/.task folder
# Done
# Downside: no real merge, last write wins
# Advantage: works for everything, not just taskwarrior
For my setup I use Syncthing for ~/.task. Simple and works.
Troubleshooting
“No matches”
# Check your context
task context show
# Clear context
task context none
Task disappeared?
task all # View everything
task status:completed # Check completed
task status:deleted # Check deleted
Undo
task undo # Undo last action
Where to Stop
Look at how far that two-line snippet from the top has stretched. task add and task 1 start grew into projects, dependencies, time tracking, a vim TUI, and a self-hosted sync server. The point of laying it out this way is that every layer is optional. You can stop wherever you have enough.
If all you ever do is task add and task done, that is already a complete system and it will serve you for years. The CLI handles quick capture, vit gives you an overview when the list gets long, timewarrior answers “where did my day go,” and the scripts are there for the day you want to wire it to your own workflow. Add each piece when you feel the friction it solves, not before.
There’s a learning curve, I won’t pretend otherwise. The reason I keep coming back to this stack is that it bends to how I work instead of forcing me into someone else’s idea of a productivity app, and the data sits in plain text that I control. If you try it and it doesn’t click, fair enough. For me it’s the setup that stays.
task add "Learn taskwarrior" +next due:today
task 1 start
Good luck.
