Consistent modern shell tooling on MacOS and Windows WSL for developers
I regularly code on both MacOS and Windows machines and I was always annoyed how different the default experiences are on each. I need to use the same tools and the same experience on both.
Windows "WSL" (Windows Subsystem for Linux) is a great tool for this you can use on Windows 10 and newer. The latest version lets you run a full Ubuntu instance that integrates seamlessly with the underlying windows instance.
By using WSL2 you can have a (mostly) identical developer experience jumping between MacOS and Windows.
Better tooling for Developers
Many of the terminal tools that come with unix environments are functionally similar to how they were 20 years ago. But other developer tooling has advanced quite a bit since then.
You can replace tools like ls
or cat
with modern equivalents that support full colour, unicode icons, git state and more. Terminal prompts can be made git aware and use colour to indicate state so you don't have to query git so often.
Keeping developer experience consistent across machines
Keeping any shell changes you make on one machine up to date on all the machines you code on is a nightmare without the right tooling.
This article also explains all the tools I use and how I keep the same terminal setup consistent on MacOS and Windows!
Let's go!
A quick look at default Vs modern terminal tools
Some examples
Old ls
New ls
(Exa) - git aware with icons and colour hints
Old cat
on a json file
New cat
(bat) on the same file - syntax highlighting and formatting
But tooling is just half the story. I'll also show settings to change, aliases and scripts that will make the terminal more productive for you.
My Pre-written scripts to save you time
It took me around 30 hours to investigate and configure all of these tools on MacOS and Windows WSL.
If you want to easily install and configure all these tools in one command please check out my solution https://usemiller.dev/dev-shell.
This set of scripts will setup your entire dev environment on all your computers - Windows or Mac! You can customize them to suit your needs.
If you prefer to do it manually then read on!👇
Use WSL2 and Windows Terminal on Windows
To get a modern shell on windows you must install WSL. Run this in a cmd.exe
window
wsl --install
You will be asked for a username and password because it's a new instance of linux. You can use the same username as you use on MacOS to make custom scripting easier for yourself later.
Next download and install the Windows Terminal from https://aka.ms/terminal. Windows Terminal provides tabs, profiles, colour, themes and everything you would expect from a modern terminal application.
Set wsl as the default shell for your user in Windows Terminal.
Full documentation for wsl is on their site.
Use ITerm2 on MacOS
iTerm2 is a really nice replacement for the MacOS terminal. You can have tabs, pane splitting, floating windows, transparency, image backgrounds and full colour.
It has a really nice autocomplete feature (ctrl-;
), full search and history. You can store profiles for starting a set of terminals (BE + FE is very common!) just like tmux sessions. It also integrates with KeyChain.
Here is my setup for a typical node and react application running on the right-hand side and the left hand side is for working in.
Full documentation for iterm2 is on their site.
Install it with brew: brew install --cask iterm2
.
Iterm2 isn't available on WSL.
Install zsh (Windows WSL and older versions of MacOS)
On modern versions of MacOS zsh
is the default shell. Zsh is bash compatible so there's no issues with changing WSL Ubuntu to use it also.
You can check which shell you're using by running echo $SHELL
.
To install zsh on WSL grab it from apt and then set it as your default shell.
sudo apt install zsh && chsh -s $(which zsh)
Use Antigen for zsh management
Zsh supports plugins and themes. Most of these are open source and are stored in various git repos around GitHub. Manually updating and managing individual repositories for Zsh plugins is a pain. Antigen is a plugin and theme management tool that makes managing these repos much easier.
You can install plugins from any repository by just adding the repo name to antigen configuration. Antigen provides simple commands to update and install everything in one go.
Additionally antigen caches plugins until you explicitly reload the configuration, so booting your shell is quite fast even with lots of active plugins.
The antigen configuration keeps your home folder and zshrc very clean because it will go and clone any repos you need in one place. It can automatically check for updates for all plugins with antigen update
.
Here's an example antigen configuration. You can see that third party plugins are just listed in shorthand as a github organisation/repository. When you've loaded everything you call antigen apply
and it will set the state to your configuration.
# Load the oh-my-zsh's library.
antigen use oh-my-zsh
# Plugins from the default oh-my-zsh repo (robbyrussell's oh-my-zsh).
antigen bundle node
antigen bundle npm
# Load a theme from a repo.
antigen bundle sindresorhus/pure@main
# Tell Antigen that you're done.
antigen apply
At any time you can manually force the configuration to refresh with antigen refresh
.
I use brew to install antigen on MacOS - brew install antigen
.
On Windows I use the shell script - curl -L git.io/antigen > antigen.zsh
.
Make sure to source antigen from the correct install location for each OS in your .zshrc
.
Full documentation for antigen is on their github
Use bat instead of cat
bat
is a command line utility that is similar to cat
but with a few extra features. Use bat
instead of cat
for reading text files to the terminal.
Developers will get full colour syntax highlighting for code files. You get line numbers for code. Bat integrates with git and will show developers changes to files right in the terminal.
Install with brew on Mac
brew install bat
or apt on Windows WSL
sudo apt install -y bat
On Windows wsl bat is installed as batcat
so if you're aliasing cat
you should use batcat
instead of bat.
On mac: alias cat=bat --paging=never
!
On wsl: alias cat=batcat --paging=never
Full documentation for bat is on their github
Exa for ls
Exa is a replacement for ls
that is well suited for developers. Exa integrates with git so you can easily see which files are ignored (I), modified (M), new (N) or untracked (?).
Exa also provides file-type icons, colour coding of file-types, symlink destinations right in the list, ability to have folders sorted to the top and much more.
On a mac you install exa with brew and you can alias it to ls
brew install exa
alias ls='exa -l --group-directories-first --color=auto --git --icons --no-permissions --no-user'
alias ll='exa -lahF --group-directories-first --color=auto --git --icons'
On Windows WSL you must upgrade Ubuntu before installing exa via apt. Here is how to upgrade WSL Ubuntu from 20.04 LTS to 20.10.
# upgrading Ubuntu on Windows WSL
# change from lts only to normal updates for OS
sudo sed -i 's/prompt=lts/prompt=normal/g' /etc/update-manager/release-upgrades
# update all packages
sudo apt update && sudo apt upgrade
# this snapd package manager breaks the Ubuntu upgrade process on WSL so remove it first
sudo apt remove snapd
# check for an Ubuntu update
sudo do-release-upgrade
now you can use exa on WSL
sudo apt install -y exa
on Windows you can't use the git integration so the alias is slightly different.
alias ls='exa -l --group-directories-first --color=auto --icons --no-permissions --no-user'
alias ll='exa -lahF --group-directories-first --color=auto --icons'
Full documentation for exa is on their github
Pure Terminal Prompt
The pure terminal prompt is a very clean zsh prompt. Pure isn't an oh-my-zsh theme. It's a stand-alone prompt.
You can see that the prompt is 2 lines high. The typing/input line is just the >
character and the prompt shows the current state one line above the actual prompt.
This configuration gives you a full line for typing commands every time. I find alternative fancy prompts like Agnoster
that are all on single line take up too much typing space.
Pure is git-aware, you can see the *
indicating that I have uncommitted changes here. The prompt will also indicate if you have commits to push or pull with ꜛ
and ↓
.
It's very fast and provides the same information as the Agnoster prompt but it's much cleaner.
Install this prompt with antigen
antigen bundle sindresorhus/pure@main
You must also remember to disable any oh-my-zsh theme if you have one set using the env var.
# disable zsh theme for pure prompt
export ZSH_THEME=""
Full documentation for Pure is on their github
Use the z plugin for zsh
This is the plugin that makes zsh awesome for me. I use it all the time.
z
will learn the folders you usually work in over time and lets you skip to them without typing the full path.
e.g. if I often work in /Users/me/projects/my-project
then typing z my-project
will take me to that folder from anywhere.
Install z in your antigen configuration
antigen bundle z
Use ncdu to examine disk usage
ncdu is a utility that shows disk usage in your terminal in an ncurses interface. It's free, very fast and easy to use.
// mac
brew install ncdu
// windows wsl
sudo apt-get install -y ncdu
//to run use ncdu <path> e.g.
ncdu .
Use ?
to examine the options. You can navigate around folders. You can change things like the sort order and the graph displayed.
Super handy tool!
diff-so-fancy for git diff
Diff so fancy is a terminal based diff tool. It provides a nicer format and better contrasting to the built in diff tool.
This screenshot from the diff-so-fancy docs shows the differences.
Install on mac with brew
brew install diff-so-fancy
and on Windows install with npm
npm i -g diff-so-fancy
You'll have to add all the required git configuration so that git uses diff-so-fancy instead of the built in diff tool. This is all on their Github page.
Full documentation for diff-so-fancy is on their github.
RipGrep instead of grep
RipGrep is a fast grep tool for finding occurrences of strings in files. It's perfect for developers because it's git aware.
It will respect your .gitignore
file and ignore files in node_modules for example.
The tool is written in Rust and it's very quick!
Install with brew
brew install ripgrep
or on Windows WSL use apt
sudo apt install -y ripgrep
Full documentation for ripgrep is on their github.
fx for json files
fx is a terminal viewer for json files. It supports syntax highlighting, understands the tree structure, supports the mouse, filtering and searching json files right in your terminal.
This gif from the tool's github page shows all of the features.
To install on mac use brew as usual
brew install fx
However for Windows WSL you should use npm for this one
npm install -g fx
You can find the full documentation on their github page.
Use vscode as default terminal editor
As well as duti (see duti section) for setting MacOS defaults for vscode as text editor. You can also set the default editor for any file type in the terminal and git by setting env vars.
This is a personal preference - I just prefer using code for everything. nano
or similar would be a great choice if you don't ike the default vim
or emacs
terminal editors.
export EDITOR="code"
export GIT_EDITOR="code"
export VISUAL="code"
fzf for searching
fzf is a command line fuzzy finder. You pipe a list of strings into it, fzf lets you quickly filter the list of strings and when you choose a result fzf pipes your selected string to the next shell command.
It sounds simple but fzf is accurate and super fast on long lists (like a list with every file on your computer).
To install, use homebrew on mac and apt on Windows WSL.
# mac
brew install fzf
# wsl
sudo apt install -y fzf
Now you can quickly search your files in the terminal on Mac with ctrl-T
.
I combine fzf with ripgrep for searching files.
export FZF_CTRL_T_COMMAND='rg --files --no-ignore --hidden --follow --glob "!.git/*" --glob "!node_modules/*" --glob "!vendor/*" 2> /dev/null'
I change z
so that it uses fzf for searching when no parameter is provided.
unalias z 2> /dev/null
z() {
[ $# -gt 0 ] && _z "$*" && return
cd "$(_z -l 2>&1 | fzf-tmux +s --tac --query "$*" | sed 's/^[0-9,.]* *//')"
}
A nice usage of fzf is to search your chrome history. Here is a function to do that. The paths in the script support chrome profiles or just a single user. Change the profile number if you want to use a different profile.
This script is the mac version. The chrome history has a different location on Windows WSL - /mnt/c/Users/$(whoami)/AppData/local/google/chrome/User Data/Default
.
function ch() {
local cols sep google_history open
cols=$(( COLUMNS / 3 ))
sep='{::}'
if [ -d "$HOME/Library/Application Support/Google/Chrome/Default" ]; then
google_history="$HOME/Library/Application Support/Google/Chrome/Default/History"
else
google_history="$HOME/Library/Application Support/Google/Chrome/Profile 1/History"
fi
open=open
cp -f "$google_history" /tmp/h
sqlite3 -separator $sep /tmp/h \
"select substr(title, 1, $cols), url
from urls order by last_visit_time desc" |
awk -F $sep '{printf "%-'$cols's \x1b[36m%s\x1b[m\n", $1, $2}' |
fzf --ansi --multi | sed 's#.*\(https*://\)#\1#' | xargs $open > /dev/null 2> /dev/null
}
The full documentation for fzf is on their GitHub page with lots of great examples.
Duti for changing default text editor
On mac most text files will open in XCode by default. This drives me insane.
You can change text and code files to open in your preferred text editor by using duti. I use vscode editor so these are the commands.
Install duti with brew brew install duti
.
duti -s com.microsoft.VSCode public.json all
duti -s com.microsoft.VSCode public.plain-text all
duti -s com.microsoft.VSCode public.python-script all
duti -s com.microsoft.VSCode public.shell-script all
duti -s com.microsoft.VSCode public.source-code all
duti -s com.microsoft.VSCode public.text all
duti -s com.microsoft.VSCode public.unix-executable all
duti -s com.microsoft.VSCode .c all
duti -s com.microsoft.VSCode .cpp all
duti -s com.microsoft.VSCode .cs all
duti -s com.microsoft.VSCode .css all
duti -s com.microsoft.VSCode .go all
duti -s com.microsoft.VSCode .java all
duti -s com.microsoft.VSCode .js all
duti -s com.microsoft.VSCode .sass all
duti -s com.microsoft.VSCode .scss all
duti -s com.microsoft.VSCode .less all
duti -s com.microsoft.VSCode .vue all
duti -s com.microsoft.VSCode .cfg all
duti -s com.microsoft.VSCode .json all
duti -s com.microsoft.VSCode .jsx all
duti -s com.microsoft.VSCode .log all
duti -s com.microsoft.VSCode .lua all
duti -s com.microsoft.VSCode .md all
duti -s com.microsoft.VSCode .php all
duti -s com.microsoft.VSCode .pl all
duti -s com.microsoft.VSCode .py all
duti -s com.microsoft.VSCode .rb all
duti -s com.microsoft.VSCode .ts all
duti -s com.microsoft.VSCode .tsx all
duti -s com.microsoft.VSCode .txt all
duti -s com.microsoft.VSCode .conf all
duti -s com.microsoft.VSCode .yaml all
duti -s com.microsoft.VSCode .yml all
duti -s com.microsoft.VSCode .toml all
Nerd fonts (Hack)
To use most of these modern terminal tools you'll need a font that has been patched with icons. These are often called "Powerline" fonts. Nerd Fonts have a nice alternative collection of fonts that are property patched with icons.
I found some issues with the backtick character when using "Inconsolata" Nerd Font so I changed over to the "Hack" font now.
To install Hack font on mac use homebrew. You'll have to tap a new repo to get the nerd fonts.
brew tap homebrew/cask-fonts
brew update
brew install --cask font-hack-nerd-font
On Windows WSL use apt-get to install nerd fonts.
sudo apt install -y fonts-hack-ttf
Set your terminal application to use the font after install.
Configure git defaults
Git has many configuration options where the defaults are not ideal. For example by default git pushes all local changes on all branches to the remote.
This isn't the way most developers work. We usually work on one branch at a time and only want to push that branch.
We usually use the same branch name on local and remote so it would be nice if git didn't make us set the upstream every time we push a new branch. Why can't git do this for us? Well, it can!
You can change what happens with a git push
by setting
git config --global push.default current
Now git will only push the current branch, and if that name isn't on the remote git will create it for you.
Another recommended change is on the default pull behaviour. If you do trunk based dev with short-lived feature branches you shouldn't have pull merges on your branches if you always work on feature branches and/or pull before changing.
You might want to change the default behaviour to fast-forward only to avoid any git "auto" merges. Auto rebasing on pull has a similar effect where you might change the history unexpectedly. This is just as bad as the auto merge.
Instead force fast-forward only. Now git will error if it cannot fast forward and you will know there's something strange happening.
git config --global pull.ff only
If you understand the reason for the merge or rebase you can override fast-forward only when you need to with a flag: git pull --no-ff
.
I use beyond compare for visual diffs so I configure that in my git config.
git config --global diff.tool bc3
git config --global difftool.bc3.trustExitCode true
git config --global merge.tool bc3
git config --global mergetool.bc3.trustExitCode true
I find that it's difficult to remember the log syntax in git so I set some log aliases.
git config --global alias.dlast 'diff HEAD^'
git config --global alias.l "log --graph -n 20 --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(green)- %an, %cr%Creset'"
git config --global alias.ll "log --stat --abbrev-commit"
git config --global alias.ln "log --graph -n 20 --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(green)- %an, %cr%Creset' --name-status"
git config --global alias.lp "log --oneline -n 20 -p"
git config --global alias.ls "log --stat --abbrev-commit -n 1" # display previous log
Git shell aliases
There are some powerful git aliases available in a single package from unixorn
github that you should try out.
You can install the git aliases with antigen by adding the following bundle to your antigen configuration.
antigen bundle unixorn/git-extra-commands@main
Now you have access to the following handy aliases.
If you accidentally commit a couple of times to the wrong branch you can use git-move-commits 2 feat/myFeatureBranch
to move them to the correct branch.
You can git-checkout-pr 586
to check out a pull request by id from GitHub.
git-what-the-hell-just-happened
will show you the result of your last command.
git-churn
will show you which files change often in your repository. This is useful to identify merge contention hotspots. You might want to split these files up.
git-incoming
will show you what you're going to pull down. git-outgoing
will show you what you're going to push.
git-thanks
will show you who has contributed to your repo.
git-wtf
will show you the current state of your local branch.
Because you installed fzf you can add some nice aliases to use fzf to search through your git repository.
gcoc() {
local commits commit
commits=$(git log --pretty=oneline --abbrev-commit --reverse) &&
commit=$(echo "$commits" | fzf --tac +s +m -e) &&
git checkout $(echo "$commit" | sed "s/ .*//")
}
gcob() {
local tags branches target
tags=$(
git tag | awk '{print "\x1b[31;1mtag\x1b[m\t" $1}') || return
branches=$(
git branch --all | grep -v HEAD |
sed "s/.* //" | sed "s#remotes/[^/]*/##" |
sort -u | awk '{print "\x1b[34;1mbranch\x1b[m\t" $1}') || return
target=$(
(echo "$tags"; echo "$branches") |
fzf-tmux -l30 -- --no-hscroll --ansi +m -d "\t" -n 2) || return
git checkout $(echo "$target" | awk '{print $2}')
}
Homeshick for managing dot files
Homeshick is a dot files manager using git as a shared store. It's a great way to manage the dot files that live in your home directory and share them on multiple computers.
To use Homeshick you create a github repository with a home
folder. Your home folder should contain the same structure as your shell home folder.
You can then clone that repository with homeshick on any computer and it symlinks all your dotfiles and configuration files to the local home
folder. Homeshick will periodically check for updates and it asks before overwriting any files you have made local changes to. It's really slick.
This is my homeshick GitHub repository for example
Now if I make a change to a file in the home
folder of one machine, I can push it to this repository and it will be updated in the shared store (git).
I can then do a simple homeshick refresh
to update the files in my home folder on all other Mac or Windows machines and everything is synchronized. It's very powerful.
Detailed instructions for homeshick can be found on the homeshick github page.
Mac defaults
There are lots of "hidden" mac settings that developers can change using defaults write
and defaults read
. It's like the Windows registry but on MacOS.
There are awesome lists of these settings available on github, here are some that are useful for developers.
# Use plain text mode for new TextEdit documents
defaults write com.apple.TextEdit RichText -int 0
# Enable full keyboard access for all controls
# (e.g. enable Tab in modal dialogs)
defaults write NSGlobalDomain AppleKeyboardUIMode -int 3
# Use scroll gesture with the Ctrl (^) modifier key to zoom (this makes mac work like Windows for this :) )
defaults write com.apple.universalaccess closeViewScrollWheelToggle -bool true
defaults write com.apple.universalaccess HIDScrollZoomModifierMask -int 262144
# Disable smart quotes and smart dashes - annoying when coding imho
defaults write NSGlobalDomain NSAutomaticQuoteSubstitutionEnabled -bool false
defaults write NSGlobalDomain NSAutomaticDashSubstitutionEnabled -bool false
defaults write NSGlobalDomain NSAutomaticPeriodSubstitutionEnabled -bool false
# Disable auto-correct
defaults write NSGlobalDomain NSAutomaticSpellingCorrectionEnabled -bool false
# Use list view in all Finder Windows by default
# Four-letter codes for the other view modes: `icnv`, `clmv`, `Flwv`
defaults write com.apple.finder FXPreferredViewStyle -string "Nlsv"
# Finder: show all filename extensions
defaults write NSGlobalDomain AppleShowAllExtensions -bool true
# Save to disk (not to iCloud) by default
defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
# Display full POSIX path as Finder window title
defaults write com.apple.finder _FXShowPosixPathInTitle -bool true
Useful unix tool aliases
You can add these small aliases for the built in unix tools to make them report what they just did. I like getting this verification.
alias mv="mv -v"
alias cp="cp -v"
alias rm="rm -v"
# ...etc
# note rm -v can be slightly slower on a node_modules folder due to the size of that folder
# so you might want an alias for that folder specifically that is not verbose.
alias rnm="\rm -rf node_modules"
Alias sudo so that it passes your current environment into the elevated shell.
alias sudo="sudo -E"
Configuring vscode
Tell vscode that you want to use the iTerm terminal on MacOS and use our Nerd Font on both.
Note that on Windows the font "Hack" and on MacOS the font is "Hack Nerd Font".
{
"terminal.explorerKind": "external",
"terminal.external.osxExec": "iTerm.app",
"terminal.integrated.fontSize": 14,
"terminal.integrated.fontFamily": "Hack Nerd Font",
"terminal.integrated.profiles.osx": {
"zsh": {
"path": "/bin/zsh",
"args": ["-l"]
}
}
}
Always hide node_modules folder.
"files.exclude": {
"**/node_modules": true
},
Always use relative imports.
"javascript.preferences.importModuleSpecifier": "relative",
"typescript.preferences.importModuleSpecifier": "relative",
Auto format various files.
"editor.formatOnSave": true,
"[xml]": {
"editor.defaultFormatter": "redhat.vscode-xml"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[yaml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
Always type check js even if there is no jsconfig.json
or tsconfig.json
file.
"js/ts.implicitProjectConfig.checkJs": true,
Open current folder in Explorer (Windows WSL)
You can use the following alias to open the current folder in Explorer from Windows WSL.
alias open="explorer.exe $1"
This mimics the mac terminal open .
finder command.
Automate ssh agent for ssl passwords (Windows WSL)
There is no key chain on Windows WSL so you have to enter your password every time you ssh into a server.
You can limit this to entering the password just once on WSL by using ssh-agent.
SSH_ENV="$HOME/.ssh/agent-environment"
function start_agent {
/usr/bin/ssh-agent | sed 's/^echo/#echo/' > "${SSH_ENV}"
chmod 600 "${SSH_ENV}"
. "${SSH_ENV}" > /dev/null
}
if [ -f "${SSH_ENV}" ]; then
. "${SSH_ENV}" > /dev/null
#ps ${SSH_AGENT_PID} doesn't work under cywgin
ps -ef | grep ${SSH_AGENT_PID} | grep ssh-agent$ > /dev/null || {
start_agent;
}
else
start_agent;
fi
Detect MacOS or Windows in zshrc
In my custom scripts I have to detect the OS a few times. I thought it would be useful to show how I do that.
# detect the machine we're running on
# assume linux is wsl on Windows (although any Ubuntu should be ok)
unameOut="$(uname -s)"
case "${unameOut}" in
Linux*) machine=Linux;;
Darwin*) machine=Mac;;
CYGWIN*) machine=Cygwin;;
MINGW*) machine=MinGw;;
*) machine="UNKNOWN:${unameOut}"
esac
Summary
That's all the steps I perform on a new machine to have it work for my development workflow.
I hope some these tools and configuration suggestions help you get more out of your terminal on Windows and Mac!
Hit me up on twitter if you have any questions.
Save 30+ hours with my scripts
It took me around 30 hours to configure all of these tools on Mac and Windows.
If you want to save the time of setting all of this up manually you can get all the scripts I use to setup everything on a new Mac or Windows machine from https://usemiller.dev/dev-shell.
You'll get
- 14 well tested, re-runnable, shell scripts that install everything you need from scratch.
- Dot files with all of the aliases and configuration I use pre-configured.
- Run on Mac or Windows WSL Ubuntu.
- In the correct structure for immediate consumption by homeshick.
- Full source - you can place these in a git repo and edit to suit your needs.
- Lifetime access - I keep the scripts updated and you can get the latest version
- My developer vscode configuration which has many improvements but in particular it sets the built in terminal and fonts correctly.
- All of my aliases and functions.
- A developer focused git configuration.
Get it here: https://usemiller.dev/dev-shell
See a 30 second demo of the install script here:
youtube: https://www.youtube.com/watch?v=laX7U9bc7rw
Get it here: https://usemiller.dev/dev-shell