# _ _ # | |_| |__ ___ ___ __ _ # | __| _ \ / _ \/ __/ _` | # | |_| | | | __/ (_| (_| | # \__|_| |_|\___|\___\__,_| # ![example usage of theca](screenshots/main.png) a simple, fully featured, command line note taking tool written in [*Rust*](http://www.rust-lang.org/). [![Build Status](https://travis-ci.org/pwoolcoc/theca.svg?branch=travis-build)](https://travis-ci.org/pwoolcoc/theca) ## 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](#installation) - [From crates.io](#binaries) - [From source](#from-source) - [Usage](#usage) - [First run](#first-run) - [Adding notes](#adding-notes) - [Editing notes](#editing-notes) - [Deleting notes](#deleting-notes) - [List all notes](#list-all-notes) - [View a single note](#view-a-single-note) - [Searching notes](#searching-notes) - [A quick note on *statuses*](#a-quick-note-on-statuses) - [Non-default profiles](#non-default-profiles) - [Setting the default profile](#setting-the-default-profile) - [Setting the default profile folder](#setting-the-default-profile-folder) - [List all profiles](#list-all-profiles) - [Transfer a note to another profile](#transfer-a-note-to-another-profile) - [Import a note from another profile](#import-a-note-from-another-profile) - [Encrypted profiles](#encrypted-profiles) - [Encrypt a plaintext profile](#encrypt-a-plaintext-profile) - [Decrypt a encrypted profile](#decrypt-a-encrypted-profile) - [Changing the encryption key for an already encrypted profile](#changing-the-encryption-key-for-an-already-encrypted-profile) - [Synchronizing profiles](#synchronizing-profiles) - [JSON output mode](#json-output-mode) - [Tab completion](#tab-completion) - [man page](#man-page) - [Contributing](#contributing) - [Bugs](#bugs) - [TODO](#todo) - [Development](#development) - [JSON profile format](#json-profile-format) - [Cryptographic design](#cryptographic-design) - [Basic Python implementation](#basic-python-implementation) - [`tools/build.sh`](#buildsh) - [`tools/theca_test_harness.py`](#theca_test_harnesspy) - [Test suite file format](#test-suite-file-format) - [Test formats](#test-formats) - [License](#license) ## Installation ### From crates.io `theca` is available from crates.io, so you can just do `cargo install theca` to install the binary to your `$CARGO_HOME/bin` directory. ### 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](http://www.rust-lang.org/install.html) or by running $ curl https://sh.rustup.rs -sSf | 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/pwoolcoc/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 [] theca [options] encrypt-profile [--new-key KEY] theca [options] decrypt-profile theca [options] info theca [options] clear theca [options] theca [options] theca [options] search [--regex, --search-body] theca [options] transfer to theca [options] import from theca [options] add [-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 ![new default profile](screenshots/first_run.png) `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 ![adding a basic note](screenshots/add_simple_note.png) `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 ![editing a notes status](screenshots/edit_notes.png) `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 ![deleting some notes](screenshots/delete_note.png) `theca del <id>..` deletes one or more notes specified by space separated note ids. ### List all notes ![list all notes](screenshots/list_notes.png) `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 ![view a note](screenshots/view_note.png) ![view a note using the short print style](screenshots/view_note_condensed.png) `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 ![searching notes](screenshots/search_notes.png) 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 non default profile](screenshots/new_second_profile.png) 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 ![setting the THECA_DEFAULT_PROFILE env var](screenshots/default_profile_env.png) 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 ![list all profiles in the current profile folder](screenshots/list_all_profiles.png) All profiles in the current profile folder can be view using `theca list-profiles`. #### Transfer a note to another profile ![transfer a note](screenshots/transfer_note.png) `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 ![import a note](screenshots/import_note.png) `theca import <id> from <name>` transfers a note from the profile `<name>` to the current profile (in this case `default`). #### Encrypted profiles ![new encrypted profile](screenshots/new_encrypted_profile.png) 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 ![decrypt a profile](screenshots/decrypt_profile.png) You can decrypt an encrypted profile in place using `theca decrypt-profile`. ##### Encrypt a plaintext profile ![encrypt a profile](screenshots/encrypt_profile.png) 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 ![change encryption key](screenshots/change_key.png) 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 ![view list as json](screenshots/json_list.png) ![view note as json](screenshots/json_note.png) 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 ![the man page](screenshots/man-page.png) `theca` uses `md2man-roff` from [md2man](https://github.com/sunaku/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 * `Profile` and `Item` and assosiated functions should be moved out of `src/theca/lib.rs` to their own file * `list-profiles` should be alphabetic * `bash_complete.sh` could use a lot of improvement, `_theca` also, but less... * `save_to_file` and `transfer_note` (and inherently the `import` logic) could use some work, specifically the profile changed stuff... <-- because of that we have pass pretty much all of the `Args` 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 into `theca` 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*](https://github.com/DaGenix/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 to `cargo build` so things like `--release` and `--verbose` work fine * `build-man` requires the `md2man-roff` tool to convert the Markdown man page to the roff man page format * `test` runs both all the Rust tests (`cargo test`) and all the Python harness tests * `install` 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 the `zsh` completion script manually. * `clean` deletes the binary in `.`, the `target/` folder, and the man page in `docs/` 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` was originally written by roland shoemaker (<rolandshoemaker@gmail.com>), and has since been taken up by Paul Woolcock (<paul@woolcock.us>). ## 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`.