My macOS (Apple Silicon) terminal setup: Ghostty + fish + Neovim (LazyVim), themed with a one-command switcher — theme <name> restyles the terminal, prompt and editor together (Kanagawa Wave by default; also Catppuccin Mocha, TokyoNight, Rose Pine). Plus modern CLI tools, code-quality tooling (SonarLint + SonarQube), and AI assistants (Claude Code, Grok, CodeRabbit).
- Stack
- Requirements
- Installation
- Themes
- Keybindings
- Per-tool notes
- Code quality
- AI assistants
- Tasks (just)
- Updating
- Uninstall
- Notes
Ghostty (terminal) → fish (shell) → Neovim / LazyVim (editor)
`theme` switcher starship prompt LSP + Telescope + SonarLint
JetBrains Nerd Font zoxide · eza · bat ripgrep · fd
blur + transparency fzf · atuin · mise Mason · dap · neotest
theme kanagawa | mocha | tokyonight | rose-pine ← restyles all three
| Layer | Tool | What for |
|---|---|---|
| Terminal | Ghostty | fast GPU terminal, Nerd Font, blur + transparency |
| Shell | fish 4.x | interactive shell (default only in Ghostty; the system stays on zsh) |
| Prompt | starship | prompt with git/language/duration; palette follows the active theme |
| Editor | Neovim + LazyVim | LSP, completion, fuzzy find — a VSCode replacement |
cd |
zoxide | jump to frequent dirs (z <name>) |
ls |
eza | listing with icons + git |
cat |
bat | syntax highlighting |
| search | ripgrep + fd | fast; used by Telescope in nvim |
| history | atuin | fuzzy Ctrl+R shell history, per-dir + synced |
| env | direnv | auto-load per-project env from .envrc |
| runtimes | mise | node/python/go/… versions (replaces nvm); global set in mise/config.toml |
| git | lazygit + delta | git TUI (lg) + pretty diffs |
| tasks | just | repo task runner (see Tasks) |
| quality | SonarLint (nvim) + SonarQube (Docker) | see Code quality |
- macOS on Apple Silicon (Homebrew lives under
/opt/homebrew). - Homebrew — everything else is installed from the
Brewfile. - git and, for the SSH clone below, a GitHub SSH key (or clone over HTTPS).
- A Nerd Font — the
Brewfileinstalls JetBrainsMono Nerd Font; Ghostty is already configured to use it. Other terminals need the font selected manually. - Docker (Docker Desktop or OrbStack) — only for the SonarQube server + its MCP; the rest of the setup works without it.
Fresh machine — one line does the base setup (Homebrew, clone, brew bundle,
symlinks, fish plugins, runtimes):
curl -fsSL https://raw.githubusercontent.com/tomymaritano/dotfiles/main/bootstrap.sh | bashOr step by step:
git clone git@github.com:tomymaritano/dotfiles.git ~/dotfiles
cd ~/dotfiles
# 1) Install everything from Homebrew
brew bundle --file=Brewfile
# 2) Symlink the configs into ~/.config (backs up anything existing)
./install.sh
# 3) fish plugins (fisher)
fish -c 'curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher'
fish -c 'fisher update' # installs what's in fish/fish_plugins
# 4) runtimes via mise (versions pinned in mise/config.toml)
mise install
# 5) Open nvim — LazyVim + Mason install plugins and the SonarLint language server
nvim
# 6) (optional) community Claude Code agents/skills, and the gh dashboard
./claude/install-templates.sh
gh extension install dlvhdr/gh-dashinstall.sh symlinks each config into ~/.config (and a few into ~, like
.gitconfig) and backs up anything already there to *.bak.<timestamp> — so
it's safe to re-run and easy to undo (see Uninstall).
Optional: ./macos/defaults.sh applies opinionated macOS system settings (fast
key repeat, Finder tweaks, screenshots → ~/Screenshots). From then on, just
runs the common tasks (see Tasks).
These are account logins / GUI toggles the bootstrap can't do for you:
- 1Password — sign in, then Settings → Developer → Use the SSH agent.
- Secrets —
cp op/secrets.env.example op/secrets.envand point theop://paths at your items. - GitHub signing key — add your 1Password SSH key to GitHub as a Signing key, then enable signing in
~/.gitconfig.local(see git). -
gh auth loginandclaudelogin (their auth is local, not in the repo). - Claude Code extras —
./claude/install-templates.shandgh extension install dlvhdr/gh-dash. - Neovim — open
nvimonce so LazyVim + Mason install plugins and language servers. - Open a fresh terminal so abbreviations, mise, atuin and the
claudewrapper load.
One command restyles the whole stack — terminal, prompt and editor — at once:
theme kanagawa # Kanagawa Wave (default)
theme mocha # Catppuccin Mocha
theme tokyonight # TokyoNight Moon
theme rose-pine # Rose Pine MoonThe banner accent and starship prompt follow the active theme:
Screenshots: drop one per theme here the same way as the header image — drag a PNG into a GitHub issue/comment to get a
user-attachmentsURL, then replace a cell with.
theme (defined in fish/functions/theme.fish) maps a short name to the
matching Ghostty theme, starship palette and Neovim colorscheme:
theme <name> |
Ghostty | starship palette | Neovim colorscheme | banner accent |
|---|---|---|---|---|
kanagawa |
Kanagawa Wave | kanagawa |
kanagawa-wave |
gold #e6c384 |
mocha |
Catppuccin Mocha | catppuccin_mocha |
catppuccin-mocha |
mauve #cba6f7 |
tokyonight |
TokyoNight Moon | tokyonight |
tokyonight-moon |
blue #82aaff |
rose-pine |
Rose Pine Moon | rose_pine |
rose-pine-moon |
pink #eb6f92 |
- starship — instant, on the next prompt.
- Ghostty — reload with
Cmd+Shift+,(or just open a new window). - Neovim — restart nvim. To preview themes live without committing, use
LazyVim's picker:
<leader>uC(that preview is not persisted;themeis what sticks). - fzf / bat — colors follow the theme too:
themesetsFZF_DEFAULT_OPTSper theme, andbatusesBAT_THEME=ansiso it tracks the terminal palette. Applies to new shells.
- Ghostty (
ghostty/config) and starship (starship.toml) each have one line swapped in place (theme = …/palette = "…"). - Neovim reads a tiny state file,
~/.config/nvim/theme.txt(git-ignored), at startup —nvim/lua/plugins/colorscheme.luamaps its contents to a colorscheme, installs all four theme plugins, and colors the dashboard banner per theme.
To add a theme: add a case in fish/functions/theme.fish, a matching
[palettes.<name>] block in starship.toml, and an entry (plus its plugin +
accent color) in nvim/lua/plugins/colorscheme.lua / nvim/lua/plugins/dashboard.lua.
<leader> is space. These are LazyVim defaults plus this repo's additions —
see lazyvim.org/keymaps for the full list.
| Key | Action |
|---|---|
<leader><space> |
Find files (project root) |
<leader>ff / <leader>fr |
Find files / recent files |
<leader>/ |
Live grep (search in project) |
<leader>, |
Switch buffer |
<leader>e / <leader>E |
File explorer (root / cwd) |
| Key | Action |
|---|---|
gd gr gI gy |
Go to definition / references / implementation / type |
K |
Hover docs |
<leader>ca / <leader>cr |
Code action / rename |
<leader>cf |
Format |
]d / [d |
Next / previous diagnostic |
<leader>cd |
Line diagnostics |
| Key | Action |
|---|---|
<leader>gg |
lazygit |
<leader>gb |
Blame line · ]h / [h next/prev hunk |
<C-h/j/k/l> |
Move between windows |
<leader>| / <leader>- |
Split vertical / horizontal |
<S-h> / <S-l> |
Previous / next buffer · <leader>bd close buffer |
<leader>uC |
Colorscheme picker (live preview) |
<leader>l / <leader>cm |
Lazy / Mason |
| Key | Action |
|---|---|
<leader>ac / <leader>af |
Toggle / focus Claude Code (claudecode.nvim) |
<leader>as (visual) |
Send selection to Claude · <leader>ab add buffer |
<leader>aa / <leader>ad |
Accept / reject Claude's diff |
<leader>H · <leader>1…5 |
Harpoon add file · jump to pinned file |
<leader>t group |
neotest — run/debug tests · <F5> dap continue |
<C-d> / <C-u>, J |
Centered half-page jump, join keeping cursor (custom) |
Custom motions (
<leader>ppaste-keep-yank,<leader>dblackhole-delete,<leader>fDopen config) live innvim/lua/config/keymaps.lua.
| Command | What it does |
|---|---|
v |
nvim |
lg |
lazygit |
ll / lt |
eza long / tree listing |
cat |
bat (syntax highlighting) |
gs / gd |
git status / git diff |
z <dir> |
zoxide jump to a frequent directory |
theme <name> |
switch the whole-stack theme |
sq-up / sq-down / sq-logs |
SonarQube container control |
grokcode |
Claude Code engine on Grok (see below) |
claude |
wrapped to inject X/xAI/SonarQube secrets from 1Password (op run) |
ghd / ghpr |
gh-dash PR/issue dashboard / open a PR |
mise use node@22 |
set a runtime version (global or per project) |
tweet "idea" |
jot a tweet idea to ~/notes/tweets.md |
- Font:
JetBrainsMono Nerd Font(icons for LazyVim). - Theme: set by the
themecommand (see Themes); thetheme = …line uses Ghostty's exact, capitalized theme names. - Translucent background:
background-opacity = 0.85+background-blur-radius = 20(macOS blur). command = /opt/homebrew/bin/fish→ fish only inside Ghostty; the system login shell stays zsh (friendlier with POSIX installers/tooling).- Reload config:
Cmd+Shift+,.
- Loads Homebrew, sets
EDITOR=nvim, adds Java (openjdk@21) to PATH for SonarLint. - Initializes starship, zoxide, fzf, atuin and direnv in interactive sessions only.
- atuin owns
Ctrl+R(fuzzy history). Runatuin import autoonce to seed it from your old history;atuin loginis optional (only for cross-machine sync). - direnv: drop an
.envrcin a project and rundirenv allowonce — vars load/unload as youcdin and out. - mise activates here too; runtimes (node/python/go/…) come from it, not nvm. Global versions live in
mise/config.toml; per project:mise use node@22. - Aliases & functions: see the Keybindings cheatsheet.
git/config→~/.gitconfig: identity, sane defaults (pull.rebase,push.autoSetupRemote,fetch.prune), delta as the diff pager, aliases (git s,git lg,git undo,git pushf, …), and SSH signing wired to 1Password'sop-ssh-sign.- Machine-specific or secret bits (signing key, coderabbit id) live in
~/.gitconfig.local, pulled in via[include]— not tracked. ssh/config→~/.ssh/config: routes auth through the 1Password SSH agent (keys stay in 1Password, Touch ID to use); the on-disk key remains a fallback.- Turn on signed commits (one-time): create an SSH key in 1Password (
op item create --category ssh-key), add its public key to GitHub as a Signing key (Settings → SSH and GPG keys; Authentication type too only if you also want 1Password to handlegit push), then set in~/.gitconfig.local:and add[user] signingkey = ssh-ed25519 AAAA…your key… [commit] gpgsign = true [gpg "ssh"] allowedSignersFile = ~/.config/git/allowed_signers
<your-email> ssh-ed25519 AAAA…to~/.config/git/allowed_signerssogit log --show-signatureverifies locally. git/ignore→~/.gitignore_global;editorconfig→~/.editorconfig.
LazyVim base, personalized (not stock). The custom bits live in:
lua/config/→options.lua,keymaps.lua,autocmds.lua(see Keybindings).lua/plugins/:colorscheme.lua+dashboard.lua— themes & banner (see Themes).lang.lua— LazyVim extras for TS/JS, Python, Go, Rust, Docker, JSON, YAML + Prettier, ESLint and neotest (each: LSP + treesitter + formatter + test/debug adapter, via Mason).editor.lua— nvim-dap (debug), harpoon2 (pin/jump files), octo (GitHub PRs), treesitter-context.claudecode.lua— Claude Code inside nvim under<leader>a.sonarlint.lua— live SonarLint (Code quality).
- Runtimes (Go/Rust compilers, etc.) come from mise, not Mason —
mise use -g go@latest rust@stable. - Language plugin versions are pinned in the tracked
lazy-lock.json; update with:Lazy updatethen commit it.
Two layers that act at different moments: SonarLint (live, in nvim) and SonarQube Server (full analysis, in Docker).
1. SonarLint in nvim (nvim/lua/plugins/sonarlint.lua) — live linting as you type, like VSCode's SonarLint. Runs locally, no server.
- Requires
openjdk@21(Java) andsonarlint-language-server(installed by Mason). - Active for JS/TS/Python/HTML/CSS/XML.
2. SonarQube Server in Docker — full analysis with a dashboard and quality gates. Reproducible via the tracked compose file (sonarqube/docker-compose.yml):
docker compose -f sonarqube/docker-compose.yml up -d-
Dashboard: http://localhost:9000 — initial login
admin/admin(forces a change). -
To analyze a project: create the project in the dashboard → generate a token → from the repo root:
sonar-scanner \ -Dsonar.projectKey=my-project \ -Dsonar.sources=. \ -Dsonar.host.url=http://localhost:9000 \ -Dsonar.token=YOUR_TOKEN
-
Container control:
sq-up/sq-down/sq-logs.
Claude Code can query your SonarQube directly through the official SonarQube MCP server (free, works with Community Build). Once wired, you can ask "fix the SonarQube issues in this file" and Claude pulls the rules/issues itself — no copy-paste.
Configured per user (in ~/.claude.json, not this repo) and reads a
token from the gitignored fish universal var SONARQUBE_TOKEN. One script does
the whole bridge — generate the token, store it, and register the MCP server:
sonarqube/setup-mcp.sh # uses the default admin password
sonarqube/setup-mcp.sh 'my-pass' # if you changed the SonarQube admin passwordIt runs claude mcp add -s user sonarqube … sonarsource/sonarqube-mcp under the
hood. Notes:
host.docker.internal:9000is how the MCP container reaches SonarQube on the host.- Launch
claudefrom fish so${SONARQUBE_TOKEN}is in its environment. - Issues only show up once a project has been analyzed with
sonar-scanner(above). - Check status with
claude mcp list; aUSER-type token is required (the script uses it).
Installed as global CLIs (their auth is local and not in this repo):
- Claude Code —
claude - Grok (xAI, native CLI) —
grok - CodeRabbit —
coderabbit/cr
Tracked here and symlinked into ~/.claude:
CLAUDE.md— global preferences applied in every project (environment, conventions, stack).commands/— slash commands you run:/tweet,/commit.agents/— subagents Claude delegates to:reviewer(correctness/security/reuse review).skills/— capabilities Claude auto-activates by description:changelog(release notes from git).voice.md— tweet voice guide used by/tweet.
Third-party components from aitmpl.com are not vendored — re-fetch them on a new machine with ./claude/install-templates.sh (or just claude-templates): agents ui-ux-designer, devops-engineer, test-engineer, deployment-engineer; skills senior-frontend, frontend-design, ui-ux-pro-max, senior-security, clean-code, react-best-practices.
Inside Neovim, claudecode.nvim bridges to the claude CLI under <leader>a (see Keybindings).
The Claude Code engine can run on xAI Grok via claude-code-router (CCR), a local proxy that translates Anthropic ⇄ OpenAI format. Run it with the grokcode alias:
grokcode # = env CLAUDE_CONFIG_DIR=~/.claude-grok ANTHROPIC_CUSTOM_MODEL_OPTION=grok-4.3 ccr code- Isolated profile (
~/.claude-grok) so it never touches your personalclaudeaccount. ANTHROPIC_CUSTOM_MODEL_OPTION=grok-4.3makes Grok selectable in the/modelpicker (Claude Code rejects non-Claude names otherwise).- Needs
XAI_API_KEYset:set -Ux XAI_API_KEY xai-...(stored in gitignoredfish_variables). ccr/config.json(in this repo) defines the providers/router; it has no key (uses$XAI_API_KEY). Copy it to~/.claude-code-router/config.json.- The model lives at
~/.claude-code-router/config.jsonRouterblock;ccr uiopens a web editor. - Note: unofficial/unsupported by Anthropic; tool-use/agentic editing on Grok can be less reliable than native Claude.
A workflow for drafting eng/product tweets without breaking flow:
-
Capture —
tweet "raw idea"appends it to~/notes/tweets.md(tweetwith no args opens the file). Fast, no context switch. -
Draft — in Claude Code,
/tweet <idea>writes tweet/thread options in a defined voice (claude/voice.md), pulling from the idea or your recent git activity./commitwrites commit messages. Both are tracked inclaude/commands/and symlinked into~/.claude/commands/. -
Post — via MCP (
x/setup-mcp.sh):x-docs(https://docs.x.com/mcp) — official X API docs search; no auth.x-twitter— community poster (Infatoshi/x-mcp) withpost_tweet,reply_to_tweet,quote_tweet,upload_media, … Uses OAuth1 keys from an X app (Read+Write), kept in 1Password and injected at launch byop run— no secrets on disk. Free tier ≈ 500 posts/month.
Just launch
claude(it's wrapped to inject the X secrets from 1Password viaop run). Then/tweet <idea>→ review → "post it". Set theop://paths inop/secrets.env(seeop/secrets.env.example).
Common jobs are wrapped in a justfile — run
just to list them:
| Recipe | What it does |
|---|---|
just install |
symlink configs (./install.sh) |
just brew |
install/update from the Brewfile |
just update |
pull + re-link + brew + fish plugins |
just lint |
shellcheck + stylua --check (same as CI) |
just fmt |
auto-format the Lua config |
just macos |
apply macos/defaults.sh |
just sonar |
start SonarQube + wire the MCP bridge |
Shell scripts and Lua are linted on every push by .github/workflows/lint.yml.
just update # git pull --rebase + ./install.sh + brew bundle + fisher updateInside nvim: :Lazy sync to update plugins (commit the changed lazy-lock.json),
:Mason to manage language servers.
install.sh never deletes — it moves anything it replaces to *.bak.<timestamp>.
To roll back, remove the symlinks and restore the most recent backups, e.g.:
cd ~/.config
rm starship.toml ghostty/config fish/config.fish nvim/init.lua nvim/lua fish/functions/theme.fish
# then restore the backups you want, for example:
mv starship.toml.bak.* starship.toml(The theme.fish symlink is the only file this repo adds to fish/functions/;
fisher's own functions there are left untouched.)
- macOS Apple Silicon (Homebrew under
/opt/homebrew). - No secrets in the repo:
auth.json,.env,fish_variablesandop/secrets.envare in.gitignore. - Secret scanning: a global pre-commit hook (
git/hooks/pre-commit, wired viacore.hooksPath) runs gitleaks on staged changes and blocks the commit if it detects a key/token — a safety net so nothing sensitive ever reaches this public repo. Config + allowlists in.gitleaks.toml; bypass one commit withgit commit --no-verify. - Secrets via 1Password: API keys (X, xAI, SonarQube) live in 1Password, not on disk. The
claudefish function (fish/functions/claude.fish) wraps launches withop run --env-file=op/secrets.envso the MCP servers get their tokens at runtime (falls back to a plain launch if op is unavailable);grokcodedoes the same. Pointop/secrets.envat your items viaop/secrets.env.example. - Keyboard: my Corne (QMK) keymap — combos, macros and custom OLEDs — lives in its own repo, corne-keymap (QMK external userspace), not here. The QMK CLI is
pipx install qmk(Brewfile installspipx). - The active theme lives in tracked files (
ghostty/config,starship.toml), so runningtheme <x>shows up as a diff on those two files — that's expected.