Logo banner

Introduction

TaskLite is a CLI task manager built with Haskell and SQLite.

$ tl add Buy milk +groceries
🆕 Added task "Buy milk" with id "01dd62xryn5fnzjgynkcy06spb"

$ tl add Go running
🆕 Added task "Go running" with id "01dd62yjtrtmaph23knff6mbsj"

$ tl
Id  Prio  Opened UTC  Body
pb   2    2019-06-12  Buy milk  +groceries
sj   0    2019-06-12  Go running

$ tl do pb
✅ Finished task "Buy milk" with id "01dd62xryn5fnzjgynkcy06spb"

The Code is available on GitHub.

For help please come visit us on one of the following sites:

Latest Versions

  • CLI version: 0.2.2.0
  • App version: 0.2.2.0

Full Changelog

Why Another CLI Task Manager?

Taskwarrior has been the gold standard for CLI task managers so far. However, I repeatedly lost tasks due to weird bugs and syncing issues. I also found several UI decisions inept and wanted something with a better workflow. But probably most importantly I couldn't see myself contributing to a C++ project. I had been working with C++ at university and it wasn't pleasant.

To sum it up: I finally wanted something which I could fully own and use until the end of days. That means:

  • Does not suddenly get bought by a bigger fish and get closed down or made unusable (looking at you Wunderlist)
  • Is written in a high-performance programming language, yet gives me lot's of guarantees about the code's stability and makes it easy for other developers to contribute
  • Free software
  • With a stable, future proof, powerful, and fast backend (currently SQLite, but support for plain files and Git is planned)

Why Not Org-mode?

I don't like the unstructured combination of outlining, notes and tasks. Furthermore I don't like interactive document editing UIs in the terminal. I prefer REPL style apps which adhere to UNIX conventions and let me compose them easily with other CLI tools.

This, however, is just a personal preference and otherwise Org-mode is certainly a good solution. Also check out Smos, which is another powerful tree-based editor with extra focus on Getting Things Done.

Installation

Check out the following pages for instructions on how to install the different versions of TaskLite.

CLI Tool

With Docker

The easiest way to get started is using the prebuilt Docker image:

docker run --rm adius/tasklite sh
tasklite help

When exiting the container all data will be discarded.

For repeated local usage run following command, but make sure to replace $TASKLITE_PATH with the path to your TaskLite installation as defined in your config.yaml file. Per default it's created in the XDG base directory: $HOME/.local/share/tasklite.

docker run \
  --rm \
  --volume "$TASKLITE_PATH":/root/.local/share/tasklite \
  adius/tasklite

To make it easier to use, create an alias like:

alias tl="docker run …"

Providing your own config.yaml file to the docker container is not yet supported.

From Source

To build TaskLite from source, you need to install Stack first.

git clone https://github.com/ad-si/TaskLite
cd TaskLite
stack install tasklite-core

To test the installation run:

tasklite help

Configuration

It's a good idea to customize your config file at ~/.config/tasklite/config.yaml afterwards.

Check out the example config file for infos about available settings.

Desktop App

Native GTK App

Attention: This is still early alpha

A few dependencies must be availabe to build the app. To install them on macOS run:

brew install \
  gtk+3 \
  libffi \
  gobject-introspection \
  gdk-pixbuf
git clone https://github.com/ad-si/TaskLite
cd TaskLite
stack install tasklite-app

It might be necessary to add the package "libffi" to the pkg-config search path before installation. For example with fish:

set -x PKG_CONFIG_PATH /usr/local/opt/libffi/lib/pkgconfig

Start it with:

tasklite-app

DB Browser for SQLite

Alternatively you can use the DB Browser for SQLite to view and modify your tasks directly in the SQLite database.

Web App

Datasette

The web app is currently based on Datasette and can only be used to view tasks, but not to create new ones.

In combination with the Docker container the web frontend for the SQLite database can be served in following way:

docker run \
  --rm \
  --entrypoint datasette \
  --publish 8001:8001 \
  --volume ~/TaskLite:/root/tasklite \
  --volume "$PWD"/datasette:/root/datasette \
  adius/tasklite \
  serve \
    --host 0.0.0.0 \
    --metadata /root/datasette/metadata.json \
    --reload \
    /root/tasklite/main.db

Attention: Make sure that the IP address matches with your host's.

There is a predefined query for a tl head like overview: http://0.0.0.0:8001/main/tasks_pretty

Generate custom view by appending the SQL query to http://0.0.0.0:8001/main?sql=. For example http://0.0.0.0:8001/main?sql=select%20\*%20from%20tasks.

Some example views:

Equivalent to tl head:

select substr(ulid,22) as ulid,priority,body,due_utc,
  replace(tags,',',', ') as tags,notes,user
from tasks_view
where closed_utc is null
order by priority desc
limit 50

Make sure to bookmark the views for easy access.

SQLite Web

Another way to host a simple web frontend is SQLite Web. While it's more bare bones than Datasette, it has the advantage that it also allows you to modify data.

docker run -it --rm \
  -p 8080:8080 \
  -v ~/TaskLite:/data \
  -e SQLITE_DATABASE=main.db \
  coleifer/sqlite-web

