10 Developer Tools That Feel Illegal to Know About

I walk through 10 CLI and desktop tools — from ripgrep and fzf to lazygit and Bruno — each with a concrete use case for the problems they actually solve.

10 Developer Tools That Feel Illegal to Know About

Most developers I know reach for the same core set of tools: grep for search, cat for reading files, curl for HTTP calls, and git for version control. These work. They’ve worked for decades. The part I didn’t expect is how much the layer just above them — the tools you find in someone’s dotfiles or catch briefly in a conference talk — changes how certain parts of the day actually feel.

This post covers 10 of them. Each one solves a real friction point. Each one installs in under a minute. You don’t need all ten today — even two or three will make a noticeable difference.

On this page

Two search tools that replace old habits

ripgrep (rg) is a recursive search tool written in Rust. It behaves like grep but respects your .gitignore automatically, which means searching a Node.js project doesn’t crawl through node_modules. The GitHub benchmark against the Linux kernel source tree shows ripgrep completing a regex search in 82ms where grep takes over 700ms on the same corpus.

# Find every TypeScript file where this function is called
rg "processPayment" --type ts

# Case-insensitive search across all markdown files
rg -i "todo" --type md

fzf is a command-line fuzzy finder. Pipe anything into it and it opens an interactive selection UI that filters as you type. The most immediately useful pairing is shell history:

# Add fzf shell integration to your shell config
eval "$(fzf --bash)"

After that, pressing CTRL-R replaces the standard reverse-search with a live, fuzzy-searchable history picker. But where it surprised me most is when you combine it with ripgrep and bat (next section):

Try this

  1. Install the three tools: brew install ripgrep fzf bat
  2. Add fzf shell integration to ~/.bashrc or ~/.zshrc: eval "$(fzf --bash)"
  3. Run this inside any project directory: rg --files | fzf --preview 'bat --color=always {}'

Expected result: An interactive file picker that shows a live syntax-highlighted preview of whatever file you hover over. On a project with 400 files you can locate any one of them by typing two letters — with the file contents visible before you open it.

That single pipeline replaces a lot of what most people open an IDE for.

A smarter cat and a readable man page

bat is a drop-in replacement for cat. Run bat src/server.ts and you get syntax highlighting, line numbers, and a sidebar marking which lines have changed since the last git commit. That last feature is what made me switch — you see your edits in context while reading the file, without running a separate diff.

# Read a file with syntax highlighting and git change markers
bat src/server.ts

# Pipe JSON output through bat for coloured, paginated output
curl -s https://api.github.com/repos/BurntSushi/ripgrep | bat -l json

One thing to know: on Ubuntu and Debian, the package installs as batcat rather than bat due to a naming conflict with an older package. Add alias bat="batcat" to your shell config on those systems, otherwise bat will silently not exist.

tldr is a community-maintained collection of simplified manual pages. Running tldr tar gives you the five or six tar commands people actually use, each with a complete example. It’s not a replacement for man when you need the full reference — but it’s the first thing I reach for when I just need a quick reminder.

tldr curl     # shows: download file, follow redirects, POST data
tldr docker   # shows: run container, build image, list containers, stop all

Coverage can be sparse for less common tools. When tldr sometool comes back empty, man sometool | bat -l man — using bat as a colourised man page viewer — is the next option.

Check this before moving on

  • rg --version returns a version number without error
  • bat src/index.ts (or any source file you have) shows syntax-highlighted output with line numbers
  • tldr curl returns a short, example-focused page

Two ways to understand HTTP you’re already sending

httpie is a command-line HTTP client with a syntax that reads like a sentence. Compare making the same POST request:

# curl — the standard approach
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"alice","role":"admin"}'

# httpie — the same request
http POST api.example.com/users name=alice role=admin

Both send identical JSON. httpie encodes key=value pairs as JSON automatically, formats and colours the response by default, and handles the Content-Type header for you. If you’re building and testing endpoints as you go — which comes up constantly in work like building a REST API with Node.js and Express — httpie saves a lot of quoting and header-typing compared to curl.

mitmproxy is an interactive HTTPS proxy you run locally. Set your device or browser to route traffic through it, and you can watch every request and response in real time — including the ones your apps make silently in the background.

