Raycast, Shortcuts, Headspace

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. We have Raycast. We have Headspace 2.0. How am I using it?

The original user story for Headspace was born out of a desire to transform my computer usage into a more focused "monotasking" environment. I've adopted a few practices to enable monotasking:

  • I set an intention for each block of time
  • I close all applications that distract me from my intention
  • I set up a task-specific work environment
  • I track my time spent on that task

With that in mind, my computing "headspace" is as follows:

  • a rule describing applications I use for this task
  • an intention for the next block of time saved as a Toggl timer description
  • a classification of the next block or time saved as a Toggl timer project
  • some scripted behavior for this kind of work (open a project folder, align my windows, etc.)

For the past two and a half years, Headspace v1.0 handled all of this in lua using a from scratch UI built in hammerspoon. Headspace v2.0 only handles blocking, so let's implement the other parts!

Raycast

Raycast's Shortcuts Interface

As I mentioned in my Raycast discussion, if a Shortcut accepts text input, Raycast can treat that Shortcut like a little function with an argument. This text argument handles the "set an intention" UI1.

Raycast's Shortcuts extension gives you the ability to turn Shortcuts on or off for the default search, so I've turned most of my "utility functions" off and only enabled Headspace or similar Shortcuts.

Shortcuts

I'm driving the automation with Shortcuts. While I prefer to write plaintext code stored in git, but Shortcuts has one huge advantage: it syncs to my mobile devices and runs there too. I can use the same interface and scripts for both my homescreen and my desktop. I've enjoyed grabbing my phone and notebook to sketch UIs outside, knowing that I can track my time just the same as if I were at my computer. This means that each Headspace Shortcut should be compatible on Mac and iOS.

I wish I had been aware that the If Shortcut Action has a built-in Device Type == Mac conditional. It would have saved me a lot of time! To setup If this way, add an If Action and change the Input to Device Details:

showing the input options for a raw If action

Then click/touch the newly selected Device Type again to select Device Type:

Showing the sub-selections for Device Details

This unlocks conditionals for is, is not, and Direct Objects for all the Apple ecosystem. 🍏

While Raycast is the UI on desktop, on iOS or iPadOS I'm using the same launcher trick I've been using for a year.

Raycast launching a shortcut

Let's take a tour through some of the Spaces I've built.

Planning

Planning is part of the origin of the Headspace story. Every day I was laying out the same windows the same way and starting a timer. Then a lightbulb came on. Repeatable steps is a program.

Start a timer labeled planning
Split my screen between Things and Fantastical
Change my Fantastical Calendar Set to Focus Budget
Close applications tagged Communication and Distraction.

Where before I would run some wild UI-manipulating lua2 now I can use built in Shortcuts Actions like this:

Showing shortcuts UI for Planning

Thanks to the magic of Shortcuts, the layout and timer work 100% the same on all Macs and on my iPad. 👏

Things and Fantastical kindly provide Shortcuts Actions to manipulate and change them.

The lovely Timery by Joe Hribar has the best Shortcuts support, so where I was maintaining a from-scratch UI and network script in lua for Toggl, now I'm letting Joe do the heavy lifting. Thanks Joe!

Let's look at that final Run Shortcut controlling Hammerspoon at the end of Planning:

Shortcut: clear headspace

That's it. All it does is call the URL, and provide it a convenient handle to be shared across Shortcuts. As I was rebuilding my spaces, I realized there were only a couple types of Headspace rules in my system. Nearly all of them just "Block apps tagged distraction and communication," so there's a Shortcut now for that. Use Open URLs instead of Open X-Callback URL. Callback doesn't return because Headspace isn't that smart yet.

Shallow

Shallow is the opposite of Deep Work: distracted, un-focused multitasking. (Maybe I should rename it Distracted… I might use it less!)

Shallow's logic is very declarative but there's some hidden complexity. 🤷

Determine Intention from User or Front window
Start Timer with that Intention (using Timery -> Toggl)
Turn off Do not Disturb
Disable Headspace application blockers

And in Shortcuts:

showing the shortcuts app with Shallow opened

A common theme as we go along as that any moderately complex logic shared between Shortcuts gets extracted and maintained separately. (I'm a software developer, I can't help it!)

Get Input or Frontmost Window

Let's look at that first nested Shortcut. Every Headspace shortcut that can take an intention needs to have an Action that accepts text input. This can be Get Text from Input action, but here we use a nested shortcut called Get Input or Frontmost Window.

Here's the basic logic:

IF Shortcut has input (the Intention) passed to it
  RETURN Intention
ELSE
  IF Device Type is Mac
    GET the Title of the frontmost window
  ASK user what is their intention, with the Window Title (if available) as default.
  RETURN Intention

And here's what that looks like in Shortcuts.

Screen showing the shortcut for Get Input or Frontmost Window

Most of this follows the above logic exactly except… Yep. That's another nested shortcut shared by other Shortcuts. Let's quickly glance at Get Frontmost Window Title.

