Personal time management for techies

Buzz through crazy busy days with as little stress as possible. Stay focused by seeing only one task at a time. Use this simple. fast, fun tool all day with the keyboard in a familiar terminal environment.

Logo by Freepik-Flaticon

Principles

Busy is built with the following usage principles in mind:

  • Monotasking: We each focus better when we work on exactly one task at a time. So busy only shows you one task.
  • Keyboard-driven: Productive people use the keyboard effectively, because muscle memory builds up over time, and it's faster to hit a key than to find an icon on a screen and move the pointer.
  • Offline use: It's designed to run on your laptop or desktop computer, without needing an internet connection, so it works extremely fast under any conditions.
  • Multi-platform Because Busy is a terminal-based application, it will run on MacOS, Linux, or Windows.
  • Personal: Busy is not a collaboration platform or project management application. It's for managing your personal time, not assigning things to others.
  • Importance over Urgency: Stop stressing out over last-minute tasks and impending deadlines! Busy makes it easy to capture future tasks and remember to do them early enough to reduce the pressure.
  • Editable data: The data is stored in text files, which can easily be edited outside of Busy itself. (In fact, Busy started as a todo.txt type of approach and grew from there.)

Data model

Busy's core model is a collection of Items, which are typically tasks but can also be discussion topics, groceries to buy, or anything else you like. Items are organized into Queues, which are named sets of Items to do. You work on the top Item in a Queue, and when it's done, that Item gets marked as "done", to reveal the next one. There is a default Queue (called "tasks") but you can also create other Queues, for example a shopping list or discussion list.

Busy actually moves Items between States within each Queue. Each Queue contains a Collection (ordered list) of Items for each State. The States are:

  • todo: Current Items for you to work on, discuss, or buy now.
  • done: Items that have been done, with the date completed.
  • plan: Items that have been deferred to a future date, with that date.

Ready to get busy?

Go to installation

Installation

Briefly (MacOS)

brew install python@3.11
python3.11 -m pip install pipx
pipx install busy

Long version

You'll need a terminal emulator to access a command or shell prompt. Examples include:

  • iTerm2 or Terminal on MacOS
  • Gnome Terminal or XTerm on Linux
  • CMD on Windows
  • Terminator on all platforms

Busy requires Python 3.11 and pipx. To check whether you already have the right version of Python on your system, start your terminal emulator and type:

python3 -V

If you don't have Python, or your version is out of date, install or upgrade it. In most cases, you'll want to do so using your system's package manager (such as Homebrew on MacOS or APT on Ubuntu). If you're not familiar with package managers, then download Python from the Python.org site directly and follow the instructions provided there. When done, use the version check above to confirm it's installed and the version is 3.11 or greater.

On MacOS, this might work:

brew install python@3.11

On Ubuntu, this might work:

apt-get install python@3.11

Install pipx with pip:

python3.11 install pipx
pipx install busy

To upgrade it later:

pipx upgrade busy

Ready to get busy?

Go to getting started

Getting started

To get started, add some tasks to your default Queue.

busy add 'Donate to the Busy project'
busy add 'Phone mom'
busy add 'Do the laundry'
busy add 'Take a shower'

Then, when you're ready to start your day, ask Busy what to do first:

busy

Returns:

Donate to the Busy project

That's it! Get to work.

When you've finished that task, mark it off to find the next task.

busy done

It will ask you to confirm that you're done. Then request the next task:

busy

Which will tell you what to do next:

Phone mom

If you want to see the whole Queue, with sequence numbers, type:

busy list

Here's the list you will see. Note that the completed Tasks are gone:

1  Do the laundry
2  Take a shower

If you decide, in the moment, to wait until later today to perform a task, drop it to the bottom of the Queue using the drop command:

busy drop

If you see a task on the list that seems urgent, and you intend to perform it immediately, pop it to the top of the list:

busy pop 2

Let's say you realize that it's not an appropriate task for today, but you want to defer it to tomorrow:

busy defer

It will ask you to confirm "tomorrow" as the day for deferral. Agree with it for now. The Item will then be moved into the plan State with tomorrow's date as the plan date.

At the start of a new day, tell Busy to add all the previously deferred Items to the current Queue:

busy activate

Congratulations! You may now start enjoying the benefits of a more relaxed approach to your busy life.

Ready to get busy?

Go to details

Details

Here's a summary of the commands in Busy.

