profile

Hi! I'm Moncef Belyamani

Automate GitHub API Calls With Ruby, Keyboard Maestro, and 1Password CLI

Published over 1 year agoĀ ā€¢Ā 5 min read

Hi Reader šŸ‘‹šŸ¼

Happy Sunday! I hope you and your loved ones are doing well.

Earlier this week, I found a great use case for the 1Password CLI that hadn't occurred to me before. I'm gonna use it a lot more often whenever I can!

If this email doesn't look right, or if you prefer reading on my site, you can click the title link below.

Automate GitHub API Calls With Ruby, Keyboard Maestro, and 1Password CLI

One of the perks of the ā€œUltimateā€ version of Ruby on Mac is access to the private GitHub repo. As a developer ā€” especially one who loves automation ā€” it was tempting to try to completely automate inviting new Ultimate customers to the repo.

To do that would require implementing a custom checkout that captures the customerā€™s GitHub username, so I can then pass it on to the Paddle checkout flow. Paddle unfortunately doesnā€™t support adding custom fields to their checkout.

From there, I would only need to add a few lines of code to my existing small Rails app that receives the Paddle webhook. Iā€™m currently using it to automatically add/update customers in my ConvertKit account, so I can easily segment them based on which product they bought, calculate their lifetime value, auto-populate a field with their upgrade coupon, and other useful things.

After extracting the GitHub username from the Paddle payload, I would use the octokit gem to add the customer as a read-only collaborator to the repo. Something like this:

client = Octokit::Client.new(access_token: github_token)
client.add_collaborator(repo, username, permission: 'pull')

Given that Iā€™m only getting a few orders of Ultimate per week, I thought I would practice the fine art of flintstoning and invite each user manually for now. However, that doesnā€™t mean I have to do it the slow way each time.

When a customer emails me to request access, all I have to do is copy their username from the email they sent me, then I press āŒƒ-āŒ„-āŒ˜-A, and itā€™s done! This automation uses Keyboard Maestro, the GitHub API, and 1Password CLI.

Hereā€™s what the Keyboard Maestro macro looks like:

Keyboard Maestro Macro to add a collaborator to a private GitHub repo

While you can run scripts directly in Keyboard Maestro, I chose to run the script from the project folder in iTerm because the octokit gem is already installed there. Hereā€™s what the add_collab.rb file looks like:

require 'octokit'

def repo
  "rubyonmac/rom-ultimate"
end

def github_token
  `op item get "add_collab GH token" --fields label=notesPlain`
end

def client
  @client ||= Octokit::Client.new(access_token: github_token)
end

customer_github_username = ARGV[0]

puts "adding collaborator #{customer_github_username}"
response = client.add_collaborator(repo, customer_github_username, permission: 'pull')
puts "response: #{response}"

And hereā€™s the Gemfile:

source "https://rubygems.org"
ruby File.read(".ruby-version").strip
gem "octokit"

If youā€™ve read my previous automation guides featuring Keyboard Maestro, youā€™ll recall that it comes with many handy tokens that are placeholders for data that would otherwise require complicated code to fetch. In this case, Iā€™m using the %SystemClipboard% token to pass in the username (that I copied from the customerā€™s email) as an argument to the Ruby script. When you pass an argument to a Ruby script, you can access it via ARGV[0].

For security reasons, I need to provide the Octokit gem with a valid GitHub token associated with my account to be able to make this particular GitHub API call. The way I create this token is by adding a new Personal Access Token, and give it the appropriate scopes: admin:org and repo. I also set it to expire after 30 days.

Because I use two Macs at home, I keep all my projects on GitHub so I can easily have the latest code on both computers. Even on my private repos, I gitignore files that contain secrets (like GitHub tokens) for added security, and also out of habit.

In the past, this would require copying the secret file (such as .envrc if using direnv) from one computer to the other, and then updating it on both computers each time I renew it. It also requires remembering to back up the gitignored file on an external drive if I ever replace my Macs.

But now that I discovered the 1Password CLI, I can get rid of .envrc, and I donā€™t need to worry about copying files back and forth or backing anything up. I can fetch the token from my 1Password account, which is automatically available on both computers.

What I like most about this approach is that I no longer need to have any secrets stored in plain text on my computer (except in 1Password)! I can also safely make this repo public if I wanted to.

So, instead of the usual ENV['GITHUB_TOKEN'], I can fetch the token with the 1Password CLI op tool:

def github_token
  `op item get "add_collab GH token" --fields label=notesPlain`
end

In Ruby, you can run shell commands by surrounding them with backticks. You might also be familiar with the system command, but it doesnā€™t return the output of the command. It returns true if the command succeeds (with a zero exit status). Since I want the actual output of the command, I need the backticks.

