TIL: magic-wormhole to send files

There are so many way to get a file from one computer to another: put it on a server, AirDrop, email, Slack, Google Drive, Dropbox, to name a few. Magic Wormhole is one more handy addition to that magic-wormhole is one more addition to that list.

What I like about magic-wormhole is that it’s a CLI and is completely ad hoc. There’s nothing to clean up when you’re done and no leftover artifacts in your email/Drive/Dropbox/etc. If you want to send a bunch of files, you don’t have to create a zip; that happens automatically. You run wormhole send on one machine, wormhole receive [code] on the other, and you’re done. The codes are short and easy to say aloud. Here’s what an exchange looks like:

Computer A

$ wormhole send projects
Building zipfile..
Sending directory (86.9 MB compressed) named 'projects'
Wormhole code is: 44-hemisphere-lemon
█▀▀▀▀▀▀▀█▀▀██▀▀█▀██▀▀▀█▀▀▀▀▀▀▀█
█ █▀▀▀█ █▄▀  █▄█▀▄▄ ▀▄█ █▀▀▀█ █
█ █   █ █▄▄▀▄██ ▀▀▄█ ██ █   █ █
█ ▀▀▀▀▀ █▀█ ▄ ▄▀█▀█ ▄ █ ▀▀▀▀▀ █
█▀█▀▀█▀▀▀▄ ▀▀▀▀█▀   ███▀██▀█▀▀█
█▄█▀ █ ▀▀▄█▄   ▄█  ▄▄▀  ▄▄   ▄█
█ █▀▄▀▀▀▄█▄ ▀██▀██▀▄▄ █████▀▀██
█▄▄█ ▄▀▀▄  ▀██▀▄▄▄ ▄ ▄▄ █▀▀█  █
█▀▀▀█▀ ▀▀▄██ ██ ▄██▀█▄▀▄█ ▄█▀ █
█▀█ ▄▀ ▀██▄█ ▄▀  ▀█ ████▄▀▀ ███
██▀█▀█ ▀▄ █▄▄█  ▀  ▀▄ ▀ ▀▀    █
█▀▀▀▀▀▀▀█ ▀█ ▄█▀█▀ ▄  █▀█ ▄ ▀▄█
█ █▀▀▀█ █▄▄█  ▄▄▀█▀█▄ ▀▀▀ █▀█▄█
█ █   █ █ ▀▄▀▀▄█▄ ▄▀▀█▄█▀▄▄▄█ █
█ ▀▀▀▀▀ █▀ ▀▄▀ ▄█▀█▀  ▀█▄ ██ ██
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
On the other computer, please run:

wormhole receive 44-hemisphere-lemon

Computer B

$ wormhole receive 44-hemisphere-lemon
Receiving directory (86.9 MB) into: 'projects'/
1093 files, 86.7 MB (uncompressed)
ok? (Y/n): Y
Receiving (->tcp:192.168.1.186:54503)..
100%|██████████████████████████████████| 86.9M/86.9M
Unpacking zipfile..
Received files written to: /Users/ryan/Downloads/projects

The code (44-hemisphere-lemon in this example) is short-lived and can only be used once. The docs have more details on how the exchange works. The package is magic-wormhole, with wormhole as an alias for the magic-wormhole command.

Update: a friend pointed me at a Rust implementation of magic-wormhole which gives you a single binary to install. The Homebrew package is magic-wormhole.rs.

TIL: main, figure, article and other HTML tags

I redid how images render on my site. While creating a new component for images, I learned about the <figure> and associated <figcaption> elements. Now my images render like this:

<figure>
  <a href="...">
    <img src="..." />
  </a>
  <figcaption>...</figcaption>
</figure>

I’ve never seen these before—are they new? I wondered. This is a recurring theme in my TILs, but no. Not even a little bit. They’re additions to HTML5 that arrived at different points between 2008–2015. There’s probably a meta lesson here about not assuming things I don’t know must be new, but that’s for me to dwell on. Here are a few more that I learned about and incorporated into my site:

  • <main> the content part of the page
  • <header> and <footer>
  • <nav> for site navigation
  • <article> for individual posts

I remember intense discussion about the value of semantic markup when I was paying closer attention to HTML. I don’t know how important that is in itself, but practically these tags help screen readers and other tools navigate your site better. I also find it nicer as an author to see more named tags instead of my usual pile of <div>s.

TIL: Sort branches by last commit date

I have too many git branches and like many people I developed a convoluted script to show me the most recent ones. I ran this command many times a day for years:

function branches {
  for k in `git branch | sed "s/^..//"`; do
    echo -e `git log --color -1 --pretty=format:"%Cgreen%ci %Cblue%cr%Creset" "$k"`\\t"$k";
  done | sort
}

This outputs the date as the first column in the output so that sort works. Turns out with modern git it’s totally unnecessary. Just configure git branch to sort by the last commit date:

git config branch.sort committerdate

You can also supply this on a one-off basis as git branch --sort committerdate.

