Introduction
TaskLite is a task manager built with Haskell and SQLite. It includes a CLI tool, a GraphQL server, and a webapp.
CLI
Webapp
Code
The code is available on GitHub.
For help and ideas please come visit us at our GitHub Discussions!
Latest Versions
- CLI:
version: 0.4.0.0
- Webapp:
"version": "0.4.0.0",
For all versions and their changes, check out the Changelog.
CLI
Check out the following pages for instructions on how to install and use the TaskLite CLI tool.
Installation
Prebuilt Binaries
MacOS
Install it via my Homebrew tap:
brew cask install ad-si/tap/tasklite
You can also get this (and previous) versions from the releases page.
Make sure to download the artifacts with curl
or wget
as macOS prevents the execution of files downloaded via a browser.
Furthermore you can get the latest CI builds on GitHub's Actions page. They are pretty stable if the build is successful due to the included tests.
Linux
You can get the latest versions on GitHub's Releases page.
Furthermore you can get the latest CI builds on GitHub's Actions page. They are pretty stable if the build is successful due to the included tests.
Prebuilt Docker Image
Another easy 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
CLI Tool
- Configuration
- Help
- Add
- Edit
- Context / Views
- Analyze and Filter Tasks
- Import
- Export
- Custom Views
- Metadata
- External Commands
Configuration
It's a good idea to customize your config file
at ~/.config/tasklite/config.yaml
before starting to use TaskLite.
Check out the example config file for infos about available settings.
Help
For a full overview of all supported subcommands run:
tasklite help
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
And even to set certain fields:
tl add Buy milk +groceries due:2020-09-01 created:2020-08-27
Attention: The tags and special commands must be the last parameters, but their order doesn't matter.
Edit
Existing tasks can be easily edited in their YAML representation:
tl edit 01hwcw6s1kzakd5pje218zmtpt
This will open your default editor,
as specified in the environment variables $VISUAL
or $EDITOR
.
You can then easily edit existing fields and add new fields.
This feature allows for a powerful batch editing workflow. Use an SQL query to select a subsection of your tasks and then edit all of them one by one:
sqlite3 \
~/TaskLite/main.db \
"SELECT ulid FROM tasks WHERE metadata LIKE '%sprint%'" \
| while read ulid; do tl edit $ulid; done
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, due_utc asc, ulid 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
TaskLite features a comprehensive and robust JSON importer.
For example to import a GitHub issue simply run:
curl https://api.github.com/repos/$OWNER/$REPO/issues/$NUM | tl import
Or to import a task from TaskWarrior:
task 123 export | tl import
In order to avoid data loss of fields which aren't directly
supported by TaskLite, the whole imported JSON object is also stored
in TaskLite's task metadata
field.
However, if the original JSON object already has a metadata
field,
its value is used instead.
warning
An import object's tags
field must be of type [string]
,
while a notes
field must be of type {ulid?: string, body: string}
.
warning
TaskLite does not properly support importing tasks which were created before 1970. While they can be imported, the creation date is set to 1970-01-01.
Export
Use one of following commands:
tl csv
tl ndjson
tl backup
- Creates a backup at$TaskLiteDir/backups/YYYY-MM-DDtHHMM.db
Custom Views
The export commands in combination with other common CLI tools like csvkit can also be used for custom views of tasks.
tl csv \
| csvgrep --column tags --match tasklite \
| head -n 6 \
| csvcut --columns ulid,body,tags \
| csvlook --max-column-width 30
yields
| ulid | body | tags |
| -------------------------- | ------------------------------ | -------- |
| 01chk64zwwjyybanvk7016hyyg | Add a burndown chart view | tasklite |
| 01chk6c08h70xra2awd8dngtr7 | Add multi user support | tasklite |
| 01chk6dxaxttwfyg019d3g3sze | Add a statistics view | tasklite |
| 01chk6f3sq1mrskgkt1046fz7q | Add a calendar view | tasklite |
| 01chk6vnm30ttvwc1qkasjaktm | Publish the TaskLite git re... | tasklite |
Metadata
The metadata field allows you to store additional data for each task which is not yet covered by TaskLite's core fields. It is stored as a JSON object and therefore supports all JSON datatypes.
This is similar to Taskwarrior's User Defined Attributes.
Metadata is especially useful for importing and migrating external tasks without losing any information.
If you, for example, want to import following task.json
file,
you will notice that it contains a field kanban-state
, which has
no equivalent in TaskLite:
{
"created_at": "2020-02-08T20:02:32Z",
"body": "Buy milk",
"kanban-state": "backlog"
}
However, you can still simply import it, as the additional field will be stored in the metadata object:
$ tl import < task.json
π₯ Imported task "Buy milk" with ulid "01e0k6a1p00002zgzc0845vayw"
Inspecting it:
$ tl info 01e0k6a1p00002zgzc0845vayw
awake_utc: null
review_utc: null
state: null
repetition_duration: null
recurrence_duration: null
body: Buy milk
user: adrian
ulid: 01e0k6a1p00002zgzc0845vayw
modified_utc: 1970-01-01T00:00:00Z
group_ulid: null
closed_utc: null
priority_adjustment: null
metadata:
body: Buy milk
kanban-state: backlog
created_at: 2020-02-08T20:02:32Z
waiting_utc: null
ready_utc: null
due_utc: null
priority: 0.0
tags:
notes:
To access the the metadata key programmatically you can do following:
$ tl ndjson \
| grep 01e0k6a1p00002zgzc0845vayw \
| jq -r '.metadata["kanban-state"]'
backlog
Or leverage SQL:
$ tl runsql "
SELECT json_extract(metadata, '\$.kanban-state')
FROM tasks
WHERE ulid == '01e0k6a1p00002zgzc0845vayw'
" \
| tail -n 1
backlog
This can also be used to update metadata fields:
tl runsql "
UPDATE tasks
SET metadata=(
SELECT json_set(tasks.metadata, '\$.kanban-state', 'sprint')
FROM tasks
WHERE ulid == '01e0k6a1p00002zgzc0845vayw'
)
WHERE ulid == '01e0k6a1p00002zgzc0845vayw'
"
Another great use-case is to automatically extract and store data from the body of the task.
E.g. you could set up a CRON job to automatically extract any GitHub link
from the body and store it in an extra github_url
metadata field:
UPDATE tasks
SET metadata = json_insert(
ifnull(metadata, '{}'),
'$.github_url',
substr(body, instr(body, 'https://github.com'),
CASE
WHEN instr(substr(body, instr(body, 'https://github.com')), ' ') == 0
THEN length(substr(body, instr(body, 'https://github.com')))
ELSE instr(substr(body, instr(body, 'https://github.com')), ' ') - 1
END
)
)
WHERE body LIKE '%https://github.com%'
External Commands
Like Git, TaskLite also supports external commands. This allows you to easily extend TaskLite's functionality with your own scripts.
For this to work, simply add an executable script (chmod +x
)
with the prefix tasklite-
to your $PATH
For example, to add a grin
command which simply prints a smiley:
$ cat /usr/local/bin/tasklite-grin
#! /usr/bin/env bash
echo 'π' "$@"
$ tasklite grin Hi
π Hi
Import / Migration
This is a best effort list on how to import your tasks from other task managers to TaskLite.
YAML File
If you have all you tasks in one YAML file like this:
- id: 123
body: Buy milk
tags: [groceries]
- id: 456
body: Go running
tags: [sport]
Run following command to import it. Be sure to make yaml2json available in your path and to install jq first.
cat tasks.yaml \
| yaml2json \
| jq -c '.[]' \
| while read -r task
do
echo "$task" | tasklite importjson
done
Taskwarrior
TaskLite supports all fields of Taskwarrior's export format. Therefore migration is really simple:
task export rc.json.array=off \
| while read -r task; \
do echo $task | tasklite importjson; \
done
Google Tasks
There is currently no proper way to export tasks.
A workaround is:
- Open the standalone view of Google Tasks
- Select all text with
cmd + a
and copy it - Paste it in a text editor
- Format it properly
- Import it with a
while
loop as seen in the Taskwarrior section
Google Keep
You can export all tasks / notes from Google Keep via Google Takeout.
It provides a Takeout/Keep
directory
with one .html
and .json
file per task.
To import the .json
files,
change into the directory and run following command:
find . -iname '*.json' \
| while read -r task
do
jq -c \
'.textContent as $txt
| .labels as $lbls
| .title as $title
| (if .isArchived then "done"
elif .isTrashed then "deletable"
else null
end) as $state
| {
utc: .userEditedTimestampUsec,
body: ((if $title and $title != "" then $title else $txt end)
+ (if .listContent
then "\n\n" +
(
.listContent
| map("- [" + (if .isChecked then "x" else " " end) + "] "
+ .text)
| join("\n")
)
else ""
end))
}
| if $lbls then . + {tags: ($lbls | map(.name))} else . end
| if $title and $title != "" and $txt and $txt != ""
then . + {notes: [{body: $txt}]}
else .
end
| if $state then . + {state: $state} else . end
' \
"$task" \
| tl importjson
done
The title of the Google Keep note becomes the body of the task and the note itself becomes a TaskLite note attached to the task. A list of sub-tasks will be converted to a GitHub Flavored Markdown task list.
Telegram
Telegram's "Saved Messages" -- a.k.a. messages to oneself -- are a pretty convenient inbox. Here is how to move them to TaskLite afterwards:
-
Install Telegram Desktop
brew install telegram-desktop
-
Go to "Saved Messages"
-
Click on 3 dots in the upper right corner
-
Click on "Export chat history"
-
Deselect all additional media and select "JSON" as output format
-
Approve download on a mobile device
-
Download the JSON file (if download window was closed, simply follow the previous steps again)
-
Either import it directly as JSON or convert it first to YAML for cleanup.
Import JSON directly:
cat result.json \ | jq -c ' .messages | map( (if (.text | type) == "string" then .text else (.text | map( if (. | type) == "string" then . else .text end ) | join(", ") ) end) as $body | { utc: .date, body: $body, tags: ["telegram"] } ) | .[] ' \ | while read -r task do echo "$task" | tasklite importjson done
Convert it to YAML for easier cleanup:
jq '.messages' result.json | yq --yaml-output > out.yaml
-
Clear chat history on Telegram
Apple Reminders
Use following Apple Script to display the reminders including their creation timestamp. Seen at discussions.apple.com/thread/8570915.
- Download the script:
export-reminders.scpt
- Run it with:
osascript export-reminders.scpt
- Enter the name of the list you want to export
β οΈ Warning
It includes all reminders - even completed ones - in the list. If it's a long list, it will take a while. A better approach would be to create a new list and move all reminders you want to export to that list. - Copy and paste the output into a
tasks.json
file - Format it as proper JSON and manually add notes, and tags fields
- Import JSON file:
cat tasks.json \ | jq -c '.[]' \ | while read -r task do echo $task | tasklite importjson done
Fixing Mistakes
It's easy to write a short shell script to fix any mistakes you might have made during the import process.
In following example I forgot to use the metadata field end
to set the closed_utc
column, but used the current date instead.
As I deleted all metadata afterwards,
I now need to extract the field from an old backup.
In the meantime I changed some closed_utc
fields and therefore I can't fully automate it.
A neat little trick I came up with is to automatically paste the correct value
into the clipboard, so I only have to insert it at the right location.
A fish script to fix the mistake could look like this:
sqlite3 \
~/TaskLite/main.db \
"SELECT ulid FROM tasks WHERE closed_utc LIKE '%2024-02-26%'" \
| while read ulid \
; echo "$ulid" \
; and \
sqlite3 \
~/TaskLite/backups/2024-02-14t1949.db \
"SELECT json_extract(metadata, '\$.end') FROM tasks WHERE ulid == '$ulid'" \
| tee /dev/tty \
| pbcopy \
; and tl edit "$ulid" \
; end
Hooks
Hooks are scripts that can be executed at various stages of TaskLite's execution.
Configuration
Hooks can either be specified via the config file or via hook files.
If the files have an extension (e.g. post-add.lua
) the corresponding
interpreter will be used.
Otherwise the file will be executed as a shell script.
Currently supported interpreters are:
lua
, python3
, ruby
, node
, and [v
][vlang].
It's recommended to write you scripts in the [V programming language][vlang]
due to its high performance (script is compiled on first execution),
its great ergonomics, and its comprehensive standard library.
The compiled versions of the script will be cached as
_v_executable_.<name-of-script>
in the hooks directory.
?
[vlang]: https://vlang.io
Another good alternative is Lua as it is simple, lightweight, and fast. Futhermore, future versions of TaskLite will include the Lua interpreter to make it independent of the system's installed Lua interpreter.
If the hook files are shell scripts, they must be executable (chmod +x
).
Otherwise they can't be executed directly by TaskLite.
The file names must start with the stage they are for (pre
or post
),
followed by a dash and the stage name (e.g. pre-add
), and optionally
followed by an underscore and a description.
E.g pre-add_validate_task.v
or pre-exit_sync.v
.
They are executed in alphabetical order.
If you want to ensure a specific order,
you can include a number in the description.
E.g. pre-add_01_x.v
, pre-add_02_y.v
, β¦.
warning
Multiple pre-add
hooks are not supported yet,
but will be in the future.
To ignore a hook, you can prefix the file name with an underscore (_
).
Stages
Following stages are available:
- Launch
pre-launch
- After reading all configs, but before any TaskLite code is executed. Can be used to prevent execution of TaskLite.post-launch
- After reading CLI arguments, setting up the database and running all migrations.
- Add
pre-add
- Right before adding a new task. Can be used to prevent addition of task.post-add
- After new task was added.
- Modify
pre-modify
- Right before a task gets modified. Can be used to prevent modification of task.post-modify
- After task was modified.
- Exit
pre-exit
- Last thing before program termination
The hooks receive JSON data from TaskLite via stdin. We're using JSON5 here for better readability.
Included fields are:
{
arguments: [β¦], // Command line arguments (after `tasklite`)
β¦ // Stage specific fields (see below)
}
During execution, a called hook should print a result JSON object to stdout. All fields of the JSON are optional.
Possible values:
{
message: "β¦", // A message to display on stdout
warning: "β¦", // A warning to display on stderr
error: "β¦", // An error to display on stderr
β¦ // Other fields depending on hook type (check out table below)
}
Hooks can write to stderr at any time, but it's not recommended. Rather write a JSON object to stdout and let TaskLite print the message / warning / error with improved formatting and coloring.
Event | Stdin | Stdout on Success (exitcode == 0) |
Stdout on Error (exitcode != 0) |
---|---|---|---|
pre‑launch |
β | { message: "β¦", β¦ } |
{ message: "β¦", β¦ }Processing terminates |
post‑launch |
{ arguments: [β¦] } |
{ message: "β¦", β¦ } |
{ message: "β¦", β¦ }Processing terminates |
pre‑add |
{ arguments: [β¦], taskToAdd: {} } |
{ task: {}, message: "β¦", β¦, } |
{ message: "β¦", β¦ }Processing terminates |
post‑add |
{ arguments: [β¦], taskAdded: {} } |
{ message: "β¦", β¦ } |
{ message: "β¦", β¦ }Processing terminates |
pre‑modify |
{ arguments: [β¦], taskToModify: {} } |
{ task: {}, message: "β¦", β¦ } |
{ message: "β¦", β¦ }Processing terminates |
post‑modify |
{ arguments: [β¦], taskOriginal: {}, taskModified: {} } |
{ message: "β¦", β¦ } |
{ message: "β¦", β¦ }Processing terminates |
pre‑exit |
β |
{ message: "β¦", β¦ } |
{ message: "β¦", β¦ }Processing terminates |
Debugging
To see the JSON for a single task run:
tl ndjson | grep $ULID_OF_TASK | head -n 1 | jq
Examples
Config
You can add a hooks
field to your config file like this:
hooks:
directory: /Users/adrian/Dropbox/TaskLite/hooks
launch:
pre:
- interpreter: v
body: |
fn main() {
println('{
"message": "π Pre launch message",
"warning": "β οΈ Pre launch warning",
"error": "β Pre launch error"
}')
}
post: []
add:
pre: []
post: []
modify:
pre: []
post: []
exit:
pre: []
V Lang Scripts
Save the examples in the hooks directory as pre-launch.v
, etc.
Pre Launch
fn main() {
println('{
"message": "π Pre launch message",
"warning": "β οΈ Pre launch warning",
"error": "β Pre launch error"
}')
}
Post Launch
import os
import json
struct PostLaunchInput {
mut:
arguments []string
}
struct PostLaunchOutput {
message string
warning string
error string
}
fn main() {
stdin := os.get_raw_lines_joined()
mut input := json.decode(PostLaunchInput, stdin)!
println(json.encode(PostLaunchOutput{
message: 'π Post launch message\n' + //
'Provided arguments: ${input.arguments}\n'
warning: 'β οΈ Post-launch warning'
error: 'β Post-launch error'
}))
}
Pre Add
import os
import json
struct Task {
mut:
body string
tags []string
}
struct PreAddInput {
mut:
task_to_add Task @[json: 'taskToAdd']
arguments []string
}
struct PreAddOutput {
mut:
task Task
message string
warning string
error string
}
fn main() {
stdin := os.get_raw_lines_joined()
input_data := json.decode(PreAddInput, stdin)!
mut task := input_data.task_to_add
task.tags << 'additional-tag'
pre_add_output := PreAddOutput{
task: task
message: 'π Pre-add message\n' + //
'Provided arguments: ${input_data.arguments.str()}\n' + //
'Provided task: ${input_data.task_to_add.str()}\n' + //
'Updated task: ${task.str()}\n'
warning: 'β οΈ Pre-add warning'
error: 'β Pre-add error'
}
println(json.encode(pre_add_output))
}
Post Add
import os
import json
struct Task {
mut:
body string
tags []string
}
struct PostAddInput {
mut:
arguments []string
task_added Task @[json: 'taskAdded']
}
struct PostAddOutput {
mut:
message string
warning string
error string
}
fn main() {
stdin := os.get_raw_lines_joined()
mut input_data := json.decode(PostAddInput, stdin)!
post_add_output := PostAddOutput{
message: 'π Post-add message\n' + //
'Provided arguments: ${input_data.arguments.str()}\n' + //
'Added task: ${input_data.task_added.str()}\n'
warning: 'β οΈ Post-add warning'
error: 'β Post-add error'
}
println(json.encode(post_add_output))
}
Pre Modify
import os
import json
struct Task {
mut:
body string
tags []string
}
struct PreModifyInput {
mut:
arguments []string
task_to_modify Task @[json: 'taskToModify']
}
struct PreModifyOutput {
mut:
task Task
message string
warning string
error string
}
fn main() {
stdin := os.get_raw_lines_joined()
input_data := json.decode(PreModifyInput, stdin)!
mut task := input_data.task_to_modify
task.tags << 'additional-tag'
pre_modify_output := PreModifyOutput{
task: task // TODO: This should be parsed by TaskLite and used downstream
message: 'π Pre-modify message\n' + //
'Provided arguments: ${input_data.arguments.str()}\n' + //
'Provided task: ${input_data.task_to_modify.str()}\n' + //
'Updated task: ${task.str()}\n'
warning: 'β οΈ Pre-modify warning'
error: 'β Pre-modify error'
}
println(json.encode(pre_modify_output))
}
Post Modify
import os
import json
struct Task {
mut:
body string
tags []string
}
struct PostModifyInput {
mut:
arguments []string
task_modified Task @[json: 'taskModified']
}
struct PostModifyOutput {
mut:
message string
warning string
error string
}
fn main() {
stdin := os.get_raw_lines_joined()
mut input_data := json.decode(PostModifyInput, stdin)!
post_modify_output := PostModifyOutput{
message: 'π Post-modify message\n' + //
'Provided arguments: ${input_data.arguments.str()}\n' + //
'Modified task: ${input_data.task_modified.str()}\n'
warning: 'β οΈ Post-modify warning'
error: 'β Post-modify error'
}
println(json.encode(post_modify_output))
}
Pre Exit
fn main() {
println('{
"message": "π Pre exit message",
"warning": "β οΈ Pre exit warning",
"error": "β Pre exit error"
}')
}
Shell Scripts
Save the examples in the hooks directory as pre-launch.sh
, etc.
Pre Launch
stdin=$(cat)
>&2 echo "File > pre-launch: Input via stdin:"
>&2 echo "$stdin"
echo "{}"
Post Launch
stdin=$(cat)
>&2 echo "File > post-launch: Input via stdin:"
>&2 echo "$stdin"
echo "{}"
Pre Add
stdin=$(cat)
>&2 echo "File > pre-add: Input via stdin:"
>&2 echo "$stdin"
echo "{}"
Web Based GUIs
Web App
The TaskLite web app is a simple Elm Land app backed by an AirGQL GraphQL server.
Usage
Start the Server
To use it you first need to start the server:
tasklite server
Then you need to start the web app server:
git clone https://github.com/ad-si/TaskLite
cd TaskLite/tasklite-webapp
make start
The web app will then be available at localhost:3000.
Dashboard
A simple way to create a dashboard with multiple views is to create a HTML file with multiple iframes that load the different views.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TaskLite Dashboard</title>
<link
rel="icon"
type="image/png"
href="https://raw.githubusercontent.com/ad-si/TaskLite/master/docs-source/images/icon.png"
>
<style type="text/css">
* { margin: 0; padding: 0; border: 0; box-sizing: border-box; }
html { height: 100%; }
body { height: 100%; font-family: sans-serif; }
header { padding: 0.5rem; }
iframe { width: 100%; height: 100%; }
#grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
height: 100%;
}
</style>
</head>
<body>
<div id="grid">
<iframe src="http://localhost:3000/tags/focus"></iframe>
<iframe src="http://localhost:3000/tags/chore"></iframe>
<iframe src="http://localhost:3000/tags/buy"></iframe>
<iframe src="http://localhost:3000/tags/work"></iframe>
</div>
</body>
</html>
SQLite Web Frontends
Datasette
Datasette is an open source multi-tool for exploring and publishing data. It provides a web frontend for SQLite databases that can be used to explore your tasks in following way:
datasette ~/TaskLite/main.db
You can then use any of TaskLite's predefined views: 127.0.0.1:8001/main/tasks_view
Generate custom view by appending the SQL query to the URL. For example: 0.0.0.0:8001/main?sql=SELECT+*+FROM+tasks+WHERE+body+LIKE+%27%25xyz%25%27
You can then bookmark those views for easy access.
SQLite Web
Another way to host a simple web frontend is SQLite Web.
You can run it with Docker like this:
docker run -it --rm \
-p 8080:8080 \
-v ~/TaskLite:/data \
-e SQLITE_DATABASE=main.db \
coleifer/sqlite-web
Haskell Library
You can also use TaskLite in your Haskell projects by adding the
Hackage package
as a dependency to your package.yaml
or your *.cabal
file:
dependencies:
- tasklite-core
- β¦
Potential use-cases:
- Queue for processing tasks
- Backend for an online task management or bookmarking service
- β¦
Concepts
General
TaskLite tries to adhere to the Unix philosophy: Do one thing and do it well. This, however, also means that it intentionally does not include certain features, which you might expect it to have.
For example there is no support for fuzzy timestamps like "in 2 weeks", as this should be handled by other CLI tools. Since CLI tools can be easily combosed, this is no disadvantage!
For example, I use tu
to convert
such fuzzy times to exact timestamps:
tl add Buy milk due:$(tu in 2 weeks)
And the big advantage of composing tools is,
that you now can use tu
in other contexts as well!
The power of the Unix philosophy:
Each new tool you learn gives you a compound interest on all other tools!
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.
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 available meta information adequately!
The exact calculation algorithm can be found
in the SQL view tasks_view
in the [Migrations.hs] file.
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
States
Instead of allowing one to explicitly set a state, TaskLite infers the current state from several other fields. There are primary, secondary, and tertiary states.
2 primary states:
Open
- Waits to be doneClosed
- Nothing left to be done
9 exclusive secondary states:
Asleep
- Is hidden because it's not relevant yetAwake
- Has become relevant or will become soonReady
- 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 doneObsolete
- Has become obsolete or impossible to finishDeletable
- Not needed anymore and can be deleted (item in the trash)
Β
State\Field | awake_utc | ready_utc | waiting_utc | review_utc | closed_utc | state |
---|---|---|---|---|---|---|
Open | β | β | β | β | β | β |
ββAsleep | > now | > now or β | β | β | β | β |
ββAwake | < now | > now or β | β | β | β | β |
ββReady | < now or β | < now | β | β | β | β |
ββWaiting | β | β | < now | > now or β | β | β |
ββReview | β | β | β | < now | β | β |
Closed | β | β | β | β | β | β |
ββDone | β | β | β | β | β | Done |
ββObsolete | β | β | β | β | β | Obsolete |
ββDeletable | β | β | β | β | β | Deletable |
Legend:
- β = Set
- β = Not set
- β = Maybe set
3 exclusive tertiary 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.Frozen
- Was previously repeating or recurring but has been stopped for the time being.
State\Field | group_ulid | repetition_duration | recurrence_duration |
---|---|---|---|
Repeating | β | β | β |
Recurring | β | β | β |
Frozen | β | β | β |
A task is either recurring or repeating, but can't be both at the same time. For more information and examples check out the corresponding documentation page
Repetition and Recurrence
The difference between repetition and recurrence is the reference point from which future tasks are calculated from.
For repetition, it's the closing datetime. E.g. if a task gets completed, a duplicate will be created with the specified time offset added to the closing datetime. Check out the "Mow the law" example below for more details.
Recurring tasks are tasks which need to be done every day, week, etc. and must not be delayed. I.e. missed completions must be caught up immediately. Check out the "Pay the rent" example below for more details.
A task is either recurring or repeating, but can't be both at the same time. For both repetition and recurrence series, only the currently active task exists and subsequent tasks will be created on demand, once the current task is closed.
Example for Repetition
$ tl add Mow the lawn due:2020-10-01
π Added task "Mow the lawn" with id "01eme2w9pqbmgme5zcwr9hpfbc"
$ tl repeat P1M 01eme2w9pqbmgme5zcwr9hpfbc
π
Set repeat duration of task "Mow the lawn"
with id "01eme2w9pqbmgme5zcwr9hpfbc" to "P1M"
$ tl info 01eme2w9pqbmgme5zcwr9hpfbc
Mow the lawn
State: Open
Priority: 12.0
ULID: 01eme2w9pqbmgme5zcwr9hpfbc
π
Due 2020-10-01 00:00:00
β¬
π Created 2020-10-12 09:39:48
β¬
βοΈ Modified 2020-10-12 09:39:58
Repetition Duration: P1M
Group Ulid: 01eme2wm3e4wcwacnw9ha7ffs1
User: adrian
$ tl do 01eme2w9pqbmgme5zcwr9hpfbc
β
Finished task "Mow the lawn" with id "01eme2w9pqbmgme5zcwr9hpfbc"
β‘οΈ Created next task "Mow the lawn"
in repetition series "01eme2wm3e4wcwacnw9ha7ffs1"
with id "01eme2xe4vjv3yd3gkg0a9y8j8"
$ tl info 01eme2xe4vjv3yd3gkg0a9y8j8
Mow the lawn
State: Open
Priority: 3.0
ULID: 01eme2xe4vjv3yd3gkg0a9y8j8
βοΈ Modified 2020-10-12 09:39:58
β¬
π Created 2020-10-12 09:40:25
β¬
π
Due 2020-11-12 09:39:58
Repetition Duration: P1M
Group Ulid: 01eme2wm3e4wcwacnw9ha7ffs1
User: adrian
Example for Recurrence
$ tl add Pay rent due:2020-10-01
π Added task "Pay rent" with id "01eme47dje7bpkmz01s5xdtw15"
$ tl recur P1M 01eme47dje7bpkmz01s5xdtw15
π
Set recurrence duration of task "Pay rent"
with id "01eme47dje7bpkmz01s5xdtw15" to "P1M"
$ tl info 01eme47dje7bpkmz01s5xdtw15
Pay rent
State: Open
Priority: 12.0
ULID: 01eme47dje7bpkmz01s5xdtw15
π
Due 2020-10-01 00:00:00
β¬
π Created 2020-10-12 10:03:21
β¬
βοΈ Modified 2020-10-12 10:03:32
Recurrence Duration: P1M
Group Ulid: 01eme47s0yy848wvbsyxh9mpj6
User: adrian
$ tl do 01eme47dje7bpkmz01s5xdtw15
β
Finished task "Pay rent" with id "01eme47dje7bpkmz01s5xdtw15"
β‘οΈ Created next task "Pay rent"
in recurrence series "01eme47s0yy848wvbsyxh9mpj6"
with id "01eme487qmxj7jm4mtn5n59nbg"
$ tl info 01eme487qmxj7jm4mtn5n59nbg
Pay rent
State: Open
Priority: 3.0
ULID: 01eme487qmxj7jm4mtn5n59nbg
βοΈ Modified 2020-10-12 10:03:32
β¬
π Created 2020-10-12 10:03:47
β¬
π
Due 2020-11-01 00:00:00
Recurrence Duration: P1M
Group Ulid: 01eme47s0yy848wvbsyxh9mpj6
User: adrian
Automation
The real power of TaskLite gets unlocked when it's set up to be tightly integrated with other apps. This section contains several examples of how it can work together with other systems and services.
Folder Actions on MacOS
Folder actions are a feature on macOS to execute some code when files are added to a specific directory.
This can for example be used to import all Email files (.eml
)
which are saved in a directory.
Setup
-
Open Automator and create a new Folder Action:
-
Specify the directory via the select field at the top
-
Add a "Run Shell Script" block with following bash code:
set -euo pipefail input=$(cat -) result=$(/Users/adrian/.local/bin/tasklite import "$input" 2>&1 || true) resultNorm=${result//[^a-zA-Z0-9 \/:.]/ } osascript -e \ "display notification \"${resultNorm:0:80}\" with title \"Email was imported into TaskLite\""
-
Save the folder action
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. E.g. AirGQL for an instant GraphQL 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:
Command Comparison
TaskWarrior | TaskLite | Description & Differences |
---|---|---|
add | add | Add a new task |
annotate | note | Add a note / comment / annotation to a task |
append | - (edit) | Append words to a task description |
calc | - | Expression calculator |
config | config | TL only displays it, TW allows modification |
context | - | Manage contexts. TL uses tags instead. |
count | count | Count the tasks matching a filter |
delete | trash | Mark a task as deletable |
denotate | unnote | Remove an annotation from a task |
done | do | Complete a task |
duplicate | duplicate | Clone an existing task |
edit | edit | Launch your text editor to modify a task |
execute | - | Execute an external command |
export | ndjson | Export tasks in NDJSON instead of JSON format |
help | help | Show high-level help, a cheat-sheet |
import | import | Additionally to JSON supports Email files (.eml) |
log | log | Record an already-completed task |
logo | - | Show the Taskwarrior logo |
modify | - (edit) | Modify one or more tasks |
prepend | - (edit) | Prepend words to a task description |
purge | delete | Completely remove task, rather than just change status |
start | start | Start working on a task, make active |
stop | stop | Stop working on a task, no longer active |
synchronize | - | Syncs tasks with Taskserver |
undo | - | Revert last change |
version | version | Version details and copyright |
active | - | Started tasks |
all | all | Pending, completed and deleted tasks |
blocked | - | Tasks that are blocked by other tasks |
blocking | - | Tasks that block other tasks |
completed | done | Tasks that have been completed |
list | open | Pending tasks |
long | - | Pending tasks, long form |
ls | - | Pending tasks, short form |
minimal | - | Pending tasks, minimal form |
newest | new | Most recent pending tasks |
next | head | Most urgent tasks |
oldest | - | Oldest pending tasks |
overdue | overdue | Overdue tasks |
ready | ready | Pending, unblocked, scheduled tasks |
recurring | recurring | Pending recurring tasks |
unblocked | - | Tasks that are not blocked |
waiting | waiting | Hidden, waiting tasks |
burndown.daily | - | Burndown chart, by day |
burndown.monthly | - | Burndown chart, by month |
burndown.weekly | - | Burndown chart, by week |
calendar | - | Calendar and holidays |
colors | - | Demonstrates all supported colors |
columns | - | List of report columns and supported formats |
commands | help | List of commands, with their behaviors |
diagnostics | - | Show diagnostics, for troubleshooting |
ghistory.annual | - | History graph, by year |
ghistory.monthly | - | History graph, by month |
ghistory.weekly | - | History graph, by week |
ghistory.daily | - | History graph, by day |
history.annual | - | History report, by year |
history.monthly | - | History report, by month |
history.weekly | - | History report, by week |
history.daily | - | History report, by day |
ids | - | Filtered list of task IDs |
information | info | All attributes shown |
projects | projects | List of projects (project in TL = active tag) |
reports | help | List of available reports |
show | config | Filtered list of configuration settings |
stats | stats | Filtered statistics |
summary | projects | Filtered project summary |
tags | tags | Filtered list of tags |
timesheet | - | Weekly timesheet report |
udas | - | Details of all defined UDAs |
uuids | - | Filtered list of UUIDs |
_aliases | - | List of active aliases |
_columns | - | List of supported columns |
_commands | - | List of supported commands |
_config | - | List of configuration setting names |
_context | - | List of defined context names |
_get | - | DOM accessor |
_ids | - | Filtered list of task IDs |
_projects | - | Filtered list of project names |
_show | - | List of name=value configuration settings |
_tags | - | Filtered list of tags in use |
_udas | - | List of configured UDA names |
_unique | - | List of unique values for the specified attribute |
_urgency | - | Filtered list of task urgencies |
_uuids | - | Filtered list of pending UUIDs |
_version | - | Task version (and optional git commit) |
_zshattributes | - | Zsh formatted task attribute list |
_zshcommands | - | Zsh formatted command list |
_zshids | - | Zsh formatted ID list |
_zshuuids | - | Zsh formatted UUID list |
-
| random | Show a random open task
Changelog
This document lists all notable changes to the functionality of TaskLite.
2024-09-23 - 0.4
General
- Add simple webapp powered by elm.land
- Add support for hooks to execute code during a task's lifecycle (ba2054b)
- Set text color according to current background color (1d9ae64)
- Add support for recurring tasks (8028190)
- Add support for external CLI commands (9cb8fa3)
- Use
.yaml
extension foredit
to have syntax highlighting (10e1e52) - Support the full ISO8601 duration syntax everywhere (8df1c65)
- Add support for filter expressions to sub-command
new
(9ab3992) - Improve design of detail view (used by "info" & "next" commands) (c32be9f)
- Extend and restructure the documentation
- Webapp: Show selected tags in header (ff1bf2e)
- Edit mode: If parsing fails, re-open editor with modified content (2b5a0e1)
- Improve import of user, title, labels fields (needed for importing GitHub issues) (af51aaa)
New Subcommands
stats
- List share of Done, Obsolete, Deletable tasks (c2a2d70)server
- Starts an API + GraphQL server powered by AirGQL (4669498)random
- Show a random task (88a999d)deletetag
(d3ab4de)deletenote
(581ffae)json
- Show tasks in JSON format (45ad3f3)reviewin
- Set review date in x days (b05c81a)idea
- Quickly capture ideas (6545500)modified
- List all tasks by modified UTC desc (71e4603)modifiedonly
- List tasks where modified UTC /= creation UTC by modified UTC desc (4809d5b)notes
- List all notes descending by creation UTC (81e6885)recurring
- List recurring tasks (0c11309)ingest
- Runsimport
->edit
->delete
workflow on files (325e0ac)importdir
- Import all .json and .eml files in a directory (4c3c50a)ingestdir
- Ingest all .json and .eml files in a directory (4c3c50a)endall
- End all provided tasks (changeend
to accept a closing note) (4d2032e)unrecur
- Remove recurring timestamp (2ee1c21)unwake
- Remove awake timestamp (987a4fe)unready
- Remove ready timestamp (987a4fe)unreview
- Remove review timestamp (1d82631)
Import
- Add support for importing
.json
and.eml
files (8a821de) - Support list of strings as
notes
in JSON import (d07a328) - Add support for parsing unix timestamps with 10, 13, and 16 digits (ce0392d)
- Add support for milliseconds in UTC timestamps (2e580ba)
- Use timestamp of parent task for imported tags and notes (ac15d47)
- Make it more robust (works with a larger variety of files now)
Check out the commit history for all changes.
2020-03-01 - 0.3
- Add
edit
command to edit YAML version of task in$EDITOR
(1add89e) - Add several
un*
commands to erase fields (0f09c3d) - Only execute trigger to set
closed_utc
after state changed (395a8e0) - Show descriptive variable names in brief help text (10f8cf6)
- Hide aliases from main help (c52df72)
- Display alias errors even with subarguments (c52df72)
- Fix parsing of timestamp part in ULIDs for small timestamp values (258df47)
- Also display full version slug with
tl version
(0c292d1) - Remove unnecessary import logging (c04b894)
- Add git hash to the version string (5f7b1ef)
- Create config directory if it does not exist (8d0657c)
2019-10-04 - 0.2.2
- 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
- Fix creation of Docker image, extend documentation accordingly (fa4cad3)
2019-06-13 - 0.2
- Initial release
FAQs
Why another CLI task manager?
Answer:
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 style?
Answer:
I don't like Org-mode's' 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.
What are your long term goals?
Answer:
For the product roadmap check out the dedicated page for it
However, this project is not just about the product, but just as well about the underlying values. Big companies are good at offering you fancy products without clarifying any of the adjacent issues which are crucial for an outstanding user experience beyond the product itself:
- Will this service also be available in the future?
- What are your incentives to keep this service alive?
- What happens if the company dies?
- Can I export my data in a practical format?
- Who has data sovereignty?
- How do you share / sell my data?
- β¦
Will this service also be available in the future?
Answer:
The code is completely free open source software and compiling and using it is straight forward. Whatever happens to me, TaskLite will always be available in this way.
What are your incentives to keep this service alive?
Answer:
I'm using it daily and it has become something like my second brain. Since I'm not interested in abandoning my brain, it will be maintained and further developed in the future.
What happens if you die?
Answer:
A good friend has access to the repository and can transfer it to the community.
Can I export my data in a practical format?
Answer:
Not locking you in is one of the most important aspects of TaskLite. Therefore it supports numerous export formats:
- JSON - All tasks as one JSON array
- JSON Lines - One JSON object per task
- CSV
- SQL
- Direct access to the SQLite database
For more information check out the export documentation
Who has data sovereignty?
Answer:
All your data is stored in your TaskLite database on your computer. No analytics data or data of any other kind gets transferred to a third party during the usage of TaskLite.
Do you share or sell my data?
Answer:
We do not have access to any of your data!
How do I keep up with any changes related to TaskLite?
Answer:
- Star TaskLite on GitHub to get notified about new releases in your GitHub feed
- Check out the official changelog
Roadmap
- Feature Parity with Taskwarrior
- More Features for Using It as a Personal Knowledge Base
- Syncing
- Desktop App
- Mobile App
There is no time frame when the following enhancements will be implemented since the amount of time I can spare to work on TaskLite fluctuates heavily.
However, I'm using it daily and it has become something like my second brain. Since I'm not interested in abandoning my brain, rest assured that there will be steady progress on this in the future.
The enhancements are roughly listed with descending priority.
Feature Parity with Taskwarrior
Since TaskLite started out as a replacement for Taskwarrior, one of my main goals is to reach feature parity with it. Some of the major features still missing:
- Hooks
- History View
- Burndown Chart
- Calendar View
Check out the page "Differences to Taskwarrior" for a detailed comparison.
More Features for Using It as a Personal Knowledge Base
- Tasks can be a note
-
Tasks can be linked to each other
- Visualize the graph of linked tasks
- Export to markdown files a la Zettelkasten apps like Obsidian
- Export to PDF, Anki, etc.
Syncing
There is no concrete plan for native synchronization support yet. I've been using Dropbox for synchronizing the TaskLite database among several computers, which has worked astonishingly well so far.
Another good solution could be Litestream, which continuously streams SQLite changes to S3-compatible storage.
Desktop App
While there is already a prototype for a desktop app included in the code, I currently don't plan to further work on it. The integrated webapp is sufficient for most use cases. However, I'm open to contributions!
Mobile App
There are no plans for a native mobile app. The integrated webapp is sufficient for most use cases.
Related
Comparison to Open Source Alternatives
If TaskLite isn't your cup of tea, maybe one of the other free task managers fits the bill:
Name | Description |
---|---|
Buku | Store and manage your bookmarks from the command line |
Dooit | A TUI todo manager |
Dstask | Single binary CLI todo manager with git sync and markdown notes |
Etm | Event and task manager |
Eureka | CLI tool to input and store ideas without leaving the terminal |
nb | CLI note-taking, bookmarking, archiving, and knowledge base app |
Neorg | Tool for structured note taking and project/task management |
Nvim-OrgMode | Orgmode clone written in Lua for Neovim |
Org mode | Notes and todo lists powered by an Emacs based plain-text system |
Smos | Purely functional semantic tree-based editor (like Org mode) |
Taskwarrior | Command line task management |
td-cli | Command line todo list manager |
Todo.txt | Simple and extensible shell script for managing a todotxt file |
Todoman | Simple iCalendar + CalDAV based CLI todo manager |
Toodles | Project management from the TODO's in your codebase |
Topydo | Powerful todo list application for the console based on todo.txt |
TTDL | Terminal Todo List Manager |
Unfog | A simple CLI task and time manager |
Vikunja | Self-hostable to-do app |
Vim-OrgMode | Text outlining and task management based on Emacs' Org-Mode |
Stats
Updated on 2024-05-05.
Nr | Name | Stars | Commits | Contrib. | Code | 1. Commit |
---|---|---|---|---|---|---|
1 | nb | 7495 | 7395 | 20 | Shell | 2014β11β12 |
2 | Buku | 6160 | 2058 | 69 | Python | 2015β11β02 |
3 | Neorg | 5872 | 3323 | 108 | Lua | 2021β04β11 |
4 | Todo.txt | 5477 | 483 | 66 | Shell | 2009β03β05 |
5 | Org mode | ~4000 | ~25000 | 543 | EmacsLisp | 2003β01β01 |
6 | Taskwarrior | 3904 | 11650 | 147 | C++ | 2008β04β19 |
7 | Vim-OrgMode | 3083 | 1044 | 47 | Python | 2010β10β09 |
8 | Nvim-OrgMode | 2752 | 1081 | 55 | Lua | 2021β05β13 |
9 | Dooit | 1908 | 948 | 14 | Python | 2022β04β17 |
9 | Toodles | 966 | 211 | 13 | Haskell | 2018β09β04 |
10 | Dstask | 770 | 882 | 14 | Go | 2018β12β08 |
10 | Topydo | 750 | 1446 | 13 | Python | 2014β10β19 |
11 | Eureka | 738 | 258 | 8 | Rust | 2017β11β20 |
12 | Vikunja | 707 | 9732 | 56 | Go | 2018-06-10 |
13 | Todoman | 472 | 974 | 35 | Python | 2015β03β29 |
14 | Smos | 283 | 2741 | 23 | Haskell | 2018β07β29 |
15 | TTDL | 193 | 247 | 7 | Rust | 2018β12β30 |
16 | TaskLite | 191 | 444 | 5 | Haskell | 2018β06β04 |
17 | Unfog | 184 | 198 | 4 | Haskell | 2019β10β22 |
18 | td-cli | 184 | 310 | 2 | Python | 2018β06β03 |
19 | Etm | 40 | 3879 | 4 | Python | 2017β09β02 |
Numbers with a ~
are not necessarily comparable,
as they are either estimated or from another platform than GitHub.
Commercial Alternatives
For completeness sake here is also a list of popular commercial apps and SaaS providers:
- Asana - Your teamβs goals, plans, tasks, files together in one shared space.
- Google Keep - Hybrid note taking and todo app.
- Jira - Bug- & issue-tracking and project-management service.
- Microsoft TODO - Cloud based task management application.
- Nirvana - To-do app available on macOS, Android, Windows, and web.
- Remember The Milk - Web based task- and time-management.
- Tasker - Tool for managing tasks, processes and employees.
- TaskPaper - Plain text to-do lists (macOS app).
- Things - Personal task manager to achieve your goals (Apple only).
- Todoist - SaaS to-do list to organize work and life.
- Trello - Web based Kanban list-making application.
Archive
The following projects appear to be inactive or no longer actively maintained:
Name | Description |
---|---|
CommitTasks | Combination between git commit and todo list |
Eagle | Minimalistic todo app for command line |
Ff | A distributed note taker and task manager |
git-pending | Git plugin to list TODO, FIXME, β¦ comments in a repository |
Pomoday | Keyboard only task management web app |
t | Minimal command-line todo list manager |
Taskbook | Tasks, boards & notes for the command-line habitat |
Taskell | Command line Kanban board / task management |
Tracli | Command line app that tracks your time |
Ultralist | Open source task management system for the command line |
Work | CLI TODO app written with Rust and SQLite |
Yokadi | Command line oriented, SQLite powered todo list |
Stats
Updated on 2024-05-05.
Name | Stars | Commits | Contrib. | Code | 1. Commit |
---|---|---|---|---|---|
Taskbook | 8888 | 232 | 28 | JavaScript | 2018β02β12 |
Taskell | 1703 | 1064 | 10 | Haskell | 2017β11β15 |
Ultralist | 947 | 419 | 20 | Go | 2016β04β23 |
t | 740 | 97 | 9 | Python | 2009β08β26 |
Pomoday | 577 | 165 | 14 | TypeScript | 2019β10β24 |
git-pending | 303 | 37 | 2 | JavaScript | 2019β06β17 |
CommitTasks | 294 | 94 | 4 | JavaScript | 2018β08β17 |
Ff | 190 | 833 | 10 | Haskell | 2017β12β29 |
Yokadi | 129 | 1159 | 12 | Python | 2008β08β24 |
Tracli | 37 | 62 | 3 | JavaScript | 2019β07β15 |
Eagle | 27 | 46 | 1 | Python | 2018β10β28 |
Work | 24 | 67 | 1 | Rust | 2020β07β15 |
How to Get Latest Stats
You can run the GraphgQL query in
load-related-stats.gql
in the GitHub GraphQL Explorer at
docs.github.com/en/graphql/overview/explorer
to get the latest stats.
Or you can use Airput to create an Aisequel table with the stats.
AIRSEQUEL_DB_ID='01β¦' \
AIRSEQUEL_API_TOKEN='ast_β¦' \
GITHUB_TOKEN='ghp_β¦' \
airput github-upload ad-si/TaskLite && \
airput github-upload xwmx/nb && \
airput github-upload jarun/Buku && \
β¦
Development
Technologies
- Programming language: Haskell
- Dependency management: Stack
- Backend: SQLite
- Database access: sqlite-simple
- Command line parsing: Optparse Applicative
- Formatting: Prettyprinter
- Prelude: Protolude
- IDs: ULID
- Desktop App: Declarative GTK
Development Environment
The recommended way to develop Haskell is with VS Code and the Haskell Language Server.
Getting Started
Check out the makefile for all development tasks.
The most important command is make test
to run the tests after any changes.
They should always pass before committing.
To try out local changes via the CLI you can use the following command:
stack run -- add "Buy milk"
Deployment
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.
Archive
Following section contains app and API experiments that are currently not further developed.
They might be reactivated in the future if there is enough interest, or if someone submits enough patches to make them work again.
Desktop App
The desktop app can currently only list the tasks. It's implemented with a declarative Haskell wrapper for GTK.
Installation
You can install the desktop app using Stack:
git clone https://github.com/ad-si/TaskLite
cd archive/tasklite-app
stack install