Replacing Luxafor's App with OSX Automator and Keyboard Shortcuts

A couple years ago, we invested in Luxafor flag lights for everyone on the team. These lights are attached to our monitors and we communicate with each other using them. If someone's light is green, they are "free" and you're welcome to drop by and interrupt what they're doing. If someone's light is red, they are in do-not-disturb mode for one reason or another. They might be on a zoom call or they might just not want to be interrupted.

The Luxafor lights come with an app that can control your flag light all sorts of ways. There are animated patterns. There's a pomodoro mode. It can integrate with other services to automatically set your color. And it can (sort of) let you program OS-wide keyboard shortcuts to set the flag color. When we first got our lights, this worked flawlessly. But Luxafor's app has degraded in stability over time and the keyboard shortcuts have taken a big hit. For many months, I've been unable to set up a shortcut for setting the flag to green. And my flag defaults to red whenever I disconnect and reconnect it. Since all we use the flags for is toggling between red and green, this regression has been painful.

I've pleaded with Luxafor to fix the critical bug of keyboard shortcuts being broken. I've heard nothing back. It's time to cut out the middle man. Here's what I wanted:

  1. An OS-wide shortcut that sets the light to green (^⌥⌘G)
  2. An OS-wide shortcut that sets the light to red (^⌥⌘R)
  3. For these shortcuts to reliably work every time I use them
  4. No need to launch an app or click anything in the UI

I didn't feel like this was asking too much, but it took a little time to get it figured out. Thankfully, Matt Goucher had already published a node-luxafor package to NPM that did all of the heavy lifting. With that in place, all I needed to do was:

  1. Write a CLI over top of node-luxafor
  2. Find a way to run the CLI from an OSX keyboard shortcut

The Node Luxafor CLI

I created node-luxafor-cli as a thin CLI wrapper around node-luxafor. Right now, it's just js files that can be invoked with node.

  • node green - sets the flag color to green
  • node red - sets the flag color to red
  • node index --color=blue - sets the flag color to blue
  • node index -r 255 -g 255 -b 255 - sets the flag color to white

This package has plenty of room for maturation, but it gives me all I needed to start.

Keyboard Shortcuts

The keyboard shortcuts were where I had the most to learn. Here's what was involved:

  1. Create Automator Services that run Shell Scripts to invoke node and run the node-luxafor-cli scripts
  2. Configure Keyboard Shortcuts to run the Automator Services

Automator Services

An Automator Service can register an application in application menus. With the right configuration, the service can be registered to be available in the menu of any application.

Here are the steps to create the Automator Services for Luxafor-Red and Luxafor-Green.

  1. git clone
  2. Open
  3. Choose "Service" as the type of document
  4. Select that the "Service receives: in "
  5. Select "Run Shell Script" from the actions library and drag it into the surface
  6. Ensure an appropriate Shell is selected
  7. In the script box, put in the equivalent of "node node-luxafor-cli/green", but you will need to provide your full path for node as well as the full path for where you cloned node-luxafor-cli - for me, it was /Users/jeffhandley/.nvm/version/node/v8.9.3/bin/node /Users/jeffhandley/code/node-luxafor-cli/green
  8. Press the Run button to test the service - your light should turn green
  9. Save the service as Luxafor-Color-Green

With this Automator Service created, follow the same steps to create one for setting the light to red.

Keyboard Shortcuts

This was finicky to figure out, but it turns out the biggest catch was how the Automator script was created. It must be a Service and it must be configured to use all applications. With that in place, the following should work for you.

  1. Open your System Preferences
  2. Go to your Keyboard preferences
  3. Select Shortcuts
  4. Choose Services on the left
  5. Scroll to the very end and the Luxafor-Color-Green and Luxafor-Color-Red services should be listed
  6. Click on the row at the right and get into the Add Shortcut mode
  7. Press the key combination you want to assign to each
  8. Close your Keyboard preferences window


