<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>tools on Saurabh Kumar</title><link>https://saurabh-kumar.com/tags/tools/</link><description>Recent content in tools on Saurabh Kumar</description><generator>Hugo</generator><language>en-US</language><copyright>Copyright © 2025, Saurabh Kumar.</copyright><lastBuildDate>Wed, 21 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://saurabh-kumar.com/tags/tools/index.xml" rel="self" type="application/rss+xml"/><item><title>Default Apps 2026</title><link>https://saurabh-kumar.com/articles/2026/10/default-apps-2026/</link><pubDate>Wed, 21 Oct 2026 00:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/articles/2026/10/default-apps-2026/</guid><description>&lt;p&gt;Apps I&amp;rsquo;ve been using regularly this year. This is my second year doing this.&#10;Here is &lt;a href="https://saurabh-kumar.com/articles/2025/11/default-apps-2025/"&gt;last year&amp;rsquo;s list&lt;/a&gt;. Inspired by&#10;&lt;a href="https://micro.webology.dev/2025/10/29/default-apps-here-are-my/" rel="noopener external"&gt;Jeff Triplett&lt;/a&gt;&#10;and catalogued by Robb Knight&amp;rsquo;s &lt;a href="https://defaults.rknight.me/" rel="noopener external"&gt;App Defaults&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="what-changed-since-2025"&gt;What changed since 2025&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;AI took over a big chunk of my day.&lt;/strong&gt; &lt;a href="https://pi.dev/" rel="noopener external"&gt;Pi&lt;/a&gt; is my core harness, I live in&#10;&lt;a href="https://pi-web.dev/" rel="noopener external"&gt;Pi Web&lt;/a&gt;, and I&amp;rsquo;m trying &lt;a href="https://tannermidd.github.io/pi-pocket/" rel="noopener external"&gt;Pi Pocket&lt;/a&gt; on the side. Agents now do most of my&#10;version control too.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Arc → &lt;a href="https://zen-browser.app/" rel="noopener external"&gt;Zen&lt;/a&gt;&lt;/strong&gt; for work. Close to Arc, well maintained, no feature bloat.&#10;Safari stays for personal browsing.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;iTerm2 → &lt;a href="https://ghostty.org/" rel="noopener external"&gt;Ghostty&lt;/a&gt;&lt;/strong&gt;, a thin layer over &lt;a href="https://herdr.dev/" rel="noopener external"&gt;herdr&lt;/a&gt;, a multiplexer built for AI&#10;agents. Setup in my &lt;a href="https://github.com/theskumar/dotfiles" rel="noopener external"&gt;dotfiles&lt;/a&gt;: &lt;a href="https://github.com/theskumar/dotfiles/blob/master/ghostty/ghostty/config" rel="noopener external"&gt;Ghostty config&lt;/a&gt;, &lt;a href="https://github.com/theskumar/dotfiles/blob/master/herdr/herdr/config.toml" rel="noopener external"&gt;herdr config&lt;/a&gt;, and&#10;&lt;a href="https://github.com/theskumar/dotfiles/blob/master/docs/HERDR.md" rel="noopener external"&gt;notes on how I use herdr&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Raycast → &lt;a href="https://github.com/abue-ammar/tinycast" rel="noopener external"&gt;Tinycast&lt;/a&gt;&lt;/strong&gt;, a tiny native launcher. Alfred is gone too.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Cursor is out.&lt;/strong&gt; Mostly &lt;a href="https://zed.dev/" rel="noopener external"&gt;Zed&lt;/a&gt; now, with its AI turned off.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Edison Mail → Apple Mail&lt;/strong&gt;, plus Gmail on the web when I need it.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Photoshop and Lightroom → &lt;a href="https://www.darktable.org/" rel="noopener external"&gt;darktable&lt;/a&gt;&lt;/strong&gt; and GIMP.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Pages → Google Docs.&lt;/strong&gt; I hardly open Pages anymore.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Perplexity → &lt;a href="https://news.ycombinator.com/" rel="noopener external"&gt;Hacker News&lt;/a&gt; and &lt;a href="https://newsboat.org/" rel="noopener external"&gt;Newsboat&lt;/a&gt;&lt;/strong&gt;,&#10;next to NetNewsWire.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Raindrop.io is gone.&lt;/strong&gt; My &lt;a href="https://saurabh-kumar.com/til/"&gt;TIL&lt;/a&gt; and personal wiki replaced bookmarks.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;New:&lt;/strong&gt; a tiling window manager (&lt;a href="https://omniwm.app/" rel="noopener external"&gt;OmniWM&lt;/a&gt;), dictation (&lt;a href="https://wisprflow.ai/" rel="noopener external"&gt;Wispr Flow&lt;/a&gt;), and&#10;&lt;a href="https://tailscale.com/" rel="noopener external"&gt;Tailscale&lt;/a&gt; in place of Zscaler.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;More small Mac utilities:&lt;/strong&gt; Mole for cleanup, UsagePal for AI limits, Thaw,&#10;Amphetamine, and AlDente.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Todoist joined&lt;/strong&gt; and jj joined lazygit. Bullet Journal use dropped a bit.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="the-list"&gt;The list&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Mail Client&lt;/strong&gt;: Apple Mail, Gmail in browser&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Notes&lt;/strong&gt;: &lt;a href="https://obsidian.md" rel="noopener external"&gt;Obsidian&lt;/a&gt;, &lt;a href="https://simplenote.com/" rel="noopener external"&gt;Simplenote&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;To-Do&lt;/strong&gt;: &lt;a href="https://todoist.com/" rel="noopener external"&gt;Todoist&lt;/a&gt;, &lt;a href="https://bulletjournal.com/" rel="noopener external"&gt;Bullet Journal&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Photo Shooting&lt;/strong&gt;: iPhone, Canon 6D&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Photo Editing&lt;/strong&gt;: &lt;a href="https://www.darktable.org/" rel="noopener external"&gt;darktable&lt;/a&gt;, &lt;a href="https://www.gimp.org/" rel="noopener external"&gt;GIMP&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Calendar&lt;/strong&gt;: &lt;a href="https://www.notion.com/product/calendar" rel="noopener external"&gt;Notion Calendar&lt;/a&gt;, Google Calendar on the web&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Cloud File Storage&lt;/strong&gt;: iCloud + Google Drive&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;RSS&lt;/strong&gt;: &lt;a href="https://netnewswire.com/" rel="noopener external"&gt;NetNewsWire&lt;/a&gt;, &lt;a href="https://newsboat.org/" rel="noopener external"&gt;Newsboat&lt;/a&gt; in the terminal&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Contacts&lt;/strong&gt;: Apple Contacts &amp;amp; Google Contacts&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Browser&lt;/strong&gt;: &lt;a href="https://zen-browser.app/" rel="noopener external"&gt;Zen&lt;/a&gt; for work, Safari for personal&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Chat&lt;/strong&gt;: Apple Messages, Slack, Discord, Signal, WhatsApp, Telegram&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Bookmarks&lt;/strong&gt;: none. My &lt;a href="https://saurabh-kumar.com/til/"&gt;TIL&lt;/a&gt; and personal wiki replaced them&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Read It Later&lt;/strong&gt;: Safari Reading List&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Word Processing&lt;/strong&gt;: Google Docs&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Spreadsheets&lt;/strong&gt;: Google Sheets&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Presentations&lt;/strong&gt;: Figma&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Shopping Lists&lt;/strong&gt;: Amazon&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Budgeting and Personal Finance&lt;/strong&gt;: &lt;a href="https://paisa.fyi/" rel="noopener external"&gt;paisa&lt;/a&gt;, with &lt;a href="https://ledger-cli.org/" rel="noopener external"&gt;ledger&lt;/a&gt; underneath&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;News&lt;/strong&gt;: &lt;a href="https://netnewswire.com/" rel="noopener external"&gt;NetNewsWire&lt;/a&gt;, &lt;a href="https://newsboat.org/" rel="noopener external"&gt;Newsboat&lt;/a&gt;, &lt;a href="https://news.ycombinator.com/" rel="noopener external"&gt;Hacker News&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Music&lt;/strong&gt;: YouTube Music on the web, not much&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Audiobooks&lt;/strong&gt;: Audible&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Password Management&lt;/strong&gt;: 1Password and Apple Passwords&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Code Editor&lt;/strong&gt;: &lt;a href="https://zed.dev/" rel="noopener external"&gt;Zed&lt;/a&gt; with AI disabled, plus &lt;a href="https://t3.codes/" rel="noopener external"&gt;T3 Code&lt;/a&gt; and others now and then&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;AI Harness&lt;/strong&gt;: &lt;a href="https://pi.dev/" rel="noopener external"&gt;Pi&lt;/a&gt;, &lt;a href="https://pi-web.dev/" rel="noopener external"&gt;Pi Web&lt;/a&gt;, trying &lt;a href="https://tannermidd.github.io/pi-pocket/" rel="noopener external"&gt;Pi Pocket&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Dictation&lt;/strong&gt;: &lt;a href="https://wisprflow.ai/" rel="noopener external"&gt;Wispr Flow&lt;/a&gt;, &lt;a href="https://muesli.works/" rel="noopener external"&gt;Muesli&lt;/a&gt; for local transcription&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VPN&lt;/strong&gt;: &lt;a href="https://adguard.com/en/welcome.html" rel="noopener external"&gt;AdGuard&lt;/a&gt;, &lt;a href="https://tailscale.com/" rel="noopener external"&gt;Tailscale&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="bonus-items"&gt;Bonus Items&lt;/h3&gt;&#10;&lt;p&gt;Important enough to include here, ordered roughly by how often I rely on each.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Launcher: &lt;a href="https://github.com/abue-ammar/tinycast" rel="noopener external"&gt;Tinycast&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Window Manager: &lt;a href="https://omniwm.app/" rel="noopener external"&gt;OmniWM&lt;/a&gt;, trying out &lt;a href="https://trelliswm.app/" rel="noopener external"&gt;TrellisWM&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Ad Blocking: &lt;a href="https://adguard.com/en/welcome.html" rel="noopener external"&gt;AdGuard&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Terminal: &lt;a href="https://ghostty.org/" rel="noopener external"&gt;Ghostty&lt;/a&gt; + &lt;a href="https://herdr.dev/" rel="noopener external"&gt;herdr&lt;/a&gt; with zsh and &lt;a href="https://starship.rs/" rel="noopener external"&gt;Starship&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Search Engine: DuckDuckGo and Google&lt;/li&gt;&#10;&lt;li&gt;Version Control: &lt;a href="https://github.com/jesseduffield/lazygit" rel="noopener external"&gt;lazygit&lt;/a&gt; and &lt;a href="https://github.com/jj-vcs/jj" rel="noopener external"&gt;jj&lt;/a&gt;, trying &lt;a href="https://github.com/modem-dev/hunk" rel="noopener external"&gt;Hunk&lt;/a&gt; for diffs. AI does most of it now.&lt;/li&gt;&#10;&lt;li&gt;Screenshots: &lt;a href="https://cleanshot.com/" rel="noopener external"&gt;CleanShot X&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Mac Cleanup: &lt;a href="https://mole.fit/" rel="noopener external"&gt;Mole&lt;/a&gt;, a CLI that cleans caches, uninstalls apps, and frees up disk&lt;/li&gt;&#10;&lt;li&gt;AI Usage Tracking: &lt;a href="https://github.com/Halloweedev/usagepal" rel="noopener external"&gt;UsagePal&lt;/a&gt;, a menu bar app that tracks my AI subscription limits&lt;/li&gt;&#10;&lt;li&gt;Menu Bar: &lt;a href="https://github.com/thaw-app/Thaw" rel="noopener external"&gt;Thaw&lt;/a&gt; to hide the clutter&lt;/li&gt;&#10;&lt;li&gt;Keep Awake: &lt;a href="https://apps.apple.com/app/amphetamine/id937984704" rel="noopener external"&gt;Amphetamine&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Battery: &lt;a href="https://apphousekitchen.com/" rel="noopener external"&gt;AlDente&lt;/a&gt; to limit charging&lt;/li&gt;&#10;&lt;li&gt;Local Dev: &lt;a href="https://orbstack.dev/" rel="noopener external"&gt;OrbStack&lt;/a&gt;, &lt;a href="https://www.usebruno.com/" rel="noopener external"&gt;Bruno&lt;/a&gt; for APIs&lt;/li&gt;&#10;&lt;li&gt;Mastodon Client: &lt;a href="https://www.mastonaut.app/" rel="noopener external"&gt;Mastonaut&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Code Repositories: GitHub&lt;/li&gt;&#10;&lt;li&gt;Reading: Kindle&lt;/li&gt;&#10;&lt;li&gt;Writing: &lt;a href="https://writetext.io/" rel="noopener external"&gt;WriteText&lt;/a&gt;, &lt;a href="https://obsidian.md" rel="noopener external"&gt;Obsidian&lt;/a&gt;, &lt;a href="https://zed.dev/" rel="noopener external"&gt;Zed&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Design: Figma, &lt;a href="https://www.gimp.org/" rel="noopener external"&gt;GIMP&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;App + Server Monitoring: Sentry&lt;/li&gt;&#10;&lt;li&gt;Brainstorming: &lt;a href="https://obsidian.md" rel="noopener external"&gt;Obsidian&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Video Calls: Meet, Zoom&lt;/li&gt;&#10;&lt;li&gt;Snippet Sharing: GitHub Gist&lt;/li&gt;&#10;&lt;li&gt;Project Management: Linear, Jira&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Microsoft eXecution Container (MXC)</title><link>https://saurabh-kumar.com/til/1791633600/</link><pubDate>Sat, 10 Oct 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1791633600/</guid><description>&lt;p&gt;&lt;a href="https://github.com/microsoft/mxc" rel="noopener external"&gt;Microsoft eXecution Container (MXC)&lt;/a&gt; is an embeddable SDK for running untrusted commands under OS-enforced policies, not a hosted sandbox service.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Defaults: Seatbelt on macOS, Bubblewrap on Linux, ProcessContainer on Windows. SDKs for Node, Rust, and .NET.&lt;/li&gt;&#10;&lt;li&gt;Restricts filesystem and network access. macOS hostname allowlists need a caller-managed proxy; not every backend is a VM.&lt;/li&gt;&#10;&lt;li&gt;Useful boundary: sandbox the command where it executes, not just the agent&amp;rsquo;s tool-composition script. Isolation limits damage; it does not make generated output trustworthy.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Arnav Gupta's pi-subagent-manager</title><link>https://saurabh-kumar.com/til/1791547201/</link><pubDate>Fri, 09 Oct 2026 12:00:01 +0000</pubDate><guid>https://saurabh-kumar.com/til/1791547201/</guid><description>&lt;p&gt;&lt;a href="https://github.com/championswimmer/pi-subagent-manager" rel="noopener external"&gt;Arnav Gupta&amp;rsquo;s pi-subagent-manager&lt;/a&gt; makes context inheritance explicit. Nested threads receive their parent&amp;rsquo;s snapshot; independent roots start without its history. Threads stay around for steering and resume. I want synchronous coordination by default, forks for continuity and fresh context for independent review.&lt;/p&gt;&#10;</description></item><item><title>Armin Ronacher's subagent extension</title><link>https://saurabh-kumar.com/til/1791547200/</link><pubDate>Fri, 09 Oct 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1791547200/</guid><description>&lt;p&gt;&lt;a href="https://github.com/mitsuhiko/agent-stuff/blob/main/extensions/subagent.ts" rel="noopener external"&gt;Armin Ronacher&amp;rsquo;s subagent extension&lt;/a&gt; runs one observable Pi child at a time in tmux. The parent waits for the result, while I can attach to inspect or intervene. Worth preserving when adding hierarchy: synchronous coordination must not mean invisible execution.&lt;/p&gt;&#10;</description></item><item><title>Wispr Flow</title><link>https://saurabh-kumar.com/til/1791460800/</link><pubDate>Thu, 08 Oct 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1791460800/</guid><description>&lt;p&gt;Back on &lt;a href="https://wisprflow.ai/" rel="noopener external"&gt;Wispr Flow&lt;/a&gt; after six months on local speech-to-text (&lt;a href="https://github.com/altic-dev/FluidVoice" rel="noopener external"&gt;FluidVoice&lt;/a&gt; with Parakeet, plus Whisper). Local transcription is fine. The cleanup is what&amp;rsquo;s missing: fixing misheard jargon ate the time dictation saved. Flow&amp;rsquo;s correction pass, OS-level integration and phone keyboard win. It matters more now because models follow high-level intent well: I speak the intent plus constraints in one go and let the LLM structure it. Whisper mode works even when you speak softly. Want to try it? My &lt;a href="https://wisprflow.ai/r?SAURABH2220" rel="noopener external"&gt;referral link&lt;/a&gt; gets you a free month of Pro, and I get one once you&amp;rsquo;ve dictated 2,000 words.&lt;/p&gt;&#10;</description></item><item><title>Pi Durable</title><link>https://saurabh-kumar.com/til/1791374400/</link><pubDate>Wed, 07 Oct 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1791374400/</guid><description>&lt;p&gt;&lt;a href="https://earendil.com/posts/pi-durable/" rel="noopener external"&gt;Pi Durable&lt;/a&gt; is Earendil&amp;rsquo;s experimental agent harness, shipped alongside Pi 1.0. &lt;a href="https://github.com/TannerMidd/pi-pocket" rel="noopener external"&gt;TannerMidd/pi-pocket&lt;/a&gt; is a mobile-first, multiplayer web app for Pi built on it.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Every model call and tool call is a task that checkpoints to storage (SQLite, JSONL, or memory) before moving on. Kill the process and a new one opens the same file and resumes.&lt;/li&gt;&#10;&lt;li&gt;Recovery is per step: a cut-off model request is resent with the partial answer kept and marked aborted. A cut-off tool call reruns only if it is replay-safe; otherwise the model is told it was interrupted.&lt;/li&gt;&#10;&lt;li&gt;A &lt;code&gt;requestId&lt;/code&gt; makes a submission exactly-once, so a client retrying after a crash gets the original back.&lt;/li&gt;&#10;&lt;li&gt;Forks see the parent transcript up to the fork point without copying it.&lt;/li&gt;&#10;&lt;li&gt;~15k lines total, small enough for an agent to read end to end.&lt;/li&gt;&#10;&lt;li&gt;pi-pocket: restart the server mid-run and the work continues. Several people can watch, steer, or queue messages on the same session.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;npm start -- --access tailscale&lt;/code&gt; binds only to the tailnet address, so my phone gets it with no tunnel.&lt;/li&gt;&#10;&lt;li&gt;The shift: the session lives in a SQLite file, not a terminal process, so the phone is just another client. Could replace my Moshi + tmux setup.&lt;/li&gt;&#10;&lt;li&gt;Caveat: anyone who can steer runs shell commands as you.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>pi-blackhole</title><link>https://saurabh-kumar.com/til/1788609600/</link><pubDate>Sat, 05 Sep 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1788609600/</guid><description>&lt;p&gt;Every coding agent (Claude Code, Codex, pi) compacts long sessions the same way: ask an LLM to prose-summarize, then summarize that summary, until load-bearing details erode (why a decision was made, which approaches got rejected). &lt;a href="https://github.com/k0valik/pi-blackhole" rel="noopener external"&gt;pi-blackhole&lt;/a&gt; swaps that for deterministic compaction: a zero-token &lt;code&gt;compile()&lt;/code&gt; that extracts structured sections (goal, files touched, commits, preferences, brief transcript). No LLM, no drift, no cost. The compile is only lossy in the window, not on disk: blackhole also ships a &lt;code&gt;recall&lt;/code&gt; tool (BM25 + regex over the raw session JSONL and the memory ledger) that re-fetches any dropped turn on demand, so compile + recall together is lossless where an LLM summary-of-a-summary destroys the original. It also adds observational memory: background observer/reflector workers append durable facts to per-session ledgers that survive every compaction, and &lt;code&gt;/blackhole-export&lt;/code&gt; walks every past-session JSONL under a git-root into a ranked context file. The clever part is what export trusts. A fact&amp;rsquo;s rank is driven by cross-session &lt;em&gt;recurrence&lt;/em&gt;, damped as &lt;code&gt;log(1 + distinctSessions)&lt;/code&gt; with a burst penalty, not raw counts, so one chatty session can&amp;rsquo;t inflate it. Recurrence-as-reinforcement is basically &lt;a href="https://saurabh-kumar.com/articles/2025/11/notes-on-understanding-hebbian-learning/" rel="noopener external"&gt;Hebbian&lt;/a&gt;: facts that keep firing across sessions get stronger. Dedup is a 3-pass funnel, exact then Levenshtein then Sørensen-Dice token-set, with a Levenshtein floor so keyword overlap alone can&amp;rsquo;t merge two distinct facts. Bonus: the OM workers run on their own model and billing, so I point them at &lt;code&gt;openai-codex/gpt-5.6-luna&lt;/code&gt; while the main thread stays on Claude.&lt;/p&gt;&#10;</description></item><item><title>gistpreview.github.io</title><link>https://saurabh-kumar.com/til/1784116801/</link><pubDate>Wed, 15 Jul 2026 12:00:01 +0000</pubDate><guid>https://saurabh-kumar.com/til/1784116801/</guid><description>&lt;p&gt;&lt;a href="https://gistpreview.github.io/" rel="noopener external"&gt;gistpreview.github.io&lt;/a&gt; renders any GitHub Gist as a live HTML page. Pass the gist ID as a query param (&lt;code&gt;?&amp;lt;gist_id&amp;gt;&lt;/code&gt;) and it fetches via GitHub API + &lt;code&gt;document.write()&lt;/code&gt;. Entire thing is 2 files (index.html + main.js), no build step, trivially self-hostable. Useful for quick HTML/CSS demos without spinning up CodePen or deploying anything.&lt;/p&gt;&#10;</description></item><item><title>SecretSpec</title><link>https://saurabh-kumar.com/til/1784116800/</link><pubDate>Wed, 15 Jul 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1784116800/</guid><description>&lt;p&gt;&lt;a href="https://secretspec.dev/" rel="noopener external"&gt;SecretSpec&lt;/a&gt; declares secrets once in &lt;code&gt;secretspec.toml&lt;/code&gt; (names + descriptions, never values) and resolves them from any of 11 backends: keyring, 1Password, Vault, AWS, GCP, .env. Same command everywhere: &lt;code&gt;secretspec run --profile production -- npm start&lt;/code&gt;. Per-secret fallback chains (&lt;code&gt;providers = [&amp;quot;vault&amp;quot;, &amp;quot;keyring&amp;quot;, &amp;quot;env&amp;quot;]&lt;/code&gt;) and a &lt;code&gt;ref = { item, field }&lt;/code&gt; form points at a secret you already have elsewhere, no renaming. Sharpest bit: agents get held to a stricter bar by default. &lt;code&gt;require_reason = &amp;quot;agents&amp;quot;&lt;/code&gt; forces &lt;code&gt;secretspec run --reason &amp;quot;...&amp;quot;&lt;/code&gt; before an AI agent can read a secret, appended to a local audit log; humans running interactively are unaffected. Rust core, SDKs for Python/Go/Node/Ruby/PHP/Haskell. Worth piloting on a project drowning in &lt;code&gt;.env&lt;/code&gt; sprawl.&lt;/p&gt;&#10;</description></item><item><title>Recordly</title><link>https://saurabh-kumar.com/til/1783944000/</link><pubDate>Mon, 13 Jul 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1783944000/</guid><description>&lt;p&gt;&lt;a href="https://github.com/webadderallorg/Recordly" rel="noopener external"&gt;Recordly&lt;/a&gt; does video recording, screenshots, and demo editing in one open-source Electron app. Not in Homebrew core, but easy to ship via a &lt;a href="https://github.com/theskumar/homebrew-tap" rel="noopener external"&gt;personal tap&lt;/a&gt;: &lt;code&gt;brew install --cask theskumar/tap/recordly&lt;/code&gt;. Replacing my previous screenshot/recording workflow with this as the single default (see &lt;a href="https://saurabh-kumar.com/articles/2025/11/default-apps-2025/" rel="noopener external"&gt;Default Apps 2025&lt;/a&gt;).&lt;/p&gt;&#10;</description></item><item><title>Vim motion tips for Zed</title><link>https://saurabh-kumar.com/til/1782043201/</link><pubDate>Sun, 21 Jun 2026 12:00:01 +0000</pubDate><guid>https://saurabh-kumar.com/til/1782043201/</guid><description>&lt;p&gt;Vim motion tips for Zed. Turn on relative line numbers, and let &lt;a href="https://zed.dev/docs/vim" rel="noopener external"&gt;vim mode&lt;/a&gt; switch them to absolute in Insert mode, so you get jump distances in Normal mode and context while typing.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;relative_line_numbers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;vim&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;toggle_relative_line_numbers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;[[&lt;/code&gt; / &lt;code&gt;]]&lt;/code&gt; jump between function definitions in a large Django or FastAPI file.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;f:&lt;/code&gt; moves to the end of an &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;def&lt;/code&gt; or &lt;code&gt;class&lt;/code&gt; line.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Hidden Gems: Community Edition</title><link>https://saurabh-kumar.com/til/1782043200/</link><pubDate>Sun, 21 Jun 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1782043200/</guid><description>&lt;p&gt;Zed&amp;rsquo;s &lt;a href="https://github.com/zed-industries/zed/discussions/22581" rel="noopener external"&gt;Hidden Gems: Community Edition&lt;/a&gt; thread collects users&amp;rsquo; favourite Zed features and workflows, a companion to &lt;a href="https://zed.dev/blog/hidden-gems-part-1" rel="noopener external"&gt;Hidden Gems: Team Edition&lt;/a&gt;. One tip from it: point &lt;code&gt;agent.commit_message_model&lt;/code&gt; at &lt;code&gt;gemini-2.0-flash&lt;/code&gt; on a free Gemini API key to write commit messages.&lt;/p&gt;&#10;</description></item><item><title>Moshi</title><link>https://saurabh-kumar.com/til/1781524800/</link><pubDate>Mon, 15 Jun 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1781524800/</guid><description>&lt;p&gt;&lt;a href="https://getmoshi.app/" rel="noopener external"&gt;Moshi&lt;/a&gt; is the best SSH client I&amp;rsquo;ve found for iPhone — connects to your laptop&amp;rsquo;s tmux sessions over &lt;a href="https://mosh.org/" rel="noopener external"&gt;Mosh&lt;/a&gt;, making it rock-solid on mobile networks with roaming support. Perfect for an agent view on the go; I use it to monitor and interact with running pi/Claude sessions from my iPhone without losing the session on network switches.&lt;/p&gt;&#10;</description></item><item><title>fabric</title><link>https://saurabh-kumar.com/til/1780920001/</link><pubDate>Mon, 08 Jun 2026 12:00:01 +0000</pubDate><guid>https://saurabh-kumar.com/til/1780920001/</guid><description>&lt;p&gt;&lt;a href="https://github.com/danielmiessler/fabric" rel="noopener external"&gt;faberic&lt;/a&gt; is a crowd-sourced collection of prompts bundeled with a cli to use them via piped interfaces.&lt;/p&gt;&#10;</description></item><item><title>How I Optimized My Zsh Startup Time</title><link>https://saurabh-kumar.com/articles/2026/05/how-i-optimized-my-zsh-startup-time/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/articles/2026/05/how-i-optimized-my-zsh-startup-time/</guid><description>&lt;p&gt;My zsh prompt was taking close to a second to appear on a fresh terminal. Not catastrophic. But you feel it every time tmux spawns a new pane, and for me that is constantly. The config had grown for years, one &lt;code&gt;eval&lt;/code&gt; and one plugin at a time, and nobody had been auditing it, least of all me.&lt;/p&gt;&#10;&lt;p&gt;What follows is the audit, and the eight things I changed. The numbers are estimates, not measurements, and I will say so again at the end.&lt;/p&gt;&#10;&lt;h2 id="measure-first"&gt;Measure first&lt;/h2&gt;&#10;&lt;p&gt;Before I touched anything, I measured. Two tools did the work:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Cold-start benchmark (run a few times, watch the variance):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; i in &lt;span class="o"&gt;{&lt;/span&gt;1..5&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; zsh -i -c exit&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# In-shell flamegraph:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1. Uncomment `zmodload zsh/zprof` at the top of .zshrc&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 2. Add `zprof` at the bottom&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 3. Open a new shell&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zprof &lt;span class="p"&gt;|&lt;/span&gt; head -40&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;zprof&lt;/code&gt; is the one that helped most. It prints which function calls and &lt;code&gt;eval&lt;/code&gt;s are burning time, sorted by self-time and call count. That is enough to stop guessing.&lt;/p&gt;&#10;&lt;h2 id="1-stop-forking-brew---prefix-five-times-per-shell"&gt;1. Stop forking &lt;code&gt;brew --prefix&lt;/code&gt; five times per shell&lt;/h2&gt;&#10;&lt;p&gt;My macOS config had this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix coreutils&lt;span class="k"&gt;)&lt;/span&gt;/libexec/gnubin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix findutils&lt;span class="k"&gt;)&lt;/span&gt;/libexec/gnubin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix gnu-sed&lt;span class="k"&gt;)&lt;/span&gt;/libexec/gnubin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix grep&lt;span class="k"&gt;)&lt;/span&gt;/libexec/gnubin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix curl&lt;span class="k"&gt;)&lt;/span&gt;/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Five forks of &lt;code&gt;brew&lt;/code&gt;, which is a Ruby program, on every shell, just to resolve paths that always end up under &lt;code&gt;${HOMEBREW_PREFIX}/opt/&amp;lt;formula&amp;gt;&lt;/code&gt; anyway. Each fork is about 35 ms on Apple Silicon, so that is roughly 180 ms gone before I have typed a single character.&lt;/p&gt;&#10;&lt;p&gt;Fix:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;_brew_opt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOMEBREW_PREFIX&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;/opt/homebrew&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/opt&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt; -d &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;_brew_opt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/usr/local/opt&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;/coreutils/libexec/gnubin:&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;/findutils/libexec/gnubin:&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;/gnu-sed/libexec/gnubin:&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;/grep/libexec/gnubin:&lt;/span&gt;&lt;span class="nv"&gt;$_brew_opt&lt;/span&gt;&lt;span class="s2"&gt;/curl/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;unset&lt;/span&gt; _brew_opt&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A path-prefix lookup costs nothing. The &lt;code&gt;/usr/local/opt&lt;/code&gt; fallback is there for Intel macOS, where &lt;code&gt;HOMEBREW_PREFIX&lt;/code&gt; may be unset, for example in bash without &lt;code&gt;brew shellenv&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Saves about 180 ms.&lt;/p&gt;&#10;&lt;h2 id="2-defer-plugins-with-zinits-wait-ice"&gt;2. Defer plugins with zinit&amp;rsquo;s &lt;code&gt;wait&lt;/code&gt; ice&lt;/h2&gt;&#10;&lt;p&gt;If you use a plugin manager, this is probably the biggest single win on the table. Syntax highlighting, autosuggestions, history-substring-search, completion add-ons: none of these need to load before the first prompt renders. They only need to be there by the time you type something.&lt;/p&gt;&#10;&lt;p&gt;With &lt;a href="https://github.com/zdharma-continuum/zinit" rel="noopener external"&gt;zinit&lt;/a&gt;&amp;rsquo;s &lt;code&gt;wait&lt;/code&gt; ice, you defer them until after the first prompt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zinit &lt;span class="nb"&gt;wait&lt;/span&gt; lucid light-mode &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; atinit&lt;span class="s2"&gt;&amp;#34;ZINIT[COMPINIT_OPTS]=-C; zicompinit; zicdreplay&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zdharma-continuum/fast-syntax-highlighting &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; atload&lt;span class="s2"&gt;&amp;#34;_zsh_autosuggest_start&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zsh-users/zsh-autosuggestions &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; blockf atpull&lt;span class="s1"&gt;&amp;#39;zinit creinstall -q .&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zsh-users/zsh-completions &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; atload&lt;span class="s2"&gt;&amp;#34;bindkey &amp;#39;^[[A&amp;#39; history-substring-search-up; bindkey &amp;#39;^[[B&amp;#39; history-substring-search-down&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zsh-users/zsh-history-substring-search &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; wfxr/forgit&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;atinit&lt;/code&gt; ice on the first plugin runs &lt;code&gt;compinit&lt;/code&gt; once and replays cached completions via &lt;code&gt;zicdreplay&lt;/code&gt;. One &lt;code&gt;compinit&lt;/code&gt; covers every turbo-loaded plugin. The &lt;code&gt;atload&amp;quot;_zsh_autosuggest_start&amp;quot;&lt;/code&gt; line re-initializes autosuggestions after deferred loading. Without it the plugin silently no-ops, which took me an embarrassing amount of time to work out.&lt;/p&gt;&#10;&lt;p&gt;Saves 100-300 ms.&lt;/p&gt;&#10;&lt;h2 id="3-compile-zcompdump-to-bytecode"&gt;3. Compile &lt;code&gt;.zcompdump&lt;/code&gt; to bytecode&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;compinit&lt;/code&gt; parses thousands of completion functions on every startup. Compiling its dump file to bytecode means subsequent shells skip the parser:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;zcompdump&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ZDOTDIR&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.zcompdump&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[[&lt;/span&gt; -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$zcompdump&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;! -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;zcompdump&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.zwc&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$zcompdump&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -nt &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;zcompdump&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.zwc&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; zcompile &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$zcompdump&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It recompiles only when the source is newer than the cached &lt;code&gt;.zwc&lt;/code&gt;. Saves 30-80 ms.&lt;/p&gt;&#10;&lt;h2 id="4-pick-one-version-manager"&gt;4. Pick one version manager&lt;/h2&gt;&#10;&lt;p&gt;I had both &lt;code&gt;fnm&lt;/code&gt; and &lt;code&gt;mise&lt;/code&gt; active, which is one more than anyone needs. Mise manages everything I actually use, Node, Python, Go; &lt;code&gt;fnm env&lt;/code&gt; in &lt;code&gt;.zshrc&lt;/code&gt; was a leftover that nothing depended on. Deleting it saved another 30-80 ms per shell.&lt;/p&gt;&#10;&lt;p&gt;Mise itself has a second optimization, &lt;code&gt;--shims&lt;/code&gt; mode:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;~/.local/bin/mise activate zsh --shims&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Default &lt;code&gt;mise activate&lt;/code&gt; installs a &lt;code&gt;precmd&lt;/code&gt; hook that runs on every prompt to keep tool versions in sync with &lt;code&gt;.mise.toml&lt;/code&gt;. Shim mode replaces that hook with symlinks on &lt;code&gt;PATH&lt;/code&gt; that resolve the right version the first time you call each tool. That is about 17 ms saved at startup, paid back as 10-30 ms on the first invocation per shell. For interactive use it is a fine trade.&lt;/p&gt;&#10;&lt;h2 id="5-cache-the-slow-evals"&gt;5. Cache the slow &lt;code&gt;eval&lt;/code&gt;s&lt;/h2&gt;&#10;&lt;p&gt;A lot of tools want you to run &lt;code&gt;eval &amp;quot;$(tool init zsh)&amp;quot;&lt;/code&gt; at startup. Each one forks the tool, runs its setup, and pipes the result back. You pay that cost every shell, even though the output rarely changes between runs.&lt;/p&gt;&#10;&lt;p&gt;A small helper fixes it:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_cache_eval&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;shift&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/.zsh/cache/&lt;span class="nv"&gt;$name&lt;/span&gt;.zsh&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[[&lt;/span&gt; -s &lt;span class="nv"&gt;$cache&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt; -nt &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;/dev/null&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; mkdir -p ~/.zsh/cache&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_cache_eval zoxide zoxide init zsh --cmd j&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;_cache_eval uv uv generate-shell-completion zsh&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;$cache -nt ${commands[$1]:-/dev/null}&lt;/code&gt; test compares mtimes. When brew or mise upgrades the binary, the cache invalidates on its own. No stale-cache surprises after a &lt;code&gt;brew upgrade&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Keep &lt;code&gt;starship init zsh&lt;/code&gt; eager. You need it for the prompt anyway, and it is already fast.&lt;/p&gt;&#10;&lt;p&gt;Saves 50-100 ms.&lt;/p&gt;&#10;&lt;h2 id="6-audit-plugins-cut-what-you-dont-use"&gt;6. Audit plugins; cut what you don&amp;rsquo;t use&lt;/h2&gt;&#10;&lt;p&gt;I was loading the &lt;a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git" rel="noopener external"&gt;Oh My Zsh git plugin&lt;/a&gt; via zinit&amp;rsquo;s snippet feature. The plugin ships roughly 150 aliases.&lt;/p&gt;&#10;&lt;p&gt;So I grepped my last few months of shell history against its alias list. Five OMZ-only aliases came back: &lt;code&gt;gsw&lt;/code&gt;, &lt;code&gt;glo&lt;/code&gt;, &lt;code&gt;gf&lt;/code&gt;, &lt;code&gt;gclean&lt;/code&gt;, &lt;code&gt;gss&lt;/code&gt;. Everything else I either did not use or had already redefined to my own taste. I copied those five into &lt;code&gt;shell/.aliases&lt;/code&gt; and dropped the &lt;code&gt;OMZP::git&lt;/code&gt; snippet from zinit.&lt;/p&gt;&#10;&lt;p&gt;There is a second reason to do this, and it has nothing to do with startup time. Because the OMZ plugin loaded in turbo mode, after my eager &lt;code&gt;shell/.aliases&lt;/code&gt;, it was quietly overwriting aliases I had defined myself. My &lt;code&gt;gc='git commit -m'&lt;/code&gt; was getting clobbered to &lt;code&gt;git commit --verbose&lt;/code&gt;, and &lt;code&gt;gg='lazygit'&lt;/code&gt; to &lt;code&gt;git gui&lt;/code&gt;. The plugin &amp;ldquo;worked&amp;rdquo; because turbo-loaded aliases land before you type, but the behavior was not the one I had configured. Dropping the plugin put my own aliases back in charge.&lt;/p&gt;&#10;&lt;p&gt;While I was at it I found &lt;code&gt;zsh/.zsh/plugins/git/git.plugin.zsh&lt;/code&gt; in my dotfiles, 150 lines of git aliases, sourced from nowhere. Years of dead code I had been carrying around. Gone.&lt;/p&gt;&#10;&lt;h2 id="7-move-path-and-env-to-zshenv"&gt;7. Move PATH and env to &lt;code&gt;.zshenv&lt;/code&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;.zshrc&lt;/code&gt; runs only for interactive shells. If you set &lt;code&gt;PATH&lt;/code&gt; there, then cron jobs, &lt;code&gt;zsh -c &amp;quot;command&amp;quot;&lt;/code&gt; invocations, and IDE-spawned non-interactive shells do not see your tools. That is how you end up burning half a day on &amp;ldquo;works in my terminal, breaks in CI.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;The right home is &lt;code&gt;.zshenv&lt;/code&gt;, which runs for every zsh invocation:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ~/.zshenv&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PNPM_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/Library/pnpm&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;typeset&lt;/span&gt; -U path PATH &lt;span class="c1"&gt;# auto-dedupe&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.local/bin&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$PNPM_HOME&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.cargo/env&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; . &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.cargo/env&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;typeset -U path PATH&lt;/code&gt; ties the &lt;code&gt;path&lt;/code&gt; array to the &lt;code&gt;PATH&lt;/code&gt; scalar and deduplicates automatically. No more 8 KB &lt;code&gt;PATH&lt;/code&gt; strings with the same entry six times because nested shells kept prepending.&lt;/p&gt;&#10;&lt;p&gt;This is not a cold-start win, since &lt;code&gt;.zshenv&lt;/code&gt; still runs. It is a correctness fix. A shorter &lt;code&gt;PATH&lt;/code&gt; does make command lookups faster, so you get a little of both.&lt;/p&gt;&#10;&lt;h2 id="8-fix-the-things-you-didnt-know-were-broken"&gt;8. Fix the things you didn&amp;rsquo;t know were broken&lt;/h2&gt;&#10;&lt;p&gt;While I was in there, &lt;code&gt;zprof&lt;/code&gt; and a slow re-read of the config surfaced a stack of small bugs that had nothing to do with cold start. They were just wrong:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;~/.profile&lt;/code&gt; was being sourced twice, once from &lt;code&gt;.zshrc&lt;/code&gt; and once from &lt;code&gt;.zshenv&lt;/code&gt;. 20-80 ms of pure waste.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;compinit&lt;/code&gt;&amp;rsquo;s cache-staleness check had inverted-looking logic and a misleading comment. It happened to do the right thing, but only by accident.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;zsh/.zsh/lib/completions.zsh&lt;/code&gt; referenced &lt;code&gt;$ZSH&lt;/code&gt;, an Oh My Zsh leftover that was unset in my setup. The completion cache-path was silently expanding to &lt;code&gt;/cache/&lt;/code&gt;, the filesystem root. Nothing got written there because the directory was not writable, so completions had no cache at all. Caching had quietly been off the whole time.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;compdef&lt;/code&gt; calls from eager tool initializations were arriving &lt;em&gt;before&lt;/em&gt; turbo-mode &lt;code&gt;compinit&lt;/code&gt; had run, so completions for those tools were not registering. I fixed it with a small shim that queues the calls until &lt;code&gt;compinit&lt;/code&gt; is ready:&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; ! &lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="p"&gt;+functions[compdef]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;typeset&lt;/span&gt; -ga __deferred_compdefs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; compdef&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;__deferred_compdefs&lt;/span&gt;&lt;span class="o"&gt;+=(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="p"&gt;(j: :)@&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then in the turbo block&amp;rsquo;s &lt;code&gt;atinit&lt;/code&gt;, after &lt;code&gt;zicompinit; zicdreplay&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;${#&lt;/span&gt;&lt;span class="nv"&gt;__deferred_compdefs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; c in &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;__deferred_compdefs&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;compdef &lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;unset&lt;/span&gt; __deferred_compdefs&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;None of those moved the startup number. They were still bugs, and now they are not.&lt;/p&gt;&#10;&lt;h2 id="doing-this-on-your-own-config"&gt;Doing this on your own config&lt;/h2&gt;&#10;&lt;p&gt;Measure before you touch anything. &lt;code&gt;time zsh -i -c exit&lt;/code&gt; for cold start, &lt;code&gt;zprof&lt;/code&gt; for the in-shell breakdown. Skip that step and you will spend an afternoon optimizing the wrong thing. I have done exactly that.&lt;/p&gt;&#10;&lt;p&gt;Then grep your shell history against the alias and function lists of whatever plugins you load. You will usually find you are loading hundreds of definitions to use a handful.&lt;/p&gt;&#10;&lt;p&gt;Defer what can be deferred. First-prompt latency is what you feel, and almost nothing actually has to load before you can type. Cache expensive &lt;code&gt;eval&lt;/code&gt;s whose output only changes when the binary changes, and key the cache on the binary&amp;rsquo;s mtime so upgrades invalidate it for you. Hardcode the constants: &lt;code&gt;brew --prefix coreutils&lt;/code&gt; is always &lt;code&gt;${HOMEBREW_PREFIX}/opt/coreutils&lt;/code&gt;. Forking a Ruby program to ask is silly.&lt;/p&gt;&#10;&lt;p&gt;And keep environment variables in &lt;code&gt;.zshenv&lt;/code&gt;, not &lt;code&gt;.zshrc&lt;/code&gt;. Future-you, debugging why something works in iTerm but not in a cron job, will be grateful.&lt;/p&gt;&#10;&lt;h2 id="estimated-total"&gt;Estimated total&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Change&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Savings&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stop forking &lt;code&gt;brew --prefix&lt;/code&gt; × 5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;~180 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Defer plugins with &lt;code&gt;zinit wait&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;100-300 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Cache remaining &lt;code&gt;eval&lt;/code&gt;s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;50-100 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Compile &lt;code&gt;.zcompdump&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30-80 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Drop &lt;code&gt;fnm&lt;/code&gt; (mise covers it)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30-80 ms&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;~400-700 ms&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;These are estimates, not measurements. Your actual numbers depend on your hardware, your plugin set, and how much cruft you had accumulated over the years. Under 200 ms on Apple Silicon is doable with stock zsh and a small, well-deferred plugin set.&lt;/p&gt;&#10;&lt;p&gt;My dotfiles, with all of this applied, are at &lt;a href="https://github.com/theskumar/dotfiles" rel="noopener external"&gt;github.com/theskumar/dotfiles&lt;/a&gt;. The &lt;code&gt;zsh/OPTIMIZATION.md&lt;/code&gt; file there is the running log of what is done and what is still open.&lt;/p&gt;&#10;&lt;p&gt;I plan to repeat the audit as the config changes.&lt;/p&gt;&#10;</description></item><item><title>colbymchenry/codegraph</title><link>https://saurabh-kumar.com/til/1778328000/</link><pubDate>Sat, 09 May 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1778328000/</guid><description>&lt;p&gt;&lt;a href="https://github.com/colbymchenry/codegraph" rel="noopener external"&gt;colbymchenry/codegraph&lt;/a&gt;: Pre-indexed code knowledge graph for Claude Code — fewer tokens, fewer tool calls, 100% local&lt;/p&gt;&#10;</description></item><item><title>codespelunker</title><link>https://saurabh-kumar.com/til/1775995202/</link><pubDate>Sun, 12 Apr 2026 12:00:02 +0000</pubDate><guid>https://saurabh-kumar.com/til/1775995202/</guid><description>&lt;p&gt;&lt;a href="https://github.com/boyter/cs?tab=readme-ov-file" rel="noopener external"&gt;codespelunker&lt;/a&gt; is a CLI code search tool that understands code structure and ranks results by relevance. No indexing required with CLI, TUI, MCP and HTTP support.&lt;/p&gt;&#10;</description></item><item><title>mise</title><link>https://saurabh-kumar.com/til/1775304000/</link><pubDate>Sat, 04 Apr 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1775304000/</guid><description>&lt;p&gt;&lt;a href="https://github.com/jdx/mise" rel="noopener external"&gt;mise&lt;/a&gt; let&amp;rsquo;s you manage dependencies at OS level and project level. Thinking to manage my dev machine. The hard part is documenting what I have installed.&lt;/p&gt;&#10;</description></item><item><title>moor</title><link>https://saurabh-kumar.com/til/1774094400/</link><pubDate>Sat, 21 Mar 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1774094400/</guid><description>&lt;p&gt;&lt;a href="https://github.com/walles/moor" rel="noopener external"&gt;&lt;code&gt;moor&lt;/code&gt;&lt;/a&gt; is a better replacement for less and bat as PAGER.&lt;/p&gt;&#10;</description></item><item><title>git-trim</title><link>https://saurabh-kumar.com/til/1771588800/</link><pubDate>Fri, 20 Feb 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1771588800/</guid><description>&lt;p&gt;&lt;code&gt;brew install git-trim&lt;/code&gt; &lt;a href="https://github.com/foriequal0/git-trim" rel="noopener external"&gt;git-trim&lt;/a&gt; - A dedicated binary for&#10;safely deleting merged branches. Handles edge cases better than bash one-liners, including squash-merged branches. &lt;a href="https://news.ycombinator.com/item?id=47089533" rel="noopener external"&gt;#&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>slingdata-io/sling-cli</title><link>https://saurabh-kumar.com/til/1771502400/</link><pubDate>Thu, 19 Feb 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1771502400/</guid><description>&lt;p&gt;&lt;a href="https://github.com/slingdata-io/sling-cli" rel="noopener external"&gt;slingdata-io/sling-cli&lt;/a&gt; is a promising tool move/sync data between databases and files, esp. helpful for local testing, ci/cd while able to do stage/sql based transformations.&lt;/p&gt;&#10;</description></item><item><title>github recommendor</title><link>https://saurabh-kumar.com/til/1768132800/</link><pubDate>Sun, 11 Jan 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1768132800/</guid><description>&lt;p&gt;Puzer published a &lt;a href="https://puzer.github.io/github_recommender/" rel="noopener external"&gt;github recommendor&lt;/a&gt; that uses semantic embedding from user&amp;rsquo;s github stars all client side, I found some great recommendations which I plan to use:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/pamburus/hl" rel="noopener external"&gt;pamburus/hl&lt;/a&gt;: A fast and powerful log viewer and processor that converts JSON logs or logfmt logs into a clear human-readable format. (⭐2657)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/samwho/spacer" rel="noopener external"&gt;samwho/spacer&lt;/a&gt;: CLI tool to insert spacers when command output stops (⭐1663)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/darrenburns/posting" rel="noopener external"&gt;darrenburns/posting&lt;/a&gt;: The modern API client that lives in your terminal. (⭐11134)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/plutov/oq" rel="noopener external"&gt;plutov/oq&lt;/a&gt;: Terminal OpenAPI Spec viewer (⭐943)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/wey-gu/py-pglite" rel="noopener external"&gt;wey-gu/py-pglite&lt;/a&gt;: PGlite wrapper in Python for testing. Test your app with Postgres just as lite as SQLite. (⭐577)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Text based diagramming tools</title><link>https://saurabh-kumar.com/til/1767787201/</link><pubDate>Wed, 07 Jan 2026 12:00:01 +0000</pubDate><guid>https://saurabh-kumar.com/til/1767787201/</guid><description>&lt;p&gt;Text based diagramming tools:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/yuzutech/kroki" rel="noopener external"&gt;yuzutech/kroki&lt;/a&gt;: self-hosted solution for unified interface to buch of diagramming tools, including mermaid, d2, etc.! (⭐3875)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/terrastruct/d2" rel="noopener external"&gt;d2&lt;/a&gt; provides a clean syntax and output as well.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>npx -y npm-check-updates</title><link>https://saurabh-kumar.com/til/1766404801/</link><pubDate>Mon, 22 Dec 2025 12:00:01 +0000</pubDate><guid>https://saurabh-kumar.com/til/1766404801/</guid><description>&lt;p&gt;&lt;a href="https://github.com/raineorshine/npm-check-updates" rel="noopener external"&gt;&lt;code&gt;npx -y npm-check-updates&lt;/code&gt;&lt;/a&gt; tells you the latest versions of your &lt;code&gt;package.json&lt;/code&gt;, ignoring pinned versions.&lt;/p&gt;&#10;</description></item><item><title>New tools</title><link>https://saurabh-kumar.com/til/1766232000/</link><pubDate>Sat, 20 Dec 2025 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1766232000/</guid><description>&lt;p&gt;New tools:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/gjtorikian/gh-actions-lockfile" rel="noopener external"&gt;gh-actions-lockfile&lt;/a&gt; to generate and verify lockfiles for gh-actions&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://proxyman.com/" rel="noopener external"&gt;Proxyman&lt;/a&gt; is more user-friendly http debugging proxy than Charles Proxy on MacOS&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Default Apps 2025</title><link>https://saurabh-kumar.com/articles/2025/11/default-apps-2025/</link><pubDate>Wed, 05 Nov 2025 00:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/articles/2025/11/default-apps-2025/</guid><description>&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; see my &lt;a href="https://saurabh-kumar.com/articles/2026/10/default-apps-2026/"&gt;Default Apps 2026&lt;/a&gt; for this year&amp;rsquo;s list.&lt;/p&gt;&#10;&lt;p&gt;Apps I’ve been using regularly this year. Heavily inspired by &lt;a href="https://micro.webology.dev/2025/10/29/default-apps-here-are-my/" rel="noopener external"&gt;Jeff&#10;Triplett&lt;/a&gt;.&#10;Catalogued by Robb Knight’s &lt;a href="https://defaults.rknight.me/" rel="noopener external"&gt;App Defaults&lt;/a&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Mail Client&lt;/strong&gt;: &lt;a href="https://www.edisonmail.com/" rel="noopener external"&gt;Edison Mail&lt;/a&gt; and gmail in browser&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Notes&lt;/strong&gt;: &lt;a href="https://obsidian.md" rel="noopener external"&gt;Obsidian&lt;/a&gt;, SimpleNotes&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;To-Do&lt;/strong&gt;: &lt;a href="https://bulletjournal.com/" rel="noopener external"&gt;Bullet Journel&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Photo Shooting&lt;/strong&gt;: iPhone, Canon 6D&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Photo Editing&lt;/strong&gt;: PhotoShop, Gimp&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Calendar&lt;/strong&gt;: &lt;a href="https://www.notion.com/product/calendar" rel="noopener external"&gt;Notion Calendar&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Cloud File Storage&lt;/strong&gt;: iCloud + Google Drive&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;RSS&lt;/strong&gt;: &lt;a href="https://netnewswire.com/" rel="noopener external"&gt;NetNewsWire&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Contacts&lt;/strong&gt;: Apple Contacts &amp;amp; Google Contacts&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Browser&lt;/strong&gt;: Arc, Zen Browser and Safari&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Chat&lt;/strong&gt;: Apple Messages, Discord, Slack&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Bookmarks&lt;/strong&gt;: RainDrop.io&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Read It Later&lt;/strong&gt;: Safari Reading List&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Word Processing&lt;/strong&gt;: Apple Pages&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Spreadsheets&lt;/strong&gt;: Google Sheets&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Presentations&lt;/strong&gt;: Figma&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Shopping Lists&lt;/strong&gt;: Amazon&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Budgeting and Personal Finance&lt;/strong&gt;: paisa&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;News&lt;/strong&gt;: Perplexity.ai, &lt;a href="https://netnewswire.com/" rel="noopener external"&gt;NetNewsWire&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Music&lt;/strong&gt;: Youtube Music&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Password Management&lt;/strong&gt;: 1Password and Apple Passwords&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Code Editor&lt;/strong&gt;: &lt;a href="https://zed.dev/" rel="noopener external"&gt;Zed&lt;/a&gt; &amp;amp; Cursor&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VPN&lt;/strong&gt;: &lt;a href="https://adguard.com/en/welcome.html" rel="noopener external"&gt;AdGuard&lt;/a&gt;, Zscalar&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="bonus-items"&gt;Bonus Items&lt;/h3&gt;&#10;&lt;p&gt;Important enough to include here, ordered roughly by how often I rely on each.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Launcher: &lt;a href="raycast.com"&gt;Raycast&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Ad Blocking: &lt;a href="https://adguard.com/en/welcome.html" rel="noopener external"&gt;AdGuard&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Terminal: iTerm 2 with ZSH&lt;/li&gt;&#10;&lt;li&gt;Search Engine: DuckDuckGo and Google&lt;/li&gt;&#10;&lt;li&gt;Version Control: &lt;a href="https://github.com/jesseduffield/lazygit" rel="noopener external"&gt;lazygit&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Screenshots: CleanShot X (also trying out Xnapper)&lt;/li&gt;&#10;&lt;li&gt;Mastodon Client: &lt;a href="https://www.mastonaut.app/" rel="noopener external"&gt;Mastonaut&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Code Repositories: GitHub&lt;/li&gt;&#10;&lt;li&gt;Reading: Kindle&lt;/li&gt;&#10;&lt;li&gt;Writing: &lt;a href="https://obsidian.md" rel="noopener external"&gt;Obsidian&lt;/a&gt;, &lt;a href="https://zed.dev/" rel="noopener external"&gt;Zed&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Design: Figma, Gimp&lt;/li&gt;&#10;&lt;li&gt;App + Server Monitoring: Sentry&lt;/li&gt;&#10;&lt;li&gt;Brainstorming: &lt;a href="https://obsidian.md" rel="noopener external"&gt;Obsidian&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Video Calls: Meet, Zoom&lt;/li&gt;&#10;&lt;li&gt;Snippet Sharing: Github Gist&lt;/li&gt;&#10;&lt;li&gt;Project Management: Linear, Jira&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>ty: Astral's Take on Python Type Checking</title><link>https://saurabh-kumar.com/articles/2025/05/ty-astrals-take-on-python-type-checking/</link><pubDate>Fri, 09 May 2025 00:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/articles/2025/05/ty-astrals-take-on-python-type-checking/</guid><description>&lt;p&gt;Anyone who has run &lt;code&gt;mypy&lt;/code&gt; on a real codebase knows the workarounds. You enable the daemon, you keep the incremental cache warm, you scope the check to the files you touched, and you still wait a few seconds for the editor to catch up after a rename. In CI it is worse, because the cache is cold and the full run can stretch into the minutes. None of this is broken, exactly. It is just the cost you learn to pay. So when a new checker says the wait can mostly go away, I pay attention, but I also want to know what it costs.&lt;/p&gt;&#10;&lt;p&gt;That checker is &lt;code&gt;ty&lt;/code&gt; (&lt;a href="https://github.com/astral-sh/ty" rel="noopener external"&gt;gh:astral-sh/ty&lt;/a&gt;), from Astral, the same people who built the Ruff linter and the &lt;code&gt;uv&lt;/code&gt; package manager. As of May 2025, it is pre-alpha, version &lt;code&gt;0.0.0a6&lt;/code&gt;, and not something you should put in production. But it is already worth talking about, partly for what it does and partly for what it deliberately leaves out.&lt;/p&gt;&#10;&lt;h2 id="speed-is-the-headline"&gt;Speed Is The Headline&lt;/h2&gt;&#10;&lt;p&gt;The numbers people are reporting are large. One user saw mypy take 18 seconds where &lt;code&gt;ty&lt;/code&gt; finished the same check in about half a second. Another compared &lt;code&gt;ty&lt;/code&gt; at 2.5 seconds against Pyright at 13.6 seconds on a 100k line codebase. Some users assumed it had failed, because nothing that finishes that fast could have actually looked at their whole project.&lt;/p&gt;&#10;&lt;p&gt;Ruff did the same thing to linting a few years ago. It was fast enough that the linter stopped being a separate step you ran and became something that just happened while you wrote code. &lt;code&gt;ty&lt;/code&gt; looks like it wants to do that for type checking. That matters more than it sounds. A check that takes a minute is a thing you run in CI and forget about. A check that runs in milliseconds is a thing your editor can run on every save, and that changes how it feels to write Python at all.&lt;/p&gt;&#10;&lt;h2 id="fast-and-frequently-wrong-for-now"&gt;Fast And Frequently Wrong, For Now&lt;/h2&gt;&#10;&lt;p&gt;Speed is the easy part. Being correct is the hard part, and here &lt;code&gt;ty&lt;/code&gt; is still young.&lt;/p&gt;&#10;&lt;p&gt;The Astral developers say this plainly: it is pre-alpha, many of the errors it reports may be wrong, and the feature set is small. Right now it does diagnostics and go-to-type-definition, and not much else. One user ran it on a codebase where Pyright reported 10 diagnostics, all correct, and &lt;code&gt;ty&lt;/code&gt; reported 1,599. That is not a small gap. Type inference is the thing that takes years to get right, and &lt;code&gt;ty&lt;/code&gt; has not had years.&lt;/p&gt;&#10;&lt;p&gt;I would not read those 1,599 diagnostics as a verdict on the project. A count like that is rarely 1,599 distinct bugs; it is usually a handful of inference patterns the checker gets wrong, multiplied across every file that hits them. Fix the pattern and the count collapses. What you are looking at is a checker that is fast first and accurate later. Given what Astral did with Ruff and uv, betting against the accuracy catching up seems unwise. But it has not caught up yet, and you should treat the output accordingly.&lt;/p&gt;&#10;&lt;h2 id="it-is-entering-a-crowded-field"&gt;It Is Entering A Crowded Field&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;ty&lt;/code&gt; is not arriving to an empty room. It joins mypy, Microsoft&amp;rsquo;s Pyright and Pylance, and Facebook&amp;rsquo;s Pyrefly, which also builds on Ruff&amp;rsquo;s parser despite being a separate project.&lt;/p&gt;&#10;&lt;p&gt;What makes all of these hard is Python itself. TypeScript was built with static typing in mind. Python&amp;rsquo;s type hints arrived later and grew unevenly, bolted onto a dynamic language, with the inconsistencies you would expect from something added after the fact. On top of that, the libraries people actually use are the hardest cases. Django and SQLAlchemy lean heavily on metaprogramming and runtime code generation, which is exactly the kind of thing a static checker cannot see. The ecosystem mostly papered over this with type stubs and plugins: SQLAlchemy 2.0 added real typed mappings, and Django users lean on &lt;code&gt;django-stubs&lt;/code&gt;, which is a mypy plugin. Hold onto that detail, because it runs straight into the next decision.&lt;/p&gt;&#10;&lt;h2 id="no-plugins-on-purpose"&gt;No Plugins, On Purpose&lt;/h2&gt;&#10;&lt;p&gt;This is where Astral makes a real choice rather than a faster one. mypy lets libraries ship plugins so the checker can understand their metaprogramming. &lt;code&gt;ty&lt;/code&gt; will not have plugins. Astral treats the interchangeability of type checking as a feature: a check should mean the same thing across tools and projects, and it should not depend on a plugin that one checker has and another does not. Instead they plan to support the popular libraries directly in the tool.&lt;/p&gt;&#10;&lt;p&gt;That is a trade, not a free win. The Django user relying on &lt;code&gt;django-stubs&lt;/code&gt; today is relying on exactly the plugin mechanism &lt;code&gt;ty&lt;/code&gt; is choosing not to have, so until Astral supports those libraries directly, that user has no escape hatch. More work falls on Astral, and the libraries they have not reached yet are simply not understood. But it also means you do not end up with type checking that only works inside one vendor&amp;rsquo;s setup, and that division is worth defending.&lt;/p&gt;&#10;&lt;p&gt;They are also borrowing from Rust&amp;rsquo;s compiler for their diagnostics, aiming for error messages that explain rather than just flag. An LSP server and a VS Code plugin are planned, though there is no release date for either.&lt;/p&gt;&#10;&lt;h2 id="the-question-that-always-comes-back"&gt;The Question That Always Comes Back&lt;/h2&gt;&#10;&lt;p&gt;Astral is a VC-backed company, and the same question hangs over &lt;code&gt;ty&lt;/code&gt; as over Ruff and uv: how does this get paid for. The code is open source and has outside contributors, but at some point the money has to come from somewhere.&lt;/p&gt;&#10;&lt;p&gt;People guess at CI products, private repositories, or enterprise services. Astral, for now, seems to be building the tools first and leaving monetization for later. That has earned them a lot of goodwill in the Python community, and it should also keep you a little cautious, because the terms you adopt a tool under are not always the terms you keep it under.&lt;/p&gt;&#10;&lt;h2 id="trying-it"&gt;Trying It&lt;/h2&gt;&#10;&lt;p&gt;If you want to look at it yourself:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;uv tool install ty&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or without installing anything:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;uvx ty check&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These notes describe the pre-alpha release in May 2025. Expect rough edges, and do not trust a red diagnostic until you have checked it by hand.&lt;/p&gt;&#10;&lt;p&gt;I do not know yet whether &lt;code&gt;ty&lt;/code&gt; becomes the type checker people reach for. The speed is real, the missing plugin system is a deliberate stance and not an oversight, and the accuracy is not there yet. What I want from it is the thing Ruff already delivered: a tool fast enough to disappear into the edit loop, honest enough to trust, and available under open source terms I can rely on. The first of those is here. The other two are still being written.&lt;/p&gt;&#10;</description></item><item><title>Navigating VS Code terminals from the keyboard</title><link>https://saurabh-kumar.com/til/1696507200/</link><pubDate>Thu, 05 Oct 2023 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1696507200/</guid><description>&lt;p&gt;VS Code&amp;rsquo;s built-in terminals are hard to move between from the keyboard. These &lt;code&gt;keybindings.json&lt;/code&gt; entries fix it, adapted from &lt;a href="https://github.com/jbranchaud/til/blob/master/vscode/toggle-between-terminals.md" rel="noopener external"&gt;jbranchaud&amp;rsquo;s TIL&lt;/a&gt;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;cmd+[&lt;/code&gt; / &lt;code&gt;cmd+]&lt;/code&gt;: previous / next terminal&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;cmd+1&lt;/code&gt; to &lt;code&gt;cmd+5&lt;/code&gt;: terminal by position&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;cmd+n&lt;/code&gt;: new terminal; &lt;code&gt;cmd+w&lt;/code&gt;: kill it&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;cmd+t&lt;/code&gt;: toggle between terminal and editor&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cmd+]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.terminal.focusNext&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;terminalFocus&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cmd+[&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.terminal.focusPrevious&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;terminalFocus&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cmd+1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.terminal.focusAtIndex1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;terminalFocus&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cmd+2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.terminal.focusAtIndex2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;terminalFocus&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cmd+3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.terminal.focusAtIndex3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;terminalFocus&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cmd+4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.terminal.focusAtIndex4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;terminalFocus&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cmd+5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.terminal.focusAtIndex5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;terminalFocus&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cmd+n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.terminal.new&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;terminalFocus &amp;amp;&amp;amp; terminalHasBeenCreated || terminalFocus &amp;amp;&amp;amp; terminalProcessSupported&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cmd+w&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.terminal.kill&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;terminalFocus&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cmd+t&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.terminal.focus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;!terminalFocus&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cmd+t&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.terminal.toggleTerminal&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item></channel></rss>