This is the development environment I set up on a new Mac. The machine provides a dependable command-line foundation, each repository declares its own build and test dependencies, and the Pi coding agent is where I do almost all development. I use JetBrains IDEs to review and debug code and to work with databases.
I keep the global installation intentionally small. Tools that define the workstation belong in Homebrew; tools that define a project belong in that project's lock files and tool manifests. This makes the computer easy to recreate without letting global package versions silently change a build.
Computer
I use a 14-inch MacBook Pro with an Apple M5 Max chip and 128 GB of memory. The memory is more important to me than maximizing any single benchmark: I regularly have several Pi sessions, JetBrains IDEs, Docker workloads, browser tests, and local application processes open at the same time.
I keep macOS current and enable FileVault during initial setup. I also sign in to the services I use for work before installing developer tools, particularly 1Password, GitHub, Google Cloud, and JetBrains Toolbox.
macOS and Homebrew
I start by installing Apple's Xcode Command Line Tools. They provide Git and the native build tools expected by many packages.
xcode-select --installI use Homebrew for workstation-level packages. On Apple silicon, I add Homebrew to zsh after installation.
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"These formulae make up the core command-line environment:
brew install cloudflared fnm gh googleworkspace-cli kubectx kubernetes-cli pulumi ripgrep starship tmux uvI install the desktop applications and larger SDKs as casks:
brew install --cask 1password-cli docker-desktop dotnet-sdk gcloud-cli ghostty jetbrains-toolboxI do not pin versions in this bootstrap list. Homebrew provides the workstation tools, while repositories pin the versions that can affect a build.
Terminal environment
I use Ghostty as my terminal. It is a native macOS application with tabs, splits, GPU-accelerated rendering, and support for the keyboard protocol Pi uses for modified key combinations.
Pi recommends one Ghostty binding so Option+Backspace behaves consistently. I add it to ~/Library/Application Support/com.mitchellh.ghostty/config.
keybind = alt+backspace=text:\x1b\x7fmacOS already uses zsh. I add Starship for a compact prompt and initialize both Starship and fnm from ~/.zshrc.
eval "$(fnm env --use-on-cd --shell zsh)"
eval "$(starship init zsh)"I use tmux for application watchers, local servers, tunnels, and any other process that should keep running while Pi continues working. A named session is easy to inspect, reattach, or stop.
tmux new-session -s app
tmux attach-session -t appSource control and credentials
The Xcode Command Line Tools install Git. I configure my identity and use GitHub CLI for authentication, repositories, issues, pull requests, checks, and releases.
git config --global user.name "Your Name"
git config --global user.email "[email protected]"
git config --global init.defaultBranch main
gh auth login
gh auth setup-gitThe 1Password CLI is a core part of the environment rather than an occasional utility. Pi skills use it to inject credentials into short-lived processes, which keeps secrets out of shell history, repositories, and ad hoc environment files.
op signin
op vault listI authenticate service CLIs interactively on the workstation. Workloads and CI use workload identity, service accounts, or repository secrets rather than copying my personal credentials.
Language runtimes
I install runtime managers globally, but let each repository declare the runtime and dependency versions it needs. My primary stacks are TypeScript and JavaScript, Python, and F#/.NET.
Node.js
I use fnm to install and switch Node.js versions. npm ships with Node.js and is also how I install Pi.
fnm install --lts
node --version
npm --versionProjects commit their package manifest and lock file. I normally use npm ci for an existing repository so the installed dependency graph matches the lock file exactly.
Python and uv
I use uv for Python versions, virtual environments, project dependencies, lock files, scripts, and Python command-line tools. I do not manage a separate pyenv, pip, virtualenv, Poetry, or pipx setup.
uv python install
uv init
uv add requests
uv add --dev pytest
uv sync
uv run pytestFor an existing project, uv sync restores the environment and uv run runs Python or a project command inside it. Keeping every Python invocation behind uv makes the selected interpreter and dependency environment explicit.
.NET and F#
The .NET SDK provides the compiler, runtime, templates, test runner, and F# Interactive. Most of my application work in .NET is written in F#.
dotnet --info
dotnet tool restore
dotnet testRepositories restore Paket, FAKE, Fantomas, and other .NET tools through checked-in manifests or build scripts. I avoid installing project build tools globally.
Pi coding agent
The Pi coding agent is my primary development environment. Pi is a terminal coding harness that gives the model repository context and tools for reading, editing, running commands, and verifying the result. Its small core can be extended with AGENTS.md files, skills, prompt templates, extensions, and packages.
After Node.js is available, I install Pi globally and start it from the repository I want to work in.
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
cd ~/repos/example
piInside Pi I run /login and select the OpenAI Codex subscription provider for my ChatGPT Pro account. I use the model selector when I want to change models and keep high reasoning as my normal default.
{
"defaultProvider": "openai-codex",
"defaultModel": "gpt-5.6-sol",
"defaultThinkingLevel": "high",
"theme": "dark",
"enableInstallTelemetry": false
}The global settings file lives at ~/.pi/agent/settings.json. Pi stores sessions by working directory, so I can resume a repository's previous work instead of rebuilding all context from scratch. Long sessions compact automatically while preserving the full JSONL history.
Instructions, skills, and packages
I keep global operating rules in ~/.pi/agent/AGENTS.md and repository-specific rules in each repository's AGENTS.md. These files tell Pi how the project is structured, which package manager to use, how to run tests, and which deployment operations are safe.
I also load a private skills package plus small packages for image generation and client context. Skills add repeatable workflows for GitHub, Google Cloud, Google Workspace, 1Password, Notion, browser testing, documents, infrastructure, and other services without placing every instruction in every prompt. Pi only loads a skill's full instructions when the task needs it.
I review package source before installing it because Pi packages and skills can direct tools with full user permissions. Pi's package documentation describes the trust and installation model.
Daily workflow
I do essentially all development through Pi. I start it in a repository, describe the outcome I want, and discuss the approach first when the work has meaningful design or operational tradeoffs. Pi inspects the repository, makes the changes, runs the relevant tests, and verifies the resulting application or artifact.
I steer the session as new information appears rather than waiting for a large batch of work to finish. For user-facing changes, Pi runs Playwright and captures rendered evidence. For infrastructure work, it previews changes and leaves deployment to the repository's established CI workflow. Git and GitHub CLI keep the branch, commit, pull request, and checks visible throughout the process.
JetBrains IDEs
I install JetBrains Toolbox and use it to manage three IDEs:
- PyCharm for Python and data projects.
- Rider for F#, .NET, and mixed .NET/web solutions.
- WebStorm for TypeScript, JavaScript, and frontend projects.
I use these IDEs primarily to review the code Pi changed: navigating types and usages, inspecting diffs, running a debugger, and understanding an unfamiliar area of a repository. They complement the agent workflow rather than serving as the main place where I type code.
JetBrains also has the best database connection support of any IDE family I have tried. Its database explorer, query console, schema navigation, result viewer, and generated diagrams are a major reason I stay with the JetBrains ecosystem.
Containers and infrastructure
I use Docker Desktop for local containers and Compose environments. I install the application globally, while repositories own their Dockerfiles, Compose files, and image versions.
docker version
docker compose version
docker psThe gcloud CLI handles Google Cloud authentication, configuration, diagnostics, and direct resource inspection.
gcloud auth login
gcloud auth application-default login
gcloud config listFor Kubernetes I use kubectl with kubectx and kubens. kubectx makes the active cluster explicit, while kubens switches the default namespace without repeatedly editing commands.
kubectl config get-contexts
kubectx
kubectx <context>
kubens <namespace>
kubectl get podsI use Pulumi for infrastructure as code. Projects select fully qualified stacks and use previews to review the proposed resource changes. Deployment is normally performed by CI after merge rather than by running an update manually from the laptop.
pulumi login
pulumi stack select <organization>/<project>/<stack>
pulumi previewI install cloudflared for local and administrative Cloudflare Tunnel work. Tunnel configuration itself belongs with the infrastructure that owns it rather than in global shell configuration.
Supporting command-line tools
Pi works best when the shell has small, composable tools. ripgrep searches repositories, jq filters JSON returned by CLIs, and curl is useful for health checks and direct HTTP diagnostics. macOS supplies jq and curl; I add ripgrep through Homebrew.
I also install the Google Workspace CLI (gws). Pi uses it through dedicated skills to work with Gmail, Calendar, Drive, Docs, Sheets, and Slides. Interactive OAuth is enough for a personal workstation.
gws auth login
gws drive files list --params '{"pageSize": 5}'Browser testing is installed per repository with Playwright. Pi uses it for end-to-end tests, screenshots, responsive checks, and visual inspection. Keeping it in the project's package manifest ensures the tests and browser tooling move together.
npm ci
npx playwright install
npx playwright testRepository-owned tools
A new workstation should not require a hand-maintained global installation of every framework used by every repository. After cloning a project, I follow its AGENTS.md and README and let its package managers restore the rest.
- npm restores TypeScript, Tailwind CSS, Playwright, and other Node.js dependencies from the lock file.
- uv restores Python, dbt, Dagster, notebook, test, lint, and formatting dependencies from pyproject.toml and uv.lock.
- dotnet restores NuGet and Paket dependencies, while the local tool manifest restores FAKE, Fantomas, and related tools.
- Docker and Compose provide services such as Postgres, Airbyte, and other repository-specific infrastructure when a project needs them.
This division is important for Pi as well as for people. When every repository contains its own instructions and deterministic restore commands, the agent can reproduce the same environment locally and in CI instead of relying on undocumented global state.
Verify the setup
I finish by opening a new Ghostty window and verifying that the main commands resolve from a clean shell:
git --version
gh --version
op --version
fnm --version
node --version
npm --version
uv --version
dotnet --version
pi --version
docker version
gcloud version
kubectl version --client
kubectx --version
kubens --version
pulumi version
cloudflared --version
gws --version
rg --version
jq --version
tmux -VAt that point the workstation is ready. Cloning a repository and following its checked-in setup instructions supplies the project-specific layer; Pi, the terminal tools, and the IDEs provide the consistent layer across all of them.