Concepts

States

Instead of allowing one to explicitly set a state, TaskLite infers the current state from several other fields.

There are 2 primary states:

  • Open - Waits to be done
  • Closed - Nothing left to be done

And 9 exclusive secondary states.

  • Asleep - Is hidden because it's not relevant yet
  • Awake - Has become relevant or will become soon
  • Ready - Is ready to be done (similar to Open)
  • Waiting - It's still unclear if the task needs to be done or really has been done. Regular checks are necessary until situation clears up.
  • Review - It's necessary to check if the task can finally be started or if it has finally been completed.
  • Done - Has been done
  • Obsolete - Has become obsolete or impossible to finish
  • Deletable - Not needed anymore and can be deleted (item in the trash)
  • Blocked - Some other task(s) must be done first. Blockers are stored in a separate table.
State\Fieldawake_utcready_utcwaiting_utcreview_utcclosed_utcstate
Open
Closed
Asleep> now> now
Awake< now> now
Ready< now< now
Waiting> now
Review< now
DoneDone
ObsoleteObsolete
DeletableDeletable
Blocked

Legend:

  • ❌ = Not allowed
  • ✅ = Required
  • ❔ = Maybe

Additional secondary states:

  • Repeating - If this task get completed, a duplicate will be created with the specified time offset. I.e. subsequent tasks get delayed (e.g. mowing the lawn)
  • Recurring - Task which needs to be done every day, week, etc. I.e. missed completions must be caught up immediately. (e.g. paying rent) The number of tasks which will be created in advance can be set via a config.
State\Fieldgroup_ulidrepetition_durationrecurrence_duration
Repeating
Recurring

Priority

The priority of a task is a decimal number between negative and positive inifity. It is automatically calculated based on the age, the due date, and several other values.

Priority

The idea is that you never have to manually set a priority, because it can be derived accurately from other values. This of course requires you to use the other availabe meta information adequately!

The exact calculation algorithm can be found in the taskViewQuery function in DbSetup.hs.

If you want to adjust the priority of selected tasks manually, you can use the tl boost [ulid] command to increase the priority by 1, or the tl hush [ulid] command to decrease it by 1.

Notes

A task can have several notes. Each note is identified by an ULID.

$ tl add Buy milk
🆕 Added task "Buy milk" with id "01dpgj8e9ws2dwgvsk5nmrvvg9"

$ tl note 'The vegan one from Super Buy' 01dpgj8e9ws2dwgvsk5nmrvvg9
🗒  Added a note to task "Buy milk" with id "01dpgj8e9ws2dwgvsk5nmrvvg9"

$ tl info 01dpgj8e9ws2dwgvsk5nmrvvg9
awake_utc: null
review_utc: null
state: null
repetition_duration: null
recurrence_duration: null
body: Buy milk
user: adrian
ulid: 01dpgj8e9ws2dwgvsk5nmrvvg9
modified_utc: 2019-10-06 12:59:46
group_ulid: null
closed_utc: null
priority_adjustment: null
metadata: null
waiting_utc: null
ready_utc: null
due_utc: null
priority: 1.0
tags:

notes:
  - note: The vegan one from Super Buy
    ulid: 01dpgjf35pq74gchsgtcd6fgsa

Usage

While the CLI interface is the main interface of TaskLite, it also supports several others (with varying amount of features).

Check out the following pages for more details.

CLI Tool

Add

To add a task run:

tl add Improve the TaskLite manual

It is also possible to immediately add tags when creating a task:

tl add Improve the TaskLite manual +tasklite +pc

Attention: The tags must be the last parameters

Help

For a full overview of all supported subcommands run:

tasklite help

Screenshot of CLI output of help command

Context / Views

There is no first class support for views (or "context" in GTD slang), because it can be easily implemented with aliases / custom CLI commands and the SQL query API.

For example I have following work command in my $PATH:

#! /usr/bin/env bash

tasklite query \
  "(tags is null or tags not like '%feram%') \
    and state is 'Open' \
    order by priority \
    desc limit 10"

Analyze and Filter Tasks

In order to further analyze and filter tasks TaskLite includes the ndjson command, which prints all tasks as newline delimited JSON objects.

This output can then easily be analyzed and filtered with standard UNIX tools. E.g. following example prints all tasks related to music:

tl ndjson | grep 'music' | jq

Import

Import a GitHub issue:

curl https://api.github.com/repos/$OWNER/$REPO/issues/$NUM | tl import

Export

Use one of following commands:

  • tl csv
  • tl ndjson
  • tl backup - Creates a backup at $TaskLiteDir/backups/YYYY-MM-DDtHHMM.db

Desktop App

The desktop app is still very early alpha and can currently only list the tasks. It's implemented with a declarative Haskell wrapper for GTK.

Screenshot of desktop app

Web App

The web app is currently provided by Datasette.

Screenshot of web app

REST API

The REST API is currently provided by Datasette.

All web views can be configured to deliver JSON by simply changing the file extension in the URL to .json.

For example:

