Hammerspoon: Toggl API Calls and Secret Tokens

hammerspoon logo

I use Hammerspoon to enhance my OSX with custom UI and automations, while also creating "monotasking" deep work environments to keep me focused.

Ok… I'm really excited to show you the tool in the next post, but some context has to be laid first.

Toggl

I've experimented with toggl for time tracking on and off for over a year now. My current methodology is lifted and modified from CGPGrey's comments in Cortex Ep. 101. To summarize his thoughts…

Grey is always running a timer, usually from Timery on iOS. He uses the act of choosing a timer as a moment of intention: "what am I intending to do?" He retroactively marks time spent unintentionally (e.g. ending up on social media instead of writing) as "bad." Periodically he will check the reports in Toggl, using the color coding to judge how much time he is spending on what moves the needle.

I really like the idea of timing as a means of intentionality. I haven't been timing my whole day, but the past month or two I've been experimenting with using time tracking next to my Focus Budget. I've isolated a few work activities that I highly value, and I'm attempting to graph how much time per week I spend on those high-value activities.

a pie graph, showing mostly light blue (shallow) and a little dark blue (deep)

Here is last week's report from toggl... light blue is things like meetings and time spent in slack/email. Dark blue is the activities I've decided will "move the needle."

I have a lot of room to grow. 🙇

And of course, this lends itself to automation, so I started writing a toggl.lua. (I was really surprised I couldn't find anything on github!) So it's time to hit some REST APIs with Hammerspoon!

An earlier version of this module used the curl executable, but once again Hammerspoon's handy stdlib has a hs.http library that works perfectly.

module.start_timer = function(project_id, description)
  local key = module.key()
  hs.http.asyncPost(
    "https://www.toggl.com/api/v8/time_entries/start",
    hs.json.encode(
      {
        ['time_entry'] = {
          ['description'] = description,
          ['pid'] = project_id,
          ['created_with'] = 'hammerspoon'
        }
      }
    ),
    {
      ["Content-Type"] = "application/json; charset=UTF-8",
      ["Authorization"] = "Basic " .. hs.base64.encode(key .. ":api_token")
    },
    function(http_number, body, headers)
      print(http_number)
      print(body)
    end
  )
end

Between hs.json and hs.base64, writing a simple toggl client was really fun and easy!1 I wrote some other functions, but they pretty much all follow the same structure.

Now I have the ability to start, stop, and view the current timer from within my Hammerspoon setup!

Handling secrets via json

As I got into writing the code for toggl.lua, I realized I needed an easy way to get secrets like API keys into memory. I started with a simple file loading system, then realized that hs.json is perfect for this.

Here is the entire module.

module = {}

module.start = function()
  hs.settings.set("secrets", hs.json.read(".secrets.json"))
end

return module

Yep. Isn't that beautiful? I could add some error handling for when the file doesn't exist and such, but it's simple enough. As it is, it supports inserting a nested table into hs.settings that you can access from any module.

Here's an example of a .secrets.json for my uses...

{
  "standupURL": "http://evantravers.com",
  "standupCall": "https://meet.google.com/respect-op-sec",
  "toggl": {
    "key": "<my key>",
    "projects": {
      "communications": "123456789",
      "meetings": "123456789",
      "planning": "123456789",
      "reading": "123456789",
      "design": "123456789",
      "research": "123456789",
      "play": "123456789",
      "leadership": "123456789"
    }
  }
}

I can access any of these keys using dot notation... like:

hs.settings.get('secret').toggl.key.

To wrap that up and add some error handling, I have some small abstractions like this one:

module.key = function()
  if hs.settings.get("secrets").toggl.key then
    return hs.settings.get("secrets").toggl.key
  else
    print("You need to load a Toggl.com API key in to hs.settings under secrets.toggl.key")
  end
end

hs.settings is a cool little module for some shared state. It's even persistent between sessions if you want.

Well… we are nearly at the finish line. If you are still reading these posts, thanks. I'm glad there's another person who cares. I have been so excited to share the next idea with y'all…


  1. I should say… I really dislike lua as a language, but the two modules I've written here almost make me like it a little. 


Changelog
  • 2022-06-08 11:31:29 -0500
    Rename articles

  • 2020-09-28 09:20:43 -0500
    Change repo name

    I decided to change the name of my hammerspoon configuration repo to
    `hammerspoon-config` to avoid confusion... it's not the actual
    hammerspoon executable, it's a configuration for that executable.

  • 2020-06-18 14:26:02 -0500
    Move everything to CST

    Don't know why I didn't do that before. It caused _no_ end of
    problems.

  • 2020-06-14 15:51:50 -0500
    Update articles with series and series partials

    After the refactor, needed to move the series metadata to the actual
    name.

  • 2020-06-13 15:41:50 -0500
    Try to reduce the scrollbar

  • 2020-06-13 08:53:29 -0500
    Post: Toggl API and Secrets