<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.2">Jekyll</generator><link href="https://matthewbilyeu.com/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://matthewbilyeu.com/blog/" rel="alternate" type="text/html" /><updated>2026-02-06T13:03:35-05:00</updated><id>https://matthewbilyeu.com/blog/feed.xml</id><title type="html">Matt’s programming blog</title><entry><title type="html">Desktop notification that takes you to the tmux pane that notified</title><link href="https://matthewbilyeu.com/blog/2026-02-05/desktop-notification-that-takes-you-to-the-tmux-pane-that-notified" rel="alternate" type="text/html" title="Desktop notification that takes you to the tmux pane that notified" /><published>2026-02-05T17:26:38-05:00</published><updated>2026-02-05T17:26:38-05:00</updated><id>https://matthewbilyeu.com/blog/2026-02-05/desktop-notification-that-takes-you-to-the-tmux-pane-that-notified</id><content type="html" xml:base="https://matthewbilyeu.com/blog/2026-02-05/desktop-notification-that-takes-you-to-the-tmux-pane-that-notified"><![CDATA[<p>After a bit of searching I didn’t find any obvious solution to the problem of: I want a process running in a tmux pane to send a desktop notification, and when I click that notification I’m taken directly to the source pane.</p>

<p>Here are a couple scripts that will achieve it (courtesy of opencode and the new codex-5.3 model).</p>

<p>It’s tailored to my setup:</p>

<ul>
  <li>macos</li>
  <li>Kitty terminal emulator</li>
  <li>tmux running locally</li>
</ul>

<p>Example invocation:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./tmux-notify-return.sh &lt;title&gt; &lt;message&gt;
</code></pre></div></div>

<p><img src="/blog/assets/17703483936920.5817444016579488@content.messagingengine.com.Kapture 2026-02-05 at 22.23.27.gif" alt="" /></p>

<p>It’s probably a little brittle, but I think it will address a pain point I’ve been facing with a flurry of agents running across different tmux windows and panes.</p>

<p>Here are the scripts:</p>

<p><code class="language-plaintext highlighter-rouge">tmux-return.sh</code></p>

<p>What it does: click-handler script run by Notification Center; activates Kitty, switches the correct tmux client/window/pane, and retries briefly to avoid focus races.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-euo</span> pipefail

