<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged terminal</title>
<link>https://coderlegion.com/tag/terminal</link>
<description>Powered by Question2Answer</description>
<item>
<title>Keeping a Web-Backed SSH Terminal Alive Across SwiftUI Navigation</title>
<link>https://coderlegion.com/24308/keeping-a-web-backed-ssh-terminal-alive-across-swiftui-navigation</link>
<description>&lt;p&gt;A desktop SSH client can keep the remote connection alive and still give the user the impression that the terminal was reset. The failure often sits one layer above SSH: the UI destroys and rebuilds the terminal surface when the user switches to another section.&lt;/p&gt;
&lt;p&gt;That distinction matters when a macOS app embeds xterm.js in &lt;code&gt;WKWebView&lt;/code&gt;. The SSH process, terminal model, WebKit process, DOM, scrollback, focus chain, and visible geometry all have different lifetimes. Treating them as one SwiftUI view makes normal navigation surprisingly destructive.&lt;/p&gt;
&lt;p&gt;This article describes the design rules we ended up using in a native macOS SSH client.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://nexusshell.app/assets/img/screens/terminal.png&quot; alt=&quot;A native macOS SSH terminal with multiple sessions and a visible server workspace&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-rebuilding-is-not-a-harmless-refresh&quot;&gt;Why rebuilding is not a harmless refresh&lt;/h3&gt;
&lt;p&gt;A straightforward SwiftUI implementation conditionally creates the selected section:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if selectedSection == .terminal {
    TerminalSurface()
} else {
    FilesView()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the condition changes, SwiftUI is free to dismantle the terminal subtree. If that subtree owns a &lt;code&gt;WKWebView&lt;/code&gt;, the app can lose much more than a view object:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the WebContent process and xterm.js DOM may be destroyed;&lt;/li&gt;
&lt;li&gt;scrollback stored only in the browser process disappears;&lt;/li&gt;
&lt;li&gt;alternate-screen applications such as &lt;code&gt;vim&lt;/code&gt;, &lt;code&gt;nano&lt;/code&gt;, or &lt;code&gt;top&lt;/code&gt; cannot be reconstructed from plain text;&lt;/li&gt;
&lt;li&gt;restoring a bounded output snapshot may omit older history;&lt;/li&gt;
&lt;li&gt;WebKit initialization and snapshot replay add visible latency;&lt;/li&gt;
&lt;li&gt;focus and resize events can arrive in a different order after reconstruction.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keeping the SSH handle alive does not solve any of these UI-state losses. The remote PTY may still be healthy while the local terminal surface has forgotten how to represent it.&lt;/p&gt;
&lt;h3 id=&quot;separate-four-lifetimes&quot;&gt;Separate four lifetimes&lt;/h3&gt;
&lt;p&gt;The useful mental model is to separate at least four things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Logical session&lt;/strong&gt;: the tab, server identity, name, working directory, and user-facing state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transport session&lt;/strong&gt;: the SSH handle and remote PTY that carry input and output.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terminal renderer&lt;/strong&gt;: the &lt;code&gt;WKWebView&lt;/code&gt;, xterm.js DOM, parser state, and scrollback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visible attachment&lt;/strong&gt;: whether that renderer currently participates in the AppKit view hierarchy and responder chain.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Navigation should normally change only the fourth lifetime. Closing a tab can end all four. A network failure may replace the transport while retaining the logical tab and renderer. Conflating those transitions is where most of the surprising behavior begins.&lt;/p&gt;
&lt;h3 id=&quot;mount-once-then-deactivate&quot;&gt;Mount once, then deactivate&lt;/h3&gt;
&lt;p&gt;After the terminal section is opened for the first time, keep its SwiftUI subtree mounted. Other sections can still be created normally, while the terminal surface changes visibility and interaction state:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if terminalHasLoaded || selectedSection == .terminal {
    TerminalSurface(isActive: selectedSection == .terminal)
        .opacity(selectedSection == .terminal ? 1 : 0)
        .allowsHitTesting(selectedSection == .terminal)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This preserves the representable and its coordinator instead of asking SwiftUI to rebuild them on every sidebar click. The &lt;code&gt;isActive&lt;/code&gt; value also gives terminal-specific shortcuts, toolbars, and event monitors an explicit lifecycle. Hidden terminal controls should not continue handling keyboard shortcuts while the user is working in Files or Settings.&lt;/p&gt;
&lt;p&gt;Opacity alone, however, is not sufficient for a WebKit-backed terminal.&lt;/p&gt;
&lt;h3 id=&quot;preserve-the-wkwebview-object-not-necessarily-its-attachment&quot;&gt;Preserve the WKWebView object, not necessarily its attachment&lt;/h3&gt;
&lt;p&gt;On macOS, an embedded &lt;code&gt;WKWebView&lt;/code&gt; participates in AppKit focus, drag registration, and window behavior. Keeping it attached but transparent can leave side effects in sections that should have nothing to do with the terminal.&lt;/p&gt;
&lt;p&gt;A more reliable arrangement is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;let &lt;code&gt;NSViewRepresentable&lt;/code&gt; return a lightweight container;&lt;/li&gt;
&lt;li&gt;let its coordinator hold a strong reference to the &lt;code&gt;WKWebView&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;attach the web view to the container only while the terminal section is active;&lt;/li&gt;
&lt;li&gt;remove it from the hierarchy while inactive, without releasing the object;&lt;/li&gt;
&lt;li&gt;attach the same object again when the user returns.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The WebKit object, WebContent process, xterm.js DOM, parser state, and scrollback stay alive even while the view is temporarily detached. The rest of the app no longer has an invisible WebKit view interfering with hit testing or drag sessions.&lt;/p&gt;
&lt;p&gt;This is different from serializing terminal text and creating a new renderer. It preserves the renderer itself.&lt;/p&gt;
&lt;h3 id=&quot;hand-focus-back-before-detaching&quot;&gt;Hand focus back before detaching&lt;/h3&gt;
&lt;p&gt;Focus is a correctness issue for terminals, not just polish. If the window's first responder remains inside the web view during a section switch, later keystrokes can be delivered somewhere the user cannot see. In the worst case, a stray character is interpreted by the remote shell.&lt;/p&gt;
&lt;p&gt;Before detaching an active terminal web view:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check whether the window's first responder is the web view or one of its descendants.&lt;/li&gt;
&lt;li&gt;Explicitly clear or move the first responder.&lt;/li&gt;
&lt;li&gt;Remove the web view from its container.&lt;/li&gt;
&lt;li&gt;Disable terminal-only keyboard monitors and shortcut controls.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When reattaching, restore focus deliberately after the container is visible. Avoid assuming that SwiftUI's appearance callback and AppKit's responder-chain update happen in the same frame.&lt;/p&gt;
&lt;h3 id=&quot;freeze-hidden-geometry&quot;&gt;Freeze hidden geometry&lt;/h3&gt;
&lt;p&gt;A hidden but mounted view can still receive geometry changes. If its width follows every animation in the visible section, xterm.js may repeatedly run &lt;code&gt;fit()&lt;/code&gt;, and each result may resize the remote PTY.&lt;/p&gt;
&lt;p&gt;That creates a subtle class of bugs: opening a file-transfer panel or animating another page changes the dimensions of a terminal the user is not looking at. Full-screen terminal programs then redraw against &quot;ghost&quot; sizes.&lt;/p&gt;
&lt;p&gt;Record the terminal's last active width and freeze the inactive container at that width. On reattachment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;align the web view to the visible container once;&lt;/li&gt;
&lt;li&gt;let the renderer's resize observer compute the new rows and columns;&lt;/li&gt;
&lt;li&gt;send one meaningful PTY resize instead of a frame-by-frame storm.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The same rule applies to side panels: reserve their width only in the section that owns them, not at a shared ancestor that also sizes the hidden terminal.&lt;/p&gt;
&lt;h3 id=&quot;keep-output-flowing-without-flooding-the-ui&quot;&gt;Keep output flowing without flooding the UI&lt;/h3&gt;
&lt;p&gt;Detaching the renderer should not pause the SSH transport. Output can continue updating the logical session and renderer while the user visits another section.&lt;/p&gt;
&lt;p&gt;High-volume PTY output needs its own boundary. Read callbacks can arrive hundreds of times per second, while sending every chunk through the main thread and WebKit IPC creates unnecessary contention. A practical policy is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;deliver sparse output immediately so typing echo feels instant;&lt;/li&gt;
&lt;li&gt;merge continuous bursts into roughly one flush per display frame;&lt;/li&gt;
&lt;li&gt;update the bounded recovery buffer before notifying the renderer;&lt;/li&gt;
&lt;li&gt;keep session logs on a separate path from visual replay.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The recovery buffer remains useful if WebKit really crashes, but it is no longer the normal navigation mechanism.&lt;/p&gt;
&lt;h3 id=&quot;test-transitions-not-just-steady-states&quot;&gt;Test transitions, not just steady states&lt;/h3&gt;
&lt;p&gt;The most valuable tests are sequences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;start &lt;code&gt;top&lt;/code&gt;, switch to Files, resize the window, and return;&lt;/li&gt;
&lt;li&gt;scroll far back, visit Settings, and confirm the same scrollback remains;&lt;/li&gt;
&lt;li&gt;focus the terminal, switch sections, type immediately, and verify nothing reaches SSH;&lt;/li&gt;
&lt;li&gt;open and close a side panel while the terminal is hidden;&lt;/li&gt;
&lt;li&gt;disconnect while hidden, then return and exercise the reconnect path;&lt;/li&gt;
&lt;li&gt;close a terminal tab while its web view is detached and confirm observers and script handlers are removed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These tests cross SwiftUI, AppKit, WebKit, and SSH boundaries. A unit test around one layer will not reveal the ordering failures between them.&lt;/p&gt;
&lt;h3 id=&quot;the-broader-rule&quot;&gt;The broader rule&lt;/h3&gt;
&lt;p&gt;View visibility and resource lifetime are not the same thing. A terminal is a long-lived interactive resource with a UI surface, not a disposable page. Once the logical session, transport, renderer, and visible attachment have separate ownership, navigation becomes predictable: switching sections changes what the user sees without quietly rebuilding the tool they were using.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Developer disclosure:&lt;/strong&gt; I work on &lt;a rel=&quot;nofollow&quot; href=&quot;https://nexusshell.app/?utm_source=coderlegion&amp;amp;utm_medium=technical_article&amp;amp;utm_campaign=terminal_view_lifetime&quot;&gt;Nexus Shell&lt;/a&gt;, a native macOS SSH workspace. This article is based on implementation and debugging lessons from that work, not a benchmark against other clients.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Editorial note:&lt;/strong&gt; This draft was prepared with AI assistance and reviewed against the current SwiftUI, AppKit, WebKit, and SSH implementation before publication.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24308/keeping-a-web-backed-ssh-terminal-alive-across-swiftui-navigation</guid>
<pubDate>Sun, 09 Aug 2026 04:53:28 +0000</pubDate>
</item>
<item>
<title>I tried to vibe code on Windows. It broke me. So I built my own terminal.</title>
<link>https://coderlegion.com/17656/i-tried-to-vibe-code-on-windows-it-broke-me-so-i-built-my-own-terminal</link>
<description>&lt;p&gt;Every AI coding setup I saw online looked the same: Mac, tmux, agents running in split panes, everything humming along perfectly.&lt;/p&gt;
&lt;p&gt;I was on Windows. It did not hum.&lt;/p&gt;
&lt;h3 id=&quot;the-tmux-wall&quot;&gt;The tmux wall&lt;/h3&gt;
&lt;p&gt;tmux is the backbone of serious AI agent workflows. Persistent sessions, split panes, detach and reattach while your agents keep running.&lt;/p&gt;
&lt;p&gt;On Windows, native tmux doesn't exist.&lt;/p&gt;
&lt;p&gt;Fine — I'll use WSL.&lt;/p&gt;
&lt;h3 id=&quot;wsl-close-enough-to-break-everything&quot;&gt;WSL: close enough to break everything&lt;/h3&gt;
&lt;p&gt;WSL feels like Linux. For about 20 minutes, you think you've solved the problem.&lt;/p&gt;
&lt;p&gt;Then the path issues start.&lt;/p&gt;
&lt;p&gt;Windows sees: &lt;code&gt;C:\Users\wooyoung\projects\wmux&lt;/code&gt;&lt;br&gt;
WSL sees: &lt;code&gt;/mnt/c/Users/wooyoung/projects/wmux&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Same folder. Completely different contexts. AI agents write files into WSL. Your Windows-side editor can't find them. Your git is on the Windows side. Your dev server is on the Windows side. Your agent is happily working in a parallel universe.&lt;/p&gt;
&lt;p&gt;The worst part: it works — until it doesn't. You're 40 minutes into a session before realizing your agent has been operating in the wrong dimension the entire time.&lt;/p&gt;
&lt;p&gt;Port forwarding breaks hot reload. The agent hits localhost:3000, gets nothing, concludes the code is broken, and starts &quot;fixing&quot; code that was never broken.&lt;/p&gt;
&lt;p&gt;You end up debugging a WSL networking quirk disguised as a React error.&lt;/p&gt;
&lt;h3 id=&quot;multi-agent-made-it-dramatically-worse&quot;&gt;Multi-agent made it dramatically worse&lt;/h3&gt;
&lt;p&gt;One agent is manageable. But real vibe coding in 2025 means an orchestrator, subagents, parallel tasks.&lt;/p&gt;
&lt;p&gt;My setup: 6 terminal windows, 3 WSL instances, no way to tell which agent was doing what. One agent had gone completely silent — was it done? Crashed? Thinking?&lt;/p&gt;
&lt;p&gt;I kept a Notepad file open just to track which terminal was which.&lt;/p&gt;
&lt;p&gt;Then one Tuesday evening, I had three agents running in parallel. I wanted to check on one of them. Found it after clicking through four windows. It had been waiting for my input for 45 minutes. The other two had moved on — and built conflicting code against each other.&lt;/p&gt;
&lt;p&gt;I closed everything. Went to bed. The next morning I started writing a PRD for Wmux.&lt;/p&gt;
&lt;h3 id=&quot;what-i-built&quot;&gt;What I built&lt;/h3&gt;
&lt;p&gt;Wmux is a Windows-native AI agent terminal. The problems I kept hitting are treated as first-class design constraints, not edge cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No WSL dependency.&lt;/strong&gt; One filesystem context. No boundary crossings, no silent failures from agents writing to the wrong place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;All agents in one view.&lt;/strong&gt; Every session visible in a single window. Status, last output, whether it's waiting — at a glance. Ctrl+D to split, Ctrl+N for a new workspace.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Browser automation built in.&lt;/strong&gt; Claude Code can open a browser, click, fill forms, take screenshots — all via CDP. Not a workaround. A first-class feature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Notifications that actually work.&lt;/strong&gt; Output throughput-based detection: when your agent finishes, you get a desktop notification. No more staring at a terminal wondering if it's done.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Session persistence.&lt;/strong&gt; Close Wmux, reopen it — your sessions are still there, scrollback intact. Like tmux. On Windows.&lt;/p&gt;
&lt;h3 id=&quot;why-this-matters&quot;&gt;Why this matters&lt;/h3&gt;
&lt;p&gt;The corporate world runs Windows. The non-CS engineers I'm trying to get into AI coding run Windows. The developers who are just now discovering multi-agent workflows — a lot of them are on Windows.&lt;/p&gt;
&lt;p&gt;The tooling just hasn't caught up. Mac users have cmux. Windows users have... friction.&lt;/p&gt;
&lt;p&gt;If you want real multi-agent AI coding on Windows today, you either accept constant pain, or you build the environment yourself.&lt;/p&gt;
&lt;p&gt;I chose to build.&lt;/p&gt;
&lt;p&gt;v2.2.0 is out. Install in 30 seconds:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;irm https://raw.githubusercontent.com/openwong2kim/wmux/main/install.ps1 | iex
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Follow the build: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/openwong2kim/wmux&quot;&gt;github.com/openwong2kim/wmux&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previously: From engineering floor to App Store&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Next: I deployed AI to 200 factory engineers. Here's what actually happened.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17656/i-tried-to-vibe-code-on-windows-it-broke-me-so-i-built-my-own-terminal</guid>
<pubDate>Tue, 12 May 2026 23:49:53 +0000</pubDate>
</item>
<item>
<title>&quot;Why .NET 9’s CLI is the Glow-up your workflow deserved&quot;</title>
<link>https://coderlegion.com/16699/why-net-9s-cli-is-the-glow-up-your-workflow-deserved</link>
<description>&lt;p&gt;&lt;strong&gt;&quot;From Boring White Text to a Dev Experience That Actually Doesn’t Hurt to Look At&quot;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s be real: for years, the .NET CLI felt like that one reliable but incredibly dull friend. It got the job done, but it had the personality of a spreadsheet. If you’re jumping from .NET 7 or 8, you’re in for a shock. Microsoft finally realized that we spend 90% of our lives staring at a terminal, so they decided to make it look—and feel—like it belongs in 2026.&lt;/p&gt;
&lt;p&gt;Here is why you, as a Senior Dev, should actually care about the .NET 9 CLI.&lt;/p&gt;
&lt;h3 id=&quot;1-the-visual-soul-goodbye-walls-of-text&quot;&gt;1. The &quot;Visual Soul&quot;: Goodbye, Walls of Text&lt;/h3&gt;
&lt;p&gt;The first thing you’ll notice isn’t a new feature; it’s the Terminal Logger. While it started appearing in .NET 8, in .NET 9 it’s the default, and it is polished.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Progress Bars that actually move: No more guessing if dotnet build is hanging or just thinking.&lt;/li&gt;
&lt;li&gt;Clickable Links: Errors now come with direct, clickable links to documentation. &lt;/li&gt;
&lt;li&gt;Color-Coded Chaos: Warnings are yellow, errors are red, and successes&lt;br&gt;
are a satisfying green. It sounds minor, but in a massive&lt;br&gt;
microservices build, your eyes will thank you.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depending on your terminal (like Windows Terminal or Oh My Posh), you’ll see icons for different project types and build stages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; Use dotnet build --tl (Terminal Logger) explicitly if you’re on a legacy system, but on .NET 9, it’s just there.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=8167650038085573077&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-automation-amp-buildchecks-the-senior-s-safety-net&quot;&gt;2. Automation &amp;amp; &quot;BuildChecks&quot;: The Senior’s Safety Net&lt;/h3&gt;
&lt;p&gt;This is where it gets interesting for those of us managing large codebases. .NET 9 introduces BuildChecks.&lt;/p&gt;
&lt;p&gt;Imagine having a linter, but for your .csproj and build logic.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What it does: It analyzes your build process for regressions or &quot;smelly&quot; configurations.&lt;/li&gt;
&lt;li&gt;The Command: dotnet build /check&lt;/li&gt;
&lt;li&gt;Why it’s useful: It catches things like &quot;Custom task X is slowing&lt;br&gt;
down the build&quot; or &quot;You’re using a deprecated MSBuild property.&quot; It’s&lt;br&gt;
basically a code review for your infrastructure.&lt;/li&gt;
&lt;li&gt;Git Integration: The CLI is Aware&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The .NET 9 CLI is now much more &quot;Git-aware.&quot; When you run dotnet new, the SDK automatically detects if you're in a Git repo and can initialize .gitignore files that are actually updated for modern .NET workloads (including stuff for DevContainers and GitHub Actions).&lt;/p&gt;
&lt;p&gt;Also, the dotnet workload history command is a lifesaver. If a teammate says, &quot;It works on my machine,&quot; you can now check exactly what version of the workloads (MAUI, Aspire, etc.) were installed and when they were updated, making environment parity much easier to debug.&lt;/p&gt;
&lt;h3 id=&quot;4-cloud-native-amp-docker-the-no-dockerfile-revolution&quot;&gt;4. Cloud-Native &amp;amp; Docker: The &quot;No-Dockerfile&quot; Revolution&lt;/h3&gt;
&lt;p&gt;You’re a Senior. You know how to write a Dockerfile. But do you want to?&lt;/p&gt;
&lt;p&gt;.NET 9 pushes SDK-based Containerization to the limit. You can now publish a fully optimized, secured, and shrunk Docker image without ever touching a Dockerfile.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet publish /t:PublishContainer -c Release
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What’s new here?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Non-root by default: Security is baked in. Images run as a non-privileged user automatically.&lt;/li&gt;
&lt;li&gt;Insecure Registries: The CLI now supports pushing to insecure local&lt;br&gt;
registries (great for local K8s testing) without hacking your Docker&lt;br&gt;
config. &lt;/li&gt;
&lt;li&gt;Architecture targeting: Easily cross-compile for ARM64 (Graviton/M1) from an x64 machine using just CLI flags.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=7471657151849509597&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;-5-azure-amp-aspire-the-secret-sauce-&quot;&gt; 5. Azure &amp;amp; Aspire: The &quot;Secret Sauce&quot;&lt;/h3&gt;
&lt;p&gt;If you aren't using .NET Aspire yet, .NET 9 is the sign you were waiting for. The CLI integration with Azure via the azd (Azure Dev CLI) and dotnet aspire commands is seamless.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Smart Provisioning: The CLI can now detect your resource requirements from your code and suggest the right Azure infrastructure. &lt;/li&gt;
&lt;li&gt;Deployment: azd up isn't just a gimmick anymore; it uses the .NET 9&lt;br&gt;
SDK to package your apps into container apps with OpenTelemetry&lt;br&gt;
pre-configured.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;6-the-advanced-power-user-commands&quot;&gt;6. The Advanced &quot;Power User&quot; Commands&lt;/h3&gt;
&lt;p&gt;For the automation nerds, these are the commands you’ll be scripting into your CI/CD pipelines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet test --parallel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now fully integrated with MSBuild for parallel execution across different Target Frameworks (TFM).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet workload sets
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Allows you to &quot;lock&quot; a version of the SDK workloads so your whole team stays on the exact same bits.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet tool update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Faster, cleaner, and handles global vs. local tools with better conflict resolution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What do I think about this?&lt;/strong&gt;&lt;br&gt;
.NET 9 isn't just about a faster JIT (though it is faster). It's about Developer Joy. The CLI has moved away from being a &quot;black box of text&quot; to a proactive assistant that helps you build, containerize, and deploy without leaving the keyboard.&lt;/p&gt;
&lt;p&gt;If you’re still on .NET 7, you’re essentially working in black and white while the rest of us are in 4K. Time to upgrade.&lt;/p&gt;
&lt;p&gt;By One Garlos P. 2026&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16699/why-net-9s-cli-is-the-glow-up-your-workflow-deserved</guid>
<pubDate>Tue, 05 May 2026 03:26:28 +0000</pubDate>
</item>
<item>
<title>Homebrew: el gestor de paquetes que todo usuario de Mac debería conocer desde el día uno</title>
<link>https://coderlegion.com/14720/homebrew-el-gestor-de-paquetes-que-todo-usuario-de-mac-deberia-conocer-desde-el-dia-uno</link>
<description>&lt;hr&gt;
&lt;p&gt;title: Homebrew: el gestor de paquetes que todo usuario de Mac debería conocer desde el día uno&lt;br&gt;
published: true&lt;/p&gt;
&lt;h3 id=&quot;tags-spanish-macos-terminal-productivity&quot;&gt;tags: spanish, macos, terminal, productivity&lt;/h3&gt;
&lt;p&gt;Hay una herramienta que debería venir preinstalada en cada Mac que sale de fábrica.&lt;/p&gt;
&lt;p&gt;No viene. Y nadie te avisa que existe hasta que llevas meses instalando software de la manera más lenta y tediosa posible.&lt;/p&gt;
&lt;p&gt;Se llama Homebrew. Y cuando la descubres tienes exactamente dos reacciones: &lt;em&gt;&quot;esto es increíble&quot;&lt;/em&gt; seguido inmediatamente de &lt;em&gt;&quot;¿por qué nadie me dijo esto antes?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;-qu-es-homebrew-&quot;&gt;¿Qué es Homebrew?&lt;/h3&gt;
&lt;p&gt;Es un gestor de paquetes para Mac. En lugar de entrar a un sitio web, descargar el instalador, hacer clic en &quot;siguiente, siguiente, siguiente, aceptar&quot; y arrastrar el ícono a Aplicaciones — todo ese proceso se convierte en esto:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install nombre-del-programa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Un comando. El software queda instalado y listo.&lt;/p&gt;
&lt;h3 id=&quot;c-mo-instalarlo&quot;&gt;Cómo instalarlo&lt;/h3&gt;
&lt;p&gt;Abre la terminal y copia esto:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verifica que quedó bien:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew --version
# Homebrew 4.x.x
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;los-comandos-que-vas-a-usar-el-90-del-tiempo&quot;&gt;Los comandos que vas a usar el 90% del tiempo&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Instalar herramienta de terminal
brew install git

# Instalar app con interfaz gráfica
brew install --cask iterm2

# Actualizar todo
brew update &amp;amp;&amp;amp; brew upgrade

# Ver qué tienes instalado
brew list

# Desinstalar
brew uninstall nombre-del-paquete

# Limpiar archivos temporales
brew cleanup
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;el-tip-que-cambia-todo-brewfile&quot;&gt;El tip que cambia todo — Brewfile&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Guarda toda tu lista de paquetes
brew bundle dump

# En un Mac nuevo, instala todo de una vez
brew bundle install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configuras un Mac nuevo en 10 minutos en lugar de un día entero. Eso solo ya justifica instalarlo hoy.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt; Artículo completo con todos los detalles, la lista de apps que instalo en cada Mac nuevo y cómo conectar Homebrew con tus dotfiles en &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.commandcat.dev/homebrew-mac-guia/&quot;&gt;CommandCat&lt;/a&gt;&lt;/strong&gt; — contenido tech en español sobre macOS, Linux, productividad y cultura geek. &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14720/homebrew-el-gestor-de-paquetes-que-todo-usuario-de-mac-deberia-conocer-desde-el-dia-uno</guid>
<pubDate>Tue, 14 Apr 2026 03:27:36 +0000</pubDate>
</item>
<item>
<title>Writing Bootable USB Drives with dd: Precision Over Convenience</title>
<link>https://coderlegion.com/10878/writing-bootable-usb-drives-with-dd-precision-over-convenience</link>
<description>&lt;p&gt;Graphical tools like Rufus or Balena Etcher are convenient and widely used. However, when working in constrained environments, rescue scenarios, or legacy hardware, abstractions can hide critical details.&lt;/p&gt;
&lt;p&gt;This article explains how to safely create bootable USB drives using the &lt;code&gt;dd&lt;/code&gt; command, focusing on correctness, transparency, and risk mitigation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=17051193713375697733&quot; alt=&quot;GNU dd - Official Documentation&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;a-serious-warning-dd-is-not-forgiving&quot;&gt;A Serious Warning: dd Is Not Forgiving&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;dd&lt;/code&gt; command writes data byte-for-byte to a target device. A single mistake in the output path can irreversibly destroy your primary disk.&lt;/p&gt;
&lt;p&gt;Before proceeding:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Disconnect unnecessary storage devices&lt;/li&gt;
&lt;li&gt;Identify the correct USB device twice&lt;/li&gt;
&lt;li&gt;Never guess the device path&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;step-by-step-iso-to-bootable-usb&quot;&gt;Step-by-Step: ISO to Bootable USB&lt;/h3&gt;
&lt;h4 id=&quot;1-identify-the-usb-device&quot;&gt;1. Identify the USB device&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lsblk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look for removable devices (&lt;code&gt;RM=1&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Confirm using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dmesg | tail -20
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-unmount-existing-partitions&quot;&gt;2. Unmount existing partitions&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo umount /dev/sdX* 2&amp;gt;/dev/null || true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-write-the-iso-using-dd&quot;&gt;3. Write the ISO using dd&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo dd if=debian-13.3.0-amd64-netinst.iso \
        of=/dev/sdX \
        bs=4M \
        status=progress \
        conv=fdatasync \
        oflag=direct
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key options explained:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bs=4M&lt;/code&gt;: improves throughput&lt;/li&gt;
&lt;li&gt;&lt;code&gt;status=progress&lt;/code&gt;: shows real-time progress&lt;/li&gt;
&lt;li&gt;&lt;code&gt;conv=fdatasync&lt;/code&gt;: ensures physical writes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oflag=direct&lt;/code&gt;: bypasses page cache&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;4-validate-the-result-optional-but-useful-&quot;&gt;4. Validate the result (optional but useful)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo fdisk -l /dev/sdX
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For deeper inspection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo dd if=/dev/sdX bs=512 count=1 | hexdump -C | head -1
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;5-safely-eject&quot;&gt;5. Safely eject&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sync
sudo eject /dev/sdX
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;why-dd-instead-of-gui-tools-&quot;&gt;Why dd Instead of GUI Tools?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dd&lt;/code&gt; excels when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Running in headless or server environments&lt;/li&gt;
&lt;li&gt;Automating workflows&lt;/li&gt;
&lt;li&gt;Debugging boot failures&lt;/li&gt;
&lt;li&gt;You need full visibility into the write process&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;common-mistakes-and-prevention&quot;&gt;Common Mistakes and Prevention&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Mistake &lt;/th&gt;&lt;th&gt;Consequence &lt;/th&gt;&lt;th&gt;Prevention&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Using &lt;code&gt;/dev/sdX1&lt;/code&gt; instead of &lt;code&gt;/dev/sdX&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Non-bootable USB &lt;/td&gt;&lt;td&gt;Always target the raw device&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Forgetting sudo &lt;/td&gt;&lt;td&gt;Permission denied &lt;/td&gt;&lt;td&gt;dd requires block-level access&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Omitting fdatasync &lt;/td&gt;&lt;td&gt;Incomplete writes &lt;/td&gt;&lt;td&gt;Always include it&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Writing to &lt;code&gt;/dev/sda&lt;/code&gt; by mistake &lt;/td&gt;&lt;td&gt;Total data loss &lt;/td&gt;&lt;td&gt;Verify with lsblk and dmesg&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;a-safer-wrapper-script&quot;&gt;A Safer Wrapper Script&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
ISO=&quot;$1&quot;
DEVICE=&quot;$2&quot;

if [ $# -ne 2 ] || [ ! -f &quot;$ISO&quot; ] || [ ! -b &quot;$DEVICE&quot; ]; then
  echo &quot;Usage: $0 &amp;lt;file.iso&amp;gt; &amp;lt;device&amp;gt;&quot;
  lsblk -d -o NAME,SIZE,MODEL
  exit 1
fi

echo &quot;WARNING: This will overwrite $DEVICE&quot;
read -p &quot;Type YES to continue: &quot; CONFIRM

[ &quot;$CONFIRM&quot; = &quot;YES&quot; ] || exit 1

sudo dd if=&quot;$ISO&quot; of=&quot;$DEVICE&quot; bs=4M status=progress conv=fdatasync oflag=direct
sync
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dd&lt;/code&gt; is not just a command — it is a reminder that low-level control comes with responsibility.&lt;/p&gt;
&lt;p&gt;When used correctly, it offers unmatched reliability and transparency, especially in real-world Linux scenarios where graphical tools fall short.&lt;/p&gt;
&lt;p&gt;Use it carefully, verify everything twice, and it will never let you down.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Debian:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.debian.org/releases/stable/amd64/&quot;&gt;https://www.debian.org/releases/stable/amd64/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GNU dd:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.gnu.org/software/coreutils/manual/html_node/dd-invocation.html&quot;&gt;https://www.gnu.org/software/coreutils/manual/html_node/dd-invocation.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Arch Wiki (dd):&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://wiki.archlinux.org/title/Dd&quot;&gt;https://wiki.archlinux.org/title/Dd&lt;/a&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/10878/writing-bootable-usb-drives-with-dd-precision-over-convenience</guid>
<pubDate>Sun, 01 Feb 2026 17:07:13 +0000</pubDate>
</item>
<item>
<title>Send emails from the terminal using pop</title>
<link>https://coderlegion.com/8712/send-emails-from-the-terminal-using-pop</link>
<description>&lt;h2 id=&quot;multi-account-email-sender-pop-wrapper-&quot;&gt;Multi-Account Email Sender (pop wrapper)&lt;/h2&gt;
&lt;p&gt;This script wraps Charm's &lt;code&gt;pop&lt;/code&gt; to send emails from &lt;strong&gt;multiple accounts&lt;/strong&gt; directly from your terminal &lt;strong&gt;without config edits&lt;/strong&gt;. Select your email, then use pop's interactive UI or CLI flags.&lt;/p&gt;
&lt;h3 id=&quot;features&quot;&gt;Features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Switch between multiple SMTP accounts via &lt;code&gt;gum&lt;/code&gt; (or fallback menu)&lt;/li&gt;
&lt;li&gt;Secure secrets from &lt;code&gt;~/.zsh_secrets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Exports &lt;code&gt;POP_SMTP_USERNAME&lt;/code&gt;, &lt;code&gt;POP_SMTP_PASSWORD&lt;/code&gt;, &lt;code&gt;POP_FROM&lt;/code&gt; automatically&lt;/li&gt;
&lt;li&gt;Passes all &lt;code&gt;pop&lt;/code&gt; args through&lt;/li&gt;
&lt;li&gt;Shellcheck clean, bash-compatible&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;environment-variables&quot;&gt;Environment Variables&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;In &lt;code&gt;~/.zshrc&lt;/code&gt; (non-sensitive):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export POP_SMTP_HOST=&quot;smtp.gmail.com&quot; # or smtp.outlook.com, etc.
export POP_SMTP_PORT=587
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;In &lt;code&gt;~/.zsh_secrets&lt;/code&gt; (chmod 600, gitignore!):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export POP_SMTP_USERNAME1=&quot;*Emails are not allowed*&quot;
export POP_SMTP_PASSWORD1=&quot;abcd efgh ijkl mnop&quot; # Gmail/Outlook APP PASSWORD
export POP_SMTP_USERNAME2=&quot;*Emails are not allowed*&quot;
export POP_SMTP_PASSWORD2=&quot;ijkl mnop qrst uvwx&quot; # APP PASSWORD (16 chars, no spaces)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;⚠️ &lt;code&gt;POP_SMTP_PASSWORD&lt;/code&gt;s are app-specific passwords&lt;/strong&gt; (not your regular login)—generate from Gmail/Outlook 2FA security settings.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;dependencies&quot;&gt;Dependencies&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/charmbracelet/pop&quot;&gt;pop&lt;/a&gt; (Charm)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/charmbracelet/gum&quot;&gt;gum&lt;/a&gt; &lt;em&gt;(optional, menu fallback built-in)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;usage&quot;&gt;Usage&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chmod +x ~/.local/bin/send_email
send_email # Interactive pop
send_email --to *Emails are not allowed* --subject &quot;Hi&quot; --body &quot;Test&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Here is the link to the gist&lt;/strong&gt; &lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://gist.github.com/Vaishnav-Sabari-Girish/51cbfc493fee25c209c39f0e74a17103&quot;&gt;https://gist.github.com/Vaishnav-Sabari-Girish/51cbfc493fee25c209c39f0e74a17103&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Save your dotfiles workflow! &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/8712/send-emails-from-the-terminal-using-pop</guid>
<pubDate>Mon, 22 Dec 2025 07:27:15 +0000</pubDate>
</item>
</channel>
</rss>