Merge pull request #144 from nwinkler/bash-it-osx-growl

Growl notification for OS X commands
diff --git a/.gitignore b/.gitignore
index 25042cb..b27cddd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 */enabled/*
 .DS_Store
-custom/*.bash
+custom/*
 !custom/example.bash
 .rvmrc
 aliases/custom.aliases.bash
diff --git a/README.md b/README.md
index 0c9c303..105d5be 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Bash it
 
-**Bash it** is a mash up of my own bash commands and scripts, other bash stuff I have found. 
+**Bash it** is a mash up of my own bash commands and scripts, other bash stuff I have found.
 
 (And a shameless ripoff of [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh). :)
 
@@ -13,18 +13,19 @@
 3. Edit your `~/.bash_profile` file in order to customize bash-it.
 
 **NOTE:**
-The install script will also prompt you asking if you use [Jekyll](https://github.com/mojombo/jekyll). 
+The install script will also prompt you asking if you use [Jekyll](https://github.com/mojombo/jekyll).
 This is to set up the `.jekyllconfig` file, which stores info necessary to use the Jekyll plugin.
 
 
 ## Help Screens
 
 ```
-bash-it (will show all the help commands)
-aliases-help
-rails-help
-git-help
-plugins-help
+bash-it show aliases        # shows installed and available aliases
+bash-it show completions    # shows installed and available completions
+bash-it show plugins        # shows installed and available plugins
+bash-it help aliases        # shows help for installed aliases
+bash-it help completions    # shows help for installed completions
+bash-it help plugins        # shows help for installed plugins
 ```
 
 ## Your Custom scripts, aliases, and functions
@@ -45,7 +46,7 @@
 
 I think everyone has their own custom scripts accumulated over time.  And so, following in the footsteps of oh-my-zsh, bash it is a framework for easily customizing your bash shell. Everyone's got a custom toolbox, so let's start making them even better, **as a community!**
 
-Send me a pull request and I'll merge it as long as it looks good. If you change an existing command, please give an explanation why. That will help a lot when I merge your changes in. 
+Send me a pull request and I'll merge it as long as it looks good. If you change an existing command, please give an explanation why. That will help a lot when I merge your changes in.
 
 Thanks, and happing bashing!
 
diff --git a/aliases/available/bundler.aliases.bash b/aliases/available/bundler.aliases.bash
index a7756be..fc20f4f 100644
--- a/aliases/available/bundler.aliases.bash
+++ b/aliases/available/bundler.aliases.bash
@@ -1,21 +1,9 @@
-#!/usr/bin/env bash
+cite 'about-alias'
+about-alias 'ruby bundler'
 
 # Bundler Commands
-alias be="bundle exec"
-alias bi="bundle install"
-alias bl="bundle list"
-alias bu="bundle update"
-alias bp="bundle package"
-
-
-function bundler-help() {
-  echo "Bundler Aliases Usage"
-  echo
-  echo "  be          = bundle exec"
-  echo "  bi          = bundle install"
-  echo "  bl          = bundle list"
-  echo "  bu          = bundle update"
-  echo "  bp          = bundle package"
-  echo
-}
-
+alias be='bundle exec'
+alias bi='bundle install'
+alias bl='bundle list'
+alias bu='bundle update'
+alias bp='bundle package'
diff --git a/aliases/available/emacs.aliases.bash b/aliases/available/emacs.aliases.bash
index a133984..ffdb9f3 100644
--- a/aliases/available/emacs.aliases.bash
+++ b/aliases/available/emacs.aliases.bash
@@ -1,4 +1,5 @@
-#!/usr/bin/env bash
+cite 'about-alias'
+about-alias 'emacs editor'
 
 case $OSTYPE in
   linux*)
@@ -6,6 +7,6 @@
     alias e='emacsclient -n'
     ;;
   darwin*)
-    alias em="open -a emacs"
+    alias em='open -a emacs'
     ;;
 esac
diff --git a/aliases/available/general.aliases.bash b/aliases/available/general.aliases.bash
index 660a8ca..3e87e2d 100644
--- a/aliases/available/general.aliases.bash
+++ b/aliases/available/general.aliases.bash
@@ -1,4 +1,5 @@
-#!/usr/bin/env bash
+cite about-alias
+about-alias 'general aliases'
 
 # List directory contents
 alias sl=ls
@@ -27,20 +28,20 @@
 alias edit="$EDITOR"
 alias pager="$PAGER"
 
-alias q="exit"
+alias q='exit'
 
 alias irc="$IRC_CLIENT"
 
-alias rb="ruby"
+alias rb='ruby'
 
 # Pianobar can be found here: http://github.com/PromyLOPh/pianobar/
 
-alias piano="pianobar"
+alias piano='pianobar'
 
 alias ..='cd ..'         # Go up one directory
 alias ...='cd ../..'     # Go up two directories
 alias ....='cd ../../..' # Go up two directories
-alias -- -="cd -"        # Go back
+alias -- -='cd -'        # Go back
 
 # Shell History
 alias h='history'
@@ -53,30 +54,4 @@
 
 # Directory
 alias	md='mkdir -p'
-alias	rd=rmdir
-
-function aliases-help() {
-echo "Generic Alias Usage"
-echo
-echo "  sl      = ls"
-echo "  ls      = ls -G"
-echo "  la      = ls -AF"
-echo "  ll      = ls -al"
-echo "  l       = ls -a"
-echo "  c/k/cls = clear"
-echo "  ..      = cd .."
-echo "  ...     = cd ../.."
-echo "  -       = cd -"
-echo "  h       = history"
-echo "  md      = mkdir -p"
-echo "  rd      = rmdir"
-echo "  editor  = $EDITOR"
-echo "  pager   = $PAGER"
-echo "  piano   = pianobar"
-echo "  q       = exit"
-echo "  irc     = $IRC_CLIENT"
-echo "  md      = mkdir -p"
-echo "  rd      = rmdir"
-echo "  rb      = ruby"
-echo
-}
+alias	rd='rmdir'
diff --git a/aliases/available/git.aliases.bash b/aliases/available/git.aliases.bash
index abfa996..bbac313 100644
--- a/aliases/available/git.aliases.bash
+++ b/aliases/available/git.aliases.bash
@@ -1,4 +1,5 @@
-#!/usr/bin/env bash
+cite 'about-alias'
+about-alias 'common git abbreviations'
 
 # Aliases
 alias gcl='git clone'
@@ -38,36 +39,3 @@
     alias gd='git diff'
     ;;
 esac
-
-
-
-function git-help() {
-  echo "Git Custom Aliases Usage"
-  echo
-  echo "  gcl	  = git clone"
-  echo "  g       = git"
-  echo "  get 	  = git"
-  echo "  ga      = git add"
-  echo "  gall	  = git add ."
-  echo "  gst/gs  = git status"
-  echo "  gss	  = git status -s"
-  echo "  gl      = git pull"
-  echo "  gup     = git fetch && git rebase"
-  echo "  gp      = git push"
-  echo "  gd      = git diff | mate"
-  echo "  gdv     = git diff -w \"$@\" | vim -R -"
-  echo "  gc      = git commit -v"
-  echo "  gca     = git commit -v -a"
-  echo "  gci 	  = git commit --interactive"
-  echo "  gb      = git branch"
-  echo "  gba     = git branch -a"
-  echo "  gcount  = git shortlog -sn"
-  echo "  gcp     = git cherry-pick"
-  echo "  gco     = git checkout"
-  echo "  gexport = git git archive --format zip --output"
-  echo "  gdel    = git branch -D"
-  echo "  gpo     = git push origin"
-  echo "  gmu     = git fetch origin -v; git fetch upstream -v; git merge upstream/master"
-  echo "  gll     = git log --graph --pretty=oneline --abbrev-commit"
-  echo
-}
diff --git a/aliases/available/heroku.aliases.bash b/aliases/available/heroku.aliases.bash
index 7bc1dbe..a749d42 100644
--- a/aliases/available/heroku.aliases.bash
+++ b/aliases/available/heroku.aliases.bash
@@ -1,4 +1,5 @@
-#!/usr/bin/env bash
+cite 'about-alias'
+about-alias 'heroku task abbreviations'
 
 # heroku
 alias h='heroku'
@@ -31,26 +32,3 @@
 alias hca='heroku config:add'
 alias hcr='heroku config:remove'
 alias hcc='heroku config:clear'
-
-function heroku-help() {
-  echo "Heroku Aliases Usage"
-  echo
-  echo "  h           = heroku"
-  echo "  hl          = heroku list"
-  echo "  hi          = heroku info"
-  echo "  ho          = heroku open"
-  echo "  hd          = heroku dynos"
-  echo "  hw          = heroku workers"
-  echo "  hr          = heroku rake"
-  echo "  hcon        = heroku console"
-  echo "  hnew        = heroku create"
-  echo "  hrestart    = heroku restart"
-  echo "  hlog        = heroku logs"
-  echo "  hon         = heroku maintenance:on"
-  echo "  hoff        = heroku maintenance:off"
-  echo "  hc          = heroku config"
-  echo "  hca         = heroku config:add"
-  echo "  hcr         = heroku config:remove"
-  echo "  hcc         = heroku config:clear"
-  echo
-}
diff --git a/aliases/available/hg.aliases.bash b/aliases/available/hg.aliases.bash
index 245c529..eea819f 100644
--- a/aliases/available/hg.aliases.bash
+++ b/aliases/available/hg.aliases.bash
@@ -1,14 +1,6 @@
-#!/usr/bin/env bash
+cite 'about-alias'
+about-alias 'mercurial abbreviations'
 
 alias hs='hg status'
 alias hsum='hg summary'
 alias hcm='hg commit -m'
-
-function hg-help() {
-  echo "Mercurial Alias Help"
-  echo
-  echo "  hs    = hg status"
-  echo "  hsum  = hg summary"
-  echo "  hcm   = hg commit -m"
-  echo
-}
diff --git a/aliases/available/homebrew.aliases.bash b/aliases/available/homebrew.aliases.bash
index 051081d..b8f1481 100644
--- a/aliases/available/homebrew.aliases.bash
+++ b/aliases/available/homebrew.aliases.bash
@@ -1,24 +1,13 @@
 # Some aliases for Homebrew
 
-alias bup="brew update && brew upgrade"
-alias bout="brew outdated"
-alias bin="brew install"
-alias brm="brew uninstall"
-alias bls="brew list"
-alias bsr="brew search"
-alias binf="brew info"
-alias bdr="brew doctor"
+cite 'about-alias'
+about-alias 'homebrew abbreviations'
 
-function brew-help() {
-  echo "Homebrew Alias Usage"
-  echo
-  echo "bup  = brew update && brew upgrade"
-  echo "bout = brew outdated"
-  echo "bin  = brew install"
-  echo "brm  = brew uninstall"
-  echo "bls  = brew list"
-  echo "bsr  = brew search"
-  echo "binf = brew info"
-  echo "bdr  = brew doctor"
-  echo
-}
+alias bup='brew update && brew upgrade'
+alias bout='brew outdated'
+alias bin='brew install'
+alias brm='brew uninstall'
+alias bls='brew list'
+alias bsr='brew search'
+alias binf='brew info'
+alias bdr='brew doctor'
diff --git a/aliases/available/maven.aliases.bash b/aliases/available/maven.aliases.bash
index 349f9d8..4cd89d2 100644
--- a/aliases/available/maven.aliases.bash
+++ b/aliases/available/maven.aliases.bash
@@ -1,22 +1,11 @@
-alias mci="mvn clean install"
-alias mi="mvn install"
-alias mrprep="mvn release:prepare"
-alias mrperf="mvn release:perform"
-alias mrrb="mvn release:rollback"
-alias mdep="mvn dependency:tree"
-alias mpom="mvn help:effective-pom"
-alias mcisk="mci -Dmaven.test.skip=true"
+cite 'about-alias'
+about-alias 'maven abbreviations'
 
-function maven-help() {
-  echo "Maven Custom Aliases Usage"
-  echo
-  echo "  mci    = mvn clean install"
-  echo "  mi     = mvn install"
-  echo "  mrprep = mvn release:prepare"
-  echo "  mrperf = mvn release:perform"
-  echo "  mrrb   = mvn release:rollback"
-  echo "  mdep   = mvn dependency:tree"
-  echo "  mpom   = mvn help:effective-pom"
-  echo "  mcisk  = mvn clean install -Dmaven.test.skip=true"  
-  echo
-}
+alias mci='mvn clean install'
+alias mi='mvn install'
+alias mrprep='mvn release:prepare'
+alias mrperf='mvn release:perform'
+alias mrrb='mvn release:rollback'
+alias mdep='mvn dependency:tree'
+alias mpom='mvn help:effective-pom'
+alias mcisk='mci -Dmaven.test.skip=true'
diff --git a/aliases/available/osx.aliases.bash b/aliases/available/osx.aliases.bash
index a688cf6..ffe3ca0 100644
--- a/aliases/available/osx.aliases.bash
+++ b/aliases/available/osx.aliases.bash
@@ -1,4 +1,5 @@
-#!/usr/bin/env bash
+cite 'about-alias'
+about-alias 'osx-specific aliases'
 
 # Desktop Programs
 alias fireworks="open -a '/Applications/Adobe Fireworks CS3/Adobe Fireworks CS3.app'"
diff --git a/aliases/available/rails.aliases.bash b/aliases/available/rails.aliases.bash
index 1d7b310..ae664bf 100644
--- a/aliases/available/rails.aliases.bash
+++ b/aliases/available/rails.aliases.bash
@@ -1,4 +1,5 @@
-#!/usr/bin/env bash
+cite 'about-alias'
+about-alias 'rails abbreviations'
 
 # Rails Commands
 alias r='rails'
@@ -19,24 +20,3 @@
 alias restart='touch tmp/restart.txt'  # restart passenger
 alias devlog='tail -f log/development.log'
 alias taild='tail -f log/development.log' # tail dev log
-
-function rails-help() {
-  echo "Rails Aliases Usage"
-  echo
-  echo "  r           = rails"
-  echo "  rg          = rails generate"
-  echo "  rs/ss       = rails server"
-  echo "  ts          = thin server"
-  echo "  rc/sc       = rails console"
-  echo "  rn          = rails new"
-  echo "  rd          = rails dbconsole"
-  echo "  rp          = rails plugin"
-  echo "  ra          = rails application"
-  echo "  rd          = rails destroy"
-  echo "  restartapp  = touch tmp/restart.txt"
-  echo "  restart     = touch tmp/restart.txt"
-  echo "  devlog      = tail -f log/development.log"
-  echo "  taild       = tail -f log/development.log"
-  echo
-}
-
diff --git a/aliases/available/textmate.aliases.bash b/aliases/available/textmate.aliases.bash
index 897c7d3..f0f69e4 100644
--- a/aliases/available/textmate.aliases.bash
+++ b/aliases/available/textmate.aliases.bash
@@ -1,4 +1,5 @@
-#!/usr/bin/env bash
+cite 'about-alias'
+about-alias 'textmate abbreviations'
 
 case $OSTYPE in
   darwin*)
diff --git a/aliases/available/todo.txt-cli.aliases.bash b/aliases/available/todo.txt-cli.aliases.bash
index ce27716..5bf35d0 100644
--- a/aliases/available/todo.txt-cli.aliases.bash
+++ b/aliases/available/todo.txt-cli.aliases.bash
@@ -1,19 +1,8 @@
-#!/usr/bin/env bash
+cite 'about-alias'
+about-alias 'todo.txt-cli abbreviations'
 
 alias tls="$TODO ls"
 alias ta="$TODO a"
 alias trm="$TODO rm"
 alias tdo="$TODO do"
 alias tpri="$TODO pri"
-
-todo-help() {
-	echo
-	echo "todo.txt-cli Custom Aliases Usage"
-	echo
-	echo " tls  = $TODO ls"
-	echo " ta   = $TODO add"
-	echo " trm  = $TODO rm"
-	echo " tdo  = $TODO do"
-	echo " tpri = $TODO pri"
-	echo
-}
diff --git a/aliases/available/vim.aliases.bash b/aliases/available/vim.aliases.bash
index c156ffd..c14f32b 100644
--- a/aliases/available/vim.aliases.bash
+++ b/aliases/available/vim.aliases.bash
@@ -1,3 +1,4 @@
-#!/usr/bin/env bash
+cite 'about-alias'
+about-alias 'vim abbreviations'
 
 alias v='mvim --remote-tab'
diff --git a/bash_it.sh b/bash_it.sh
index 693c024..226213f 100755
--- a/bash_it.sh
+++ b/bash_it.sh
@@ -24,6 +24,9 @@
 # Load composure first, so we support function metadata
 source "${BASH_IT}/lib/composure.sh"
 
+# support 'plumbing' metadata
+cite _about _param _example _group _author _version
+
 # Load colors first so they can be use in base theme
 source "${BASH_IT}/themes/colors.theme.bash"
 source "${BASH_IT}/themes/base.theme.bash"
@@ -72,23 +75,3 @@
 then
   . $HOME/.jekyllconfig
 fi
-
-
-#
-# Custom Help
-
-function bash-it() {
-  echo "Welcome to Bash It!"
-  echo
-  echo "Here is a list of commands you can use to get help screens for specific pieces of Bash it:"
-  echo
-  echo "  rails-help                  list out all aliases you can use with rails."
-  echo "  git-help                    list out all aliases you can use with git."
-  echo "  todo-help                   list out all aliases you can use with todo.txt-cli"
-  echo "  brew-help                   list out all aliases you can use with Homebrew"
-  echo "  aliases-help                generic list of aliases."
-  echo "  plugins-help                list out all functions you have installed with bash-it"
-  echo "  bash-it-plugins             summarize bash-it plugins, and their installation status"
-  echo "  reference <function name>   detailed help for a specific function"
-  echo
-}
diff --git a/completion/available/tmux.completion.bash b/completion/available/tmux.completion.bash
index c872ec5..a1b8f06 100644
--- a/completion/available/tmux.completion.bash
+++ b/completion/available/tmux.completion.bash
@@ -5,6 +5,90 @@
 # Usage: Put "source bash_completion_tmux.sh" into your .bashrc
 # Based upon the example at http://paste-it.appspot.com/Pj4mLycDE
 
+    _tmux_cmds=" \
+attach-session \
+bind-key \
+break-pane \
+capture-pane \
+choose-client \
+choose-session \
+choose-window \
+clear-history \
+clock-mode \
+command-prompt \
+confirm-before \
+copy-buffer \
+copy-mode \
+delete-buffer \
+detach-client \
+display-message \
+display-panes \
+down-pane \
+find-window \
+has-session \
+if-shell \
+join-pane \
+kill-pane \
+kill-server \
+kill-session \
+kill-window \
+last-window \
+link-window \
+list-buffers \
+list-clients \
+list-commands \
+list-keys \
+list-panes \
+list-sessions \
+list-windows \
+load-buffer \
+lock-client \
+lock-server \
+lock-session \
+move-window \
+new-session \
+new-window \
+next-layout \
+next-window \
+paste-buffer \
+pipe-pane \
+previous-layout \
+previous-window \
+refresh-client \
+rename-session \
+rename-window \
+resize-pane \
+respawn-window \
+rotate-window \
+run-shell \
+save-buffer \
+select-layout \
+select-pane \
+select-prompt \
+select-window \
+send-keys \
+send-prefix \
+server-info \
+set-buffer \
+set-environment \
+set-option \
+set-window-option \
+show-buffer \
+show-environment \
+show-messages \
+show-options \
+show-window-options \
+source-file \
+split-window \
+start-server \
+suspend-client \
+swap-pane \
+swap-window \
+switch-client \
+unbind-key \
+unlink-window \
+up-pane"
+
 _tmux_expand () 
 { 
     [ "$cur" != "${cur%\\}" ] && cur="$cur"'\';
@@ -34,12 +118,12 @@
 function _tmux_complete_client() {
     local IFS=$'\n'
     local cur="${1}"
-    COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$(tmux -q list-clients | cut -f 1 -d ':')" -- "${cur}") )
+    COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$(tmux -q list-clients 2>/dev/null | cut -f 1 -d ':')" -- "${cur}") )
 }
 function _tmux_complete_session() {
     local IFS=$'\n'
     local cur="${1}"
-    COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$(tmux -q list-sessions | cut -f 1 -d ':')" -- "${cur}") )
+    COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$(tmux -q list-sessions 2>/dev/null | cut -f 1 -d ':')" -- "${cur}") )
 }
 function _tmux_complete_window() {
     local IFS=$'\n'
@@ -47,10 +131,10 @@
     local session_name="$(echo "${cur}" | sed 's/\\//g' | cut -d ':' -f 1)"
     local sessions
     
-    sessions="$(tmux -q list-sessions | sed -re 's/([^:]+:).*$/\1/')"
+    sessions="$(tmux -q list-sessions 2>/dev/null | sed -re 's/([^:]+:).*$/\1/')"
     if [[ -n "${session_name}" ]]; then
         sessions="${sessions}
-$(tmux -q list-windows -t "${session_name}" | sed -re 's/^([^:]+):.*$/'"${session_name}"':\1/')"
+$(tmux -q list-windows -t "${session_name}" 2>/dev/null | sed -re 's/^([^:]+):.*$/'"${session_name}"':\1/')"
     fi
     cur="$(echo "${cur}" | sed -e 's/:/\\\\:/')"
     sessions="$(echo "${sessions}" | sed -e 's/:/\\\\:/')"
@@ -102,8 +186,7 @@
 
     if [[ $COMP_CWORD -le $cmd_index ]]; then
         # The user has not specified a command yet
-        local all_commands="$(tmux -q list-commands | cut -f 1 -d ' ')"
-        COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "${all_commands}" -- "${cur}") )
+        COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "${_tmux_cmds}" -- "${cur}") )
     else        
         case ${cmd} in
             attach-session|attach)
diff --git a/install.sh b/install.sh
index 19f6f52..0f1da39 100755
--- a/install.sh
+++ b/install.sh
@@ -31,7 +31,16 @@
 function load_all() {
   file_type=$1
   [ ! -d "$BASH_IT/$file_type/enabled" ] && mkdir "$BASH_IT/${file_type}/enabled"
-  ln -s $BASH_IT/${file_type}/[^_]available/* "${BASH_IT}/${file_type}/enabled"
+  for src in $BASH_IT/${file_type}/available/*; do
+      filename="$(basename ${src})"
+      [ ${filename:0:1} = "_" ] && continue
+      dest="${BASH_IT}/${file_type}/enabled/${filename}"
+      if [ ! -e "${dest}" ]; then
+          ln -s "${src}" "${dest}"
+      else
+          echo "File ${dest} exists, skipping"
+      fi
+  done
 }
 
 function load_some() {
diff --git a/lib/helpers.bash b/lib/helpers.bash
index dfd64b0..e1787fe 100644
--- a/lib/helpers.bash
+++ b/lib/helpers.bash
@@ -1,11 +1,11 @@
 # Helper function loading various enable-able files
 function _load_bash_it_files() {
-  file_type="$1"
-  if [ ! -d "${BASH_IT}/${file_type}/enabled" ]
+  subdirectory="$1"
+  if [ ! -d "${BASH_IT}/${subdirectory}/enabled" ]
   then
     continue
   fi
-  FILES="${BASH_IT}/${file_type}/enabled/*.bash"
+  FILES="${BASH_IT}/${subdirectory}/enabled/*.bash"
   for config_file in $FILES
   do
     if [ -e "${config_file}" ]; then
@@ -29,106 +29,285 @@
   _load_bash_it_files "plugins"
 }
 
-bash-it-plugins ()
+bash-it ()
 {
-    about 'summarizes available bash_it plugins'
-    group 'lib'
+    about 'bash-it help and maintenance'
+    param '1: verb [one of: help | show | enable | disable ]'
+    param '2: component type [one of: alias(es) | completion(s) | plugin(s) ]'
+    param '3: specific component [optional]'
+    example '$ bash-it show plugins'
+    example '$ bash-it help aliases'
+    example '$ bash-it enable plugin git'
+    example '$ bash-it disable alias hg'
+    typeset verb=${1:-}
+    shift
+    typeset component=${1:-}
+    shift
+    typeset func
+    case $verb in
+         show)
+             func=_bash-it-$component;;
+         enable)
+             func=_enable-$component;;
+         disable)
+             func=_disable-$component;;
+         help)
+             func=_help-$component;;
+         *)
+             reference bash-it
+             return;;
+    esac
+
+    # pluralize component if necessary
+    if ! _is_function $func; then
+        if _is_function ${func}s; then
+            func=${func}s
+        else
+            if _is_function ${func}es; then
+                func=${func}es
+            else
+                echo "oops! $component is not a valid option!"
+                reference bash-it
+                return
+            fi
+        fi
+    fi
+    $func $*
+}
+
+_is_function ()
+{
+    _about 'sets $? to true if parameter is the name of a function'
+    _param '1: name of alleged function'
+    _group 'lib'
+    [ -n "$(type -a $1 2>/dev/null | grep 'is a function')" ]
+}
+
+_bash-it-aliases ()
+{
+    _about 'summarizes available bash_it aliases'
+    _group 'lib'
+
+    _bash-it-describe "aliases" "an" "alias" "Alias"
+}
+
+_bash-it-completions ()
+{
+    _about 'summarizes available bash_it completions'
+    _group 'lib'
+
+    _bash-it-describe "completion" "a" "completion" "Completion"
+}
+
+_bash-it-plugins ()
+{
+    _about 'summarizes available bash_it plugins'
+    _group 'lib'
+
+    _bash-it-describe "plugins" "a" "plugin" "Plugin"
+}
+
+_bash-it-describe ()
+{
+    _about 'summarizes available bash_it components'
+    _param '1: subdirectory'
+    _param '2: preposition'
+    _param '3: file_type'
+    _param '4: column_header'
+    _example '$ _bash-it-describe "plugins" "a" "plugin" "Plugin"'
+    
+    subdirectory="$1"
+    preposition="$2"
+    file_type="$3"
+    column_header="$4"
 
     typeset f
     typeset enabled
-    printf "%-20s%-10s%s\n" 'Plugin' 'Enabled?' 'Description'
-    for f in $BASH_IT/plugins/available/*.bash
+    printf "%-20s%-10s%s\n" "$column_header" 'Enabled?' 'Description'
+    for f in $BASH_IT/$subdirectory/available/*.bash
     do
-        if [ -e $BASH_IT/plugins/enabled/$(basename $f) ]; then
+        if [ -e $BASH_IT/$subdirectory/enabled/$(basename $f) ]; then
             enabled='x'
         else
             enabled=' '
         fi
-        printf "%-20s%-10s%s\n" "$(basename $f | cut -d'.' -f1)" "  [$enabled]" "$(cat $f | metafor about-plugin)"
+        printf "%-20s%-10s%s\n" "$(basename $f | cut -d'.' -f1)" "  [$enabled]" "$(cat $f | metafor about-$file_type)"
     done
-    printf '\n%s\n' 'to enable a plugin, do:'
-    printf '%s\n' '$ enable-plugin  <plugin name> -or- $ enable-plugin all'
-    printf '\n%s\n' 'to disable a plugin, do:'
-    printf '%s\n' '$ disable-plugin <plugin name> -or- $ disable-plugin all'
+    printf '\n%s\n' "to enable $preposition $file_type, do:"
+    printf '%s\n' "$ bash-it enable $file_type  <$file_type name> -or- $ bash-it enable $file_type all"
+    printf '\n%s\n' "to disable $preposition $file_type, do:"
+    printf '%s\n' "$ bash-it disable $file_type <$file_type name> -or- $ bash-it disable $file_type all"
 }
 
-disable-plugin ()
+_disable-plugin ()
 {
-    about 'disables bash_it plugin'
-    param '1: plugin name'
-    example '$ disable-plugin rvm'
-    group 'lib'
+    _about 'disables bash_it plugin'
+    _param '1: plugin name'
+    _example '$ disable-plugin rvm'
+    _group 'lib'
 
-    if [ -z "$1" ]; then
-        reference disable-plugin
+    _disable-thing "plugins" "plugin" $1
+}
+
+_disable-alias ()
+{
+    _about 'disables bash_it alias'
+    _param '1: alias name'
+    _example '$ disable-alias git'
+    _group 'lib'
+
+    _disable-thing "aliases" "alias" $1
+}
+
+_disable-completion ()
+{
+    _about 'disables bash_it completion'
+    _param '1: completion name'
+    _example '$ disable-completion git'
+    _group 'lib'
+
+    _disable-thing "completion" "completion" $1
+}
+
+_disable-thing ()
+{
+    _about 'disables a bash_it component'
+    _param '1: subdirectory'
+    _param '2: file_type'
+    _param '3: file_entity'
+    _example '$ _disable-thing "plugins" "plugin" "ssh"'
+    
+    subdirectory="$1"
+    file_type="$2"
+    file_entity="$3"
+
+    if [ -z "$file_entity" ]; then
+        reference "disable-$file_type"
         return
     fi
 
-    if [ "$1" = "all" ]; then
-        typeset f plugin
-        for f in $BASH_IT/plugins/available/*.bash
+    if [ "$file_entity" = "all" ]; then
+        typeset f $file_type
+        for f in $BASH_IT/$subdirectory/available/*.bash
         do
             plugin=$(basename $f)
-            if [ -e $BASH_IT/plugins/enabled/$plugin ]; then
-                rm $BASH_IT/plugins/enabled/$(basename $plugin)
+            if [ -e $BASH_IT/$subdirectory/enabled/$plugin ]; then
+                rm $BASH_IT/$subdirectory/enabled/$(basename $plugin)
             fi
         done
     else
-        typeset plugin=$(command ls $BASH_IT/plugins/enabled/$1.*bash 2>/dev/null | head -1)
-        if [ ! -h $plugin ]; then
-            printf '%s\n' 'sorry, that does not appear to be an enabled plugin.'
+        typeset plugin=$(command ls $BASH_IT/$subdirectory/enabled/$file_entity.*bash 2>/dev/null | head -1)
+        if [ -z "$plugin" ]; then
+            printf '%s\n' "sorry, that does not appear to be an enabled $file_type."
             return
         fi
-        rm $BASH_IT/plugins/enabled/$(basename $plugin)
+        rm $BASH_IT/$subdirectory/enabled/$(basename $plugin)
     fi
 
-    printf '%s\n' "$1 disabled."
+    printf '%s\n' "$file_entity disabled."
 }
 
-enable-plugin ()
+_enable-plugin ()
 {
-    about 'enables bash_it plugin'
-    param '1: plugin name'
-    example '$ enable-plugin rvm'
-    group 'lib'
+    _about 'enables bash_it plugin'
+    _param '1: plugin name'
+    _example '$ enable-plugin rvm'
+    _group 'lib'
 
-    if [ -z "$1" ]; then
-        reference enable-plugin
+    _enable-thing "plugins" "plugin" $1
+}
+
+_enable-alias ()
+{
+    _about 'enables bash_it alias'
+    _param '1: alias name'
+    _example '$ enable-alias git'
+    _group 'lib'
+
+    _enable-thing "aliases" "alias" $1
+}
+
+_enable-completion ()
+{
+    _about 'enables bash_it completion'
+    _param '1: completion name'
+    _example '$ enable-completion git'
+    _group 'lib'
+
+    _enable-thing "completion" "completion" $1
+}
+
+_enable-thing ()
+{
+    cite _about _param _example
+    _about 'enables a bash_it component'
+    _param '1: subdirectory'
+    _param '2: file_type'
+    _param '3: file_entity'
+    _example '$ _enable-thing "plugins" "plugin" "ssh"'	
+	
+    subdirectory="$1"
+    file_type="$2"
+    file_entity="$3"
+
+    if [ -z "$file_entity" ]; then
+        reference "enable-$file_type"
         return
     fi
 
-    if [ "$1" = "all" ]; then
-        typeset f plugin
-        for f in $BASH_IT/plugins/available/*.bash
+    if [ "$file_entity" = "all" ]; then
+        typeset f $file_type
+        for f in $BASH_IT/$subdirectory/available/*.bash
         do
             plugin=$(basename $f)
-            if [ ! -h $BASH_IT/plugins/enabled/$plugin ]; then
-                ln -s $BASH_IT/plugins/available/$plugin $BASH_IT/plugins/enabled/$plugin
+            if [ ! -h $BASH_IT/$subdirectory/enabled/$plugin ]; then
+                ln -s $BASH_IT/$subdirectory/available/$plugin $BASH_IT/$subdirectory/enabled/$plugin
             fi
         done
     else
-        typeset plugin=$(command ls $BASH_IT/plugins/available/$1.*bash 2>/dev/null | head -1)
+        typeset plugin=$(command ls $BASH_IT/$subdirectory/available/$file_entity.*bash 2>/dev/null | head -1)
         if [ -z "$plugin" ]; then
-            printf '%s\n' 'sorry, that does not appear to be an available plugin.'
+            printf '%s\n' "sorry, that does not appear to be an available $file_type."
             return
         fi
 
         plugin=$(basename $plugin)
-        if [ -e $BASH_IT/plugins/enabled/$plugin ]; then
-            printf '%s\n' "$1 is already enabled."
+        if [ -e $BASH_IT/$subdirectory/enabled/$plugin ]; then
+            printf '%s\n' "$file_entity is already enabled."
             return
         fi
 
-        ln -s $BASH_IT/plugins/available/$plugin $BASH_IT/plugins/enabled/$plugin
+        ln -s $BASH_IT/$subdirectory/available/$plugin $BASH_IT/$subdirectory/enabled/$plugin
     fi
 
-    printf '%s\n' "$1 enabled."
+    printf '%s\n' "$file_entity enabled."
 }
 
-plugins-help ()
+_help-aliases()
 {
-    about 'summarize all functions defined by enabled bash-it plugins'
-    group 'lib'
+    _about 'shows help for all aliases, or a specific alias group'
+    _param '1: optional alias group'
+    _example '$ alias-help'
+    _example '$ alias-help git'
+
+    if [ -n "$1" ]; then
+        cat $BASH_IT/aliases/available/$1.aliases.bash | metafor alias | sed "s/$/'/"
+    else
+        typeset f
+        for f in $BASH_IT/aliases/enabled/*
+        do
+            typeset file=$(basename $f)
+            printf '\n\n%s:\n' "${file%%.*}"
+            # metafor() strips trailing quotes, restore them with sed..
+            cat $f | metafor alias | sed "s/$/'/"
+        done
+    fi
+}
+
+_help-plugins()
+{
+    _about 'summarize all functions defined by enabled bash-it plugins'
+    _group 'lib'
 
     # display a brief progress message...
     printf '%s' 'please wait, building help...'
diff --git a/plugins/available/base.plugin.bash b/plugins/available/base.plugin.bash
index 98d1fb4..e07401b 100644
--- a/plugins/available/base.plugin.bash
+++ b/plugins/available/base.plugin.bash
@@ -151,18 +151,20 @@
     fi
 }
 
-t ()
-{
-    about 'one thing todo'
-    param 'if not set, display todo item'
-    param '1: todo text'
-    group 'base'
-	if [[ "$*" == "" ]] ; then
-	    cat ~/.t
-	else
-	    echo "$*" > ~/.t
-	fi
-}
+if [ ! -e $BASH_IT/plugins/enabled/todo.plugin.bash ]; then
+# if user has installed todo plugin, skip this...
+    t ()
+    {
+        about 'one thing todo'
+        param 'if not set, display todo item'
+        param '1: todo text'
+        if [[ "$*" == "" ]] ; then
+            cat ~/.t
+        else
+            echo "$*" > ~/.t
+        fi
+    }
+fi
 
 command_exists ()
 {
diff --git a/plugins/available/todo.plugin.bash b/plugins/available/todo.plugin.bash
new file mode 100755
index 0000000..28559de
--- /dev/null
+++ b/plugins/available/todo.plugin.bash
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# you may override any of the exported variables below in your .bash_profile
+
+if [ -z "$TODO_DIR" ]; then
+    export TODO_DIR=$BASH_IT/custom  # store todo items in user's custom dir, ignored by git
+fi
+if [ -z "$TODOTXT_DEFAULT_ACTION" ]; then
+    export TODOTXT_DEFAULT_ACTION=ls       # typing 't' by itself will list current todos
+fi
+if [ -z "$TODO_SRC_DIR" ]; then
+    export TODO_SRC_DIR=$BASH_IT/plugins/available/todo
+fi
+
+# respect ENV var set in .bash_profile, default is 't'
+alias $TODO='$TODO_SRC_DIR/todo.sh -d $TODO_SRC_DIR/todo.cfg'
+
+export PATH=$PATH:$TODO_SRC_DIR
+source $TODO_SRC_DIR/todo_completion   # bash completion for todo.sh
+complete -F _todo $TODO                # enable completion for 't' alias
diff --git a/plugins/available/todo/todo.cfg b/plugins/available/todo/todo.cfg
new file mode 100644
index 0000000..d79bf77
--- /dev/null
+++ b/plugins/available/todo/todo.cfg
@@ -0,0 +1,75 @@
+# === EDIT FILE LOCATIONS BELOW ===
+
+# Your todo/done/report.txt locations
+export TODO_FILE="$TODO_DIR/todo.txt"
+export DONE_FILE="$TODO_DIR/done.txt"
+export REPORT_FILE="$TODO_DIR/report.txt"
+
+# You can customize your actions directory location
+#export TODO_ACTIONS_DIR="$HOME/.todo.actions.d"
+
+# == EDIT FILE LOCATIONS ABOVE ===
+
+# === COLOR MAP ===
+
+## Text coloring and formatting is done by inserting ANSI escape codes.
+## If you have re-mapped your color codes, or use the todo.txt
+## output in another output system (like Conky), you may need to
+## over-ride by uncommenting and editing these defaults.
+## If you change any of these here, you also need to uncomment
+## the defaults in the COLORS section below. Otherwise, todo.txt
+## will still use the defaults!
+
+# export BLACK='\\033[0;30m'
+# export RED='\\033[0;31m'
+# export GREEN='\\033[0;32m'
+# export BROWN='\\033[0;33m'
+# export BLUE='\\033[0;34m'
+# export PURPLE='\\033[0;35m'
+# export CYAN='\\033[0;36m'
+# export LIGHT_GREY='\\033[0;37m'
+# export DARK_GREY='\\033[1;30m'
+# export LIGHT_RED='\\033[1;31m'
+# export LIGHT_GREEN='\\033[1;32m'
+# export YELLOW='\\033[1;33m'
+# export LIGHT_BLUE='\\033[1;34m'
+# export LIGHT_PURPLE='\\033[1;35m'
+# export LIGHT_CYAN='\\033[1;36m'
+# export WHITE='\\033[1;37m'
+# export DEFAULT='\\033[0m'
+
+# === COLORS ===
+
+## Uncomment and edit to override these defaults.
+## Reference the constants from the color map above,
+## or use $NONE to disable highlighting.
+#
+# Priorities can be any upper-case letter.
+# A,B,C are highlighted; you can add coloring for more.
+#
+# export PRI_A=$YELLOW        # color for A priority
+# export PRI_B=$GREEN         # color for B priority
+# export PRI_C=$LIGHT_BLUE    # color for C priority
+# export PRI_D=...            # define your own
+# export PRI_X=$WHITE         # color unless explicitly defined
+
+# There is highlighting for tasks that have been done,
+# but haven't been archived yet.
+#
+# export COLOR_DONE=$LIGHT_GREY
+
+# === BEHAVIOR ===
+
+## customize list output
+#
+# TODOTXT_SORT_COMMAND will filter after line numbers are
+# inserted, but before colorization, and before hiding of
+# priority, context, and project.
+#
+# export TODOTXT_SORT_COMMAND='env LC_COLLATE=C sort -f -k2'
+
+# TODOTXT_FINAL_FILTER will filter list output after colorization,
+# priority hiding, context hiding, and project hiding. That is,
+# just before the list output is displayed.
+#
+# export TODOTXT_FINAL_FILTER='cat'
diff --git a/plugins/available/todo/todo.sh b/plugins/available/todo/todo.sh
new file mode 100755
index 0000000..59f88a9
--- /dev/null
+++ b/plugins/available/todo/todo.sh
@@ -0,0 +1,1321 @@
+#! /bin/bash
+
+# === HEAVY LIFTING ===
+shopt -s extglob extquote
+
+# NOTE:  Todo.sh requires the .todo/config configuration file to run.
+# Place the .todo/config file in your home directory or use the -d option for a custom location.
+
+[ -f VERSION-FILE ] && . VERSION-FILE || VERSION="2.9"
+version() {
+    cat <<-EndVersion
+		TODO.TXT Command Line Interface v$VERSION
+
+		First release: 5/11/2006
+		Original conception by: Gina Trapani (http://ginatrapani.org)
+		Contributors: http://github.com/ginatrapani/todo.txt-cli/network
+		License: GPL, http://www.gnu.org/copyleft/gpl.html
+		More information and mailing list at http://todotxt.com
+		Code repository: http://github.com/ginatrapani/todo.txt-cli/tree/master
+	EndVersion
+    exit 1
+}
+
+# Set script name and full path early.
+TODO_SH=$(basename "$0")
+TODO_FULL_SH="$0"
+export TODO_SH TODO_FULL_SH
+
+oneline_usage="$TODO_SH [-fhpantvV] [-d todo_config] action [task_number] [task_description]"
+
+usage()
+{
+    cat <<-EndUsage
+		Usage: $oneline_usage
+		Try '$TODO_SH -h' for more information.
+	EndUsage
+    exit 1
+}
+
+shorthelp()
+{
+    cat <<-EndHelp
+		  Usage: $oneline_usage
+
+		  Actions:
+		    add|a "THING I NEED TO DO +project @context"
+		    addm "THINGS I NEED TO DO
+		          MORE THINGS I NEED TO DO"
+		    addto DEST "TEXT TO ADD"
+		    append|app ITEM# "TEXT TO APPEND"
+		    archive
+		    command [ACTIONS]
+		    deduplicate
+		    del|rm ITEM# [TERM]
+		    depri|dp ITEM#[, ITEM#, ITEM#, ...]
+		    do ITEM#[, ITEM#, ITEM#, ...]
+		    help
+		    list|ls [TERM...]
+		    listall|lsa [TERM...]
+		    listaddons
+		    listcon|lsc
+		    listfile|lf [SRC [TERM...]]
+		    listpri|lsp [PRIORITIES] [TERM...]
+		    listproj|lsprj [TERM...]
+		    move|mv ITEM# DEST [SRC]
+		    prepend|prep ITEM# "TEXT TO PREPEND"
+		    pri|p ITEM# PRIORITY
+		    replace ITEM# "UPDATED TODO"
+		    report
+		    shorthelp
+
+		  Actions can be added and overridden using scripts in the actions
+		  directory.
+	EndHelp
+
+    # Only list the one-line usage from the add-on actions. This assumes that
+    # add-ons use the same usage indentation structure as todo.sh.
+    addonHelp | grep -e '^  Add-on Actions:' -e '^    [[:alpha:]]'
+
+    cat <<-EndHelpFooter
+
+		  See "help" for more details.
+	EndHelpFooter
+    exit 0
+}
+
+help()
+{
+    cat <<-EndOptionsHelp
+		  Usage: $oneline_usage
+
+		  Options:
+		    -@
+		        Hide context names in list output.  Use twice to show context
+		        names (default).
+		    -+
+		        Hide project names in list output.  Use twice to show project
+		        names (default).
+		    -c
+		        Color mode
+		    -d CONFIG_FILE
+		        Use a configuration file other than the default ~/.todo/config
+		    -f
+		        Forces actions without confirmation or interactive input
+		    -h
+		        Display a short help message; same as action "shorthelp"
+		    -p
+		        Plain mode turns off colors
+		    -P
+		        Hide priority labels in list output.  Use twice to show
+		        priority labels (default).
+		    -a
+		        Don't auto-archive tasks automatically on completion
+		    -A
+		        Auto-archive tasks automatically on completion
+		    -n
+		        Don't preserve line numbers; automatically remove blank lines
+		        on task deletion
+		    -N
+		        Preserve line numbers
+		    -t
+		        Prepend the current date to a task automatically
+		        when it's added.
+		    -T
+		        Do not prepend the current date to a task automatically
+		        when it's added.
+		    -v
+		        Verbose mode turns on confirmation messages
+		    -vv
+		        Extra verbose mode prints some debugging information and
+		        additional help text
+		    -V
+		        Displays version, license and credits
+		    -x
+		        Disables TODOTXT_FINAL_FILTER
+
+
+	EndOptionsHelp
+
+    [ $TODOTXT_VERBOSE -gt 1 ] && cat <<-'EndVerboseHelp'
+		  Environment variables:
+		    TODOTXT_AUTO_ARCHIVE            is same as option -a (0)/-A (1)
+		    TODOTXT_CFG_FILE=CONFIG_FILE    is same as option -d CONFIG_FILE
+		    TODOTXT_FORCE=1                 is same as option -f
+		    TODOTXT_PRESERVE_LINE_NUMBERS   is same as option -n (0)/-N (1)
+		    TODOTXT_PLAIN                   is same as option -p (1)/-c (0)
+		    TODOTXT_DATE_ON_ADD             is same as option -t (1)/-T (0)
+		    TODOTXT_VERBOSE=1               is same as option -v
+		    TODOTXT_DISABLE_FILTER=1        is same as option -x
+		    TODOTXT_DEFAULT_ACTION=""       run this when called with no arguments
+		    TODOTXT_SORT_COMMAND="sort ..." customize list output
+		    TODOTXT_FINAL_FILTER="sed ..."  customize list after color, P@+ hiding
+		    TODOTXT_SOURCEVAR=\$DONE_FILE   use another source for listcon, listproj
+
+
+	EndVerboseHelp
+    cat <<-EndActionsHelp
+		  Built-in Actions:
+		    add "THING I NEED TO DO +project @context"
+		    a "THING I NEED TO DO +project @context"
+		      Adds THING I NEED TO DO to your todo.txt file on its own line.
+		      Project and context notation optional.
+		      Quotes optional.
+
+		    addm "FIRST THING I NEED TO DO +project1 @context
+		    SECOND THING I NEED TO DO +project2 @context"
+		      Adds FIRST THING I NEED TO DO to your todo.txt on its own line and
+		      Adds SECOND THING I NEED TO DO to you todo.txt on its own line.
+		      Project and context notation optional.
+
+		    addto DEST "TEXT TO ADD"
+		      Adds a line of text to any file located in the todo.txt directory.
+		      For example, addto inbox.txt "decide about vacation"
+
+		    append ITEM# "TEXT TO APPEND"
+		    app ITEM# "TEXT TO APPEND"
+		      Adds TEXT TO APPEND to the end of the task on line ITEM#.
+		      Quotes optional.
+
+		    archive
+		      Moves all done tasks from todo.txt to done.txt and removes blank lines.
+
+		    command [ACTIONS]
+		      Runs the remaining arguments using only todo.sh builtins.
+		      Will not call any .todo.actions.d scripts.
+
+		    deduplicate
+		      Removes duplicate lines from todo.txt.
+
+		    del ITEM# [TERM]
+		    rm ITEM# [TERM]
+		      Deletes the task on line ITEM# in todo.txt.
+		      If TERM specified, deletes only TERM from the task.
+
+		    depri ITEM#[, ITEM#, ITEM#, ...]
+		    dp ITEM#[, ITEM#, ITEM#, ...]
+		      Deprioritizes (removes the priority) from the task(s)
+		      on line ITEM# in todo.txt.
+
+		    do ITEM#[, ITEM#, ITEM#, ...]
+		      Marks task(s) on line ITEM# as done in todo.txt.
+
+		    help
+		      Display this help message.
+
+		    list [TERM...]
+		    ls [TERM...]
+		      Displays all tasks that contain TERM(s) sorted by priority with line
+		      numbers.  Each task must match all TERM(s) (logical AND); to display
+		      tasks that contain any TERM (logical OR), use
+		      "TERM1\|TERM2\|..." (with quotes), or TERM1\\\|TERM2 (unquoted).
+		      Hides all tasks that contain TERM(s) preceded by a
+		      minus sign (i.e. -TERM). If no TERM specified, lists entire todo.txt.
+
+		    listall [TERM...]
+		    lsa [TERM...]
+		      Displays all the lines in todo.txt AND done.txt that contain TERM(s)
+		      sorted by priority with line  numbers.  Hides all tasks that
+		      contain TERM(s) preceded by a minus sign (i.e. -TERM).  If no
+		      TERM specified, lists entire todo.txt AND done.txt
+		      concatenated and sorted.
+
+		    listaddons
+		      Lists all added and overridden actions in the actions directory.
+
+		    listcon
+		    lsc
+		      Lists all the task contexts that start with the @ sign in todo.txt.
+
+		    listfile [SRC [TERM...]]
+		    lf [SRC [TERM...]]
+		      Displays all the lines in SRC file located in the todo.txt directory,
+		      sorted by priority with line  numbers.  If TERM specified, lists
+		      all lines that contain TERM(s) in SRC file.  Hides all tasks that
+		      contain TERM(s) preceded by a minus sign (i.e. -TERM).  
+		      Without any arguments, the names of all text files in the todo.txt
+		      directory are listed.
+		
+		    listpri [PRIORITIES] [TERM...]
+		    lsp [PRIORITIES] [TERM...]
+		      Displays all tasks prioritized PRIORITIES.
+		      PRIORITIES can be a single one (A) or a range (A-C).
+		      If no PRIORITIES specified, lists all prioritized tasks.
+		      If TERM specified, lists only prioritized tasks that contain TERM(s).
+		      Hides all tasks that contain TERM(s) preceded by a minus sign
+		      (i.e. -TERM).  
+
+		    listproj
+		    lsprj
+		      Lists all the projects (terms that start with a + sign) in
+		      todo.txt.
+
+		    move ITEM# DEST [SRC]
+		    mv ITEM# DEST [SRC]
+		      Moves a line from source text file (SRC) to destination text file (DEST).
+		      Both source and destination file must be located in the directory defined
+		      in the configuration directory.  When SRC is not defined
+		      it's by default todo.txt.
+
+		    prepend ITEM# "TEXT TO PREPEND"
+		    prep ITEM# "TEXT TO PREPEND"
+		      Adds TEXT TO PREPEND to the beginning of the task on line ITEM#.
+		      Quotes optional.
+
+		    pri ITEM# PRIORITY
+		    p ITEM# PRIORITY
+		      Adds PRIORITY to task on line ITEM#.  If the task is already
+		      prioritized, replaces current priority with new PRIORITY.
+		      PRIORITY must be a letter between A and Z.
+
+		    replace ITEM# "UPDATED TODO"
+		      Replaces task on line ITEM# with UPDATED TODO.
+
+		    report
+		      Adds the number of open tasks and done tasks to report.txt.
+
+		    shorthelp
+		      List the one-line usage of all built-in and add-on actions.
+
+	EndActionsHelp
+
+        addonHelp
+    exit 1
+}
+
+addonHelp()
+{
+    if [ -d "$TODO_ACTIONS_DIR" ]; then
+        didPrintAddonActionsHeader=
+        for action in "$TODO_ACTIONS_DIR"/*
+        do
+            if [ -f "$action" -a -x "$action" ]; then
+                if [ ! "$didPrintAddonActionsHeader" ]; then
+                    cat <<-EndAddonActionsHeader
+		  Add-on Actions:
+	EndAddonActionsHeader
+                    didPrintAddonActionsHeader=1
+                fi
+                "$action" usage
+            fi
+        done
+    fi
+}
+
+die()
+{
+    echo "$*"
+    exit 1
+}
+
+cleaninput()
+{
+    # Parameters:    When $1 = "for sed", performs additional escaping for use
+    #                in sed substitution with "|" separators.
+    # Precondition:  $input contains text to be cleaned.
+    # Postcondition: Modifies $input.
+
+    # Replace CR and LF with space; tasks always comprise a single line.
+    input=${input//$'\r'/ }
+    input=${input//$'\n'/ }
+
+    if [ "$1" = "for sed" ]; then
+        # This action uses sed with "|" as the substitution separator, and & as
+        # the matched string; these must be escaped.
+        # Backslashes must be escaped, too, and before the other stuff.
+        input=${input//\\/\\\\}
+        input=${input//|/\\|}
+        input=${input//&/\\&}
+    fi
+}
+
+getPrefix()
+{
+    # Parameters:    $1: todo file; empty means $TODO_FILE.
+    # Returns:       Uppercase FILE prefix to be used in place of "TODO:" where
+    #                a different todo file can be specified.
+    local base=$(basename "${1:-$TODO_FILE}")
+    echo "${base%%.[^.]*}" | tr 'a-z' 'A-Z'
+}
+
+getTodo()
+{
+    # Parameters:    $1: task number
+    #                $2: Optional todo file
+    # Precondition:  $errmsg contains usage message.
+    # Postcondition: $todo contains task text.
+
+    local item=$1
+    [ -z "$item" ] && die "$errmsg"
+    [ "${item//[0-9]/}" ] && die "$errmsg"
+
+    todo=$(sed "$item!d" "${2:-$TODO_FILE}")
+    [ -z "$todo" ] && die "$(getPrefix "$2"): No task $item."
+}
+getNewtodo()
+{
+    # Parameters:    $1: task number
+    #                $2: Optional todo file
+    # Precondition:  None.
+    # Postcondition: $newtodo contains task text.
+
+    local item=$1
+    [ -z "$item" ] && die 'Programming error: $item should exist.'
+    [ "${item//[0-9]/}" ] && die 'Programming error: $item should be numeric.'
+
+    newtodo=$(sed "$item!d" "${2:-$TODO_FILE}")
+    [ -z "$newtodo" ] && die "$(getPrefix "$2"): No updated task $item."
+}
+
+replaceOrPrepend()
+{
+  action=$1; shift
+  case "$action" in
+    replace)
+      backref=
+      querytext="Replacement: "
+      ;;
+    prepend)
+      backref=' &'
+      querytext="Prepend: "
+      ;;
+  esac
+  shift; item=$1; shift
+  getTodo "$item"
+
+  if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
+    echo -n "$querytext"
+    read input
+  else
+    input=$*
+  fi
+  cleaninput "for sed"
+
+  # Retrieve existing priority and prepended date
+  priority=$(sed -e "$item!d" -e $item's/^\((.) \)\{0,1\}\([0-9]\{2,4\}-[0-9]\{2\}-[0-9]\{2\} \)\{0,1\}.*/\1/' "$TODO_FILE")
+  prepdate=$(sed -e "$item!d" -e $item's/^\((.) \)\{0,1\}\([0-9]\{2,4\}-[0-9]\{2\}-[0-9]\{2\} \)\{0,1\}.*/\2/' "$TODO_FILE")
+
+  if [ "$prepdate" -a "$action" = "replace" ] && [ "$(echo "$input"|sed -e 's/^\([0-9]\{2,4\}-[0-9]\{2\}-[0-9]\{2\}\)\{0,1\}.*/\1/')" ]; then
+    # If the replaced text starts with a date, it will replace the existing
+    # date, too.
+    prepdate=
+  fi
+
+  # Temporarily remove any existing priority and prepended date, perform the
+  # change (replace/prepend) and re-insert the existing priority and prepended
+  # date again.
+  sed -i.bak -e "$item s/^${priority}${prepdate}//" -e "$item s|^.*|${priority}${prepdate}${input}${backref}|" "$TODO_FILE"
+  if [ $TODOTXT_VERBOSE -gt 0 ]; then
+    getNewtodo "$item"
+    case "$action" in
+      replace)
+        echo "$item $todo"
+        echo "TODO: Replaced task with:"
+        echo "$item $newtodo"
+        ;;
+      prepend)
+        echo "$item $newtodo"
+        ;;
+    esac
+  fi
+}
+
+#Preserving environment variables so they don't get clobbered by the config file
+OVR_TODOTXT_AUTO_ARCHIVE="$TODOTXT_AUTO_ARCHIVE"
+OVR_TODOTXT_FORCE="$TODOTXT_FORCE"
+OVR_TODOTXT_PRESERVE_LINE_NUMBERS="$TODOTXT_PRESERVE_LINE_NUMBERS"
+OVR_TODOTXT_PLAIN="$TODOTXT_PLAIN"
+OVR_TODOTXT_DATE_ON_ADD="$TODOTXT_DATE_ON_ADD"
+OVR_TODOTXT_DISABLE_FILTER="$TODOTXT_DISABLE_FILTER"
+OVR_TODOTXT_VERBOSE="$TODOTXT_VERBOSE"
+OVR_TODOTXT_DEFAULT_ACTION="$TODOTXT_DEFAULT_ACTION"
+OVR_TODOTXT_SORT_COMMAND="$TODOTXT_SORT_COMMAND"
+OVR_TODOTXT_FINAL_FILTER="$TODOTXT_FINAL_FILTER"
+
+# == PROCESS OPTIONS ==
+while getopts ":fhpcnNaAtTvVx+@Pd:" Option
+do
+  case $Option in
+    '@' )
+        ## HIDE_CONTEXT_NAMES starts at zero (false); increment it to one
+        ##   (true) the first time this flag is seen. Each time the flag
+        ##   is seen after that, increment it again so that an even
+        ##   number shows context names and an odd number hides context
+        ##   names.
+        : $(( HIDE_CONTEXT_NAMES++ ))
+        if [ $(( $HIDE_CONTEXT_NAMES % 2 )) -eq 0 ]
+        then
+            ## Zero or even value -- show context names
+            unset HIDE_CONTEXTS_SUBSTITUTION
+        else
+            ## One or odd value -- hide context names
+            export HIDE_CONTEXTS_SUBSTITUTION='[[:space:]]@[[:graph:]]\{1,\}'
+        fi
+        ;;
+    '+' )
+        ## HIDE_PROJECT_NAMES starts at zero (false); increment it to one
+        ##   (true) the first time this flag is seen. Each time the flag
+        ##   is seen after that, increment it again so that an even
+        ##   number shows project names and an odd number hides project
+        ##   names.
+        : $(( HIDE_PROJECT_NAMES++ ))
+        if [ $(( $HIDE_PROJECT_NAMES % 2 )) -eq 0 ]
+        then
+            ## Zero or even value -- show project names
+            unset HIDE_PROJECTS_SUBSTITUTION
+        else
+            ## One or odd value -- hide project names
+            export HIDE_PROJECTS_SUBSTITUTION='[[:space:]][+][[:graph:]]\{1,\}'
+        fi
+        ;;
+    a )
+        OVR_TODOTXT_AUTO_ARCHIVE=0
+        ;;
+    A )
+        OVR_TODOTXT_AUTO_ARCHIVE=1
+        ;;
+    c )
+        OVR_TODOTXT_PLAIN=0
+        ;;
+    d )
+        TODOTXT_CFG_FILE=$OPTARG
+        ;;
+    f )
+        OVR_TODOTXT_FORCE=1
+        ;;
+    h )
+        # Short-circuit option parsing and forward to the action.
+        # Cannot just invoke shorthelp() because we need the configuration
+        # processed to locate the add-on actions directory.
+        set -- '-h' 'shorthelp'
+        ;;
+    n )
+        OVR_TODOTXT_PRESERVE_LINE_NUMBERS=0
+        ;;
+    N )
+        OVR_TODOTXT_PRESERVE_LINE_NUMBERS=1
+        ;;
+    p )
+        OVR_TODOTXT_PLAIN=1
+        ;;
+    P )
+        ## HIDE_PRIORITY_LABELS starts at zero (false); increment it to one
+        ##   (true) the first time this flag is seen. Each time the flag
+        ##   is seen after that, increment it again so that an even
+        ##   number shows priority labels and an odd number hides priority
+        ##   labels.
+        : $(( HIDE_PRIORITY_LABELS++ ))
+        if [ $(( $HIDE_PRIORITY_LABELS % 2 )) -eq 0 ]
+        then
+            ## Zero or even value -- show priority labels
+            unset HIDE_PRIORITY_SUBSTITUTION
+        else
+            ## One or odd value -- hide priority labels
+            export HIDE_PRIORITY_SUBSTITUTION="([A-Z])[[:space:]]"
+        fi
+        ;;
+    t )
+        OVR_TODOTXT_DATE_ON_ADD=1
+        ;;
+    T )
+        OVR_TODOTXT_DATE_ON_ADD=0
+        ;;
+    v )
+        : $(( TODOTXT_VERBOSE++ ))
+        ;;
+    V )
+        version
+        ;;
+    x )
+        OVR_TODOTXT_DISABLE_FILTER=1
+        ;;
+  esac
+done
+shift $(($OPTIND - 1))
+
+# defaults if not yet defined
+TODOTXT_VERBOSE=${TODOTXT_VERBOSE:-1}
+TODOTXT_PLAIN=${TODOTXT_PLAIN:-0}
+TODOTXT_CFG_FILE=${TODOTXT_CFG_FILE:-$HOME/.todo/config}
+TODOTXT_FORCE=${TODOTXT_FORCE:-0}
+TODOTXT_PRESERVE_LINE_NUMBERS=${TODOTXT_PRESERVE_LINE_NUMBERS:-1}
+TODOTXT_AUTO_ARCHIVE=${TODOTXT_AUTO_ARCHIVE:-1}
+TODOTXT_DATE_ON_ADD=${TODOTXT_DATE_ON_ADD:-0}
+TODOTXT_DEFAULT_ACTION=${TODOTXT_DEFAULT_ACTION:-}
+TODOTXT_SORT_COMMAND=${TODOTXT_SORT_COMMAND:-env LC_COLLATE=C sort -f -k2}
+TODOTXT_DISABLE_FILTER=${TODOTXT_DISABLE_FILTER:-}
+TODOTXT_FINAL_FILTER=${TODOTXT_FINAL_FILTER:-cat}
+
+# Export all TODOTXT_* variables
+export ${!TODOTXT_@}
+
+# Default color map
+export NONE=''
+export BLACK='\\033[0;30m'
+export RED='\\033[0;31m'
+export GREEN='\\033[0;32m'
+export BROWN='\\033[0;33m'
+export BLUE='\\033[0;34m'
+export PURPLE='\\033[0;35m'
+export CYAN='\\033[0;36m'
+export LIGHT_GREY='\\033[0;37m'
+export DARK_GREY='\\033[1;30m'
+export LIGHT_RED='\\033[1;31m'
+export LIGHT_GREEN='\\033[1;32m'
+export YELLOW='\\033[1;33m'
+export LIGHT_BLUE='\\033[1;34m'
+export LIGHT_PURPLE='\\033[1;35m'
+export LIGHT_CYAN='\\033[1;36m'
+export WHITE='\\033[1;37m'
+export DEFAULT='\\033[0m'
+
+# Default priority->color map.
+export PRI_A=$YELLOW        # color for A priority
+export PRI_B=$GREEN         # color for B priority
+export PRI_C=$LIGHT_BLUE    # color for C priority
+export PRI_X=$WHITE         # color unless explicitly defined
+
+# Default highlight colors.
+export COLOR_DONE=$LIGHT_GREY   # color for done (but not yet archived) tasks
+
+# Default sentence delimiters for todo.sh append.
+# If the text to be appended to the task begins with one of these characters, no
+# whitespace is inserted in between. This makes appending to an enumeration
+# (todo.sh add 42 ", foo") syntactically correct.
+export SENTENCE_DELIMITERS=',.:;'
+
+[ -e "$TODOTXT_CFG_FILE" ] || {
+    CFG_FILE_ALT="$HOME/todo.cfg"
+
+    if [ -e "$CFG_FILE_ALT" ]
+    then
+        TODOTXT_CFG_FILE="$CFG_FILE_ALT"
+    fi
+}
+
+[ -e "$TODOTXT_CFG_FILE" ] || {
+    CFG_FILE_ALT="$HOME/.todo.cfg"
+
+    if [ -e "$CFG_FILE_ALT" ]
+    then
+        TODOTXT_CFG_FILE="$CFG_FILE_ALT"
+    fi
+}
+
+[ -e "$TODOTXT_CFG_FILE" ] || {
+    CFG_FILE_ALT=$(dirname "$0")"/todo.cfg"
+
+    if [ -e "$CFG_FILE_ALT" ]
+    then
+        TODOTXT_CFG_FILE="$CFG_FILE_ALT"
+    fi
+}
+
+
+if [ -z "$TODO_ACTIONS_DIR" -o ! -d "$TODO_ACTIONS_DIR" ]
+then
+    TODO_ACTIONS_DIR="$HOME/.todo/actions"
+    export TODO_ACTIONS_DIR
+fi
+
+[ -d "$TODO_ACTIONS_DIR" ] || {
+    TODO_ACTIONS_DIR_ALT="$HOME/.todo.actions.d"
+
+    if [ -d "$TODO_ACTIONS_DIR_ALT" ]
+    then
+        TODO_ACTIONS_DIR="$TODO_ACTIONS_DIR_ALT"
+    fi
+}
+
+# === SANITY CHECKS (thanks Karl!) ===
+[ -r "$TODOTXT_CFG_FILE" ] || die "Fatal Error: Cannot read configuration file $TODOTXT_CFG_FILE"
+
+. "$TODOTXT_CFG_FILE"
+
+# === APPLY OVERRIDES
+if [ -n "$OVR_TODOTXT_AUTO_ARCHIVE" ] ; then
+  TODOTXT_AUTO_ARCHIVE="$OVR_TODOTXT_AUTO_ARCHIVE"
+fi
+if [ -n "$OVR_TODOTXT_FORCE" ] ; then
+  TODOTXT_FORCE="$OVR_TODOTXT_FORCE"
+fi
+if [ -n "$OVR_TODOTXT_PRESERVE_LINE_NUMBERS" ] ; then
+  TODOTXT_PRESERVE_LINE_NUMBERS="$OVR_TODOTXT_PRESERVE_LINE_NUMBERS"
+fi
+if [ -n "$OVR_TODOTXT_PLAIN" ] ; then
+  TODOTXT_PLAIN="$OVR_TODOTXT_PLAIN"
+fi
+if [ -n "$OVR_TODOTXT_DATE_ON_ADD" ] ; then
+  TODOTXT_DATE_ON_ADD="$OVR_TODOTXT_DATE_ON_ADD"
+fi
+if [ -n "$OVR_TODOTXT_DISABLE_FILTER" ] ; then
+  TODOTXT_DISABLE_FILTER="$OVR_TODOTXT_DISABLE_FILTER"
+fi
+if [ -n "$OVR_TODOTXT_VERBOSE" ] ; then
+  TODOTXT_VERBOSE="$OVR_TODOTXT_VERBOSE"
+fi
+if [ -n "$OVR_TODOTXT_DEFAULT_ACTION" ] ; then
+  TODOTXT_DEFAULT_ACTION="$OVR_TODOTXT_DEFAULT_ACTION"
+fi
+if [ -n "$OVR_TODOTXT_SORT_COMMAND" ] ; then
+  TODOTXT_SORT_COMMAND="$OVR_TODOTXT_SORT_COMMAND"
+fi
+if [ -n "$OVR_TODOTXT_FINAL_FILTER" ] ; then
+  TODOTXT_FINAL_FILTER="$OVR_TODOTXT_FINAL_FILTER"
+fi
+
+ACTION=${1:-$TODOTXT_DEFAULT_ACTION}
+
+[ -z "$ACTION" ]    && usage
+[ -d "$TODO_DIR" ]  || die "Fatal Error: $TODO_DIR is not a directory"
+( cd "$TODO_DIR" )  || die "Fatal Error: Unable to cd to $TODO_DIR"
+
+[ -f "$TODO_FILE" ] || cp /dev/null "$TODO_FILE"
+[ -f "$DONE_FILE" ] || cp /dev/null "$DONE_FILE"
+[ -f "$REPORT_FILE" ] || cp /dev/null "$REPORT_FILE"
+
+if [ $TODOTXT_PLAIN = 1 ]; then
+    for clr in ${!PRI_@}; do
+        export $clr=$NONE
+    done
+    PRI_X=$NONE
+    DEFAULT=$NONE
+    COLOR_DONE=$NONE
+fi
+
+_addto() {
+    file="$1"
+    input="$2"
+    cleaninput
+
+    if [[ $TODOTXT_DATE_ON_ADD = 1 ]]; then
+        now=$(date '+%Y-%m-%d')
+        input=$(echo "$input" | sed -e 's/^\(([A-Z]) \)\{0,1\}/\1'"$now /")
+    fi
+    echo "$input" >> "$file"
+    if [ $TODOTXT_VERBOSE -gt 0 ]; then
+        TASKNUM=$(sed -n '$ =' "$file")
+        echo "$TASKNUM $input"
+        echo "$(getPrefix "$file"): $TASKNUM added."
+    fi
+}
+
+shellquote()
+{
+    typeset -r qq=\'; printf %s\\n "'${1//\'/${qq}\\${qq}${qq}}'";
+}
+
+filtercommand()
+{
+    filter=${1:-}
+    shift
+    post_filter=${1:-}
+    shift
+
+    for search_term
+    do
+        ## See if the first character of $search_term is a dash
+        if [ "${search_term:0:1}" != '-' ]
+        then
+            ## First character isn't a dash: hide lines that don't match
+            ## this $search_term
+            filter="${filter:-}${filter:+ | }grep -i $(shellquote "$search_term")"
+        else
+            ## First character is a dash: hide lines that match this
+            ## $search_term
+            #
+            ## Remove the first character (-) before adding to our filter command
+            filter="${filter:-}${filter:+ | }grep -v -i $(shellquote "${search_term:1}")"
+        fi
+    done
+
+    [ -n "$post_filter" ] && {
+        filter="${filter:-}${filter:+ | }${post_filter:-}"
+    }
+
+    printf %s "$filter"
+}
+
+_list() {
+    local FILE="$1"
+    ## If the file starts with a "/" use absolute path. Otherwise,
+    ## try to find it in either $TODO_DIR or using a relative path
+    if [ "${1:0:1}" == / ]; then
+        ## Absolute path
+        src="$FILE"
+    elif [ -f "$TODO_DIR/$FILE" ]; then
+        ## Path relative to todo.sh directory
+        src="$TODO_DIR/$FILE"
+    elif [ -f "$FILE" ]; then
+        ## Path relative to current working directory
+        src="$FILE"
+    elif [ -f "$TODO_DIR/${FILE}.txt" ]; then
+        ## Path relative to todo.sh directory, missing file extension
+        src="$TODO_DIR/${FILE}.txt"
+    else
+        die "TODO: File $FILE does not exist."
+    fi
+
+    ## Get our search arguments, if any
+    shift ## was file name, new $1 is first search term
+
+    _format "$src" '' "$@"
+
+    if [ $TODOTXT_VERBOSE -gt 0 ]; then
+        echo "--"
+        echo "$(getPrefix "$src"): ${NUMTASKS:-0} of ${TOTALTASKS:-0} tasks shown"
+    fi
+}
+getPadding()
+{
+    ## We need one level of padding for each power of 10 $LINES uses.
+    LINES=$(sed -n '$ =' "${1:-$TODO_FILE}")
+    printf %s ${#LINES}
+}
+_format()
+{
+    # Parameters:    $1: todo input file; when empty formats stdin
+    #                $2: ITEM# number width; if empty auto-detects from $1 / $TODO_FILE.
+    # Precondition:  None
+    # Postcondition: $NUMTASKS and $TOTALTASKS contain statistics (unless $TODOTXT_VERBOSE=0).
+
+    FILE=$1
+    shift
+
+    ## Figure out how much padding we need to use, unless this was passed to us.
+    PADDING=${1:-$(getPadding "$FILE")}
+    shift
+
+    ## Number the file, then run the filter command,
+    ## then sort and mangle output some more
+    if [[ $TODOTXT_DISABLE_FILTER = 1 ]]; then
+        TODOTXT_FINAL_FILTER="cat"
+    fi
+    items=$(
+        if [ "$FILE" ]; then
+            sed = "$FILE"
+        else
+            sed =
+        fi                                                      \
+        | sed -e '''
+            N
+            s/^/     /
+            s/ *\([ 0-9]\{'"$PADDING"',\}\)\n/\1 /
+            /^[ 0-9]\{1,\} *$/d
+         '''
+    )
+
+    ## Build and apply the filter.
+    filter_command=$(filtercommand "${pre_filter_command:-}" "${post_filter_command:-}" "$@")
+    if [ "${filter_command}" ]; then
+        filtered_items=$(echo -n "$items" | eval "${filter_command}")
+    else
+        filtered_items=$items
+    fi
+    filtered_items=$(
+        echo -n "$filtered_items"                              \
+        | sed '''
+            s/^     /00000/;
+            s/^    /0000/;
+            s/^   /000/;
+            s/^  /00/;
+            s/^ /0/;
+          ''' \
+        | eval ${TODOTXT_SORT_COMMAND}                                        \
+        | awk '''
+            function highlight(colorVar,      color) {
+                color = ENVIRON[colorVar]
+                gsub(/\\+033/, "\033", color)
+                return color
+            }
+            {
+                if (match($0, /^[0-9]+ x /)) {
+                    print highlight("COLOR_DONE") $0 highlight("DEFAULT")
+                } else if (match($0, /^[0-9]+ \([A-Z]\) /)) {
+                    clr = highlight("PRI_" substr($0, RSTART + RLENGTH - 3, 1))
+                    print \
+                        (clr ? clr : highlight("PRI_X")) \
+                        (ENVIRON["HIDE_PRIORITY_SUBSTITUTION"] == "" ? $0 : substr($0, 1, RLENGTH - 4) substr($0, RSTART + RLENGTH)) \
+                        highlight("DEFAULT")
+                } else { print }
+            }
+          '''  \
+        | sed '''
+            s/'"${HIDE_PROJECTS_SUBSTITUTION:-^}"'//g
+            s/'"${HIDE_CONTEXTS_SUBSTITUTION:-^}"'//g
+            s/'"${HIDE_CUSTOM_SUBSTITUTION:-^}"'//g
+          '''                                                   \
+        | eval ${TODOTXT_FINAL_FILTER}                          \
+    )
+    [ "$filtered_items" ] && echo "$filtered_items"
+
+    if [ $TODOTXT_VERBOSE -gt 0 ]; then
+        NUMTASKS=$( echo -n "$filtered_items" | sed -n '$ =' )
+        TOTALTASKS=$( echo -n "$items" | sed -n '$ =' )
+    fi
+    if [ $TODOTXT_VERBOSE -gt 1 ]; then
+        echo "TODO DEBUG: Filter Command was: ${filter_command:-cat}"
+    fi
+}
+
+export -f cleaninput getPrefix getTodo getNewtodo shellquote filtercommand _list getPadding _format die
+
+# == HANDLE ACTION ==
+action=$( printf "%s\n" "$ACTION" | tr 'A-Z' 'a-z' )
+
+## If the first argument is "command", run the rest of the arguments
+## using todo.sh builtins.
+## Else, run a actions script with the name of the command if it exists
+## or fallback to using a builtin
+if [ "$action" == command ]
+then
+    ## Get rid of "command" from arguments list
+    shift
+    ## Reset action to new first argument
+    action=$( printf "%s\n" "$1" | tr 'A-Z' 'a-z' )
+elif [ -d "$TODO_ACTIONS_DIR" -a -x "$TODO_ACTIONS_DIR/$action" ]
+then
+    "$TODO_ACTIONS_DIR/$action" "$@"
+    exit $?
+fi
+
+## Only run if $action isn't found in .todo.actions.d
+case $action in
+"add" | "a")
+    if [[ -z "$2" && $TODOTXT_FORCE = 0 ]]; then
+        echo -n "Add: "
+        read input
+    else
+        [ -z "$2" ] && die "usage: $TODO_SH add \"TODO ITEM\""
+        shift
+        input=$*
+    fi
+    _addto "$TODO_FILE" "$input"
+    ;;
+
+"addm")
+    if [[ -z "$2" && $TODOTXT_FORCE = 0 ]]; then
+        echo -n "Add: "
+        read input
+    else
+        [ -z "$2" ] && die "usage: $TODO_SH addm \"TODO ITEM\""
+        shift
+        input=$*
+    fi
+
+    # Set Internal Field Seperator as newline so we can 
+    # loop across multiple lines
+    SAVEIFS=$IFS
+    IFS=$'\n'
+
+    # Treat each line seperately
+    for line in $input ; do
+        _addto "$TODO_FILE" "$line"
+    done
+    IFS=$SAVEIFS
+    ;;
+
+"addto" )
+    [ -z "$2" ] && die "usage: $TODO_SH addto DEST \"TODO ITEM\""
+    dest="$TODO_DIR/$2"
+    [ -z "$3" ] && die "usage: $TODO_SH addto DEST \"TODO ITEM\""
+    shift
+    shift
+    input=$*
+
+    if [ -f "$dest" ]; then
+        _addto "$dest" "$input"
+    else
+        die "TODO: Destination file $dest does not exist."
+    fi
+    ;;
+
+"append" | "app" )
+    errmsg="usage: $TODO_SH append ITEM# \"TEXT TO APPEND\""
+    shift; item=$1; shift
+    getTodo "$item"
+
+    if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
+        echo -n "Append: "
+        read input
+    else
+        input=$*
+    fi
+    case "$input" in
+      [$SENTENCE_DELIMITERS]*)  appendspace=;;
+      *)                        appendspace=" ";;
+    esac
+    cleaninput "for sed"
+
+    if sed -i.bak $item" s|^.*|&${appendspace}${input}|" "$TODO_FILE"; then
+        if [ $TODOTXT_VERBOSE -gt 0 ]; then
+            getNewtodo "$item"
+            echo "$item $newtodo"
+	fi
+    else
+        die "TODO: Error appending task $item."
+    fi
+    ;;
+
+"archive" )
+    # defragment blank lines
+    sed -i.bak -e '/./!d' "$TODO_FILE"
+    [ $TODOTXT_VERBOSE -gt 0 ] && grep "^x " "$TODO_FILE"
+    grep "^x " "$TODO_FILE" >> "$DONE_FILE"
+    sed -i.bak '/^x /d' "$TODO_FILE"
+    if [ $TODOTXT_VERBOSE -gt 0 ]; then
+	echo "TODO: $TODO_FILE archived."
+    fi
+    ;;
+
+"del" | "rm" )
+    # replace deleted line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1
+    errmsg="usage: $TODO_SH del ITEM# [TERM]"
+    item=$2
+    getTodo "$item"
+
+    if [ -z "$3" ]; then
+        if  [ $TODOTXT_FORCE = 0 ]; then
+            echo "Delete '$todo'?  (y/n)"
+            read ANSWER
+        else
+            ANSWER="y"
+        fi
+        if [ "$ANSWER" = "y" ]; then
+            if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then
+                # delete line (changes line numbers)
+                sed -i.bak -e $item"s/^.*//" -e '/./!d' "$TODO_FILE"
+            else
+                # leave blank line behind (preserves line numbers)
+                sed -i.bak -e $item"s/^.*//" "$TODO_FILE"
+            fi
+            if [ $TODOTXT_VERBOSE -gt 0 ]; then
+                echo "$item $todo"
+                echo "TODO: $item deleted."
+            fi
+        else
+            echo "TODO: No tasks were deleted."
+        fi
+    else
+        sed -i.bak \
+            -e $item"s/^\((.) \)\{0,1\} *$3 */\1/g" \
+            -e $item"s/ *$3 *\$//g" \
+            -e $item"s/  *$3 */ /g" \
+            -e $item"s/ *$3  */ /g" \
+            -e $item"s/$3//g" \
+            "$TODO_FILE"
+        getNewtodo "$item"
+        if [ "$todo" = "$newtodo" ]; then
+            [ $TODOTXT_VERBOSE -gt 0 ] && echo "$item $todo"
+            die "TODO: '$3' not found; no removal done."
+        fi
+        if [ $TODOTXT_VERBOSE -gt 0 ]; then
+            echo "$item $todo"
+            echo "TODO: Removed '$3' from task."
+            echo "$item $newtodo"
+        fi
+    fi
+    ;;
+
+"depri" | "dp" )
+    errmsg="usage: $TODO_SH depri ITEM#[, ITEM#, ITEM#, ...]"
+    shift;
+    [ $# -eq 0 ] && die "$errmsg"
+
+    # Split multiple depri's, if comma separated change to whitespace separated
+    # Loop the 'depri' function for each item
+    for item in ${*//,/ }; do
+        getTodo "$item"
+
+	if [[ "$todo" = \(?\)\ * ]]; then
+	    sed -i.bak -e $item"s/^(.) //" "$TODO_FILE"
+	    if [ $TODOTXT_VERBOSE -gt 0 ]; then
+		getNewtodo "$item"
+		echo "$item $newtodo"
+		echo "TODO: $item deprioritized."
+	    fi
+	else
+	    echo "TODO: $item is not prioritized."
+	fi
+    done
+    ;;
+
+"do" )
+    errmsg="usage: $TODO_SH do ITEM#[, ITEM#, ITEM#, ...]"
+    # shift so we get arguments to the do request
+    shift;
+    [ "$#" -eq 0 ] && die "$errmsg"
+
+    # Split multiple do's, if comma separated change to whitespace separated
+    # Loop the 'do' function for each item
+    for item in ${*//,/ }; do
+        getTodo "$item"
+
+        # Check if this item has already been done
+        if [ "${todo:0:2}" != "x " ]; then
+            now=$(date '+%Y-%m-%d')
+            # remove priority once item is done
+            sed -i.bak $item"s/^(.) //" "$TODO_FILE"
+            sed -i.bak $item"s|^|x $now |" "$TODO_FILE"
+            if [ $TODOTXT_VERBOSE -gt 0 ]; then
+                getNewtodo "$item"
+                echo "$item $newtodo"
+                echo "TODO: $item marked as done."
+	    fi
+        else
+            echo "TODO: $item is already marked done."
+        fi
+    done
+
+    if [ $TODOTXT_AUTO_ARCHIVE = 1 ]; then
+        # Recursively invoke the script to allow overriding of the archive
+        # action.
+        "$TODO_FULL_SH" archive
+    fi
+    ;;
+
+"help" )
+    if [ -t 1 ] ; then # STDOUT is a TTY
+        if which "${PAGER:-less}" >/dev/null 2>&1; then
+            # we have a working PAGER (or less as a default)
+            help | "${PAGER:-less}" && exit 0
+        fi
+    fi
+    help # just in case something failed above, we go ahead and just spew to STDOUT
+    ;;
+
+"shorthelp" )
+    if [ -t 1 ] ; then # STDOUT is a TTY
+        if which "${PAGER:-less}" >/dev/null 2>&1; then
+            # we have a working PAGER (or less as a default)
+            shorthelp | "${PAGER:-less}" && exit 0
+        fi
+    fi
+    shorthelp # just in case something failed above, we go ahead and just spew to STDOUT
+    ;;
+
+"list" | "ls" )
+    shift  ## Was ls; new $1 is first search term
+    _list "$TODO_FILE" "$@"
+    ;;
+
+"listall" | "lsa" )
+    shift  ## Was lsa; new $1 is first search term
+
+    TOTAL=$( sed -n '$ =' "$TODO_FILE" )
+    PADDING=${#TOTAL}
+
+    post_filter_command="awk -v TOTAL=$TOTAL -v PADDING=$PADDING '{ \$1 = sprintf(\"%\" PADDING \"d\", (\$1 > TOTAL ? 0 : \$1)); print }' "
+    cat "$TODO_FILE" "$DONE_FILE" | TODOTXT_VERBOSE=0 _format '' "$PADDING" "$@"
+
+    if [ $TODOTXT_VERBOSE -gt 0 ]; then
+        TDONE=$( sed -n '$ =' "$DONE_FILE" )
+        TASKNUM=$(TODOTXT_PLAIN=1 TODOTXT_VERBOSE=0 _format "$TODO_FILE" 1 "$@" | sed -n '$ =')
+        DONENUM=$(TODOTXT_PLAIN=1 TODOTXT_VERBOSE=0 _format "$DONE_FILE" 1 "$@" | sed -n '$ =')
+        echo "--"
+        echo "$(getPrefix "$TODO_FILE"): ${TASKNUM:-0} of ${TOTAL:-0} tasks shown"
+        echo "$(getPrefix "$DONE_FILE"): ${DONENUM:-0} of ${TDONE:-0} tasks shown"
+        echo "total $((TASKNUM + DONENUM)) of $((TOTAL + TDONE)) tasks shown"
+    fi
+    ;;
+
+"listfile" | "lf" )
+    shift  ## Was listfile, next $1 is file name
+    if [ $# -eq 0 ]; then
+        [ $TODOTXT_VERBOSE -gt 0 ] && echo "Files in the todo.txt directory:"
+        cd "$TODO_DIR" && ls -1 *.txt
+    else
+        FILE="$1"
+        shift  ## Was filename; next $1 is first search term
+
+        _list "$FILE" "$@"
+    fi
+    ;;
+
+"listcon" | "lsc" )
+    FILE=$TODO_FILE
+    [ "$TODOTXT_SOURCEVAR" ] && eval "FILE=$TODOTXT_SOURCEVAR"
+    grep -ho '[^ ]*@[^ ]\+' "${FILE[@]}" | grep '^@' | sort -u
+    ;;
+
+"listproj" | "lsprj" )
+    FILE=$TODO_FILE
+    [ "$TODOTXT_SOURCEVAR" ] && eval "FILE=$TODOTXT_SOURCEVAR"
+    shift
+    eval "$(filtercommand 'cat "${FILE[@]}"' '' "$@")" | grep -o '[^ ]*+[^ ]\+' | grep '^+' | sort -u
+    ;;
+
+"listpri" | "lsp" )
+    shift ## was "listpri", new $1 is priority to list or first TERM
+
+    pri=$(printf "%s\n" "$1" | tr 'a-z' 'A-Z' | grep -e '^[A-Z]$' -e '^[A-Z]-[A-Z]$') && shift || pri="A-Z"
+    post_filter_command="grep '^ *[0-9]\+ ([${pri}]) '"
+    _list "$TODO_FILE" "$@"
+    ;;
+
+"move" | "mv" )
+    # replace moved line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1
+    errmsg="usage: $TODO_SH mv ITEM# DEST [SRC]"
+    item=$2
+    dest="$TODO_DIR/$3"
+    src="$TODO_DIR/$4"
+
+    [ -z "$4" ] && src="$TODO_FILE"
+    [ -z "$dest" ] && die "$errmsg"
+
+    [ -f "$src" ] || die "TODO: Source file $src does not exist."
+    [ -f "$dest" ] || die "TODO: Destination file $dest does not exist."
+
+    getTodo "$item" "$src"
+    [ -z "$todo" ] && die "$item: No such item in $src."
+    if  [ $TODOTXT_FORCE = 0 ]; then
+        echo "Move '$todo' from $src to $dest? (y/n)"
+        read ANSWER
+    else
+        ANSWER="y"
+    fi
+    if [ "$ANSWER" = "y" ]; then
+        if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then
+            # delete line (changes line numbers)
+            sed -i.bak -e $item"s/^.*//" -e '/./!d' "$src"
+        else
+            # leave blank line behind (preserves line numbers)
+            sed -i.bak -e $item"s/^.*//" "$src"
+        fi
+        echo "$todo" >> "$dest"
+
+        if [ $TODOTXT_VERBOSE -gt 0 ]; then
+            echo "$item $todo"
+            echo "TODO: $item moved from '$src' to '$dest'."
+        fi
+    else
+        echo "TODO: No tasks moved."
+    fi
+    ;;
+
+"prepend" | "prep" )
+    errmsg="usage: $TODO_SH prepend ITEM# \"TEXT TO PREPEND\""
+    replaceOrPrepend 'prepend' "$@"
+    ;;
+
+"pri" | "p" )
+    item=$2
+    newpri=$( printf "%s\n" "$3" | tr 'a-z' 'A-Z' )
+
+    errmsg="usage: $TODO_SH pri ITEM# PRIORITY
+note: PRIORITY must be anywhere from A to Z."
+
+    [ "$#" -ne 3 ] && die "$errmsg"
+    [[ "$newpri" = @([A-Z]) ]] || die "$errmsg"
+    getTodo "$item"
+
+    oldpri=
+    if [[ "$todo" = \(?\)\ * ]]; then
+        oldpri=${todo:1:1}
+    fi
+
+    if [ "$oldpri" != "$newpri" ]; then
+        sed -i.bak -e $item"s/^(.) //" -e $item"s/^/($newpri) /" "$TODO_FILE"
+    fi
+    if [ $TODOTXT_VERBOSE -gt 0 ]; then
+        getNewtodo "$item"
+        echo "$item $newtodo"
+        if [ "$oldpri" != "$newpri" ]; then
+            if [ "$oldpri" ]; then
+                echo "TODO: $item re-prioritized from ($oldpri) to ($newpri)."
+            else
+                echo "TODO: $item prioritized ($newpri)."
+            fi
+        fi
+    fi
+    if [ "$oldpri" = "$newpri" ]; then
+        echo "TODO: $item already prioritized ($newpri)."
+    fi
+    ;;
+
+"replace" )
+    errmsg="usage: $TODO_SH replace ITEM# \"UPDATED ITEM\""
+    replaceOrPrepend 'replace' "$@"
+    ;;
+
+"report" )
+    # archive first
+    # Recursively invoke the script to allow overriding of the archive
+    # action.
+    "$TODO_FULL_SH" archive
+
+    TOTAL=$( sed -n '$ =' "$TODO_FILE" )
+    TDONE=$( sed -n '$ =' "$DONE_FILE" )
+    NEWDATA="${TOTAL:-0} ${TDONE:-0}"
+    LASTREPORT=$(sed -ne '$p' "$REPORT_FILE")
+    LASTDATA=${LASTREPORT#* }   # Strip timestamp.
+    if [ "$LASTDATA" = "$NEWDATA" ]; then
+        echo "$LASTREPORT"
+        [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: Report file is up-to-date."
+    else
+        NEWREPORT="$(date +%Y-%m-%dT%T) ${NEWDATA}"
+        echo "${NEWREPORT}" >> "$REPORT_FILE"
+        echo "${NEWREPORT}"
+        [ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: Report file updated."
+    fi
+    ;;
+
+"deduplicate" )
+    if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then
+        deduplicateSedCommand='d'
+    else
+        deduplicateSedCommand='s/^.*//; p'
+    fi
+
+    # To determine the difference when deduplicated lines are preserved, only
+    # non-empty lines must be counted.
+    originalTaskNum=$( sed -e '/./!d' "$TODO_FILE" | sed -n '$ =' )
+
+    # Look for duplicate lines and discard the second occurrence.
+    # We start with an empty hold space on the first line.  For each line:
+    #   G - appends newline + hold space to the pattern space
+    #   s/\n/&&/; - double up the first new line so we catch adjacent dups
+    #   /^\([^\n]*\n\).*\n\1/b dedup
+    #       If the first line of the hold space shows up again later as an
+    #       entire line, it's a duplicate. Jump to the "dedup" label, where
+    #       either of the following is executed, depending on whether empty
+    #       lines should be preserved:
+    #       d           - Delete the current pattern space, quit this line and
+    #                     move on to the next, or:
+    #       s/^.*//; p  - Clear the task text, print this line and move on to
+    #                     the next.
+    #   s/\n//;   - else (no duplicate), drop the doubled newline
+    #   h;        - replace the hold space with the expanded pattern space
+    #   P;        - print up to the first newline (that is, the input line)
+    #   b         - end processing of the current line
+    sed -i.bak -n \
+        -e 'G; s/\n/&&/; /^\([^\n]*\n\).*\n\1/b dedup' \
+        -e 's/\n//; h; P; b' \
+        -e ':dedup' \
+        -e "$deduplicateSedCommand" \
+        "$TODO_FILE"
+
+    newTaskNum=$( sed -e '/./!d' "$TODO_FILE" | sed -n '$ =' )
+    deduplicateNum=$(( originalTaskNum - newTaskNum ))
+    if [ $deduplicateNum -eq 0 ]; then
+        echo "TODO: No duplicate tasks found"
+    else
+        echo "TODO: $deduplicateNum duplicate task(s) removed"
+    fi
+    ;;
+
+"listaddons" )
+    if [ -d "$TODO_ACTIONS_DIR" ]; then
+        cd "$TODO_ACTIONS_DIR" || exit $?
+        for action in *
+        do
+            if [ -f "$action" -a -x "$action" ]; then
+                echo "$action"
+            fi
+        done
+    fi
+    ;;
+
+* )
+    usage;;
+esac
diff --git a/plugins/available/todo/todo_completion b/plugins/available/todo/todo_completion
new file mode 100644
index 0000000..3f9d308
--- /dev/null
+++ b/plugins/available/todo/todo_completion
@@ -0,0 +1,107 @@
+#!/bin/bash source-this-script
+[ "$BASH_VERSION" ] || return
+
+_todo()
+{
+    local cur prev opts
+    COMPREPLY=()
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    prev="${COMP_WORDS[COMP_CWORD-1]}"
+
+    local -r OPTS="-@ -@@ -+ -++ -d -f -h -p -P -PP -a -n -t -v -vv -V -x"
+    local -r COMMANDS="\
+        add a addto addm append app archive command del  \
+        rm depri dp do help list ls listaddons listall lsa listcon  \
+        lsc listfile lf listpri lsp listproj lsprj move \
+        mv prepend prep pri p replace report shorthelp"
+
+    local _todo_sh=${_todo_sh:-todo.sh}
+    local completions
+    if [ $COMP_CWORD -eq 1 ]; then
+        completions="$COMMANDS $(eval TODOTXT_VERBOSE=0 $_todo_sh command listaddons) $OPTS"
+    elif [[ $COMP_CWORD -gt 2 && ( \
+        "${COMP_WORDS[COMP_CWORD-2]}" =~ ^(move|mv)$ || \
+        "${COMP_WORDS[COMP_CWORD-3]}" =~ ^(move|mv)$ ) ]]; then
+        # "move ITEM# DEST [SRC]" has file arguments on positions 2 and 3.
+        completions=$(eval TODOTXT_VERBOSE=0 $_todo_sh command listfile)
+    else
+        case "$prev" in
+            command)
+                completions=$COMMANDS;;
+            addto|listfile|lf)
+                completions=$(eval TODOTXT_VERBOSE=0 $_todo_sh command listfile);;
+            -*) completions="$COMMANDS $(eval TODOTXT_VERBOSE=0 $_todo_sh command listaddons) $OPTS";;
+            *)  case "$cur" in
+                    +*) completions=$(eval TODOTXT_VERBOSE=0 $_todo_sh command listproj)
+                        COMPREPLY=( $( compgen -W "$completions" -- $cur ))
+                        [ ${#COMPREPLY[@]} -gt 0 ] && return 0
+                        # Fall back to projects extracted from done tasks.
+                        completions=$(eval 'TODOTXT_VERBOSE=0 TODOTXT_SOURCEVAR=\$DONE_FILE' $_todo_sh command listproj)
+                        ;;
+                    @*) completions=$(eval TODOTXT_VERBOSE=0 $_todo_sh command listcon)
+                        COMPREPLY=( $( compgen -W "$completions" -- $cur ))
+                        [ ${#COMPREPLY[@]} -gt 0 ] && return 0
+                        # Fall back to contexts extracted from done tasks.
+                        completions=$(eval 'TODOTXT_VERBOSE=0 TODOTXT_SOURCEVAR=\$DONE_FILE' $_todo_sh command listcon)
+                        ;;
+                    *)  if [[ "$cur" =~ ^[0-9]+$ ]]; then
+                            # Remove the (padded) task number; we prepend the
+                            # user-provided $cur instead.
+                            # Remove the timestamp prepended by the -t option,
+                            # and the done date (for done tasks); there's no
+                            # todo.txt option for that yet.
+                            # But keep priority and "x"; they're short and may
+                            # provide useful context.
+                            # Remove any trailing whitespace; the Bash
+                            # completion inserts a trailing space itself.
+                            # Finally, limit the output to a single line just as
+                            # a safety check of the ls action output.
+                            local todo=$( \
+                                eval TODOTXT_VERBOSE=0 $_todo_sh '-@ -+ -p -x command ls "^ *${cur} "' | \
+                                sed -e 's/^ *[0-9]\{1,\} //' -e 's/\((.) \)[0-9]\{2,4\}-[0-9]\{2\}-[0-9]\{2\} /\1/' \
+                                    -e 's/\([xX] \)\([0-9]\{2,4\}-[0-9]\{2\}-[0-9]\{2\} \)\{1,2\}/\1/' \
+                                    -e 's/[[:space:]]*$//' \
+                                    -e '1q' \
+                            )
+                            # Append task text as a shell comment. This
+                            # completion can be a safety check before a
+                            # destructive todo.txt operation.
+                            [ "$todo" ] && COMPREPLY[0]="$cur # $todo"
+                            return 0
+                        else
+                            return 0
+                        fi
+                        ;;
+                esac
+                ;;
+        esac
+    fi
+
+    COMPREPLY=( $( compgen -W "$completions" -- $cur ))
+    return 0
+}
+complete -F _todo todo.sh
+
+# If you define an alias (e.g. "t") to todo.sh, you need to explicitly enable
+# completion for it, too:
+#complete -F _todo t
+
+# If you have renamed the todo.sh executable, or if it is not accessible through
+# PATH, you need to add and use a wrapper completion function, like this:
+#_todoElsewhere()
+#{
+#    local _todo_sh='/path/to/todo2.sh'
+#    _todo "$@"
+#}
+#complete -F _todoElsewhere /path/to/todo2.sh
+
+# If you use aliases to use different configuration(s), you need to add and use
+# a wrapper completion function for each configuration if you want to complete
+# fron the actual configured task locations:
+#alias todo2='todo.sh -d "$HOME/todo2.cfg"'
+#_todo2()
+#{
+#    local _todo_sh='todo.sh -d "$HOME/todo2.cfg"'
+#    _todo "$@"
+#}
+#complete -F _todo2 todo2