<span class="k">if</span> <span class="o">[[</span> <span class="nv">$# </span><span class="nt">-lt</span> 4 <span class="o">||</span> <span class="nv">$# </span><span class="nt">-gt</span> 5 <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"Usage: </span><span class="nv">$0</span><span class="s2"> &lt;client_tty&gt; &lt;socket_path&gt; &lt;target_win&gt; &lt;target_pane&gt; [target_pane_id]"</span> <span class="o">&gt;</span>&amp;2
  <span class="nb">exit </span>2
<span class="k">fi

</span><span class="nv">client_tty</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
<span class="nv">socket_path</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
<span class="nv">target_win</span><span class="o">=</span><span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span>
<span class="nv">target_pane</span><span class="o">=</span><span class="s2">"</span><span class="nv">$4</span><span class="s2">"</span>
<span class="nv">target_pane_id</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">5</span><span class="k">:-}</span><span class="s2">"</span>

<span class="nv">TMUX_BIN</span><span class="o">=</span><span class="s2">"/opt/homebrew/bin/tmux"</span>
<span class="nv">OSA_BIN</span><span class="o">=</span><span class="s2">"/usr/bin/osascript"</span>

<span class="s2">"</span><span class="nv">$OSA_BIN</span><span class="s2">"</span> <span class="nt">-e</span> <span class="s1">'tell application id "net.kovidgoyal.kitty" to activate'</span>

<span class="k">for </span>_ <span class="k">in </span>1 2 3 4 5 6 7 8 9 10<span class="p">;</span> <span class="k">do</span>
  <span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> <span class="nt">-S</span> <span class="s2">"</span><span class="nv">$socket_path</span><span class="s2">"</span> switch-client <span class="nt">-c</span> <span class="s2">"</span><span class="nv">$client_tty</span><span class="s2">"</span> <span class="nt">-t</span> <span class="s2">"</span><span class="nv">$target_win</span><span class="s2">"</span> <span class="o">||</span> <span class="nb">true</span>
  <span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> <span class="nt">-S</span> <span class="s2">"</span><span class="nv">$socket_path</span><span class="s2">"</span> <span class="k">select</span><span class="nt">-window</span> <span class="nt">-t</span> <span class="s2">"</span><span class="nv">$target_win</span><span class="s2">"</span> <span class="o">||</span> <span class="nb">true

  </span><span class="k">if</span> <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$target_pane_id</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
    <span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> <span class="nt">-S</span> <span class="s2">"</span><span class="nv">$socket_path</span><span class="s2">"</span> <span class="k">select</span><span class="nt">-pane</span> <span class="nt">-t</span> <span class="s2">"</span><span class="nv">$target_pane_id</span><span class="s2">"</span> <span class="o">||</span> <span class="nb">true
  </span><span class="k">else</span>
    <span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> <span class="nt">-S</span> <span class="s2">"</span><span class="nv">$socket_path</span><span class="s2">"</span> <span class="k">select</span><span class="nt">-pane</span> <span class="nt">-t</span> <span class="s2">"</span><span class="nv">$target_pane</span><span class="s2">"</span> <span class="o">||</span> <span class="nb">true
  </span><span class="k">fi

  if</span> <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$target_pane_id</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">current_pane</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> <span class="nt">-S</span> <span class="s2">"</span><span class="nv">$socket_path</span><span class="s2">"</span> display-message <span class="nt">-p</span> <span class="nt">-c</span> <span class="s2">"</span><span class="nv">$client_tty</span><span class="s2">"</span> <span class="s1">'#{pane_id}'</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">true</span><span class="si">)</span><span class="s2">"</span>
    <span class="o">[[</span> <span class="s2">"</span><span class="nv">$current_pane</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"</span><span class="nv">$target_pane_id</span><span class="s2">"</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">exit </span>0
  <span class="k">else
    </span><span class="nv">current_pane</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> <span class="nt">-S</span> <span class="s2">"</span><span class="nv">$socket_path</span><span class="s2">"</span> display-message <span class="nt">-p</span> <span class="nt">-c</span> <span class="s2">"</span><span class="nv">$client_tty</span><span class="s2">"</span> <span class="s1">'#{session_name}:#{window_index}.#{pane_index}'</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">true</span><span class="si">)</span><span class="s2">"</span>
    <span class="o">[[</span> <span class="s2">"</span><span class="nv">$current_pane</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"</span><span class="nv">$target_pane</span><span class="s2">"</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">exit </span>0
  <span class="k">fi

  </span><span class="nb">sleep </span>0.1
<span class="k">done

</span><span class="nb">exit </span>1
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">tmux-notify-return.sh</code></p>

<p>What it does: captures the tmux pane running this command (TMUX_PANE), then sends a clickable notification whose click runs tmux-return.sh back to that captured pane.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-euo</span> pipefail

<span class="c"># Send a clickable macOS notification that returns you to the tmux pane</span>
<span class="c"># where this command was run.</span>
<span class="nv">TMUX_BIN</span><span class="o">=</span><span class="s2">"/opt/homebrew/bin/tmux"</span>
<span class="nv">NOTIFIER_BIN</span><span class="o">=</span><span class="s2">"/opt/homebrew/bin/terminal-notifier"</span>
<span class="nv">RETURN_SCRIPT</span><span class="o">=</span><span class="s2">"/path/to/tmux-return.sh"</span>

<span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="k">:-}</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"-h"</span> <span class="o">||</span> <span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="k">:-}</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"--help"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"Usage: </span><span class="nv">$0</span><span class="s2"> [title] [message]"</span>
  <span class="nb">echo</span> <span class="s2">"  title   Notification title (default: Return to tmux pane)"</span>
  <span class="nb">echo</span> <span class="s2">"  message Notification body  (default: Click to jump back in Kitty)"</span>
  <span class="nb">exit </span>0
<span class="k">fi

</span><span class="nv">title</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="k">:-</span><span class="nv">Return</span><span class="p"> to tmux pane</span><span class="k">}</span><span class="s2">"</span>
<span class="nv">message</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">2</span><span class="k">:-</span><span class="nv">Click</span><span class="p"> to jump back in Kitty</span><span class="k">}</span><span class="s2">"</span>

<span class="k">if</span> <span class="o">[[</span> <span class="o">!</span> <span class="nt">-x</span> <span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"tmux not found at </span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> <span class="o">&gt;</span>&amp;2
  <span class="nb">exit </span>1
<span class="k">fi

if</span> <span class="o">[[</span> <span class="o">!</span> <span class="nt">-x</span> <span class="s2">"</span><span class="nv">$NOTIFIER_BIN</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"terminal-notifier not found at </span><span class="nv">$NOTIFIER_BIN</span><span class="s2">"</span> <span class="o">&gt;</span>&amp;2
  <span class="nb">exit </span>1
<span class="k">fi

if</span> <span class="o">[[</span> <span class="o">!</span> <span class="nt">-x</span> <span class="s2">"</span><span class="nv">$RETURN_SCRIPT</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"return script not executable: </span><span class="nv">$RETURN_SCRIPT</span><span class="s2">"</span> <span class="o">&gt;</span>&amp;2
  <span class="nb">exit </span>1
<span class="k">fi</span>

<span class="c"># Capture the pane that is actually running this process when possible.</span>
<span class="c"># In tmux shells, TMUX_PANE is stable even if the user navigates away later.</span>
<span class="nv">source_pane</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">TMUX_PANE</span><span class="k">:-}</span><span class="s2">"</span>

<span class="k">if</span> <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$source_pane</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nv">target_win</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> display-message <span class="nt">-p</span> <span class="nt">-t</span> <span class="s2">"</span><span class="nv">$source_pane</span><span class="s2">"</span> <span class="s1">'#S:#I'</span><span class="si">)</span><span class="s2">"</span>
  <span class="nv">target_pane</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> display-message <span class="nt">-p</span> <span class="nt">-t</span> <span class="s2">"</span><span class="nv">$source_pane</span><span class="s2">"</span> <span class="s1">'#S:#I.#P'</span><span class="si">)</span><span class="s2">"</span>
  <span class="nv">target_pane_id</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> display-message <span class="nt">-p</span> <span class="nt">-t</span> <span class="s2">"</span><span class="nv">$source_pane</span><span class="s2">"</span> <span class="s1">'#{pane_id}'</span><span class="si">)</span><span class="s2">"</span>
<span class="k">else
  </span><span class="nv">target_win</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> display-message <span class="nt">-p</span> <span class="s1">'#S:#I'</span><span class="si">)</span><span class="s2">"</span>
  <span class="nv">target_pane</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> display-message <span class="nt">-p</span> <span class="s1">'#S:#I.#P'</span><span class="si">)</span><span class="s2">"</span>
  <span class="nv">target_pane_id</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> display-message <span class="nt">-p</span> <span class="s1">'#{pane_id}'</span><span class="si">)</span><span class="s2">"</span>
<span class="k">fi

</span><span class="nv">client_tty</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> display-message <span class="nt">-p</span> <span class="s1">'#{client_tty}'</span><span class="si">)</span><span class="s2">"</span>
<span class="nv">socket_path</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="s2">"</span><span class="nv">$TMUX_BIN</span><span class="s2">"</span> display-message <span class="nt">-p</span> <span class="s1">'#{socket_path}'</span><span class="si">)</span><span class="s2">"</span>

<span class="k">if</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$client_tty</span><span class="s2">"</span> <span class="o">||</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$socket_path</span><span class="s2">"</span> <span class="o">||</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$target_win</span><span class="s2">"</span> <span class="o">||</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$target_pane</span><span class="s2">"</span> <span class="o">||</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$target_pane_id</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"failed to capture tmux target metadata"</span> <span class="o">&gt;</span>&amp;2
  <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nb">printf</span> <span class="nt">-v</span> q_client <span class="s1">'%q'</span> <span class="s2">"</span><span class="nv">$client_tty</span><span class="s2">"</span>
<span class="nb">printf</span> <span class="nt">-v</span> q_socket <span class="s1">'%q'</span> <span class="s2">"</span><span class="nv">$socket_path</span><span class="s2">"</span>
<span class="nb">printf</span> <span class="nt">-v</span> q_win <span class="s1">'%q'</span> <span class="s2">"</span><span class="nv">$target_win</span><span class="s2">"</span>
<span class="nb">printf</span> <span class="nt">-v</span> q_pane <span class="s1">'%q'</span> <span class="s2">"</span><span class="nv">$target_pane</span><span class="s2">"</span>
<span class="nb">printf</span> <span class="nt">-v</span> q_pane_id <span class="s1">'%q'</span> <span class="s2">"</span><span class="nv">$target_pane_id</span><span class="s2">"</span>

<span class="nv">exec_cmd</span><span class="o">=</span><span class="s2">"</span><span class="nv">$RETURN_SCRIPT</span><span class="s2"> </span><span class="nv">$q_client</span><span class="s2"> </span><span class="nv">$q_socket</span><span class="s2"> </span><span class="nv">$q_win</span><span class="s2"> </span><span class="nv">$q_pane</span><span class="s2"> </span><span class="nv">$q_pane_id</span><span class="s2">"</span>

<span class="s2">"</span><span class="nv">$NOTIFIER_BIN</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">-title</span> <span class="s2">"</span><span class="nv">$title</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">-subtitle</span> <span class="s2">"</span><span class="nv">$target_pane</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">-message</span> <span class="s2">"</span><span class="nv">$message</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">-execute</span> <span class="s2">"</span><span class="nv">$exec_cmd</span><span class="s2">"</span>
</code></pre></div></div>]]></content><author><name>Matthew Bilyeu</name></author><summary type="html"><![CDATA[After a bit of searching I didn’t find any obvious solution to the problem of: I want a process running in a tmux pane to send a desktop notification, and when I click that notification I’m taken directly to the source pane.]]></summary></entry><entry><title type="html">Agent harness improvements</title><link href="https://matthewbilyeu.com/blog/2026-01-30/agent-harness-improvements" rel="alternate" type="text/html" title="Agent harness improvements" /><published>2026-01-30T14:41:44-05:00</published><updated>2026-01-30T14:41:44-05:00</updated><id>https://matthewbilyeu.com/blog/2026-01-30/agent-harness-improvements</id><content type="html" xml:base="https://matthewbilyeu.com/blog/2026-01-30/agent-harness-improvements"><![CDATA[<p>Just logging for posterity that AI models and specifically coding agent harnesses have gotten really good at discovering and using tools lately.</p>

