<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xml" href="https://aldur.blog/feed.xslt.xml"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://aldur.blog/feed/micros.xml" rel="self" type="application/atom+xml" /><link href="https://aldur.blog/" rel="alternate" type="text/html" /><updated>2026-03-13T19:25:01+00:00</updated><id>https://aldur.blog/feed/micros.xml</id><title type="html">Universal Bits | Micros</title><subtitle>Exploring mental models, connecting the dots, and writing about it.</subtitle><author><name>aldur</name><email>hello@aldur.blog</email></author><entry><title type="html">Stop claude-code from fetching git at startup</title><link href="https://aldur.blog/micros/2026/03/12/stop-claude-code-from-fetching-git-at-startup/" rel="alternate" type="text/html" title="Stop claude-code from fetching git at startup" /><published>2026-03-12T00:00:00+00:00</published><updated>2026-03-12T00:00:00+00:00</updated><id>https://aldur.blog/micros/2026/03/12/stop-claude-code-from-fetching-git-at-startup</id><content type="html" xml:base="https://aldur.blog/micros/2026/03/12/stop-claude-code-from-fetching-git-at-startup/"><![CDATA[<p>Since a couple of weeks ago, issuing a first prompt to <code class="language-plaintext highlighter-rouge">claude-code</code> results in
an unattended request to unlock/touch the Yubikey that holds my SSH keys, as if
it is trying to do a <code class="language-plaintext highlighter-rouge">git</code> operation on my behalf. The whole thing is
confusing, because the Yubikey request is “blind”: it doesn’t specify which
command is being executed and for which purpose. Others have noticed the
<a href="https://github.com/anthropics/claude-code/issues/21108">issue</a> as well.</p>

<p>It looks like <code class="language-plaintext highlighter-rouge">claude-code</code> does a <code class="language-plaintext highlighter-rouge">git fetch</code> at startup, which requires SSH
if the repository was cloned that way. To fix it, set this environmental
variable:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC</span><span class="o">=</span>1
</code></pre></div></div>

<p>From the <a href="https://code.claude.com/docs/en/settings">manual</a>:</p>

<blockquote>
  <p>CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: Equivalent of setting
  DISABLE_AUTOUPDATER, DISABLE_BUG_COMMAND, DISABLE_ERROR_REPORTING, and
  DISABLE_TELEMETRY</p>
</blockquote>

<p>I haven’t tried scoping it down to one of these variables, let me know if you
do! Meanwhile, this should also prevent <code class="language-plaintext highlighter-rouge">claude-code</code> from asking for
feedback during a session.</p>]]></content><author><name>aldur</name><email>hello@aldur.blog</email></author><category term="micros" /><summary type="html"><![CDATA[Since a couple of weeks ago, issuing a first prompt to claude-code results in an unattended request to unlock/touch the Yubikey that holds my SSH keys, as if it is trying to do a git operation on my behalf. The whole thing is confusing, because the Yubikey request is “blind”: it doesn’t specify which command is being executed and for which purpose. Others have noticed the issue as well.]]></summary></entry><entry><title type="html">All Ruby versions in Cloudflare pages</title><link href="https://aldur.blog/micros/2026/03/06/ruby-versions-cloudflare-pages/" rel="alternate" type="text/html" title="All Ruby versions in Cloudflare pages" /><published>2026-03-06T00:00:00+00:00</published><updated>2026-03-06T00:00:00+00:00</updated><id>https://aldur.blog/micros/2026/03/06/ruby-versions-cloudflare-pages</id><content type="html" xml:base="https://aldur.blog/micros/2026/03/06/ruby-versions-cloudflare-pages/"><![CDATA[<p>This post originally started as a rant: until late February, Cloudflare Pages’
documentation stated to support <em>any</em> Ruby version (or Python, Node.js, etc.),
but <a href="https://github.com/aldur/aldur.github.io/pull/116" title="didn’t really"><svg class="svg-icon grey" viewBox="0 0 512 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
  didn’t really</a>.</p>

