# Advent 2022: 18. Bash Script to Display Coloured Plain-Text Beeminder Dashboard

**URL:** <https://forum.beeminder.com/t/advent-2022-18-bash-script-to-display-coloured-plain-text-beeminder-dashboard/10676>\
**Category:** uncategorized\
**Created:** [December 18, 2022, 9:41am UTC](https://forum.beeminder.com/t/advent-2022-18-bash-script-to-display-coloured-plain-text-beeminder-dashboard/10676 "2022-12-18T09:41:36Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![alys](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.beeminder.com/alys/32/4726_2.png) [@alys](https://forum.beeminder.com/u/alys)\
**Post date:** [December 18, 2022, 9:41am UTC](https://forum.beeminder.com/t/advent-2022-18-bash-script-to-display-coloured-plain-text-beeminder-dashboard/10676/1 "2022-12-18T09:41:36Z")

</div>

Today’s [Advent Calendar](https://forum.beeminder.com/t/beeminder-advent-calendar-december-2022/10635) 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](https://forum.beeminder.com/t/advent-2022-08-orange-is-the-new-red-blue-means-do/10654) this week! 😆

This script is purely to display data. It doesn’t let you add or edit datapoints.

 ![Screenshot from 2022-12-18 19-20-56](https://us1.discourse-cdn.com/flex019/uploads/beeminder/original/2X/0/0662e276b4e26c58897cd185c161ef7ac8146202.png)

> **alt text for screenshot**
>
> The image shows the data below but with the lines coloured green, blue, orange, or red, depending on goal deadline.
> 
> ```auto
> 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](https://curl.se/) for running an [API command](https://forum.beeminder.com/t/advent-2022-12-a-simple-introduction-to-beeminders-api/10662) to fetch all the recent goal data
- [jq](https://stedolan.github.io/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](https://forum.beeminder.com/t/advent-2022-06-pessimistic-presumption-in-do-more-goals-selfdestruct-datapoints/10646). 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:**
>
> ```auto
> #!/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

---

<div class="post-metadata">

**Author:** ![sheik](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.beeminder.com/sheik/32/5107_2.png) [@sheik](https://forum.beeminder.com/u/sheik)\
**Post date:** [December 20, 2022, 9:11pm UTC](https://forum.beeminder.com/t/advent-2022-18-bash-script-to-display-coloured-plain-text-beeminder-dashboard/10676/2 "2022-12-20T21:11:43Z")

</div>

This is really cool. But…

Are you actually running Linux??  
Well, not that I would expect anything else than my fellow Beeminder fanatics…  
But I’m still surprised.

The reason I say this is that your `bee.sh` script it blurts out tons of MacOS-specific errors on my computer.

 ![image](https://us1.discourse-cdn.com/flex019/uploads/beeminder/original/2X/2/23cc839f2c243c282d06c1f7cd70239022f36a18.png)

I won’t try to fix it though, because I’m on my way to _writing the official Beeminder for WearOS app_.

As I write these words, your script starts trembling in fear.

_“I don’t want to be forgotten!”_  
-bee.sh

_“don’t worry, I last about 20 hours. They’ll still need you to remember to breathe or drink water”_  
-pixel watch

---

<div class="post-metadata">

**Author:** ![adamwolf](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.beeminder.com/adamwolf/32/484_2.png) [@adamwolf](https://forum.beeminder.com/u/adamwolf)\
**Post date:** [December 20, 2022, 9:40pm UTC](https://forum.beeminder.com/t/advent-2022-18-bash-script-to-display-coloured-plain-text-beeminder-dashboard/10676/3 "2022-12-20T21:40:07Z")

</div>

`date -d` is in GNU date. It looks to be an extension to POSIX.2. It’s not in the `date` that ships with macOS (at least on 12.6), which I presume is BSD date.

`readarray` is part of bash 5 ([Bash Builtins (Bash Reference Manual)](https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-readarray)). I think macOS ships with bash 3.2.

I can take a stab at adjusting it!

---

<div class="post-metadata">

**Author:** ![sheik](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.beeminder.com/sheik/32/5107_2.png) [@sheik](https://forum.beeminder.com/u/sheik)\
**Post date:** [December 20, 2022, 9:49pm UTC](https://forum.beeminder.com/t/advent-2022-18-bash-script-to-display-coloured-plain-text-beeminder-dashboard/10676/4 "2022-12-20T21:49:10Z")

</div>

Well, if you like the challenge, I can guarantee you that I will use this script!

Also, how did you know about this? Was it from experience, did you google it, or is there something I’m missing? Are you some kind of computer guru?

_Not that I don’t like the web UI… But I do love my CLIs_

---

<div class="post-metadata">

**Author:** ![adamwolf](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.beeminder.com/adamwolf/32/484_2.png) [@adamwolf](https://forum.beeminder.com/u/adamwolf)\
**Post date:** [December 20, 2022, 9:57pm UTC](https://forum.beeminder.com/t/advent-2022-18-bash-script-to-display-coloured-plain-text-beeminder-dashboard/10676/5 "2022-12-20T21:57:21Z")

</div>

A little from experience, a little from Google, a little from having smashed my face against computers and dates for far too long 🙂

If you use Homebrew on your mac, you can `brew install coreutils`, and then you can run the GNU version of date as `gdate`. You could also install a newer bash that way, come to think of it, and then edit the script so that every instance of `date -d` is replaced with `gdate -d`, and then…

```auto
at 15:56:04 ❯ /opt/homebrew/bin/bash beeminder.sh

espanol_cm +12 in 3 days
duospanish +9 in 2 days
deutsch_cm +6 in 2 days
family_meetings +1 by midnight tomorrow
esperanto +10 by midnight tomorrow
duodeutsch +10 by midnight

```

Tada! (This isn’t quite what I had in mind–I still think we can wrangle that date command to work on systems with BSD date and GNU date–but sometimes sooner is better!)

---

<div class="post-metadata">

**Author:** ![alys](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.beeminder.com/alys/32/4726_2.png) [@alys](https://forum.beeminder.com/u/alys)\
**Post date:** [December 21, 2022, 3:53am UTC](https://forum.beeminder.com/t/advent-2022-18-bash-script-to-display-coloured-plain-text-beeminder-dashboard/10676/6 "2022-12-21T03:53:58Z")

</div>

Ah damn, I forgot that there’d be significant differences with the bash environment on MacOS / BSD. It’s been years since I used it. I’ve added a comment about it to the bottom of the post. @sheik thanks for asking about this and @adamwolf thanks for posting about fixes!

---

<div class="post-metadata">

**Author:** ![sheik](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.beeminder.com/sheik/32/5107_2.png) [@sheik](https://forum.beeminder.com/u/sheik)\
**Post date:** [January 2, 2023, 1:20pm UTC](https://forum.beeminder.com/t/advent-2022-18-bash-script-to-display-coloured-plain-text-beeminder-dashboard/10676/7 "2023-01-02T13:20:14Z")

</div>

Hello guys,

It works!

```bash
# here: download Alys' script to your home folder
# here: replace Beeminder token
brew install bash
brew install coreutils
# here: open bee.sh and replace all instances of `date -d` with `gdate -d`
# here: open your .zshrc / .bashrc and add: alias bee="/opt/homebrew/bin/bash bee.sh"
# here: open a new terminal window
bee

```

Thanks a lot @adamwolf, and also thansk @alys 🙏