<p>Some of my recentish posts are already dated and seem obvious now:</p>

<ul>
  <li><a href="https://matthewbilyeu.com/blog/2025-08-04/agentic-git-bisect">https://matthewbilyeu.com/blog/2025-08-04/agentic-git-bisect</a></li>
  <li><a href="https://matthewbilyeu.com/blog/2025-07-10/coding-with-agent-helper-scripts">https://matthewbilyeu.com/blog/2025-07-10/coding-with-agent-helper-scripts</a></li>
</ul>

<p>I’ve been having this feeling on and off since 2022, but I need to check most impulses to build any significant low-level (close to the model) customization on top of what’s offered. It’s invariably only a matter of (short) time until the foundation models overcome the next crop of limitations and the harnesses fill more ergonomic gaps. It feels like Skills are getting to a good place, but there’s still quite a lot of thrash around security best practices, MCP vs direct CLI/SDK/cURL, orchestration, and agent to agent communication.</p>]]></content><author><name>Matthew Bilyeu</name></author><summary type="html"><![CDATA[Just logging for posterity that AI models and specifically coding agent harnesses have gotten really good at discovering and using tools lately.]]></summary></entry><entry><title type="html">Agentic git bisect</title><link href="https://matthewbilyeu.com/blog/2025-08-04/agentic-git-bisect" rel="alternate" type="text/html" title="Agentic git bisect" /><published>2025-08-04T10:49:54-04:00</published><updated>2025-08-04T10:49:54-04:00</updated><id>https://matthewbilyeu.com/blog/2025-08-04/agentic-git-bisect</id><content type="html" xml:base="https://matthewbilyeu.com/blog/2025-08-04/agentic-git-bisect"><![CDATA[<p><a href="https://git-scm.com/docs/git-bisect">Git bisect</a> is a git command to find when some property in your codebase changed, e.g. when a bug was introduced or when a variable was first used.</p>