<p>Although the problem was real, my post felt overly negative, not constructive,
and <em>wrong</em>. Instead of publishing it, I just opened an <a href="https://github.com/cloudflare/cloudflare-docs/issues/27779#event-23112259622">issue in
<code class="language-plaintext highlighter-rouge">cloudflare-docs</code></a>.</p>

<p>The maintainers have now kindly fixed the issue. Anyone can use <em>any</em> Ruby
version in a Cloudflare Pages build. If the worker doesn’t have that version
available, it will automatically download it and compile it (in which case, the
build might take a bit longer).</p>

<p>As for me, this means I can pin my <code class="language-plaintext highlighter-rouge">.ruby_version</code> to one that is cached in
<code class="language-plaintext highlighter-rouge">nixpkgs</code> so that I can quickly start writing on low-powered devices.</p>]]></content><author><name>aldur</name><email>hello@aldur.blog</email></author><category term="micros" /><summary type="html"><![CDATA[This post originally started as a rant: until late February, Cloudflare Pages’ documentation stated to support any Ruby version (or Python, Node.js, etc.), but didn’t really.]]></summary></entry><entry><title type="html">Port forwarding to a running QEMU VM</title><link href="https://aldur.blog/micros/2026/02/04/port-forwarding-to-a-running-qemu-vm/" rel="alternate" type="text/html" title="Port forwarding to a running QEMU VM" /><published>2026-02-04T00:00:00+00:00</published><updated>2026-02-04T00:00:00+00:00</updated><id>https://aldur.blog/micros/2026/02/04/port-forwarding-to-a-running-qemu-vm</id><content type="html" xml:base="https://aldur.blog/micros/2026/02/04/port-forwarding-to-a-running-qemu-vm/"><![CDATA[<p>If you are running a QEMU VM with <code class="language-plaintext highlighter-rouge">-qmp</code> (<a href="https://wiki.qemu.org/Documentation/QMP">QEMU Machine Protocol</a>), then you
can add port-forwarding to it while running as follows:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s1">'{ "execute": "qmp_capabilities" }
{ "execute": "human-monitor-command", "arguments": { "command-line": "hostfwd_add tcp::2222-:22" } }'</span> <span class="se">\</span>
  | socat - UNIX-CONNECT:/path/to/qmp.sock
</code></pre></div></div>]]></content><author><name>aldur</name><email>hello@aldur.blog</email></author><category term="micros" /><summary type="html"><![CDATA[If you are running a QEMU VM with -qmp (QEMU Machine Protocol), then you can add port-forwarding to it while running as follows:]]></summary></entry><entry><title type="html">Importing p12 certificates on a Chromebook</title><link href="https://aldur.blog/micros/2026/01/22/importing-p12-certificates-on-a-chromebook/" rel="alternate" type="text/html" title="Importing p12 certificates on a Chromebook" /><published>2026-01-22T00:00:00+00:00</published><updated>2026-01-22T00:00:00+00:00</updated><id>https://aldur.blog/micros/2026/01/22/importing-p12-certificates-on-a-chromebook</id><content type="html" xml:base="https://aldur.blog/micros/2026/01/22/importing-p12-certificates-on-a-chromebook/"><![CDATA[<p>I sometimes need to use a Chromebook and a <code class="language-plaintext highlighter-rouge">.p12</code> certificate to authenticate
through TLS (e.g. through the Spanish “Identificación electrónica”). Everytime
that happens, I need to “rediscover” the process. Here it is for future memory.</p>

<p>To add a <code class="language-plaintext highlighter-rouge">.p12</code> certificate to ChromeOS’ certificate manager:</p>

<ol>
  <li>Navigate to <code class="language-plaintext highlighter-rouge">chrome://certificate-manager</code></li>
  <li>→ “<em>Your certificates</em>”</li>
  <li>→ “<em>View imported certificates from ChromeOS</em>”</li>
  <li>→ “<em>Import and bind</em>”</li>
  <li>Select your certificate from the file picker</li>
  <li>Enter your certificate’s password</li>
</ol>

