Today’s Advent Calendar post may be of interest only to people who use Linux, BSD, or Mac OS because it’s about a Bash script, but if anyone would like to comment with their own code that does something similar for any platform, that would be awesome! Feel free to link to other forum threads or external sites if you already have code elsewhere. You’re also welcome to post improvements to my code if you wish!
I do almost all my task management and Beeminder data entry on the command line, so this is a script I wrote for myself to display my upcoming goals in a plain-text dashboard. Goals due in more than 3 days are not displayed. The screenshot below shows a) what it looks like and b) that I’m not actually doing at all well on Blue Means Do this week!
This script is purely to display data. It doesn’t let you add or edit datapoints.
alt text for screenshot
The image shows the data below but with the lines coloured green, blue, orange, or red, depending on goal deadline.
breathing +00:10 in 3 days
water +335.25001 in 2 days
should +4 in 2 days
pomadayyo +1 in 2 days
fruitveg +6.794 in 2 days
stretches +3 by midnight tomorrow has DELETE datapoint
inboxes +3 by midnight tomorrow
gratitude +1 by midnight tomorrow
thisbe_firefox_tabs -1 by midnight
single_neck_stretch +11 by midnight has DELETE datapoint
exercise +3 by midnight
eat_responsibly +0.55 by midnight
clutterbox +4 by midnight has #SELFDESTRUCT datapoint
bk_dinosaurs +1 by midnight
bed_time_good +0.6 by midnight
aerobic +00:07 by 23:00
tax +00:11 in 57 minutes!
The script requires that you have installed:
- bash (standard on any *nix system I expect)
- curl for running an API command to fetch all the recent goal data
- jq for parsing the data (it’s JSON data)
You need to edit the script to insert your Auth Token and to specify a directory that the downloaded data can be stored in (or use the default one in the script). Script comments tell you where to edit - search for CUSTOMISE. You may also need to adjust the path to bash in the very first line, depending on your system.
After that, just run the script with no command-line arguments to download and display your data. Rerunning it like that will display the same data again (but the remaining time displays may change).
Rerunning it with the ‘-r’ option will cause new data to be fetched (i.e., do this after you’ve added datapoints).
Run it with ‘-h’ for help.
As you can see in the screenshot, the output will identify any goals that have datapoints with ‘#SELFDESTRUCT’ or ‘DELETE’ in their comments. You may find this useful if you use Pessimistic Presumption in Do More Goals. Note that only recent datapoints are searched for those comments. “Recent” is defined by Beeminder’s API; I’m not sure how far back it goes, but it’s at least a couple of days for my goals.
And here is the script:
#!/bin/bash
# Author: Alice Harris, 2022-12-18
# https://forum.beeminder.com/t/advent-2022-18-bash-script-to-display-coloured-plain-text-beeminder-dashboard/10676
# Share and Enjoy
if [[ "$1" == "-h" ]]; then
echo
echo "Beeminder summary script"
echo
echo "A command-line tool to fetch your Beeminder goal data and display the most urgent goals with colours."
echo "Requires curl and jq (https://curl.se/ and https://stedolan.github.io/jq/)."
echo
echo "Goal data will be saved locally - configure the BEEMINDER_TEMP_DIR variable for the storage directory."
echo
echo "Configure the BEEMINDER_TOKEN variable to hold your Auth Token from https://www.beeminder.com/settings/account#account-permissions"
echo
echo "Run as '$0' for normal use."
echo "That will fetch and save the data if not already present but then will use the same saved data every time."
echo
echo "Run with -r option to erase and refetch the stored data: '$0 -r'"
echo
echo "Also shows if the goal has recent datapoints commented with '#SELFDESTRUCT' or 'DELETE'."
echo "See https://forum.beeminder.com/t/advent-2022-06-pessimistic-presumption-in-do-more-goals/10646 for why."
echo
exit 0
fi
erase_stored_data=
# use the "-r" command-line option to erase stored data (causes it to be refetched)
if [[ "$1" == "-r" ]]; then
erase_stored_data=on
fi
BEEMINDER_TEMP_DIR="/tmp/beeminder-data" # CUSTOMISE THIS - a directory that data about your goals will be downloaded to
# The directory and its parent directories will be created if they don't exist.
BEEMINDER_TOKEN="AbC12-abcd1234efgHIJ" # CUSTOMISE THIS with your Auth Token from https://www.beeminder.com/settings/account#account-permissions
## END OF REQUIRED CUSTOMISATION
# define colour codes for terminal output - see https://stackoverflow.com/a/5947802
NORMAL="\033[0;39m"
RED="\033[1;31m"
ORANGE="\033[1;33m"
BLUE="\033[1;34m"
GREEN="\033[1;32m"
mkdir -p $BEEMINDER_TEMP_DIR
all_goals_file="$BEEMINDER_TEMP_DIR/goals-raw-data" # downloaded JSON data about your goals
all_goals_file_pretty="$BEEMINDER_TEMP_DIR/goals-raw-data-pretty" # the same data but neatened for easier reading (not needed for this script but may be interesting)
goals_file_coloured_lines="$BEEMINDER_TEMP_DIR/goals-coloured-lines" # human-friendly, coloured list of the most urgent goals
# Use the "-r" command-line option to erase stored data (causes the data to be refetched):
[[ $erase_stored_data ]] && rm -f -- "$all_goals_file" "$all_goals_file_pretty"
rm -f -- "$goals_file_coloured_lines" # always delete this since we recreate it from the stored data
if [[ ! -f "$all_goals_file" ]]; then
echo "fetching Beeminder goal data..."
echo
curl -f -s "https://www.beeminder.com/api/v1/users/me/goals.json?auth_token=$BEEMINDER_TOKEN" -o "$all_goals_file" >/dev/null
if [[ ! -f "$all_goals_file" ]]; then
echo "ERROR: file '$all_goals_file' was not created" >&2
echo "I am about to try to refetch the data and show you the output for troubleshooting." >&2
echo >&2
curl "https://www.beeminder.com/api/v1/users/me/goals.json?auth_token=$BEEMINDER_TOKEN"
echo >&2
echo >&2
exit 1
fi
cat $all_goals_file | jq . > $all_goals_file_pretty
fi
date_now=$( date "+%s" )
date_start_of_day_0=$( date -d "today 0" "+%s" ) # start of today
date_start_of_day_1=$(( $date_start_of_day_0 + 60*60*24 * 1 ))
date_start_of_day_2=$(( $date_start_of_day_0 + 60*60*24 * 2 ))
date_start_of_day_3=$(( $date_start_of_day_0 + 60*60*24 * 3 ))
date_start_of_day_4=$(( $date_start_of_day_0 + 60*60*24 * 4 ))
# Yeah, that's messy. Ah well. :)
# With those variables, the script will show you all goals due within the next 3 days inclusive.
# If you want to see more days, add more variables and adjust the if-elif block below where the variables are used.
readarray -t all_goals_summarised < <( cat "$all_goals_file" | jq -r ". | .[] | \"\(.losedate)\t\(.slug)\t\(.baremin)\t\(.limsum)\t\(.recent_data)\"" | sort -nr )
# That code uses jq to parse the downloaded goal data,
# extract the goalnames (slugs), time remaining, etc,
# and store the extracted values in a bash array called all_goals_summarised
# Process one goal at a time, working out what colour to use for it, and what data to display:
for one_goal in "${all_goals_summarised[@]}"; do
IFS=$'\t'
one_goal_arr=($one_goal)
losedate="${one_goal_arr[0]}"
goalname="${one_goal_arr[1]}" # this is 'slug' in the JSON data
baremin="${one_goal_arr[2]}"
limsum="${one_goal_arr[3]}"
recent_data="${one_goal_arr[4]}"
losetime=$( date -d "@$((losedate + 1))" "+%H:%M" )
# That converts the deadline from seconds since epoch ('@') to HH:MM.
# The '+ 1' part adds one second to the deadline to convert it from (for example) 22:59 to 23:00.
# This means the displayed deadline is not strictly accurate but I find it more intuitive and neater.
# Remove '+ 1" from the line above to use the exact deadline from your data.
[[ $losetime == "00:00" ]] || [[ $losetime == "23:59" ]] && losetime="midnight"
# That causes the display to say "midnight" instead of "00:00" or "23:59".
# Comment out that line if you don't want that.
display_goal=on
if (( $losedate < $date_start_of_day_1 )); then
seconds_remaining_till_derail=$(( $losedate - $date_now ))
if (( $seconds_remaining_till_derail < 60*60*1 )); then
# derailment is less than an hour away so we change the displayed text to make it more obvious
due_string=$( date -ud @$seconds_remaining_till_derail "+in %M minutes!" )
else
due_string="by $losetime"
fi
colour=$RED
elif (( $losedate < $date_start_of_day_2 )); then
due_string="by $losetime tomorrow"
colour=$ORANGE
elif (( $losedate < $date_start_of_day_3 )); then
due_string="in 2 days"
colour=$BLUE
elif (( $losedate < $date_start_of_day_4 )); then
due_string="in 3 days"
colour=$GREEN
else
due_string="in 4 or more days"
display_goal= # turns off the display of this goal
fi
delete_flag=
selfdestruct_flag=
[[ "$recent_data" =~ DELETE ]] && delete_flag="\thas DELETE datapoint"
[[ "$recent_data" =~ \#SELFDESTRUCT ]] && selfdestruct_flag="\thas #SELFDESTRUCT datapoint"
if [[ $display_goal ]]; then
echo -e "$colour$goalname\t$baremin\t$due_string$delete_flag$selfdestruct_flag$NORMAL" >> $goals_file_coloured_lines
fi
done
echo
cat $goals_file_coloured_lines | column -s" " -t
echo
exit 0
EDIT:
For use on MacOS or BSD, you may run into trouble with GNU date not being present or with an older version of bash not containing readarray
. See the comments for a fixes from @adamwolf and @sheik