<blockquote>
  <p> This command uses a binary search algorithm to find which commit in your project’s history introduced a bug. You use it by first telling it a “bad” commit that is known to contain the bug, and a “good” commit that is known to be before the bug was introduced. Then <code class="language-plaintext highlighter-rouge">git</code> <code class="language-plaintext highlighter-rouge">bisect</code>picks a commit between those two endpoints and asks you whether the selected commit is “good” or “bad”. It continues narrowing down the range until it finds the exact commit that introduced the change.</p>
</blockquote>

<blockquote>
  <p>In fact, <code class="language-plaintext highlighter-rouge">git</code> <code class="language-plaintext highlighter-rouge">bisect</code> can be used to find the commit that changed <strong>any</strong> property of your project; e.g., the commit that fixed a bug, or the commit that caused a benchmark’s performance to improve.</p>
</blockquote>

<p>In the hands of an AI agent coding assistant like Claude Code, git bisect can quickly find that property change event for you with very little intervention. Connect it to tools like Jira and GitHub and code archaeology is much faster.</p>]]></content><author><name>Matthew Bilyeu</name></author><summary type="html"><![CDATA[Git bisect is a git command to find when some property in your codebase changed, e.g. when a bug was introduced or when a variable was first used.]]></summary></entry><entry><title type="html">MacWhisper</title><link href="https://matthewbilyeu.com/blog/2025-07-19/macwhisper" rel="alternate" type="text/html" title="MacWhisper" /><published>2025-07-19T11:43:20-04:00</published><updated>2025-07-19T11:43:20-04:00</updated><id>https://matthewbilyeu.com/blog/2025-07-19/macwhisper</id><content type="html" xml:base="https://matthewbilyeu.com/blog/2025-07-19/macwhisper"><![CDATA[<p>After sparse but longtime frustration with macOS’s built-in dictation (doesn’t input in every context, transcription is mid), I finally installed <a href="https://goodsnooze.gumroad.com/l/macwhisper">MacWhisper</a>. After just a few minutes of setup it already seems much better than the built-in option. Obligatory gen AI commentary: yes I am using it for vibecoding. This post isn’t dictated, though – my partner is watching TV beside me. Next step: <a href="https://en.wikipedia.org/wiki/Brain%E2%80%93computer_interface">BCI</a>.</p>]]></content><author><name>Matthew Bilyeu</name></author><summary type="html"><![CDATA[After sparse but longtime frustration with macOS’s built-in dictation (doesn’t input in every context, transcription is mid), I finally installed MacWhisper. After just a few minutes of setup it already seems much better than the built-in option. Obligatory gen AI commentary: yes I am using it for vibecoding. This post isn’t dictated, though – my partner is watching TV beside me. Next step: BCI.]]></summary></entry><entry><title type="html">Coding with agent helper scripts</title><link href="https://matthewbilyeu.com/blog/2025-07-10/coding-with-agent-helper-scripts" rel="alternate" type="text/html" title="Coding with agent helper scripts" /><published>2025-07-10T08:16:59-04:00</published><updated>2025-07-10T08:16:59-04:00</updated><id>https://matthewbilyeu.com/blog/2025-07-10/coding-with-agent-helper-scripts</id><content type="html" xml:base="https://matthewbilyeu.com/blog/2025-07-10/coding-with-agent-helper-scripts"><![CDATA[<p>Lately I’ve been doing more AI agent assisted coding and vibecoding using Cursor and Claude Code. Oftentimes the agent will need some feedback about how a particular approach is working. This can involve me running the program, going through a particular UX flow, and then reporting the results back to the agent. Such manual verification is time consuming and kind of tedious when there are multiple rounds of back and forth.</p>