<p>“<em>Import and bind</em>” stores the certificate <a href="https://www.chromium.org/developers/design-documents/tpm-usage/#protecting-certain-user-rsa-keys">on the device’s Trusted Platform
Module (TPM)</a>.</p>

<p>It might be possible to also store the certificate on a Yubikey through the
<a href="https://docs.yubico.com/yesdk/users-manual/application-piv/slots.html">Personal Identity Verification (PIV)</a> app and then use it on a Chromebook
through the <a href="https://chromewebstore.google.com/detail/smart-card-connector/khpfeaanjngmcnplbdlpegiifgpfgdco?sjid=12624726738251298427-NA">Smart Card Connector</a> and a middleware. I haven’t tried this
approach yet, but I hear it is tricky on non-enterprise devices.</p>]]></content><author><name>aldur</name><email>hello@aldur.blog</email></author><category term="micros" /><category term="ChromeOS" /><summary type="html"><![CDATA[I sometimes need to use a Chromebook and a .p12 certificate to authenticate through TLS (e.g. through the Spanish “Identificación electrónica”). Everytime that happens, I need to “rediscover” the process. Here it is for future memory.]]></summary></entry><entry><title type="html">ChromeOS notifications from NixOS</title><link href="https://aldur.blog/micros/2026/01/18/chromeos-notifications-from-nixos/" rel="alternate" type="text/html" title="ChromeOS notifications from NixOS" /><published>2026-01-18T00:00:00+00:00</published><updated>2026-01-18T00:00:00+00:00</updated><id>https://aldur.blog/micros/2026/01/18/chromeos-notifications-from-nixos</id><content type="html" xml:base="https://aldur.blog/micros/2026/01/18/chromeos-notifications-from-nixos/"><![CDATA[<p>A few days ago <a href="https://github.com/aldur/nixos-crostini/commit/ad6a7720ec13eaa970f7f4df83d543cc0df735a2" title="I extended nixos-crostini"><svg class="svg-icon grey" viewBox="0 0 512 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>  I extended nixos-crostini</a> to support the <code class="language-plaintext highlighter-rouge">cros-notificationd</code>
service. NixOS can now display notifications in ChromeOS through Wayland
forwarding.</p>

<p>You can see the result as follows:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>notify-send <span class="nt">--app-name</span><span class="o">=</span><span class="s2">"baguette-nixos"</span> <span class="s2">"Hello, ChromeOS!"</span>
</code></pre></div></div>

<p class="text-align-center"><img src="/images/chromeos-nixos-notifications.webp" alt="A notification sent from NixOS and display in ChromeOS" class="centered" style="width: 50%; border-radius: 10px;" />
<em>An example notification</em></p>]]></content><author><name>aldur</name><email>hello@aldur.blog</email></author><category term="micros" /><category term="ChromeOS" /><summary type="html"><![CDATA[A few days ago I extended nixos-crostini to support the cros-notificationd service. NixOS can now display notifications in ChromeOS through Wayland forwarding.]]></summary></entry><entry><title type="html">Fix Slack notifications in Safari</title><link href="https://aldur.blog/micros/2026/01/16/fix-slack-notifications-in-safari/" rel="alternate" type="text/html" title="Fix Slack notifications in Safari" /><published>2026-01-16T00:00:00+00:00</published><updated>2026-01-16T00:00:00+00:00</updated><id>https://aldur.blog/micros/2026/01/16/fix-slack-notifications-in-safari</id><content type="html" xml:base="https://aldur.blog/micros/2026/01/16/fix-slack-notifications-in-safari/"><![CDATA[<p>Somehow, Slack in Safari never notified me within a particular
workspace/profile.</p>

<p>The Slack <a href="https://slack.com/help/articles/360001559367-Troubleshoot-Slack-notifications#browser-2">troubleshooting guide</a> suggested to check that Safari’s
preferences allowed notifications from the Slack website. But I never got the
prompt to allow that in the first place: <code class="language-plaintext highlighter-rouge">app.slack.com</code> was missing from the
settings, both in Safari and in macOS’ notification center.</p>

