/blog/

2026 0410 Color HTML Ghostty transcripts

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:

Last login: Wed Apr 8 13:54:21 on ttys003
13:57:48
E0
Orocroix
~
whoami micahrl
13:57:59
E0
Orocroix
~
hostname Orocroix.local
13:58:02
E0
Orocroix
~
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
13:58:14
E0
Orocroix
~

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;">&#8756;</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;">&#8756;</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;">&#8756;</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;">&#8756;</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.


  1. 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 ↩︎

Responses

Webmentions

Hosted on remote sites, and collected here via Webmention.io (thanks!).

Comments

Comments are hosted on this site and powered by Remark42 (thanks!).