# _ _
# | |_| |__ ___ ___ __ _
# | __| _ \ / _ \/ __/ _` |
# | |_| | | | __/ (_| (_| |
# \__|_| |_|\___|\___\__,_|
#
a simple, fully featured, command line note taking tool written in Rust.
Features
- Multiple profile support
- Plaintext or 256-bit AES encrypted profiles
- JSON profile format for easy scripting/integration
- Traditional and condensed printing modes
- Add/edit/delete notes
- Add/edit note body using command line arguments,
STDIN
, or using the editor set via$VISUAL
or$EDITOR
- Transfer notes between profiles
- Search notes (title or body using keyword or regex pattern)
Contents
- Installation
- Usage
- First run
- Adding notes
- Editing notes
- Deleting notes
- List all notes
- View a single note
- Searching notes
- A quick note on statuses
- Non-default profiles
- JSON output mode
- Tab completion
- man page
- Contributing
- Development
- License
Installation
Binaries
I've built a simple multi-(platform/arch) binary package builder (tools/theca-packer.py
)
based on Fabric so I can provide both x86_64
and i686
packages
for unknown-linux-gnu
and apple-darwin
. You can either download the binary packages and install
them using the packaged install.sh
script, or you can use curl
to install theca
like so
$ curl -s https://static.bracewel.net/theca/get_theca.sh | sh
If you want to uninstall you just need to add the --uninstall
flag like so
$ curl -s https://static.bracewel.net/theca/get_theca.sh | sh -s -- --uninstall
From source
All that's needed to build theca is a copy of the rustc
compiler and the cargo
packaging tool which can
be downloaded directly from the Rust website or by running
$ curl -s https://static.rust-lang.org/rustup.sh | sh
to get the nightly rustc
and cargo
binaries, once those have finished building we can clone and build theca
$ git clone https://github.com/rolandshoemaker/theca.git
...
$ cd theca
$ cargo build [--release]
...
$ sudo bash tools/build.sh install [--release, --man, --bash-complete, --zsh-complete]
The cargo
flag --release
enables rustc
optimizations. F
The cargo
flag --release
enables rustc
optimizations.or the install
the flag --man
will additionally install the man page and --bash-complete
and --zsh-complete
will
additionally install the bash
or zsh
tab completion scripts. cargo
will automatically
download and compile theca
s dependencies for you.
Usage
$ theca --help
theca - simple cli note taking tool
Usage:
theca [options] new-profile [<name>]
theca [options] encrypt-profile [--new-key KEY]
theca [options] decrypt-profile
theca [options] info
theca [options] clear
theca [options]
theca [options] <id>
theca [options] search [--regex, --search-body] <pattern>
theca [options] transfer <id> to <name>
theca [options] import <id> from <name>
theca [options] add <title> [-s|-u] [-b BODY|-t|-]
theca [options] edit <id> [<title>] [-s|-u|-n] [-b BODY|-t|-]
theca [options] del <id>...
Profiles:
-f PATH, --profile-folder PATH Path to folder containing profile.json
files [default can be set with env var
THECA_PROFILE_FOLDER].
-p PROFILE, --profile PROFILE Specify non-default profile [default
can be set with env var
THECA_DEFAULT_PROFILE].
Printing format:
-c, --condensed Use the condensed printing format.
-j, --json Print list output as a JSON object.
Note list formatting:
-l LIMIT, --limit LIMIT Limit output to LIMIT notes
[default: 0].
-d, --datesort Sort notes by date.
-r, --reverse Reverse list.
Input:
-y, --yes Silently agree to any [y/n] prompts.
Statuses:
-n, --none No status. (note default)
-s, --started Started status.
-u, --urgent Urgent status.
Body:
-b BODY, --body BODY Set body of the note to BODY.
-t, --editor Drop to $EDITOR to set/edit note body.
- Set body of the note from STDIN.
Encryption:
-e, --encrypted Specifies using an encrypted profile.
-k KEY, --key KEY Encryption key to use for encryption/
decryption, a prompt will be
displayed if no key is provided.
--new-key KEY Specifies the encryption key for a
profile when using `encrypt-profile`,
a prompt will be displayed if no key
is provided.
Search:
--search-body Search the body of notes instead of
the title.
--regex Set search pattern to regex (default
is keyword).
Miscellaneous:
-h, --help Display this help and exit.
-v, --version Display the version of theca and exit.
First run
theca new-profile
will create the ~/.theca
folder as well as the default
note profile in ~/.theca/default.json
. If you would like to use a non-standard
profile folder you can use --profile-folder PATH
.
Adding notes
theca add <title>
will add a note to the default profile with no body or status.
These flags can be used to add a note with a status and/or a body
Statuses:
-s, --started Started status.
-u, --urgent Urgent status.
Body:
-b BODY, --body BODY Set body of the note to BODY.
-t, --editor Drop to $EDITOR to set/edit note body.
- Set body of the note from STDIN.
Editing notes
theca edit <id>
is used to edit the title, status, or body of a note.
Statuses:
-n, --none No status. (note default)
-s, --started Started status.
-u, --urgent Urgent status.
Body:
-b BODY, --body BODY Set body of the note to BODY.
-t, --editor Drop to $EDITOR to set/edit note body.
- Set body of the note from STDIN.
Deleting notes
theca del <id>..
deletes one or more notes specified by space separated note ids.
List all notes
theca
prints out all notes in the current profile, the following options can be
used to limit/sort the resulting list
Printing format:
-c, --condensed Use the condensed printing format.
-j, --json Print list output as a JSON object.
Note list formatting:
-l LIMIT, --limit LIMIT Limit output to LIMIT notes.
[default: 0].
-d, --datesort Sort notes by date.
-r, --reverse Reverse list.
View a single note
theca <id>
prints out a single note, including the status and body, the following
options can be used to alter the output style
Printing format:
-c, --condensed Use the condensed printing format.
-j, --json Print list output as a JSON object.
Searching notes
Notes can be search using either keyword or regex matching against note titles or bodies
using theca search
. theca
doesn't support any kind of tagging (beyond the two basic
statuses) but you can implement this quite simply by just appending or prepending a tag
of sorts to the note title or body, e.g. "(THECA) something about theca"
, and then
do a keyword search for "(THECA)"
to get all notes tagged as such.
Search:
--search-body Search the body of notes instead of
the title.
--regex Set search pattern to regex (default
is keyword).
A quick note on statuses
During initial development of theca
I spent quite a bit of time trying to figure out
what statuses I should include (or if I should allow completely custom statuses) and after
playing with quite a few I ended up realising I only ever used three (well... two, if that).
- No status at all (
-n
or--none
) Started
(-s
or--started
)Urgent
(-u
or--urgent
)
These flags can be used when adding notes, editing notes, searching notes, and listing events to either specify the note status or filter lists by status.
Non-default profiles
New named profiles can be created with the theca new-profile <name>
command and will be
stored alongside default.json
in either ~/.theca/
or in the folder specified by
--profile-folder PATH
.
Setting the default profile
The default profile that theca
loads (normalled default
) can be changed by setting the
environment variable THECA_DEFAULT_PROFILE
.
Setting the default profile folder
The default profile folder can also be set via a enviroment variable, THECA_PROFILE_FOLDER
.
List all profiles
All profiles in the current profile folder can be view using theca list-profiles
.
Transfer a note to another profile
theca transfer <id> to <name>
transfers a note from the current profile (in this case
default
) to another profile.
Import a note from another profile
theca import <id> from <name>
transfers a note from the profile <name>
to
the current profile (in this case default
).
Encrypted profiles
Using --encrypted
tells theca that it should be dealing with encrypted profiles, so
using theca --encrypted new-profile secrets
theca knows to create an encrypted profile
and will ask you for a key to encrypt the resulting secrets.json
. If you'd like not to
be prompted you can specify it with the argument --key KEY
.
--encrypted
and --key
can be used with all the other commands that read or write a
profile to specify that the profile you want to use will need to be encrypted/decrypted.
Encryption:
-e, --encrypted Specifies using an encrypted profile.
-k KEY, --key KEY Encryption key to use for encryption/
decryption, a prompt will be
displayed if no key is provided.
Decrypt a encrypted profile
You can decrypt an encrypted profile in place using theca decrypt-profile
.
Encrypt a plaintext profile
You can also encrypt a profile in place using theca encrypt-profile [--new-key KEY]
, if --new-key
isn't used you will be prompted for the encryption key to use to encrypt the profile.
--new-key KEY Specifies the encryption key for a
profile when using `encrypt-profile`,
a prompt will be displayed if no key
is provided.
Changing the encryption key for an already encrypted profile
You can also use theca encrypt-profile --new-key KEY
to change the encryption key of an already encrypted profile which is pretty cool and avoids the user having to do encrypted with old key -> plaintext -> encrypted with new key
!
Synchronizing profiles
If you use a synchronization tool like Dropbox, ownCloud, BitTorrent Sync, or even some obscure
rsync
setup you can easily share your note profiles between machines by using
--profile-folder
to specify a folder for your profiles that is synced and your sync'r should
do the rest for you. Since theca
makes transactional*-ish* updates to the profile files it
should be perfectly safe, unless you concurrently edit a profile, though theca
will attempt
to merge changes when this happens. You could even store a profle in a git repository if you
really wanted to.
JSON output mode
You can view a single note or note list (using theca
or theca search
) to output the
result as either a JSON object or list of JSON objects by passing the --json
or -j
flag.
This works with the standard limit formatting arguments like -r
, -d
, and -l LIMIT
.
Printing format:
-j, --json Print list output as a JSON object.
Note list formatting:
-l LIMIT, --limit LIMIT Limit output to LIMIT notes
[default: 0].
-d, --datesort Sort notes by date.
-r, --reverse Reverse list.
Tab completion
There are preliminary bash
and zsh
tab completion scripts in the completion/
directory
which can be installed manually or by using the --bash-complete
or --zsh-complete
flags with
sudo bash tools/build.sh install
when installing the theca
binary or by default when using the binary installer.sh
. They both need quite a bit of
work but are still relatively usable for the time being.
man page
theca
uses md2man-roff
from md2man to convert
docs/THECA.1.md
to the roff format man page docs/THECA.1
.
Contributing
If you think I've left out some necessary feature feel free to open an issue or to fork the project and work on a patch that introduces it.
I'm pretty sure there are quite a few places where memory optimizations could be made, as well as various other performance and (extensive) design improvements.
Any and all pull requests will be considered and tremendously appreciated.
Bugs
theca
almost certainly contains bugs, I haven't had the time to write as many test cases as are really
necessary to fully cover the codebase. if you find one, please submit a issue explaining how to trigger
the bug, and if you're really awesome a test case that exposes it.
TODO
- clean-ups/optimizations pretty much everywhere
ThecaProfile
andThecaItem
and assosiated functions should be moved out ofsrc/theca/lib.rs
to their own filelist-profiles
should be alphabeticbash_complete.sh
could use a lot of improvement,_theca
also, but less...save_to_file
andtransfer_note
(and inherently theimport
logic) could use some work, specifically the profile changed stuff... <-- because of that we have pass pretty much all of theArgs
struct- probably the bold/plain line printing could be done cleaner... (macro perhaps?)
- (long term)
remote
encrypted storage (some kind of super simple standalone REST API to hold encrypted profile blobs + client integrated intotheca
to retrieve them)
Development
JSON profile format
As described much more verbosely in docs/schema.json
, this is what a note profile might look like
{
"encrypted": false,
"notes": [
{
"id": 1,
"title": "\\(â—• â—¡ â—•\\)",
"status": "",
"body": "",
"last_touched": "2015-01-22 15:01:39 -0800"
},
{
"id": 3,
"title": "(THECA) add super secret stuff",
"status": "",
"body": "",
"last_touched": "2015-01-22 15:21:01 -0800"
}
]
}
Cryptographic design
theca
uses the AES CBC mode symmetric cipher (implementation provided by rust-crypto) with a 256-bit key to encrypt/decrypt
profile files. The key is derived using pbkdf2 (using the sha-256 PRF, again from rust-crypto) with 2056 rounds
salted with the sha256 hash of the password used for the key derivation (probably not the best idea).
Basic Python implementation
During development it can be quite useful to encrypt/decrypt profiles using a scripting
language like Python. A key can be derived quite quickly using hashlib
and passlib
from hashlib import sha256
from passlib.utils.pbkdf2 import pbkdf2
passphrase = "DEBUG"
key = pbkdf2(
bytes(passphrase.encode("utf-8")),
sha256(bytes(passphrase.encode("utf-8"))).hexdigest().encode("utf-8"),
2056,
32,
"hmac-sha256"
)
and the ciphertext can be decrypted using the AES implementation from pycrypto
from Crypto.Cipher import AES
# the IV makes up the first 16 bytes of the ciphertext
iv = ciphertext[0:16]
decryptor = AES.new(key, AES.MODE_CBC, iv)
plaintext = decryptor.decrypt(ciphertext[16:])
# remove any padding from the end of the final block
plaintext = plaintext[:-plaintext[-1]].decode("utf-8")
tools/build.sh
build.sh
is a pretty simple bash
holdall in lieu of a Makefile
(ew) that really exists
because I have a bad memory and forget some of the commands i'm supposed to remember. It will also
set the build version environment variable (THECA_BUILD_VER
) which is used to set the verson theca -v
.
Usage is pretty simple
$ bash tools/build.sh
Usage: build.sh {build|build-man|test|install|clean}
build
passes through any argument tocargo build
so things like--release
and--verbose
work finebuild-man
requires themd2man-roff
tool to convert the Markdown man page to the roff man page formattest
runs both all the Rust tests (cargo test
) and all the Python harness testsinstall
copies the binary to/usr/local/bin
. It can also be used with--man
,--bash-complete
, or--zsh-complete
to install the man page,bash
completion script, or thezsh
completion script manually.clean
deletes the binary in.
, thetarget/
folder, and the man page indocs/
if they exist
tools/theca_test_harness.py
theca_test_harness.py
is a relatively simple python3 test harness for the compiled theca
binary.
It reads in JSON files which describe test cases and executes them, providing relatively simple
information like passed/failed/time taken.
The harness can preform three different output checks, against
- the resulting profile file
- the JSON output of view, list, and search commands
- the text output of add, edit, delete commands, etc
The python script has a number of arguments that may or may not be helpful
$ python3 tools/theca_test_harness.py -h
usage: theca_test_harness.py [-h] [-tc THECA_COMMAND] [-tf TEST_FILE] [-pt]
[-jt] [-tt]
test harness for the theca cli binary.
optional arguments:
-h, --help show this help message and exit
-tc THECA_COMMAND, --theca-command THECA_COMMAND
where is the theca binary
-tf TEST_FILE, --test-file TEST_FILE
path to specific test file to run
-pt, --profile-tests only run the profile output tests
-jt, --json-tests only run the json output tests
-tt, --text-tests only run the text output tests
Test suite file format
A JSON test suite file for theca_test_harness.py
looks something like this
{
"title": "GOOD DEFAULT TESTS",
"desc": "testing correct input with the default profile.",
"tests": [
...
]
}
Test formats
-
a profile result test looks something like this
{ "name": "add note", "cmds": [ ["new-profile"], ["add", "this is the title"] ], "result_path": "default.json", "result": { "encrypted": false, "notes": [ { "id": 1, "title": "this is the title", "status": "", "body": "" } ] } }
-
a JSON output test looks something like this
{ "name": "list", "cmds": [ ["new-profile"], ["add", "a title this is"], ["add", "another title this is"], ["-j"] ], "result_type": "json", "results": [ null, null, null, [ { "id": 1, "title": "a title this is", "status": "", "body": "" },{ "id": 2, "title": "another title this is", "status": "", "body": "" } ] ] }
-
a text output test looks something like this
{ "name": "new-profile", "cmds": [ ["new-profile"] ], "result_type": "text", "results": [ "creating profile 'default'\n" ] }
Author
theca
is written by roland shoemaker ([email protected]), this is my first foray
into a Rust project and my first time diving back into a systems language since 2007 or so,
so please excuse the messiness of some of the code, dynamic languages have ruined me.
License
theca
is licensed under the MIT license, the full text of which can be found at
http://opensource.org/licenses/MIT or in LICENSE
.