Output commands:

  • describe tells the most about an item or set of items
  • simple includes less detail than describe
  • base includes even less detail
  • resource shows the URL (if any) associated with an item
  • list is like describe but with sequence numbers
  • queues to lists all the queues
  • tags to lists tags from a queue

Commands that make changes:

  • add adds a new item
  • done marks an item as 'done' and gives the option of iterating - "done for today"
  • defer puts a task or set of items on the plan for a future date
  • pop moves a task or set of items to the top of a queue - "pop it to the top"
  • drop moves a task or set of items to the bottom of a queue - "drop it to the bottom"
  • pick moves one random item from the filtered set to the top of the queue - "pick one for me"
  • delete permanently removes a task or set of items from a queue
  • edit opens a text editor to edit items - the default is to edit only the top item
  • manage is the same as edit, but defaults to edit the whole collection
  • activate finds everything planned for today and adds it to the list - "activate my day"

Here are some of the options which apply to some or all of the commands:

  • filters to designate items to be acted upon, using sequence numbers or tags
  • the add command requires a description of the new item instead of filters
  • --queue if not the default tasks queue
  • --help to find out which options apply
  • --yes to skip confirmation of any command that requires it
  • --state to work on Items of a different state (todo, plan, done)
  • --when applies to the defer command
  • --defer applies to the done command

Sequence numbers

Sequence numbers appear in the output from the list command. Note that the numbering starts with 1, and is not an ID -- the number of a item will change when the collection is modified. So always reference the most recent output from the list command.

Sequence numbers are used with filters. To designate more than one item, separate the sequence numbers with a space.

Another choice is ranges. A range of sequence numbers is separated by a hyphen, with no whitespace, and is inclusive. For example, 4-6 designates items 4, 5, and 6. A hyphen without a number after it includes all the items from that item to the end of the queue. A hyphen on its own indicates the last item in the queue.

Below are some examples of task designations by sequence number.

  • busy pop 5 pops item number 5
  • busy drop 3-7 drops items 3 through 7 (4 items)
  • busy list 3- lists all the items from number 3 through the end of the list
  • busy delete 3 5 7 9 deletes only the items designated
  • busy defer - defers the last task
  • busy edit -4 is an error! Use busy edit 1-4 instead
  • busy manage allows you to edit the entire queue

Items will always be handled in the order they appear in the queue, regardless of the order the filter are provided. So for example, if a pop command designates some items, they will be moved to the top of the queue in the order, relative to each other, they currently appear in the queue.

The sequence numbers in the list command output are from the collection itself. So the list command does not modify the sequence numbers, even when item designation is applied.

Tags

BusyML includes tags as words in item descriptions starting with #.

Tags may be used as filter in addition to sequence numbers. For example, the following command will move all the items with the #errands tag to the top of the queue.

busy pop errands

Numerical filters use OR logic with each other, and tag filters use OR logic with each other. But AND logic applies between the two types. For example, the command below deletes tasks in the range 1-10 that have either the admin or sales tag.

busy delete 1-10 admin sales

To use AND logic with tags in filters, combine them with a plus (+). For example, the command below lists tasks that are tagged both daily and admin.

busy list daily+admin

Named Filters

Specific filters can be named for the plan and done queues to specify date ranges. Use a colon.

  • planmin:2024-09-28
  • planmax:2024-09-28
  • donemin:2024-09-28
  • donemax:2024-09-28

Default item designations

For the most part, commands that accept item designations default to only act on the top item in the queue. The exceptions are:

  • list and manage default to handle the entire collection
  • pop defaults to pop the last item in the collection to the top
  • activate defaults to activate plan items for today (more on that below)
  • add adds to the bottom of the todo queue

Alternate queues

Busy will manage any number of queues, which are entirely separate sets of items. For example, you might have a shopping queue for items to buy at the store, and a movies queue for films you'd like to watch. The default queue is called tasks.

To designate an alternate queue, use the --queue or -q option. For example:

busy add --queue shopping -d "Skimmed Milk"
busy list -q movies

Managing plans

Busy supports several specific commands related to planning -- that is, scheduling tasks for the future. They are done, defer, and activate. The task-specific commands handle items in the plan state and, in some cases, the done state.

The task commands accept filters. The done and defer commands reference the todo collection; the activate command references the plan collection. The default for done and defer is the top item in the collection; the default for activate is to activate only plans deferred to today or earlier.

Planning by date

Planning is by date, not time, and is relative to the current date according to the system clock.

