My Tools

My Tools

My Tools

This page is my attempt to define the tools that I use and the context in which I use them.

This page is a work-in-progress and honestly will probably never be finished since I'm constantly updating how I work and therefore the tools/systems that I use are always evolving too.


I start my day by having a look at my DailyAgenda.

My DailyAgenda is a consolidated view of my day which is built by pulling information from the various tools that I use to manage things like my calendar and to-do's. The exact contents of my DailyAgenda have evolved over the years and continues to evolve as I grow and change.

The process that builds my DailyAgenda runs every morning at 6am. My DailyAgenda is generated in markdown and is sent to me via e-mail and is added as a new daily note in my Obsidian vault. Often my initial view of the DailyAgenda is in my e-mail before I make it into the office, but once in the office, I open it in Obsidian (which is always open).

My DailyAgenda consists of inspirational quotes/affirmations, my ToDo items, Calendar events, Local Weather Information and TV Highlights.


My Daily Agenda starts with a quote from Since my DailyAgenda is primarily consumed in Obsidian, I convert it to Obsidian specific markdown:

/usr/bin/curl -s -S |
    /usr/local/bin/jq -r 'map("> [!quote]\n> " + .q + "  \n> --" + .a)|.[]'

I also add a daily affirmation:

/usr/bin/curl -s -S | /usr/local/bin/jq -r .affirmation


I manage my events in Google calendars. I have one calendar that is for my personal events, another that is for family events, another for my travel schedule (that was very useful when I traveled a lot for work), another for Ham Radio events, and another for Off-Road events. I also have calendars for my work accounts. All of which I combine to view in Apple's Calendar application.


UPDATE: This process replaces My Old Calendar Process which was much more complicated, but worked with the tools I had available at the time.

I now use icalBuddy to read the calendar information from the calendars I have in Apple's Calendar application. Rendering the update calendar script and Remind obsolete, while solving the Google Apps Calendar challenges in the process!

My configuration for icalBuddy is simple, I just have the includeCals setup to include the calendars that I want to see in my daily agenda. With that in place, I use the command line options to format the list as a simple markdown checklist that gets included in my daily agenda:

icalBuddy -cf ~/.icalBuddyConfig.plist -nc -b '- [ ] ' -iep "title,datetime" -ps "| |" -po "datetime,title" -tf "%H:%M" -df "" eventsToday | uniq

Built-in Calendar Reminders

In addition to the information in the ical calendars, I have a few recurring events in the default Unix calendar format and use the built-in information for things like holidays and history as part of my DailyAgenda. To fetch these reminders and convert them to markdown, I use the following one-line script:

calendar -A 0 | tr '\n' '⏎' | sed -e 's/[⏎]* //g' | tr '⏎' '\n' | sed 's/^/- /'


The primary tool that I use to manage my ToDo list is is OmniFocus. I try to follow a GTD methodology and I find that OmniFocus facilitates that that very well.


My OmniFocus Setup

OmniFocus to todo.txt

To get information out of OmniFocus and into a format that was usable from the command line, and on non-iOS platforms (such as my Android tablet), I wrote another AppleScript. That synchronizes OmniFocus with todo.txt.

Todo.txt to markdown

To convert my todo.txt entries to markdown, I take the output of ls and "massage" it a bit to:

  • strip out the ID that my sync script adds,
  • remove the start date,
  • remove the task number and date entered,
  • convert JIRA ticket numbers to links, and
  • add custom checkboxes indicating started tasks (@flagged in my case) and waiting tasks.

Here's the script I use in case you find it useful: -P -+ -c -A -t -p -d ~/todo/todo.cfg ls |
sed -e 's/    \$ID:[^ ]*//' \
    -e 's/ start:[0-9-]* / /' \
    -e 's/^\([0-9]*\) [0-9-]* //g' \
    -e 's/\(PROJ-[0-9]*\)/[\1](https:\/\/\/browse\/\1)/' \
while read line
  if [[ "$line" =~ ^.*@Waiting.*$ ]] ; then
  elif [[ "$line" =~ ^.*@flagged.*$ ]] ; then
    flag=" "
  echo "$line" | sed "s/^/- [$flag] /"

Apple Reminders

In addition to OmniFocus, I also use Apple's Reminders app. Things that I put into this are less to-do (certainly not project based things) and more just things that I need to be reminded about at a particular time or place. I frequently say "Hey Siri, remind me to do something when I get home".

Local Weather Information