Get Frontmost Window Title

Get Frontmost Window Title

Simple except for the "guard clauses" that keep certain strange windows from being "frontmost." That's largely why this Shortcut is broken out and shared as OSX and my applications change some other conditions may need to be added and it's nice to change it in one place.

Deep

With some of the main functions explained, let's look at a meaty Headspace Shortcut: Deep. Deep represents the work I ought to be doing: focused, uninterrupted, intentional work.

In my day jobby-job I work as a User Experience Designer. I balance a couple of roles and tasks, so I'm interested in tracking how much time I spend on different design activities.

Here's the logic:

Determine Intention from User or Front window
IF Intention contains "Design"
  Start Design Timer with that Intention
ELSE IF Intention contains "Research"
  Start Research Timer with that Intention
ELSE
  Start Deep Timer with that Intention
OPEN Things to my list of $High focus items
Close and Block Distractions and Communications

You can see the family resemblence to Shallow's logic. They share a structure, but there's some extra time tracking logic here.

And here's how it looks in Shortcuts (a little longer than the last two…)

Shortcut: Deep

The Run Shortcut target Block Distraction and Communication looks exactly like you may predict:

Shortcut: Block Distraction

Block Distraction and Communication is shared between several of my Headspace Shortcuts. Very common. I should use it more. 😜

I realized while writing this Shortcut that it could absorb and contain two or three of the options I was maintaining before: I used to have a Design, Deep, and Writing spaces in lua, just to have different Projects in Toggl. Now I can just have one master Deep shortcut.

Wasted

Sadly, I'm not as focused as I make myself appear on the internet. I often find myself some way into a block and realize I'm lost in the weeds, ignoring the original Intention.

Since I want to be a good steward of my God-given time, I typically track each wasted block just to nudge me towards better behavior.

I have been manually adding a wasted block until I realized there is repeatable logic here:

Stop the current time entry
Add a time block categorized Unintentionality for that same time

So now it's a Shortcut called Wasted ☠️:

Shortcut: Wasted

If Timery supported deleting a Time Entry, I'd probably delete the duplicate entry, but this works great. I'm excited to see how showing the report of wasted time each time I run the Shortcut will hopefully change my behavior.

As long as this madhouse epic is, you would be surprised how much I cut out of this post. I only covered three of my (at present) eleven Headspace Shortcuts… there is still a lot to share.

While I would like to share the actual Shortcuts with you, but they require Timery, Things, Fantastical, other things specific to my workflow. My hope was that these examples will spark your creativity to help you craft monotasking environments for your devices, applications, and purpose.

Think about what is the simplest expresssion of "how do I set myself up for my best work?" and start tinkering to see if its scriptable, build from there!

Let me know how I can help!


  1. Before I had two different chooser UIs, and occasionally would get frustrated when I was trying to launch a space from Alfred or an app from Headspace. Glad to have that gone. 

  2. Taken from a previous implementation

    Config.funcs.focus_budget = {
      setup = function()
        if tonumber(os.date("%H")) > 15 then
          if os.date("%a") == "Sun" or os.date("%a") == "Sat" or os.date("%a") == "Fri" then
            hs.urlevent.openURL("things:///show?id=upcoming&filter=%40Proctoru%2CEstimates")
          else
            hs.urlevent.openURL("things:///show?id=tomorrow&filter=%40Proctoru%2CEstimates")
          end
        else
          hs.urlevent.openURL("things:///show?id=today&filter=%40Proctoru%2CEstimates")
        end
    
        local things = hs.application.find('com.culturedcode.ThingsMac')
        local fantastical = hs.application.find('com.flexibits.fantastical2.mac')
    
        local upcoming = things:focusedWindow()
        upcoming:application():selectMenuItem("Hide Sidebar")
    
        local cal = fantastical:focusedWindow()
        cal:application():selectMenuItem("Hide Sidebar")
        cal:application():selectMenuItem("Focus Budget")
        cal:application():selectMenuItem("By Week")
      end,
      teardown = function()
        local fantastical = hs.application.find('com.flexibits.fantastical2.mac')
        fantastical:selectMenuItem("Show Sidebar")
        fantastical:selectMenuItem("Daily Focus")
      end
    }
    

Changelog
  • 2023-02-20 10:59:51 -0600
    Add mention of the Shortcut extension controls

  • 2023-02-20 10:53:26 -0600
    Adjust final paragraph

  • 2023-02-20 06:14:10 -0600
    Adjust date to post

  • 2023-02-19 20:46:08 -0600
    Publish

  • 2023-02-19 20:45:55 -0600
    Update date

  • 2023-02-18 21:42:00 -0600
    Take it down

  • 2023-02-18 21:41:32 -0600
    Removing some ellipsis

  • 2023-02-18 21:32:20 -0600
    Edits

  • 2023-02-18 14:53:20 -0600
    Draft: Raycast Post