<p>A technique that helps is to prompt the agent to write a small helper script to test some functionality — like interfacing with an API or database or evaluating an algorithm with particular inputs. The agent can then invoke that small script itself quickly, view debug output, and plan its next move. It takes me out of that loop and makes the whole experience smoother. It’s not quite a unit test or full integration test, just some throwaway code to give the agent a feeler into the environment.</p>]]></content><author><name>Matthew Bilyeu</name></author><summary type="html"><![CDATA[Lately I’ve been doing more AI agent assisted coding and vibecoding using Cursor and Claude Code. Oftentimes the agent will need some feedback about how a particular approach is working. This can involve me running the program, going through a particular UX flow, and then reporting the results back to the agent. Such manual verification is time consuming and kind of tedious when there are multiple rounds of back and forth.]]></summary></entry><entry><title type="html">Vibecoding’s allure: the inventor and the fiend</title><link href="https://matthewbilyeu.com/blog/2025-06-14/vibecoding-s-allure-the-inventor-and-the-fiend" rel="alternate" type="text/html" title="Vibecoding’s allure: the inventor and the fiend" /><published>2025-06-14T17:13:18-04:00</published><updated>2025-06-14T17:13:18-04:00</updated><id>https://matthewbilyeu.com/blog/2025-06-14/vibecoding-s-allure-the-inventor-and-the-fiend</id><content type="html" xml:base="https://matthewbilyeu.com/blog/2025-06-14/vibecoding-s-allure-the-inventor-and-the-fiend"><![CDATA[<p><img src="/blog/assets/17499380549160.26184962180456517@content.messagingengine.com.vibecoding.png.jpeg" alt="" /></p>