What else can you sort by? The manual says “The keys supported are the same as those in git-for-each-ref(1).” The manpage for git-for-each-ref lists a ton of fields, including some that don’t make sense to me as sort keys. Here are a few that stood out as interesting:

  • authordate, committerdate, creatordate, taggerdate
  • contents:size: The size in bytes of the commit or tag message.
  • push: The name of a local ref which represents the @{push} location for the displayed ref.
  • refname: The name of the ref (the part after $GIT_DIR/) (this is the default).
  • upstream: The name of a local ref which can be considered “upstream” from the displayed ref.

TIL: Show the application switcher on all screens

I have multiple monitors and I use the cmd + tab application switcher often. I find the current behavior of macOS pretty confusing. By default, it only shows the switcher on the most recently used screen. But that’s not the screen I’m looking at when switching to another application!

After getting confused a bunch of times, I found this setting which lets you show the switcher on all screens:

defaults write com.apple.dock appswitcher-all-displays -bool true
killall Dock # you need to restart the Dock for settings to take effect
Screenshot of two screens with the same App Switcher active on each

It’s wonderful.

TIL: Control less options globally with LESS

While researching interactive flags in less I learned about the LESS env var. This lets you set less’s startup flags globally. For example, if you always want ANSI color escape sequences to be interpreted (so you see colors instead of control characters), put LESS=-R in your shell config.

export LESS=-R
less request.log

Screenshot of HTTP request log with HTTP status codes in color.

export LESS=
less request.log

