<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Open Source | Bas Nijholt</title><link>https://www.nijho.lt/category/open-source/</link><atom:link href="https://www.nijho.lt/category/open-source/index.xml" rel="self" type="application/rss+xml"/><description>Open Source</description><generator>Hugo</generator><language>en-us</language><copyright>© 2026</copyright><lastBuildDate>Fri, 26 Jun 2026 00:00:00 +0000</lastBuildDate><image><url>https://www.nijho.lt/media/icon_hu_408a8f6cef43e327.png</url><title>Open Source</title><link>https://www.nijho.lt/category/open-source/</link></image><item><title>MindRoom: AI agents that live in Matrix and work everywhere 🧠</title><link>https://www.nijho.lt/post/mindroom/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/mindroom/</guid><description>&lt;details class="toc" open&gt;&lt;summary&gt;Table of contents&lt;/summary&gt;&lt;nav id="TableOfContents"&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#1-the-problem-your-ai-is-trapped-in-apps"&gt;1. The problem: your AI is trapped in apps&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#2-what-is-mindroom"&gt;2. What is MindRoom?&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#3-how-it-actually-works"&gt;3. How it actually works&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#4-building-on-matrix-the-good-and-the-tricky"&gt;4. Building on Matrix: the good and the tricky&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#5-memory-that-follows-you"&gt;5. Memory that follows you&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#6-100-built-in-tool-integrations"&gt;6. 100+ built-in tool integrations&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#7-teams-agents-that-collaborate"&gt;7. Teams: agents that collaborate&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#8-hot-reload-change-config-and-plugins-without-downtime"&gt;8. Hot-reload: change config and plugins without downtime&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#9-voice-scheduling-and-other-features"&gt;9. Voice, scheduling, and other features&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#10-the-obsession-the-saas-dream-and-the-burnout"&gt;10. The obsession, the SaaS dream, and the burnout&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#11-openclaw-and-why-it-matters"&gt;11. OpenClaw and why it matters&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#12-whats-next"&gt;12. What&amp;rsquo;s next&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#links-and-resources"&gt;Links and resources&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10;&lt;/nav&gt;&lt;/details&gt;&#10;&#10;&lt;h2 id="1-the-problem-your-ai-is-trapped-in-apps"&gt;1. The problem: your AI is trapped in apps &lt;a class="anchor" href="#1-the-problem-your-ai-is-trapped-in-apps" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Programming is my biggest passion in life.&#10;I&amp;rsquo;ve been actively involved in open source for over ten years now, maintaining &lt;a href="https://github.com/basnijholt" target="_blank" rel="noopener"&gt;40+ installable packages&lt;/a&gt; across Python, JavaScript, and Rust.&#10;Some of these have over 100,000 users; others have exactly one—me.&#10;I mention this because it matters for what comes next: when AI coding tools arrived, I didn&amp;rsquo;t start from zero.&#10;As I wrote about in my &lt;a href="https://www.nijho.lt/post/agentic-coding/"&gt;agentic coding post&lt;/a&gt;, AI has completely transformed how I work—when I&amp;rsquo;m AFK, I regularly build full features or entire projects by &lt;a href="https://www.nijho.lt/post/agentic-mobile-workflow/"&gt;dictating to my phone&lt;/a&gt; and routing it through my own &lt;a href="https://github.com/basnijholt/agent-cli" target="_blank" rel="noopener"&gt;transcription software&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Through all of this, I became deeply convinced that AI agents are the future.&#10;Not just a useful tool for programmers—a fundamental shift in how &lt;em&gt;everyone&lt;/em&gt; will interact with computers.&lt;/p&gt;&#10;&lt;p&gt;But if that&amp;rsquo;s true, then the way agents work today is broken.&lt;/p&gt;&#10;&lt;p&gt;We&amp;rsquo;ve built artificial barriers between artificial intelligences.&#10;ChatGPT knows you in one tab, Claude knows you in another, your Slack bot knows you in a third.&#10;Your email AI can&amp;rsquo;t tell your calendar AI about that meeting invitation.&#10;Your coding assistant has no idea about the project specs sitting in your document AI.&#10;Switch platforms, and your AI starts from scratch.&lt;/p&gt;&#10;&lt;p&gt;Most agent frameworks require you to &lt;em&gt;program&lt;/em&gt; them.&#10;That works for developers like me, but if agents are truly the future, they need to work for everyone.&#10;Non-programmers don&amp;rsquo;t want to write code—they just want a chat.&lt;/p&gt;&#10;&lt;p&gt;And then there&amp;rsquo;s privacy.&#10;I&amp;rsquo;m fine sharing my email with Gemini—Google already owns my Gmail.&#10;But do I want to send my financial data, my health records, or my personal notes to a cloud provider?&#10;Not really.&#10;I&amp;rsquo;d rather use a local model for sensitive tasks and pick the best, cheapest Chinese model for general-purpose deep research.&lt;/p&gt;&#10;&lt;p&gt;So I built &lt;strong&gt;MindRoom&lt;/strong&gt; on a chat protocol—if people just want a chat, give them a chat.&#10;Agents live in the same rooms as you, collaborate in threads, and follow you across every platform.&#10;The first commit landed on July 29, 2025 (four months before OpenClaw&amp;rsquo;s first commit 😅), so by the time I&amp;rsquo;m publishing this MindRoom has been with me for nearly eleven months: first as an obsessive side project, then, since March, with much more sustained focus.&#10;At first I got so obsessed with it that I eventually burned out and had to step away completely.&lt;/p&gt;&#10;&lt;p&gt;Recently, seeing &lt;a href="https://openclaw.ai" target="_blank" rel="noopener"&gt;OpenClaw&lt;/a&gt; gain massive traction solving a similar problem reminded me that what I&amp;rsquo;d been building wasn&amp;rsquo;t just a niche obsession.&#10;So here I am, dusting off MindRoom and writing about what it is, how it works, and why I think the approach still matters.&lt;/p&gt;&#10;&lt;aside class="callout callout-note"&gt;&lt;svg class="ico" viewBox="0 0 256 256" aria-hidden="true"&gt;&lt;path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/&gt;&lt;/svg&gt;&lt;div&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; MindRoom is an open-source system that creates AI agents living inside the &lt;a href="https://matrix.org/" target="_blank" rel="noopener"&gt;Matrix protocol&lt;/a&gt;.&#10;Because Matrix has bridges to Slack, Telegram, Discord, WhatsApp, and everything else, your agents can follow you across every platform—with persistent memory, 100+ built-in tool integrations, and multi-agent collaboration.&#10;Think of it as &amp;ldquo;your AI assistant, but it lives in Matrix and works everywhere.&amp;rdquo;&lt;/div&gt;&lt;/aside&gt;&#10;&lt;figure id="figure-live-chatmindroomchat-personal-thread-with-the-prompt-explain-how-mindroom-and-what-you-can-do-mind-answers-in-the-real-matrix-room-using-tool-calls-and-the-codex-backed-local-mindroom-runtime"&gt;&lt;img src="https://www.nijho.lt/post/mindroom/mindroom-in-action_hu_5c1001a61c1ce0a1.webp" srcset="https://www.nijho.lt/post/mindroom/mindroom-in-action_hu_cba228417596d894.webp 400w, https://www.nijho.lt/post/mindroom/mindroom-in-action_hu_5c1001a61c1ce0a1.webp 760w, https://www.nijho.lt/post/mindroom/mindroom-in-action_hu_46631ca52a25e910.webp 1200w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="489" alt="Live MindRoom Personal thread showing the prompt Explain how MindRoom and what you can do, followed by Mind explaining Matrix agents, tools, memory, and skills." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;Live chat.mindroom.chat Personal thread with the prompt: “Explain how MindRoom and what you can do.” Mind answers in the real Matrix room, using tool calls and the Codex-backed local MindRoom runtime.&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="2-what-is-mindroom"&gt;2. What is MindRoom? &lt;a class="anchor" href="#2-what-is-mindroom" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;MindRoom is an open-source system that creates AI agents living inside the &lt;a href="https://matrix.org/" target="_blank" rel="noopener"&gt;Matrix protocol&lt;/a&gt;.&#10;If you&amp;rsquo;re not familiar with Matrix—it&amp;rsquo;s a federated, end-to-end encrypted communication standard.&#10;The same protocol used by the French government for 5.5 million civil servants, by German healthcare for 150K+ organizations, and by the Element app that millions of people use daily.&lt;/p&gt;&#10;&lt;p&gt;The key insight: Matrix has &lt;a href="https://matrix.org/ecosystem/bridges/" target="_blank" rel="noopener"&gt;bridges&lt;/a&gt; to many major platforms—Slack, Telegram, Discord, WhatsApp, IRC, email, even SMS.&lt;/p&gt;&#10;&lt;p&gt;So if your AI agent lives in Matrix, it can reach you on any platform.&#10;One agent, every platform, continuous memory.&lt;/p&gt;&#10;&lt;p&gt;One practical caveat: Matrix bridges vary in maturity. Some, like the &lt;a href="https://github.com/mautrix/telegram" target="_blank" rel="noopener"&gt;Telegram bridge&lt;/a&gt;, work very well, while others can be finicky. Your mileage may vary depending on which platforms you need.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s what a typical setup looks like in &lt;code&gt;config.yaml&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CodeAgent&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Generate code, manage files, execute shell commands&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;opus-4.8&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;file, shell, github]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;Always read files before modifying them.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rooms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;lobby, dev]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;research&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ResearchAgent&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Search the web, summarize papers, find information&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gpt-5.5&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;tavily, arxiv, wikipedia]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rooms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;lobby, research]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;teams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;super_team&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Super Team&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;code, research]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;collaborate&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Define your agents, give them tools and rooms, and they show up in Matrix as real users—with avatars, typing indicators, and online status.&lt;/p&gt;&#10;&lt;figure id="figure-mindroom-agents-are-real-matrix-users-with-their-own-accounts-generated-avatars-display-names-room-memberships-and-online-presence"&gt;&lt;img src="https://www.nijho.lt/post/mindroom/mindroom-members_hu_5090cd21f23bad91.webp" srcset="https://www.nijho.lt/post/mindroom/mindroom-members_hu_fbc884b2330f0fe3.webp 400w, https://www.nijho.lt/post/mindroom/mindroom-members_hu_5090cd21f23bad91.webp 760w, https://www.nijho.lt/post/mindroom/mindroom-members_hu_e6a566cfcdf2d915.webp 1200w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="489" alt="Live MindRoom room showing RouterAgent, Mind, and Bas as Matrix users with generated avatars." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;MindRoom agents are real Matrix users with their own accounts, generated avatars, display names, room memberships, and online presence.&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="3-how-it-actually-works"&gt;3. How it actually works &lt;a class="anchor" href="#3-how-it-actually-works" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;At the core sits what I call the &lt;strong&gt;MultiAgentOrchestrator&lt;/strong&gt;—a class in &lt;code&gt;bot.py&lt;/code&gt; that boots every configured entity (router, agents, teams), provisions Matrix user accounts for each one via &lt;a href="https://github.com/matrix-nio/matrix-nio" target="_blank" rel="noopener"&gt;matrix-nio&lt;/a&gt;, and keeps sync loops alive.&#10;The agents themselves are powered by the &lt;a href="https://github.com/agno-agi/agno" target="_blank" rel="noopener"&gt;Agno&lt;/a&gt; framework, which provides a unified interface across AI model providers.&lt;/p&gt;&#10;&lt;p&gt;When someone sends a message in a Matrix room:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;Explicit mentions&lt;/strong&gt; go directly to the named agent (&lt;code&gt;@mindroom_code help me debug this&lt;/code&gt;)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Thread continuations&lt;/strong&gt; keep the same agent responding (no weird context switches mid-conversation)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;New conversations&lt;/strong&gt; hit the &lt;strong&gt;router&lt;/strong&gt;—an AI that analyzes the message and picks the best agent based on capabilities&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;All conversations happen in threads, which keeps rooms organized.&#10;Agents stream their responses in real-time, editing a single message as they think rather than spamming new ones.&#10;You see tool calls happening live:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🔧 Tool Call: search_web(query=&amp;#34;matrix protocol bridges&amp;#34;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[waiting for result...]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✅ search_web result:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[results here]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The recording below starts in the main Personal room, sends the prompt &lt;code&gt;Explain how MindRoom and what you can do.&lt;/code&gt;, and then follows MindRoom as it opens the new thread and streams the reply to completion. It is sped up 2.5x to keep the flow skimmable.&lt;/p&gt;&#10;&#10;&lt;script type="module" src="https://www.nijho.lt/js/reduced-motion.3dfea0061c17c60a9a200c61ef1e3b3e98d5bdc4eaa4e0a64b3ed2a4d13c72fd.js" integrity="sha256-Pf6gBhwXxgqaIAxh7x47PpjVvcTqpOCmSz7SpNE8cv0=" crossorigin="anonymous"&gt;&lt;/script&gt;&#10;&lt;video controls autoplay muted playsinline loop preload="metadata"&gt;&#10;&lt;source src="https://www.nijho.lt/post/mindroom/mindroom-agent-response.mp4" type="video/mp4"&gt;&#10;&lt;/video&gt;&#10;&#10;&lt;h2 id="4-building-on-matrix-the-good-and-the-tricky"&gt;4. Building on Matrix: the good and the tricky &lt;a class="anchor" href="#4-building-on-matrix-the-good-and-the-tricky" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;One of the best things about building on Matrix is what you get for free.&#10;End-to-end encryption, a deeply interoperable chat protocol with bridges to many platforms, a choice of clients, federation between organizations—all of that comes with the protocol.&#10;You don&amp;rsquo;t have to build any of it yourself.&lt;/p&gt;&#10;&lt;p&gt;But because Matrix has such a tight specification, it also brings challenges.&lt;/p&gt;&#10;&lt;p&gt;The protocol doesn&amp;rsquo;t support streaming.&#10;AI agents that think for 30 seconds before dumping a wall of text make for a terrible chat experience, so I hacked streaming in by rapidly editing the same message as new tokens arrive.&#10;An &lt;code&gt;⋯&lt;/code&gt; marker shows while the agent is still thinking—a small touch, but it makes the experience feel responsive and alive.&lt;/p&gt;&#10;&lt;p&gt;There&amp;rsquo;s also a size limit on message content.&#10;That&amp;rsquo;s fine for human chat, but AI responses can get long—especially when tool calls and their results are included.&#10;I worked around this by using Matrix&amp;rsquo;s attachment feature: when a response exceeds the limit, the content continues in an attachment that gets updated as the message keeps streaming in.&#10;This required forking the &lt;a href="https://cinny.in/" target="_blank" rel="noopener"&gt;Cinny&lt;/a&gt; chat client so that attachments display inline rather than as downloadable files, making the whole thing seamless.&lt;/p&gt;&#10;&lt;aside class="callout callout-note"&gt;&lt;svg class="ico" viewBox="0 0 256 256" aria-hidden="true"&gt;&lt;path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/&gt;&lt;/svg&gt;&lt;div&gt;These workarounds are the kind of thing you don&amp;rsquo;t anticipate when you pick a protocol.&#10;Matrix gives you an incredible foundation, but making it work well for AI required bending it in ways it wasn&amp;rsquo;t designed for.&lt;/div&gt;&lt;/aside&gt;&#10;&lt;h2 id="5-memory-that-follows-you"&gt;5. Memory that follows you &lt;a class="anchor" href="#5-memory-that-follows-you" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;MindRoom currently supports two memory implementations.&lt;/p&gt;&#10;&lt;p&gt;The first is the more traditional semantic memory system built on &lt;a href="https://mem0.ai/" target="_blank" rel="noopener"&gt;Mem0&lt;/a&gt;&amp;rsquo;s &lt;code&gt;AsyncMemory&lt;/code&gt;, with configurable embedding providers (&lt;a href="https://platform.openai.com/docs/guides/embeddings" target="_blank" rel="noopener"&gt;OpenAI&lt;/a&gt;, &lt;a href="https://ollama.com/" target="_blank" rel="noopener"&gt;Ollama&lt;/a&gt;, or HuggingFace) and vector storage via &lt;a href="https://www.trychroma.com/" target="_blank" rel="noopener"&gt;ChromaDB&lt;/a&gt;.&#10;This gives agents searchable memory for preferences, project context, decisions, and recurring facts.&lt;/p&gt;&#10;&lt;p&gt;The second is a file-based memory system inspired by the simplicity of OpenClaw-style agent files.&#10;Instead of burying identity in a database, durable context can live in plain Markdown files that travel with the agent.&#10;That portability matters: I can move the same agent identity from OpenClaw to Hermes to MindRoom without starting from scratch.&lt;/p&gt;&#10;&lt;p&gt;MindRoom also has a memory flush step that periodically extracts durable facts from conversations and writes them back into memory.&#10;So the chat transcript remains chat, while stable preferences, decisions, and identity details become reusable context for future turns.&lt;/p&gt;&#10;&lt;figure id="figure-a-live-personal-memory-demo-mindroom-stores-a-harmless-preference-in-one-turn-and-retrieves-it-later-from-the-same-matrix-conversation"&gt;&lt;img src="https://www.nijho.lt/post/mindroom/mindroom-memory_hu_5090cd21f23bad91.webp" srcset="https://www.nijho.lt/post/mindroom/mindroom-memory_hu_fbc884b2330f0fe3.webp 400w, https://www.nijho.lt/post/mindroom/mindroom-memory_hu_5090cd21f23bad91.webp 760w, https://www.nijho.lt/post/mindroom/mindroom-memory_hu_e6a566cfcdf2d915.webp 1200w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="489" alt="Live MindRoom memory thread showing the generated agent avatars and a public demo where the agent remembers teal as the blog demo accent color." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;A live Personal memory demo: MindRoom stores a harmless preference in one turn and retrieves it later from the same Matrix conversation.&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="6-100-built-in-tool-integrations"&gt;6. 100+ built-in tool integrations &lt;a class="anchor" href="#6-100-built-in-tool-integrations" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Agents can use over 100 built-in integrations:&lt;/p&gt;&#10;&lt;div class="table-wrap"&gt;&#10;&lt;table&gt;&#10;&lt;thead&gt;&#10;&lt;tr&gt;&#10;&lt;th&gt;Category&lt;/th&gt;&#10;&lt;th&gt;Examples&lt;/th&gt;&#10;&lt;/tr&gt;&#10;&lt;/thead&gt;&#10;&lt;tbody&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;strong&gt;Communication&lt;/strong&gt;&lt;/td&gt;&#10;&lt;td&gt;Gmail, Slack, Telegram, Discord&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;strong&gt;Development&lt;/strong&gt;&lt;/td&gt;&#10;&lt;td&gt;GitHub, Shell, Python, Docker&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;strong&gt;Search&lt;/strong&gt;&lt;/td&gt;&#10;&lt;td&gt;Tavily, Wikipedia, Arxiv, DuckDuckGo&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;strong&gt;Productivity&lt;/strong&gt;&lt;/td&gt;&#10;&lt;td&gt;Google Calendar, Jira, Linear, Todoist&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;strong&gt;Smart Home&lt;/strong&gt;&lt;/td&gt;&#10;&lt;td&gt;Home Assistant&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;strong&gt;AI/ML&lt;/strong&gt;&lt;/td&gt;&#10;&lt;td&gt;DALL-E, ElevenLabs, Replicate&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;strong&gt;Data&lt;/strong&gt;&lt;/td&gt;&#10;&lt;td&gt;Pandas, SQL, DuckDB, Yahoo Finance&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;&lt;strong&gt;Web&lt;/strong&gt;&lt;/td&gt;&#10;&lt;td&gt;Firecrawl, Crawl4ai, Browser automation&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Tools are lazy-loaded and credential-managed, so an agent only loads what it needs.&lt;/p&gt;&#10;&lt;h2 id="7-teams-agents-that-collaborate"&gt;7. Teams: agents that collaborate &lt;a class="anchor" href="#7-teams-agents-that-collaborate" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Single agents are useful, but sometimes you need a team.&#10;MindRoom supports two collaboration modes:&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Coordinate mode&lt;/strong&gt;: A lead agent orchestrates others.&#10;You ask a question, the lead delegates subtasks, collects results, and synthesizes a unified response.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Collaborate mode&lt;/strong&gt;: All agents work on the same task in parallel, each providing their independent analysis.&#10;The system then merges their responses with a consensus summary.&lt;/p&gt;&#10;&lt;p&gt;In practice, you might have a research team where one agent searches academic papers, another checks industry news, and a third validates claims—all triggered by a single message.&#10;The live room view below shows a real team response in the same Matrix thread surface normal agents use: threads, routed agents, and room history are all first-class chat objects rather than a separate dashboard.&lt;/p&gt;&#10;&lt;figure id="figure-live-lobby-thread-in-chatmindroomchat-showing-a-real-team-response-from-homeassistant-and-agentbuilder-team-collaboration-uses-the-same-matrix-native-interface-as-regular-agent-chats-with-routed-agents-and-room-membership-visible-beside-the-conversation"&gt;&lt;img src="https://www.nijho.lt/post/mindroom/mindroom-team_hu_afbbb6ff5ac54e3f.webp" srcset="https://www.nijho.lt/post/mindroom/mindroom-team_hu_bf973e9c13f8cf6.webp 400w, https://www.nijho.lt/post/mindroom/mindroom-team_hu_afbbb6ff5ac54e3f.webp 760w, https://www.nijho.lt/post/mindroom/mindroom-team_hu_ee227208566f8d1.webp 1200w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="533" alt="Live MindRoom Lobby thread showing a Team Response from HomeAssistant and AgentBuilder, with the member list showing Super Team and other agents." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;Live Lobby thread in chat.mindroom.chat showing a real Team Response from HomeAssistant and AgentBuilder. Team collaboration uses the same Matrix-native interface as regular agent chats, with routed agents and room membership visible beside the conversation.&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="8-hot-reload-change-config-and-plugins-without-downtime"&gt;8. Hot-reload: change config and plugins without downtime &lt;a class="anchor" href="#8-hot-reload-change-config-and-plugins-without-downtime" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;config.yaml&lt;/code&gt; is watched at runtime.&#10;When you edit it—add an agent, change a model, update instructions—MindRoom diffs the old and new config, gracefully restarts only the affected agents, and has them rejoin their rooms.&#10;No downtime, no full runtime restart required.&lt;/p&gt;&#10;&lt;p&gt;The same development loop applies to plugins.&#10;MindRoom has a rich Python hook system, and plugins can be live-developed while the system is running.&#10;Change a plugin file, save it, and MindRoom automatically reloads the plugin without restarting the Python runtime.&lt;/p&gt;&#10;&lt;p&gt;This sounds minor, but when you&amp;rsquo;re iterating on agent behavior or plugin hooks, being able to tweak code and config and see results in seconds significantly improves the development loop.&lt;/p&gt;&#10;&#10;&lt;script type="module" src="https://www.nijho.lt/js/reduced-motion.3dfea0061c17c60a9a200c61ef1e3b3e98d5bdc4eaa4e0a64b3ed2a4d13c72fd.js" integrity="sha256-Pf6gBhwXxgqaIAxh7x47PpjVvcTqpOCmSz7SpNE8cv0=" crossorigin="anonymous"&gt;&lt;/script&gt;&#10;&lt;video controls autoplay muted playsinline loop preload="metadata"&gt;&#10;&lt;source src="https://www.nijho.lt/post/mindroom/mindroom-hot-reload.mp4" type="video/mp4"&gt;&#10;&lt;/video&gt;&#10;&#10;&lt;h2 id="9-voice-scheduling-and-other-features"&gt;9. Voice, scheduling, and other features &lt;a class="anchor" href="#9-voice-scheduling-and-other-features" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Some features I built purely because I wanted them for myself (a recurring theme in my projects, as anyone who&amp;rsquo;s read my &lt;a href="https://www.nijho.lt/post/local-ai-journey/"&gt;local AI journey&lt;/a&gt; can attest).&#10;I even built &lt;a href="https://github.com/basnijholt/matty" target="_blank" rel="noopener"&gt;Matty&lt;/a&gt;, a Matrix CLI client, from bed at midnight because I needed a way to interact with my agents from the terminal:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Voice messages&lt;/strong&gt;: Audio messages in Matrix are auto-transcribed via Whisper and treated as regular text input.&#10;You can talk to your agents.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Scheduled tasks&lt;/strong&gt;: Natural language scheduling (&lt;code&gt;!schedule &amp;quot;check my email every morning at 9 AM&amp;quot;&lt;/code&gt;) backed by cron jobs.&#10;Agents can run tasks in the background and escalate to you when needed.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;DM support&lt;/strong&gt;: Agents respond naturally in 1:1 conversations without needing mentions.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Cross-organization federation&lt;/strong&gt;: Because Matrix is federated, two companies&amp;rsquo; AI agents can collaborate in a shared room—something that&amp;rsquo;s hard to do on proprietary platforms.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;aside class="callout callout-note"&gt;&lt;svg class="ico" viewBox="0 0 256 256" aria-hidden="true"&gt;&lt;path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/&gt;&lt;/svg&gt;&lt;div&gt;The voice feature pairs nicely with my &lt;a href="https://github.com/basnijholt/agent-cli" target="_blank" rel="noopener"&gt;&lt;code&gt;agent-cli&lt;/code&gt;&lt;/a&gt; tool—local Whisper transcription on my RTX 3090 means I can talk to my Matrix agents without any cloud dependency.&lt;/div&gt;&lt;/aside&gt;&#10;&lt;h2 id="10-the-obsession-the-saas-dream-and-the-burnout"&gt;10. The obsession, the SaaS dream, and the burnout &lt;a class="anchor" href="#10-the-obsession-the-saas-dream-and-the-burnout" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I need to be honest about what actually happened with MindRoom, because &amp;ldquo;life got in the way&amp;rdquo; is a sanitized version of the story.&lt;/p&gt;&#10;&lt;p&gt;What really happened is that I got completely, utterly obsessed.&#10;Every single second I wasn&amp;rsquo;t working at my day job or sleeping, I was working on MindRoom.&#10;Many hundreds of hours went into it.&#10;I seriously considered quitting my job and starting an AI startup around it.&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;ve read Armin Ronacher&amp;rsquo;s post &lt;a href="https://lucumr.pocoo.org/2026/1/18/agent-psychosis/" target="_blank" rel="noopener"&gt;Agent Psychosis: Are We Going Insane?&lt;/a&gt;, you&amp;rsquo;ll know exactly what I&amp;rsquo;m talking about.&#10;The dopamine hit from building with AI agents is incredibly real.&#10;As Armin writes: &amp;ldquo;You feel productive, you feel like everything is amazing, and if you hang out just with people that are into that stuff too, without any checks, you go deeper and deeper into the belief that this all makes perfect sense.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;That was me.&#10;I was building and building, shipping feature after feature, and it felt incredible.&#10;The codebase grew to over 1,000 commits.&#10;I built the core system, then a React dashboard, then I started on a full SaaS platform—Kubernetes deployments on Hetzner Cloud, a FastAPI backend, a Next.js 15 frontend, Stripe integration, Supabase auth, Helm charts for multi-tenant isolation.&lt;/p&gt;&#10;&lt;p&gt;And that&amp;rsquo;s where the enthusiasm started to erode.&#10;Not because the core idea was bad, but because the work shifted from building interesting things (agent orchestration, memory systems, routing intelligence) to grinding through SaaS boilerplate: GDPR compliance, payment processing, automated Kubernetes deployments, terms of service, cookie banners.&#10;The dopamine loop that kept me going at 2 AM broke when the work stopped being creative and started being compliance paperwork.&lt;/p&gt;&#10;&lt;aside class="callout callout-note"&gt;&lt;svg class="ico" viewBox="0 0 256 256" aria-hidden="true"&gt;&lt;path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/&gt;&lt;/svg&gt;&lt;div&gt;Looking back, I think this is a pattern worth recognizing.&#10;Agentic coding makes it so easy to &lt;em&gt;start&lt;/em&gt; things that you can build yourself into a scope that would normally require a team.&#10;The gap between &amp;ldquo;I can build this&amp;rdquo; and &amp;ldquo;I can maintain and ship this as a product&amp;rdquo; is larger than the tools make it feel.&lt;/div&gt;&lt;/aside&gt;&#10;&lt;p&gt;The hosted control plane lives at &lt;a href="https://app.mindroom.chat" target="_blank" rel="noopener"&gt;app.mindroom.chat&lt;/a&gt;.&#10;I am deliberately not turning this section into a dashboard tour; the interesting part, and the part I still care about most, is the Matrix-native runtime underneath it.&lt;/p&gt;&#10;&lt;h2 id="11-openclaw-and-why-it-matters"&gt;11. OpenClaw and why it matters &lt;a class="anchor" href="#11-openclaw-and-why-it-matters" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://openclaw.ai" target="_blank" rel="noopener"&gt;OpenClaw&lt;/a&gt; takes a different approach to the same core problem.&#10;Where MindRoom builds on Matrix federation as the backbone, OpenClaw runs a local Gateway on your machine and connects to messaging platforms directly (WhatsApp via Baileys, Telegram via grammY, Discord via discord.js, and so on).&lt;/p&gt;&#10;&lt;p&gt;The similarities are striking:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Both give you a single AI assistant across all your messaging platforms&lt;/li&gt;&#10;&lt;li&gt;Both have persistent memory&lt;/li&gt;&#10;&lt;li&gt;Both support multiple tools and integrations&lt;/li&gt;&#10;&lt;li&gt;Both are open-source and privacy-focused&lt;/li&gt;&#10;&lt;li&gt;Both let you choose your own AI model&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The differences are interesting too.&#10;OpenClaw is local-first and TypeScript-based with 380K+ stars and a massive community.&#10;MindRoom is Python-based, federation-first, and&amp;hellip; well, let&amp;rsquo;s just say my GitHub star count is a few orders of magnitude lower.&lt;/p&gt;&#10;&lt;p&gt;But seeing OpenClaw validate the core idea—that people want AI assistants that aren&amp;rsquo;t trapped in apps—reminded me that what I&amp;rsquo;d been building matters.&#10;The federation angle is something OpenClaw doesn&amp;rsquo;t have: the ability for agents from different organizations to collaborate natively, with end-to-end encryption (Olm/Megolm), on a protocol that governments already trust and deploy at scale.&lt;/p&gt;&#10;&lt;figure id="figure-mindroom-uses-matrix-as-the-federated-backbone-openclaw-takes-a-local-gateway-direct-connector-approach"&gt;&lt;img src="https://www.nijho.lt/post/mindroom/mindroom-architecture_hu_aaf71cef609b8340.webp" srcset="https://www.nijho.lt/post/mindroom/mindroom-architecture_hu_2cca5883511aed87.webp 400w, https://www.nijho.lt/post/mindroom/mindroom-architecture_hu_aaf71cef609b8340.webp 760w, https://www.nijho.lt/post/mindroom/mindroom-architecture_hu_afb78d90679bf4a4.webp 1200w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="507" alt="Architecture diagram comparing MindRoom and OpenClaw. MindRoom flows from user to Matrix room to federated homeserver to bridges to messaging platforms. OpenClaw flows from user to local gateway to direct connectors to platform APIs." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;MindRoom uses Matrix as the federated backbone; OpenClaw takes a local-gateway, direct-connector approach.&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p&gt;And then there&amp;rsquo;s &lt;a href="https://moltbook.com" target="_blank" rel="noopener"&gt;Moltbook&lt;/a&gt;—a social network built exclusively for AI agents that the internet was losing its mind over for a couple of days.&#10;I&amp;rsquo;m not personally convinced it&amp;rsquo;s the next big thing, but what caught my attention is that MindRoom could support something like it &lt;em&gt;natively&lt;/em&gt;.&#10;Matrix is already federated—agents on different servers can already interact, join shared rooms, and collaborate across organizational boundaries.&#10;A bridge from a Reddit-like platform to Matrix and your agents could participate without any special integration.&lt;/p&gt;&#10;&lt;p&gt;Building agent-to-agent infrastructure from scratch also means building security from scratch—and that&amp;rsquo;s where new platforms tend to struggle.&#10;Matrix has spent years hardening its E2E encryption, and it&amp;rsquo;s already deployed by governments and healthcare organizations at scale.&#10;Building on that foundation means you inherit those security properties rather than hoping to get them right yourself.&lt;/p&gt;&#10;&lt;h2 id="12-whats-next"&gt;12. What&amp;rsquo;s next &lt;a class="anchor" href="#12-whats-next" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I first drafted this post a couple of months ago, when MindRoom was still something I was trying to come back to.&#10;That part is already out of date.&#10;Since January I&amp;rsquo;ve been spending nearly every free hour on it again, and since March it has effectively been my full-time work.&#10;The codebase supports 8+ AI model providers—OpenAI, Anthropic, Ollama, Groq, Google, OpenRouter, DeepSeek, Cerebras—and the core architecture is solid.&#10;It also has real users now.&#10;My rough guess is that something like 30-100 people have tried MindRoom in one form or another, which is still early, but meaningfully different from &amp;ldquo;only I have touched this.&amp;rdquo;&#10;There&amp;rsquo;s already a Docker Compose file and a &lt;a href="https://github.com/mindroom-ai/mindroom-stack" target="_blank" rel="noopener"&gt;starter repository&lt;/a&gt; to get started, but I want to make it even simpler.&lt;/p&gt;&#10;&lt;p&gt;What I&amp;rsquo;m focusing on now:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Skills system&lt;/strong&gt;: Building an ecosystem of reusable agent behaviors (already partially implemented with OpenClaw-compatible format)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Even easier onboarding&lt;/strong&gt;: A &lt;code&gt;docker compose up&lt;/code&gt; gets you running today, but I want a wizard that gets you from zero to working agents without touching a YAML file&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Community&lt;/strong&gt;: Turning the small group of early users into a real operator and contributor community&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Scope discipline&lt;/strong&gt;: Keeping the work sustainable now that MindRoom is effectively full-time, not slipping back into the obsessive 2 AM marathon from before&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I&amp;rsquo;m deliberately leaving the SaaS ambitions aside for now.&#10;The core system is what matters, and that&amp;rsquo;s where the energy is going.&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;re interested in AI agents that live in Matrix and work everywhere, check out &lt;a href="https://github.com/mindroom-ai/mindroom" target="_blank" rel="noopener"&gt;MindRoom on GitHub&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Sometimes seeing someone else succeed with a similar idea is enough to remember why you started building in the first place.&#10;And sometimes the most important lesson from a project isn&amp;rsquo;t technical—it&amp;rsquo;s learning when to step away and when to come back.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Are you running AI agents on any messaging platforms? Have you tried Matrix for anything beyond regular chat? I&amp;rsquo;d love to hear about your setups!&lt;/em&gt;&lt;/p&gt;&#10;&lt;h2 id="links-and-resources"&gt;Links and resources &lt;a class="anchor" href="#links-and-resources" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/mindroom-ai/mindroom" target="_blank" rel="noopener"&gt;MindRoom on GitHub&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/mindroom-ai/mindroom-stack" target="_blank" rel="noopener"&gt;MindRoom Stack (starter repository)&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://matrix.org/" target="_blank" rel="noopener"&gt;Matrix protocol&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://openclaw.ai" target="_blank" rel="noopener"&gt;OpenClaw&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://mem0.ai/" target="_blank" rel="noopener"&gt;Mem0 (memory system inspiration)&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/agno-agi/agno" target="_blank" rel="noopener"&gt;Agno framework&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/matty" target="_blank" rel="noopener"&gt;Matty (Matrix CLI client)&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://lucumr.pocoo.org/2026/1/18/agent-psychosis/" target="_blank" rel="noopener"&gt;Agent Psychosis: Are We Going Insane?&lt;/a&gt; by Armin Ronacher&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.nijho.lt/post/agentic-coding/"&gt;My agentic coding post&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.nijho.lt/post/local-ai-journey/"&gt;My local AI journey&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.nijho.lt/post/agentic-mobile-workflow/"&gt;Coding from my phone&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>tuitorial: terminal-based interactive code tutorials 📚</title><link>https://www.nijho.lt/post/tuitorial/</link><pubDate>Sat, 04 Jan 2025 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/tuitorial/</guid><description>&lt;p&gt;While trying to demonstrate my &lt;a href="https://github.com/pipefunc/pipefunc" target="_blank" rel="noopener"&gt;&lt;code&gt;pipefunc&lt;/code&gt; package&lt;/a&gt; (a tool for automating computational pipelines), I hit a common presentation problem: how do you effectively walk through code without endless repetition?&lt;/p&gt;&#10;&lt;p&gt;Traditional presentation tools like PowerPoint force you to copy-paste the same code snippets multiple times just to highlight different parts. Jupyter notebooks are great for self-paced learning but clunky for live presentations (too much text that the viewer will try to read). I needed something that would let me define code once and create multiple views highlighting different aspects.&lt;/p&gt;&#10;&lt;p&gt;So, during the holiday season, I did what any reasonable developer would do: procrastinated on my actual work and built a new tool instead!&lt;/p&gt;&#10;&lt;h2 id="what-is-tuitorial"&gt;What is tuitorial? &lt;a class="anchor" href="#what-is-tuitorial" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/basnijholt/tuitorial" target="_blank" rel="noopener"&gt;&lt;code&gt;tuitorial&lt;/code&gt;&lt;/a&gt; is a Python package that lets you create interactive, terminal-based code tutorials where you define the code ONCE, then create steps that highlight different parts based on rules (like regex patterns or line numbers). No more copy-paste presentations!&lt;/p&gt;&#10;&lt;h2 id="key-features"&gt;Key Features &lt;a class="anchor" href="#key-features" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Write code once&lt;/strong&gt;: Define your code once, then create multiple views highlighting different aspects&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Rich highlighting options&lt;/strong&gt;: From simple literal matches to complex regex patterns&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Interactive navigation&lt;/strong&gt;: Intuitive keyboard controls for stepping through tutorials&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Beautiful terminal UI&lt;/strong&gt;: Powered by Textual for a polished look&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Flexible configuration&lt;/strong&gt;: Choose between Python API or YAML format&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Development mode&lt;/strong&gt;: Live reloading for quick iterations&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="see-it-in-action"&gt;See It in Action &lt;a class="anchor" href="#see-it-in-action" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s a demo of &lt;a href="https://github.com/basnijholt/tuitorial" target="_blank" rel="noopener"&gt;&lt;code&gt;tuitorial&lt;/code&gt;&lt;/a&gt; explaining &lt;a href="https://github.com/pipefunc/pipefunc" target="_blank" rel="noopener"&gt;&lt;code&gt;pipefunc&lt;/code&gt;&lt;/a&gt; - a package that lets you create computational pipelines with automatic parallelization, caching, and dependency management:&lt;/p&gt;&#10;&#10;&lt;script type="module" src="https://www.nijho.lt/js/reduced-motion.3dfea0061c17c60a9a200c61ef1e3b3e98d5bdc4eaa4e0a64b3ed2a4d13c72fd.js" integrity="sha256-Pf6gBhwXxgqaIAxh7x47PpjVvcTqpOCmSz7SpNE8cv0=" crossorigin="anonymous"&gt;&lt;/script&gt;&#10;&lt;video controls autoplay muted playsinline loop preload="metadata"&gt;&#10;&lt;source src="https://www.nijho.lt/post/tuitorial/tuitorial-0.4.0.mp4" type="video/mp4"&gt;&#10;&lt;/video&gt;&#10;&#10;&lt;h2 id="who-should-use-this"&gt;Who Should Use This? &lt;a class="anchor" href="#who-should-use-this" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While &lt;a href="https://github.com/basnijholt/tuitorial" target="_blank" rel="noopener"&gt;&lt;code&gt;tuitorial&lt;/code&gt;&lt;/a&gt; isn&amp;rsquo;t for everyone, it&amp;rsquo;s perfect for:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Developers giving technical presentations&lt;/li&gt;&#10;&lt;li&gt;Teachers explaining code concepts&lt;/li&gt;&#10;&lt;li&gt;Teams creating interactive documentation&lt;/li&gt;&#10;&lt;li&gt;Anyone who loves terminal-based tools&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="getting-started"&gt;Getting Started &lt;a class="anchor" href="#getting-started" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;aside class="callout callout-note"&gt;&lt;svg class="ico" viewBox="0 0 256 256" aria-hidden="true"&gt;&lt;path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/&gt;&lt;/svg&gt;&lt;div&gt;Have &lt;code&gt;uv&lt;/code&gt; installed? Run the following command to see a quick example:&#10;&lt;code&gt;uvx tuitorial https://raw.githubusercontent.com/basnijholt/tuitorial/refs/heads/main/examples/pipefunc.yaml&lt;/code&gt;&lt;/div&gt;&lt;/aside&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip install tuitorial&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Check out the &lt;a href="https://github.com/basnijholt/tuitorial" target="_blank" rel="noopener"&gt;documentation&lt;/a&gt; for examples and detailed usage instructions.&lt;/p&gt;&#10;&lt;h2 id="final-thoughts"&gt;Final Thoughts &lt;a class="anchor" href="#final-thoughts" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Building &lt;a href="https://github.com/basnijholt/tuitorial" target="_blank" rel="noopener"&gt;&lt;code&gt;tuitorial&lt;/code&gt;&lt;/a&gt; was a lot of fun!&#10;While it may not replace PowerPoint for your next business presentation, it offers a unique approach to code demonstrations that I hope others will find useful.&lt;/p&gt;&#10;&lt;p&gt;Try it out and let me know what you think!&lt;/p&gt;&#10;</description></item><item><title>Home Assistant Ecosystem 🏠</title><link>https://www.nijho.lt/post/advent-of-open-source/24-home-assistant-ecosystem/</link><pubDate>Tue, 24 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/24-home-assistant-ecosystem/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;There are not enough days in this advent calendar to highlight all my projects. So today is a collection of IoT/Home Assistant-related packages. Each has its own story.&lt;/p&gt;&#10;&lt;h2 id="-origin-stories--technical-details"&gt;📖 Origin Stories &amp;amp; Technical Details &lt;a class="anchor" href="#-origin-stories--technical-details" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;My Home Assistant obsession began as described in &lt;a href="https://www.linkedin.com/posts/basnijholt_opensource-homeassistant-smarthome-activity-7272248713882820608-w7RV" target="_blank" rel="noopener"&gt;Day 10&lt;/a&gt;. Many projects emerged from this hobby:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/aiokef" target="_blank" rel="noopener"&gt;&lt;strong&gt;aiokef&lt;/strong&gt;&lt;/a&gt;: To control my KEF speakers, I reverse-engineered network traffic and decoded raw byte sequences, diving deep into proprietary protocols to &amp;ldquo;speak&amp;rdquo; the speaker&amp;rsquo;s language.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/miflora" target="_blank" rel="noopener"&gt;&lt;strong&gt;miflora&lt;/strong&gt;&lt;/a&gt;: When &lt;a href="https://github.com/ChristianKuehnel" target="_blank" rel="noopener"&gt;@ChristianKuehnel&lt;/a&gt; couldn&amp;rsquo;t maintain this plant sensor library anymore, he transferred it to me. A perfect example of open source collaboration, it continues to help users monitor plant health and demonstrates how projects can thrive through community handovers.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/addon-otmonitor" target="_blank" rel="noopener"&gt;&lt;strong&gt;addon-otmonitor&lt;/strong&gt;&lt;/a&gt;: This HA add-on packages OpenTherm Monitor for the OpenTherm Gateway (OTGW). Using Docker to simplify deployment, it&amp;rsquo;s become a powerful open-source alternative to Nest or Ecobee, giving full control over heating systems.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/home-assistant-macbook-touch-bar" target="_blank" rel="noopener"&gt;&lt;strong&gt;home-assistant-macbook-touch-bar&lt;/strong&gt;&lt;/a&gt;: Remember that useless touch bar on the old MacBook? I transformed it into a mini Home Assistant control panel for lights, temperature, and more - a classic case of hardware hacking giving new life to abandoned technology.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/python-kasa/python-kasa" target="_blank" rel="noopener"&gt;&lt;strong&gt;python-kasa&lt;/strong&gt;&lt;/a&gt;: To modernize the popular &lt;code&gt;pyHS100&lt;/code&gt; library for TP-Link devices, I converted it to use &lt;code&gt;asyncio&lt;/code&gt;. With &lt;a href="https://github.com/rytilahti" target="_blank" rel="noopener"&gt;@rytilahti&lt;/a&gt;, we created the &lt;code&gt;python-kasa&lt;/code&gt; organization through community collaboration. While I haven&amp;rsquo;t contributed much since its inception, it&amp;rsquo;s now the standard for controlling Kasa devices.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact--lessons"&gt;📊 Impact &amp;amp; Lessons &lt;a class="anchor" href="#-impact--lessons" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;These projects serve thousands of users while teaching valuable lessons:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Open source empowers us: from KEF speaker integration to plant monitoring&lt;/li&gt;&#10;&lt;li&gt;Reverse engineering unlocks potential: turning proprietary protocols into open solutions&lt;/li&gt;&#10;&lt;li&gt;Community drives success: projects like miflora and python-kasa thrive through collaboration&lt;/li&gt;&#10;&lt;li&gt;Innovation through repurposing: giving new life to hardware like the MacBook Touch Bar&lt;/li&gt;&#10;&lt;li&gt;Modern tech matters: Docker simplifies deployment, async code boosts performance&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Want to explore these projects? Check them out on GitHub:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/aiokef" target="_blank" rel="noopener"&gt;aiokef&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/miflora" target="_blank" rel="noopener"&gt;miflora&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/addon-otmonitor" target="_blank" rel="noopener"&gt;addon-otmonitor&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/home-assistant-macbook-touch-bar" target="_blank" rel="noopener"&gt;home-assistant-macbook-touch-bar&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/python-kasa/python-kasa" target="_blank" rel="noopener"&gt;python-kasa&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;This concludes our Advent of Open Source! I hope you&amp;rsquo;ve enjoyed this journey through my projects as much as I&amp;rsquo;ve enjoyed sharing them. Happy holidays and happy coding!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #HomeAssistant #IoT #Python #SmartHome&lt;/p&gt;&#10;</description></item><item><title>pfapack 🔢</title><link>https://www.nijho.lt/post/advent-of-open-source/23-pfapack/</link><pubDate>Mon, 23 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/23-pfapack/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Ever needed to calculate the square root of the determinant of a skew-symmetric matrix? Probably not, but if you&amp;rsquo;re in quantum physics, you might have! This is called the Pfaffian. Today&amp;rsquo;s project, &lt;a href="https://github.com/basnijholt/pfapack" target="_blank" rel="noopener"&gt;&lt;code&gt;pfapack&lt;/code&gt;&lt;/a&gt;, is a Python package that provides efficient routines for computing it. The underlying algorithms and original code implementations were developed by my PhD co-promotor Michael Wimmer. I created the &lt;code&gt;pfapack&lt;/code&gt; package to make this powerful tool easily accessible.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Working in quantum physics, I frequently need to compute the Pfaffian. Michael had not only developed the algorithms but also wrote highly optimized C, FORTRAN, and Python implementations, distributing them as a tar file &lt;a href="https://michaelwimmer.org/downloads.html" target="_blank" rel="noopener"&gt;on his website&lt;/a&gt;. To make it easier for researchers working in Python, I packaged his existing Python code, added wrappers for his C and FORTRAN implementations, and set up automated builds for PyPI and conda-forge to make it easily installable.&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Python Wrapper for Efficient Code:&lt;/strong&gt; User-friendly Python interface to Wimmer&amp;rsquo;s optimized C and FORTRAN.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Multiple Algorithms:&lt;/strong&gt; Supports both the Parlett-Reid and Householder methods.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Optimized for Performance:&lt;/strong&gt; Leverages low-level optimizations for speed.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Conda and Pip Installable:&lt;/strong&gt; Easy installation via &lt;code&gt;conda-forge&lt;/code&gt; and PyPI.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Automated Wheel Builds:&lt;/strong&gt; Uses &lt;a href="https://github.com/pypa/cibuildwheel" target="_blank" rel="noopener"&gt;&lt;code&gt;pypa/cibuildwheel&lt;/code&gt;&lt;/a&gt; for streamlined, automated builds and releases.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Cross-Platform Compatibility:&lt;/strong&gt; Works on Linux, macOS, and Windows (with some caveats).&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Makes specialized mathematical algorithms readily available to Python users.&lt;/li&gt;&#10;&lt;li&gt;Simplifies the use of highly optimized code.&lt;/li&gt;&#10;&lt;li&gt;15 GitHub stars.&lt;/li&gt;&#10;&lt;li&gt;Used in research projects that require efficient Pfaffian computations, particularly in quantum physics.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Bridging Languages:&lt;/strong&gt; Wrapping C and FORTRAN for Python integration.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Automated Builds:&lt;/strong&gt; Setting up automated builds for multiple platforms was complex.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Windows Compatibility:&lt;/strong&gt; As always Windows support is a PITA; it currently requires MSYS2 and MinGW64.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Making specialized tools accessible broadens their impact.&lt;/li&gt;&#10;&lt;li&gt;Wrapping low-level code in Python can improve usability.&lt;/li&gt;&#10;&lt;li&gt;Automated builds are essential for maintainability.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/pypa/cibuildwheel" target="_blank" rel="noopener"&gt;&lt;code&gt;pypa/cibuildwheel&lt;/code&gt;&lt;/a&gt; is awesome for cross-platform builds.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-credits"&gt;🙏 Credits &lt;a class="anchor" href="#-credits" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;All the credits for the underlying algorithms and original code implementations go to Michael Wimmer. I simply made it easier to distribute and use in Python.&lt;/p&gt;&#10;&lt;p&gt;Want to compute Pfaffians efficiently in Python? Check out &lt;a href="https://github.com/basnijholt/pfapack" target="_blank" rel="noopener"&gt;&lt;code&gt;pfapack&lt;/code&gt; on GitHub&lt;/a&gt; or install it via &lt;code&gt;pip install pfapack&lt;/code&gt; or &lt;code&gt;conda install -c conda-forge pfapack&lt;/code&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #ScientificComputing #Math #Pfaffian #FORTRAN #C&lt;/p&gt;&#10;</description></item><item><title>tiller-streamlit 💰</title><link>https://www.nijho.lt/post/advent-of-open-source/22-tiller-streamlit/</link><pubDate>Sun, 22 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/22-tiller-streamlit/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Tracking personal finances is crucial, especially when working towards financial independence (see my &lt;a href="../04-wenfire"&gt;WenFIRE post on Day 4&lt;/a&gt;). Today&amp;rsquo;s project is &lt;a href="https://github.com/basnijholt/tiller-streamlit" target="_blank" rel="noopener"&gt;&lt;code&gt;tiller-streamlit&lt;/code&gt;&lt;/a&gt;, a Streamlit app that visualizes your financial data from &lt;a href="https://www.tillerhq.com/" target="_blank" rel="noopener"&gt;Tiller&lt;/a&gt;. I host it on my local network under &lt;code&gt;money.local&lt;/code&gt; using Traefik for easy access.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Used to use Intuit Mint, but when they shut down, I needed an alternative with full data control.&lt;/li&gt;&#10;&lt;li&gt;Tiller automatically populates your financial data into a Google Sheet that you own forever.&lt;/li&gt;&#10;&lt;li&gt;With 20+ accounts, manual updates are tedious – Tiller automates it.&lt;/li&gt;&#10;&lt;li&gt;Combined Tiller&amp;rsquo;s data with Streamlit&amp;rsquo;s ease of use, creating a personal finance dashboard.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Powered by Streamlit: Turns Python into a web app with minimal code.&lt;/li&gt;&#10;&lt;li&gt;Tiller Integration: Connects to Tiller&amp;rsquo;s Google Sheets for automated data retrieval.&lt;/li&gt;&#10;&lt;li&gt;Interactive Visualizations: Uses Plotly and Altair for dynamic charts.&lt;/li&gt;&#10;&lt;li&gt;Customizable Analysis: Filter by categories, time periods, and more.&lt;/li&gt;&#10;&lt;li&gt;Net Worth Tracking: Calculates and visualizes net worth over time.&lt;/li&gt;&#10;&lt;li&gt;Dockerized Deployment: Easy deployment with Docker, and served locally with Traefik.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Primarily for personal use, it has significantly improved my understanding of my finances.&lt;/li&gt;&#10;&lt;li&gt;Great way to learn Streamlit and explore its capabilities.&lt;/li&gt;&#10;&lt;li&gt;Visualizing spending patterns helps identify areas to cut back and make informed financial decisions on the path to financial independence.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Learning Streamlit: Excellent documentation and community support made it a smooth process.&lt;/li&gt;&#10;&lt;li&gt;Data Wrangling: Transforming Tiller&amp;rsquo;s raw data into a format suitable for visualization.&lt;/li&gt;&#10;&lt;li&gt;Deployment: Docker and Traefik simplified local deployment, making it accessible on my home network.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Data Ownership Matters: Owning your financial data gives you control and flexibility.&lt;/li&gt;&#10;&lt;li&gt;Visualization is Key: Seeing your finances visually makes a huge difference.&lt;/li&gt;&#10;&lt;li&gt;Streamlit is Powerful: Simplifies web app development.&lt;/li&gt;&#10;&lt;li&gt;Automation is Essential: Tiller&amp;rsquo;s automatic data population makes the whole process sustainable.&lt;/li&gt;&#10;&lt;li&gt;Docker and Traefik Simplify Local Deployment: Making it easy to access the app on my home network.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-future-plans"&gt;🔮 Future Plans &lt;a class="anchor" href="#-future-plans" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Budgeting Features: Integrate budget tracking and forecasting.&lt;/li&gt;&#10;&lt;li&gt;Goal Setting: Set financial goals and track progress.&lt;/li&gt;&#10;&lt;li&gt;Investment Tracking: Incorporate investment data for a complete picture.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Want to visualize your own finances with Tiller and Streamlit? Check out &lt;a href="https://github.com/basnijholt/tiller-streamlit" target="_blank" rel="noopener"&gt;&lt;code&gt;tiller-streamlit&lt;/code&gt; on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #Streamlit #PersonalFinance #DataVisualization #Tiller #Traefik #Docker&lt;/p&gt;&#10;</description></item><item><title>Interactive Graph Visualization 🕸️</title><link>https://www.nijho.lt/post/advent-of-open-source/21-interactive-graph-visualization/</link><pubDate>Sat, 21 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/21-interactive-graph-visualization/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;After creating &lt;a href="../20-pipefunc"&gt;pipefunc (Day 20)&lt;/a&gt;, I wanted a way to visualize its (possibly large and complex) computational graphs interactively in Jupyter notebooks.&#10;What started as a simple visualization PR evolved into two standalone packages: &lt;a href="https://github.com/pipefunc/graphviz-anywidget" target="_blank" rel="noopener"&gt;graphviz-anywidget&lt;/a&gt; (Python) and &lt;a href="https://github.com/pipefunc/graphvizsvg" target="_blank" rel="noopener"&gt;graphvizsvg&lt;/a&gt; (JavaScript) - which unexpectedly introduced me to JavaScript development!&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While working on pipefunc&amp;rsquo;s visualization features, I discovered that existing solutions didn&amp;rsquo;t quite meet my needs.&#10;However, I found several components that would solve my issue:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://anywidget.dev/" target="_blank" rel="noopener"&gt;AnyWidget&lt;/a&gt; (interactive GraphViz in Jupyter)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/hpcc-systems/hpcc-js-wasm" target="_blank" rel="noopener"&gt;@hpcc-js/wasm-graphviz&lt;/a&gt; (GraphViz in WASM)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/magjac/d3-graphviz" target="_blank" rel="noopener"&gt;d3-graphviz&lt;/a&gt; (interactive GraphViz in D3)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/mountainstorm/jquery.graphviz.svg/" target="_blank" rel="noopener"&gt;jquery.graphviz.svg&lt;/a&gt; (GraphViz SVG manipulation)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;AnyWidget would work with ESM modules, however, I had no clue what that even was.&#10;The &lt;code&gt;jquery.graphviz.svg&lt;/code&gt; package wasn&amp;rsquo;t one and hadn&amp;rsquo;t been maintained in 8 years.&lt;/p&gt;&#10;&lt;p&gt;With AI as my JavaScript mentor, I first rewrote &lt;code&gt;jquery.graphviz.svg&lt;/code&gt; into a modern ESM module (graphvizsvg), adding comprehensive tests that uncovered bugs in the original.&#10;Then, using AnyWidget, I created graphviz-anywidget to connect everything together - combining the WASM-powered d3-graphviz with the interactive features from graphvizsvg into a seamless Python package.&lt;/p&gt;&#10;&lt;p&gt;To my surprise, I found JavaScript&amp;rsquo;s development tooling remarkably sophisticated - especially npm, which handles everything from linting to testing to bundling to publishing.&#10;Something we&amp;rsquo;re still missing in Python!&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/pipefunc/graphviz-anywidget" target="_blank" rel="noopener"&gt;graphviz-anywidget&lt;/a&gt;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Interactive Graphviz visualization in Jupyter&lt;/li&gt;&#10;&lt;li&gt;WASM-powered graph rendering&lt;/li&gt;&#10;&lt;li&gt;Directional graph traversal&lt;/li&gt;&#10;&lt;li&gt;Node and edge highlighting&lt;/li&gt;&#10;&lt;li&gt;Works in JupyterLab, Notebook, and VS Code&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/pipefunc/graphvizsvg" target="_blank" rel="noopener"&gt;graphvizsvg&lt;/a&gt;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Modern ESM module for interactive SVG visualization&lt;/li&gt;&#10;&lt;li&gt;Automatic color transitions&lt;/li&gt;&#10;&lt;li&gt;Bi-directional graph traversal&lt;/li&gt;&#10;&lt;li&gt;Comprehensive test coverage&lt;/li&gt;&#10;&lt;li&gt;My first npm package!&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Enhances &lt;a href="https://github.com/pipefunc/pipefunc" target="_blank" rel="noopener"&gt;pipefunc&lt;/a&gt;&amp;rsquo;s visualization capabilities&lt;/li&gt;&#10;&lt;li&gt;Modernizes legacy jQuery code for modern web development&lt;/li&gt;&#10;&lt;li&gt;Proves that diving into new technologies can yield useful tools&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Learning JavaScript/TypeScript from scratch&lt;/li&gt;&#10;&lt;li&gt;Making WASM work in Jupyter&lt;/li&gt;&#10;&lt;li&gt;Getting it to work in VS Code&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Don&amp;rsquo;t let preconceptions limit your tech choices&lt;/li&gt;&#10;&lt;li&gt;Modern JavaScript development is actually enjoyable&lt;/li&gt;&#10;&lt;li&gt;AI can be an excellent programming mentor&lt;/li&gt;&#10;&lt;li&gt;Good visualization makes complex data accessible&lt;/li&gt;&#10;&lt;li&gt;Breaking features into separate packages increases reusability&lt;/li&gt;&#10;&lt;li&gt;Test coverage helps find bugs in even well-used code&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to visualize graphs interactively? Check out &lt;a href="https://github.com/pipefunc/graphviz-anywidget" target="_blank" rel="noopener"&gt;graphviz-anywidget&lt;/a&gt; and &lt;a href="https://github.com/pipefunc/graphvizsvg" target="_blank" rel="noopener"&gt;graphvizsvg&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #JavaScript #Python #DataVisualization #WebDev&lt;/p&gt;&#10;</description></item><item><title>pipefunc 🕸️</title><link>https://www.nijho.lt/post/advent-of-open-source/20-pipefunc/</link><pubDate>Fri, 20 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/20-pipefunc/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Of all the projects I&amp;rsquo;m sharing this month, this one that started as a passion project excites me the most!&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Sometimes the best projects are born from the most unexpected moments. More than 1½ year ago, while on parental leave with my twin boys, I found myself with small pockets of time during naps to work on a problem that had been bothering me for years: the tedious bookkeeping required in complex computational workflows. Every scientific computation project seemed to reinvent the same patterns - managing function dependencies, parameter sweeps, result caching, parallelization, and a lot of boilerplate to combine the resulting data. I wanted something that would let scientists focus on their science, not on pipeline management.&lt;/p&gt;&#10;&lt;figure id="figure-example-of-a-pipeline-generated-by-pipefunc"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/20-pipefunc/image_2_hu_91068e582234f921.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/20-pipefunc/image_2_hu_75ce53994a0e90e4.webp 400w, https://www.nijho.lt/post/advent-of-open-source/20-pipefunc/image_2_hu_91068e582234f921.webp 760w, https://www.nijho.lt/post/advent-of-open-source/20-pipefunc/image_2_hu_220beece6a403360.webp 1200w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="383" alt="A screenshot of a code editor showing Python code that uses the &amp;#39;pipefunc&amp;#39; library to define and visualize a computational pipeline. On the right, a graph generated by &amp;#39;pipeline.visualize()&amp;#39; is displayed, showing nodes and connections representing the pipeline&amp;#39;s structure." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;Example of a pipeline generated by PipeFunc&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Automatic DAG construction via very simple and lightweight syntax&lt;/li&gt;&#10;&lt;li&gt;N-dimensional parameter sweeps with automatic parallelization&lt;/li&gt;&#10;&lt;li&gt;Visual pipeline representation using NetworkX&lt;/li&gt;&#10;&lt;li&gt;Resource profiling (CPU, memory, time)&lt;/li&gt;&#10;&lt;li&gt;Type validation between pipeline stages&lt;/li&gt;&#10;&lt;li&gt;Ultra-fast: only 15 µs overhead per function&lt;/li&gt;&#10;&lt;li&gt;Flexible caching strategies (memory, disk, cloud)&lt;/li&gt;&#10;&lt;li&gt;Integration with scientific computing tools:&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://networkx.org/" target="_blank" rel="noopener"&gt;NetworkX&lt;/a&gt; for graph algorithms&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://numpy.org/" target="_blank" rel="noopener"&gt;NumPy&lt;/a&gt; for multi-dimensional arrays&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://docs.xarray.dev/" target="_blank" rel="noopener"&gt;Xarray&lt;/a&gt; for labeled arrays&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://zarr.dev/" target="_blank" rel="noopener"&gt;Zarr&lt;/a&gt; for distributed storage&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://adaptive.readthedocs.io/" target="_blank" rel="noopener"&gt;Adaptive&lt;/a&gt; for parallel computing&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;230 GitHub stars&lt;/li&gt;&#10;&lt;li&gt;700+ tests with 100% coverage&lt;/li&gt;&#10;&lt;li&gt;Fully typed codebase&lt;/li&gt;&#10;&lt;li&gt;Comprehensive documentation&lt;/li&gt;&#10;&lt;li&gt;Tested on real workflow on SLURM cluster&lt;/li&gt;&#10;&lt;li&gt;Covered on &lt;a href="https://pycoders.com/issues/650" target="_blank" rel="noopener"&gt;Pycoder&amp;rsquo;s Weekly&lt;/a&gt; with &amp;gt;100k subscribers&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Balancing simplicity with power&lt;/li&gt;&#10;&lt;li&gt;Making complex workflows intuitive&lt;/li&gt;&#10;&lt;li&gt;Handling distributed computing edge cases&lt;/li&gt;&#10;&lt;li&gt;Ensuring type safety across the pipeline&lt;/li&gt;&#10;&lt;li&gt;Optimizing performance without sacrificing features&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Sometimes the best time to code is during baby naps&lt;/li&gt;&#10;&lt;li&gt;Complex problems can have elegant solutions&lt;/li&gt;&#10;&lt;li&gt;Scientific computing needs better tooling&lt;/li&gt;&#10;&lt;li&gt;Good abstractions make hard things easy&lt;/li&gt;&#10;&lt;li&gt;Type hints and tests prevent headaches&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="-future-plans"&gt;🔮 Future Plans &lt;a class="anchor" href="#-future-plans" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The journey is far from over. Plans include:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Enhanced cloud computing support&lt;/li&gt;&#10;&lt;li&gt;More interactive visualization options&lt;/li&gt;&#10;&lt;li&gt;Interactive pipeline debugging tools&lt;/li&gt;&#10;&lt;li&gt;Expanded parameter sweep capabilities&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Want to simplify your computational workflows? Check out &lt;a href="https://github.com/pipefunc/pipefunc" target="_blank" rel="noopener"&gt;pipefunc on GitHub&lt;/a&gt; or read the &lt;a href="https://pipefunc.readthedocs.io/" target="_blank" rel="noopener"&gt;documentation&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #DataScience #ScientificComputing #Programming&lt;/p&gt;&#10;</description></item><item><title>fileup 📤</title><link>https://www.nijho.lt/post/advent-of-open-source/19-fileup/</link><pubDate>Thu, 19 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/19-fileup/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Sometimes the best tools are the ones you build for yourself and keep using for years. Today&amp;rsquo;s project is &lt;a href="https://github.com/basnijholt/fileup" target="_blank" rel="noopener"&gt;&lt;code&gt;fileup&lt;/code&gt;&lt;/a&gt;, a tiny command-line utility that&amp;rsquo;s been making my life easier for 7 years - because sharing files shouldn&amp;rsquo;t require leaving the terminal!&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Back in 2017, I found myself repeatedly going through the same tedious process: needing to share a file, opening a browser, navigating to a file sharing service, waiting for uploads, and copying URLs. As someone who lives in the terminal, this felt inefficient. I wanted something as simple as &lt;code&gt;fu filename&lt;/code&gt; that would handle everything and put the URL in my clipboard. Seven years and countless file shares later, it&amp;rsquo;s still one of my most-used tools.&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Supports both FTP and SCP uploads&lt;/li&gt;&#10;&lt;li&gt;Smart URL generation:&#10;&lt;ul&gt;&#10;&lt;li&gt;Jupyter notebooks → nbviewer links&lt;/li&gt;&#10;&lt;li&gt;Images → markdown-formatted links&lt;/li&gt;&#10;&lt;li&gt;Regular files → direct URLs&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Automatic file expiration and cleanup&lt;/li&gt;&#10;&lt;li&gt;Zero dependencies - uses only Python standard library&lt;/li&gt;&#10;&lt;li&gt;Clipboard integration (macOS)&lt;/li&gt;&#10;&lt;li&gt;Simple INI configuration&lt;/li&gt;&#10;&lt;li&gt;Works as both a standalone script or installed package&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;This is one of those tools where GitHub stars (currently 0!) don&amp;rsquo;t tell the whole story:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Used almost daily for 7 years&lt;/li&gt;&#10;&lt;li&gt;Shared thousands of files&lt;/li&gt;&#10;&lt;li&gt;Saved countless minutes of browser interactions&lt;/li&gt;&#10;&lt;li&gt;Proves that utility trumps popularity in open source&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Longevity comes from solving a real, persistent need&lt;/li&gt;&#10;&lt;li&gt;A tool doesn&amp;rsquo;t need to be revolutionary to be useful&lt;/li&gt;&#10;&lt;li&gt;Zero dependencies mean zero maintenance headaches&lt;/li&gt;&#10;&lt;li&gt;Sometimes the impact of a tool isn&amp;rsquo;t measured in stars&lt;/li&gt;&#10;&lt;li&gt;Simple tools that remove friction are worth maintaining&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to share files from your terminal? Check out &lt;a href="https://github.com/basnijholt/fileup" target="_blank" rel="noopener"&gt;fileup on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #DevTools #CommandLine #Programming&lt;/p&gt;&#10;</description></item><item><title>Psychedelic Data Science 🍄</title><link>https://www.nijho.lt/post/advent-of-open-source/18-psychedelic-data-science/</link><pubDate>Wed, 18 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/18-psychedelic-data-science/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;📝 Note: Running out of awesome projects and I want to save the best for last, so today just something fun.&lt;/p&gt;&#10;&lt;p&gt;While preparing for this advent calendar, I browsed through my 382 GitHub repositories and rediscovered this time capsule from 8 years ago. It explores the language of psychedelic experiences through data science - with an interesting hypothesis: do psychedelic experiences generate richer vocabulary compared to other substance reports?&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Back in 2015, when I was still &amp;ldquo;young&amp;rdquo; (before back pain and two-day hangovers became a thing), I was learning data science and natural language processing. I had an amusing hypothesis: surely people describing their psychedelic experiences would use richer vocabulary than those writing about stimulants - I mean, who hasn&amp;rsquo;t pondered the linguistic complexity of &amp;ldquo;everything is connected&amp;rdquo; versus &amp;ldquo;I cleaned my entire apartment at 4 AM&amp;rdquo;? Erowid.org, with its thousands of detailed first-person narratives across different substances, provided the perfect dataset to test this theory.&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Natural language processing of experience reports&lt;/li&gt;&#10;&lt;li&gt;Vocabulary richness analysis across substance categories&lt;/li&gt;&#10;&lt;li&gt;TF-IDF vectorization for text analysis&lt;/li&gt;&#10;&lt;li&gt;Support Vector Machine classification of experience types&lt;/li&gt;&#10;&lt;li&gt;Web scraping with BeautifulSoup&lt;/li&gt;&#10;&lt;li&gt;K-means clustering for discovering common themes&lt;/li&gt;&#10;&lt;li&gt;Word cloud generation to visualize vocabulary differences&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;A learning project that helped understand:&#10;&lt;ul&gt;&#10;&lt;li&gt;How different experiences shape language use&lt;/li&gt;&#10;&lt;li&gt;Processing subjective experience narratives&lt;/li&gt;&#10;&lt;li&gt;Document classification techniques&lt;/li&gt;&#10;&lt;li&gt;The challenges of quantifying vocabulary richness&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;10 GitHub stars&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Implementing respectful web scraping&lt;/li&gt;&#10;&lt;li&gt;Controlling for report length and education level&lt;/li&gt;&#10;&lt;li&gt;Creating meaningful metrics for vocabulary richness&lt;/li&gt;&#10;&lt;li&gt;Visualizing linguistic patterns across categories&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Putting things on GitHub means it&amp;rsquo;s less likely forgotten things get lost&lt;/li&gt;&#10;&lt;li&gt;Early data science projects often reveal our initial fascinations&lt;/li&gt;&#10;&lt;li&gt;Web scraping requires both technical skill and ethical consideration&lt;/li&gt;&#10;&lt;li&gt;Text analysis tools have evolved dramatically since 2015&lt;/li&gt;&#10;&lt;li&gt;Sometimes the most interesting projects are the ones you almost forgot about&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to explore this intersection of psychedelics, language, and data science? Check out &lt;a href="https://github.com/basnijholt/psychedelic-data-science" target="_blank" rel="noopener"&gt;the project on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #DataScience #NLP #MachineLearning&lt;/p&gt;&#10;</description></item><item><title>Adaptive Scheduler 🚀</title><link>https://www.nijho.lt/post/advent-of-open-source/17-adaptive-scheduler/</link><pubDate>Tue, 17 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/17-adaptive-scheduler/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Ever tried to efficiently run 100,000 cores from a Jupyter notebook? No? Well, I have, and it led to some interesting discoveries about the limits of parallel computing tools.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;During my 2018 internship at Microsoft Quantum, we faced a unique challenge: our &lt;a href="https://github.com/python-adaptive/adaptive" target="_blank" rel="noopener"&gt;adaptive sampling algorithms&lt;/a&gt; needed to run on massive computing clusters (50,000+ cores), but existing tools couldn&amp;rsquo;t handle it efficiently.&lt;/p&gt;&#10;&lt;p&gt;The problem was interesting: traditional parallel computing tools like Dask rely on a central scheduler process. Imagine 100,000 cores, each finishing a task every 10 seconds. That means every 10 microseconds, a core needs new work! With typical scheduler overhead being 1-50 milliseconds, workers would spend 99% of their time waiting rather than computing.&lt;/p&gt;&#10;&lt;p&gt;Usually, you&amp;rsquo;d solve this by batching work ahead of time. But with adaptive sampling (&lt;a href="../16-adaptive"&gt;yesterday&amp;rsquo;s project&lt;/a&gt;), that&amp;rsquo;s impossible - you need the results of previous calculations to know what to compute next. It was like trying to conduct an orchestra where each musician needs personal instructions every few seconds!&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;We created a solution that:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Acts as a meta-scheduler, launching multiple sub-schedulers (&lt;a href="https://www.dask.org/" target="_blank" rel="noopener"&gt;Dask&lt;/a&gt;, &lt;a href="https://ipyparallel.readthedocs.io/en/latest/" target="_blank" rel="noopener"&gt;ipyparallel&lt;/a&gt;, &lt;a href="https://mpi4py.readthedocs.io/en/stable/" target="_blank" rel="noopener"&gt;mpi4py&lt;/a&gt;, etc.)&lt;/li&gt;&#10;&lt;li&gt;Minimizes communication between scheduling layers&lt;/li&gt;&#10;&lt;li&gt;Handles job management through a simple database&lt;/li&gt;&#10;&lt;li&gt;Supports automatic fault tolerance and data saving&lt;/li&gt;&#10;&lt;li&gt;Works with any cluster scheduler (SLURM, PBS)&lt;/li&gt;&#10;&lt;li&gt;Provides real-time progress tracking in Jupyter notebooks&lt;/li&gt;&#10;&lt;li&gt;Scales to 50,000+ cores without breaking a sweat&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Used extensively for real quantum device simulations&lt;/li&gt;&#10;&lt;li&gt;26 GitHub stars (but don&amp;rsquo;t let that fool you - it&amp;rsquo;s highly specialized!)&lt;/li&gt;&#10;&lt;li&gt;Enables interactive supercomputing from a simple Jupyter notebook&lt;/li&gt;&#10;&lt;li&gt;Likely ran for many millions of core-hours across various projects&lt;/li&gt;&#10;&lt;li&gt;Actively maintained with 61 releases since 2018, proving its ongoing value&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-we-solved"&gt;🎯 Challenges We Solved &lt;a class="anchor" href="#-challenges-we-solved" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Scheduler overhead killing performance at scale&lt;/li&gt;&#10;&lt;li&gt;Data locality and fault tolerance&lt;/li&gt;&#10;&lt;li&gt;Real-time monitoring of massive parallel jobs&lt;/li&gt;&#10;&lt;li&gt;Making supercomputing accessible through notebooks&lt;/li&gt;&#10;&lt;li&gt;Automatic job recovery after crashes (e.g., when using spot VMs or Heisenbugs)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Sometimes you need to think outside the box&lt;/li&gt;&#10;&lt;li&gt;The best tools hide complexity behind simple interfaces&lt;/li&gt;&#10;&lt;li&gt;Real-time feedback is crucial, even for massive computations&lt;/li&gt;&#10;&lt;li&gt;When scaling up 1000x, you often need a completely different approach&lt;/li&gt;&#10;&lt;li&gt;Making things &amp;ldquo;just work&amp;rdquo; from a notebook is worth the effort&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to run massive parallel computations efficiently? Check out &lt;a href="https://github.com/basnijholt/adaptive-scheduler" target="_blank" rel="noopener"&gt;Adaptive Scheduler on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #HPC #QuantumComputing #ParallelComputing&lt;/p&gt;&#10;</description></item><item><title>Adaptive 📈</title><link>https://www.nijho.lt/post/advent-of-open-source/16-adaptive/</link><pubDate>Mon, 16 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/16-adaptive/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;During my PhD, I was running quantum transport simulations on our 800-core computational cluster.&#10;Traditional parameter sweeps using dense grids were hitting the cluster&amp;rsquo;s limits - wasting precious compute time on uninteresting regions while potentially missing important features.&#10;Most parameter spaces we explored were &amp;ldquo;boring&amp;rdquo; with occasional interesting regions that required higher sampling density.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Initially, in 2016, I had a collection of one-off adaptive sampling scripts.&#10;Together with Joseph Weston and the support of my PhD advisor Anton Akhmerov, we turned it into a full-fledged Python package &amp;ldquo;&lt;a href="https://github.com/python-adaptive/adaptive" target="_blank" rel="noopener"&gt;Adaptive&lt;/a&gt;&amp;rdquo;.&#10;What began as an internal tool to make our cluster usage more efficient slowly gained traction in the wider scientific computing community.&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Multiple adaptive sampling strategies (1D, 2D, ND, averaging stochastic functions)&lt;/li&gt;&#10;&lt;li&gt;Parallel execution support (local, MPI clusters, distributed systems)&lt;/li&gt;&#10;&lt;li&gt;Live plotting in Jupyter notebooks with real-time feedback&lt;/li&gt;&#10;&lt;li&gt;Customizable loss functions for different sampling priorities&lt;/li&gt;&#10;&lt;li&gt;Minimal dependencies&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact--applications"&gt;📊 Impact &amp;amp; Applications &lt;a class="anchor" href="#-impact--applications" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;1164 GitHub stars and thousands of daily downloads&lt;/li&gt;&#10;&lt;li&gt;Cited in 20+ scientific publications&lt;/li&gt;&#10;&lt;li&gt;Dependency of many packages, most notably: Orange Quantum System&amp;rsquo;s Quantify software for controlling real quantum computers!&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Used in cutting-edge research across fields:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Quantum Computing&lt;/strong&gt;: Simulating and controlling superconducting qubits and trapped ion systems&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Quantum Physics&lt;/strong&gt;: Exploring topological quantum systems and novel materials&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Materials Science&lt;/strong&gt;: Studying graphene and other 2D materials&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Engineering&lt;/strong&gt;:&#10;&lt;ul&gt;&#10;&lt;li&gt;Optimizing radar system performance&lt;/li&gt;&#10;&lt;li&gt;Fluid dynamics simulations&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Chemistry&lt;/strong&gt;: Molecular modeling and vibrational spectroscopy&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Most Unexpected Use&lt;/strong&gt;: Found in a paper on predicting energy usage of wartime bungalows!&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;This range of applications shows how a tool built for quantum transport simulations can find surprising uses across disciplines, from fundamental physics to everyday buildings.&lt;/p&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Making parallel execution truly scalable while maintaining sampling intelligence&lt;/li&gt;&#10;&lt;li&gt;Creating intuitive visualization tools for complex parameter spaces&lt;/li&gt;&#10;&lt;li&gt;Handling stochastic functions and noisy data&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Building a user base takes active effort - giving talks and engaging online made all the difference&lt;/li&gt;&#10;&lt;li&gt;Good documentation converts interested users into regular contributors&lt;/li&gt;&#10;&lt;li&gt;This was my first open source package and learned all about packaging, testing, and CI/CD&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want smarter parameter space exploration? Check out &lt;a href="https://github.com/python-adaptive/adaptive" target="_blank" rel="noopener"&gt;Adaptive on GitHub&lt;/a&gt; or try our &lt;a href="https://adaptive.readthedocs.io/en/latest/tutorial/tutorial.html" target="_blank" rel="noopener"&gt;interactive tutorial&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #ScientificComputing #ParallelComputing #Programming&lt;/p&gt;&#10;</description></item><item><title>clip-files 📋</title><link>https://www.nijho.lt/post/advent-of-open-source/15-clip-files/</link><pubDate>Sun, 15 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/15-clip-files/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Sometimes the most useful tools are born from daily frustrations.&#10;When working with AI assistants like ChatGPT or Claude, I found myself constantly copying files to share context.&#10;After doing this manually dozens of times, I created a tool to automate it.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While contributing to open source, I frequently like to share entire (or large parts of) codebases with AI assistants for context or code review.&#10;Since the code is open source anyway, there&amp;rsquo;s no reason not to share the full context! But manually copying files, adding their paths for context, and ensuring I didn&amp;rsquo;t exceed token limits was tedious.&#10;Instead of continuing this manual process, I spent an evening creating &lt;a href="https://github.com/basnijholt/clip-files" target="_blank" rel="noopener"&gt;&lt;code&gt;clip-files&lt;/code&gt;&lt;/a&gt; - and have used it multiple times daily ever since.&lt;/p&gt;&#10;&lt;p&gt;In fact, I&amp;rsquo;m using it right now! The command (shown below) lets me share this entire advent calendar series with AI assistants for proofreading.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clip-files --initial-file ai.md --files introduction.md day*.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Uses &lt;code&gt;tiktoken&lt;/code&gt; for accurate GPT token counting&lt;/li&gt;&#10;&lt;li&gt;Supports both directory traversal and specific file selection&lt;/li&gt;&#10;&lt;li&gt;Automatically generates a file index for reference&lt;/li&gt;&#10;&lt;li&gt;Allows custom initial instructions via file&lt;/li&gt;&#10;&lt;li&gt;Preserves file paths in output for context&lt;/li&gt;&#10;&lt;li&gt;Clean, typed, and documented codebase&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While this tool has no GitHub stars (I haven&amp;rsquo;t promoted it), its real impact is personal - I use it multiple times daily for:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Sharing entire open source codebases with AI assistants&lt;/li&gt;&#10;&lt;li&gt;Getting comprehensive code reviews&lt;/li&gt;&#10;&lt;li&gt;Discussing bug fixes with full context&lt;/li&gt;&#10;&lt;li&gt;Documentation improvements&lt;/li&gt;&#10;&lt;li&gt;Open source contributions&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The tool&amp;rsquo;s real value isn&amp;rsquo;t in its complexity (it&amp;rsquo;s just ~250 lines of code) but in how it removes friction from open source development.&#10;When working with open code, we might as well share all the context we can - and this tool makes that effortless.&lt;/p&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Maintaining consistent formatting across different input methods&lt;/li&gt;&#10;&lt;li&gt;Calculating accurate token counts for different components&lt;/li&gt;&#10;&lt;li&gt;Providing meaningful error messages for invalid inputs&lt;/li&gt;&#10;&lt;li&gt;Structuring output to be AI-assistant friendly&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;When working with open source code, sharing full context is valuable&lt;/li&gt;&#10;&lt;li&gt;Simple command-line tools can significantly improve daily workflows&lt;/li&gt;&#10;&lt;li&gt;Consistent formatting matters when working with AI assistants&lt;/li&gt;&#10;&lt;li&gt;Sometimes a 250-line tool can save hours of manual work&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to make sharing code with AI assistants easier? Check out &lt;a href="https://github.com/basnijholt/clip-files" target="_blank" rel="noopener"&gt;clip-files on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #DevTools #AI #Programming&lt;/p&gt;&#10;</description></item><item><title>PhD Thesis Cover Design 🎨</title><link>https://www.nijho.lt/post/advent-of-open-source/14-phd-thesis-cover-design/</link><pubDate>Sat, 14 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/14-phd-thesis-cover-design/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;When most people think of open source, they think of software. But what about open-sourcing your PhD thesis and creating a generative art system for its cover?&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;With my PhD defense approaching and needing around 100 copies of my thesis, I faced a design challenge. As someone more comfortable with Python than Photoshop, I turned what started as a simple visualization idea into a full-blown generative art project with machine learning-based clustering.&lt;/p&gt;&#10;&lt;figure id="figure-multiple-printed-copies-of-the-thesis"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/14-phd-thesis-cover-design/image_2_hu_8cd2dfb49d0239e5.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/14-phd-thesis-cover-design/image_2_hu_91ed23b7c6d41771.webp 400w, https://www.nijho.lt/post/advent-of-open-source/14-phd-thesis-cover-design/image_2_hu_8cd2dfb49d0239e5.webp 760w, https://www.nijho.lt/post/advent-of-open-source/14-phd-thesis-cover-design/image_2_hu_1ad1cec7a7457f36.webp 1200w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="616" alt="A photograph of multiple printed copies of Bas&amp;#39;s PhD thesis laid out on a table outdoors. Each copy has a unique, colorful, generated cover design. Potted plants are visible in the background." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;Multiple printed copies of the thesis&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Generated 3,671 unique covers from quantum physics simulation data&lt;/li&gt;&#10;&lt;li&gt;Used VGG16 neural network to extract features from covers&lt;/li&gt;&#10;&lt;li&gt;Implemented K-means clustering to group similar designs&lt;/li&gt;&#10;&lt;li&gt;Created interactive IPywidgets for design selection&lt;/li&gt;&#10;&lt;li&gt;Built a Binder app for real-time color scheme adjustment&lt;/li&gt;&#10;&lt;li&gt;Added unique edition numbers (1/120, 2/120, etc.)&lt;/li&gt;&#10;&lt;li&gt;Made both thesis and cover design code fully open source&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The project combines:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Adaptive sampling for physics simulations&lt;/li&gt;&#10;&lt;li&gt;Neural networks for image feature extraction&lt;/li&gt;&#10;&lt;li&gt;Interactive visualization tools&lt;/li&gt;&#10;&lt;li&gt;Parallel processing for batch generation&lt;/li&gt;&#10;&lt;li&gt;Version control for scientific writing&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Thesis repository: 14 GitHub stars&lt;/li&gt;&#10;&lt;li&gt;Cover design repository: 36 GitHub stars&lt;/li&gt;&#10;&lt;li&gt;Real-world adoption: &lt;a href="https://repository.tudelft.nl/record/uuid:e88a1897-0033-47e3-8b4f-84fd9cd5eec0" target="_blank" rel="noopener"&gt;Dr. Tomas Pippia from TU Delft&lt;/a&gt; used the code to create his own thesis cover&lt;/li&gt;&#10;&lt;li&gt;~100 unique printed copies, each with its own generated cover and edition number&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Managing 3,671 unique PDF files and their conversions&lt;/li&gt;&#10;&lt;li&gt;Creating a clustering system for visually similar designs&lt;/li&gt;&#10;&lt;li&gt;Building interactive tools for non-technical collaborators&lt;/li&gt;&#10;&lt;li&gt;Ensuring consistent print quality across designs&lt;/li&gt;&#10;&lt;li&gt;Balancing artistic appeal with scientific relevance&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Open source isn&amp;rsquo;t just about code - it&amp;rsquo;s about sharing knowledge&lt;/li&gt;&#10;&lt;li&gt;Python can be a powerful tool for creative projects&lt;/li&gt;&#10;&lt;li&gt;Machine learning can help solve unexpected problems&lt;/li&gt;&#10;&lt;li&gt;Good visualization requires both technical skill and artistic sense&lt;/li&gt;&#10;&lt;li&gt;Interactive tools make collaboration with non-programmers easier&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="-fun-fact"&gt;📚 Fun Fact &lt;a class="anchor" href="#-fun-fact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Due to COVID restrictions, I defended my thesis without any guests present. The silver lining? I still have about 100 beautifully unique copies at home, each with a different generated cover and edition number. If you&amp;rsquo;d like a physical copy of this piece of quantum physics meets generative art, just let me know!&lt;/p&gt;&#10;&lt;p&gt;Want to create your own thesis cover or see how I wrote mine? Check out the &lt;a href="https://github.com/basnijholt/thesis-cover" target="_blank" rel="noopener"&gt;thesis cover project&lt;/a&gt; and &lt;a href="https://github.com/basnijholt/thesis" target="_blank" rel="noopener"&gt;thesis source&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #PhD #Python #MachineLearning #DataVisualization&lt;/p&gt;&#10;</description></item><item><title>UniDep 🧬</title><link>https://www.nijho.lt/post/advent-of-open-source/13-unidep/</link><pubDate>Fri, 13 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/13-unidep/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Ever tried to set up a Python project that needs both pip and conda packages? Then you know the pain of maintaining multiple requirement files. &lt;a href="https://github.com/basnijholt/unidep" target="_blank" rel="noopener"&gt;UniDep&lt;/a&gt; was born from years of frustration with this exact problem.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While working on large monorepos with hundreds of dependencies, I kept running into the same issue: maintaining separate files for conda (&lt;code&gt;environment.yml&lt;/code&gt;), pip (&lt;code&gt;requirements.txt&lt;/code&gt;), and Python packaging (&lt;code&gt;pyproject.toml&lt;/code&gt;). Coming from languages like Rust, where package management is a joy, Python&amp;rsquo;s fragmented ecosystem can feel overwhelming, especially to newcomers (obligatory &lt;a href="https://xkcd.com/1987/" target="_blank" rel="noopener"&gt;xkcd.com/1987&lt;/a&gt;).&lt;/p&gt;&#10;&lt;p&gt;What started as a simple tool to avoid duplicate dependency definitions turned into something much more powerful. In enterprise environments, it&amp;rsquo;s common to find monorepos with over 1000 dependencies spread across dozens of packages. Setting up these environments traditionally involves following lengthy setup guides, installing packages in the correct order, and hoping nothing breaks.&lt;/p&gt;&#10;&lt;p&gt;Then came the &amp;ldquo;aha&amp;rdquo; moment - what if we could install everything with a single command? After implementing UniDep, those complex setup guides became a one-liner: &lt;code&gt;unidep install-all -e&lt;/code&gt;. It installs all dependencies and sets up every package in the monorepo in editable mode. What used to take hours now takes minutes.&lt;/p&gt;&#10;&lt;figure id="figure-the-difficulty-of-managing-pip-and-conda"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/13-unidep/meme_hu_1916102bc051ce0e.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/13-unidep/meme_hu_992ea341768024aa.webp 400w, https://www.nijho.lt/post/advent-of-open-source/13-unidep/meme_hu_1916102bc051ce0e.webp 760w, https://www.nijho.lt/post/advent-of-open-source/13-unidep/meme_hu_283eb791a4b7e11e.webp 980w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="447" alt="Meme with the text &amp;#39;ONE DOES NOT SIMPLY MANAGE BOTH PIP AND CONDA DEPENDENCIES&amp;#39; featuring Sean Bean as Boromir from The Lord of the Rings." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;The difficulty of managing pip and conda&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Single source of truth for all dependencies&lt;/li&gt;&#10;&lt;li&gt;Supports both &lt;code&gt;requirements.yaml&lt;/code&gt; and &lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Handles platform-specific dependencies&lt;/li&gt;&#10;&lt;li&gt;Generates conda-lock files both for entire monorepos and fully consistent lock files for its individual packages&lt;/li&gt;&#10;&lt;li&gt;Works with pip, conda, and mamba&lt;/li&gt;&#10;&lt;li&gt;Monorepo-friendly with local dependency support&lt;/li&gt;&#10;&lt;li&gt;Integrates with setuptools and hatchling&lt;/li&gt;&#10;&lt;li&gt;≥99% test coverage, fully-typed&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;221 GitHub stars&lt;/li&gt;&#10;&lt;li&gt;Featured on &lt;a href="https://www.youtube.com/live/PRaTs3PnJvI?si=UrVozo81Pj8WcyXh&amp;amp;t=489" target="_blank" rel="noopener"&gt;Python Bytes podcast&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Saves countless hours of dependency management&lt;/li&gt;&#10;&lt;li&gt;Makes complex project setups accessible&lt;/li&gt;&#10;&lt;li&gt;Actively maintained with 70 releases&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Different package naming between pip and conda&lt;/li&gt;&#10;&lt;li&gt;Platform-specific dependencies&lt;/li&gt;&#10;&lt;li&gt;Cross-platform compatibility (Linux, Windows, MacOS)&lt;/li&gt;&#10;&lt;li&gt;Supporting pip-only workflows&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Good developer experience is worth the effort&lt;/li&gt;&#10;&lt;li&gt;Sometimes you need to build a bridge between existing tools&lt;/li&gt;&#10;&lt;li&gt;The best tools make hard things easy&lt;/li&gt;&#10;&lt;li&gt;Dependency resolution is hard but can be simple for users&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="-future-plans"&gt;🔮 Future Plans &lt;a class="anchor" href="#-future-plans" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Adding Pixi support - allowing seamless migration to Pixi without changing config files&lt;/li&gt;&#10;&lt;li&gt;Eventually, Pixi might make UniDep obsolete (and that&amp;rsquo;s okay!)&lt;/li&gt;&#10;&lt;li&gt;For now, UniDep bridges the gap between pip and conda, making life easier for developers&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Want simpler dependency management? Check out &lt;a href="https://github.com/basnijholt/unidep" target="_blank" rel="noopener"&gt;UniDep on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #DevTools #Programming #PackageManagement #Conda&lt;/p&gt;&#10;</description></item><item><title>instacron 📸</title><link>https://www.nijho.lt/post/advent-of-open-source/12-instacron/</link><pubDate>Thu, 12 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/12-instacron/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Ever found yourself with beautiful photos but too lazy to post them? Instead of changing my habits, I did what any developer would do: spent way more time automating the process than it would have taken to post manually.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Back in 2017, I had hundreds of landscape photos from traveling through South America that I wanted to share on Instagram. The solution? Write a Python script that would randomly select photos and post them automatically, complete with philosophical quotes randomly selected from Bukowski, Einstein, or Dostoevsky because&amp;hellip; it seemed to be the default of all &amp;ldquo;influencers&amp;rdquo; at the time. Nothing says &amp;ldquo;authentic travel experience&amp;rdquo; like an auto-generated Bukowski quote about drinking next to your serene mountain photo!&lt;/p&gt;&#10;&lt;figure id="figure-instacron-example-post"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_2_hu_b99c73d57edbea5b.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_2_hu_5a3939660236b043.webp 400w, https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_2_hu_b99c73d57edbea5b.webp 760w, https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_2_hu_749d311a92e20dbc.webp 1200w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="433" alt="Screenshot of an Instagram post with a person standing in a stone archway overlooking a sunset, captioned &amp;#39;Taken in United States, Santa Barbara&amp;#39; with hashtags related to nature and travel." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;Instacron Example Post&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;figure id="figure-instacron-example-post"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_3_hu_847939577f1e3dd1.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_3_hu_a10eeae7bee69925.webp 400w, https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_3_hu_847939577f1e3dd1.webp 760w, https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_3_hu_12c1ed0c7f55a937.webp 1200w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="558" alt="Screenshot of an Instagram post of a cave with a river flowing through it, captioned &amp;#39;Taken in Thailand, some cave&amp;#39; with hashtags related to travel and adventure." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;Instacron Example Post&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;figure id="figure-instacron-example-post"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_4_hu_8832527ce6759925.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_4_hu_5430615296d94efb.webp 400w, https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_4_hu_8832527ce6759925.webp 760w, https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_4_hu_be87e46e6482de14.webp 1200w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="473" alt="Screenshot of an Instagram post showing a misty mountain landscape at sunrise, captioned &amp;#39;Taken in Thailand-Laos border&amp;#39; with hashtags related to nature and travel." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;Instacron Example Post&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;figure id="figure-instacron-example-post"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_5_hu_10c2294abc4f0ed4.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_5_hu_8e63ad58ac7e0b76.webp 400w, https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_5_hu_10c2294abc4f0ed4.webp 760w, https://www.nijho.lt/post/advent-of-open-source/12-instacron/image_5_hu_226dfc0f12041227.webp 1200w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="480" alt="Screenshot of an Instagram post of a mountain range with a green field in the foreground, captioned &amp;#39;Taken in Lago Argentino, SC, Argentina&amp;#39; with hashtags related to nature and travel." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;Instacron Example Post&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Randomly selects photos from a directory&lt;/li&gt;&#10;&lt;li&gt;Adds location data from EXIF metadata&lt;/li&gt;&#10;&lt;li&gt;Generates captions with random philosophical quotes&lt;/li&gt;&#10;&lt;li&gt;Adds two random emojis to match the &amp;ldquo;mood&amp;rdquo;&lt;/li&gt;&#10;&lt;li&gt;Automatically generates relevant hashtags&lt;/li&gt;&#10;&lt;li&gt;Tracks posting frequency&lt;/li&gt;&#10;&lt;li&gt;Uses cron for scheduled posting&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;21 GitHub stars&lt;/li&gt;&#10;&lt;li&gt;4143 Instagram followers&lt;/li&gt;&#10;&lt;li&gt;Successfully posted hundreds of photos&lt;/li&gt;&#10;&lt;li&gt;Generated some concerned DMs (more on that below!)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Instagram&amp;rsquo;s ever-changing API&lt;/li&gt;&#10;&lt;li&gt;Handling rate limits and restrictions&lt;/li&gt;&#10;&lt;li&gt;Ensuring photos weren&amp;rsquo;t posted multiple times&lt;/li&gt;&#10;&lt;li&gt;Making captions interesting without human input&lt;/li&gt;&#10;&lt;li&gt;The delicate art of random emoji selection&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-funny-story"&gt;😅 Funny Story &lt;a class="anchor" href="#-funny-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The random quote and emoji selector once created an unexpectedly dramatic post. Picture this: a beautiful landscape photo paired with a deeply philosophical quote about solitude, and topped with a broken heart emoji (💔). Within hours, I started receiving concerned DMs from friends asking if everything was okay and whether my relationship had ended!&lt;/p&gt;&#10;&lt;p&gt;I had to explain that no, I wasn&amp;rsquo;t going through a breakup - my bot just had a flair for the dramatic! These occasional mismatches made the whole thing more entertaining.&lt;/p&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Sometimes the best projects come from pure laziness&lt;/li&gt;&#10;&lt;li&gt;Automation doesn&amp;rsquo;t always save time, but it&amp;rsquo;s fun&lt;/li&gt;&#10;&lt;li&gt;Never underestimate the power of a misplaced emoji to trigger concerned messages&lt;/li&gt;&#10;&lt;li&gt;Some followers actually care about you (even if they&amp;rsquo;re worried about your bot-generated content)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;While I haven&amp;rsquo;t used this project in 6 years (Instagram&amp;rsquo;s API changes eventually won), it remains a testament to the lengths we&amp;rsquo;ll go to avoid repetitive tasks. Check out &lt;a href="https://github.com/basnijholt/instacron" target="_blank" rel="noopener"&gt;instacron on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #Automation #Photography #Instagram&lt;/p&gt;&#10;</description></item><item><title>conda-forge Contributions 🐍</title><link>https://www.nijho.lt/post/advent-of-open-source/11-conda-forge-contributions/</link><pubDate>Wed, 11 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/11-conda-forge-contributions/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Unlike other projects in this advent calendar, I want to celebrate being part of something much bigger: the &lt;a href="https://conda-forge.org/" target="_blank" rel="noopener"&gt;conda-forge&lt;/a&gt; community, where thousands of volunteers maintain the packages that power scientific Python.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I started with conda-forge in 2016 during my PhD work with Kwant, a quantum transport simulator. Installing it was a nightmare - it required system-level numerical libraries like MUMPS and Scotch which required a special config file to link. When I discovered conda-forge and its mission to make scientific software installation painless, I knew this was the solution!&lt;/p&gt;&#10;&lt;figure id="figure-the-humble-work-of-conda-forge-maintainers"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/11-conda-forge-contributions/meme_hu_227391ecf0bd252.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/11-conda-forge-contributions/meme_hu_9b170f53ebe4dc1b.webp 400w, https://www.nijho.lt/post/advent-of-open-source/11-conda-forge-contributions/meme_hu_227391ecf0bd252.webp 760w, https://www.nijho.lt/post/advent-of-open-source/11-conda-forge-contributions/meme_hu_95b0c28d6df1a41a.webp 978w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="586" alt="Meme showing a farmer standing in a field, with the text &amp;#39;MAINTAINING CONDA-FORGE PACKAGES FOR THE SCIENTIFIC COMMUNITY&amp;#39; above and &amp;#39;It ain&amp;#39;t much, but it&amp;#39;s honest work&amp;#39; below." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;The humble work of conda-forge maintainers&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Over the past 8 years, I&amp;rsquo;ve maintained over 40 build recipes, including:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Major scientific packages like &lt;a href="https://vtk.org/" target="_blank" rel="noopener"&gt;VTK&lt;/a&gt;, &lt;a href="https://mumps-solver.org/" target="_blank" rel="noopener"&gt;MUMPS&lt;/a&gt;, and &lt;a href="https://www.labri.fr/perso/pelegrin/scotch/" target="_blank" rel="noopener"&gt;Scotch&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Quantum physics tools like &lt;a href="https://quantumai.google/qsim" target="_blank" rel="noopener"&gt;Qsim&lt;/a&gt;, &lt;a href="https://github.com/microsoft/Qcodes" target="_blank" rel="noopener"&gt;Qcodes&lt;/a&gt;, and &lt;a href="https://kwant-project.org/" target="_blank" rel="noopener"&gt;Kwant&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Visualization libraries like &lt;a href="https://holoviews.org/" target="_blank" rel="noopener"&gt;HoloViews&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Plus all the packages I&amp;rsquo;ve authored myself&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The real magic of conda-forge is its infrastructure:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Automated builds across Linux, macOS, and Windows&lt;/li&gt;&#10;&lt;li&gt;Strict dependency version management&lt;/li&gt;&#10;&lt;li&gt;Comprehensive CI/CD pipelines&lt;/li&gt;&#10;&lt;li&gt;Community-driven quality control&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;What makes conda-forge truly special is how initial contributions blossom into collaborative efforts. I&amp;rsquo;ve experienced this firsthand: after creating the initial MUMPS feedstock, 25 other contributors joined in, each bringing their unique expertise.&lt;/p&gt;&#10;&lt;p&gt;A recent experience also illustrates this: I added CUDA support to both &lt;a href="https://github.com/conda-forge/qsimcirq-feedstock/pull/3" target="_blank" rel="noopener"&gt;Google&amp;rsquo;s Qsim&lt;/a&gt; and &lt;a href="https://github.com/conda-forge/qiskit-aer-feedstock/pull/19" target="_blank" rel="noopener"&gt;IBM&amp;rsquo;s Cirq&lt;/a&gt; packages. Shortly after, &lt;a href="https://github.com/leofang" target="_blank" rel="noopener"&gt;Leo Fang&lt;/a&gt;, an engineer from NVIDIA who knows CUDA far better than I do, stepped up and made significant improvements. This is the beauty of open source - experts naturally gravitate to where they can make the biggest impact.&lt;/p&gt;&#10;&lt;p&gt;This collaborative approach solves a problem I&amp;rsquo;ve encountered at several companies: the tendency to maintain complicated build systems internally with custom hacks. Instead of each organization reinventing the wheel, conda-forge provides a shared platform where:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Maintenance burden is distributed&lt;/li&gt;&#10;&lt;li&gt;Expert knowledge flows freely&lt;/li&gt;&#10;&lt;li&gt;Build practices are standardized&lt;/li&gt;&#10;&lt;li&gt;Infrastructure is public and reusable&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Keeping up with upstream changes&lt;/li&gt;&#10;&lt;li&gt;Managing complex dependency trees&lt;/li&gt;&#10;&lt;li&gt;Cross-platform compatibility&lt;/li&gt;&#10;&lt;li&gt;Coordinating with upstream maintainers&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Community effort beats solo work for infrastructure&lt;/li&gt;&#10;&lt;li&gt;Automation is crucial for reliability&lt;/li&gt;&#10;&lt;li&gt;Small contributions compound over time&lt;/li&gt;&#10;&lt;li&gt;Being part of something bigger is rewarding&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to contribute to scientific Python? Check out &lt;a href="https://conda-forge.org/" target="_blank" rel="noopener"&gt;conda-forge&lt;/a&gt; and the &lt;a href="https://github.com/conda-forge/staged-recipes" target="_blank" rel="noopener"&gt;staged-recipes repository&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #Scientific #Programming #Community&lt;/p&gt;&#10;</description></item><item><title>Home Assistant Config 🏠</title><link>https://www.nijho.lt/post/advent-of-open-source/10-home-assistant-config/</link><pubDate>Tue, 10 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/10-home-assistant-config/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Funny how things work out - initially, I didn&amp;rsquo;t care for home automation at all. But when in 2020 I discovered Home Assistant was written in Python, my favorite programming language, I suddenly found a new hobby. Over the years, I&amp;rsquo;ve built a comprehensive setup with over 137 automations (and counting!). These automations are triggered by everything from simple motion sensors to complex patterns of conditions and sensor states, creating a home that responds intelligently to our needs.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;What started with basic light controls evolved into a complete home automation system. In the beginning, my wife was continuously annoyed by lights turning on at the wrong moment or not at all. However, as I debugged and refined the system, things started working exactly as expected, and she began to really appreciate it. When we moved from The Netherlands to the US in 2022 (and two more times since), we felt like we were back in the stone age having to manually control lights. The annoyance had shifted from buggy automations to not having Home Assistant fully installed yet!&lt;/p&gt;&#10;&lt;p&gt;Unfortunately, I&amp;rsquo;ve been less active lately with my Home Assistant setup because so many other projects draw my attention. One of these &amp;ldquo;projects&amp;rdquo; is my 1.5-year-old twin boys, who now have their own room with specialized automations, including 100% local cameras with ML pattern detection for monitoring without compromising privacy.&lt;/p&gt;&#10;&lt;figure id="figure-the-evolution-of-home-automation"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/10-home-assistant-config/meme_hu_6700885751a5eed.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/10-home-assistant-config/meme_hu_a2f1eaea094dec73.webp 285w, https://www.nijho.lt/post/advent-of-open-source/10-home-assistant-config/meme_hu_6700885751a5eed.webp 542w, https://www.nijho.lt/post/advent-of-open-source/10-home-assistant-config/meme_hu_2a8639bb88253b09.webp 856w" sizes="auto, (max-width: 760px) 100vw, 720px" width="542" height="760" alt="Meme with four panels, each showing a brain with increasing complexity. The panels are labeled: &amp;#39;MANUAL LIGHT SWITCHES&amp;#39;, &amp;#39;SMART BULBS&amp;#39;, &amp;#39;MOTION SENSORS&amp;#39;, and &amp;#39;137 AUTOMATIONS RUNNING YOUR ENTIRE HOME&amp;#39;." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;The evolution of home automation&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;137 documented automations with auto-generated documentation&lt;/li&gt;&#10;&lt;li&gt;Presence detection in every room&lt;/li&gt;&#10;&lt;li&gt;Adaptive lighting throughout the house&lt;/li&gt;&#10;&lt;li&gt;Privacy-focused baby monitoring&lt;/li&gt;&#10;&lt;li&gt;Fully local processing&lt;/li&gt;&#10;&lt;li&gt;2,432 commits of continuous improvement&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;1,747 GitHub stars&lt;/li&gt;&#10;&lt;li&gt;185 forks&lt;/li&gt;&#10;&lt;li&gt;Used as reference by countless Home Assistant enthusiasts&lt;/li&gt;&#10;&lt;li&gt;Featured in Home Assistant community&lt;/li&gt;&#10;&lt;li&gt;Spawned several spin-off projects (like &lt;a href="../02-adaptive-lighting"&gt;Adaptive Lighting&lt;/a&gt; from Day 2!)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Start small, expand gradually&lt;/li&gt;&#10;&lt;li&gt;Privacy and local processing matter&lt;/li&gt;&#10;&lt;li&gt;Automated documentation stays accurate&lt;/li&gt;&#10;&lt;li&gt;Home automation is never &amp;ldquo;finished&amp;rdquo;&lt;/li&gt;&#10;&lt;li&gt;Sometimes life (like twins!) brings new automation challenges&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to explore my home automation setup? Check out &lt;a href="https://github.com/basnijholt/home-assistant-config" target="_blank" rel="noopener"&gt;the full config on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #HomeAssistant #SmartHome #IoT #Python&lt;/p&gt;&#10;</description></item><item><title>yaml2bib 📚</title><link>https://www.nijho.lt/post/advent-of-open-source/09-yaml2bib/</link><pubDate>Mon, 09 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/09-yaml2bib/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Every academic writing in LaTeX knows the pain: managing BibTeX files with hundreds of citations, each potentially formatted slightly differently, with inconsistent journal abbreviations, and duplicated entries across chapters. While procrastinating on writing my PhD thesis, I became obsessed with solving this universal academic headache.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;When writing a PhD thesis with multiple chapters that share citations, maintaining consistency is crucial but tedious. Different chapters might cite the same paper but with slightly different BibTeX entries - one using the full journal name, another using abbreviations, or worse, having slightly different titles or author lists. Rather than manually standardizing ~350 citations, I created yaml2bib: give it a list of DOIs in YAML format, and it generates a perfect BibTeX file with consistent journal abbreviations and formatting.&lt;/p&gt;&#10;&lt;figure id="figure-the-pain-of-manual-bibtex-management"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/09-yaml2bib/meme_hu_aac0680d796f5d9.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/09-yaml2bib/meme_hu_f547b35ecc31869c.webp 400w, https://www.nijho.lt/post/advent-of-open-source/09-yaml2bib/meme_hu_aac0680d796f5d9.webp 760w, https://www.nijho.lt/post/advent-of-open-source/09-yaml2bib/meme_hu_5f55d375b7929c53.webp 976w" sizes="auto, (max-width: 760px) 100vw, 720px" width="760" height="371" alt="Meme depicting a cartoon dog sitting calmly in a room engulfed in flames, saying &amp;#39;This is fine.&amp;#39; The dog is labeled &amp;#39;Managing 300&amp;#43; BibTeX entries manually&amp;#39;." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;The pain of manual BibTeX management&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Fetches citation data from CrossRef using DOIs&lt;/li&gt;&#10;&lt;li&gt;Ensures consistent journal name abbreviations&lt;/li&gt;&#10;&lt;li&gt;Caches API responses to avoid repeated queries&lt;/li&gt;&#10;&lt;li&gt;Supports custom text replacements&lt;/li&gt;&#10;&lt;li&gt;Combines multiple YAML sources&lt;/li&gt;&#10;&lt;li&gt;Works as both CLI tool and Python library&lt;/li&gt;&#10;&lt;li&gt;Zero manual citation formatting needed&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While this tool has only 11 GitHub stars, it saved me countless hours during thesis writing and helped ensure professional consistency in my citations. It&amp;rsquo;s particularly useful for:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Large academic documents with many citations&lt;/li&gt;&#10;&lt;li&gt;Multi-chapter works sharing references&lt;/li&gt;&#10;&lt;li&gt;Collaborative papers needing citation standardization&lt;/li&gt;&#10;&lt;li&gt;Anyone tired of manually formatting BibTeX entries&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Procrastination can be productive if channeled right (story of my life&amp;hellip;)&lt;/li&gt;&#10;&lt;li&gt;A single source of truth prevents inconsistencies&lt;/li&gt;&#10;&lt;li&gt;Good tools make you care about details you&amp;rsquo;d otherwise skip&lt;/li&gt;&#10;&lt;li&gt;Every LaTeX user has fought with BibTeX at some point&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want perfectly consistent citations? Check out &lt;a href="https://github.com/basnijholt/yaml2bib" target="_blank" rel="noopener"&gt;yaml2bib on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #Academia #LaTeX #Research&lt;/p&gt;&#10;</description></item><item><title>Home Assistant Stream Deck YAML 🎮</title><link>https://www.nijho.lt/post/advent-of-open-source/08-home-assistant-stream-deck-yaml/</link><pubDate>Sun, 08 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/08-home-assistant-stream-deck-yaml/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;What started as a personal hack to control my home (temperature, lights, TV, music) with a gifted Stream Deck evolved into something bigger. This project became my first real exploration of AI-assisted development, showing me how much faster I could work with AI as a tool. With 250+ stars and 9 contributors adding significant features, it&amp;rsquo;s grown beyond my initial vision.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While the Stream Deck&amp;rsquo;s official software is great for streamers, it doesn&amp;rsquo;t support Linux and its Home Assistant integration is not very programmable. After creating a simple solution using &lt;a href="https://github.com/abcminiuser/python-elgato-streamdeck" target="_blank" rel="noopener"&gt;&lt;code&gt;python-elgato-streamdeck&lt;/code&gt;&lt;/a&gt;, I realized others might be interested. Using AI as a development partner changed my workflow - it helped write tests, reviewed code, and generated use cases I hadn&amp;rsquo;t thought of. What really surprised me was asking AI to brainstorm example configurations - it generated over 30 creative use cases with complete code examples, which I then turned into unit tests. These tests now automatically generate the documentation using &lt;a href="https://github.com/basnijholt/markdown-code-runner" target="_blank" rel="noopener"&gt;&lt;code&gt;markdown-code-runner&lt;/code&gt; &lt;/a&gt;(&lt;a href="../05-markdown-code-runner"&gt;from Day 5&lt;/a&gt;).&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;YAML configuration with Jinja2 templating&lt;/li&gt;&#10;&lt;li&gt;Supports all Stream Deck models, including the Plus with dials&lt;/li&gt;&#10;&lt;li&gt;Cross-platform (Linux, macOS, Windows)&lt;/li&gt;&#10;&lt;li&gt;30+ example configurations (AI-generated, then human-refined)&lt;/li&gt;&#10;&lt;li&gt;Documentation auto-generated from code and unit tests&lt;/li&gt;&#10;&lt;li&gt;Available as a Home Assistant add-on&lt;/li&gt;&#10;&lt;li&gt;Community-contributed features like touchscreen support and dial controls&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;AI can significantly speed up development&lt;/li&gt;&#10;&lt;li&gt;Faster development enables broader project scope&lt;/li&gt;&#10;&lt;li&gt;Good examples make the best documentation&lt;/li&gt;&#10;&lt;li&gt;Tools building upon tools multiply productivity&lt;/li&gt;&#10;&lt;li&gt;Community contributions can take a project in exciting new directions&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to control Home Assistant with a Stream Deck? Check out &lt;a href="https://github.com/basnijholt/home-assistant-streamdeck-yaml" target="_blank" rel="noopener"&gt;the project on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #HomeAssistant #SmartHome #Python #AI&lt;/p&gt;&#10;</description></item><item><title>numthreads 🧵</title><link>https://www.nijho.lt/post/advent-of-open-source/07-numthreads/</link><pubDate>Sat, 07 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/07-numthreads/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Sometimes the smallest tools solve the most persistent problems.&#10;Today&amp;rsquo;s project is about taming automatic parallelization in scientific computing - a deceptively simple challenge that has cost countless CPU hours.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While working on high-performance computing clusters, I frequently encountered a counterintuitive problem: code running slower on multiple CPU cores than on a single one.&#10;Many scientific libraries (NumPy, SciPy) automatically parallelize operations, which sounds great but can actually harm performance when you&amp;rsquo;re already parallelizing at a higher level.&#10;After repeatedly explaining to colleagues why they needed to set various environment variables to disable this behavior, I created &lt;a href="https://github.com/basnijholt/numthreads" target="_blank" rel="noopener"&gt;&lt;code&gt;numthreads&lt;/code&gt;&lt;/a&gt; to solve it once and for all.&lt;/p&gt;&#10;&lt;figure id="figure-overenthusiastic-threading"&gt;&lt;img src="https://www.nijho.lt/post/advent-of-open-source/07-numthreads/meme_hu_4d36ab41a66a48cb.webp" srcset="https://www.nijho.lt/post/advent-of-open-source/07-numthreads/meme_hu_8178332437a0cb36.webp 297w, https://www.nijho.lt/post/advent-of-open-source/07-numthreads/meme_hu_4d36ab41a66a48cb.webp 565w, https://www.nijho.lt/post/advent-of-open-source/07-numthreads/meme_hu_f55150e31c7bd3b9.webp 891w" sizes="auto, (max-width: 760px) 100vw, 720px" width="565" height="760" alt="Meme showing a person labeled &amp;#39;YOUR CODE&amp;#39; about to be hugged by a pink creature labeled &amp;#39;NUMTHREADS.SET(1)&amp;#39;, representing setting the number of threads to 1. In the background, a yellow circle labeled &amp;#39;CPU PERFORMANCE&amp;#39; is visible." loading="lazy" data-zoomable&gt;&lt;figcaption&gt;Overenthusiastic threading&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Controls thread count for major numerical libraries:&#10;&lt;ul&gt;&#10;&lt;li&gt;OpenBLAS&lt;/li&gt;&#10;&lt;li&gt;Intel&amp;rsquo;s Math Kernel Library (MKL)&lt;/li&gt;&#10;&lt;li&gt;OpenMP&lt;/li&gt;&#10;&lt;li&gt;NumExpr&lt;/li&gt;&#10;&lt;li&gt;Accelerate&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Zero dependencies&lt;/li&gt;&#10;&lt;li&gt;Tiny (≤7KB) yet solves a real problem&lt;/li&gt;&#10;&lt;li&gt;Both CLI and Python API&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Over my scientific computing career, I&amp;rsquo;ve encountered this threading issue dozens of times. Since creating this package a year ago, I&amp;rsquo;ve helped at least 5 different researchers speed up their workflows, sometimes by orders of magnitude. The beauty lies in its simplicity - the fix is often as simple as:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;numthreads 1&lt;span class="k"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;The simplest solutions are often the best&lt;/li&gt;&#10;&lt;li&gt;More cores doesn&amp;rsquo;t always mean faster code&lt;/li&gt;&#10;&lt;li&gt;If you find yourself repeating the same instructions, write a tool&lt;/li&gt;&#10;&lt;li&gt;Even a 7KB package can save hours of computing time&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want better control over your numerical computations? Check out &lt;a href="https://github.com/basnijholt/numthreads" target="_blank" rel="noopener"&gt;numthreads on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #HPC #ScientificComputing #Programming&lt;/p&gt;&#10;</description></item><item><title>Open Science Publications 🔬</title><link>https://www.nijho.lt/post/advent-of-open-source/06-open-science-publications/</link><pubDate>Fri, 06 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/06-open-science-publications/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Today&amp;rsquo;s post is about something I have strong opinions on: making scientific research reproducible. While many researchers talk about open science, actually making your work reproducible for anyone requires significant effort.&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;During my Ph.D. and subsequent research, I noticed a frustrating pattern in scientific publications: claims of &amp;ldquo;code available upon request&amp;rdquo; that often led nowhere, or code snippets that wouldn&amp;rsquo;t run without substantial modification. I made an effort that my publications would be different – each includes complete, runnable code that reproduces every figure and result.&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Multiple repositories covering quantum physics experiments and simulations:&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/orbitalfield" target="_blank" rel="noopener"&gt;orbitalfield&lt;/a&gt; - &amp;ldquo;Orbital effect of magnetic field on the Majorana phase diagram&amp;rdquo; (94 citations)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/supercurrent-majorana-nanowire" target="_blank" rel="noopener"&gt;supercurrent-majorana-nanowire&lt;/a&gt; - &amp;ldquo;Supercurrent interference in few-mode nanowire Josephson junctions&amp;rdquo; (78 citations)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/zigzag-majoranas" target="_blank" rel="noopener"&gt;zigzag-majoranas&lt;/a&gt; - &amp;ldquo;Enhanced proximity effect in zigzag-shaped Majorana Josephson junctions&amp;rdquo; (56 citations)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/microsoft/azure-quantum-tgp" target="_blank" rel="noopener"&gt;azure-quantum-tgp&lt;/a&gt; - &amp;ldquo;Protocol to identify a topological superconducting phase in a three-terminal device&amp;rdquo; (52 citations)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/basnijholt/spin-orbit-nanowires" target="_blank" rel="noopener"&gt;spin-orbit-nanowires&lt;/a&gt; - &amp;ldquo;Spin-orbit protection of induced superconductivity in Majorana nanowires&amp;rdquo; (81 citations)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-evolution-of-best-practices"&gt;🔄 Evolution of Best Practices &lt;a class="anchor" href="#-evolution-of-best-practices" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;What I consider best practices has evolved over time. Today, I would:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Use &lt;a href="https://pixi.sh/" target="_blank" rel="noopener"&gt;Pixi&lt;/a&gt; for universal lock files across operating systems and programming languages&lt;/li&gt;&#10;&lt;li&gt;Provide a minimal Docker container that just installs the pixi lock file&lt;/li&gt;&#10;&lt;li&gt;Create self-documenting Jupyter notebooks that reproduce every result&lt;/li&gt;&#10;&lt;li&gt;Include clear figure-to-code mapping for paper reproducibility&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Balancing code cleanliness with research deadlines&lt;/li&gt;&#10;&lt;li&gt;Managing large datasets efficiently&lt;/li&gt;&#10;&lt;li&gt;Ensuring long-term reproducibility&lt;/li&gt;&#10;&lt;li&gt;Making complex physics simulations accessible&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Clean code takes time but saves more in the long run&lt;/li&gt;&#10;&lt;li&gt;Documentation is as crucial as the code itself&lt;/li&gt;&#10;&lt;li&gt;Lock files are essential for true reproducibility&lt;/li&gt;&#10;&lt;li&gt;Making code public improves its quality&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to explore quantum physics simulations? Check out the repositories above, each linked to their corresponding papers with full reproduction instructions.&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #OpenScience #Physics #QuantumComputing #Python&lt;/p&gt;&#10;</description></item><item><title>markdown-code-runner 📝</title><link>https://www.nijho.lt/post/advent-of-open-source/05-markdown-code-runner/</link><pubDate>Thu, 05 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/05-markdown-code-runner/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Documentation should be as close to the code as possible - that&amp;rsquo;s the principle of &amp;ldquo;information locality.&amp;rdquo; But how do you keep code examples and their outputs in sync? Enter markdown-code-runner, a tool that executes code blocks within Markdown files and updates their output in-place!&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While maintaining various open source and work projects, I found myself constantly updating README or Wiki files with new command outputs, updated tables, and fresh examples. This manual process was error-prone and tedious. Instead of using external templating files or more advanced documentation generators like Sphinx and Quarto (which are often overkill for single-page projects), I wanted something simpler: code and its output living together in the same Markdown file, always in sync.&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Executes code blocks (Python and Bash) within Markdown files&lt;/li&gt;&#10;&lt;li&gt;Updates outputs in-place while preserving the original file structure&lt;/li&gt;&#10;&lt;li&gt;Supports hidden code blocks for generating content without showing the code&lt;/li&gt;&#10;&lt;li&gt;Works with any language by using file code blocks and bash execution&lt;/li&gt;&#10;&lt;li&gt;Zero external dependencies&lt;/li&gt;&#10;&lt;li&gt;GitHub Actions integration for automatic README updates&lt;/li&gt;&#10;&lt;li&gt;Preserves Markdown spec compatibility by hiding code in HTML comments (&lt;code&gt;&amp;lt;!-- CODE HERE --&amp;gt;&lt;/code&gt;)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;97 GitHub stars&lt;/li&gt;&#10;&lt;li&gt;Used in many of my other projects for auto-generating:&#10;&lt;ul&gt;&#10;&lt;li&gt;Command-line help texts&lt;/li&gt;&#10;&lt;li&gt;API documentation&lt;/li&gt;&#10;&lt;li&gt;Example outputs&lt;/li&gt;&#10;&lt;li&gt;Data tables&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Helps maintain &amp;ldquo;information locality&amp;rdquo; - everything needed is in one file&lt;/li&gt;&#10;&lt;li&gt;Enables dynamic documentation that stays up-to-date automatically&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Parsing Markdown without breaking its structure&lt;/li&gt;&#10;&lt;li&gt;Supporting multiple programming languages&lt;/li&gt;&#10;&lt;li&gt;Making it work seamlessly with GitHub Actions&lt;/li&gt;&#10;&lt;li&gt;Handling complex outputs (tables, images, etc.)&lt;/li&gt;&#10;&lt;li&gt;Maintaining backward compatibility with existing Markdown files&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Documentation is best when it&amp;rsquo;s close to what it documents&lt;/li&gt;&#10;&lt;li&gt;Automation should be invisible - the tool shouldn&amp;rsquo;t change how you write Markdown&lt;/li&gt;&#10;&lt;li&gt;Sometimes the simplest solution (using HTML comments) is the best&lt;/li&gt;&#10;&lt;li&gt;Good documentation tools should make maintaining docs easier, not harder&lt;/li&gt;&#10;&lt;li&gt;Auto-generated content should be clearly marked as such&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to keep your documentation always in sync? Check out &lt;a href="https://github.com/basnijholt/markdown-code-runner" target="_blank" rel="noopener"&gt;markdown-code-runner on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #Documentation #DevTools #Programming&lt;/p&gt;&#10;</description></item><item><title>WenFire: When Could I retire?! 🔥</title><link>https://www.nijho.lt/post/advent-of-open-source/04-wenfire/</link><pubDate>Wed, 04 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/04-wenfire/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;While I absolutely love my job, I believe I&amp;rsquo;ll love it even more when my family&amp;rsquo;s livelihood doesn&amp;rsquo;t depend on it.&#10;That&amp;rsquo;s the dream of financial independence - doing what you love because you want to, not because you have to.&#10;This led me to create &lt;a href="https://wenfire.nijho.lt/" target="_blank" rel="noopener"&gt;WenFire&lt;/a&gt;, a financial independence calculator that helps answer the eternal questions: &amp;ldquo;When can I retire?&amp;rdquo; and &amp;ldquo;How many days of extra work will that new iPhone or car cost me?&amp;rdquo;&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;There are many retirement calculators out there, but they all made assumptions I didn&amp;rsquo;t quite agree with (and I&amp;rsquo;m sure others won&amp;rsquo;t agree with my assumptions either!).&#10;I wanted something that matched my specific views on salary growth and investment returns.&#10;Plus, I had been itching to learn more about building web applications, so this project let me explore FastAPI, htmx, and Vega-Lite while solving a personal need.&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Built with FastAPI and Bootstrap&lt;/li&gt;&#10;&lt;li&gt;Interactive visualizations using Vega-Lite&lt;/li&gt;&#10;&lt;li&gt;Responsive and mobile-friendly design&lt;/li&gt;&#10;&lt;li&gt;Customizable assumptions for:&#10;&lt;ul&gt;&#10;&lt;li&gt;Investment growth&lt;/li&gt;&#10;&lt;li&gt;Inflation rates&lt;/li&gt;&#10;&lt;li&gt;Salary increases&lt;/li&gt;&#10;&lt;li&gt;Safe withdrawal rates&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Allows parameter changes at certain dates&lt;/li&gt;&#10;&lt;li&gt;Calculate retirement impact of one-time expenses&lt;/li&gt;&#10;&lt;li&gt;All parameters are encoded in the URL for easy sharing&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While this project only has 3 GitHub stars, its real value is personal - it helps me track my own journey to financial independence.&#10;It&amp;rsquo;s also been a great learning experience in web development, an area where I had little prior experience.&lt;/p&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Learning web development from scratch&lt;/li&gt;&#10;&lt;li&gt;Creating intuitive visualizations for financial data&lt;/li&gt;&#10;&lt;li&gt;Balancing simplicity with customization options&lt;/li&gt;&#10;&lt;li&gt;Making reasonable default assumptions&lt;/li&gt;&#10;&lt;li&gt;Ensuring mobile responsiveness&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Financial modeling requires careful consideration of assumptions&lt;/li&gt;&#10;&lt;li&gt;Web development is a different beast from scientific computing&lt;/li&gt;&#10;&lt;li&gt;Sometimes the best way to learn new tech is to solve a personal problem&lt;/li&gt;&#10;&lt;li&gt;The best calculator is the one that matches your own assumptions&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want to calculate your path to financial independence? Try it at &lt;a href="https://wenfire.nijho.lt/" target="_blank" rel="noopener"&gt;wenfire.nijho.lt&lt;/a&gt; or check out the &lt;a href="https://github.com/basnijholt/wenfire" target="_blank" rel="noopener"&gt;source code&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #FinancialIndependence #WebDev #FIRE&lt;/p&gt;&#10;</description></item><item><title>rsync-time-machine.py 🕒</title><link>https://www.nijho.lt/post/advent-of-open-source/03-rsync-time-machine/</link><pubDate>Tue, 03 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/03-rsync-time-machine/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Sometimes the best open source projects start with &amp;ldquo;I can do better than this.&amp;rdquo; That&amp;rsquo;s exactly what happened when I looked at &lt;a href="https://github.com/laurent22/rsync-time-backup" target="_blank" rel="noopener"&gt;laurent22/rsync-time-backup&lt;/a&gt;, a popular 600-line Bash script for Time Machine-style backups. While it worked, it had known bugs and was tricky to maintain due to Bash&amp;rsquo;s limitations. It also lacked tests, type hints (bash duh&amp;hellip;), and proper error handling. So, I did what any reasonable developer would do: rewrote it in Python: &lt;a href="https://github.com/basnijholt/rsync-time-machine.py" target="_blank" rel="noopener"&gt;&lt;code&gt;rsync-time-machine.py&lt;/code&gt;&lt;/a&gt;! 🐍&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I needed reliable backups that worked across platforms and filesystems, unlike Apple&amp;rsquo;s Time Machine.&#10;While many excellent backup tools exist, I was drawn to the simplicity of this approach: it just creates folders named &lt;code&gt;YYYY-MM-DD-HH-MM-SS&lt;/code&gt; containing your files.&#10;No special tools needed for restoring or browsing - just your regular file explorer!&#10;I created a fully tested, typed, and documented Python port that maintains compatibility with the original while adding modern development practices.&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Creates incremental backups using hard links (saving space while keeping full snapshots)&lt;/li&gt;&#10;&lt;li&gt;Works on Linux, macOS, and Windows (via WSL or Cygwin)&lt;/li&gt;&#10;&lt;li&gt;Supports local and remote (SSH) backups&lt;/li&gt;&#10;&lt;li&gt;Smart backup expiration strategy&lt;/li&gt;&#10;&lt;li&gt;Built-in safety checks and locking mechanism&lt;/li&gt;&#10;&lt;li&gt;Pretty terminal output&lt;/li&gt;&#10;&lt;li&gt;Absolutely zero dependencies beyond Python itself - it just works!&lt;/li&gt;&#10;&lt;li&gt;Usable as a standalone &lt;code&gt;.py&lt;/code&gt; script or pip installable package&lt;/li&gt;&#10;&lt;li&gt;100% test coverage&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The project reached the front page of &lt;a href="https://news.ycombinator.com/item?id=35933238" target="_blank" rel="noopener"&gt;Y Combinator&amp;rsquo;s Hacker News&lt;/a&gt;, causing its popularity to explode:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;381 GitHub stars&lt;/li&gt;&#10;&lt;li&gt;16 forks&lt;/li&gt;&#10;&lt;li&gt;6 contributors&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://realpython.com/podcasts/rpp/158/#t=1588" target="_blank" rel="noopener"&gt;Featured on Real Python podcast&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Active discussion on Reddit&amp;rsquo;s &lt;a href="https://www.reddit.com/r/commandline/comments/13gtnrp/ported_a_popular_untested_600_line_bash_script_to/" target="_blank" rel="noopener"&gt;r/commandline&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Maintaining compatibility with the original Bash script while improving the codebase&lt;/li&gt;&#10;&lt;li&gt;Making it work on Windows through WSL/Cygwin (surprisingly tricky!)&lt;/li&gt;&#10;&lt;li&gt;Handling various edge cases in filesystem operations&lt;/li&gt;&#10;&lt;li&gt;Implementing a robust backup expiration strategy&lt;/li&gt;&#10;&lt;li&gt;Adding comprehensive test coverage for file operations&lt;/li&gt;&#10;&lt;li&gt;Keeping the codebase dependency-free while maintaining functionality&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Sometimes a rewrite is worth it, especially when adding modern development practices&lt;/li&gt;&#10;&lt;li&gt;Good documentation and test coverage are crucial for backup tools&lt;/li&gt;&#10;&lt;li&gt;The Python community appreciates well-structured ports of useful tools&lt;/li&gt;&#10;&lt;li&gt;Zero dependencies means zero headaches for users&lt;/li&gt;&#10;&lt;li&gt;Cross-platform compatibility (especially Windows) requires careful consideration&lt;/li&gt;&#10;&lt;li&gt;Simple, transparent solutions (like regular folders) can be better than complex ones&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want reliable, cross-platform Time Machine-style backups? Check out &lt;a href="https://github.com/basnijholt/rsync-time-machine.py" target="_blank" rel="noopener"&gt;the project on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #Backups #DevTools #Programming&lt;/p&gt;&#10;</description></item><item><title>Adaptive Lighting 💡</title><link>https://www.nijho.lt/post/advent-of-open-source/02-adaptive-lighting/</link><pubDate>Mon, 02 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/02-adaptive-lighting/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;Unlike &lt;a href="../01-calendar-of-life"&gt;yesterday&amp;rsquo;s Calendar of Life&lt;/a&gt; project, today we&amp;rsquo;re looking at one of my most popular contributions. It&amp;rsquo;s one of several Home Assistant and home automation related projects (&lt;a href="../08-home-assistant-stream-deck-yaml"&gt;Day 8&lt;/a&gt;, &lt;a href="../10-home-assistant-config"&gt;Day 10&lt;/a&gt;, &lt;a href="../24-home-assistant-ecosystem"&gt;Day 24&lt;/a&gt;) I&amp;rsquo;ll be sharing this month. While my coding journey started in scientific computing with physics simulations, discovering how to control my home environment through code sparked a new obsession. Home Assistant, currently the &lt;strong&gt;#1&lt;/strong&gt; open source project on GitHub, is the brain of my smart home and lets you control and automate everything from lights to thermostats, completely locally and privacy-focused.&lt;/p&gt;&#10;&lt;p&gt;Keep your lighting in sync with the sun, they said. It&amp;rsquo;ll be simple, they said&amp;hellip;&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Back in 2020, inspired by @claytonjn&amp;rsquo;s circadian_lighting Home Assistant component, I wanted to create a more comprehensive solution for automatically adjusting lights based on the time of day. What started as a contribution attempt to Home Assistant Core evolved into its own custom component when the PR wasn&amp;rsquo;t merged - handling all the edge cases made the code too complex for core. The standalone component gave us the freedom to implement features properly, becoming one of Home Assistant&amp;rsquo;s most installed custom integrations.&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;While the concept is simple, making it work reliably is&amp;hellip; interesting:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Handles unreliable Zigbee networks and flaky WiFi connections&lt;/li&gt;&#10;&lt;li&gt;Manages different light brands&amp;rsquo; quirks (looking at you, IKEA bulbs 👀)&lt;/li&gt;&#10;&lt;li&gt;Provides smooth transitions between color temperatures&lt;/li&gt;&#10;&lt;li&gt;Detects manual overrides without disrupting user control&lt;/li&gt;&#10;&lt;li&gt;Supports sleep modes for bedtime routines&lt;/li&gt;&#10;&lt;li&gt;Configurable via YAML or the UI (only integration to support this!)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The integration has 38 parameters to fine-tune your lighting. Try our &lt;a href="https://basnijholt.github.io/adaptive-lighting/" target="_blank" rel="noopener"&gt;WebAssembly simulator&lt;/a&gt; to visualize your settings!&lt;/p&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;2000+ GitHub stars&lt;/li&gt;&#10;&lt;li&gt;103 contributors&lt;/li&gt;&#10;&lt;li&gt;512 issues&lt;/li&gt;&#10;&lt;li&gt;Tens of thousands of active users&lt;/li&gt;&#10;&lt;li&gt;Available in 20+ languages&lt;/li&gt;&#10;&lt;li&gt;One of the most installed custom integrations&lt;/li&gt;&#10;&lt;li&gt;Featured in several YouTube videos and podcasts&lt;/li&gt;&#10;&lt;li&gt;Dedicated parameter visualization web app&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-challenges-we-solved"&gt;🎯 Challenges We Solved &lt;a class="anchor" href="#-challenges-we-solved" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Lights randomly turning on (bulbs falsely reporting state)&lt;/li&gt;&#10;&lt;li&gt;Network congestion (solved with Zigbee grouping)&lt;/li&gt;&#10;&lt;li&gt;Different brands interpreting colors differently&lt;/li&gt;&#10;&lt;li&gt;Handling manual overrides gracefully&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&amp;ldquo;It works on my setup&amp;rdquo; is just the beginning&lt;/li&gt;&#10;&lt;li&gt;Users surprise you (like using it for plants, or above the polar circle!)&lt;/li&gt;&#10;&lt;li&gt;Edge cases multiply quickly&lt;/li&gt;&#10;&lt;li&gt;Documentation is as important as code&lt;/li&gt;&#10;&lt;li&gt;Open source thrives on community effort&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Want your lights to follow the sun? Check out the &lt;a href="https://github.com/basnijholt/adaptive-lighting" target="_blank" rel="noopener"&gt;project on GitHub&lt;/a&gt; and try the &lt;a href="https://basnijholt.github.io/adaptive-lighting/" target="_blank" rel="noopener"&gt;simulator&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #HomeAssistant #SmartHome #IoT #Python&lt;/p&gt;&#10;</description></item><item><title>Calendar of Life 📅</title><link>https://www.nijho.lt/post/advent-of-open-source/01-calendar-of-life/</link><pubDate>Sun, 01 Dec 2024 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/advent-of-open-source/01-calendar-of-life/</guid><description>&lt;p&gt;(See my &lt;a href="../"&gt;intro post&lt;/a&gt;.)&lt;/p&gt;&#10;&lt;p&gt;Visualizing the finite weeks of our lives, because apparently, I needed another reminder of my mortality!&lt;/p&gt;&#10;&lt;h2 id="-origin-story"&gt;📖 Origin Story &lt;a class="anchor" href="#-origin-story" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;You know how sometimes you&amp;rsquo;re just casually browsing the internet, not looking to have an existential crisis? Well, that&amp;rsquo;s exactly what happened when I stumbled upon Tim Urban&amp;rsquo;s Wait But Why post &amp;ldquo;Your Life in Weeks&amp;rdquo; and saw Kurzgesagt&amp;rsquo;s &amp;ldquo;Calendar of Your Life&amp;rdquo; poster. Instead of just feeling mildly anxious about the finite nature of existence like a normal person, I decided to cope by writing some Python code. Because that&amp;rsquo;s what we do, right?&lt;/p&gt;&#10;&lt;h2 id="-technical-highlights"&gt;🔧 Technical Highlights &lt;a class="anchor" href="#-technical-highlights" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The project uses matplotlib to create a grid where:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Each row represents one year (up to 80 years, optimistically speaking!)&lt;/li&gt;&#10;&lt;li&gt;Each column represents one week (52 weeks per year)&lt;/li&gt;&#10;&lt;li&gt;Different colors show different life stages (early childhood, school, university, etc.)&lt;/li&gt;&#10;&lt;li&gt;The current week blinks in the animated version, just in case you needed a more urgent reminder of time passing&lt;/li&gt;&#10;&lt;li&gt;Supports both dark and light modes, because existential dread comes in all themes&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;One interesting technical detail is handling the fact that 52 weeks isn&amp;rsquo;t exactly one year. The code uses a small correction factor (&lt;code&gt;days_per_week = 365.25 / 52&lt;/code&gt;) to ensure the visualization stays accurate over many years. Yes, I made sure your existential crisis would be mathematically precise.&lt;/p&gt;&#10;&lt;h2 id="-impact"&gt;📊 Impact &lt;a class="anchor" href="#-impact" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;With its whopping 5 GitHub stars, this project clearly hasn&amp;rsquo;t taken the world by storm. But hey, sometimes the best tools are the ones we make for ourselves, right?&lt;/p&gt;&#10;&lt;h2 id="-challenges-and-solutions"&gt;🎯 Challenges and Solutions &lt;a class="anchor" href="#-challenges-and-solutions" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The main challenges were (this was actually a very simple one):&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Accurately mapping life events to week numbers (turns out life doesn&amp;rsquo;t fit neatly into boxes)&lt;/li&gt;&#10;&lt;li&gt;Creating smooth animations for the current week indicator (because a blinking dot really drives home the whole &amp;ldquo;time is passing&amp;rdquo; thing)&lt;/li&gt;&#10;&lt;li&gt;Handling the 52 vs 52.18 weeks per year discrepancy (thanks, Earth&amp;rsquo;s orbit)&lt;/li&gt;&#10;&lt;li&gt;Making the visualization work well in both dark and light modes (for viewing your life calendar at any time of day)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="-lessons-learned"&gt;💡 Lessons Learned &lt;a class="anchor" href="#-lessons-learned" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;This project taught me that sometimes the most impactful tools are the simplest ones. Sure, it&amp;rsquo;s just a bunch of dots on a grid, but each dot represents a week of life - past, present, or future. It&amp;rsquo;s a humbling reminder that while we can&amp;rsquo;t add more dots to our grid, we can try to make each one count.&lt;/p&gt;&#10;&lt;p&gt;Want to contemplate your own mortality in a more organized way? Check out the &lt;a href="https://github.com/basnijholt/calendar-of-life" target="_blank" rel="noopener"&gt;project on GitHub&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;#OpenSource #Python #DataVisualization #Programming&lt;/p&gt;&#10;</description></item></channel></rss>