As you might guess from the op command, the token is stored in a Secure Note in 1Password called ā€œadd_collab GH tokenā€. I figured out the full command by reading the documentation for item get, and then running just this command at first:

op item get "add_collab GH token"

which returned something like this:

ID:          some_unique_id
Title:       add_collab GH token
Vault:       Personal
Created:     4 days ago
Updated:     4 days ago by Moncef Belyamani
Favorite:    false
Version:     1
Category:    SECURE_NOTE
Fields:
  notesPlain:    my_github_token

Thatā€™s how I knew that the label I needed was notesPlain. Hereā€™s the full command again:

op item get "add_collab GH token" --fields label=notesPlain

To make this more robust, I could redirect stderr to stdout by adding 2>&1 to the end of the command, then store the result in a variable, and only call the GitHub API if thereā€™s no error. Something like this:

def github_token
  token = `op item get "add_collab GH token" --fields label=notesPlain 2>&1`
  if token.include?("ERROR")
    puts "Failed to fetch token from 1Password: #{token}"
    nil
  else
    token
  end
end

def client
  @client ||= Octokit::Client.new(access_token: github_token)
end

if github_token
  puts "adding collaborator #{ARGV[0]}"
  response = client.add_collaborator(repo, ARGV[0], permission: 'pull')
  puts "response: #{response}"
end

The reason for redirecting stderr to the output is to be able to read and store the error message. Without the redirection, if thereā€™s an error, token will just be an empty string.

Alternatively, I could still use direnv by calling the op command in .envrc, like this:

export GITHUB_TOKEN=`op item get "add_collab GH token" --fields label=notesPlain`

Since thereā€™s no secrets in it anymore, I would be able to commit .envrc to the repo. And if thereā€™s an error fetching the token, I would see it after running direnv allow.

To save even more time, I could automate the process of verifying that the person who is requesting access did indeed buy Ruby on Mac Ultimate. Again, Keyboard Maestro makes this easy with the %MailSender% token.

I would then pass this email address as a second argument to my Ruby script, and then use the ConvertKit API via the convertkit-ruby gem to see if thereā€™s an existing entry for that email address, and that the custom field that indicates they purchased Ultimate is filled in.

There you have it. Thanks to Keyboard Maestro, 1Password CLI, the Octokit gem, and a few lines of Ruby, I save about 30 seconds per customer compared to doing everything manually via the GitHub site. With 49 Ultimate customers so far (I just launched the Ultimate version about a month ago in late July 2022), thatā€™s about 25 minutes saved so far!

ā€‹

As always, if you have any questions, suggestions, or want to say hi, just hit reply. All responses go directly to my inbox. With an email newsletter, it's hard to get feedback, so if you could let me know what you thought of this article, it would make my day. Even if it's just one or two words or emoji, like "great", "chef's kiss", šŸŽ‰, "sucked", "thumbs down", šŸ’©.

Until next time, take care!

Moncef

P.S. If you missed the previous article, here it is: Automate Context Switching With Bunchā€‹

P.P.S I'm going on vacation this week. The next automation guide will go out in about 2 weeks.

ā€‹

Hi! I'm Moncef Belyamani

Every week, I send out an automation tutorial that will save you time and make you more productive. I also write about being a solopreneur, and building helpful things with Ruby. Join 2853 others who value their time.

Read more from Hi! I'm Moncef Belyamani

Hi Reader! This week's automation guide is about a free but powerful Mac app called Bunch. I had heard of it years ago but never took the time to explore it in detail. Until now, and it has proven very useful so far. I'm not sure how the code samples will look like in your email, so you might prefer to read this on my site by clicking the title below. Automate Context Switching With Bunch You sit down to work on a feature, and wake up your Mac. Oh hey, Slack is open. You decide to check it...

over 1 year agoĀ ā€¢Ā 12 min read
PopClip default extensions

Hi Reader! This week's automation guide is about a little-known app called PopClip. PopClip was originally released in 2011, but I didnā€™t hear about it until four years ago, and Iā€™m sure there are still a lot of people who donā€™t know about it. Itā€™s one of the many useful apps you can discover and quickly install with the ā€œUltimateā€ version of Ruby on Mac. You can pick and choose from hundreds of Mac apps, fonts, and dev tools in the included Brewfile, and Ruby on Mac will install them all at...

almost 2 years agoĀ ā€¢Ā 2 min read

Hi Reader! This week's automation tip is a simple one but packs a punch. Itā€™s 2022, and there are still annoying sites that block pasting into form fields, for passwords, or your bankā€™s account and routing numbers. If you look up how to bypass this copy-paste restriction, the three most common solutions all have downsides: Use a browser extension, that might only work in certain browsers and only on some sites Change the Firefox configuration settings, which comes with a ā€œProceed with...

almost 2 years agoĀ ā€¢Ā 2 min read
Share this post