# General Development Mac Tahoe Setup
A language-agnostic walkthrough for setting up a modern development environment on an **Apple Silicon Mac running macOS Tahoe (26)**. The pieces work together:
- **Ghostty** — the terminal emulator (what you see)
- **Starship** — the shell prompt (what tells you where you are)
- **Git** — version control (covered in Prerequisites)
- **GitHub CLI** — publishing repos from the command line (covered in Prerequisites)
- **VS Code** — the editor (Part 6)
- **Claude Code** — the AI, everywhere (Part 7)
Each layer is independently swappable, but together they form a fast, native, low-friction stack with a single AI surface across both terminal and editor.
Once this is complete, add language support by following the individual guides:
- [[Python_Development_Mac_Tahoe_Setup]]
- [[C_Development_Mac_Tahoe_Setup]]
- [[Ruby_Development_Mac_Tahoe_Setup]]
- [[Go_Development_Mac_Tahoe_Setup]]
- [[Rust_Development_Mac_Tahoe_Setup]]
Each language guide assumes this general setup is already in place and only adds language-specific tooling.
> [!tip] AI assistance is optional
> Part 7 of this guide sets up Claude Code as an AI assistant. **You can skip Part 7 entirely** and still have a complete, professional dev environment from Parts 1–6. If you prefer a different AI tool (GitHub Copilot, Cursor, Continue, Cline, Aider), Part 7 includes pointers for adapting the setup to those instead.
---
## Prerequisites
Before starting, you should have:
- macOS 26 (Tahoe) on Apple Silicon
- Homebrew installed (`/opt/homebrew` on ARM Macs)
- Git installed (`brew install git` gets you a newer version than Apple's)
- Xcode Command Line Tools (`xcode-select --install` if not already)
If you don't have Homebrew yet:
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
### Configuring Git
Once `git` is installed, set it up before doing anything else — these settings apply globally and only need to be set once per machine.
#### Identity
Every commit is stamped with a name and email. Use the same email you use for GitHub:
```bash
git config --global user.name "Your Name"
git config --global user.email "
[email protected]"
```
> [!tip] If you use multiple identities
> If you commit to both personal and work repos, you can override `user.email` per-repo later with `git config user.email "
[email protected]"` inside that repo's directory.
#### Modern defaults
These have all become standard in 2026 but aren't the default in the `git` that shipped on older systems:
```bash
# Use 'main' as the default branch name for new repos
git config --global init.defaultBranch main
# On 'git pull', fast-forward if possible; otherwise merge (don't rebase)
git config --global pull.rebase false
# On 'git push' for a new branch, automatically set upstream
git config --global push.autoSetupRemote true
# Store credentials in the macOS Keychain (for HTTPS remotes)
git config --global credential.helper osxkeychain
# Colored output in ls-files, status, diff, etc.
git config --global color.ui auto
# Use VS Code as the default editor for commit messages
# (We'll install VS Code in Part 6 — this line can be run now or later)
git config --global core.editor "code --wait"
```
#### Useful quality-of-life config
```bash
# Auto-correct obvious typos like 'git stauts' → 'git status'
git config --global help.autocorrect 20
# Prune deleted remote branches when fetching
git config --global fetch.prune true
# Show diff stats in commit message editor
git config --global commit.verbose true
# Better diff algorithm for code
git config --global diff.algorithm histogram
# Reuse conflict resolutions (magic — turn this on and forget about it)
git config --global rerere.enabled true
```
#### SSH key for GitHub
HTTPS with credential helper works, but SSH is the conventional path for GitHub and avoids repeated auth prompts.
```bash
# Generate a new ED25519 key
ssh-keygen -t ed25519 -C "
[email protected]"
# Accept the default location (~/.ssh/id_ed25519) and set a passphrase
# Start the ssh-agent
eval "$(ssh-agent -s)"
# Configure ~/.ssh/config so macOS remembers the passphrase in Keychain
cat >> ~/.ssh/config <<'EOF'
Host github.com
AddKeysToAgent yes
UseKeychain yes
IdentityFile ~/.ssh/id_ed25519
EOF
# Add the key to the agent + Keychain
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
# Copy the public key to your clipboard — paste it at
# https://github.com/settings/keys
pbcopy < ~/.ssh/id_ed25519.pub
```
Then test:
```bash
ssh -T
[email protected]
# Should say: "Hi yourusername! You've successfully authenticated..."
```
#### Verify
```bash
git config --global --list
```
Should show everything you just set. If you ever need to edit these by hand, they all live in `~/.gitconfig`.
### Publishing a repo to GitHub
`git` itself only manages local repositories and synchronization with remotes that already exist. It cannot create a new repository on GitHub's servers — that requires either the GitHub web UI or the `gh` CLI. The CLI is faster, scriptable, and keeps you in your terminal.
#### Install and authenticate GitHub CLI (one-time)
```bash
brew install gh
gh auth login
```
`gh auth login` walks through an interactive prompt. Recommended answers:
- **GitHub.com** (not Enterprise)
- **HTTPS** as the Git protocol (simpler than SSH for `gh`'s credential management — the macOS Keychain handles it)
- **Login with a web browser** (opens github.com, you paste a one-time code shown in the terminal, done)
After auth, configure `gh` as git's credential helper so plain `git push` works for HTTPS remotes:
```bash
gh auth setup-git
```
> [!info] SSH vs HTTPS for `gh`
> If you already set up SSH keys in the previous section, you can choose SSH during `gh auth login` instead. Either works. HTTPS is slightly less friction because `gh` manages the credentials automatically; SSH is the GitHub convention for daily work and what most tutorials assume.
#### Create a new GitHub repo from an existing local repo
From inside any directory that already has a local git repo with at least one commit:
```bash
gh repo create myproject --public --source=. --remote=origin --push
```
Flag breakdown:
| Flag | Purpose |
|------|---------|
| `myproject` | Name of the new GitHub repo (owner defaults to your authenticated username) |
| `--public` | Visibility. Use `--private` for private, `--internal` for org-only |
| `--source=.` | Use the current directory's existing local repo as the source |
| `--remote=origin` | Name the new remote "origin" (git convention) |
| `--push` | Push the current branch immediately after creating the remote |
Output looks like:
```
✓ Created repository yourusername/myproject on GitHub
✓ Added remote https://github.com/yourusername/myproject.git
✓ Pushed commits to https://github.com/yourusername/myproject.git
```
Verify by opening it in your browser:
```bash
gh repo view --web
```
#### The full new-project workflow
Here's the complete sequence any time you start a new project, combining your language toolchain, git, and GitHub:
```bash
# Create and initialize locally
cd ~/projects
mkdir myproject && cd myproject
# <language-specific init>
# Python: uv init --python 3.13
# Rust: cargo init
# Go: go mod init github.com/you/myproject
# Ruby: rv ruby pin 3.3
# C: touch main.c CMakeLists.txt
# Typical .gitignore (language-specific; each language guide covers what to add)
touch .gitignore
git init
git add .
git commit -m "Initial commit"
# Create on GitHub and push (assumes `gh` is authenticated)
gh repo create myproject --private --source=. --remote=origin --push
```
A few commands, and you have a fully configured project: local environment, git history, and a GitHub remote.
#### Day-to-day after the initial push
No more `gh` needed for routine work — standard git handles it:
```bash
# Edit files...
git add .
git commit -m "Add feature X"
git push
```
Because your `.gitconfig` has `push.autoSetupRemote = true` from the previous section, the first push on any new branch automatically sets the upstream tracking.
#### Alternative: plain git without `gh`
If you don't want to install `gh`, or you need to work with a non-GitHub host (Bitbucket, GitLab, a self-hosted Gitea instance, etc.), the manual path is:
**Step 1 — Create the empty repo on GitHub's web UI:**
1. Go to [github.com/new](https://github.com/new)
2. Enter the repository name
3. Choose Public or Private
4. **Leave all "Add a README/.gitignore/license" checkboxes unchecked** — otherwise you'll create a divergent history that's annoying to reconcile
5. Click "Create repository"
**Step 2 — Wire it up locally:**
```bash
# Using SSH (requires the SSH key setup above)
git remote add origin
[email protected]:yourusername/myproject.git
# Or using HTTPS
# git remote add origin https://github.com/yourusername/myproject.git
# Verify
git remote -v
# Push and set upstream
git push -u origin main
```
The `-u origin main` on the first push is what `push.autoSetupRemote = true` does automatically once the remote exists — for this truly-first push with a manually-added remote, spelling it out explicitly is clearer.
#### Useful `gh` commands
```bash
gh repo view
gh repo view --web
gh repo clone owner/name
gh repo list
gh pr create
gh pr list
gh issue create
gh issue list
gh auth status
```
---
## Part 1 — Installing Ghostty
### Install
Use the Homebrew cask. It pulls the same notarized binary the Ghostty project ships, and it keeps Ghostty updated alongside the rest of your Homebrew stack.
```bash
brew install --cask ghostty
```
Launch it once from `/Applications` or Spotlight so macOS registers it as a known app.
### Config file location
Ghostty reads a single key-value file at:
```
~/.config/ghostty/config
```
Create the directory and file if they don't exist:
```bash
mkdir -p ~/.config/ghostty
touch ~/.config/ghostty/config
```
> [!info] Why `~/.config/` might not exist yet
> Neither Ghostty nor Starship creates `~/.config/` on install. Both tools fall back to internal defaults when no config file is present, and both refuse to modify your filesystem without being asked. On macOS, `~/.config/` is an XDG convention borrowed from Linux and isn't created by the OS. If `ls -la ~/.config` returns "No such file or directory," that's expected — just run the `mkdir -p` above.
>
> Ghostty also supports `~/Library/Application Support/com.mitchellh.ghostty/config` as an alternative on macOS, but `~/.config/ghostty/config` is the recommended and more portable location.
Reload config in-app with `Cmd+Shift+,` after editing. No restart needed.
### Useful introspection commands
Before customizing, these commands show you what's available:
```bash
ghostty +list-themes
ghostty +list-fonts
ghostty +list-keybinds --default
ghostty +show-config --default --docs
```
### A recommended starter config
Paste this into `~/.config/ghostty/config`. Comments explain each section.
```ini
# ── Appearance ────────────────────────────────────────────────
theme = Catppuccin Mocha
font-family = "MesloLGM Nerd Font Mono"
font-size = 14
window-padding-x = 12
window-padding-y = 12
window-padding-balance = true
background-opacity = 0.97
background-blur-radius = 20
# ── Cursor ────────────────────────────────────────────────────
cursor-style = block
cursor-style-blink = false
# Shell integration forces a bar cursor at prompts by default;
# this line preserves your cursor-style choice above.
shell-integration-features = no-cursor,sudo,title
# ── macOS-specific ────────────────────────────────────────────
macos-titlebar-style = tabs
macos-option-as-alt = left
# (critical for shell editing shortcuts)
window-save-state = always
auto-update = check
# ── Scrollback & clipboard ────────────────────────────────────
scrollback-limit = 10000
clipboard-read = allow
clipboard-write = allow
copy-on-select = clipboard
# ── Shell integration ─────────────────────────────────────────
# 'detect' lets Ghostty inject the right bits for zsh/bash/fish/nu
shell-integration = detect
# ── Quality-of-life keybinds ──────────────────────────────────
keybind = cmd+d=new_split:right
keybind = cmd+shift+d=new_split:down
keybind = cmd+w=close_surface
keybind = cmd+shift+enter=toggle_split_zoom
```
### Why each piece matters
> [!info] Font
> **MesloLGM Nerd Font Mono** includes programming glyphs (git branches, folder icons, language logos) that Starship uses. Install via `brew install --cask font-meslo-lg-nerd-font`.
> [!info] `macos-option-as-alt = left`
> Without this, `Option+←/→` for word-wise cursor movement on the command line doesn't work. This single line fixes the most common complaint about macOS terminals.
> [!info] `shell-integration-features = no-cursor`
> Ghostty's shell integration normally overrides your cursor style at the prompt (turning it into a bar). `no-cursor` preserves what you set in `cursor-style` while keeping the other integration features you want.
> [!info] `shell-integration = detect`
> Ghostty auto-injects shell integration code for **bash, zsh, fish, elvish, and nushell**. This enables Cmd-click to jump to prompts, prompt markers, `ssh` terminfo handling, and more — with zero setup.
### Install the Nerd Font
```bash
brew install --cask font-meslo-lg-nerd-font
```
Alternatives you might prefer:
- `font-jetbrains-mono-nerd-font` — clean, very popular
- `font-fira-code-nerd-font` — programming ligatures
- `font-hack-nerd-font` — dense, compact
Swap the `font-family` line in the config accordingly.
---
## Part 2 — Installing and Configuring Starship
### What Starship is
A cross-shell prompt written in Rust. It replaces the default zsh prompt with one that shows **only what matters right now** — current directory, git branch/status, active language version (Python, Rust, Go, Ruby, Node, etc.), and more. It's fast enough to feel instantaneous.
### Install
```bash
brew install starship
```
### Enable in zsh
macOS ships zsh as the default shell. Add Starship's initialization to the **end** of `~/.zshrc`:
```bash
echo 'eval "$(starship init zsh)"' >> ~/.zshrc
```
Then reload:
```bash
source ~/.zshrc
```
You should immediately see a new prompt. If glyphs look like boxes or question marks, your Ghostty font isn't a Nerd Font — revisit [[#Install the Nerd Font]].
### Configure
Starship's config file lives at:
```
~/.config/starship.toml
```
Like Ghostty, Starship won't create this file for you — it uses internal defaults until the file exists. Create it with:
```bash
mkdir -p ~/.config
touch ~/.config/starship.toml
```
To see what Starship is currently using (defaults or your config), run `starship print-config`.
A solid starting config for multi-language development:
```toml
# ── Overall format ────────────────────────────────────────────
# Two-line prompt: info on top, clean input line below.
format = """
$directory\
$git_branch\
$git_status\
$python\
$c\
$rust\
$golang\
$ruby\
$nodejs\
$cmd_duration\
$line_break\
$character"""
# Prompt character (the bit you actually type after)
[character]
success_symbol = "[❯](bold green)"
error_symbol = "[❯](bold red)"
vimcmd_symbol = "[❮](bold green)"
# Directory — keep it short
[directory]
truncation_length = 3
truncate_to_repo = true
style = "bold cyan"
# Git branch
[git_branch]
symbol = " "
style = "bold purple"
# Git status — dirty/clean indicators
[git_status]
style = "bold yellow"
format = '([\[$all_status$ahead_behind\]]($style) )'
# Language modules — each only shows when a relevant file is in the directory
[python]
symbol = " "
style = "bold yellow"
format = '[${symbol}${pyenv_prefix}(${version})(\($virtualenv\) )]($style)'
[c]
symbol = " "
style = "149 bold"
format = '[$symbol($version(-$name) )]($style)'
[rust]
symbol = " "
style = "bold red"
format = '[$symbol($version )]($style)'
[golang]
symbol = " "
style = "bold cyan"
format = '[$symbol($version )]($style)'
[ruby]
symbol = " "
style = "bold red"
format = '[$symbol($version )]($style)'
# How long the last command took (only shows if > 2s)
[cmd_duration]
min_time = 2000
style = "bold yellow"
format = "took [$duration]($style) "
# Hide things not relevant to most dev work
[aws]
disabled = true
[gcloud]
disabled = true
[nodejs]
detect_files = ["package.json"]
```
### Why the language modules matter
When you `cd` into a project directory, Starship detects the language based on files present and shows:
- **Python project** (has `.py`, `pyproject.toml`, or `.python-version`) → Python version + active venv
- **C project** (has `.c` or `CMakeLists.txt`) → Compiler version
- **Rust project** (has `Cargo.toml`) → Rust version
- **Go project** (has `.go` or `go.mod`) → Go version
- **Ruby project** (has `.rb`, `Gemfile`, or `.ruby-version`) → Ruby version
Reading the prompt left-to-right tells you at a glance what language and version you're working with. No `which python`, `go version`, `ruby -v`, or `rustc --version` — the prompt tells you before you type.
This is especially useful when juggling multiple languages across projects in a day — the prompt makes the active toolchain impossible to miss.
### Useful Starship commands
```bash
starship explain
starship print-config
starship module python
starship preset list
starship preset gruvbox-rainbow > ~/.config/starship.toml
```
---
## Part 3 — How It All Fits Together
Here's what a typical workflow looks like with the full stack:
```
┌─────────────────────────────────────────────────────────────┐
│ Ghostty (native macOS app, GPU-rendered, sub-2ms latency) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ zsh (macOS default shell) │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Starship prompt │ │ │
│ │ │ ~/projects/demo main <lang version> │ │ │
│ │ │ ❯ <your commands> │ │ │
│ │ │ ┌───────────────────────────────────────────┐ │ │ │
│ │ │ │ Language toolchain (uv, cargo, go, etc.) │ │ │ │
│ │ │ └───────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### A day-in-the-life example
Regardless of which language guide you follow next, the pattern is the same:
```bash
# Create a new project
cd ~/projects
mkdir demo && cd demo
# <language-specific init> (uv init, cargo new, go mod init, etc.)
# Prompt shows: projects/demo <lang version> ❯
git init
git add .
git commit -m "Initial commit"
# After editing files, the prompt updates to show dirty state:
# projects/demo main [!] <lang version> ❯
# Commit, push, and the prompt returns to clean
git add . && git commit -m "Add feature X"
gh repo create demo --private --source=. --remote=origin --push
```
Every piece of the prompt has a job:
- **Directory** → where am I
- **Git branch + status** → what's the repo state
- **Language version** → which toolchain will run my next command
---
## Part 4 — Colors: Terminal vs Shell vs Prompt
A common point of confusion: **setting a Ghostty theme does not give your `ls` output colors for directories, executables, and symlinks.** These are three separate layers, and each one has to be configured independently.
### The three layers
| Layer | What it controls | Configured in |
|-------|------------------|---------------|
| **Terminal palette** | The 16 ANSI color slots + foreground/background. "When a program asks for color 34, render it as *this blue*." | Ghostty `theme = ...` |
| **File-type colors** | Which color `ls` uses for directories, symlinks, executables, etc. | `LSCOLORS` (macOS) or `eza` / `lsd` |
| **Syntax coloring at prompt** | Highlighting of commands as you type them (green = valid command, red = typo) | zsh plugins |
A default Ghostty install with no shell tweaking will give you:
- ✅ Colored output from programs that emit ANSI codes (like `git status`, compiler errors, `pytest`)
- ❌ Plain white `ls` output
- ❌ Plain white text as you type commands
Ghostty's maintainers are explicit about this:
> [!quote]
> Ghostty lets you configure a color theme. Meanwhile features like colored prompts and command coloring are generally part of your shell, not the terminal.
So the theme in your Ghostty config determines *what "blue" looks like*, but your shell config determines *whether directories are shown in blue at all*.
### Fixing `ls` colors on macOS
macOS ships BSD `ls`, which doesn't colorize by default. Two options:
**Option A — Keep BSD `ls`, enable colors**
Add to `~/.zshrc`:
```bash
# Enable colored ls output
export CLICOLOR=1
# Customize which colors are used (see "LSCOLORS decoded" below)
export LSCOLORS="ExGxBxDxCxEgEdxbxgxcxd"
# Alias ls to always use color
alias ls='ls -G'
```
Then `source ~/.zshrc` and `ls` will show directories in blue, executables in red, symlinks in magenta, etc.
**LSCOLORS decoded.** The string is 11 pairs of letters. Each pair is `(foreground)(background)` for one file type, in this order:
| Position | File type | Default |
|----------|-----------|---------|
| 1 | Directory | `Ex` (bold blue) |
| 2 | Symbolic link | `Gx` (bold cyan) |
| 3 | Socket | `Bx` (bold red) |
| 4 | Pipe | `Dx` (bold brown) |
| 5 | Executable | `Cx` (bold green) |
| 6 | Block special | `Eg` |
| 7 | Character special | `Ed` |
| 8 | Executable with setuid | `xb` |
| 9 | Executable with setgid | `xg` |
| 10 | Dir writable by others, sticky | `xc` |
| 11 | Dir writable by others, no sticky | `xd` |
Color letters: `a`=black, `b`=red, `c`=green, `d`=brown, `e`=blue, `f`=magenta, `g`=cyan, `h`=white. Capitalize for bold. `x` = default.
There's a visual generator at [geoff.greer.fm/lscolors](https://geoff.greer.fm/lscolors/) if you want to tune this interactively.
**Option B — Replace `ls` with `eza`** (recommended)
`eza` is a modern `ls` replacement with richer colors, git status columns, and icons. This is what the main guide already recommends in Part 6.
```bash
brew install eza
```
Then in `~/.zshrc`:
```bash
alias ls='eza --group-directories-first --icons'
alias ll='eza -lah --group-directories-first --git --icons'
alias tree='eza --tree --level=3 --git-ignore --icons'
```
With `eza`, directories are colored, executables are colored, git status shows as an extra column (`N` = new, `M` = modified, `I` = ignored), and file type icons appear next to filenames (requires a Nerd Font, which you've already installed).
### Fixing command coloring at the zsh prompt
By default, commands you type into zsh are all one color (usually white). To get:
- **Green** for valid commands on your `PATH`
- **Red** for typos / unknown commands
- **Grey ghost text** suggesting completions from history
Install two zsh plugins:
```bash
brew install zsh-syntax-highlighting zsh-autosuggestions
```
Then add to the **end** of `~/.zshrc`, *after* the `starship init` line:
```bash
# Autosuggestions (grey ghost text from history)
source $(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh
# Syntax highlighting (green for valid commands, red for typos)
# IMPORTANT: this must be the LAST thing sourced in .zshrc
source $(brew --prefix)/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
```
Reload with `source ~/.zshrc`. You'll immediately see:
```
❯ git status ← all green (valid commands + args)
❯ gti status ← "gti" in red (typo), rest uncolored
❯ git sta ← "git" green, "sta" grey ghost text (press → to accept "status")
```
> [!warning] Order matters
> `zsh-syntax-highlighting` **must be the last thing sourced** in `.zshrc`. If you add more plugins later, put them before it.
### Testing your color setup
To confirm Ghostty is rendering the full 256-color palette correctly:
```bash
# Quick 16-color test
for i in {0..15}; do printf "\e[48;5;${i}m %2d \e[0m" $i; done; echo
# 256-color test (nice gradient)
for i in {0..255}; do printf "\e[48;5;${i}m \e[0m"; done; echo
# True color (24-bit) test
awk 'BEGIN{ for (i=0; i<77; i++) { r=255-(i*255/76); g=(i*510/76); b=(i*255/76); if (g>255) g=510-g; printf "\033[48;2;%d;%d;%dm ", r,g,b } printf "\033[0m\n" }'
```
If all three produce smooth color strips, you're set — Ghostty supports full truecolor out of the box.
### Summary of what you get from each piece
| Result | Requires |
|--------|----------|
| A dark, pleasant background and nice base colors | Ghostty `theme = Catppuccin Mocha` |
| `git status` showing red/green for changes | Nothing — git emits ANSI, Ghostty renders it |
| `ls` showing blue directories, green executables | `export CLICOLOR=1` + `LSCOLORS=...`, or use `eza` |
| File type icons next to filenames | `eza --icons` + a Nerd Font |
| Green/red coloring as you type commands | `zsh-syntax-highlighting` plugin |
| Grey ghost-text command suggestions | `zsh-autosuggestions` plugin |
| `~/projects/demo main 3.13.2` prompt | Starship + Nerd Font |
The theme is just the paint palette. The shell tells each tool which colors to reach for.
---
## Part 5 — Optional Enhancements
### Modern CLI replacements
```bash
brew install eza bat ripgrep fd fzf zoxide
```
Then add to `~/.zshrc`:
```bash
# Modern replacements
alias ls='eza --group-directories-first'
alias ll='eza -lah --group-directories-first --git'
alias tree='eza --tree --level=3 --git-ignore'
alias cat='bat --style=plain'
# zoxide (smarter cd — learns your most-visited dirs)
eval "$(zoxide init zsh)"
alias cd='z'
# fzf (fuzzy finder — powers Ctrl+R history, Ctrl+T file picker)
source <(fzf --zsh)
```
After running this, `Ctrl+R` becomes a fuzzy-searchable command history, and `z proj` jumps to the most-used directory matching "proj".
### direnv — auto-activate per-project environments
```bash
brew install direnv
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
```
In any project directory, create a `.envrc` file that runs whenever you `cd` in:
```bash
# Example: activate a Python venv automatically
echo 'source .venv/bin/activate' > .envrc
direnv allow
```
Other common `.envrc` patterns per language:
```bash
# Python (uv-managed venv)
echo 'source .venv/bin/activate' > .envrc
# Node.js (load .nvmrc)
echo 'use node' > .envrc
# Rust (use a specific toolchain)
echo 'use rust stable' > .envrc
# Environment variables for any project
echo 'export DATABASE_URL=postgres://localhost/mydb' > .envrc
```
Every time you `cd` into that directory, the environment activates automatically. `cd` out and it deactivates.
---
## Part 6 — Visual Studio Code
### Install
```bash
brew install --cask visual-studio-code
```
This installs the latest stable VS Code to `/Applications`, and — importantly — adds the `code` CLI shim to your `PATH`. That means you can open files and folders from Ghostty:
```bash
code .
code myfile.py
code -d old.py new.py
```
> [!info] Why the cask, not the App Store
> The Homebrew cask pulls the same official Microsoft build, keeps it updated via `brew upgrade`, and installs the `code` CLI automatically. The App Store version of VS Code doesn't exist; third-party "VS Code" apps there are unrelated.
### Essential general-purpose extensions
Install these from the command line so they're reproducible. These apply regardless of which languages you work in:
```bash
# Better TOML (for editing pyproject.toml, Cargo.toml, starship.toml)
code --install-extension tamasfe.even-better-toml
# YAML (for GitHub Actions, docker-compose, Kubernetes)
code --install-extension redhat.vscode-yaml
# GitLens — richer git integration than the built-in
code --install-extension eamodio.gitlens
# Docker (if you use containers in your workflow)
code --install-extension ms-azuretools.vscode-docker
# EditorConfig — respects .editorconfig files in projects
code --install-extension editorconfig.editorconfig
```
Language-specific extensions are covered in each language's individual setup guide.
### Configure VS Code — `settings.json`
VS Code stores its user settings at:
```
~/Library/Application Support/Code/User/settings.json
```
Open it from inside VS Code via `Cmd+Shift+P` → "Preferences: Open User Settings (JSON)". Replace or merge with:
```json
{
// ── Editor appearance ─────────────────────────────────────
"editor.fontFamily": "'MesloLGM Nerd Font Mono', Menlo, monospace",
"editor.fontSize": 13,
"editor.fontLigatures": false,
"editor.lineNumbers": "on",
"editor.renderWhitespace": "boundary",
"editor.rulers": [100],
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": "active",
"editor.minimap.enabled": false,
"editor.cursorBlinking": "solid",
// ── Editor behavior ───────────────────────────────────────
"editor.formatOnSave": true,
"editor.tabSize": 4,
"editor.insertSpaces": true,
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
// ── Terminal (integrated) ─────────────────────────────────
"terminal.integrated.defaultProfile.osx": "zsh",
"terminal.integrated.fontFamily": "'MesloLGM Nerd Font Mono'",
"terminal.integrated.fontSize": 13,
"terminal.integrated.cursorBlinking": false,
"terminal.integrated.cursorStyle": "block",
"terminal.integrated.inheritEnv": true,
// ── Files & search ────────────────────────────────────────
// Common clutter to hide from the explorer; language-specific
// additions go in the per-language guides.
"files.exclude": {
"**/.DS_Store": true,
"**/.git": false
},
"search.exclude": {
"**/node_modules": true,
"**/dist": true,
"**/build": true,
"**/target": true,
"**/.venv": true
},
// ── Git ───────────────────────────────────────────────────
"git.autofetch": true,
"git.confirmSync": false,
"git.enableSmartCommit": true,
"git.suggestSmartCommit": false,
// ── Workbench & theme ─────────────────────────────────────
"workbench.colorTheme": "Default Dark Modern",
"workbench.iconTheme": "vs-seti",
"workbench.startupEditor": "none",
"workbench.editor.enablePreview": false,
// ── Telemetry ─────────────────────────────────────────────
"telemetry.telemetryLevel": "off",
// ── AI: disable built-in AI features ──────────────────────
// This disables Copilot prompts and any built-in AI so Claude Code
// is the only AI surface. See Part 7 for the rationale.
"chat.disableAIFeatures": true
}
```
### Why each block matters
> [!info] Font matching
> Using the same **MesloLGM Nerd Font Mono** as Ghostty means your integrated terminal inside VS Code looks identical to your standalone Ghostty windows. Same Starship prompt, same glyphs, same feel.
> [!info] `chat.disableAIFeatures: true`
> VS Code ships with built-in AI chat features (tied to GitHub Copilot subscriptions). Setting this to `true` hides them entirely, ensuring there's no accidental routing of code or prompts through GitHub/OpenAI. This prepares the environment for Claude Code to be your only AI — covered in Part 7.
> [!info] Language-specific settings go in per-language guides
> `settings.json` supports language-scoped blocks like `"[python]": { ... }` that only apply to that language's files. Each language guide adds its own block to this file. They don't conflict — you can have Python, Rust, Go, Ruby, and C blocks all present simultaneously.
### Configure Claude Code as the AI (in VS Code)
The rest of Claude Code integration lives in **Part 7** — it applies equally to Ghostty and VS Code, so it's covered in one place.
---
## Part 7 — Claude Code as the Only AI (Ghostty + VS Code)
> [!important] AI assistance is entirely optional
> **Everything in Parts 1–6 stands on its own.** Ghostty, Starship, Git, GitHub CLI, VS Code with language extensions, linters, formatters, and debuggers form a complete professional development environment. You can stop after Part 6 and have a fully functional setup — that's what coding looked like before AI assistants existed, and it still works fine. Skip this part entirely if you don't want an AI in your workflow, or come back to it later.
> [!note] If you use a different AI tool
> This guide configures Claude Code specifically. **The general principle — install your AI of choice, configure VS Code so it doesn't fight other AI tools — still applies, but the specific commands differ.** Quick pointers for the major alternatives:
>
> - **GitHub Copilot**: Install via the `github.copilot` and `github.copilot-chat` VS Code extensions. Sign in through VS Code's GitHub account integration (no separate CLI). Requires a Copilot subscription. **Remove `"chat.disableAIFeatures": true` from settings.json** — Copilot uses VS Code's built-in chat UI that this setting hides.
> - **Cursor**: Cursor is a separate editor (a fork of VS Code), not an extension. Install Cursor from cursor.com instead of, or alongside, VS Code. Your VS Code extension list and settings.json mostly port over.
> - **Continue**: Open-source, multi-provider. Install the `continue.continue` VS Code extension and configure your preferred model provider (Anthropic API, OpenAI, local Ollama, etc.) in its config file.
> - **Cline** (formerly Claude Dev): Install the `saoudrizwan.claude-dev` VS Code extension. Brings your own API key for Anthropic, OpenAI, or others.
> - **Aider**: Terminal-based, like Claude Code. Install with `pip install aider-chat` (or `uv tool install aider-chat`). Configure with API keys for your provider of choice.
>
> Whichever you pick, the "uninstall competing extensions" advice still applies: pick one AI surface and remove the others to avoid dueling autocomplete suggestions and conflicting chat UIs.
This section sets up Claude Code so it's your single AI surface across both the terminal (Ghostty) and the editor (VS Code). No Copilot, no Cursor, no Continue — just Claude Code, used two ways.
### What you're setting up
| Surface | How Claude Code appears |
|---------|-------------------------|
| **Ghostty** | Run `claude` inside any project directory → interactive terminal agent with file edits, shell commands, git operations |
| **VS Code** | Spark icon in Activity Bar → sidebar chat with inline diffs, `@`-file mentions, plan mode, accept/reject buttons |
Both surfaces share the **same auth**, **same `~/.claude/` config**, and **same `CLAUDE.md` project files**. You can start a conversation in one and continue it in the other.
### Prerequisites
- An **Anthropic account** with access to Claude Code. Claude Pro ($20/month) or Claude Max is the straightforward path; API credits via Anthropic Console also work.
- **VS Code 1.98.0 or newer** (covered in Part 6).
- **Ghostty** (covered in Part 1).
> [!warning] The free Claude.ai plan does not include Claude Code
> Any paid Claude subscription (Pro, Max, Teams, Enterprise) does, and so does Anthropic Console with API credits.
### Install Claude Code
Anthropic now ships a **native binary** as the recommended install — no Node.js dependency, auto-updates in the background:
```bash
curl -fsSL https://claude.ai/install.sh | bash
```
The binary lands at `~/.local/bin/claude`. Verify it's on your PATH:
```bash
which claude
claude --version
```
If `which claude` returns nothing, add the install location to your PATH. Add this to `~/.zshrc`:
```bash
export PATH="$HOME/.local/bin:$PATH"
```
Then `source ~/.zshrc`.
> [!info] Why not Homebrew?
> A `brew install --cask claude-code` cask exists and works, but Homebrew installs lag behind Anthropic's release cycle (sometimes by several days), and Homebrew installs **don't auto-update** — you'd need to run `brew upgrade claude-code` manually. The native installer auto-updates silently in the background, which is what you want for an AI tool that improves weekly.
>
> The npm install method (`npm install -g @anthropic-ai/claude-code`) still works but is deprecated — Anthropic explicitly recommends against it now.
### First-run authentication
From Ghostty, in any project directory:
```bash
cd ~/projects
claude
```
On first launch Claude Code opens a browser window for OAuth login. Sign in with the same Anthropic account as your Claude Pro/Max subscription. The token is saved to `~/.claude/` and reused across both Ghostty and VS Code.
### Using Claude Code in Ghostty
Inside any project, start a session:
```bash
cd ~/projects/my-demo
claude
```
You'll drop into an interactive prompt. Useful built-in commands:
```
/help List all slash commands
/init Generate a CLAUDE.md for this project (do this first!)
/clear Clear conversation context
/rewind Restore to a checkpoint (undo Claude's changes)
/plugins Browse and install plugins
/terminal-setup Auto-configure Shift+Enter for multi-line prompts
/exit Leave the session
```
Claude can read and edit files, run shell commands (with your permission per-command), use `git`, and manage its context automatically. Every code change is checkpointed — press **Esc twice** or run `/rewind` to undo.
> [!tip] Make CLAUDE.md your first step in any project
> `/init` scans your project and writes a `CLAUDE.md` with project structure, conventions, and commonly-used commands. Claude loads this file at the start of every session. Commit it to git so it's shared with anyone else working on the repo (or with yourself on another machine).
### Installing the VS Code extension
Two paths — both end at the same place:
**Path A — From inside VS Code (recommended):**
1. `Cmd+Shift+X` to open Extensions
2. Search **"Claude Code"** — install the one published by **Anthropic** (avoid look-alikes)
3. Reload VS Code if prompted
**Path B — From Ghostty:**
```bash
code --install-extension anthropic.claude-code
```
Or just run `claude` inside a VS Code integrated terminal once — Claude Code auto-installs its extension when it detects a VS Code environment.
### First use in VS Code
After install, you'll see new UI elements:
- **Spark icon** in the Activity Bar (left sidebar) → opens the sessions list
- **Spark icon** in the top-right editor toolbar (only when a file is open) → quick-open Claude
- **"✱ Claude Code"** in the status bar (bottom-right) → works with no file open
Click any of them. First launch prompts you to sign in (reuses the same Anthropic auth as the CLI — one browser redirect and you're done).
Once signed in:
- Type into the prompt box at the bottom of the sidebar
- `@filename` to attach specific files as context
- Highlight code → right-click → "Add to Claude Code context"
- Changes appear as **inline diffs** in the editor — accept per-hunk or per-file
### Making Claude Code the only AI
VS Code ships with **built-in AI features tied to GitHub Copilot**, and the extension ecosystem has other AI tools (Cline, Continue, Cody, etc.). To keep Claude Code as the single AI surface:
**1. Disable VS Code's built-in AI** (done in Part 6 via `settings.json`):
```json
"chat.disableAIFeatures": true
```
This hides Copilot prompts and the entire built-in chat UI, even if someone later installs the Copilot extension.
**2. Don't install (or uninstall) competing extensions:**
```bash
# If any of these exist, remove them:
code --uninstall-extension github.copilot
code --uninstall-extension github.copilot-chat
code --uninstall-extension continue.continue
code --uninstall-extension saoudrizwan.claude-dev
code --uninstall-extension sourcegraph.cody-ai
```
**3. In Ghostty, don't wire in competing CLIs.** Claude Code is sufficient — avoid installing Aider, Gemini CLI, or similar tools alongside it if the goal is a single AI.
**4. Don't sign into GitHub Copilot at the organization level** in VS Code. The setting above blocks the UI, but keeping Copilot unauthorized at the account level is belt-and-suspenders.
### Project setup pattern
Any time you create a new project:
```bash
# Create the project with uv
cd ~/projects
uv init my-new-demo --python 3.13
cd my-new-demo
# Initialize git and make first commit
git init
echo ".venv/" > .gitignore
git add .
git commit -m "Initial commit"
# Start Claude Code and generate CLAUDE.md
claude
> /init
> Now plan out the structure for a [whatever the project does]
# Open in VS Code in another window — Claude Code is already available there too
code .
```
The `CLAUDE.md` file that `/init` generates is the key piece. It's plain markdown; edit it to capture project-specific context Claude should always know:
```markdown
# Project: hashing-demo
## Purpose
Demo code exploring cryptographic hashing primitives.
## Conventions
- Python 3.13 via uv
- Format with `ruff format`, lint with `ruff check --fix`
- Tests in `tests/`, run with `uv run pytest`
- All public functions include type hints and docstrings
## Commands
- `uv run python hash_demo.py` — run the main demo
- `uv run pytest` — run test suite
- `uv run ruff check --fix . && uv run ruff format .` — lint + format
## Out of scope
- Don't include cleartext password examples — use `bcrypt` or `argon2` only
```
Claude reads this at the start of every session (both in Ghostty and VS Code), so you don't have to re-explain conventions each time.
### Ghostty vs VS Code — when to use which
| Use **Ghostty + `claude`** when | Use **VS Code extension** when |
|---------------------------------|--------------------------------|
| Exploring a new repo you haven't opened yet | Reviewing changes with inline diffs |
| Bulk refactors spanning many files | Editing a specific function with tight feedback |
| Running tests/builds interactively | Using `@mentions` to pull specific files into context |
| SSH'd into a remote machine | You want to pair with Claude while you edit |
| Scripting Claude via hooks or headless mode | You're screen-sharing or pair-programming |
They aren't mutually exclusive — it's common to have `claude` running in a Ghostty pane while VS Code is open on the same project. Changes from either surface appear in the other immediately (both are editing the same files on disk).
### Useful extras
**Multi-line prompts in VS Code's integrated terminal** — run this once inside Claude Code:
```
/terminal-setup
```
This configures `Shift+Enter` to insert a newline instead of submitting.
**Browser connection (optional)** — Claude Code can control a Chrome browser for web app testing:
```
/plugins
```
then install "Claude in Chrome" from the Anthropic marketplace.
**Checkpoints** — Claude automatically checkpoints before each change. Undo with:
- Press `Esc` twice (in terminal)
- Run `/rewind` and pick a point
Checkpoints only cover Claude's edits, not your manual edits or bash commands you ran outside Claude. Keep using git commits for the real backup.
---
## Troubleshooting
> [!warning] Glyphs show as boxes or `?` in the prompt
> Your font isn't a Nerd Font. Either install one (`brew install --cask font-meslo-lg-nerd-font`) or remove glyph symbols from `starship.toml`.
> [!warning] `Option+←` doesn't jump words
> Add `macos-option-as-alt = left` to Ghostty config.
> [!warning] Cursor stays as a bar even though I set `cursor-style = block`
> Shell integration is overriding it. Add `no-cursor` to `shell-integration-features`.
> [!warning] `uv: command not found` after install
> Restart Ghostty, or run `source ~/.zshrc`. If still missing, check `brew --prefix`/bin is on PATH.
> [!warning] Python version not showing in Starship
> Starship only shows the Python module when it detects a Python project — you need a `.py` file, `pyproject.toml`, or `.python-version` in the directory. This is intentional: prevents clutter in non-Python dirs.
> [!warning] `claude: command not found` after install
> The native installer puts the binary at `~/.local/bin/claude`. If your shell can't find it, add `export PATH="$HOME/.local/bin:$PATH"` to `~/.zshrc` and `source ~/.zshrc`. A stale npm install can also shadow the native binary — check with `which -a claude` and remove any `/opt/homebrew/bin/claude` or npm-global version.
> [!warning] Claude Code extension says "CLI not found" in VS Code
> The extension expects the `claude` binary on `PATH`. Make sure you can run `claude --version` from Ghostty first — once that works, restart VS Code.
> [!warning] Copilot prompts keep appearing in VS Code even after disabling
> Check `~/Library/Application Support/Code/User/settings.json` — `chat.disableAIFeatures` must be `true`. Also run `code --list-extensions | grep -i copilot` and uninstall anything that turns up.
> [!warning] `git push` asks for username/password on every push
> You're using HTTPS remotes. Either set up SSH (see Prerequisites → SSH key for GitHub) or run `git config --global credential.helper osxkeychain` and push once to cache.
> [!warning] `gh: command not found` or `gh auth login` hangs
> Verify install with `which gh`. If it's missing, `brew install gh`. If the browser login flow hangs, try `gh auth login --web` explicitly, or choose the "Paste an authentication token" option and generate a token at [github.com/settings/tokens](https://github.com/settings/tokens).
> [!warning] `gh repo create` says "remote origin already exists"
> The local repo already has an `origin` remote pointing somewhere else. Remove it first with `git remote remove origin`, then re-run `gh repo create`.
---
## Summary — the complete install sequence
> [!warning] This is a checklist, not a script
> The block below is **not a shell script you can save and run.** It's a numbered sequence of commands to **copy and paste into Ghostty (or Terminal) one block at a time**, in order. Several steps depend on the previous one having finished, and three steps require you to **manually copy files** before continuing:
>
> - **Step 11** asks you to paste the recommended **Ghostty config** into `~/.config/ghostty/config`
> - **Step 11** also asks you to paste the recommended **Starship config** into `~/.config/starship.toml`
> - **Step 11** also asks you to paste the recommended **VS Code settings** into `~/Library/Application Support/Code/User/settings.json`
>
> Two later steps (12 — `gh auth login`; 13 — `claude` first run) **open a browser window** and require you to complete OAuth login interactively before continuing.
If you're setting this up from scratch on a fresh Tahoe install, work through the block below top to bottom, pasting each numbered section into your terminal and waiting for it to finish before moving on:
```bash
# 1. Homebrew (if not present)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 2. Core tools
brew install git gh starship
brew install eza bat ripgrep fd fzf zoxide direnv
brew install zsh-syntax-highlighting zsh-autosuggestions
# 3. Terminal + font
brew install --cask ghostty
brew install --cask font-meslo-lg-nerd-font
# 4. Editor
brew install --cask visual-studio-code
# 5. Configure git (REPLACE with your actual name and email)
git config --global user.name "Your Name"
git config --global user.email "
[email protected]"
git config --global init.defaultBranch main
git config --global pull.rebase false
git config --global push.autoSetupRemote true
git config --global credential.helper osxkeychain
git config --global color.ui auto
git config --global core.editor "code --wait"
git config --global fetch.prune true
git config --global commit.verbose true
git config --global diff.algorithm histogram
git config --global rerere.enabled true
git config --global help.autocorrect 20
# 6. Install general-purpose VS Code extensions
code --install-extension tamasfe.even-better-toml
code --install-extension redhat.vscode-yaml
code --install-extension eamodio.gitlens
code --install-extension editorconfig.editorconfig
# 7. Install Claude Code (native installer — auto-updating)
curl -fsSL https://claude.ai/install.sh | bash
# 8. Install the Claude Code VS Code extension
code --install-extension anthropic.claude-code
# 9. Wire up zsh
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(starship init zsh)"' >> ~/.zshrc
echo 'eval "$(zoxide init zsh)"' >> ~/.zshrc
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
echo 'source <(fzf --zsh)' >> ~/.zshrc
# Aliases: modern CLI replacements
echo "alias ls='eza --group-directories-first --icons'" >> ~/.zshrc
echo "alias ll='eza -lah --group-directories-first --git --icons'" >> ~/.zshrc
echo "alias tree='eza --tree --level=3 --git-ignore --icons'" >> ~/.zshrc
echo "alias cat='bat --style=plain'" >> ~/.zshrc
echo "alias cd='z'" >> ~/.zshrc
# Colors: enable ls coloring (fallback for when not using eza) + command syntax highlighting
echo 'export CLICOLOR=1' >> ~/.zshrc
echo 'source $(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh' >> ~/.zshrc
# zsh-syntax-highlighting MUST be last
echo 'source $(brew --prefix)/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh' >> ~/.zshrc
# 10. Create config directories and empty config files
# Neither Ghostty nor Starship creates these for you — they use
# built-in defaults until you provide a config file.
mkdir -p ~/.config/ghostty
touch ~/.config/ghostty/config
touch ~/.config/starship.toml
# 11. Paste the Ghostty config into ~/.config/ghostty/config
# and the Starship config into ~/.config/starship.toml
# and the VS Code settings into ~/Library/Application Support/Code/User/settings.json
# (see Parts 1, 2, and 6 above for the recommended contents)
# 12. Authenticate GitHub CLI (interactive — choose HTTPS, browser login)
gh auth login
gh auth setup-git
# 13. On first launch of Claude Code, authenticate with your Anthropic account
# Run `claude` in Ghostty — a browser window will open for OAuth login.
# The same credentials work in the VS Code extension.
```
Open Ghostty, and you're done with the general setup. Pick a language guide to continue:
- [[Python_Development_Mac_Tahoe_Setup]]
- [[C_Development_Mac_Tahoe_Setup]]
- [[Ruby_Development_Mac_Tahoe_Setup]]
- [[Go_Development_Mac_Tahoe_Setup]]
- [[Rust_Development_Mac_Tahoe_Setup]]
---
## Related notes
**Language-specific setup (build on this general guide):**
- [[Python_Development_Mac_Tahoe_Setup]]
- [[C_Development_Mac_Tahoe_Setup]]
- [[Ruby_Development_Mac_Tahoe_Setup]]
- [[Go_Development_Mac_Tahoe_Setup]]
- [[Rust_Development_Mac_Tahoe_Setup]]
**Topic references:**
- [zsh Customization](https://zsh.sourceforge.io/Doc/Release/index.html)
- [Homebrew Maintenance](https://docs.brew.sh/FAQ)
- [Git Cheat Sheet](https://git-scm.com/cheat-sheet)
- [Claude Code Tips](https://code.claude.com/docs/en/best-practices)
- [CLAUDE.md Templates](https://code.claude.com/docs/en/memory)