A CLI for journaling to structured data, Markdown, and Day One
Description
The journal command reads a journal definition and provides command line prompts to fill it out. The results are stored in a JSON database for each journal, and can optionally output to Markdown (individual files per entry, daily digest, or one large file for the journal).
Installation
Use RubyGems to install journal:
$ gem install journal-cli
If you run into errors, try running with the --user-install flag:
$ gem install --user-install journal-cli
I’ve noticed lately with asdf that I have to run asdf reshim after installing gems containing binaries.
If Gum is installed, it will be used for prettier input prompts and editing. The easiest way is with Homebrew:
$ brew install gum
If you want to use Day One with Journal, you’ll need to install the Day One CLI. It’s just one command:
A skeleton file will be written the first time Journal is run if the config file doesn’t exist.
This file contains a YAML definition of your journal. Each journal gets a top-level key, which is what you’ll specify it with on the command line. It gets a few settings, and then you define sections containing questions.
Weather
You can include weather data automatically by setting a question type to ‘weather’. In order for this to work, you’ll need to define zip and weather_api keys. zip is just your zip code, and weather_api is a key from WeatherAPI.com. Sign up here for a free plan, and then visit the profile page to see your API key at the top.
Zip codes beginning with zero (0) must be quoted. Use:
zip: ‘01001’
You can optionally set the key temp_in: to f or c to control what scale is used for temperatures.
If a question type is set to weather.forecast, the moon phase and predicted condition, high, and low will be included in the JSON data for the question. A full printout of hourly temps will be included in the Markdown/Day One output.
If the question type is weather.current, only the current condition and temperature will be recorded to the JSON, and a string containing “[TEMP] and [CONDITION]” (e.g. “64 and Sunny”) will be recorded to Markdown/Day One for the question.
If the question type is weather.moon, only the moon phase will be output. Moon phase is also included in weather.forecast JSON and Markdown output.
Journal Configuration
Edit the file at ~/.config/journal/journals.yaml following this structure:
# Where to save all journal entries (unless this key is defined inside the journal). # The journal key will be appended to this to keep each journal separateentries_folder:~/.local/share/journal/journals:daily:# journal key, will be used on the command line as `journal daily`dayone:true# Enable or disable Day One integrationjournal:Journal# Day One journal to add to (if using Day One integration)markdown:daily# Type of Markdown file to create, false to skip (can be daily, individual, or digest)title:Daily Journal# Title for every entry, date will be appended where neededsections:# Required key-title:null# The title for the section. If null, no section header will be createdkey:journal# The key for the data collected, must be one word, alphanumeric characters and _ onlyquestions:# Required key-prompt:How are you feeling?# The question to askkey:journal# alphanumeric characters and _ only, will be nested in section keytype:multiline# The type of entry expected (numeric, string, or multiline)
Keys must be alphanumeric characters and _ (underscore) only. Titles and questions can be anything, but if they contain a colon (:), you’ll need to quote the string.
The entries_folder key can be set to save JSON and Markdown files to a custom, non-default location. The default is ~/.local/share/journal. This key can also be used within a journal definition to offer custom save locations on a per-journal basis.
A more complex configuration file can contain multiple journals with multiple questions defined:
zip:55987# Your zip code for weather integrationweather_api:XXXXXXXXXXXX# Your weatherapi.com API keyjournals:# required keymood:# name of the journalentries_folder:~/Desktop/Journal/mood# Where to save this specific journal's entriesjournal:Mood Journal# Optional, Day One journal to add totags:[checkin]# Optional, array of tags to add to Day One entriesmarkdown:individual# Can be daily or individual, any other value will create a single filedayone:true# true to log entries to Day One, false to skiptitle:"Moodcheckin%M"# The title of the entry. Use %M to insert AM or PMsections:# required key-title:Weather# Title of the section (will create template sections in Day One)key:weather# the key to use in the structured data, will contain all of the answersquestions:# required key-prompt:Current Weatherkey:weather.currenttype:weather.current-prompt:Weather Forecast# The prompt shown on the command line, will also become a header in the journal entries (Markdown, Day One)key:weather.forecast# if a key contains a dot, it will create nested data, e.g. `{ 'weather': { 'forecast': data } }`type:weather.forecast# Set this to weather for weather data-title:Health# New sectionkey:healthquestions:-prompt:Health ratingkey:health.ratingtype:numeric# type can be numeric, string, or multilinemin:1# Only need min/max definitions on numeric types (defaults 1-5)max:5-prompt:Health noteskey:health.notestype:multiline-title:Journal# New sectionkey:journalquestions:-prompt:Daily noteskey:notestype:multilinedaily:# New journaljournal:Journalmarkdown:dailydayone:truetitle:Daily Journalsections:-title:nullkey:journalquestions:-prompt:How are you feeling?key:journaltype:multiline
A journal must contain a sections key, and each section must contain a questions key with an array of questions. Each question must (at minimum) have a prompt, key, and type.
If a question has a key secondary_question, the prompt will be repeated with the secondary question until it’s returned empty, answers will be joined together.
Question Types
A question type can be one of:
text or string will request a single-line string, submitted on return
multiline for multiline strings. (Opens a readline editor, use ctrl-d to save. If the latest version of gum is being used, use ctrl-j to create a new line, enter will save.)
weather will just insert current weather data with no prompt
weather.forecast will insert just the forecast (using weather history for backdated entries)
weather.current will insert just the current temperature and condition (using weather history for backdated entries)
weather.moon will insert the current moon phase for the entry date
number or float will request numeric input, stored as a float (decimal)
integer will convert numeric input to the nearest integer
date will request a natural language date which will be parsed into a date object
Conditional Questions
You can have a question only show up based on conditions. Currently the only condition is time based. Just add a key called condition to the question definition, then include a natural language string like before noon or after 3pm. If the condition is matched, then the question will be displayed, otherwise it will be skipped and its data entry in the JSON will be null.
Conditions can be applied to individual questions, or to entire sections, depending on where the condition key is placed.
Naming Keys
If you want data stored in a nested object, you can set a question type to dictionary and set the prompt to null (or just leave the key out), but give it a key that will serve as the parent in the object. Then in the nested questions, give them a key in the dot format [PARENT_KEY].[CHILD_KEY]. Section keys automatically nest their questions, but if you want to go deeper, you could have a question with the key health and type dictionary, then have questions with keys like health.rating and health.notes. If the section key was status, the resulting dictionary would look like this in the JSON:
{"date":"2023-09-08 12:19:40 UTC","data":{"status":{"health":{"rating":4,"notes":"Feeling much better today. Still a bit groggy."}}}}
If a question has the same key as its parent section, it will be moved up the chain so that you don’t get { 'journal': { 'journal': 'Journal notes' } }. You’ll just get { 'journal': 'Journal notes' }. This offers a way to organize data with fewer levels of nesting in the output.
Usage
Once your configuration file is set up, you can just run journal JOURNAL_KEY to begin prompting for the answers to the configured questions.
If a second argument contains a natural language date, the journal entry will be set to that date instead of the current time. For example, journal mood "yesterday 5pm" will create a new entry (in the journal configured for mood) for yesterday at 5pm.
Answers will always be written to ~/.local/share/journal/[KEY].json (where [KEY] is the journal key, one data file for each journal). If you’ve specified a top-level custom path with entries_folder in the config, entries will be written to [top level folder]/[KEY].json. If you’ve specified a custom path using entries_folder within the journal, entries will be written to [custom folder]/[KEY].json.
If you’ve specified daily or individual Markdown formats, entries will be written to Markdown files in ~/.local/share/journal/[KEY]/entries, either in a [KEY]-%Y-%m-%d.md file (daily), or in timestamped individual files. If digest is specified for the markdown key, a single file will be created at ~/.local/share/journal/[KEY]/entries/[KEY].md (or a folder defined by entries_folder).
At present there’s no tool for querying the dataset created. You just need to parse the JSON and use your language of choice to extract the data. Numeric entries are stored as numbers, and every entry is timestamped, so you should be able to do some advanced analysis once you have enough data.
Answering prompts
Questions with numeric answers will have a valid range assigned. Enter just a number within the range and hit return.
Questions with type ‘string’ or ‘text’ will save when you hit return. Pressing return without typing anything will leave that answer blank, and it will be ignored when exporting to Markdown or Day One (an empty value will exist in the JSON database).
When using the mutiline type, you’ll get an edit field that responds to most control-key navigation and allows insertion and movement. To save a multiline field, type CTRL-d. If the latest version of Gum is being used, ctrl-j will create a new line, enter will save.
Changelog
Click to expand
1.0.36
2024-07-03 11:31
FIXED
Give Day One time to launch and update db when adding entry
1.0.35
2024-06-22 11:45
IMPROVED
Display answers to prompt when filling out entry
Launch Day One before trying to add entry. Day One’s database frequently gets updated and the command line tool will throw an error if Day One hasn’t been launched. So launch it hidden in the background, and then quit if it wasn’t running to begin with. This will cause the database to update.
Method documentation
1.0.34
2024-06-22 11:01
FIXED
Force -W1 so prompts always show up
1.0.33
2024-04-20 13:47
1.0.32
2024-04-20 13:43
IMPROVED
Added note to README that zip codes with leading zero must be quoted
FIXED
Spelling error in weather exception
1.0.31
2023-12-14 09:12
FIXED
Remove debug line
1.0.30
2023-12-13 17:22
FIXED
Weather.moon detection
1.0.29
2023-10-01 15:24
IMPROVED
Config option temp_in can be set to c or f to control what scale temps are recorded in
1.0.28
2023-09-30 11:01
IMPROVED
If creating an entry for a past date, get the historical weather for that date
Add weather.moon type for getting moon phase, and include moon_phase in general weather data
1.0.27
2023-09-23 08:03
FIXED
Time comparisons for past entries failing when using a past date for entry
1.0.26
2023-09-20 19:18
FIXED
Nil entry error on conditional questions
1.0.25
2023-09-20 09:40
FIXED
Coloring of min/max on numeric questions
1.0.24
2023-09-20 09:12
1.0.23
2023-09-20 08:30
NEW
Question types weather.forecast and weather.current allow more specific weather entry types
Time-based conditions for questions and sections (condition: before noon or condition: < 12pm)
FIXED
Test for existence of dayone2 binary before attempting to write a Day One entry, provide error message
1.0.22
2023-09-18 10:23
IMPROVED
Removed colon from timestamp of individual entries to better work with Obsidian Sync
1.0.21
2023-09-13 11:00
IMPROVED
If gum is installed, continue using it, but no longer require gum to function
1.0.20
2023-09-12 19:10
IMPROVED
Store all numeric entries as floats, allowing decimal entries on the command line
FIXED
Ensure local time for date objects
1.0.19
2023-09-11 10:52
IMPROVED
Better output of date types in Markdown formats
1.0.18
2023-09-09 12:29
IMPROVED
Include the answers to all questions as YAML front matter when writing individual Markdown files. This allows for tools like obsidian-dataview to be used as parsers
FIXED
Daily markdown was being saved to /journal/entries/KEY/entries
Missing color library
1.0.17
2023-09-08 07:21
IMPROVED
More confirmation messages when saving
1.0.16
2023-09-07 11:12
IMPROVED
Use optparse for command line options
Completion-friendly list of journals with journal -l
1.0.15
2023-09-07 07:57
FIXED
Messed up the fix for nested keys
1.0.14
2023-09-07 07:35
FIXED
Keys nested with dot syntax were doubling
1.0.13
2023-09-07 06:57
NEW
Allow entries_folder setting for top level and individual journals to put JSON and Markdown files anywhere the user wants
IMPROVED
If a custom folder doesn’t exist, create it automatically
Updated documentatioh
1.0.12
2023-09-06 16:43
IMPROVED
Add note about ctrl-d to save multiline input
1.0.11
2023-09-06 16:37
IMPROVED
Write a demo config file for editing
1.0.10
2023-09-06 16:03
IMPROVED
Refactoring code
1.0.9
2023-09-06 11:58
NEW
If the second argument is a natural language date, use the parsed result instead of the current time for the entry