With this in place, your keyboard shortcuts should work. If they don't, here are some settings to check that seem to be touchy.

  • The "Shell" selection in the Automator Service. Mine defaulted to /usr/bin/zsh, but that didn't save properly. When I opened the service back up in Automator (easily done from the Keyboard shortcuts list), the Shell field was blank. I set it to /bin/zsh and this has worked.
  • The Automator script was saved as a generic Workflow and it didn't show up in the General list of services. I had to use the Convert to... UI and choose to convert it to a Service.
  • The paths for node or the script were incorrect. Just be careful with these and test them inside the Automator UI.

author: Jeff Handley | posted @ Wednesday, March 7, 2018 5:10 PM

Why do I hate this code?

During a recent code review, I gave feedback asking for code like the following to be refactored:

function getPreferenceLevelText(companyName, companyPreferenceLevel, segmentType) {
    var preferenceLevel;

    if (companyPreferenceLevel == PREFERENCE_LEVEL_MOST_PREFERRED) {
        preferenceLevel = getLocalizedMessage("Most preferred");
    } else if (companyPreferenceLevel == PREFERENCE_LEVEL_LESS_PREFERRED) {
        preferenceLevel = getLocalizedMessage("Less preferred");
    } else if (companyPreferenceLevel == PREFERENCE_LEVEL_PREFERRED) {
        preferenceLevel = getLocalizedMessage("Preferred");
    } else {
        return null;

    if (segmentType == SEGMENT_TYPE_HOTEL) {
        return getLocalizedMessage("{0} hotel for {1}", preferenceLevel, companyName);
    } else {
        return getLocalizedMessage("{0} vendor for {1}", preferenceLevel, companyName);

My request was to avoid passing the segment type into the function and for the function to have hotel-specific code in it. Instead, I wanted to see the message template passed in, like this:

function getPreferenceLevelText(companyName, companyPreferenceLevel, messageTemplate) {
    var preferenceLevel;

    if (companyPreferenceLevel == PREFERENCE_LEVEL_MOST_PREFERRED) {
        preferenceLevel = getLocalizedMessage("Most preferred");
    } else if (companyPreferenceLevel == PREFERENCE_LEVEL_LESS_PREFERRED) {
        preferenceLevel = getLocalizedMessage("Less preferred");
    } else if (companyPreferenceLevel == PREFERENCE_LEVEL_PREFERRED) {
        preferenceLevel = getLocalizedMessage("Preferred");
    } else {
        return null;

    return getLocalizedMessage(messageTemplate, preferenceLevel, companyName);

This means that the callers will need to specify the message template and hotel displays will pass in the hotel-specific template whereas other segment types will pass in the "vendor" message.

The developer on the team working on this asked me why I wanted to see this change, commenting that this approach was easy and kept the calling code terse and even allowed the segment type argument to be optional. I'll be honest, I had a hard time explaining all of the reasons why I wanted to see this change made.

Can you clearly explain the benefits of the change?

author: Jeff Handley | posted @ Sunday, May 1, 2016 11:02 PM | Feedback (5)

Øredev 2015 - Using Node and React for LOB Apps

I had the privilege of attending Øredev again this year; it was the first week of November. At the conference, I presented two sessions that were both related to my experience this year using Node and React for building Line-of-Business applications at Concur.

Learning Node After a Career on Microsoft

In my first session, I presented my story of how I started using Node and React after previously dedicating my entire career to the Microsoft stack.

This was not a technical session, but rather one that covered the mental blockers we have in making platform changes, how I overcome those blockers and realized that building applications on the Microsoft platform for 15+ years actually trained me to make bold platform changes. I then covered the tools and processes I used to learn Node as a new platform, the mistakes I made and saw others making, and highlighted some key take-aways. Attendees of the session told me the guidance of "learn concepts, not libraries" was the most impactful lesson for them.

Session Abstract

After 15 years building web applications on the Microsoft stack, and several years working at Microsoft on ASP.NET and NuGet, I found myself using Node.js and React. Come hear how I survived the boldest change of my career and how you too can overcome the challenge of a platform change.

Building Line of Business Apps with Isomorphic React/Node

My second session was a technical one, illustrating the details about the Flux architecture pattern that works well with Node and React. But before I got to that point, I went through a historical recap of how I've seen LOB apps designed over the last 15 years, going all the way back to Classic ASP and showing the progression toward MVC and what problems arose when we started building rich applications with lots of client-side interactions.

By telling this tale of how LOB web apps have evolved, we can see that it's time for history to repeat itself and how Node/React/Flux can help us work through another evolution.

Session Abstract

Did you know that React and Node can be used to build good old-fashioned line of business applications? We'll look at how!

You see, we all grew up building web applications with server-side rendering. Then we were convinced that we should render in the browser--but that proved to be a maintenance nightmare for LOB applications. With React, Node, and Fluxible, we can build apps that initially render on the server and have the client take over from there. Best of all, we can do this by using a single programming model that you'll realize you already know.

author: Jeff Handley | posted @ Wednesday, November 18, 2015 2:53 PM | Feedback (0)

QuickReactions - Isomorphic Hello World with React and Node

While working through sample after sample for Node.js and React.js, I experienced a pattern that wasn’t very helpful. Instead of truly starting from scratch, the samples kept walking through step-by-step of cloning a working solution. They’d start with “Step 1: paste this fully-working code into this file” and “Step 2: paste this fully-working code into this other file.” I was having a hard time finding a breakdown of the concepts being applied.

I wanted to learn by starting truly from scratch and building the app up in logical, incremental steps. To accomplish the goal of learning this new material one concept at a time, I created a new project and then documented each new concept that was introduced in a giant file. I then transformed the giant README into a 19-step tutorial web site using GitHub Pages.

If you are feeling overwhelmed trying to learn Node and React, you might benefit from this QuickReactions tutorial.

Technorati Tags: ,,

author: Jeff Handley | posted @ Wednesday, May 20, 2015 10:23 AM | Feedback (2)

Following Passions (and Leaving Microsoft)

I decided to leave Microsoft; Friday, March 20th is my last day.


My family and I moved to Redmond almost 7 years ago so that I could join Microsoft.  After 13 years in the industry, it was my dream job: Creating a UI Framework that enterprise application developers would use for their web applications. The project was Alexandria, which became WCF RIA Services, and it helped developers use Silverlight for Line of Business applications.

As I blogged about when I moved out here, I had put together a 5-year plan for how to get a job at Microsoft building UI frameworks, but I ended up getting that job within just a few months. I was thrilled to work on RIA and have the opportunity to create software that became part of the .NET Framework and shipped to my mom’s computer. It was an honor to take what I learned building user interfaces for dozens of enterprise applications and create a framework that countless developers could benefit from.

With my experience on RIA, I got a taste of delivering frameworks and tools to large developer audiences, and that became a new direction for me.

Growing Scope

While working on RIA Services, I watched NuGet come into existence and I was immediately sold. While NuGet was still quite nascent, I started pitching to the RIA team that we should abandon our MSI and instead ship RIA as a collection of NuGet packages. When I became the dev lead for the project, it was one of the first new efforts I invested time into. I even blogged about my excitement around NuGet. NuGet became what I wanted to work on.

By chance, shortly after that blog post, a re-org happened. NuGet was going to become part of my group—and it didn’t have a dev lead! I jumped at the opportunity to become the project’s dev lead; in order to pick up ownership of NuGet I also needed to take ASP.NET Web Pages and Razor as well. Sold! Suddenly, I was the dev lead for WCF RIA Services, WCF for Silverlight, NuGet,, ASP.NET Web Pages, Razor, and a couple of other small projects. And there were 6 developers on my team. I immediately started working on how the team could become dedicated to NuGet.


When I became the dev lead for NuGet, version 1.5 had just shipped. We then shipped 1.6, 1.7, 1.8, 2.0, and several more releases leading up to NuGet 2.8. For over 2 years, we averaged 11 weeks between RTM releases, with an average of 85 issues addressed in each release. At the same time, we completely redesigned the gallery, re-implemented it from the ground up to run in Azure on the latest ASP.NET MVC bits, and we did the work in the open on GitHub.

NuGet grew and grew. Our usage was doubling time and time again. The project matured from being a “toy” that was used only for ASP.NET projects into something that almost every project system in Visual Studio was benefiting from. I spent a great deal of my time selling NuGet to teams and groups around the company, gaining broader and deeper adoption. It was exciting to watch the tables turn as we gained more acceptance. Over time, teams were coming to us instead of the other way around. Visual Studio started fixing bugs that made NuGet better. NuGet had arrived.


It was inevitable—its users wanted NuGet to become more natural. They wanted deep integration with the project systems—not just macros over top of VS actions. They wanted integration with the project templates. And with the build system. With every aspect of the development lifecycle, NuGet should be there and be supported. NuGet needed to become part of the platform.

This is where we are today. NuGet is no longer a toy—it’s truly become a first-class aspect of how developers work on the Microsoft platform. There is still a lot of work to get done to accomplish the goals we’ve set, but I believe the direction is right and the project is on path to get there.

Rewarding Projects

When I recognized that NuGet was on path to become part of the platform, I started thinking about what would be next. What would be the next round of goals for the project? And secondarily, what would be the next round of goals for me? Don’t get me wrong, there is still a lot of work to be done for NuGet to succeed in these goals—the team and the project have plenty of room for improvement, but I started assuming we’d succeed in execution on those items. So I sought out what passions I wanted to follow as I reached my 20th anniversary in the field.

At Øredev 2014’s speaker’s dinner at City Hall in Malmö, I was talking with someone from Jayway about passions and what makes a project rewarding. She asked me what the most rewarding project was that I’d ever worked on. My knee-jerk reaction was to name NuGet. But I held back and really thought about the question. Was NuGet really it? Was it RIA? Was it the web-based replacement for Ohio’s student information system mainframe? That project actually was more rewarding than NuGet! Was it Statsworld—the web-based fantasy football app that competed directly with CBS Sportsline? What about when I created a web-based system to run a cooking school for Proctor and Gamble? Those were great too! And then I kept going back through my career until I decided what my most rewarding project really was—and it is surprising.

Impact on Individuals

My very first professional software project was in high school. I created a DOS-based CRM system for a math teacher’s husband’s lawn care company. Imagine QuickBooks, but running in DOS. I sold it to him with a bound user manual and a custom printer driver for his dot-matrix printer—for $100. It even had mouse support using a library I created in QuickBasic. I think I made about $0.50/hour on that project and built it on my mom’s computer at her office, working nights after the office had closed.

When we first met, he asked if I could create something to print invoices so that he didn’t have to type each one by hand. I most certainly could. But I started asking him questions about what other routine tasks he had and I asserted that I could automate a great deal of his routine administrative work. When I delivered this software to him and trained him on it, his eyes lit up. A few weeks later when I was delivering a new round of floppy disks with some bug fixes, he told me I saved him about 40 hours per week.

That $100 DOS-based invoicing system for a self-employed lawn care professional is the most rewarding project of my career. That was my answer at Øredev and I knew then I needed to think more seriously about what was next for me.

Following Passions

Looking back on my career, I’ve always had passion for interviewing business owners and employees and finding ways to simplify and automate their administrative tasks. In fact, after I completed that lawn care project, I dreamed of owning my own software company—it even had a (horrible) name: HANDLinc. Computer Programming. My junior year in High School, I was telling people that when I grew up I wanted to create software to help other people run their businesses. In 2000, I co-founded and did just that. But somewhere between then and now, I lost sight of those objectives and found myself working on frameworks and tools for developers.

I have decided I want to return to building software for business owners and employees. I want to concentrate on user interfaces that simplify administrative tasks that cannot (yet) be automated. I want to work with non-developers and make their lives better and less frustrating—to make computers work for them instead of the other way around.


Friday, March 20, 2015 is my last day at Microsoft and my last day working on NuGet.

I start at Concur on Monday, March 23, 2015. I will be following my passions and I am very excited!


  1. Are you going to stay involved in NuGet?
    • I don’t think so. I’m going to be focused on returning to a different kind of work—I have a lot to learn and remember.
  2. Who is taking over NuGet?
    • Yishai Galatzer is the new Engineering Manager for the NuGet team at Microsoft
  3. Who should I connect with to talk about NuGet?
  4. Are you moving?
    • Nope. I’ll be working at Concur’s headquarters in downtown Bellevue, WA.

If you have other questions, feel free to reach out to me here or on Twitter (@jeffhandley).

author: Jeff Handley | posted @ Thursday, March 19, 2015 8:36 PM | Feedback (9)