brew install mitmproxy
mitmproxy -p 8080   # starts the interactive terminal UI on port 8080

The most useful thing I’ve done with it: configured a phone to use mitmproxy as its HTTP proxy, installed the mitmproxy CA certificate on the device, then opened a third-party app and watched exactly which API calls it made. No source code required.

The certificate setup is where it gets tricky. Android 7 and later don’t trust user-installed CA certificates by default in production apps. To work around it you need to install the cert at the system level, which requires a rooted device or an emulator with a writable /system partition. On iOS and desktop browsers the setup is more straightforward.

mitmproxy also supports a Python scripting API for automating changes — for example, intercepting responses and replacing them with fixture data during local development, without touching your backend.

The terminal tools for navigation and git

zoxide is a smarter cd. It tracks which directories you visit and scores them by frequency and recency. After a few visits, you can jump to any directory with one fragment:

# Jump to your most-visited directory matching "backend"
z backend

# Narrow by two fragments
z proj api

# Open an fzf picker showing all matching directories
zi backend

The zi variant is what I actually use most. It pairs with fzf to give you an interactive picker that filters as you type. Zoxide works on bash, zsh, fish, PowerShell, and most other major shells.

lazygit is a terminal UI for git. Two things changed how I work with it.

The first is line-level staging. In lazygit you navigate to a diff, press enter to expand a file, then press space on any individual line to stage just that line. Without lazygit the equivalent requires git add -p, stepping through hunks manually, and editing a patch file by hand when a hunk can’t be split further. If you’re comfortable with the underlying git model (the git basics post covers that foundation) but find the CLI friction tedious, lazygit removes it.

The second is interactive rebase. Press i on a commit to start an interactive rebase, then s to squash, f for fixup, or d to drop. The rebase view shows the commit graph with live feedback — you’re not editing a git-rebase-todo file in your text editor. This pairs well with keeping your commit history clean before merging, which matters more when you have automated pipelines like the GitHub Actions setup covered here.

brew install lazygit

# Then inside any git repo
lazygit

The API client and database tool worth switching to

Bruno stores API collections as .bru files on your filesystem. Your collections live in your project folder, tracked by git, visible in pull requests. No account required, no cloud sync, no proprietary binary format.

brew install bruno

A request file in Bruno’s Bru markup looks like this:

meta {
  name: Create user
  type: http
  seq: 1
}

http {
  method: POST
  url: {{baseUrl}}/users
  body: json
}

body:json {
  {
    "name": "alice",
    "role": "admin"
  }
}

You can diff that the same way you’d diff application code. When a teammate checks out the branch, they have the exact same collection in the same state. For API-heavy work — the kind you do when building a REST API — having your test requests versioned alongside the handlers that respond to them is genuinely useful.

Bruno also ships a CLI (bru run) for running collections in automated pipelines, so the same requests you use during development can be run as part of a CI job.

Beekeeper Studio is a cross-platform database GUI with a free open-source edition. It connects to PostgreSQL, MySQL, SQLite, and several other databases from one interface.

brew install --cask beekeeper-studio

The part I find useful for daily development is the query editor with inline results and one-click export. You run a query, see the results in a table, and export to CSV or JSON without a separate step. Working directly in psql is fine, but Beekeeper removes the formatting work when you’re quickly checking data or debugging a query. The interface is clean enough that you can point a new team member at a connection string and have them productive in under five minutes, without any psql syntax to learn first.

Stop there and pick three

These tools don’t make you a different developer. They remove friction that was slowing down the developer you already are. The terminal doesn’t have to feel like a compromise — the defaults are just old, and most of these alternatives exist because enough people found the originals genuinely painful.

Start with the friction you hit most often:

Current friction Tool to try first
Code search crawls node_modules or misses results ripgrep
Finding files requires knowing the full path fzf
man pages are too dense for quick reminders tldr
HTTP testing requires memorising curl flags httpie
API collections aren’t tracked in version control Bruno
Interactive rebase means editing a text file lazygit
Database queries go through psql with awkward output Beekeeper Studio

The developer workflow automation post goes deeper on combining tools like these into shell functions and aliases — a natural next step once each tool feels comfortable on its own.

Sources