curl --location http://0.0.0.0:8001/main/tasks_view.json

Haskell API for Programmatic Usage

While TaskLite is primarily a tool to manage your personal tasks, it can also be used as a dependency of other programms.

For example as a queue for processing tasks or a backend for a bookmarking service.

Differences to Taskwarrior

General

  • Simpler
    Taskwarrior has several redundant features and unnecessarily re-implements shell features like aliases.

  • More Robust & Stable
    Taskwarrior is plagued by numerous bugs due to its unnecessary complexity and non-optimal choice of programming languages. TaskLite's simple structure and Haskell's excellent correctness guarantees, however, yield a stable and robust piece of software.

  • Faster
    Haskell plus SQLite delivers excellent performance. Check out the section about performance for a simple benchmark.

  • More Powerful
    As all tasks are stored in an SQLite database, so you can use the most daring SQL queries to extract hidden insights. E.g. What is the average completion time for a task created on Monday tagged "sprint7" created by user "john-evil"?

    Furthermore, extensive tooling is available for SQLite to supercharge your TaskLite installation. For example Datasette as an instant REST API, or DB Browser for SQLite to view, manipulate, and plot your tasks in a GUI.

    Other 3rd party tools to edit SQLite databases are:

    • VisiData - Interactive CLI multitool for tabular data.
    • LiteCLI - CLI SQLite browser with auto-completion and syntax highlighting.

Performance

Coming soon!

Development

Main technologies:

Check out the makefile for all development tasks

Generate Screenshots

Use asciinema to generate the terminal recording:

asciinema rec \
  --title 'TaskLite Help Page' \
  --command 'tasklite help' \
  --overwrite \
  screenshots/recording.json
asciinema rec \
  --title 'TaskLite "withtag" Command' \
  --command 'tasklite withtag tasklite' \
  --overwrite \
  screenshots/withtag.json

Change the size of the terminal in the recording.json file to:

  "width": 80,
  "height": 86,

Then use svg-term to generate the SVG image:

svg-term \
  --no-cursor \
  --at 99999 \
  --window \
  --term iterm2 \
  --profile ~/dotfiles/terminal/adius.itermcolors \
  < screenshots/recording.json \
  > screenshots/recording.svg

Ghcid

Ghcid with color output for GHC 8.4 (probably obsolete in 8.6):

ghcid \
  --command="stack ghci --ghci-options=-fdiagnostics-color=always"

Hlint

hlint \
  --ignore="Redundant do" \
  --ignore="Use list literal" \
  --ignore="Use String" \
  --ignore="Redundant bracket" \
  --ignore="Use camelCase" \
  .

Webapp

Build Images

Build base image for webapp runtime image:

docker build \
  --file tasklite-core/dockerfiles/haskell-datasette \
  --tag haskell-datasette \
  dockerfiles

Build runtime image:

stack image container
docker tag adius/tasklite-tasklite:latest adius/tasklite:latest

Deployment

On Google Cloud:

docker tag adius/tasklite-tasklite:latest gcr.io/deploy-219812/tasklite:latest
docker push gcr.io/deploy-219812/tasklite:latest
kubectl create -f kubernetes/deployment.yaml
kubectl port-forward tasklite-deployment-77884ff4f6-66sjf 8001

Open 127.0.0.1:8001

docker build \
  --file dockerfiles/nginx-proxy \
  --tag gcr.io/deploy-219812/nginx-proxy:latest \
  dockerfiles; \
and docker push gcr.io/deploy-219812/nginx-proxy:latest; \
and kubectl replace --filename kubernetes/deployment.yaml --force; \
and sleep 8;
and kubectl port-forward \
  (kubectl get pods --selector app=tasklite --output name) 8080

Afterwards change the health check URL to /healthcheck for the load balancer at https://console.cloud.google.com/compute/healthChecks.

Changelog

2019-10-04 - 0.2.2.0

  • Support optional filter expression after "count" command (61e87b7)
  • Automatically create a config file if it doesn't exist (7407f87)

2019-07-14 - 0.2.1.0

  • Fix creation of Docker image, extend documentation accordingly (fa4cad3)

2019-06-13 - 0.2.0.0

  • Initial release

Related

If TaskLite isn't your cup of tea, maybe one of the other free task managers fits the bill:

  • Buku - Store and manage your bookmarks from the command line.
  • CommitTasks - Combination between git commit and todo list.
  • Eureka - CLI tool to input and store ideas without leaving the terminal.
  • Ff - A distributed note taker and task manager.
  • git-pending - Git plugin to list TODO, FIXME, TESTME, DOCME comments in a repository.
  • Org mode - Notes and todo lists powered by an Emacs based plain-text system.
  • Smos - Purely functional semantic tree-based editor (similar to Org mode).
  • Taskbook - Tasks, boards & notes for the command-line habitat.
  • Taskell - Command line Kanban board / task management.
  • Taskwarrior - Command line task management.
  • Toodles - Project management from the TODO's in your codebase.
  • Tracli - Command line app that tracks your time.
  • Ultralist - Open source task management system for the command line.
  • Yokadi - Command line oriented, SQLite powered todo list.