<p>The first time I programmed a computer I was in high school. I learned to write small Visual Basic programs in an elective class called Computer Programming. Instantly, I “got it” (though at the time <em>it</em> wasn’t crisply articulated in my mind): that a program was a machine that operated on information rather than matter; that writing code was like building such a machine; and that the computer could automate any informational work that could be precisely described to it. Writing code was the way to conjure informational machines.</p>

<p>As I learned, there was a long while where I didn’t really know what I was doing. I didn’t grasp recursion or object orientation too well at first. As I did my many assignments and projects, I was feeling my way through the dark. In those days I did plenty of guess and check debugging. And this continued as I exposed myself to new languages, paradigms, and technologies. I didn’t always immediately know whether or not some code I’d cobbled together would do the thing I wanted, but I knew what I wanted it to do. I could explain it pretty clearly in English, and I could tell whether the goal had been sufficiently satisfied when I ran the program and the tests passed. This was not exactly fun, but it triggered some reward circuit in my brain. Run the code: it fails; tweak this part: it fails differently, interesting; move that there: it works!</p>

<p>These two paths intertwined and developed into a love of building: first, the slow satisfaction of inventing something useful that essentially existed only in the world of ideas, and second, the pleasant thrum of hitting goal after goal.</p>

<p>Once I’d actually more or less figured out what I was doing, the shine wore off a bit. I understood that software could merely appear to run correctly while containing subtle logical bugs. And that the trial and error approach is often slower in the long run than RTFMing. The systems I was building with other people were valuable, but creating professional grade software took a really long time and required a lot of diverse expertise. I don’t think I became disillusioned, but rather the reality of the labor of software engineering set in. I saw that it wasn’t much of a game.</p>

<p>Lately, though, I’m starting to feel like the advent of agentic AI and vibecoding has reignited in me some of the appeal of creating software. I recently saw this take somewhere: “vibecoding is like gambling”. It was meant to be derisive, and it’s kind of true, but it’s that <em>chance</em> aspect that makes vibecoding fun. You ask the agent to do something repeatedly, feeding it back resultant errors and additional context, and nudging it till it gets the right answer. It reminds me of the pleasure I felt long ago when I would stay up late just fiddling with a program until it ran.</p>

<p>With generative AI, I can turn ideas into programs much faster than before and without the chore of learning some API I may never need again, or some library that will be obsolete the next time I reach for it. Seeing a working prototype takes minutes instead of days, and the fast feedback loop between my brain and the UI gets me easily into a flow state (I felt similarly when I worked in Clojure). It feels empowering and exciting. For the time being, I’m energized by the new AI-backed software generation tools.</p>

