With Ghostty, all I need to do to get a realistic transcript of a shell session is select and copy, extract the HTML source with pbpaste-htmlsrc, and reference it on this site with a Hugo shortcode.
As I learned previously, the macOS clipboard can hold more than one version of the same content.
Examples
Text copied from the built-in Terminal contains:
> osascript -e 'Tell app "Finder" to clipboard info'
«class RTF », 1272, «class utf8», 259, «class ut16», 512, string, 66, Unicode text, 510
And text from Ghostty contains:
> osascript -e 'Tell app "Finder" to clipboard info'
«class utf8», 356, «class HTML», 638, «class ut16», 714, string, 356, Unicode text, 712
Ghostty specifically includes HTML content in the data it provides to the pasteboard.
When pasting HTML pasteboard content,
the result is formatted text with hyperlinks, bold, italics, and so on.
pbpaste-htmlsrc gets the HTML source code for that formatted text.
# step 1: select text in a Ghostty window, and copy it with cmd-c. Then:
pbpaste-htmlsrc > transcript.html
And it makes for nice embedding in a Hugo site:
I use a shortcode for this and some CSS to clean it up:
HTML contents of transcript.html
Here’s the raw HTML that it produces:
<div style="font-family: monospace; white-space: pre;">Last login: Wed Apr 8 13:54:21 on ttys003
<div style="display: inline;color: rgb(255, 255, 255);font-weight: bold;">13:57:48</div> E0 <div style="display: inline;color: rgb(0, 191, 255);font-weight: bold;">Orocroix</div> <div style="display: inline;color: rgb(0, 251, 172);">~</div> <div style="display: inline;color: rgb(223, 149, 255);font-weight: bold;">∴</div> whoami
micahrl
<div style="display: inline;color: rgb(255, 255, 255);font-weight: bold;">13:57:59</div> E0 <div style="display: inline;color: rgb(0, 191, 255);font-weight: bold;">Orocroix</div> <div style="display: inline;color: rgb(0, 251, 172);">~</div> <div style="display: inline;color: rgb(223, 149, 255);font-weight: bold;">∴</div> hostname
Orocroix.local
<div style="display: inline;color: rgb(255, 255, 255);font-weight: bold;">13:58:02</div> E0 <div style="display: inline;color: rgb(0, 191, 255);font-weight: bold;">Orocroix</div> <div style="display: inline;color: rgb(0, 251, 172);">~</div> <div style="display: inline;color: rgb(223, 149, 255);font-weight: bold;">∴</div> ping naragua
PING naragua.banded-goblin.ts.net (100.112.192.27): 56 data bytes
64 bytes from 100.112.192.27: icmp_seq=0 ttl=64 time=87.343 ms
64 bytes from 100.112.192.27: icmp_seq=1 ttl=64 time=87.167 ms
64 bytes from 100.112.192.27: icmp_seq=2 ttl=64 time=88.772 ms
^C
--- naragua.banded-goblin.ts.net ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 87.167/87.761/88.772/0.719 ms
<div style="display: inline;color: rgb(255, 255, 255);font-weight: bold;">13:58:14</div> E0 <div style="display: inline;color: rgb(0, 191, 255);font-weight: bold;">Orocroix</div> <div style="display: inline;color: rgb(0, 251, 172);">~</div> <div style="display: inline;color: rgb(223, 149, 255);font-weight: bold;">∴</div> </div>
I use a shortcode like this to include it on this page:
{{- $filename := (path.Join (path.Dir .Page.File.Path) (.Get 0)) -}}
<div class="ghostty-transcript">
{{- readFile $filename | safeHTML -}}
</div>
Ghostty apparently doesn’t copy an <html> wrapper element or anything else, which we would have to strip,
but it also doesn’t copy the primary foreground and background colors.
I’m currently using the Cyberpunk theme,
so I specify its foreground and background colors in the CSS:
.ghostty-transcript {
color: #e5e5e5;
background-color: #332a57;
padding: 0.5em;
overflow-x: auto;
font-size: 0.8125em;
}
I’m using this weekly at work to retain logs of what I’ve done on production hosts1, because my work planner and notebok is actually just an intranet Hugo site. It’s made a real difference: in one instance something went wrong with one of my changes, and someone was able to find the problem based on the transcript I shared.
-
oh my god yes I am trying so hard to get configuration into a repo so I can stop opening shells on live production boxes ↩︎