<p class="text-align-center"><img src="/images/safari-troubleshoot.webp" alt="A screenshot from the Slack troubleshooting guide for Safari" class="centered" style="width: 70%; border-radius: 10px;" />
<em>Slack’s troubleshooting guide for Safari.</em></p>

<p>To fix it, I manually requested the permission to show notifications from the
developer console of Slack’s tab:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Notification</span><span class="p">.</span><span class="nf">requestPermission</span><span class="p">().</span><span class="nf">then</span><span class="p">(</span><span class="nx">permission</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">permission</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">granted</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">new</span> <span class="nc">Notification</span><span class="p">(</span><span class="dl">'</span><span class="s1">Hello!</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">body</span><span class="p">:</span> <span class="dl">'</span><span class="s1">This is a notification from Safari</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">icon</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/path/to/icon.png</span><span class="dl">'</span>
    <span class="p">});</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>After that, notifications started to work.</p>]]></content><author><name>aldur</name><email>hello@aldur.blog</email></author><category term="micros" /><summary type="html"><![CDATA[Somehow, Slack in Safari never notified me within a particular workspace/profile.]]></summary></entry><entry><title type="html">Short-lived OpenRouter API keys</title><link href="https://aldur.blog/micros/2025/11/30/short-lived-openrouter-api-keys/" rel="alternate" type="text/html" title="Short-lived OpenRouter API keys" /><published>2025-11-30T20:30:00+00:00</published><updated>2025-11-30T20:30:00+00:00</updated><id>https://aldur.blog/micros/2025/11/30/short-lived-openrouter-api-keys</id><content type="html" xml:base="https://aldur.blog/micros/2025/11/30/short-lived-openrouter-api-keys/"><![CDATA[<p>I have recently started playing with <a href="https://github.com/simonw/llm"><code class="language-plaintext highlighter-rouge">simonw/llm</code></a> to <a href="https://github.com/simonw/llm-openrouter">query OpenRouter
models</a> and do quick CLI queries without breaking <em>flow</em>.</p>

<p>I generally don’t like having long-lived API keys somewhere on-disk, because
a rogue executable (or an LLM falling for prompt injection) could easily leak
them. This is the main reason why I don’t use the popular <code class="language-plaintext highlighter-rouge">gh</code> CLI application,
which relies (by default) on a very privileged access token stored on disk.</p>

<p>To avoid this issue altogether, I often work inside throwaway VMs. The downside
is that, each time, I will need to re-authenticate to those services requiring
local credentials (including <code class="language-plaintext highlighter-rouge">llm-openrouter</code>).</p>

<p>Luckily, OpenRouter allows to <a href="https://openrouter.ai/docs/guides/overview/auth/provisioning-api-keys">programmatically provision API keys</a>, which
makes re-authentication fast and easy. I wrote and host a simple <a href="https://github.com/aldur/openrouter-provisioner" title="openrouter-provisioner"><svg class="svg-icon grey" viewBox="0 0 512 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>  openrouter-provisioner</a> on one of my nodes, where it is only
accessible through Tailscale (configured on the host, not the guest VM). By
default, it will provision and return API keys expiring after 24h.</p>

<p>With that in place, I can quickly get things running on a new VM:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>llm keys <span class="nb">set </span>openrouter <span class="nt">--value</span> <span class="se">\</span>
  <span class="si">$(</span>curl <span class="nt">-s</span> <span class="nt">-X</span> POST https://openrouter | jq <span class="nt">-r</span> <span class="s1">'.api_key'</span><span class="si">)</span>
</code></pre></div></div>]]></content><author><name>aldur</name><email>hello@aldur.blog</email></author><category term="micros" /><summary type="html"><![CDATA[I have recently started playing with simonw/llm to query OpenRouter models and do quick CLI queries without breaking flow.]]></summary></entry><entry><title type="html">TIL: nixpkgs.writers</title><link href="https://aldur.blog/micros/2025/11/30/til-nixpkgs-writers/" rel="alternate" type="text/html" title="TIL: nixpkgs.writers" /><published>2025-11-30T00:00:00+00:00</published><updated>2025-11-30T00:00:00+00:00</updated><id>https://aldur.blog/micros/2025/11/30/til-nixpkgs-writers</id><content type="html" xml:base="https://aldur.blog/micros/2025/11/30/til-nixpkgs-writers/"><![CDATA[<p>TIL to use <code class="language-plaintext highlighter-rouge">nixpkgs.writers</code> to quickly:</p>