<p>To be clear, what I get out of vibecoding is orthogonal to <em>craft</em>, which is a different kind of pursuit. I don’t know whether or to what extent craftsmanship is compatible with AI-assisted coding. I think it still requires some clarity of thought to work with these new tools effectively, and there is a bit of an art to it. But I also have to imagine that with more powerful foundational coding models and more capable agentic systems, the human developer will be even further removed from production. There are all kinds of implications for what this will mean for the career of software engineering, which I’ve written about before. For knowledge workers generally, on a long time horizon, this de-emphasis on production may turn us from creators into curators; rather than build the system we may just tune the system, by thumbing up or down its decisions. On the other hand, maybe it could allow us to unleash our imaginations.</p>]]></content><author><name>Matthew Bilyeu</name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Philadelphia Data Explorer</title><link href="https://matthewbilyeu.com/blog/2025-06-14/philadelphia-data-explorer" rel="alternate" type="text/html" title="Philadelphia Data Explorer" /><published>2025-06-14T08:20:05-04:00</published><updated>2025-06-14T08:20:05-04:00</updated><id>https://matthewbilyeu.com/blog/2025-06-14/philadelphia-data-explorer</id><content type="html" xml:base="https://matthewbilyeu.com/blog/2025-06-14/philadelphia-data-explorer"><![CDATA[<p>My home city of Philadelphia, PA makes various datasets related to city housing, crime, traffic, and bureaucracy <a href="https://cityofphiladelphia.carto.com/datasets">publicly available</a>. I discovered that the data is all accessible via a <a href="https://carto.com/">Carto</a> REST API, and that the queries are in SQL.</p>

<p>I (vibe)coded this small local web application to let you easily query the datasets and view and download the results.</p>

<p>Check it out here:</p>

<p><a href="https://matthewbilyeu.com/phila-data-explorer/">https://matthewbilyeu.com/phila-data-explorer/</a></p>

<p>If I find some time I’d like to make an mcp server for it as well.</p>

<p><img src="/blog/assets/17499179673100.0899403878712507@content.messagingengine.com.Screenshot 2025-06-14 at 12.19.21 PM.png.jpeg" alt="" /></p>]]></content><author><name>Matthew Bilyeu</name></author><summary type="html"><![CDATA[My home city of Philadelphia, PA makes various datasets related to city housing, crime, traffic, and bureaucracy publicly available. I discovered that the data is all accessible via a Carto REST API, and that the queries are in SQL. I (vibe)coded this small local web application to let you easily query the datasets and view and download the results. Check it out here:]]></summary></entry><entry><title type="html">Plant identification</title><link href="https://matthewbilyeu.com/blog/2025-05-25/plant-identification" rel="alternate" type="text/html" title="Plant identification" /><published>2025-05-25T05:56:57-04:00</published><updated>2025-05-25T05:56:57-04:00</updated><id>https://matthewbilyeu.com/blog/2025-05-25/plant-identification</id><content type="html" xml:base="https://matthewbilyeu.com/blog/2025-05-25/plant-identification"><![CDATA[<p><img src="/blog/assets/17481813194800.21814995906221046@content.messagingengine.com.image.jpeg.jpeg" alt="" /></p>

<p>Despite AI advancements I don’t think plant identification is solved.</p>

<p>Take the above plant, a weed that started growing near my house.</p>

<ul>
  <li>The iPhone photo built-in identification says this is Jesuit’s tea</li>
  <li>ChatGPT says “That plant looks like epazote (Dysphania ambrosioides), a pungent herb commonly used in Mexican and Central American cooking” (this is aka Jesuit’s tea)</li>
  <li>Seek by iNaturalist says Pennsylvania pellitory</li>
</ul>

<p>Based on more research I’m inclined to say Seek was right, that this random weed is Pennsylvania pellitory.</p>

<p>Aside from referencing other photos and examining the stem and leaves closely, I found this <a href="https://urban-herbology.org/tag/pennsylvania-pellitory/">helpful website</a> describing Pennsylvania pellitory:</p>

<blockquote>
  <p>It is called Glaskruid in Dutch and <a href="https://www.backyardnature.net/n/h/pellitor.htm">Cucumber weed</a> in parts of the USA. Both helpful common names as it kind of looks glassy when held to the light (translucent) and it has a mild cucumer taste.</p>
</blockquote>

<p>The weed on my property did have these properties (I smelled it before tasting it).</p>

<p>ChatGPT doubles down on its incorrect answer:</p>

<p><img src="/blog/assets/17481811485110.04045397933008743@content.messagingengine.com.image.png.jpeg" alt="" /></p>

