Moncef Belyamani's Coding GuidesCoding guides for beginners and beyond.https://www.moncefbelyamani.com/2022-08-26T13:21:44-04:00Moncef BelyamaniAutomate GitHub API Calls With Ruby, Keyboard Maestro, and 1Password CLIhttps://www.moncefbelyamani.com/automate-github-api-calls-with-ruby-keyboard-maestro-and-1password-cli/2022-08-26T13:21:44-04:002022-08-30T22:01:30-04:00Moncef Belyamani<p>One of the perks of the “Ultimate” version of <a href="https://www.rubyonmac.dev/#ultimate">Ruby on Mac</a> 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.</p>
<p>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 <a href="https://paddle.com">Paddle</a> checkout flow. Paddle unfortunately doesn’t support adding custom fields to their checkout.</p>
<p>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 <a href="https://convertkit.com">ConvertKit</a> 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.</p>
<p>After extracting the GitHub username from the Paddle payload, I would use the <a href="https://github.com/octokit/octokit.rb">octokit</a> gem to add the customer as a read-only collaborator to the repo. Something like this:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">client</span> <span class="o">=</span> <span class="no">Octokit</span><span class="o">::</span><span class="no">Client</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">access_token: </span><span class="n">github_token</span><span class="p">)</span>
<span class="n">client</span><span class="p">.</span><span class="nf">add_collaborator</span><span class="p">(</span><span class="n">repo</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="ss">permission: </span><span class="s1">'pull'</span><span class="p">)</span>
</code></pre></div>
<p>Given that I’m only getting a few orders of Ultimate per week, I thought I would practice <a href="https://stackingthebricks.com/the-fine-art-of-flintstoning/">the fine art of flintstoning</a> and invite each user manually for now. However, that doesn’t mean I have to do it the slow way each time. </p>
<p>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 <code>⌃-⌥-⌘-A</code>, and it’s done!
This automation uses <a href="https://www.keyboardmaestro.com/main/">Keyboard Maestro</a>, the GitHub API, and <a href="https://developer.1password.com/docs/cli/">1Password CLI</a>.</p>
<p>Here’s what the Keyboard Maestro macro looks like:</p>
<p><a href="/images/km_macro_add_github_collaborator.png" target="_blank"><img src="/images/km_macro_add_github_collaborator-compressed.jpg" alt="Keyboard Maestro Macro to add a collaborator to a private GitHub repo" width="750" height="660"/></a></p>
<p>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 <code>add_collab.rb</code> file looks like:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="nb">require</span> <span class="s1">'octokit'</span>
<span class="k">def</span> <span class="nf">repo</span>
<span class="s2">"rubyonmac/rom-ultimate"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">github_token</span>
<span class="sb">`op item get "add_collab GH token" --fields label=notesPlain`</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">client</span>
<span class="vi">@client</span> <span class="o">||=</span> <span class="no">Octokit</span><span class="o">::</span><span class="no">Client</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">access_token: </span><span class="n">github_token</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">customer_github_username</span> <span class="o">=</span> <span class="no">ARGV</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="nb">puts</span> <span class="s2">"adding collaborator </span><span class="si">#{</span><span class="n">customer_github_username</span><span class="si">}</span><span class="s2">"</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="nf">add_collaborator</span><span class="p">(</span><span class="n">repo</span><span class="p">,</span> <span class="n">customer_github_username</span><span class="p">,</span> <span class="ss">permission: </span><span class="s1">'pull'</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s2">"response: </span><span class="si">#{</span><span class="n">response</span><span class="si">}</span><span class="s2">"</span>
</code></pre></div>
<p>And here’s the <code>Gemfile</code>:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="n">source</span> <span class="s2">"https://rubygems.org"</span>
<span class="n">ruby</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="s2">".ruby-version"</span><span class="p">).</span><span class="nf">strip</span>
<span class="n">gem</span> <span class="s2">"octokit"</span>
</code></pre></div>
<p>If you’ve read my previous <a href="https://www.moncefbelyamani.com/tags/keyboard-maestro">automation guides featuring Keyboard Maestro</a>, you’ll recall that it comes with many handy <a href="https://wiki.keyboardmaestro.com/Tokens">tokens</a> that are placeholders for data that would otherwise require complicated code to fetch. In this case, I’m using the <code>%SystemClipboard%</code> 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 <code>ARGV[0]</code>.</p>
<p>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 created this token was by adding a new <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token">Personal Access Token</a> to my GitHub account, and giving it the appropriate scopes: <code>admin:org</code> and <code>repo</code>. I also set it to expire after 30 days.</p>
<p>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. However, I gitignore files that contain secrets (like GitHub tokens), even on my private repos. I do this for added security, and also out of habit.</p>
<p>In the past, this would require copying the secret file (such as <code>.envrc</code> if using <a href="https://direnv.net">direnv</a>) 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.</p>
<p>But now that I discovered the 1Password CLI, I can get rid of <code>.envrc</code>, 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. </p>
<p>What I like most about this approach is that <strong>I no longer need to have any secrets stored in plain text on my computer (except in 1Password)!</strong> I can also safely make this repo public if I wanted to.</p>
<p>So, instead of the usual <code>ENV['GITHUB_TOKEN']</code>, I can fetch the token with the 1Password CLI <code>op</code> tool:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">def</span> <span class="nf">github_token</span>
<span class="sb">`op item get "add_collab GH token" --fields label=notesPlain`</span>
<span class="k">end</span>
</code></pre></div>
<p>In Ruby, you can run shell commands by surrounding them with backticks. You might also be familiar with the <a href="https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-system">system</a> command, but it doesn’t return the output of the command. It returns <code>true</code> if the command succeeds (with a zero exit status). Since I want the actual output of the command, I need the backticks.</p>
<p>As you might guess from the <code>op</code> 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 <a href="https://developer.1password.com/docs/cli/reference/management-commands/item#item-get">item get</a>, and then running just this command at first:</p>
<div class="highlight"><pre class="highlight shell"><code>op item get <span class="s2">"add_collab GH token"</span>
</code></pre></div>
<p>which returned something like this:</p>
<div class="highlight"><pre class="highlight plaintext"><code>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
</code></pre></div>
<p>That’s how I knew that the <code>label</code> I needed was <code>notesPlain</code>. Here’s the full command again:</p>
<div class="highlight"><pre class="highlight shell"><code>op item get <span class="s2">"add_collab GH token"</span> <span class="nt">--fields</span> <span class="nv">label</span><span class="o">=</span>notesPlain
</code></pre></div>
<p>To make this more robust, I could redirect <code>stderr</code> to <code>stdout</code> by adding <code>2>&1</code> 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:</p>
<div class="highlight"><pre class="highlight ruby"><code><span class="k">def</span> <span class="nf">github_token</span>
<span class="n">token</span> <span class="o">=</span> <span class="sb">`op item get "add_collab GH token" --fields label=notesPlain 2>&1`</span>
<span class="k">if</span> <span class="n">token</span><span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="s2">"ERROR"</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s2">"Failed to fetch token from 1Password: </span><span class="si">#{</span><span class="n">token</span><span class="si">}</span><span class="s2">"</span>
<span class="kp">nil</span>
<span class="k">else</span>
<span class="n">token</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">client</span>
<span class="vi">@client</span> <span class="o">||=</span> <span class="no">Octokit</span><span class="o">::</span><span class="no">Client</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">access_token: </span><span class="n">github_token</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">if</span> <span class="n">github_token</span>
<span class="nb">puts</span> <span class="s2">"adding collaborator </span><span class="si">#{</span><span class="n">customer_github_username</span><span class="si">}</span><span class="s2">"</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="nf">add_collaborator</span><span class="p">(</span><span class="n">repo</span><span class="p">,</span> <span class="n">customer_github_username</span><span class="p">,</span> <span class="ss">permission: </span><span class="s1">'pull'</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s2">"response: </span><span class="si">#{</span><span class="n">response</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
</code></pre></div>
<p>The reason for redirecting <code>stderr</code> to the output is to be able to read and store the error message. Without the redirection, if there’s an error, <code>token</code> will just be an empty string.</p>
<p>Alternatively, I could still use <code>direnv</code> by calling the <code>op</code> command in <code>.envrc</code>, like this:</p>
<div class="highlight"><pre class="highlight shell"><code><span class="nb">export </span><span class="nv">GITHUB_TOKEN</span><span class="o">=</span><span class="sb">`</span>op item get <span class="s2">"add_collab GH token"</span> <span class="nt">--fields</span> <span class="nv">label</span><span class="o">=</span>notesPlain<span class="sb">`</span>
</code></pre></div>
<p>Since there’s no secrets in it anymore, I would be able to commit <code>.envrc</code> to the repo. And if there’s an error fetching the token, I would see it after running <code>direnv allow</code>. This approach would be handy for projects that depend on secrets being stored in env vars, like the <code>RAILS_MASTER_KEY</code> in Rails apps. </p>
<p>To save even more time, I could automate the process of verifying that the person who is requesting access did indeed buy <a href="https://www.rubyonmac.dev/#ultimate">Ruby on Mac</a> Ultimate. Again, Keyboard Maestro makes this easy with the <code>%MailSender%</code> token. </p>
<p>I would then pass this email address as a second argument to my Ruby script, and then use the ConvertKit API via the <a href="https://github.com/hookengine/convertkit-ruby">convertkit-ruby</a> 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.</p>
<p>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!</p>
Automate Context Switching With Bunchhttps://www.moncefbelyamani.com/automate-context-switching-with-bunch/2022-08-12T16:53:37-04:002022-08-16T11:43:43-04:00Moncef Belyamani<p>You sit down to work on a feature, and wake up your Mac. Oh hey, Slack is open. You decide to check it real quick. 30 minutes later, you remember what you wanted to do and close Slack. </p>
<p>Then you switch to the terminal, <code>cd</code> to the project directory, open up the project in your favorite text editor, run the server, open the app in a browser, and arrange all the windows on your screen the way you like them. Then you get an email notification, so you quit all communication apps and turn on Do Not Disturb.</p>
<p>Or maybe you manage multiple projects like a blog, a product, a podcast, and a livestream. Each one has its own context with app layout, development, and macOS settings. And you find yourself setting everything up manually each time.</p>
<p>If this sounds familiar, you’ll want to add the <a href="https://bunchapp.co/">Bunch</a> app to your automation toolbox.</p>
<p>Unlike other GUI-based automation apps like Alfred, Keyboard Maestro, and Shortcuts, a Bunch is just a text file. The Bunch documentation is great and includes <a href="https://bunchapp.co/docs/bunch-files/samplebunch/">sample bunches</a> to get you started.</p>
<p>I created one for myself called <code>Coding.bunch</code> that showcases some of the cool features and the syntax. Below is the full file, which I’ll go over step by step. And here’s a <a href="/videos/coding_bunch_demo.mov">video that shows it in action</a>.</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="ch">//</span><span class="c1"> Hide all visible apps</span>
<span class="no">@@</span>
<span class="nc"><</span><span class="nl">quit_comms.snippet</span>
<span class="kv">project </span><span class="p">=</span><span class="err"> </span><span class="s2">?[
</span><span class="err">Blog</span>
<span class="err">Ruby</span> <span class="err">on</span> <span class="err">Mac</span> <span class="err">Site</span>
<span class="s2">] "Whatcha workin' on?"</span>
<span class="nc"><<</span><span class="kn">#</span><span class="p">${</span><span class="o">project</span><span class="p">}</span>
<span class="kv">soundcloud </span><span class="p">=</span><span class="err"> </span><span class="nf">$ osascript ~/Documents/Bunches/search_open_tabs.applescript "soundcloud"</span>
<span class="nb">___
</span>
<span class="cs">#[</span><span class="kn">Blog</span><span class="cs">]</span>
<span class="no">Tower</span>
<span class="nd">-</span><span class="err"> ~/projects/monfresh/blog</span>
<span class="no">iTerm</span>
<span class="nd">-</span><span class="err"> [blog\\n]</span>
<span class="nd">-</span><span class="err"> {shift-command-d}</span>
<span class="kv">port </span><span class="p">=</span><span class="err"> 4567</span>
<span class="kv">path </span><span class="p">=</span><span class="err"> archives</span>
<span class="nc"><<</span><span class="kn">#Arrange Safari and Sublime</span>
<span class="cs">#[</span><span class="kn">Ruby on Mac Site</span><span class="cs">]</span>
<span class="no">iTerm</span>
<span class="nd">-</span><span class="err"> {"cd ~/projects/rubyonmac/rubyonmac-site" return "git pull && bundle && bin/bridgetown start" return }</span>
<span class="kv">port </span><span class="p">=</span><span class="err"> 4000</span>
<span class="nc"><<</span><span class="kn">#Arrange Safari and Sublime</span>
<span class="cs">#[</span><span class="kn">Arrange Safari and Sublime</span><span class="cs">]</span>
<span class="bp">if</span> <span class="p">${</span><span class="o">soundcloud</span><span class="p">}</span>
<span class="kr">(log</span> <span class="err">soundcloud</span> <span class="err">is</span> <span class="err">already</span> <span class="err">open</span><span class="kr">)</span>
<span class="bp">else</span>
<span class="o">%</span><span class="err">Safari</span>
<span class="o">-</span> <span class="no">https://soundcloud.com/discover/sets/new-for-you::monfresh</span>
<span class="bp">end</span>
<span class="no">%Safari</span>
<span class="nd">-</span><span class="err"> (pause 2)</span>
<span class="nd">-</span><span class="err"> http://localhost:</span><span class="p">${</span><span class="o">port</span><span class="p">}</span><span class="err">/</span><span class="p">${</span><span class="o">path</span><span class="p">}</span>
<span class="nd">-</span><span class="err"> {command-r}</span>
<span class="no">@Sublime Text</span>
<span class="nf">*</span><span class="err"> tell application "Moom" to arrange windows according to snapshot "Safari and Sublime Side by Side"</span>
</code></pre></div>
<p>Brett Terpstra (the author of Bunch) also created <a href="https://bunchapp.co/docs/integration/ide-packages/">packages for various IDEs</a> that include highlighting and completions, which was nice when working on the Bunch in Sublime Text. He also graciously shared his <a href="https://twitter.com/ttscoff/status/1554954340682571776?s=21&t=jqDFFXZ6W1YIEMnCwINj7g">Rouge lexer for Bunch</a> so I could have syntax highlighting on this page.</p>
<p>The first thing this Bunch does is hide all visible apps with the <code>@@</code> command. Alternatively, to start with a clean slate, you can quit everything:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="pi">(</span><span class="err">quit</span> <span class="err">everything</span><span class="pi">)</span>
</code></pre></div>
<p>You can also exclude certain apps from quitting:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="pi">(</span><span class="err">quit</span> <span class="err">everything</span> <span class="err">except</span> <span class="err">Safari</span><span class="pi">)</span>
</code></pre></div>
<p>Brett also supports some fun variations for the <a href="https://bunchapp.co/docs/bunch-files/commands/quit-everything/">quit everything command</a>.</p>
<p>Check out all the <a href="https://bunchapp.co/docs/bunch-files/commands/">commands</a> you can run.</p>
<p>Next, I wanted to try quitting all distractions at once, which for me are communication apps. Thinking about reusability in other bunches, I created a snippet that quits all these apps, and I named it <code>quit_comms.snippet</code>:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="no">!Discord</span>
<span class="no">!Mail</span>
<span class="no">!Messages</span>
<span class="no">!Slack</span>
<span class="no">!Stellar</span>
<span class="no">!Telegram</span>
<span class="no">!Twitter</span>
<span class="no">!WhatsApp</span>
</code></pre></div>
<p>And then I called it in my <code>Coding</code> Bunch like this:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="nc"><</span><span class="nl">quit_comms.snippet</span>
</code></pre></div>
<p>A snippet file can be named with any extension other than <code>.bunch</code>, and should be stored in your Bunches Folder (<code>~/Documents/Bunches</code> by default I think.)</p>
<p>At first, I thought I would be able to create a <code>Comms.bunch</code> that launched all of these apps, and that I could tell it to close all of those apps in my <code>Coding.bunch</code>, like in the <a href="https://bunchapp.co/docs/bunch-files/samplebunch/#podcast">Podcasting sample Bunch</a> in the Bunch documentation:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="no">!Comms.bunch</span>
</code></pre></div>
<p>However, that’s not how Bunch works. If one of those communication apps is already running, starting the <code>Coding</code> bunch will not quit that app. And when the <code>Coding</code> bunch is stopped, the <code>Comms</code> bunch will automatically start and open all the communication apps! That’s definitely not what I wanted.</p>
<p>The next part of the <code>Coding</code> bunch displays a prompt that asks me which project I’m working on:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="kv">project </span><span class="p">=</span><span class="err"> </span><span class="s2">?[
</span><span class="err">Blog</span>
<span class="err">Ruby</span> <span class="err">on</span> <span class="err">Mac</span> <span class="err">Site</span>
<span class="s2">] "Whatcha workin' on?"</span>
<span class="nc"><<</span><span class="kn">#</span><span class="p">${</span><span class="o">project</span><span class="p">}</span>
</code></pre></div>
<p>Here, we’re assigning the name of the project to a <code>project</code> variable, and then we’re telling the Bunch to run the actions that are specific to that project. Each project’s actions is defined within a “fragment”. The fragment header can start with either a <code>#</code>, <code>-</code>, <code>=</code>, or <code>></code>, followed by the fragment name enclosed in square brackets. In this example, there are 2 fragments: <code>#[Blog]</code> and <code>#[Ruby on Mac Site]</code>.</p>
<p>You call fragments within a Bunch with <code><<#</code> followed by the fragment name, and you can even add a delay to that fragment with <code>~</code> followed by the number of seconds. For example:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="nc"><<</span><span class="kn">#Blog</span><span class="ni"> ~2</span>
</code></pre></div>
<p>Let’s look at the Blog fragment:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="cs">#[</span><span class="kn">Blog</span><span class="cs">]</span>
<span class="no">iTerm</span>
<span class="nd">-</span><span class="err"> [blog\\n]</span>
<span class="nd">-</span><span class="err"> {shift-command-d}</span>
<span class="kv">port </span><span class="p">=</span><span class="err"> 4567</span>
<span class="kv">path </span><span class="p">=</span><span class="err"> archives</span>
<span class="nc"><<</span><span class="kn">#Arrange Safari and Sublime</span>
</code></pre></div>
<p>First, it launches iTerm, and then if you want to perform certain actions within an application, you start a new line with a <code>-</code> followed by an action. To type a string, you put it between square brackets. In this case, I want Bunch to type <code>blog</code> followed by the return key:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="nd">-</span><span class="err"> [blog\\n]</span>
</code></pre></div>
<p><code>blog</code> is an alias in my Fish shell that runs these commands:</p>
<div class="highlight"><pre class="highlight shell"><code><span class="nb">cd</span> ~/projects/monfresh/blog <span class="o">&&</span> git pull <span class="o">&&</span> chruby <span class="o">(</span><span class="nb">cat</span> .ruby-version<span class="o">)</span> <span class="se">\</span>
<span class="o">&&</span> bundle <span class="o">&&</span> subl <span class="nb">.</span> <span class="o">&&</span> bundle <span class="nb">exec </span>middleman serve
</code></pre></div>
<p>Next, I want to open a new pane in iTerm under the current one by pressing <code>shift-⌘-D</code>. That way, I have the server running in the top pane, and in the bottom pane I can run commands if I need to. To tell Bunch to send <a href="https://bunchapp.co/docs/bunch-files/keystrokes/">keystrokes</a>, you enclose them in curly brackets:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="nd">-</span><span class="err"> {shift-command-d}</span>
</code></pre></div>
<p>You can also combine typing and keystrokes, as shown in the <code>Ruby on Mac Site</code> fragment:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="nd">-</span><span class="err"> {"cd ~/projects/rubyonmac/rubyonmac-site" return "git pull && bundle && bin/bridgetown start" return }</span>
</code></pre></div>
<p>Finally, I call another fragment called <code>Arrange Safari and Sublime</code>, and I pass in the <code>port</code> value of 4000 and <code>path</code> value of “archives”:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="kv">port </span><span class="p">=</span><span class="err"> 4000</span>
<span class="kv">path </span><span class="p">=</span><span class="err"> archives</span>
<span class="nc"><<</span><span class="kn">#Arrange Safari and Sublime</span>
</code></pre></div>
<p>Here’s what the fragment looks like:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="cs">#[</span><span class="kn">Arrange Safari and Sublime</span><span class="cs">]</span>
<span class="bp">if</span> <span class="p">${</span><span class="o">soundcloud</span><span class="p">}</span>
<span class="kr">(log</span> <span class="err">soundcloud</span> <span class="err">is</span> <span class="err">already</span> <span class="err">open</span><span class="kr">)</span>
<span class="bp">else</span>
<span class="o">%</span><span class="err">Safari</span>
<span class="o">-</span> <span class="no">https://soundcloud.com/discover/sets/new-for-you::monfresh</span>
<span class="bp">end</span>
<span class="no">%Safari</span>
<span class="nd">-</span><span class="err"> (pause 2)</span>
<span class="nd">-</span><span class="err"> http://localhost:</span><span class="p">${</span><span class="o">port</span><span class="p">}</span><span class="err">/</span><span class="p">${</span><span class="o">path</span><span class="p">}</span>
<span class="nd">-</span><span class="err"> {command-r}</span>
<span class="no">@Sublime Text</span>
<span class="nf">*</span><span class="err"> tell application "Moom" to arrange windows according to snapshot "Safari and Sublime Side by Side"</span>
</code></pre></div>
<p><strong>This one ended up being tricky to figure out due to the way conditionals and variables work, so pay attention.</strong></p>
<p>I like to listen to “The Upload” playlist that SoundCloud curates for me, and when I was testing opening the SoundCloud URL, it was already playing, but when I ran the Bunch, it stopped the playback because it refreshed the page. 😢</p>
<p>That’s when I looked into Bunch’s <a href="https://bunchapp.co/docs/bunch-files/logic/">conditional logic</a> and <a href="https://bunchapp.co/docs/bunch-files/variables/">variables</a>. I didn’t find a built-in way to check if a browser tab is already open, so I asked DuckDuckGo how to do that with AppleScript. The examples I found were pretty much the same, but they did more than I needed, so I trimmed it down and saved it to a file called <code>search_open_tabs.applescript</code> in my Bunches folder.</p>
<div class="highlight"><pre class="highlight applescript"><code><span class="k">on</span> <span class="nb">run</span><span class="w"> </span><span class="nv">argv</span><span class="w">
</span><span class="k">set</span><span class="w"> </span><span class="nv">searchText</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="p">(</span><span class="nb">item</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nv">argv</span><span class="p">)</span><span class="w">
</span><span class="k">tell</span><span class="w"> </span><span class="nb">application</span><span class="w"> </span><span class="s2">"Safari"</span><span class="w">
</span><span class="k">set</span><span class="w"> </span><span class="nv">winlist</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="nb">every</span><span class="w"> </span><span class="na">window</span><span class="w">
</span><span class="k">set</span><span class="w"> </span><span class="nv">tabmatchlist</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="p">{}</span><span class="w">
</span><span class="k">repeat</span><span class="w"> </span><span class="nv">with</span><span class="w"> </span><span class="nv">win</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nv">winlist</span><span class="w">
</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nv">tabs</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nv">win</span><span class="p">)</span><span class="w"> </span><span class="ow">is not</span><span class="w"> </span><span class="ow">equal to</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="k">then</span><span class="w">
</span><span class="k">set</span><span class="w"> </span><span class="nv">tablist</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="nb">every</span><span class="w"> </span><span class="nb">tab</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nv">win</span><span class="w">
</span><span class="k">repeat</span><span class="w"> </span><span class="nv">with</span><span class="w"> </span><span class="nv">t</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nv">tablist</span><span class="w">
</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nv">searchText</span><span class="w"> </span><span class="ow">is in</span><span class="w"> </span><span class="p">(</span><span class="na">name</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nv">t</span><span class="w"> </span><span class="k">as </span><span class="nc">string</span><span class="p">))</span><span class="w"> </span><span class="ow">or</span><span class="w"> </span><span class="p">(</span><span class="nv">searchText</span><span class="w"> </span><span class="ow">is in</span><span class="w"> </span><span class="p">(</span><span class="nv">URL</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nv">t</span><span class="w"> </span><span class="k">as </span><span class="nc">string</span><span class="p">))</span><span class="w"> </span><span class="k">then</span><span class="w">
</span><span class="k">set</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nv">tabmatchlist</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="nv">t</span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="k">if</span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="k">repeat</span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="k">if</span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="k">repeat</span><span class="w">
</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nv">tabmatchlist</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">then</span><span class="w">
</span><span class="nb">return</span><span class="w"> </span><span class="nb">true</span><span class="w">
</span><span class="k">else</span><span class="w">
</span><span class="nb">return</span><span class="w"> </span><span class="nb">false</span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="k">if</span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="k">tell</span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="nb">run</span><span class="w">
</span></code></pre></div>
<p>At first, I tried defining the variable inside the <code>#[Arrange Safari and Sublime]</code> fragment:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="cs">#[</span><span class="kn">Arrange Safari and Sublime</span><span class="cs">]</span>
<span class="kv">soundcloud </span><span class="p">=</span><span class="err"> </span><span class="nf">$ osascript ~/Documents/Bunches/search_open_tabs.applescript "soundcloud"</span>
</code></pre></div>
<p>and then using it like this:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="bp">if</span> <span class="err">soundcloud</span>
<span class="kr">(log</span> <span class="err">soundcloud</span> <span class="err">is</span> <span class="err">already</span> <span class="err">open</span><span class="kr">)</span>
<span class="bp">else</span>
<span class="o">%</span><span class="err">Safari</span>
<span class="o">-</span> <span class="no">https://soundcloud.com/discover/sets/new-for-you::monfresh</span>
<span class="bp">end</span>
</code></pre></div>
<p>but for some reason Bunch didn’t like that. What was so strange is that Bunch could see that the value was <code>true</code>, but it would still execute the <code>else</code> statement. Here’s how I added logging to debug:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="kr">(log</span> <span class="err">soundcloud</span> <span class="err">is</span> <span class="p">${</span><span class="o">soundcloud</span><span class="p">}</span><span class="kr">)</span>
<span class="bp">if</span> <span class="err">soundcloud</span>
<span class="kr">(log</span> <span class="err">soundcloud</span> <span class="err">is</span> <span class="p">${</span><span class="o">soundcloud</span><span class="p">}</span><span class="kr">)</span>
<span class="kr">(log</span> <span class="err">soundcloud</span> <span class="err">is</span> <span class="err">already</span> <span class="err">open</span><span class="kr">)</span>
<span class="bp">else</span>
<span class="kr">(log</span> <span class="err">soundcloud</span> <span class="err">is</span> <span class="p">${</span><span class="o">soundcloud</span><span class="p">}</span><span class="kr">)</span>
<span class="o">%</span><span class="err">Safari</span>
<span class="o">-</span> <span class="no">https://soundcloud.com/discover/sets/new-for-you::monfresh</span>
<span class="bp">end</span>
</code></pre></div>
<p>So then I tried variations on the variable name and comparison:</p>
<ul>
<li><code>if $soundcloud</code></li>
<li><code>if ${soundcloud}</code></li>
<li><code>if soundcloud is true</code></li>
<li><code>if soundcloud is "true"</code></li>
<li><code>else if soundcloud is false</code></li>
</ul>
<p>and so on, but nothing worked. That’s when I realized that maybe the variable needs to be defined outside the fragment, and that worked!</p>
<p>If you scroll up to the full file, you’ll see the <code>soundcloud</code> variable is defined before any fragments:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="nc"><<</span><span class="kn">#</span><span class="p">${</span><span class="o">project</span><span class="p">}</span>
<span class="kv">soundcloud </span><span class="p">=</span><span class="err"> </span><span class="nf">$ osascript ~/Documents/Bunches/search_open_tabs.applescript "soundcloud"</span>
<span class="nb">___
</span>
<span class="cs">#[</span><span class="kn">Blog</span><span class="cs">]</span>
<span class="o">...</span>
</code></pre></div>
<p><strong>The other thing to watch out for with conditionals is that indentation needs to be 4 spaces.</strong></p>
<p>Next, I tell Safari to pause for 2 seconds before opening localhost with the port number and path that were passed in earlier when the fragment was called. The reason for the pause is because I want localhost to be the last tab opened, but Bunch kept opening it first, even though it comes after SoundCloud in the Bunch file. </p>
<p>Then I send the <code>⌘-R</code> keystroke to refresh the page because sometimes it visits localhost before the server has finished loading. The <code>%</code> before Safari tells the Bunch to leave Safari open after the Bunch is closed. Otherwise, any apps that are opened by the Bunch get quit when the Bunch is closed.</p>
<p>Here’s the Safari bit again:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="no">%Safari</span>
<span class="nd">-</span><span class="err"> (pause 2)</span>
<span class="nd">-</span><span class="err"> http://localhost:</span><span class="p">${</span><span class="o">port</span><span class="p">}</span><span class="err">/</span><span class="p">${</span><span class="o">path</span><span class="p">}</span>
<span class="nd">-</span><span class="err"> {command-r}</span>
</code></pre></div>
<p>Then I focus Sublime Text with the <code>@</code> prefix, and finally, I run an AppleScript inline that tells <a href="https://manytricks.com/moom/">Moom</a> to arrange Safari on the right half of the screen, and Sublime Text on the left half:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="no">@Sublime Text</span>
<span class="nf">*</span><span class="err"> tell application "Moom" to arrange windows according to snapshot "Safari and Sublime Side by Side"</span>
</code></pre></div>
<p>I was hoping to be able to send the Moom keyboard shortcuts I have for sending windows left or right, but it doesn’t look like Bunch supports that. I think keystrokes have to be sent in an app’s context, and it seems like the keystroke has to be recognized by the app, and since <code>⌃-⌥-⌘-←</code> doesn’t exist as a Safari or Sublime keyboard shortcut, I got an error in the Bunch log that said this:</p>
<div class="highlight"><pre class="highlight plaintext"><code>Could not convert control-option-command-left into key codes
</code></pre></div>
<p>So, for window management, I had to set Safari and Sublime side by side the way I wanted them, and then save a snapshot in Moom.</p>
<p>And that’s it! I’ve been using this Bunch regularly now and it’s been working great. One improvement I need to make is to first check if Git is clean before running <code>git pull</code> because if there are unstaged changes, <code>git pull</code> will fail and the server won’t be started.</p>
<p>By default, to start and stop Bunches, you have to do it via the menu bar icon. To speed things up, Bunch makes it easy to <a href="https://bunchapp.co/docs/integration/">integrate with other automation tools</a> by providing its own <code>x-bunch:</code> <a href="https://bunchapp.co/docs/integration/url-handler/">URL scheme</a>.</p>
<p>I chose to set up a hotkey in Keyboard Maestro that will open <code>x-bunch://open?bunch=Coding</code> when I press <code>shift-control-option-command-C</code>. Here’s what the macro looks like:</p>
<p><img src="/images/keyboard_maestro_open_bunch.jpg" alt="Keyboard Maestro Macro to open a Bunch" width="750" height="789"/></p>
<p>To take this to another level, I used <a href="https://karabiner-elements.pqrs.org">Karabiner-Elements</a> to turn the caps lock key into a hyper key that combines the <code>shift-control-option-command</code> keys. That way, all I have to do to open the Coding Bunch is to press <code>caps lock</code> and <code>C</code>.</p>
<h2 id="using-bunch-to-set-up-a-live-stream">Using Bunch to set up a live stream</h2>
<p>I ran into some issues with another Bunch I created, and wanted you to be aware of them. No one else has reported them in the <a href="https://github.com/ttscoff/bunch/issues">Issue Tracker</a> or the <a href="https://github.com/ttscoff/bunch/discussions">Discussions</a>, so I’m not sure if they’re bugs or something on my end.</p>
<p>I recently started live coding on my <a href="https://www.youtube.com/channel/UClQOoDTkpK6xHaQt7mOu6vQ">YouTube channel</a> in the spirit of building in public. So far, I’ve been working on <a href="https://www.rubyonmac.dev/">Ruby on Mac</a>, and automating my daily and business workflows. I also plan on sharing how I <a href="/services">maintain Rails apps</a>, and recording video versions of my <a href="/tags/automation">automation tutorials</a>.</p>
<p>To reduce the chances of accidentally showing something sensitive or personal on the screen during the live stream, I like to quit all applications except the ones I’ll be using, which are typically Sublime Text, iTerm, and Safari.</p>
<p>I also like to hide all desktop icons, and <a href="https://www.ecamm.com/mac/ecammlive/">Ecamm Live</a>, the app I use to stream, has a preference to automatically hide desktop icons when I’m sharing my screen. I also noticed that Bunch has a command to do the same thing, and wanted to try it even though I don’t need it when using Ecamm Live:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="kr">(hide desktop)</span>
</code></pre></div>
<p>However, it didn’t work for me. I tried on my M1 Macbook Air and Intel iMac, both running macOS 12.5 (Monterey). I even tried with a Bunch whose only action was to hide the desktop. Both Macs are running the latest version of Bunch: 1.4.9 (148).</p>
<p>If I told the Bunch to directly run the command to hide the desktop icons, and then restore them when the Bunch is closed, it worked:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="nf">$</span><span class="err"> defaults write com.apple.finder CreateDesktop -bool false && killall Finder</span>
<span class="nf">!$</span><span class="err"> defaults write com.apple.finder CreateDesktop -bool true && killall Finder</span>
</code></pre></div>
<p>As you might guess from the example above, to perform an action when the Bunch closes, you precede it with an exclamation point. The documentation has more examples of ways to <a href="https://bunchapp.co/docs/bunch-files/run-on-close/">run on close</a>.</p>
<p>Here’s the full livestreaming Bunch I created, and then I’ll go over it, and a couple of things that tripped me up:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="cd">---</span><span class="err">
</span><span class="vg">title</span><span class="pi">:</span><span class="na"> 🎥Livestream</span><span class="err">
</span><span class="vg">open on</span><span class="pi">:</span><span class="na"> Mon 12:30pm, Wed 12:30pm</span><span class="err">
</span><span class="cd">---
</span>
<span class="ch">#</span><span class="c1"> Close all Finder windows</span>
<span class="no">Finder</span>
<span class="nd">-</span><span class="err"> XX</span>
<span class="pi">(</span><span class="err">quit</span> <span class="err">everything</span> <span class="err">except</span> <span class="err">Safari</span><span class="o">,</span> <span class="err">Moom</span><span class="o">,</span> <span class="err">Keyboard</span> <span class="err">Maestro</span><span class="o">,</span> <span class="err">iTerm</span><span class="pi">)</span>
<span class="kr">(hide desktop)</span>
<span class="ch">#</span><span class="c1"> Turn on Do Not Disturb</span>
<span class="kr">(dnd on)</span>
<span class="no">Ecamm Live</span>
<span class="no">iTerm</span>
<span class="ch">#</span><span class="c1"> Ask about specific project, handle setup</span>
<span class="s2">?{
</span><span class="err">Ruby</span> <span class="err">on</span> <span class="err">Mac</span> <span class="o">=></span> <span class="nc"><<</span><span class="kn">#Ruby on Mac</span>
<span class="err">Rails</span> <span class="err">app</span> <span class="err">maintenance</span> <span class="o">=></span> <span class="nc"><<</span><span class="kn">#Ohana</span>
<span class="s2">} "Whatcha workin' on?"</span>
<span class="ch">#</span><span class="c1"> Quit the bandwidth heavy apps</span>
<span class="nf">$</span><span class="err"> killall Dropbox</span>
<span class="nf">$</span><span class="err"> /Library/Backblaze.bzpkg/bztransmit -pausebackup</span>
<span class="ch">#</span><span class="c1"> Focus ECamm Live, hiding other apps</span>
<span class="no">@ECamm Live</span>
<span class="ch">#</span><span class="c1"> Turn Backblaze and Dropbox back on when this Bunch closes</span>
<span class="nf">!$</span><span class="err"> /Library/Backblaze.bzpkg/bztransmit -completesync</span>
<span class="ch">##</span><span class="c1"> Double negatives (!!) to launch apps and other Bunches when closing</span>
<span class="no">!!Dropbox</span>
<span class="nb">___
</span>
<span class="cs">#[</span><span class="kn">Ruby on Mac</span><span class="cs">]</span>
<span class="no">Tower</span>
<span class="nd">-</span><span class="err"> ~/projects/rubyonmac/rom-ultimate</span>
<span class="nf">$</span><span class="err"> subl ~/projects/rubyonmac/rom-ultimate</span>
<span class="cs">#[</span><span class="kn">Ohana</span><span class="cs">]</span>
<span class="no">Tower</span>
<span class="nd">-</span><span class="err"> ~/projects/codeforamerica/ohana-api</span>
<span class="no">iTerm</span>
<span class="nd">-</span><span class="err"> {command-t}</span>
<span class="nd">-</span><span class="err"> [cfaoa\\n]</span>
</code></pre></div>
<p>Similarly to the <code>Coding.bunch</code>, this one prompts me for the project I’ll be live coding on:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="s2">?{
</span><span class="err">Ruby</span> <span class="err">on</span> <span class="err">Mac</span> <span class="o">=></span> <span class="nc"><<</span><span class="kn">#Ruby on Mac</span>
<span class="err">Rails</span> <span class="err">app</span> <span class="err">maintenance</span> <span class="o">=></span> <span class="nc"><<</span><span class="kn">#Ohana</span>
<span class="s2">} "Whatcha workin' on?"</span>
</code></pre></div>
<p>The difference is that this uses a Hash instead of an Array, which allows for the dropdown label to have a different name than the fragment in the Bunch:</p>
<p><img src="/images/livestream_bunch_dropdown.png" alt="Livestream Bunch dropdown" width="514" height="592"/></p>
<p>This is just to show you a different way to set up these prompts. In reality, I use the same name for the label and the fragment. </p>
<p>When I first tested this, it wasn’t working. In the log, it kept saying it couldn’t find a matching fragment. It took me a while to realize what I did wrong, which was using 3 dashes (<code>---</code>) above the fragments instead of 3 underscores (<code>___</code>). I knew something was wrong thanks to the syntax highlighting in Sublime Text, which turned off all highlighting in the fragments, but I couldn’t figure out why!</p>
<p>The documentation mentions that the fragment separator should be 3 underscores, but I missed it, and to create the <code>Coding.bunch</code>, I had copied and pasted from the sample Bunches in the documentation, so I had never experienced typing the 3 underscores. <strong>This is a reminder of the importance of typing code directly yourself when following tutorials instead of copying and pasting.</strong></p>
<p>Another cool thing you can do is add <a href="https://bunchapp.co/docs/bunch-files/frontmatter/">frontmatter</a>, such as customizing the label in the Bunch menu bar, as well as telling the Bunch to open (and/or close) on certain days and times. When I stream, it’s usually between 1 and 2:30pm, so I set this Bunch to open 30 minutes before, so I can make sure everything is working and ready to go.</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="cd">---</span><span class="err">
</span><span class="vg">title</span><span class="pi">:</span><span class="na"> 🎥Livestream</span><span class="err">
</span><span class="vg">open on</span><span class="pi">:</span><span class="na"> Mon 12:30pm, Wed 12:30pm</span><span class="err">
</span><span class="cd">---
</span></code></pre></div>
<p>The rest of the Bunch should be straightforward, but I wanted to point out one last possible bug. The release notes for Bunch version 1.4.9 say that “/opt/homebrew/bin is now included in the default path for M1 users using shell scripting.” However, the <code>subl</code> command below failed when running the livestream Bunch on my M1 Mac. It works fine on my Intel iMac.</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="nf">$</span><span class="err"> subl ~/projects/rubyonmac/rom-ultimate</span>
</code></pre></div>
<p>When I run <code>which subl</code>, I get <code>/opt/homebrew/bin/subl</code> so I’m not sure what’s going on. I will open a bug in the Bunch GitHub repo and update this post. It’s no big deal because I can tell Sublime Text to open it, or do it via iTerm:</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="no">Sublime Text</span>
<span class="nd">-</span><span class="err"> ~/projects/rubyonmac/rom-ultimate</span>
</code></pre></div>
<p>or</p>
<div class="highlight"><pre class="highlight bunch"><code><span class="no">iTerm</span>
<span class="nd">-</span><span class="err"> {command-t}</span>
<span class="nd">-</span><span class="err"> {"subl ~/projects/rubyonmac/rom-ultimate" return}</span>
</code></pre></div>
<p>That wraps it up! I hope I inspired you to give Bunch a try. Let me know if you do. </p>
<p>Finally, I wanted to mention that if you’re setting up a new Mac soon, you might be interested in the <a href="https://www.rubyonmac.dev/#ultimate">“Ultimate” version of Ruby on Mac</a>. With a single command, it will set up your Mac just the way you like it with all your dev tools, Mac apps, macOS preferences, and GitHub repos. It comes with a <code>Brewfile</code> that contains hundreds of dev tools and Mac apps, including all the apps mentioned in this post: <a href="https://secure.backblaze.com/r/02i6e7">Backblaze</a>, Bunch, Ecamm Live, Fish shell, iTerm2, Karabiner-Elements, Moom, Sublime Text, and <a href="https://www.git-tower.com/p/refer-a-friend/R-H8JZ3A2WYM">Tower</a>.</p>
How to Install (Or Get Rid Of) therubyracer on M1 or M2 Macshttps://www.moncefbelyamani.com/stop-trying-to-install-therubyracer-on-m1-or-m2-macs/2022-07-29T17:01:32-04:002023-04-08T20:21:51-04:00Moncef Belyamani<p><strong>UPDATE</strong>: An updated version of this post now lives on the <a href="https://www.rubyonmac.dev/how-to-install-therubyracer-on-m1-m2-apple-silicon-mac/">Ruby on Mac blog</a></p>
<p>If you’ve been banging your head against a wall trying to install therubyracer gem on an Apple Silicon Mac (M1 or M2), then you’ve come to the right place.</p>
<p>From talking to <a href="https://www.rubyonmac.dev/">Ruby on Mac</a> customers and other Ruby developers, a common pain point is not being able to run legacy Rails apps on M1 Macs. This is due to the projects using old versions of Ruby, and old versions of gems that might not be maintained anymore and that aren’t supported on the M1/M2 chip. </p>
<p>The gem most people complain about is <code>therubyracer</code>. They spend days trying to install it, and either end up giving up or using workarounds they’re not happy with. The reason why you can’t install it natively is because it hasn’t been updated to support the ARM architecture that the Apple Silicon chips use. If you look it up in <a href="https://rubygems.org/gems/therubyracer">rubygems.org</a>, you’ll see that it hasn’t been updated since January 5, 2017.</p>
<p>Instead, I recommend replacing <code>therubyracer</code> or eliminating it entirely. In many cases, one of these 3 solutions should work:</p>
<ul>
<li><p>Depending on what you’re using <code>therubyracer</code> for, you might be able to just remove the gem and use Node instead. <a href="https://www.rubyonmac.dev/pricing">Ruby on Mac</a> automatically installs and configures Node for you, among many other web development tools.</p></li>
<li><p>If the dependency on <code>therubyracer</code> comes from another gem that depends on Less, replace it with LibSass via the <a href="https://rubygems.org/gems/sassc">sassc</a> or <a href="https://rubygems.org/gems/sassc-rails">sassc-rails</a> gems.</p></li>
<li><p>Try replacing <code>therubyracer</code> with <a href="https://rubygems.org/gems/mini_racer">mini_racer</a>, which works fine on Apple Silicon natively.</p></li>
</ul>
<p>Another popular gem that gives people trouble on M1 macs is <code>ffi</code>, and the most common reason is using an older version of the gem. The general rule of thumb is to update any gem that is the source of the error in the stacktrace. Updating gems to their latest version resolves most issues with old projects on Apple Silicon Macs.</p>
<p>If you’re still stuck getting your old Ruby projects to run, you can <a href="/services">hire me</a> and I’d be happy to help you. I’m taking on a limited number of clients at this time. Feel free to email me at my first name @ rubyonmac.dev and we’ll go from there.</p>
Quickly Act on Selected Text With PopClip and Its 180+ Extensionshttps://www.moncefbelyamani.com/quickly-act-on-selected-text-with-popclip-and-its-180-extensions/2022-07-25T14:08:55-04:002022-12-01T21:43:16-05:00Moncef Belyamani<p><a href="https://pilotmoon.com/popclip/">PopClip</a> 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.</p>
<p>It’s one of the many useful apps you can discover and quickly install with the “Ultimate” version of <a href="https://www.rubyonmac.dev/pricing/">Ruby on Mac</a>. You can pick and choose from hundreds of Mac apps, fonts, and dev tools in the included <code>Brewfile</code>, and Ruby on Mac will install them all at once, via <a href="https://github.com/Homebrew/homebrew-bundle">Homebrew Bundle</a>. That way, you can manage everything from one file, and keep it in sync with your other Macs.</p>
<p>Here’s how PopClip works:</p>
<ol>
<li><a href="https://pilotmoon.com/popclip/">Download the free trial</a></li>
<li>Drag it to your <code>Applications</code> folder, then double-click on it. </li>
<li><p>Agree to the prompt from Apple about opening apps that you downloaded from the internet. You should now see PopClip in the menu bar. It looks like this, although it will be grayed out at first:</p>
<p><img src="/images/popclip_icon.png" alt="PopClip icon in menu bar" /></p></li>
<li><p>Click on the menu bar icon, then click on “Enable PopClip”. This will prompt you to allow PopClip to control your computer with accessibility features.</p></li>
<li><p>Click on “Open System Preferences”, click the lock to make changes, then check the PopClip checkbox in the right pane. As soon as you do that, PopClip should display its “Home” tab:</p>
<p><img src="/images/popclip_home.jpg" alt="PopClip home tab" /></p></li>
<li><p>Once that’s done, you should now be able to start using it, although it only comes out of the box with a few extensions, which you can see by clicking on the puzzle icon:</p>
<p><img src="/images/popclip_default_extensions.jpg" alt="PopClip default extensions" /></p></li>
<li><p>To add more, click on the “+” button at the bottom. This will take you to the <a href="https://pilotmoon.com/popclip/extensions/">PopClip Extensions</a> web page.</p></li>
<li><p>Download the ones that look useful to you, then double-click on them from the Downloads folder, and they will be automatically added to your PopClip instance.</p></li>
<li><p>If the PopClip menu bar icon says “PopClip is OFF”, click the OFF button to turn it ON.</p></li>
<li><p>Now try it by selecting text on this page. When you release, you should see the PopClip menu appear above the selected text, and then you can choose one of the actions.</p></li>
</ol>
<p>Here are the ones I use regularly:</p>
<ul>
<li>DuckDuckGo</li>
<li>Character Count</li>
<li>Terminal (for running the selected text in either the default Terminal app or iTerm)</li>
<li>Maps</li>
<li>WorldCat (to see if books are available at my local library)</li>
<li>Title Case (for my blog post article titles)</li>
<li>Readwise (to add things to my <a href="https://readwise.io/i/moncef2">Readwise</a><sup id="fnref1"><a href="#fn1">1</a></sup> collection)</li>
</ul>
<p>One caveat is that sometimes, PopClip interferes with copying and pasting. When I select text, then press ⌘-C, sometimes it doesn’t copy it. I’m not sure if it’s because I disabled PopClip’s cut, copy, and paste extensions, or something else.</p>
<p>Having said that, you can disable PopClip in certain apps by excluding them via the fourth tab, the one with the <a href="https://en.wikipedia.org/wiki/No_symbol">“no” symbol</a> (the circle with a diagonal line through it; although I think the line is supposed to go from top left to bottom right).</p>
<p>You can also <a href="https://github.com/pilotmoon/PopClip-Extensions">make your own PopClip extensions</a>. I haven’t yet, but I’m going to try soon. I want to make it super easy to install new dev tools or Mac apps by simply selecting the name of the tool, and choosing a PopClip action.</p>
<p>Here’s how I imagine it will work:</p>
<ol>
<li>The PopClip action will run a script that will search for the tool using Homebrew’s API.</li>
<li>If it’s found, it will install it, then add it to the <code>Brewfile-rom-custom</code> file that comes with <a href="https://www.rubyonmac.dev/">Ruby on Mac</a> Ultimate. Ideally, it will put it in alphabetical order, and in the right section. The file groups “brews” in one section, then “casks”, then Mac App Store apps that are installed via the <a href="https://github.com/mas-cli/mas">Mac App Store command line interface</a> .</li>
</ol>
<p>That’s it for today. I hope you learned something new, and if you’ve already been using PopClip, let me know what your favorite extensions are. I’m <a href="https://twitter.com/monfresh">@monfresh</a> on Twitter, and my email is moncef + my Twitter handle + .com.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>This is a referral link that will give both you and me a free month. <a href="#fnref1">↩</a></p>
</li>
</ol>
</div>
Paste Text on Sites That Block It Without Extensions or Browser Hackshttps://www.moncefbelyamani.com/paste-text-on-sites-that-block-it-without-extensions-or-browser-hacks/2022-07-16T15:15:38-04:002022-07-29T10:58:26-04:00Moncef Belyamani<p>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:</p>
<ul>
<li>Use a browser extension, that might only work in certain browsers and only on some sites</li>
<li>Change the Firefox configuration settings, which comes with a “Proceed with Caution” warning, and breaks copying and pasting with keyboard shortcuts in Google Docs and other sites</li>
<li>Use the browser developer tools to make changes to the source code, which doesn’t work all the time</li>
</ul>
<p>Today, I’ll show you a simple and safe way to paste on any site that blocks it, using any browser. The easiest and fastest way I’ve found to do this is with <a href="https://www.keyboardmaestro.com/main/">Keyboard Maestro</a>, and it only requires a single action.</p>
<p>To keep this page light in size, the screenshots are low quality, but if you click on them, you’ll get the high-res image.</p>
<ol>
<li>Launch Keyboard Maestro</li>
<li>⌘-N to create a new Macro (or File -> New Macro)</li>
<li>It should look like this:
<a href="/images/new_macro_plain-fs8.png" target="_blank"><img src="/images/new_macro_plain-fs8-small.jpg" alt="Keyboard Maestro New Macro" width="750" height="330"/></a></li>
<li><p>In the right pane, replace “Untitled Macro” with a descriptive name like “Paste in sites that block it”
<a href="/images/km_paste_rename_macro-fs8.png" target="_blank"><img src="/images/km_paste_rename_macro-fs8-small.jpg" alt="Keyboard Maestro Rename Macro" width="750" height="318"/></a></p></li>
<li><p>Click on the “New Trigger” green button and select “Hot Key Trigger”, which should be the first choice.
<a href="/images/km_paste_new_trigger-fs8.png" target="_blank"><img src="/images/km_paste_new_trigger-fs8-small.jpg" alt="Keyboard Maestro New Trigger Button" width="750" height="320"/></a></p></li>
<li><p>Type in your desired hot key, such as ⌃⌥⌘P (control-option-command-P), then click on the “New Action” green button at the bottom of the right pane.
<a href="/images/km_paste_new_action-fs8.png" target="_blank"><img src="/images/km_paste_new_action-fs8-small.jpg" alt="Keyboard Maestro Insert Token" width="750" height="330"/></a></p></li>
<li><p>You should now see an Actions pane on the left that you can search through. Type “insert” in the search field, then double-click on “Insert Text by Typing”
<a href="/images/km_insert_text_by_typing-fs8.png" target="_blank"><img src="/images/km_insert_text_by_typing-fs8-small.jpg" alt="Keyboard Maestro Insert Text by Typing Action" width="750" height="370"/></a></p></li>
<li><p>With the cursor inside the text field, click on “Insert Token” above the top right of the text field, then choose “Clipboard”, then “System Clipboard”. You can also directly type <code>%SystemClipboard%</code>.
<a href="/images/km_insert_token_system_clipboard-fs8.png" target="_blank"><img src="/images/km_insert_token_system_clipboard-fs8-small.jpg" alt="Keyboard Maestro Insert System Clipboard Token" width="750" height="525"/></a></p></li>
<li><p>Click the Edit button at the bottom of the window to get out of edit mode. And that’s it!</p></li>
</ol>
<p>Now, whenever you need to paste something into a field on one of those pesky sites, all you need to do is press your hot key and Keyboard Maestro will insert it into the field one character at a time as if you were typing it yourself, except it will do it much faster.</p>
<p>To take it a step further, you can create macros that will insert specific text without having to first copy it from somewhere like 1Password. For example, I have two separate macros to insert my bank routing and account numbers that I trigger by typing <code>brout</code> and <code>bnum</code>. You can see them listed in the middle Macros pane in the first three screenshots above.</p>
<p>Those macros are very similar to the one above. The only difference is that the trigger is “Typed String Trigger” instead of “Hot Key Trigger”, and instead of <code>%SystemClipboard%</code>, I put in my actual bank routing or account number.</p>
<p>There you have it. I hope you learned something new today. And if you were already doing something similar with a different tool, I’d love to hear about it. Let me know on <a href="https://twitter.com/monfresh">Twitter</a> or <a href="/about#contact">email me</a>.</p>
Automate Copying and Pasting the Current Page Title and URL with Keyboard Maestrohttps://www.moncefbelyamani.com/automate-pasting-title-and-url-of-frontmost-browser/2022-07-07T18:14:27-04:002022-07-29T10:59:22-04:00Moncef Belyamani<p>An action I perform multiple times per day is adding web page links to my Obsidian <a href="/tags/daily-note">daily notes</a>. I keep track of everything interesting or useful I come across.</p>
<p>When I first started my daily notes habit, I would manually copy the URL from the browser, then switch back to Obsidian, then format the link in Markdown format, and if I wanted the full title of the web page, I would go back to the browser, look for the title, select it, copy it, switch back to Obsidian, then paste it in between the square brackets.</p>
<p>I quickly realized that this should be automated, and the first tool I reach for is <a href="https://www.keyboardmaestro.com/main/">Keyboard Maestro</a>. It’s absolutely worth the $36. It’s by far the easiest and fastest way to automate this particular workflow without writing a single line of code.</p>
<p>Now, all I have to do is press ⌃⌥⌘L while a browser is in focus, and then ⌘-V in Obsidian, which Keyboard Maestro automatically puts in focus for me. The whole workflow takes less than 2 seconds, instead of 20 seconds.</p>
<p><strong>Let’s say this happens 5 times per weekday. That’s 90 seconds (18x5) saved per day. Over a year, with 4 weeks of vacation, that’s 48 weeks x 5 days/week x 90 seconds/day = 6 hours saved!</strong></p>
<p>In upcoming guides, I’ll show you how to automate this action using three other tools — Alfred, Raycast, and macOS Shortcuts — all of which require a script. Also, stay tuned for video versions of these tutorials. Subscribe to my brand new <a href="https://www.youtube.com/channel/UClQOoDTkpK6xHaQt7mOu6vQ">YouTube channel</a> and my <a href="/newsletter">newsletter</a> to be notified when they come out.</p>
<p>To keep this page light in size, the screenshots are low quality, but if you click on them, you’ll get the high-res image.</p>
<p>Here’s how easy it is to do this in Keyboard Maestro:</p>
<ol>
<li>Launch Keyboard Maestro</li>
<li>⌘-N to create a new Macro (or File -> New Macro)</li>
<li>It should look like this:
<a href="/images/new_macro_plain-fs8.png" target="_blank"><img src="/images/new_macro_plain-fs8-small.jpg" alt="Keyboard Maestro New Macro" width="750" height="330"/></a></li>
<li>In the right pane, replace “Untitled Macro” with a descriptive name like “Paste URL and title of current web page in Markdown”</li>
<li>Click on the “New Trigger” green button and select “Hot Key Trigger”, which should be the first choice.</li>
<li>Type in your desired hot key, such as ⌃⌥⌘L (control-option-command-L)</li>
<li>Click on the “New Action” green button at the bottom of the right pane. You should now see an Actions pane on the left that you can search through:
<a href="/images/km_new_action-fs8.png" target="_blank"><img src="/images/km_new_action-fs8-small.jpg" alt="Keyboard Maestro New Action" width="750" height="330"/></a></li>
<li>Search for “set clipboard”, then double-click on “Set Clipboard to Text”. You should end up with this:
<a href="/images/km_set_clipboard_to_text-fs8.png" target="_blank"><img src="/images/km_set_clipboard_to_text-fs8-small.jpg" alt="Keyboard Maestro Set Clipboard to Text" width="750" height="378"/></a></li>
<li>Inside the text field, type <code>[]()</code>, then with the cursor inside the square brackets, click on “Insert Token” above the top right of the text field, then choose “Front Browser”, then “Front Browser Document Title”. You can also directly type <code>%FrontBrowserTitle%</code>. This is Keyboard Maestro’s magic sauce. It comes with all kinds of variables out of the box that can fetch data for you without having to write scripts.
<a href="/images/km_insert_token-fs8.png" target="_blank"><img src="/images/km_insert_token-fs8-small.jpg" alt="Keyboard Maestro Insert Token" width="750" height="547"/></a></li>
<li>Place the cursor inside the parentheses, and this time either type <code>%FrontBrowserURL%</code>, or select “Front Browser -> Front Browser Document URL” via the Insert Token dropdown. You should end up with this:
<a href="/images/km_front_browser_title_and_url-fs8.png" target="_blank"><img src="/images/km_front_browser_title_and_url-fs8-small.jpg" alt="Keyboard Maestro Front Browser Document URL token" width="750" height="384"/></a></li>
<li>In the Actions pane, delete the search text “set clipboard”, and search for “activate”, then double-click on “Activate a Specific Application”. It will default to “Finder”.</li>
<li>Click on the “Activate:” dropdown, and select your desired app where you keep your links. You should end up with something like this:
<a href="/images/km_activate_application-fs8.png" target="_blank"><img src="/images/km_activate_application-fs8-small.jpg" alt="Keyboard Maestro activate application" width="750" height="474"/></a></li>
</ol>
<p>That’s it! Click the “Edit” button under the right pane to get out of Edit mode, and give your hot key a try. Make sure you have a browser in focus before you press your keyboard shortcut.</p>
<h2 id="important-notes">Important Notes</h2>
<p>This should work in all Chromium and WebKit browsers, but not in Firefox. Firefox doesn’t play nice with automation tools 😢. If the page title is missing when you paste from the clipboard, you’ll need to allow JavaScript from Apple Events. Here’s how:</p>
<h3 id="in-safari">In Safari</h3>
<p>From the menu bar, go to Develop -> Allow JavaScript from Apple Events</p>
<p>If you don’t have the “Develop” menu in Safari’s menu bar, go to Safari’s preferences, then the Advanced tab, then click on “Show Develop menu in menu bar”.</p>
<h3 id="in-chrome">In Chrome</h3>
<p>Go to View -> Developer -> Allow JavaScript from Apple Events</p>
<h3 id="in-microsoft-edge">In Microsoft Edge</h3>
<p>Go to Tools -> Developer -> Allow JavaScript from Apple Events</p>