<ol>
  <li>Package scripts into executables with proper shebangs (e.g., Python).</li>
  <li>Bundle any dependencies.</li>
  <li>Perform checks/lints (e.g., through <code class="language-plaintext highlighter-rouge">flake8</code>)</li>
</ol>

<p>I am using it in my <a href="https://github.com/aldur/openrouter-provisioner/blob/0606ce7637856f4fc2138b473ed2831cd6dc2dcc/flake.nix#L17-L21" title="openrouter-provisioner"><svg class="svg-icon grey" viewBox="0 0 512 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
  openrouter-provisioner</a> as follows:</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">server</span> <span class="o">=</span> <span class="nv">pkgs</span><span class="o">.</span><span class="nv">writers</span><span class="o">.</span><span class="nv">writePython3Bin</span> <span class="s2">"openrouter-provisioner"</span> <span class="p">{</span>
  <span class="nv">flakeIgnore</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s2">"E501"</span> <span class="c"># line too long</span>
  <span class="p">];</span>
<span class="p">}</span> <span class="p">(</span><span class="kr">builtins</span><span class="o">.</span><span class="nv">readFile</span> <span class="sx">./openrouter.py</span><span class="p">);</span>
</code></pre></div></div>

<p>When an LLM originally suggested this snippet, I thought it was <em>hallucinating</em>
because I couldn’t find any reference in the <a href="https://nixos.org/manual/nixpkgs/unstable/">manual</a>. Then, when
challenged, the LLM returned the <a href="https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/writers/scripts.nix">source for the <code class="language-plaintext highlighter-rouge">writers</code> library</a>. LLMs
are obviously not suited for all tasks, but this is an example of something
they are excellent at: they will easily <em>discover</em> and use less-known library
features by going through the source code when the documentation is lacking.</p>

<p>In this particular case, the <code class="language-plaintext highlighter-rouge">writers</code> library includes documentation comments
for most of the derivations, but none of them make it in the overall manual. I
was about to open an issue but then discovered that <a href="https://github.com/NixOS/nixpkgs/issues/89759">there’s already one</a>
dating back to 2020.</p>

<h3 id="the-writers">The writers</h3>

<p>In case it’s useful, <a href="https://gist.github.com/aldur/518cc92d30a897b226ed18f7f10926cb">this is the Markdown documentation that would be
generated</a> and below is a list of all the included writers (some are missing
from the docs):</p>