<p>I wonder how Seek got it right while the other two didn’t. Aside from it probably using a better model fine-tuned on lots of plant photos, I believe it incorporates time of year and location info as well.</p>]]></content><author><name>Matthew Bilyeu</name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Let’s Flip an Unfair Coin</title><link href="https://matthewbilyeu.com/blog/2025-05-20/let-s-flip-an-unfair-coin" rel="alternate" type="text/html" title="Let’s Flip an Unfair Coin" /><published>2025-05-20T05:55:19-04:00</published><updated>2025-05-20T05:55:19-04:00</updated><id>https://matthewbilyeu.com/blog/2025-05-20/let-s-flip-an-unfair-coin</id><content type="html" xml:base="https://matthewbilyeu.com/blog/2025-05-20/let-s-flip-an-unfair-coin"><![CDATA[<p>Flipping a coin is a universal way to quickly solve small disagreements between two parties. E.g. you want pizza and I want Mexican, so let’s just flip a coin.</p>

<p>But why do we typically flip a <em>fair</em> coin? The  average coin flip gives each person a 50% probability of having their way, but that might not make sense.</p>

<p>Suppose one of the two parties has a much stronger preference, or a stronger argument — perhaps the stronger position shouldn’t automatically win, because that would ignore the other party’s valid preferences and arguments, even if they’re weaker.</p>

<p>Say I’m merely in the mood for a burrito but you are really craving pizza. By flipping an <em>unfair</em> coin that approximates our relative preferences we’re still letting chance take the wheel, but in a way that better aligns with the situation. Maybe we could flip a coin that has a 30% chance of landing on burrito but a 70% chance of landing on pizza.</p>

<p>Or say that in some disagreement we both have solid arguments for our position, but I admit your arguments are slightly stronger. I don’t want to fully concede, but I may be willing to flip a coin that gives your preferred outcome a 60% chance.</p>

<p>Of course we don’t have easy access to unfair physical coins, but anyone reading this blog most likely has a computer in their pocket that can do unfair coin flips easily.</p>

<p>Maybe this is a weird robotic way to interact with people when common sense fairness and communication will do, but for low stakes disagreements I think unfair coin flipping could be a time saving tool that leaves both parties feeling accounted for.</p>]]></content><author><name>Matthew Bilyeu</name></author><summary type="html"><![CDATA[Flipping a coin is a universal way to quickly solve small disagreements between two parties. E.g. you want pizza and I want Mexican, so let’s just flip a coin.]]></summary></entry><entry><title type="html">Flight Focus</title><link href="https://matthewbilyeu.com/blog/2025-05-10/flight-focus" rel="alternate" type="text/html" title="Flight Focus" /><published>2025-05-10T05:37:18-04:00</published><updated>2025-05-10T05:37:18-04:00</updated><id>https://matthewbilyeu.com/blog/2025-05-10/flight-focus</id><content type="html" xml:base="https://matthewbilyeu.com/blog/2025-05-10/flight-focus"><![CDATA[<p>To me, an airplane cabin is an almost ideal place to read, write, study, focus, etc. I tend to get distracted and procrastinate a lot, but flights have been a pretty good environment for deep thought.</p>

<ul>
  <li>Minimal distraction: there technically is internet access on many flights, but it costs money, so access is disincentivized. The only available media are the built-in entertainment options or whatever you bring along. There’s minimal range of movement so you can’t even go for a walk or have a look around.</li>
  <li>Hard to sleep: you can’t simply snooze because it’s quite uncomfortable and you’re forced to be sitting</li>
  <li>Can’t leave</li>
  <li>Predetermined duration: you know approx how long you’ll be sitting there, making it possible to plan focus blocks</li>
</ul>

<p>Maybe one day there will be a flight passenger simulator where you can go get strapped in for 7 hours, offering the deep focus afforded by a flight without the dry air, pressure changes, cost, etc.</p>]]></content><author><name>Matthew Bilyeu</name></author><summary type="html"><![CDATA[To me, an airplane cabin is an almost ideal place to read, write, study, focus, etc. I tend to get distracted and procrastinate a lot, but flights have been a pretty good environment for deep thought.]]></summary></entry></feed>