In the done command, the date can be specified using the --defer option (or by inputting a value after issuing the command). If the option is omitted, then the date can be provided as input during confirmation.

The date may take any of the following forms:

  • A specific date in YYYY-MM-DD format, such as 2018-10-28. Slashes are also acceptable, but the order is always year, then month, then day.
  • A specific date without the year in MM-DD format, such as 7-4, which will defer the item to that date in the future (even if it's in the next year).
  • A specific day of the month as a simple integer, such as 12, which will defer the item to that day of the month, in either the current month or the next month.
  • An integer, a space, and the word day or days, such as 4 days, which will defer the item to that number of days from today.
  • An integer without a space and the letter d, such as 4d, which is a short form of 4 days.
  • The word tomorrow, which is also the default if no date is provided.
  • The word today, which is useful for activating tasks later in the day.

As an example, the following command will defer tasks 4, 5, and 6 from the todo collection to the date 4 days from today, keeping them in the plan collection until that date.

busy defer 4-6 -t "4 days"

Note that the plan collection keep the task information from the todo collection along with the date information (as an absolute date).

To pull tasks from the plan collection and put them back into the todo collection, use the activate command. There are two ways to use the activate command:

  • With no filter, in which case Busy activates all the tasks scheduled for today or earlier, bringing the todo list up to date
  • With designated items from the plan collection; note that the activate command accepts item designation from the plan queue itself so use busy list -s plan first to get the right list.

Finishing and iterating

The done command removes the designated Task (or the top task if none is designated) from the todo state and adds it to the done state, with today's date to indicate when it was completed.

Optionally, a task can have a repeat value by adding a right angle bracket and the word "repeat" with a time value.

  • check email > repeat in 1 day
  • phone mom > repeat on sunday
  • balance the checkbook > repeat on 6

The exact syntax for a Repeat is the word "repeat" followed by either "on" or "in" and a relative date phrase -- the same phrases that work with the defer command.

The done command uses the repeat value as the default for its iteration.

Editing items

The edit and manage commands launch the user's default text editor to directly edit a task, the whole queue, or part of a queue. Note that edit and manage are identical commands except for their default filter.

A text editor can be designated in a configuration file (.busy.yml) otherwise Busy uses Emacs.

The edit command with no filter will edit the top item in the list, and the manage command with no filter will edit the entire list. But it's also possible to designate items to be edited with both commands using a filter. The commands do their best to replace the edited items in place in the list order. So if you edit or manage a tag whose items are recently popped (at the top of the collection), then the edited items will still appear at the top. Even if you add items, they will be inserted after the last item in the edited set, not at the end of the queue. But all the items brought up in the editor will be edited. So if you remove an item in the editor, it will be deleted and the others will be moved up to take its place.

The editing process includes the full descriptions in BusyML.

Data storage

Busy keeps the collections in plain text files, so if the tool doesn't do something you want, you may edit the files. The files are in a directory together, referred to as the "root". Each file is the named according to the following convention:

<queue>.<state>.psv

If a required file is missing, it will be created automatically. So typically, the root includes tasks.todo.psv, tasks.plan.psv, tasks.done.txt, and any number of custom queue files.

Technically, Busy data files are pipe-delimited data files, though the todo collections only have one field ("description") while the plan and done files have only two fields (date and description).

Busy is not a database (yet). There is no support for managing separate fields in the Busy tool itself.

The default location for the files is a directory at ~/.busy, which will be generated as needed. Specify an alternative location using the --config option referencing a YAML file with the following format:

busy:
  storage:
    directory: /some/other/directory

Note that Busy does not support concurrency or locking in any form. If two commands are executing at the same time, they might overwrite each other. Overwriting is especially risky with the edit and manage commands, which keeps the user's editor open until they close it.

The format is designed to be simple but not idiot-proof. Experimentation might result in unintended consequences.

Even more details

BusyML item description markup

BusyML description markup

Every item in a Busy collection is created and stored with a description that uses a specific form of markup we call "BusyML".

Loosely, an item's description contains one or more space-separated words, and those works that start with a marker indicate some special data field.

The idea is to concentrate useful information in a way that's easy to remember and type.

The markers are:

  • # - tag
  • % - data value
  • ! - timing-related data
  • @ - url (only one allowed)
  • > - repeat timing, special because the marker itself can exist as its own word (i.e. have a space after) - only one allowed

All words without markers that appear before the repeat marker are considered the "base" description, and represent the human-readable action or topic of the item.

Example

`Check new Mac prices #shopping #weekend %i88 !e14 @https://apple.com/store > repeat on saturday'

  • The base is "Check new Mac prices"
  • Tags are shopping and weekend
  • One data item i88 (specifically might refer to a GitLab issue iid if using the busy-gitlab integration)
  • 14 minutes of elapsed time
  • Related url is the Apple store
  • When done, the task will repeat next Saturday

Tags

Items can have tags, which are space-separated hashtags in the item description. An item can have no tags, one tag, or more than one tag. For example the following item description has the tag "errands":

go to the supermarket #errands

The only punctuation that tags can contain is the hyphen ("-").

Data values

Data values are used by integrations to correlate to outside systems. They start with % followed immediately by a 1-letter reference to the data value being tracked. Busy itself does little with the data values; it's up to the integrations.

The time tracking subsystem inserts words starting with ! into tasks. Leave them alone.

URL

Useful place to bookmark a single URL related to the item

Repeat timing

Described on the details page.

Ready to get Busy?

Contribute!

Time tracking

Busy keeps track of how long you spend on the current task, and records it in the list of completed tasks. It only works on the tasks (default) queue and only works in minutes.

The elapsed time is stored in the task description using !e and is visible with the busy describe command.

For reporting, Busy can optionally apply a multiplier to the clock-minutes for e.g. invoicing (basic theory is that clock time at keyboard is only a part of overall value). The multiplier lives in the busy configuration.

At the bottom of busy list, see the total value (time*multiplier) for the queried tasks.

Works great with the new donemin: and donemax: filters, for example:

busy list -s done donemin:2024-10-12 donemax:2024-10-18 myclient

The timing system records the "start" time (basically clock time as of the last busy command) in the queue, then updates elapsed time when the next command runs. So while it gives the impression of a running clock, it's really just subtracting.

Also note that tasks without text (i.e. purely tags) don't record time. Use them for gaps in your day. For example, I use a task called simply #pause for breaks and such.

Integrations

Status: alpha

For developers

Busy features a very simple plugin architecture for integrations. It's still a work in progress and might change with any release.

Integrations are designed for external systems that need to sync data (in either direction or both) with the Busy "database".

Integrations must be preloaded into the venv from which Busy is run, typically within the pipx installation. In the future, we might provide a mechanism for loading them.

Two commands support integrations. Both tage the integration name as their first argument.

  • get - to get a piece of data
  • sync - to "sync" data

When an integration-related command is run, Busy will attempt to import a module called busy_ plus the integration name. The module must be preloaded. General expectation is that the package name would start with busy- plus the same integration name. For example, to sync with Asana, an integration might come in a package called busy-asana containing a module called busy_asana.

The interface for integrations is still under development and will be documented when it becomes more stable.

The general expectation is that the sync method will perform some read and write activity in either of Busy or some external system, and raise an exception if the operation files. Additionally, it might return a value, for example if the integration is purely designed to generate output in a specific format.

The sync method doesn't generate a status - that's up to the sync command.

When developing an integration, include busy itself in pyproject.toml, ideally with a specific version or narrow range, to avoid the possibility of dependency conflicts.

Under current temporary guidelines, installing an integration might look something like this (example from MacsOS):

.local/pipx/venvs/busy/bin/python -m pip install busy-asana

Items support the following readable properties, all parsed from the description

  • base (str)
  • tags - (set)
  • url - (str)
  • repeat (str)
  • elapsed - (int)
  • data_value - The only one that's a method; takes the key as input
  • description - Everything as entered, plus elapsed time as timing data

Note: Integrations first introduced in Busy 7.4.1 but considered unstable until at least 8.0.

Contribute

Request access to the source repo to contribute.

The code is intended to demonstrate some Python best practices:

  • Object-oriented with classes and subclasses.
  • Dynamic configuration using a unique approach we call "class families" - for example, the names of the commands are properties of the command classes, not in a big "if" statement.
  • Extensive testing with high test coverage, guaranteed by CI.
  • Leverage the standard library by requiring as few 3rd party PIP modules as possible.

To set everything up:

  • Requires Python 3.11
  • Clone the repo and CD into it
  • make init
  • poetry install
  • To run it: python -m busy ...

We use Visual Studio Code to build Busy, so there is a VS Code configuration file in the repository.

Then to run the test suite:

make test

Or to run test coverage:

make cover

And to check style:

make style

And to run all of the above, and prepare for CI:

make