<details>
  <summary>Click to expand (LLM output of included writers)</summary>

  <h4 id="base">Base</h4>

  <ul>
    <li><strong>makeScriptWriter</strong> - Base implementation for creating script writers</li>
    <li><strong>makeBinWriter</strong> - Base implementation for compiled language writers</li>
  </ul>

  <h4 id="shell">Shell</h4>

  <ul>
    <li><strong>writeBash</strong> - Bash script writer</li>
    <li><strong>writeBashBin</strong> - Bash script writer (outputs to /bin)</li>
    <li><strong>writeDash</strong> - Dash script writer</li>
    <li><strong>writeDashBin</strong> - Dash script writer (outputs to /bin)</li>
    <li><strong>writeFish</strong> - Fish shell script writer</li>
    <li><strong>writeFishBin</strong> - Fish shell script writer (outputs to /bin)</li>
    <li><strong>writeNu</strong> - Nushell script writer</li>
    <li><strong>writeNuBin</strong> - Nushell script writer (outputs to /bin)</li>
  </ul>

  <h4 id="interpreted-languages">Interpreted languages</h4>

  <ul>
    <li><strong>writeBabashka</strong> - Babashka (Clojure) script writer</li>
    <li><strong>writeBabashkaBin</strong> - Babashka script writer (outputs to /bin)</li>
    <li><strong>writeGuile</strong> - Guile Scheme script writer</li>
    <li><strong>writeGuileBin</strong> - Guile Scheme script writer (outputs to /bin)</li>
    <li><strong>writeRuby</strong> - Ruby script writer</li>
    <li><strong>writeRubyBin</strong> - Ruby script writer (outputs to /bin)</li>
    <li><strong>writeLua</strong> - Lua script writer</li>
    <li><strong>writeLuaBin</strong> - Lua script writer (outputs to /bin)</li>
    <li><strong>writePerl</strong> - Perl script writer</li>
    <li><strong>writePerlBin</strong> - Perl script writer (outputs to /bin)</li>
    <li><strong>writeJS</strong> - JavaScript (Node.js) script writer</li>
    <li><strong>writeJSBin</strong> - JavaScript script writer (outputs to /bin)</li>
  </ul>

  <h4 id="python">Python</h4>

  <ul>
    <li><strong>writePython3</strong> - Python 3 script writer</li>
    <li><strong>writePython3Bin</strong> - Python 3 script writer (outputs to /bin)</li>
    <li><strong>writePyPy2</strong> - PyPy2 script writer</li>
    <li><strong>writePyPy2Bin</strong> - PyPy2 script writer (outputs to /bin)</li>
    <li><strong>writePyPy3</strong> - PyPy3 script writer</li>
    <li><strong>writePyPy3Bin</strong> - PyPy3 script writer (outputs to /bin)</li>
  </ul>

  <h4 id="compiled-languages">Compiled Languages</h4>

  <ul>
    <li><strong>writeHaskell</strong> - Haskell compiled script writer</li>
    <li><strong>writeHaskellBin</strong> - Haskell compiled script writer (outputs to /bin)</li>
    <li><strong>writeNim</strong> - Nim compiled script writer</li>
    <li><strong>writeNimBin</strong> - Nim compiled script writer (outputs to /bin)</li>
    <li><strong>writeRust</strong> - Rust compiled script writer</li>
    <li><strong>writeRustBin</strong> - Rust compiled script writer (outputs to /bin)</li>
    <li><strong>writeFSharp</strong> - F# script writer</li>
    <li><strong>writeFSharpBin</strong> - F# script writer (outputs to /bin)</li>
  </ul>

  <h4 id="specialized-writers">Specialized Writers</h4>

  <ul>
    <li><strong>writeNginxConfig</strong> - Nginx configuration file writer</li>
  </ul>

  <h4 id="data-writers">Data Writers</h4>

  <ul>
    <li><strong>makeDataWriter</strong> - Base transformer for writing data (deprecated)</li>
    <li><strong>writeText</strong> - Plain text file writer</li>
    <li><strong>writeJSON</strong> - JSON file writer</li>
    <li><strong>writeTOML</strong> - TOML file writer</li>
    <li><strong>writeYAML</strong> - YAML file writer</li>
  </ul>

</details>]]></content><author><name>aldur</name><email>hello@aldur.blog</email></author><category term="micros" /><category term="TIL" /><summary type="html"><![CDATA[TIL to use nixpkgs.writers to quickly:]]></summary></entry><entry><title type="html">Debugging LSPs</title><link href="https://aldur.blog/micros/2025/11/23/debugging-lsps/" rel="alternate" type="text/html" title="Debugging LSPs" /><published>2025-11-23T00:00:00+00:00</published><updated>2025-11-23T00:00:00+00:00</updated><id>https://aldur.blog/micros/2025/11/23/debugging-lsps</id><content type="html" xml:base="https://aldur.blog/micros/2025/11/23/debugging-lsps/"><![CDATA[<p>More often than I’d like to, I run into issues with some <a href="https://microsoft.github.io/language-server-protocol/">LSP</a> server that
either does not work at all or stops working. When that happens, I will
try not to break out of flow and, eventually, try to debug it so it
doesn’t happen again.</p>