Screenshot of HTTP request log with HTTP status codes surrounded by control characters like ESC[33m.

TIL: less lets you toggle startup flags interactively

I love the less pager. You can use it to read, search, tail, and more. As with many versatile programs, however, the man page for less has a bazillion different options:

$ man less

NAME
    less - opposite of more

SYNOPSIS
    less [-[+]aABcCdeEfFgGiIJKLmMnNqQrRsSuUVwWX~]

There’s hardly a letter it doesn’t use!

Of these, my favorites are:

  • -S or --chop-long-lines. Truncate lines rather than wrapping them.
  • -N or --LINE-NUMBERS. Show line numbers at the start of each line.
  • -R or --RAW-CONTROL-CHARS. Show control characters vs. interpret them.

You can supply these as flags when starting less. But you often don’t start less directly; it’s set as your pager and then gets invoked when you run commands that produce lots of output, like man or git. No matter because you can type the startup flags while less is running to toggle their behavior!

If lines are wrapped and you want to truncate them, just type -S. You’ll see a message at bottom describing the change and then you press enter to accept. Want to see what line number something is on? Type -N, enter.

Screen recording of opening an HTTP request log with less, then toggling escape codes to color, toggling wrapped long lines to be truncated, then enabling line numbers

As an aside: the dominant pager that preceded less was more. Despite the names, less has more features. On macOS, more is now less (or more precisely, /usr/bin/more and /usr/bin/less are the same file):

ls -lih /usr/bin/more /usr/bin/less
1152921500312523552 -rwxr-xr-x  2 360K Nov 22  2025 /usr/bin/less
1152921500312523552 -rwxr-xr-x  2 360K Nov 22  2025 /usr/bin/more

Whether you get more’s impoverished capabilities or less’s expansive set is determined by the name you use to invoke it.

TIL: Mojibake

I learned a new word today: mojibake!

Some of my old blog posts have random gibberish in them like ñ or ’. I always assumed this was the result of some character encoding mismatch but I didn’t know exactly what was going on. While I was fixing this issue via find/replace technology, I did some reading to learn more.

The short version is a mismatch between UTF-8 and Latin-1 (ISO-8859-1) encodings. As an example, this is how ñ becomes ñ.

First, when I type ñ in my CMS it is represented in UTF-8:

>> "ñ".codepoints
=> [241]
>> "ñ".bytes
=> [195, 177]

In UTF-8, codepoints above 127 are represented with more than one byte. You can see here the codepoint 241 (U+00F1) corresponds to two bytes, 195 and 177.

Then, when these bytes get written to the DB, they are not converted from UTF-8 to Latin-1. We store [195, 177] but believe they are Latin-1. Later, we re-encode these bytes as UTF-8 to convert from not-actually-Latin-1:

bytes = "ñ".bytes               # => [195, 177]
latin1_str = bytes.pack("C*").force_encoding("ISO-8859-1")
puts latin1_str.encode("UTF-8") # => ñ

I was fixing these bugs in old posts and one AI result said, “Oh yeah, this is a classic mojibake problem.” My first reaction was, “I don’t know man—How ‘classic’ can it be if I’m hearing it for the first time after working with strings and encodings for years?” But then again, you have to learn everything sometime. Maybe it is classic!

Mojibake or 文字化け is a Japanese word for exactly this kind of garbled text. Before UTF-8, the dominant character encoding in Japan was Shift JIS, and this kind of character mangling was probably common when text interacted with systems that did not convert encodings properly.

The “moji” in mojibake (文字) means text and, yes, it’s the same moji found in emoji (絵文字)! I knew that emoji originated in Japan but I did not know that the name was actually Japanese. I assumed that emo was related to emotion since the first emoji were all faces showing different emotions. Nope! E (絵) means pictures, moji (文字) means text.

Now that UTF-8 is the dominant text encoding, I’m pretty confident you’ll see all the text here correctly and not as a “classic” mojibake issue.

TIL: Spotlight can do math and unit conversions

You can open macOS’s Spotlight with cmd + space. Then you can type in math like (4 + 23) * 92 and it will show you the answer. I’ve been using this feature for awhile but I just learned that it can do a lot more than I realized! This came up because I typed 2**256 and it gave me the answer. Interesting…what else can it do?

Screenshot of Spotlight answers to math like round(4723/125) and sin(pi/2), as well as unit conversions like 95F and 10 miles in feet. The answer to each query is selected in blue so it can be copy/pasted.

I couldn’t find a comprehensive list of supported features. This Stack Overflow answer has a long list; one other commenter says it’s everything the macOS calculator can do. All of these things work:

  • Exponents, written as any of 2**3, 3^2, or pow(2, 10)
  • Factorials, written as 5! or fact(7)
  • Trigonometry, functions like sin, cos, tan, and constants like pi.
  • Lots of other math. You can round(), pow(), abs(), sqrt(), ceil(), floor(), and more.

The absolutely best part is that it does unit conversions. Historically this is one of the things I have always typed into Google search. I’m sure I’m going to use this all the time now. Type 95F and it tells you that’s 35°C. Type 170 lbs and it says 77 kg. You can specify the output unit like 4 Tbsp in mL or 10 miles in feet (it does not, however, support unhinged unit conversion like “1 mile in furlongs”).

One really nice UX touch is that Spotlight automatically selects the answer, so you can easily copy it with cmd + C without dragging.

TIL: pkill to stop a process by name

Sometimes when I learn about a tool I think “wow, that would have been useful [when I had some problem in the past]”. That happened with pkill, which is a command you can use to stop a process accurately.

Many years ago I was slogging through debugging a Selenium test failure in a Rails application with my coworker, Ron. Every time we made a change we had to stop the server, restart it, and re-run the test suite. This had a bunch of steps so naturally we wrote a script to automate them. At the end of the script, we killed the Rails server by looking for the process using port 3000 with kill -9 $(lsof -t -i:3000).

  • lsof lists open files. In unix/linux, everything is a file including things like network connections (via sockets).
  • -t terse output—just the pid
  • -i:3000 process using port 3000

It worked! Until one run when, all of a sudden, my entire browser disappeared. It was unexpected, abrupt, and rude. We were using that browser! Ron and I were really baffled … until we realized that there were two processes using the port: the server and the browser (maybe with a keep alive connection?).

This is what the lsof output looks like when you look for processes using a port:

$ lsof -i:3000 -P

COMMAND  PID    TYPE  NODE  NAME
ruby3.2  42727  IPv6  TCP   *:3000                           (LISTEN)
ruby3.2  42727  IPv6  TCP   localhost:3000->localhost:57537  (ESTABLISHED)
Google   6439   IPv6  TCP   localhost:57537->localhost:3000  (ESTABLISHED)

Notice that process ID 42727 is using :3000 in the LISTEN state, while that same process and Chrome are both using it for a connection in the ESTABLISHED state. So we got unlucky on one run, grabbed the browser’s process ID, and unceremonously killed it. At the time, we worked around this by adding another flag, -sTCP:LISTEN. This filters to TCP sockets in the LISTEN state, so we’d always get the server process.

pkill would have been perfect for this! It stops processes by matching the process name. For example, pkill ruby would stop all running ruby processes. That’s a little aggressive if you only want to stop one process. With pkill -f you match the program arguments as well, like ruby bin/rails so pkill -9 -f rails would do it. (You could also argue that I should have the server process itself write its pid to a file to later stop it. You’re probably right!)

One thing I love is programs that have two modes to either (1) take action or (2) preview that action. Many systems expose this as a “dry run”. In the case of pkill, the dry-run equivalent is pgrep, which works the same way, but just outputs the pid instead of sending a kill signal.

pkill has a ton of other options so be sure to look at the tldr or man page.

TIL: cmd + shift + G to Go To Folder

GUIs are great! But I’m used to typing cd, and sometimes I prefer that to clicking around to get somewhere.

MacOS merges these worlds with cmd + shift + G. In the Finder it’s under Go → Go to Folder.... This opens a dialog where you can type a path to navigate to. It supports tab completion, it understands that ~ is your home directory, and it shows recently used options that you can navigate with your arrow keys.

Screenshot of Go to Folder window with ~/code/ in the input box and a list of possible destinations below

It also works in the open/save dialog boxes! This is more hidden; as far as I know there is no menu that shows “Go To Folder” so you just have to know the cmd + shift + G shortcut.

Screenshot of Go to Folder being used with an open/save dialog box in the background

Now when I’m not navigating to the Desktop, Documents, or Downloads (which each have their own keyboard shortcuts), I often use cmd + shift + G to get there.