A cohesive walkthrough for setting up a modern Python 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) - **[[uv]]** — the Python toolchain (what runs your code) - **[[Git]]** — version control (covered in Prerequisites) - **[[VS Code]]** — the editor (Part 7) - **[[Claude Code]]** — the AI, everywhere (Part 8) 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. --- ## 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 7 — 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 Python project, combining [[uv]], git, and GitHub: ```bash # Create and initialize locally cd ~/projects uv init myproject --python 3.13 cd myproject echo ".venv/" > .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 ``` Ten commands, and you have a fully configured project: uv-managed venv, 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 # Works thanks to push.autoSetupRemote = true ``` 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 # View current repo's details gh repo view --web # Open current repo on github.com gh repo clone owner/name # Clone (friendlier than the git URL) gh repo list # List your repos gh pr create # Open a PR from the current branch gh pr list # List open PRs gh issue create # Open a new issue gh issue list # List open issues gh auth status # Check which account you're logged in as ``` --- ## 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 # Browse 300+ built-in themes ghostty +list-fonts # See what fonts Ghostty can see ghostty +list-keybinds --default # All default keybindings ghostty +show-config --default --docs # Full config reference with 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 # Integrates tabs into the titlebar macos-option-as-alt = left # Makes left Option behave as Alt # (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 Python version, active virtualenv, 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 tuned for Python work: ```toml # ── Overall format ──────────────────────────────────────────── # Two-line prompt: info on top, clean input line below. format = """ $directory\ $git_branch\ $git_status\ $python\ $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) )' # Python — shows version AND active venv [python] symbol = " " style = "bold yellow" format = '[${symbol}${pyenv_prefix}(${version})(\($virtualenv\) )]($style)' detect_extensions = ["py"] detect_files = ["pyproject.toml", "requirements.txt", ".python-version"] # How long the last command took (only shows if > 2s) [cmd_duration] min_time = 2000 style = "bold yellow" format = "took [$duration]($style) " # Hide things that aren't interesting for Python work [aws] disabled = true [gcloud] disabled = true [nodejs] detect_files = ["package.json"] # Only show in Node projects ``` ### Why the Python section matters When you `cd` into a directory with a `pyproject.toml`, `.python-version`, or any `.py` file, Starship will show: ``` ~/projects/mydemo main [!] 3.13.2 (mydemo) ``` Reading left-to-right: you're in a git repo on `main` with uncommitted changes, using Python 3.13.2 inside a venv called `mydemo`. No `which python`, no `echo $VIRTUAL_ENV`, no guesswork. This becomes important the moment you're juggling multiple Python versions across different projects — the prompt makes the active environment impossible to miss. ### Useful Starship commands ```bash starship explain # Explains each module in your current prompt starship print-config # Prints the fully-resolved config starship module python # Just the python module in isolation starship preset list # See available presets starship preset gruvbox-rainbow > ~/.config/starship.toml # Apply a preset ``` --- ## Part 3 — Installing uv and Setting Up Python ### What uv is Astral's unified Python toolchain — replaces **pyenv + venv + pip + pip-tools + pipx** with a single fast binary. It manages Python versions, virtual environments, dependencies, and lockfiles. Written in Rust, 10–100x faster than pip for most operations. ### Install ```bash brew install uv ``` Verify: ```bash uv --version ``` ### Install Python versions uv manages Python installations independently of the system Python. You can have multiple versions side-by-side. ```bash uv python install 3.13 # Latest stable uv python install 3.12 # One back uv python install 3.11 # Sometimes needed for older libraries uv python list # See what's installed and available ``` These install to `~/.local/share/uv/python/` — nothing touches system Python. ### Creating a new project ```bash mkdir ~/projects/my-demo cd ~/projects/my-demo uv init --python 3.13 ``` This creates: ``` my-demo/ ├── .python-version # Pins the Python version for this project ├── .gitignore ├── README.md ├── main.py └── pyproject.toml # Dependency spec (modern replacement for requirements.txt) ``` ### Adding dependencies ```bash uv add requests pandas # Runtime deps uv add --dev pytest ruff mypy # Dev-only deps ``` uv automatically: 1. Creates `.venv/` in the project root 2. Installs into it 3. Updates `pyproject.toml` 4. Writes `uv.lock` (a reproducible lockfile — commit this to git) ### Running code ```bash uv run main.py # Runs inside the project venv uv run pytest # Runs pytest from dev-deps uv run python # Drops you into the venv's Python REPL ``` You don't need to `source .venv/bin/activate` unless you want to — `uv run` handles activation per-command. ### Installing CLI tools globally This is uv's replacement for `pipx`: ```bash uv tool install ruff uv tool install ipython uv tool install jupyter uv tool install black # If you need it; ruff format covers most cases ``` These land in `~/.local/bin/` on your `PATH` and are isolated from any project venv. ### Converting an existing pip/requirements.txt project ```bash cd ~/projects/old-project uv init --no-readme . # Initialize without overwriting README uv add -r requirements.txt # Pull deps into pyproject.toml ``` --- ## Part 4 — 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 3.13.2 (demo) │ │ │ │ │ │ ❯ uv run pytest │ │ │ │ │ │ ┌───────────────────────────────────────────┐ │ │ │ │ │ │ │ uv (runs pytest in .venv/) │ │ │ │ │ │ │ └───────────────────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### A day-in-the-life example ```bash # Create a new project cd ~/projects uv init hashing-demo --python 3.13 cd hashing-demo # Prompt now shows: projects/hashing-demo 3.13.2 ❯ # Add the libraries you need uv add cryptography pytest # Prompt updates to show dirty state after pyproject.toml changes: # projects/hashing-demo main [!] 3.13.2 (hashing-demo) ❯ # Write some code, then run it uv run python hash_demo.py # Commit it git add . && git commit -m "Add hashing demo" # Prompt returns to clean: # projects/hashing-demo main 3.13.2 (hashing-demo) ❯ ``` Every piece of the prompt has a job: - **Directory** → where am I - **Git branch + status** → what's the repo state - **Python version + venv** → which interpreter will run my next command --- ## Part 5 — 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: ``` ❯ uv run pytest ← all green (valid commands + args) ❯ uvv run pytest ← "uvv" 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 6 — 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 venvs on `cd` ```bash brew install direnv echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc ``` Then in any project directory: ```bash echo 'source .venv/bin/activate' > .envrc direnv allow ``` Every time you `cd` into that directory, the venv activates automatically. `cd` out and it deactivates. ### VS Code / Cursor integration In VS Code settings (JSON): ```json { "terminal.integrated.defaultProfile.osx": "zsh", "terminal.integrated.fontFamily": "MesloLGM Nerd Font Mono", "terminal.integrated.fontSize": 13, "python.defaultInterpreterPath": ".venv/bin/python", "python.terminal.activateEnvInCurrentTerminal": true } ``` The Microsoft Python extension automatically detects `.venv/` created by uv and uses it as the interpreter. --- ## Part 7 — 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 . # Open current directory as a workspace code myfile.py # Open a single file code -d old.py new.py # Diff two files ``` > [!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 extensions for Python Install these from the command line so they're reproducible: ```bash # Core Python support — language server, debugger, formatter integration code --install-extension ms-python.python code --install-extension ms-python.vscode-pylance code --install-extension ms-python.debugpy # Ruff (linter + formatter, matches the Ruff you installed with uv) code --install-extension charliermarsh.ruff # Jupyter notebooks (optional, but useful for notebooks / demos) code --install-extension ms-toolsai.jupyter code --install-extension ms-toolsai.jupyter-renderers # Better TOML for editing pyproject.toml and starship.toml code --install-extension tamasfe.even-better-toml # YAML (for GitHub Actions, etc.) 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 ``` ### 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": [88, 120], "editor.bracketPairColorization.enabled": true, "editor.guides.bracketPairs": "active", "editor.minimap.enabled": false, "editor.cursorBlinking": "solid", // ── Editor behavior ─────────────────────────────────────── "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.ruff": "explicit", "source.organizeImports.ruff": "explicit" }, "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, // ── Python ──────────────────────────────────────────────── "python.defaultInterpreterPath": ".venv/bin/python", "python.terminal.activateEnvInCurrentTerminal": true, "python.terminal.activateEnvironment": true, "python.analysis.typeCheckingMode": "basic", "python.analysis.autoImportCompletions": true, "python.analysis.inlayHints.functionReturnTypes": true, "python.analysis.inlayHints.variableTypes": false, "python.testing.pytestEnabled": true, "python.testing.unittestEnabled": false, // ── Python files use Ruff for formatting + linting ──────── "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnSave": true, "editor.tabSize": 4 }, "ruff.nativeServer": "on", "ruff.lineLength": 88, // ── Files & search ──────────────────────────────────────── "files.exclude": { "**/__pycache__": true, "**/*.pyc": true, "**/.pytest_cache": true, "**/.ruff_cache": true, "**/.mypy_cache": true }, "search.exclude": { "**/.venv": true, "**/uv.lock": true, "**/node_modules": 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 8 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] Ruff as formatter `editor.formatOnSave: true` combined with `source.fixAll.ruff` means every save auto-formats (like Black), auto-sorts imports (like isort), and applies safe lint fixes. The two-way sync with the `ruff` Claude installed via `uv tool install ruff` is automatic — the VS Code extension calls the `ruff` binary. > [!info] `python.defaultInterpreterPath` Pointing at `.venv/bin/python` means any project made with `uv init` "just works" — VS Code finds the venv, activates it in new terminals, and uses it for Pylance type-checking. No manual interpreter selection per-project. > [!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 8. ### Configure Claude Code as the AI (in VS Code) The rest of Claude Code integration lives in **Part 8** — it applies equally to Ghostty and VS Code, so it's covered in one place. --- ## Part 8 — Claude Code as the Only AI (Ghostty + VS Code) 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 7). - **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 # Should print /Users/you/.local/bin/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 7 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 # Cline 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 If you're setting this up from scratch on a fresh Tahoe install: ```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 uv 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. Python versions uv python install 3.13 3.12 3.11 # 6. Global Python CLI tools uv tool install ruff uv tool install ipython # 7. 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 # 8. Install VS Code Python extensions code --install-extension ms-python.python code --install-extension ms-python.vscode-pylance code --install-extension ms-python.debugpy code --install-extension charliermarsh.ruff code --install-extension ms-toolsai.jupyter code --install-extension tamasfe.even-better-toml code --install-extension redhat.vscode-yaml code --install-extension eamodio.gitlens # 9. Install Claude Code (native installer — auto-updating) curl -fsSL https://claude.ai/install.sh | bash # 10. Install the Claude Code VS Code extension code --install-extension anthropic.claude-code # 11. Wire up zsh echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc # For claude binary 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 # 12. 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 # 13. 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 7 above for the recommended contents) # 14. Authenticate GitHub CLI (interactive — choose HTTPS, browser login) gh auth login gh auth setup-git # 15. 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. --- ## Related notes - [[uv Quick Reference]] - [[zsh Customization]] - [[VS Code Python Setup]] - [[Homebrew Maintenance]] - [[Git Cheat Sheet]] - [[Claude Code Tips]] - [[CLAUDE.md Templates]]