<p>When using <code class="language-plaintext highlighter-rouge">nvim</code>, <code class="language-plaintext highlighter-rouge">:h lsp-log</code> provides these useful commands to trace
protocol messages:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vim</span><span class="p">.</span><span class="n">lsp</span><span class="p">.</span><span class="n">set_log_level</span> <span class="s1">'trace'</span>
<span class="nb">require</span><span class="p">(</span><span class="s1">'vim.lsp.log'</span><span class="p">).</span><span class="n">set_format_func</span><span class="p">(</span><span class="n">vim</span><span class="p">.</span><span class="n">inspect</span><span class="p">)</span>
</code></pre></div></div>

<p>The problem is that tracing slows down the editor, bloats the logfile, and
won’t have the best ergonomics to parse the messages.</p>

<p>Instead, I have written and improved over time a small Nix derivation that
wraps the LSP to dump its standard input, output, and error to files so that I
can easily take a look at them later.</p>

<p>Here it is, in case it’s useful to anyone:</p>

<div class="language-nix highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">wrapLSP</span> <span class="o">=</span>
<span class="p">{</span>
  <span class="nv">lsp</span><span class="p">,</span>
  <span class="nv">cmd</span> <span class="o">?</span> <span class="p">(</span><span class="nv">pkgs</span><span class="o">.</span><span class="nv">lib</span><span class="o">.</span><span class="nv">meta</span><span class="o">.</span><span class="nv">getExe</span> <span class="nv">lsp</span><span class="p">),</span>
<span class="p">}:</span>
<span class="nv">pkgs</span><span class="o">.</span><span class="nv">writeShellApplication</span> <span class="p">{</span>
  <span class="nv">name</span> <span class="o">=</span> <span class="nv">lsp</span><span class="o">.</span><span class="nv">meta</span><span class="o">.</span><span class="nv">mainProgram</span><span class="p">;</span>
  <span class="nv">runtimeInputs</span> <span class="o">=</span> <span class="p">[</span>
    <span class="nv">lsp</span>
  <span class="p">];</span>

  <span class="nv">text</span> <span class="o">=</span> <span class="s2">''</span><span class="err">
</span><span class="s2">    coproc LSP_SERVER { </span><span class="si">${</span><span class="nv">cmd</span><span class="si">}</span><span class="s2"> "$@" 2&gt; /tmp/</span><span class="si">${</span><span class="nv">lsp</span><span class="o">.</span><span class="nv">name</span><span class="si">}</span><span class="s2">_error.log; }</span><span class="err">

</span><span class="s2">    # first some necessary file-descriptors fiddling</span><span class="err">
</span><span class="s2">    exec {srv_input}&gt;&amp;"</span><span class="se">''$</span><span class="s2">{LSP_SERVER[1]}"-</span><span class="err">
</span><span class="s2">    exec {srv_output}&lt;&amp;"</span><span class="se">''$</span><span class="s2">{LSP_SERVER[0]}"-</span><span class="err">

</span><span class="s2">    # background commands to relay normal stdin/stdout activity</span><span class="err">
</span><span class="s2">    tee /tmp/</span><span class="si">${</span><span class="nv">lsp</span><span class="o">.</span><span class="nv">name</span><span class="si">}</span><span class="s2">_input.log &lt;&amp;0 &gt;&amp;</span><span class="se">''$</span><span class="s2">{srv_input} &amp;</span><span class="err">
</span><span class="s2">    tee /tmp/</span><span class="si">${</span><span class="nv">lsp</span><span class="o">.</span><span class="nv">name</span><span class="si">}</span><span class="s2">_output.log &lt;&amp;</span><span class="se">''$</span><span class="s2">{srv_output} &amp;</span><span class="err">

</span><span class="s2">    while true; do sleep infinity; done</span><span class="err">
</span><span class="s2">  ''</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p>To use it, call it with an LSP as an argument (e.g., <code class="language-plaintext highlighter-rouge">wrapLSP pkgs.ctags-lsp</code>),
build the derivation, and then add the result to <code class="language-plaintext highlighter-rouge">PATH</code>: <code class="language-plaintext highlighter-rouge">PATH=$(readlink -f
result/bin)/:$PATH</code> when launching your editor.</p>]]></content><author><name>aldur</name><email>hello@aldur.blog</email></author><category term="micros" /><summary type="html"><![CDATA[More often than I’d like to, I run into issues with some LSP server that either does not work at all or stops working. When that happens, I will try not to break out of flow and, eventually, try to debug it so it doesn’t happen again.]]></summary></entry><entry><title type="html">MLX with Metal support through Nix</title><link href="https://aldur.blog/micros/2025/11/04/mlx-with-metal-support-through-nix/" rel="alternate" type="text/html" title="MLX with Metal support through Nix" /><published>2025-11-04T22:48:00+00:00</published><updated>2025-11-04T22:48:00+00:00</updated><id>https://aldur.blog/micros/2025/11/04/mlx-with-metal-support-through-nix</id><content type="html" xml:base="https://aldur.blog/micros/2025/11/04/mlx-with-metal-support-through-nix/"><![CDATA[<p>We have <a href="/micros/2025/07/13/unlimited-tokens-with-llm-mlx/">talked about</a> how
I like running local models on macOS through the <a href="https://github.com/ml-explore/mlx">MLX framework</a>: it is
<em>fast</em> and it doesn’t require an <code class="language-plaintext highlighter-rouge">ollama</code> daemon in the background.
<a href="https://github.com/simonw/llm"><code class="language-plaintext highlighter-rouge">simonw/llm</code></a> provides good CLI ergonomics (for instance, to provide
examples, set a system prompt, temperature, etc.).</p>

