Home

Awesome

Kronos protocol

Kronos is a synchronized, cross-platform task and time manager, inspired by Taskwarrior and Timewarrior.

Taskwarrior and Timewarrior are very good and complete tools, but complex and not so easy to understand. Kronos aims to unify both tools in one, and to be more simple (focusing on what it's really needed). In fact, Kronos is a group of clients which follow this protocol. Feel free to contribute, share some idea, or even code a Kronos client.

List of clients

Table of contents

Database

Each client has its own locale database. It can be a file, a local storage, a database or whatever, but it should be packaged with the client, so it's possible to use the client offline. It's used to store user's tasks and config.

interface Database {
  // User tasks
  tasks: Task[]

  // User config
  hide_done: boolean
  enable_sync: boolean
  sync_host: string
  sync_user_id: string
  sync_device_id: string
  sync_version: number
}

Read

Reads all data from database. Throws read database failed.

function read(): Database

Write

Writes partial data to database. Throws write database failed.

type Partial<T> = {
  [P in keyof T]?: T[P]
}

function write(data: Partial<Database>): void

Config

Hide done

If true, the list action does not display done tasks.

Enable sync

If true, tasks are synchronized with a Kronos realtime server instance.

Host

The realtime server hostname. Eg: localhost:5000.

User id

The user identifier. It should be kept secretly by the users. It will be prompted each time a user connects for the first time to a Kronos client.

Device id

The user's device identifier. It should be hidden from the users, and should be kept by clients.

Version

The current version of the database. Each time the locale database is modified, sets this version with the current date. This way, all clients are synchronized with the most recent database version.

Task

interface Task {
  id: Id
  desc: Desc
  tags: Tag[]
  active: DateTime
  last_active: DateTime
  due: DateTime
  done: DateTime
  worktime: Duration
}

Id

type Id = number // An integer > 0

Desc

type Desc = string

Tag

type Tag = string // Matches [0-9a-zA-Z\-_]*

Duration

type Duration = number // An integer

DateTime

type DateTime = number // A timestamp

Repository

Create

Receives a partial Task, validates values, formats it as a Task (with default values) and inserts it into database. Only the description is mandatory. The id is auto-generated. Throws invalid <property> and create task failed.

type Partial<T> = {
  [P in keyof T]?: T[P]
}

type PartialWithDesc<T> = Partial<T> & {
  desc: Desc
}

function create(task: PartialWithDesc<Task>): Id

If sync is enabled, send a create request to the server, in order to notify all other user's clients.

Read

Retrieves a task by Id. Throws task not found and read task failed.

function read(id: Id): Task

Read All

Retrieves all tasks from database. Throws read all tasks failed.

function read_all(): Task[]

Update

Updates a task with the partial task given. Throws task not found and update task failed.

type Partial<T> = {
  [P in keyof T]?: T[P]
}

function update(id: Id, task: Partial<Task>): void

If sync is enabled, send a update request to the server, in order to notify all other user's clients.

Delete

Deletes a task by id. Throws task not found and delete task failed.

function delete(id: Id): void

If sync is enabled, send a delete request to the server, in order to notify all other user's clients.

Generate Id

Generate a unique id from a list of task.

function generate_id(tasks: Task[]): Id

Here the algorithm to follow:

function generate_id(tasks: Task[]): Id {
  const ids = tasks.map(t => t.id)
  let new_id = 1

  while (true) {
    if (ids.indexOf(new_id) !== -1) {
      return new_id
    }
    
    new_id++
  }
}

Stringify task props

Transforms all properties of a task to string (in order to prepare the task to be displayed in an list or info context).

enum Context {
  List,
  Info,
}

type StringProps<T> = {
  [P in keyof T]: string;
}

function stringify_props(context: Context, task: Task): StringProps<Task>

List context

Info context

User interface

A client can have a CLI, a GUI, or both.

Actions

Add

Adds a new task. Throws invalid <property>, task already exist and task add failed.

function add(args: string): void

The args match this pattern: <desc> <tags> <due>.

A tag must start by + and should not contain any space. For example:

add("+tag +tag-2 +tag_3")

A due must start by : and should contain numbers only. The full format of a valid due is :DDMMYY:HHMM but almost everything can be omitted. Here some example to understand better the concept:

Full due:

add(":100518:1221") // 10th of May 2018, 12h21

If minutes omitted, set to 00:

add(":100518:12")   // 10th of May 2018, 12h00

If hours omitted, set to 00:

add(":100518")      // 10th of May 2018, 00h00

If years omitted, try first the current year. If the final date is exceeded, try with the next year:

add(":1005")        // 10th of May <year> or <year>+1, 00h00

If months omitted, try first the current month. If the final date is exceeded, try with the next month:

add(":10")          // 10th of <month> or <month>+1 <year>, 00h00

If days omitted, try first the current day. If the final date is exceeded try with the next day:

add(":")            // <day> or <day>+1 of <month> <year>, 00h00
add("::8")          // <day> or <day>+1 of <month> <year>, 08h00

All together:

// Command executed on 1st of March, 2018 at 21h21
add("my awesome task +firstTask :3:18 +awesome")

will result in:

{
  "desc": "my awesome task",
  "tags": ["firstTask", "awesome"],
  "due": "3rd of March 2018, 18h00"
}

The order is not important, tags can be everywhere, and due as well. The desc is the remaining of text present after removing tags and due. Both examples end up with the same result:

add("my awesome task +firstTask :3:18 +awesome")
add("my +awesome awesome :3:18 +firstTask task")

Info

Displays a task by id. The function stringify_props is used to format the task to show. Throws task not found and task info failed.

function info(id: Id): void

List

Displays all tasks. The function stringify_props is used to format all tasks to show. If user config hide done is enabled, does not show up done tasks. Throws task list failed.

function list(): void

Update

Updates a task by id. Throws task not found and task update failed.

function update(id: Id, args: string): void

Same usage as Add, except for tags. You can remove an existing tag by prefixing it with a -.

For eg., to remove oldtag and add newtag to task 42:

update(42, "-oldtag +newtag")

Delete

Deletes a task by id. Throws task not found and task delete failed.

function delete(id: Id): void

Start

Starts a task by id. Throws task not found, task already started and task start failed.

function start(id: Id): void

Also set active property to now (when this action is triggered).

Stop

Stops a task by id. Throws task not found, task already stopped and task stop failed.

function stop(id: Id): void

Also update the worktime (increase the amount by now - active), set active to 0, and set last_active to now.

Toggle

If task is active, triggers stop action, otherwise trigger start action. Throws task not found, task already started, task already stopped and task toggle failed.

function toggle(id: Id): void

Done

Marks a task as done. Throws task not found, task already done and task done failed.

function done(id: Id): void

If the task is active, triggers stop action first. Then sets done property to now, and sets id to ${id}${now}. For example, if the id = 5, and now = 1530716924, then the new id will be 51530716924.

Undone

Unmarks a task as done. Throws task not found, task not done and task undone failed.

function undone(id: Id): void

Also sets done to 0, and generate a new id for this task.

Toggle hide done

Toggles the user configuration hide done. Throws task toggle hide done failed.

function toggle_hide_done(): void

Worktime

Shows the total worktime by tags. Throws task worktime failed.

function worktime(args: string): void

For example, to print the total worktime for tags tag1 and tag2:

worktime("tag1 tag2")

Context

Sets a context by tags. It means that only tasks containing tags in context will be shown, and when a task is added, these tags will be applied by default. Throws task context failed.

function context(args: string): void

For example, to set the context to project-A:

context("project-A")

If you list all tasks, you will see only tasks with project-A tag. If you add a new task, it will automatically get the tag project-A.

To clear the context, just call context with empty args.

CLI

The command name is kronos, and has a shortcut named k. In some specific case, the command can be Kronos, and its shortcut K. If a command is entered without any parameter, then start the GUI (if exists). Otherwise, the first parameter is the action, and the other parameters are transmitted to the action. Each action has a shortcut:

ActionShortcutLink to function
addaAdd
infoiInfo
listlList
updateuUpdate
deletedelDelete
startstartStart
stopstopStop
toggle start/stopsToggle
donedoDone
undoneundoUndone
toggle hide donehToggleHideDone
worktimewWorktime
contextcContext

GUI

When the GUI mode is started, the list action is triggered as main function. So there is no action list in GUI mode. But there is an action toggle hide done to show and hide done tasks in this list. By default, the first time this list is showed up, done tasks are hidden. To change the default behaviour, check out the user configuration Hide done.

Actions are triggered by screen events (mouse click, finger touch) or by keyboard events (shortcuts). The list and the info show data in realtime. If it's not possible, a refresh action is needed, in order to have up-to-date informations.

ActionKey mappingLink to function
add<a>Add
info<i>Info
update<u>Update
delete<Backspace>, <Del>Delete
start<s>Start
stop<S>Stop
toggle start/stop<Enter>Toggle
done<d>Done
undone<U>Undone
toggle hide done<H>ToggleHideDone
worktime<W>Worktime
context<C>Context
refresh<R>Refreshes all the GUI (only when there is no realtime showing)
quit<q>, <Esc>Quits the GUI mode (only if CLI mode exists also)

Sync

Tasks can be synchronized with a Kronos realtime server instance. This feature can be activated or deactivated from user config.

Initialization

When the client starts, contact the server and send a login request. user_id and device_id can be omitted if it's the first time a client connects to the server. You will receive a user_id, a device_id and a version.

Notifications

Once initialised, your client is connected to the server, and will receive a notification each time the database changes. See server notifications to learn more about how to handle them.

Contributing

Git commit messages follow the Angular Convention, but contain only a subject.

Use imperative, present tense: “change” not “changed” nor “changes”<br>Don't capitalize first letter<br>No dot (.) at the end

Code should be as clean as possible, variables and functions use the snake case convention. A line should never contain more than 80 characters.

Tests should be added for each new functionality. Be sure to run tests before proposing a pull request.