My old dotfiles ran vim in docker, but now I'm going with the traditional route of installing everything locally.
My dotfiles mostly deal with three things: vim, tmux, and bash. See the "Adding new things" section for more detail about how I manage the files for these things.
Use the OS's recommended package manager to install or update everything mentioned below.
-
If on a mac, install homebrew for use as a package manager.
-
Upgrade to the latest version of bash.
-
Install git if it doesn't already exist.
-
Set up GPG commit signing and tell git about your signing key (
.gitconfighascommit.gpgsign = true). -
Generate a new SSH key for GitHub and then add it to GitHub.
-
Because this repository uses git submodules, clone it recusively:
git clone --recurse-submodules git@github.com:johnnymo87/dotfiles.git. Thencd dotfiles. -
Symlink the necessary files to
~.for x in .bash_profile .bashrc .bashrc.d .config/nvim .gitconfig .gitignore_global .tmux.conf .tmux; do ln -sf $(pwd)/$x ~/$x; done ln -sf $(pwd)/.claude/settings.json ~/.claude/settings.json ln -sf $(pwd)/.claude/statusline.sh ~/.claude/statusline.sh ln -sf $(pwd)/.claude/hooks ~/.claude/hooks # Symlink Claude Code skills (both personal and company skills into ~/.claude/skills/) mkdir -p ~/.claude/skills for skill in $(pwd)/.claude/skills/*/; do [ -d "$skill" ] && ln -sf "$skill" ~/.claude/skills/$(basename "$skill") done for skill in $(pwd)/.claude/skills.private/*/; do [ -d "$skill" ] && ln -sf "$skill" ~/.claude/skills/$(basename "$skill") done # Symlink Claude Code custom slash commands (both personal and company commands) mkdir -p ~/.claude/commands for cmd in $(pwd)/.claude/commands/*.md; do [ -f "$cmd" ] && ln -sf "$cmd" ~/.claude/commands/$(basename "$cmd") done for cmd in $(pwd)/.claude/commands.private/*.md; do [ -f "$cmd" ] && ln -sf "$cmd" ~/.claude/commands/$(basename "$cmd") done -
Install tmux.
-
Install vim.
-
Install ripgrep for faster grepping with ag.vim.
-
Finish installing YouCompleteMe.
- Install prerequisites, follow the instructions specific to your operating system
- Compile YouCompleteMe
cd .vim/pack/foo/start/YouCompleteMe git pull --recurse-submodules origin master python3 install.py --all --verbose
-
Install direnv or delete
.bashrc.d/direnv.bashrc. -
Install pyenv or delete
.bashrc.d/py.bashrc.- For data science things, consider installing miniconda as well. After installing, run
conda init. This will modify some general rc files (e.g..bash_profile). Relocate these changes to.bashrc.d/conda.private.bashrc. (This configuration is shell- and installation-specific, so we don't check it into version control.)
- For data science things, consider installing miniconda as well. After installing, run
-
Install rbenv or delete
.bashrc.d/rb.bashrc. -
Install rust or delete
.bashrc.d/rust.bashrc.curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -
For Elixir:
- If you don't want Elixir, just delete
.bashrc.d/asdf.bashrc. - Otherwise, install asdf.
- Install Elixir with asdf.
asdf plugin add elixir asdf install elixir latest - Install Erlang with asdf.
asdf plugin add erlang asdf install erlang latest - Install mix.
mix local.hex
- If you don't want Elixir, just delete
-
For a prettier shell prompt, install one of these Nerd Fonts and Starship.
- E.g. I'm currently using
font-fira-code-nerd-font.
- E.g. I'm currently using
I use Vim's built-in package management, see :help packages. I submodule all vim plugins in .vim/pack/foo/start/. So to add a new vim plugin, do:
git submodule add <git@github ...> .vim/pack/foo/start/my-new-vim-plugin
To initialize existing submodules (for example, if new ones appear after getting a fresh checkout from origin):
git submodule update --init --recursive
I use tpm for tmux plugins. So to add a new plugin, simply add a set -g @plugin '...' reference to the top of the .tmux.conf file, and press prefix + I (capital i, as in Install) to fetch the plugin.
New *.bashrc files need to be in the .bashrc.d directory, and need to be executable, so do chmod +x .bashrc.d/*.bashrc after adding a new one.
-
Install Claude Code:
curl -fsSL https://claude.ai/install.sh | bash -
Authenticate with Claude Code (restart your shell first to pick up the PATH from
.bashrc.d/claude.bashrc):claudeThen type
/loginand follow the prompts. -
The status line and hooks are pre-configured in
settings.json(symlinked in step 6). -
Claude Code Skills: Skills are packaged instructions that extend Claude's capabilities. This repository manages two types:
Directory Structure:
dotfiles/.claude/ ├── skills/ (version controlled personal skills) └── skills.private/ (NOT version controlled company skills) ~/.claude/ └── skills/ (contains symlinks to individual skills from both sources)-
Personal Skills (source:
dotfiles/.claude/skills/):- Portable skills that work across any company or project
- Version controlled in this repository
- Symlinked to
~/.claude/skills/ - Examples: Generic coding patterns, tool workflows (git, docker), debugging techniques
-
Company Skills (source:
dotfiles/.claude/skills.private/):- Company-specific workflows and infrastructure
- Managed in dotfiles repo but NOT version controlled (excluded via
.gitignore) - Symlinked to
~/.claude/skills/alongside personal skills - Examples: Internal tool access, company infrastructure patterns
- Backup via work-provided cloud storage (e.g., Google Drive) before migrating to new machine
For detailed guidance on creating and organizing skills, see the "Creating Claude Code Skills" skill in
.claude/skills/creating-claude-code-skills/. -
-
Claude Code Commands: Custom slash commands invoked with
/command-name. Same pattern as skills:Directory Structure:
dotfiles/.claude/ ├── commands/ (version controlled personal commands) └── commands.private/ (NOT version controlled company commands) ~/.claude/ └── commands/ (contains symlinks to commands from both sources)-
Personal Commands (source:
dotfiles/.claude/commands/):- Portable commands that work across any company or project
- Version controlled in this repository
- Examples:
/so-questionfor drafting Stack Overflow questions
-
Company Commands (source:
dotfiles/.claude/commands.private/):- Company-specific workflows
- Managed in dotfiles repo but NOT version controlled (excluded via
.gitignore) - Examples:
/fr-incident-responsefor Fresh Realm incident handling - Backup via work-provided cloud storage before migrating to new machine
-
-
Claude Code Hooks: Shell scripts that run in response to Claude Code events. Configured in
settings.jsonand symlinked fromdotfiles/.claude/hooks/:Hook File Purpose SessionStart on-session-start.shTracks session ID, registers with notification daemon Stop on-stop.shSends Telegram notification when task completes (if opted in) SubagentStop on-subagent-stop.shSends notification when subagent completes (if opted in) -
Telegram Notifications (Optional): Get notified on your phone when Claude Code completes tasks.
Prerequisites:
- Claude-Code-Remote daemon running locally
- Telegram bot configured in Claude-Code-Remote (
.envfile) - ngrok or similar tunnel for webhook (if not on a public IP)
How it works:
- Hooks in
settings.jsonreport session events to the local daemon (port 3001) - Sessions start with notifications disabled by default
- Run
/notify-telegram <label>in any Claude Code session to opt-in to notifications - When Claude stops (task complete or waiting for input), you get a Telegram message
- Reply via Telegram to send commands back to Claude Code
Usage:
# In Claude Code, opt into notifications for this session: /notify-telegram backend-refactor # Walk away from your computer... # When Claude completes, you'll receive a Telegram message with inline buttons # Swipe-reply directly to the notification (no token needed!) # Or use: /cmd <TOKEN> your message here (for old notifications)Transport priority: The system tries nvim RPC injection first (requires ccremote plugin), then falls back to tmux injection if available. For nvim injection to work, start neovim with the
nvimsalias and register the terminal with:CCRegister <name>.