<p>I also like my pipelines deterministic so that upstream releases don’t break
them by mistake. For this, I use Nix to package things up.</p>

<p>Unfortunately, when using the <code class="language-plaintext highlighter-rouge">mlx</code> Nix derivation available in <code class="language-plaintext highlighter-rouge">nixpkgs</code>, the
CLI would stall and the inference process would eventually <code class="language-plaintext highlighter-rouge">SIGSEV</code>. That’s
because that derivation <a href="https://github.com/NixOS/nixpkgs/blob/b3d51a0365f6695e7dd5cdf3e180604530ed33b4/pkgs/development/python-modules/mlx/default.nix#L78C1-L81C102">does not build with Metal support</a>:</p>

<pre><code class="language-txt"># NOTE The `metal` command-line utility used to build the Metal kernels is not open-source.
# To build mlx with Metal support in Nix, you'd need to use one of the sandbox escape
# hatches which let you interact with a native install of Xcode, such as `composeXcodeWrapper`
# or by changing the upstream (e.g., https://github.com/zed-industries/zed/discussions/7016).
</code></pre>

<p>The sandbox escape hatch sounds scary. Instead, I fixed it by pulling wheels
from Pypi (it turns out that <code class="language-plaintext highlighter-rouge">mlx</code> and <code class="language-plaintext highlighter-rouge">mlx-metal</code> are required) and patching
them to work with Nix.</p>

<p>If anyone needs it, <a href="https://github.com/aldur/dotfiles/blob/1b93ba9ace983a875034bb41718b22bca427cfdd/nix/packages/mlx/default.nix" title="here is the resulting derivation"><svg class="svg-icon grey" viewBox="0 0 512 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>  here is the resulting derivation</a>. Below it is in action:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"Hi"</span> | nix shell .#llm <span class="nt">-c</span> llm prompt <span class="nt">-m</span> mlx-community/Llama-3.2-3B-Instruct-4bit

How can I assist you today?
</code></pre></div></div>]]></content><author><name>aldur</name><email>hello@aldur.blog</email></author><category term="micros" /><summary type="html"><![CDATA[We have talked about how I like running local models on macOS through the MLX framework: it is fast and it doesn’t require an ollama daemon in the background. simonw/llm provides good CLI ergonomics (for instance, to provide examples, set a system prompt, temperature, etc.).]]></summary></entry></feed>