Spotter Activation Statement

While I am a trained weather spotter, that's not the primary reason for looking at the spotter activation statement. This statement gives me a quick way to tell if the weather over the next little while is something I need to pay attention to or not. If spotter activation is not needed, then I don't have to worry about it - I may still want to look at the forecast.

I get this information by looking at the NWS Hazardous Weather Outlook for my area, then parsing out the "SPOTTER INFORMATION STATEMENT". This is accomplished with a bit of awk and sed work:

/usr/local/bin/wget -q -O - "" | \
  awk '/\.SPOTTER INFORMATION STATEMENT/{flag=1;next}/\$\$/{flag=0}flag' | \
  tr '\n' ' ' | \
  sed -e 's/^[ \t]*//' -e 's/[ \t]*$//' -e 's/^/- /'

In addition to including this information in my DailyAgenda, I have a process that looks at the Spotter Activation Statement every hour and will send a notification using IFTTT if the statement changes, that way if something in the forecast changes during the day, I'm made aware of it. The trigger for IFTTT is just an e-mail to [email protected] with a specially crafted subject - works like a champ.


I get forecast information from the National Weather Service using their API. Specifically, I fetch,52/forecast and parse it down to just a couple of days worth of forecast information to be in my DailyAgenda.


while [ "$WEATHER_RESULTS" == "" ] ; do
  while [ "$WEATHER_DATA" == "" ] ; do
    WEATHER_DATA=$(/usr/bin/curl -s -S ',52/forecast' \
    -H 'pragma: no-cache' \
    -H 'cache-control: no-cache' \
    -H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9' \
    -H 'accept-language: en-US,en;q=0.9' \
    --compressed | /usr/local/bin/jq '.')
    if [ "$WEATHER_DATA" == "" ] ; then
      sleep 30
  echo "Weather Data: " >&2
  echo "$WEATHER_DATA" >&2
  WEATHER_RESULTS=$(echo "$WEATHER_DATA" | /usr/local/bin/jq -r '.properties.periods|map(select(.number < 7 and .number > 0)|"- **" + .name + "**: " + .detailedForecast) | .[]')
  if [ "$WEATHER_RESULTS" == "" ] ; then
    sleep 30

Space Weather

In addition to the normal forecast, I also look at the geophysical alerts. The geophysical alerts provide information about the current conditions for long distance HF radio communications. I don't fully understand how the solar-terrestrial indices impacts HF radio, but I hope to learn.

TV Highlights

Possibly the most frivolous item on my DailyAgenda is my TV Highlights.


This is where tt-rss, pocket, ifttt, slogger and Day-One come into play.


This is where Obsidian (and previously Joplin/Evernote) come into play as well as my paper notebook.



For a long time, I have found it useful to kept track of where I've been and the places that I've visited. Over the years I've used a variety of tools to facilitate capturing this information. Currently, my primary solution to this is to use Swarm. I capture data from Swarm into my calendar system using ifttt.

What I'm Doing

In January of 2022, I started using a command line tool written by Brett Terpstra called doing to keep track of what I've done and to take notes as I work during the day. This system is still new to my workflow, so I'm still making refinements.

Overall process

I start my workday by adding a task to the TimeSheet section of my doing file. I do this with a @meanwhile tag as that is designed to be a long-running entry that encompass smaller entries. I have an alias setup for this to make it an easy task of typing timesheet followed by the name of the project that I'm working on. If I switch projects during the day, I just execute another timesheet command to track the time appropriately.

The alias is super simple:

alias timesheet='doing meanwhile -s TimeSheet'

Once I have my day started, I want to ensure that I don't forget to track what I'm working on as the day goes on.

This starts with a visual indicator on my task bar using a small utility called AnyBar. I use a simple red/green indicator to let me know if I am currently tracking something or not.

To keep the AnyBar dot up-to-date and to prompt me to enter doing information, I have an AppleScript app that I call "Doing Update" that I start which will check my status every 10 minutes.

Doing Update

I started with a shell script. The shell script first does some maintenance on my doing file, moving tasks from the "Currently" section to Work or Personal sections based on tags.

Then, the script will update my Obsidian daily note, replacing the "doing" section in that with the output of doing today in markdown format that I do some further processing on to make it a little cleaner.

From there, the script gets what I say I'm currently working on and what is currently on my calendar. If I'm not doing something OR what I say I'm doing doesn't match my calendar, I'm prompted (with a nice bell) to enter what I'm doing - if my calendar says I'm in a meeting, that is auto-populated in the input box so I can easily accept that value and begin tracking that meeting.

anybar() {
  echo -n $1 | nc -4u -w0 localhost ${2:-1738};

doing move --no-label --bool=AND --tag work,done --to Work
doing move --no-label --bool=AND --tag personal,done --to Personal

OFILE="~/sync/Obsidian/DailyAgenda/$(date "+%Y-%m-%d").md"
DOING=$(doing today  --output markdown --no-time | gsed -z 's/\n/\\\n/g' | gsed -E -e 's/ @[^@\)]*\)//g' -e 's/ @([^ \*]*)/ #\1/g')

gsed -i "/## doing/,/---/c\\
---" "$OFILE"

C=$(doing view wjm)
MTG=$(icalBuddy -nc -b '' -iep 'title' -ea eventsNow | uniq)

if [[ "$C" != "$MTG"* ]] ; then

if [ "$C" == "" ] ; then
  anybar red
  if [ "$MTG" != "" ] ; then
    MTG="$MTG @meeting"
  afplay ding.wav &
  N=$(osascript -e "text returned of(display dialog \"What are you doing?\" default answer \"$MTG\")")
  if [ "$N" != "" ] ; then
    doing now $N
  anybar green

I have an abbreviated version of this script (I call that one that runs using doing's run-after configuration. This abbreviated script just updates my daily note and will set the AnyBar icon.

To have this script run every 10 minutes, I turned to launchd...and since icalBuddy requires access to the calendar, this presented some problems with Apple's security model.

My first step in solving this was to write an AppleScript app which calls the script. Once saved, I was able to execute the app interactively and give it the permissions it needed to access my calendars. That caused a minor annoyance in that every time it ran, the app started and my focus shifted to the app. So I then figured out how to have the app running continuously (thanks to helpful resources on the internet) while only prompting every 10 minutes, finally solving both the security issue and the pop-up annoyance.

-- Modeled from
on idle
	set runInterval to (10 * minutes) -- run on this schedule
	-- e.g. (10 * minutes) => 12:10:00, 12:20:00, 12:30:00, etc.
	-- or   (15 * minutes) => 12:15:00, 12:30:00, 12:45:00, etc.
	do shell script "/bin/bash /Users/mckeehan/src/DailyAgenda/"
	-- get the current time
	set c to time of (get current date)
	-- work out how far into the current runInterval we are
	-- and invert that to work out how long until the next one:
	set nextRun to runInterval - (c mod runInterval)
end idle

Improving the display

To make my to-do tasks and doing log a little easier to read, I'm taking advantage of Obsidian's minimal theme support for Alternate checkboxes

For my "doing" output, I'm using the following for my markdown template:

## <%= @page_title %>
<% @items.each do |i| %>
  - [<% if i[:finished] %>x<% elsif i[:flagged] %>*<% else%>/<% end %>] <%= i[:shortdate] %> <%= i[:title] %> <% if i[:time] && i[:time] != "00:00:00" %>[<%= i[:time] %>]<% end %><% if i[:note].length.positive? %><%= "\n    - " + i[:note].map{|n| n.strip }.join("\n    - ") %><% end %><% end %>

<%= @totals %>

Items that are not done are marked as incomplete, [/], items that are flagged get a star, [*] and finished items get a done checkbox [x].


I like to take photos. My primary camera is a bit old now, but has served me well, it's a Nikon D5100 that I've had for a while.

I have built a system for managing my photos and their associated metadata and making them available here. The heart of the system is DigiKam which I use to manage the images and metadata. One thing I get from DigiKam is the metadata for my images in a SQL format that I can easily manipulate (I should note that the metadata is also stored on the image so if DigiKam should ever fail me, I won't lose access to that information).

As with most of my processes, in order to minimize cost, I spend way too much time developing a solution. The details of a slightly older process are explained in my DigiKam to PHP Photo Album page...I'll update that soon to include details for how I publish them within this gatsby generated sit.

Raw List of Tools:

  • OmniFocus
  • Google calendar
  • TVSherpa
  • todo.txt
  • JIRA
  • Obsidian
  • Gatsby
  • Spark - email client
  • Slack
  • Pocket
  • Ifttt
  • Apple reminders (with Siri)
  • MacOS
  • Apache
  • tt-rss
  • pocket
  • Slogger
  • SchedulesDirect
  • digiKam
  • pen and paper