This project is a modern, efficient, and out-of-the-box macOS terminal development environment. All configurations are version-controlled centrally and deployed with GNU Stow in one shot.
Core stack: Ghostty (terminal) + Zellij (multiplexer) + Fish (shell) + Helix (editor) + Mise (version manager) + AIChat (terminal AI assistant), with a unified visual and interaction style across the entire stack.
Core design principles:
- Configuration as Code: All configs are tracked by Git and managed through Stow symlinks, enabling idempotent one-shot resets.
- Terminal as Container: The terminal is only the rendering container (Ghostty). Session and layout orchestration is unified in the multiplexer (Zellij), while code editing is handled by an out-of-the-box modern editor (Helix), eliminating the mental burden of piecing together plugins.
- Environment as Sandbox: No more global pollution or chaotic multi-version managers. A single foundation declares all language sandboxes in one place (Mise).
- Comments as Documentation: Every config file in this project is the most detailed manual itself, with deep Chinese comments, design trade-offs, and best-practice guidance.
- AI-Driven Productivity: Large-model capabilities are built directly into the CLI and common workflows, making the terminal environment AI-native.
Note
These dotfiles are macOS-only, incompatible with Linux or Windows (WSL), and there are no plans for cross-platform adaptation.
- 0. TL;DR (Quick Start)
- 1. Project Structure
- 2. AI Capabilities
- 3. Installation
- 4. Configuration Guide
- 5. Usage
- 6. Key Differences from Official Defaults
- 6.1 Karabiner Global Key Remapping
- 6.2 Shottr Integration Notes
- 6.3 Ghostty Terminal Behavior and Keybindings
- 6.4 Zellij Shortcuts and Session Architecture
- 6.5 Fish Shell Behavior and Keybindings
- 6.6 Helix Editor Keybindings and Display
- 6.7 Git Workflow Enhancements
- 6.8 macOS System Preferences and Trackpad Gestures
- 7. Common Maintenance Commands (Makefile)
- 8. FAQ / Troubleshooting
- 9. Acknowledgments
- 10. License
The simplest approach is to use the one-shot bootstrap installer, which is designed specifically for first-time setup:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/windvalley/dotfiles/main/bootstrap.sh)"Note
If the target directory ~/dotfiles already exists, the script exits proactively for safety.
If you want to reinstall, it is recommended to move the old directory away first as a backup (or delete it if you prefer): mv ~/dotfiles ~/dotfiles.bak
To install into another location, specify an environment variable: export DOTFILES_DIR=~/custom_path; curl ... | bash
This script automatically installs Homebrew (if missing), Ghostty, Fish, Zellij, Helix, Mise, and Stow, then links configs and initializes Fish Shell.
Tip
If ~/dotfiles is already cloned locally, you can safely run ./install.sh inside it multiple times. It is idempotent and commonly used to update dependencies or repair symlinks.
If you want precise manual control, refer to the detailed steps below.
This repository contains the following config packages and core files:
ghostty/: Ghostty (/ˈɡoʊs.ti/, Ghost + ty) terminal config (modern, fast, GPU-accelerated)fish/: Fish (/fɪʃ/, Friendly Interactive SHell) shell config (friendly, interactive, ready out of the box)zellij/: Zellij (/ˈzɛl.ɪdʒ/, from Arabic mosaic tile art) terminal multiplexer config (easy to configure, multi-layout support)helix/: Helix (/ˈhiː.lɪks/, helix) modern modal editor config (Rust-based, ultra-responsive, built-in LSP support)karabiner/: Karabiner-Elements (/ˌkær.əˈbiː.nər/, German for carabiner) keyboard mapping (swaps Caps Lock and Left Control)git/: Git base configuration (includes high-frequency aliases, Delta modern diff styling, global ignores, and a multi-account isolation architecture)mise/: Mise (/miːz/, from French mise en place) tool version manager config (Unified management of Go, Node, Bun, Python, Lua, and Rust runtimes together with LSP / formatter toolchains)aichat/: AIChat terminal AI client (Integrates multiple models, command generation/troubleshooting, and workflow enhancements)bat/: bat custom theme assets for the syntax-highlighting pager, used bycolorschemeto keep Bat / Delta syntect themes in sync.btop/: btop modern system resource monitoring config.bin/: High-frequency custom scripts (includes thezjproject launcher,gdoctordiagnostic tool,aic/aiprAI-enhancement tools, etc., automatically linked to~/.local/bin)local/: Private local config templates (for Fish environment variable redaction, Git multi-account isolation, private Ghostty overrides, and go-musicfox local bootstrap config)Makefile: Automation for build and maintenance tasks.editorconfig: Cross-editor formatting rules. It includes strict formatting controls such as indentation mode, forced LF line endings, and final newline protection to keep the codebase clean and avoid cross-platform/editor formatting issues.
This project consolidates AI large-model capabilities into the command-line editing area, Git workflow, and daily productivity tools, forming a unified entry point and reusable toolchain rather than scattered aliases. The foundation is AIChat, supporting mainstream models such as OpenAI, Claude, Gemini, Tongyi Qianwen, Zhipu, and Moonshot, and also local models through Ollama.
This repository now exposes two AI layers:
q: a direct local-model entrypoint built on the native Ollama CLI, with thinking disabled by default- A shared AI routing layer: workflows such as
Ctrl+y,?,??,aic,aipr,ait, andtfollow the backend order declared byAI_CHAT_BACKENDSin~/.fish.local.fish(for exampleq,aichatoraichat,q)
AIChat remains the provider aggregator for OpenAI / Claude / Gemini / Qwen / Zhipu / Moonshot; if you want, you can also point it at a local model through Ollama's local-llm: provider.
Command-line agent:
| Entry | Function | Description |
|---|---|---|
Ctrl+y |
Command explanation | Press after typing a command; explanation is shown through bat pagination and is never executed |
# <description> + Ctrl+y |
Command generation | Describe intent in natural language, generate multiple candidate commands, pick one with fzf, and write it back to the command line |
? |
Quick generation | Send natural language to the shared AI router and generate one executable command (q -> aichat by default) |
?? |
Troubleshooting | Automatically captures the last failed command plus terminal output, sends them to the shared AI router for diagnosis, and returns repair suggestions (depends on Zellij dump-screen) |
q [prompt] |
Local direct mode | Call a local Ollama model directly; the default model comes from AI_LOCAL_MODEL, and thinking follows AI_LOCAL_THINK (defaults to false when unset) |
Git workflow:
| Command | Function | Description |
|---|---|---|
aic |
Commit message | Analyzes the staged diff and generates Conventional Commits style messages, with rewrite/refine/Chinese-English switching support |
aipr |
PR description | Compares branch diffs and generates a structured PR description, which can be copied to the clipboard or used to create a PR directly via gh |
ait |
Release notes | Analyzes commits since the last tag, generates a version number and CHANGELOG.md, commits it, and creates a tag automatically |
Daily productivity:
| Command | Function | Description |
|---|---|---|
t <text> |
Smart translation | Auto-detects input type: English word → dictionary definition (phonetics + bilingual explanation); Chinese phrase → English candidates; paragraph → two-way translation |
aip |
Prompt library | Interactively selects commonly used AI programming prompts, supports multi-select with fzf plus filtering by number/keyword, and copies the result to the clipboard automatically |
Configuration: Set AI_CHAT_BACKENDS, AI_LOCAL_MODEL, AI_LOCAL_THINK, AICHAT_MODEL, and API keys in ~/.fish.local.fish. See 4.8 Configure AIChat.
The repository root provides an install.sh script that automates almost the entire installation and configuration process.
The script performs the following:
- Environment preparation: Checks for and installs Homebrew automatically if it is not already installed.
- Core dependencies: Reads
Brewfileand installs all CLI tools (stow, zellij, fish, helix, mise, aichat, go-musicfox, fzf, telnet, etc.) and GUI apps (Ghostty, OrbStack, Shottr, Thaw, JetBrains Mono font, etc.). - Font installation: JetBrains Mono is installed through Brew by default, and the script asks whether to install other extended fonts (Maple Mono, Geist Mono).
- Shottr hotkeys (optional): If
Shottris installed, the script asks whether to write the recommended global capture shortcutsShift + Cmd + 1/2/A/Sso it never silently overwrites your existing hotkey habits. - Symlink setup: Detects existing configs, backs them up automatically, then uses
stowto symlink all configs, including thebinscripts, into the correct system locations. - AI model sync: Automatically runs
aichat --sync-modelsto synchronize the default model catalog into the local index. - Local model backend (optional): In interactive installs, the script asks whether to install and start
Ollama; in non-interactive mode it is skipped by default unless you pass--with-ollama. The script does not pull any local model automatically. - Runtime installation: Installs core language runtimes via Mise (Go, Node, Bun, Python, Lua, Rust), common language servers and formatters (such as
gopls,pyright,lua-language-server,stylua, andvtsls), plus out-of-the-box CLI tools (gh,bat,eza,fd,ripgrep,glow,shellcheck,codex,kimi,claude-code, etc.). - Privacy template setup: Automatically creates Git identity templates (
.gitconfig.local/.work), a Fish private environment template (.fish.local.fish), a Ghostty private config template (.ghostty.local), and a go-musicfox local template (~/.local/share/go-musicfox/config.toml) in the user's home directory. - Shell initialization: Sets Fish as the default shell and, when clear Zsh usage signals are detected, optionally migrates PATH variables from the old Zsh setup into Fish.
- Plugin setup: Installs the Fisher plugin manager and syncs all Fish plugins.
- System optimization: Prompts whether to apply common macOS system preference tweaks via
macos.sh.
Usage:
git clone --depth=1 https://github.com/windvalley/dotfiles.git "$HOME/dotfiles"
cd "$HOME/dotfiles"
./install.shTip
Non-interactive mode: In automation environments such as CI/CD, append -y or --unattended to skip all confirmations and automatically use each prompt's default answer: ./install.sh -y
If you also want the local-model backend in non-interactive mode, append --with-ollama: ./install.sh -y --with-ollama
If the current session does not have reusable sudo credentials, karabiner-elements will be skipped automatically in -y mode so Homebrew does not block on a password prompt. You can install it later from an interactive shell with brew install --cask karabiner-elements
Installation notes:
- If Homebrew is missing, the script asks whether to install it.
- In
-ymode, steps whose default isy(such as installing Homebrew) run automatically, while optional prompts whose default isn(such as extra fonts,macos.sh, or Ollama) are skipped automatically. - If you prefer to install Homebrew manually, visit https://brew.sh
- The script automatically detects and migrates your Zsh PATH setup into Fish.
If you prefer to do everything manually, follow this order:
# Symlink manager
brew install stow
# Terminal
brew install --cask ghostty@tip
# Multi-pane/session management
brew install zellij
# Interactive shell
brew install fish
# Text editor (replacing vim/neovim)
brew install helix
# Tool version manager
brew install mise
# Official go-musicfox Tap
brew tap go-musicfox/go-musicfox
# NetEase Cloud Music terminal client
brew install go-musicfox/go-musicfox/go-musicfox
# Git beautifier (syntax-highlighted diffs)
brew install git-delta
# Terminal AI all-in-one assistant (LLM integration + shell assistance)
brew install aichat
# Modern cross-platform system resource monitor
brew install btop
# Lightweight macOS screenshot annotation tool
brew install --cask shottr
# macOS menu bar manager (Thaw, the official continuation fork of Ice after the original project went inactive; requires macOS 26+)
brew install --cask thaw
# Global key remapping tool
brew install --cask karabiner-elements
# Fonts
brew install --cask font-jetbrains-mono-nerd-font
# Common tools
brew install fzf zoxide grc gawk gnu-sed grep telnet
# Volume control
brew install switchaudio-osxNotes:
aichat: A native terminal client for large language models, supporting multimodal and local/cloud models. This config providesCtrl+yfor one-key command explanation/generation. A leading#means “describe intent -> generate command”.zoxide: Smart directory jumping, a modern replacement forcd. Usage:z <keyword>to jump directly,zi <keyword>for interactive selection (requiresfzf).gnu-sed: Providesgsed, used by scripts such ascolorscheme,font-size, andopacity.telnet: Provides the classic TCP connectivity test command for quickly checking whether a target host and port are reachable.switchaudio-osx: ProvidesSwitchAudioSource, used byaudio-volume.grc: Generic Colouriser. Combined with Fish plugins, it adds colored output enhancements to commands likeping,ls,docker, anddiff.shottr: The default macOS screenshot annotation tool in this setup. It is its own capture-and-annotation workflow, with support for scrolling captures, OCR, and pinned overlays. Under Shottr's current licensing terms, personal use is free and commercial use requires a paid license. If you want to apply the repository's recommended global hotkeys in one shot after cloning, run"$HOME/dotfiles/bin/configure-shottr-hotkeys" --forcethaw: The default macOS menu bar manager in this setup, provided by Thaw — the official continuation fork of Ice after the original project went inactive. The repository tracks the stable caskcask "thaw"and requires macOS 26+. For now, the repo manages installation only and does not directly version GUI runtime preferences such ascom.stonerl.Thaw.plist. If you previously installedjordanbaird-ice@beta, migrate withbrew uninstall --cask jordanbaird-ice@betafollowed bybrew bundle --file="$HOME/dotfiles/Brewfile", so Ice and Thaw do not both fight for the menu bar at launch.
git clone --depth=1 https://github.com/windvalley/dotfiles.git "$HOME/dotfiles"
# Update later
cd "$HOME/dotfiles"
git pull --rebaseTip
If make is already installed, you can use make stow for routine resyncs. For first-time setup, or when real directories already exist under ~/.config/*, prefer install.sh because its backup and migration safeguards are more complete.
If you still want to link configs manually, and want Stow to create clean directory-level symlinks for GUI apps such as Btop and Karabiner as well as tools with extensible config directories, you need to defensively clean the target config directories first:
# If a target config directory already exists as a real directory (not a symlink),
# you must rename or remove it. Do not keep it in place.
# Goal: make stow see the target directory as “missing”, so it maps the whole
# directory as a pure directory-level symlink.
# Otherwise stow will descend into the real directory and create file-level symlinks,
# causing newly generated local files to drift out of version control.
for pkg in ghostty helix zellij mise karabiner bat btop fish git aichat; do
if [ -d ~/.config/$pkg ] && [ ! -L ~/.config/$pkg ]; then
mv ~/.config/$pkg ~/.config/$pkg.bak
elif [ -L ~/.config/$pkg ]; then
unlink ~/.config/$pkg
fi
doneThen apply all symlink mappings in one shot:
cd "$HOME/dotfiles"
# Link all standard config packages that follow XDG and map into ~/.config/
stow --restow --target="$HOME" --dir="$HOME/dotfiles" --dotfiles ghostty helix zellij mise karabiner bat btop fish git aichat
# Link packages that require a special target structure
# (for example, custom commands placed under ~/.local/bin)
mkdir -p "$HOME/.local/bin"
stow --restow --target="$HOME/.local/bin" --dir="$HOME/dotfiles" binOnce configurations are linked, use mise to automatically fetch all configured toolchains, including runtimes, LSPs, and AI CLIs such as codex, kimi, claude-code, and opencode:
# Automatically reads ~/.config/mise/config.toml and installs all tools
mise installIf you find that this configuration doesn't suit your habits after trying it out, you can safely uninstall and explicitly revert your system to its default state through the following steps:
- Remove all symlinks:
cd ~/dotfiles make unstow
- Restore your system default Shell (e.g., reverting to zsh):
chsh -s /bin/zsh
- Restore your backed-up configurations:
When you initially ran
install.sh, the script automatically renamed your existing configuration directories to*.bak(e.g.,~/.config/fish.bak). You can locate these directories, remove the.baksuffix, and overwrite the symlink to restore them to their original state.
Set Fish as the default shell:
which fish | sudo tee -a /etc/shells
chsh -s $(which fish)After restarting the terminal (or running exec fish -l), execute the following inside Fish:
# Verify the shell switch
echo $SHELL
# Generate completions automatically from man pages
fish_update_completions
# Theme (affects syntax highlighting only, not the prompt; Tide controls the prompt)
fish_config theme choose draculaTip
Homebrew bin/sbin paths are already handled by the repo's config.fish, so you usually do not need to run fish_add_path (brew --prefix)/bin manually.
Important
After switching from Zsh to Fish, PATH entries from Zsh config files such as ~/.zshrc and ~/.zprofile are not inherited automatically. That can make installed commands suddenly disappear from your shell.
Automatic migration (recommended): install.sh first checks for clear Zsh usage signals, such as the current shell being Zsh or non-empty ~/.zshenv / ~/.zprofile / ~/.zshrc / ~/.zlogin files. Only when those signals exist will it prompt you to write extra Zsh PATH entries into a managed block inside ~/.fish.local.fish.
If you are already a long-time Fish user, the installer now defaults to skipping this step. That keeps the source of truth visible and editable without hiding anything in out-of-repo fish_user_paths state.
If you want to audit the actual migration coverage afterwards, run make path-audit (or path-audit directly once bin/ has been linked into ~/.local/bin). The tool compares zsh -l, zsh -il, and fish -l side by side, so you can see which paths exist only in .zshrc or interactive plugin chains and therefore will not be migrated automatically by install.sh.
Manual migration: If you want to add paths by hand, prefer putting them in ~/.fish.local.fish as well:
# ~/.fish.local.fish
test -d "$HOME/.cargo/bin"; and fish_add_path --append --path "$HOME/.cargo/bin"
test -d "$HOME/.local/bin"; and fish_add_path --append --path "$HOME/.local/bin"Tip
Run exec fish -l after updating the file.
Use printf '%s\n' $PATH to inspect the current path list.
In real-world use, we often need environment variables that are machine-specific or sensitive, such as OPENAI_API_KEY, internal company proxies, or aliases for a specific machine.
To prevent those values from being tracked by Git and leaked into a public repository, this dotfiles setup already includes a local isolation mechanism:
- Copy the example template from this repository to the target location and remove the
.examplesuffix:cp ~/dotfiles/local/config.local.fish.example ~/.fish.local.fish - Put all your private settings into the newly created file. The model name, API key, and command below are placeholders only, so replace them with your own values and never commit real credentials back to the repository:
# ~/.fish.local.fish # set -gx AICHAT_MODEL "gemini:gemini-3-flash-preview" # set -gx OPENAI_API_KEY "sk-xxxxxxxxx" # abbr -a -g work-vpn "sudo launchctl restart com.corp.vpn"
- Copy the example template from this repository to the target location and remove the
.examplesuffix:cp ~/dotfiles/local/ghostty.config.local.example ~/.ghostty.local
- Add your private or machine-specific config in the new file, such as keybindings or fonts:
# ~/.ghostty.local # Example: press ctrl+backspace to auto-type placeholder text and hit Enter keybind = ctrl+backspace=text:<your-secret>\r
This project's Git configuration adopts a "base + local override" pattern, supporting multiple identities through the include directive without polluting the main repository:
- Set your personal global identity:
cp ~/dotfiles/local/dot-gitconfig.local.example ~/.gitconfig.local # Edit ~/.gitconfig.local and fill in your common Name and Email
- Set a separate work/directory identity (optional):
cp ~/dotfiles/local/dot-gitconfig.work.example ~/.gitconfig.work # Edit ~/.gitconfig.work and fill in your company email; this config only applies to repositories inside ~/work/
The active go-musicfox configuration is not a good fit for direct Stow management inside the repository, because the application may write login state, cookies, and cache-related state back into its config root while running. To keep those private values out of the Git worktree, this project integrates go-musicfox as “tracked template + out-of-repo live config”:
- Fish exports
MUSICFOX_ROOT="$HOME/.local/share/go-musicfox"so go-musicfox always uses a private directory outside the repository for config, cache, and runtime state. install.shcopies~/dotfiles/local/go-musicfox.config.toml.exampleto~/.local/share/go-musicfox/config.tomlon first install.- After that, edit
~/.local/share/go-musicfox/config.tomldirectly, and never commit real cookies or account state back into the repository.
The template makes two conservative defaults on purpose:
- It pins
player.engine = "osx"for a more predictable macOS media-control path. - It disables
enableMouseEventby default to reduce input conflicts inside the Ghostty + Zellij combination.
Note
Fish and Ghostty private local files now live directly under $HOME (~/.fish.local.fish, ~/.ghostty.local); go-musicfox private config and runtime state live under ~/.local/share/go-musicfox/. All of them stay physically outside the dotfiles repository, so Stow no longer writes them back into the repo worktree.
Fisher is the Fish plugin manager.
By setting fisher_path in config.fish, all plugin-related files are fully isolated under ~/.local/share/fisher, preventing ~/.config/fish from being polluted.
# Important: clear the fisher_path directory before installation to avoid conflicts
rm -rf ~/.local/share/fisher
# Install Fisher
curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher
# Install plugins (synced from the dotfiles plugin list)
fisher install (string trim < ~/.config/fish/fish_plugins | string match -rv '^(#|$)')For more, see fish/dot-config/fish/README.md.
Tide is the Fish prompt plugin.
# One-shot automated configuration
tide configure --auto \
--style=Lean \
--prompt_colors='16 colors' \
--show_time='24-hour format' \
--lean_prompt_height='Two lines' \
--prompt_connection=Disconnected \
--prompt_spacing=Compact \
--icons='Many icons' \
--transient=Yes
# Or configure it interactively according to personal preference
tide configureThe repository root includes a macos.sh script that uses defaults write to apply deep system preferences commonly preferred by developers.
- Keyboard experience: Sets a very fast key repeat rate and disables “press and hold for special characters”.
- Finder: Shows all filename extensions, status bar, and path bar; keeps folders on top when sorting by name; disables
.DS_Storecreation on network and USB drives. - Trackpad / mouse: Enables tap-to-click, disables Force Click, and turns on three-finger drag for both text selection and window dragging.
- Dock: Enables auto-hide and hides recent applications.
You can apply or reapply these preferences at any time with:
make macos
# or ./macos.shWarning
This script reflects strong personal preferences for system settings.
It is strongly recommended that you open macos.sh and skim the source with its detailed inline comments before executing it. You can easily comment out any defaults write command that does not match your habits.
1. Configure user identity and multi-account isolation (Local Overrides)
To prevent mixing personal and work email addresses or leaking identity information accidentally, the repository's ~/.config/git/config removes hardcoded user identity and adopts an include-based isolation mechanism.
Set your personal global identity (required): Create a Git-ignored local config file in your home directory. You can copy the template directly and edit it:
cp ~/dotfiles/local/dot-gitconfig.local.example ~/.gitconfig.local
# Then edit ~/.gitconfig.local and fill in your personal informationSet a separate work identity (optional):
If one machine also handles company code, and all work repositories live under ~/work/ and its subdirectories, copy the template as a dedicated work config:
cp ~/dotfiles/local/dot-gitconfig.work.example ~/.gitconfig.work
# Then edit ~/.gitconfig.work and fill in your company emailWhenever you git commit inside that directory, Git uses the includeIf "gitdir:~/work/" condition in the config to switch to your work identity automatically, completely preventing identity mistakes.
2. Custom global ignore file
The repository already includes a generic ~/.config/git/ignore (Git XDG standard location, auto-discovered). If you have your own global ignore rules, such as IDE files or temporary files, edit it directly:
# Add your own global ignore rule (for example, ignore all .log files)
echo "*.log" >> ~/.config/git/ignoreTip
The change above updates ~/dotfiles/git/dot-config/git/ignore directly. It is recommended to commit those changes into your own dotfiles repository.
This project already includes an AIChat config package. After Stow, it maps to ~/.config/aichat/config.yaml.
1. Config file locations and responsibilities
- Repository source:
~/dotfiles/aichat/dot-config/aichat/config.yaml - Active path:
~/.config/aichat/config.yaml - Responsibility boundary:
config.yamlcontrols behavior strategy such asstream,function_calling,save_session, andkeybindingsinstall.shautomatically runsaichat --sync-modelsafter Stow, so models referenced by the default config are available in the local catalog and do not fail with “model not found” errors- API keys and model overrides should be injected through Fish local private files whenever possible, to avoid storing secrets in the repo
2. Inject model and secret locally through a private file (recommended)
# ~/.fish.local.fish
# Shared AI workflow priority: local q first, then aichat fallback
set -gx AI_CHAT_BACKENDS "q,aichat"
# Default Ollama model used by q
set -gx AI_LOCAL_MODEL "qwen3.5:35b"
# Thinking mode for q / local-first workflows: false / true / low / medium / high
set -gx AI_LOCAL_THINK "false"
# Provider prefix examples: claude: / qianwen: / zhipuai: / moonshot: / deepseek: / openai: / gemini: / local-llm:
set -gx AICHAT_MODEL "gemini:gemini-3-flash-preview"
set -gx GEMINI_API_KEY "YOUR_API_KEY_HERE"Important
Do not write any API key directly into aichat/dot-config/aichat/config.yaml in the repository. Secrets belong only in ~/.fish.local.fish.
3. Use Ollama as the local backend (optional)
This repository now exposes both the public q command and a shared AI routing layer:
qtalks to the native Ollama CLI directly, which is the most reliable path when you explicitly want a local model with thinking disabled- Shared workflows such as
Ctrl+y,?,??,aic,aipr,ait, andtfollow the order declared inAI_CHAT_BACKENDS(recommended default:q,aichat) - If you want
aichatitself to use a local Ollama model as well, setAICHAT_MODELtolocal-llm:<model>
# In interactive installs the script asks whether to enable Ollama;
# for unattended installs, turn it on explicitly:
./install.sh -y --with-ollama
# If you prefer to install it manually instead:
brew install ollama
brew services start ollama
# After installation, pull a local model when you actually need one
ollama pull llama3.2# ~/.fish.local.fish
# Local Ollama does not require an API key
set -gx AI_CHAT_BACKENDS "q,aichat"
set -gx AI_LOCAL_MODEL "llama3.2"
# Optional: point aichat itself at a local Ollama model
set -gx AICHAT_MODEL "local-llm:llama3.2"If you pull a different model, run ollama list first to get the exact name. You only need to update the built-in local-llm.models list inside ~/.config/aichat/config.yaml when you also want aichat itself to recognize and use that local model.
4. Verify that the configuration is active
# Reload Fish environment variables
exec fish
# Check AIChat directories and data isolation paths
# (set centrally by fish/config.fish)
aichat --info
# If you enabled local Ollama, verify the local q command and resolved model
# If you only use cloud aichat, you can skip this step
q --list-models
q hi
# Refresh the official model catalog manually
aichat --sync-models
# Inspect the model list
aichat --list-models
# If you use local Ollama, also verify the local service and pulled models
ollama list
# Check whether aichat itself works
aichat hiConfig file: ~/.config/ghostty/config
Note: Native tabs are disabled and managed uniformly by Zellij, while multiple windows remain available.
Shortcuts:
| Shortcut | Function |
|---|---|
Cmd + Shift + , |
Reload config after editing |
Cmd + ; |
Open Quick Terminal (custom shortcut) |
Cmd + n |
Open a new window (plain Fish terminal, skips Zellij auto-start) |
Cmd + click |
Open a URL shown on screen in bare Ghostty (without entering Zellij) |
Note
It is recommended to use Zellij tabs and panes instead of Ghostty's native tabs and splits, so you get more flexible layout control and cross-session persistence.
Config file: ~/.config/zellij/config.kdl
Auto-start: This setup integrates Zellij auto-start logic directly in Ghostty (via initial-command). Opening the first terminal window automatically starts or attaches to a Zellij session. If Zellij is not installed, it automatically falls back to a plain Fish terminal. Any additional terminal windows opened via Cmd + n will remain as plain Fish terminals and will not trigger the auto-attach logic again, giving you the flexibility to use a raw shell when needed.
Mode system: Zellij has multiple modes. Press Ctrl + p/t/n/h/s/o/a to enter the corresponding mode directly. Press Ctrl + g to enter locked mode, which disables all shortcuts.
Common shortcuts:
| Shortcut | Function |
|---|---|
Ctrl + g |
Enter/exit locked mode (disable all shortcuts) |
Ctrl + p |
Enter pane mode |
Ctrl + t |
Enter tab mode |
Ctrl + n |
Enter resize mode |
Ctrl + h |
Enter move mode |
Ctrl + s |
Enter scroll mode |
Ctrl + o |
Enter session mode |
Ctrl + a |
Enter TMUX compatibility mode |
Pane mode (Ctrl + p):
| Shortcut | Function |
|---|---|
h/j/k/l |
Switch panes in Vim style |
d |
Split pane downward |
r |
Split pane right |
n |
Create a new pane |
x |
Close the current pane |
f |
Toggle fullscreen |
Tab mode (Ctrl + t):
| Shortcut | Function |
|---|---|
n |
Create a new tab |
x |
Close the current tab |
1-9 |
Switch to a specific tab |
h/k |
Previous tab |
l/j |
Next tab |
Resize mode (Ctrl + n):
| Shortcut | Function |
|---|---|
h/j/k/l |
Increase pane size in the corresponding direction |
H/J/K/L |
Decrease pane size in the corresponding direction |
+/- |
Scale up/down proportionally |
Global shortcuts (no need to enter a mode):
| Shortcut | Function |
|---|---|
Cmd + 1-9 |
Switch to a specific tab |
Shift + Cmd + click |
Open a URL shown on screen inside a Zellij session (Shift hands the mouse click back to the terminal) |
Layout:
- Default layout:
dev-workspace(defined at~/.config/zellij/layouts/dev-workspace.kdl), path-agnostic by default, so all panes inherit the current directory used to launch Zellij. - Built-in language-specific layouts: Includes specialized workspace layouts such as
layout-go,layout-rust,layout-python,layout-node,layout-cpp, andlayout-fullstack, providing out-of-the-box tailored pane splits and functional tabs. - Smart Launcher: Use the custom
zjcommand to launch Zellij from any directory. It auto-detects the project structure and smartly selects a tailored layout, while generating a stable session name from the current working context: inside a Git repository, it automatically collapses to a repository-level session; in ordinary directories, it distinguishes sessions by the current directory. The command is terminal-aware: in a bare terminal, it starts directly; if run from inside an active Zellij session, it automatically opens a new Ghostty window via AppleScript to create or reattach to the session, elegantly avoiding nesting restrictions. Multiple Ghostty windows/sessions created this way can be quickly toggled using the `Ctrl + `` shortcut. - Load layout manually:
zellij --layout <layout_name>(e.g.,zellij --layout layout-go)
Config file: ~/.config/fish/config.fish
Common Fish commands:
| Command | Function |
|---|---|
fish_update_completions |
Update command completions |
fish_add_path <path> |
Add a path |
fish_config |
Open interactive configuration |
Custom functions (type c to list functions, a to list abbreviations):
| Command | Function |
|---|---|
c |
Meta command: list all custom Fish functions with descriptions |
a |
Meta command: list all built-in abbreviations and what they do |
d |
Quickly display current date and time |
nh <cmd> |
Run a command in the background and discard output (short for nohup) |
ch <cmd> |
Query cheat.sh for quick command help |
wt [city] |
Show a detailed 15-day weather forecast table including PM2.5 / air quality. Supports all for parallel summaries of multiple cities |
lunar [date] |
Perpetual calendar: Gregorian date + lunar date + zodiac + Heavenly Stems / Earthly Branches (supports specific dates such as 2025-01-29) |
myip |
Show local IP, public IP, and geolocation |
port <num> |
Inspect local TCP/UDP listeners and processes for a specific port |
ports |
Show all local listening TCP ports and processes |
extract <file> |
Universal extractor that auto-detects archive formats (zip, tar, gz, rar, 7z...) |
mkcd <dir> |
Create a directory and cd into it immediately |
proxy / unproxy |
Enable/disable global terminal proxy with one command (helpful when pulling code is slow) |
gitignore <language> |
Fetch a standard .gitignore from GitHub templates (example: gitignore Node) |
backup <file/dir> |
Create a full backup copy of a sensitive file or directory with an exact timestamp |
copy [file] |
Copy a file's contents or the previous command's stdout (| copy) to the macOS clipboard instantly |
f [query] |
Search for a file and open it with Helix. Opens directly if the query matches a single result |
t <text> |
Smart translation / explanation: Chinese to English; English words return US phonetics plus bilingual definitions; English passages are translated into Chinese |
aic |
Auto-generate Git commit messages from code changes. Built on the shared AI router, with rewrite/refine support |
aipr |
Auto-generate Pull Request descriptions from branch changes. Uses a large model to analyze commits and diff |
ait |
Auto-generate a changelog from Git history and create a tag |
aip |
Plug-and-play AI prompt library. Interactively pick common development prompts and copy them to the clipboard automatically |
q [prompt] |
Run the local model declared by AI_LOCAL_MODEL directly through Ollama; thinking follows AI_LOCAL_THINK (defaults to false when unset); without arguments it starts an interactive session |
b [query] |
Search for a file and preview it with bat. Opens directly if the query matches a single result |
s [query] |
Parse hosts from ~/.ssh/config, choose one via fzf, then establish the SSH connection |
rec [name] |
Minimal terminal screencast tool based on asciinema, supporting record, replay (rec play), and web upload (rec upload) |
gtd <tag> |
Delete a Git tag locally and remotely in one command |
gdoctor |
Git repository health diagnostic tool: detects interrupted operations, working tree status, remote sync, stale branches, loose objects, and data integrity, with actionable fix suggestions |
lg |
Launch lazygit terminal UI |
zj |
Smart project-aware Zellij launcher. Generates a stable session name from the current working context: Git projects collapse to a repository-level session, while ordinary directories are distinguished by the current directory. Automatically opens a new window if run inside Zellij to avoid nesting conflicts |
Tip
FZF Performance Boost: This project integrates fd as the default search backend for fzf. This means interactive commands like zi are not only lightning fast but also automatically respect .gitignore rules.
Built-in Abbreviations:
Abbreviations expand automatically when you press space after typing them.
| Abbreviation | Expands to | Meaning |
|---|---|---|
mkdir |
mkdir -p |
Recursively create nested directories, including missing parents |
ls / ll |
eza / eza -l |
Modern file listing / with detailed permissions and sizes |
... / .... |
../.. / ../../.. |
Jump up two or three parent directories quickly |
vi / vim / h |
hx |
Always launch the modern Helix editor |
r |
exec fish |
Reload the current Fish session so config changes take effect quickly |
mfx |
musicfox |
Launch the go-musicfox NetEase Cloud Music terminal client |
cs... |
colorscheme... |
See the custom commands colorscheme / font-size / opacity / audio-volume |
? / ?? |
__ai_cmd / ai_diag_last |
Natural-language to command generation (default q -> aichat) / diagnose the previous failed command (depends on Zellij dump-screen capture) |
g |
git |
Entry point for basic Git commands |
lg |
lazygit |
Launch lazygit terminal UI |
gcs |
git code-stats |
Show today's / this week's / this month's committed code volume for the current repository |
grs |
git repo-stats |
Show a full repository overview including scale, activity, contributors, and object-store size |
ga / gs |
git add / git_status_stats |
Stage files / show status with staged and unstaged line-count stats |
gd / gds |
git diff / git diff --staged |
Show unstaged changes / show staged but uncommitted changes |
gb / gba / gbd |
git branch... |
Show local branches / show all branches including remotes / force-delete a branch |
gc / gca |
git commit / git commit --amend |
Commit code / amend the last commit |
gcm / gcam |
git commit -m / git commit -a -m |
Commit with a message / stage tracked files and commit with a message |
gp / gpl |
git push / git pull |
Push code to remote / pull from remote |
gm / gms |
git merge / git merge --squash |
Merge branches / squash a whole branch into a single change |
grb / grbc / grbi |
git rebase... |
Rebase a branch / continue after resolving conflicts / interactive rebase for picking or squashing commits |
gco / gcl |
git checkout / git clean -fd |
Check out a branch or file (legacy style) / remove untracked files and directories (dangerous, confirm before using) |
gsw / gswc |
git switch / git switch -c |
Switch branches / create and switch to a new branch (recommended modern workflow) |
gr / grh |
git reset / git reset HEAD |
Reset index or HEAD / reset only the index (undo add) |
gro / gros |
git restore... |
Undo working tree changes / undo staged changes (recommended modern reset workflow) |
gsta / gstp |
git stash / git stash pop |
Stash current uncommitted changes / restore stashed changes |
gt / gts |
git tag / git tag -s |
Show local tags / create a GPG-signed tag |
gg |
git log |
Show raw Git commit history |
gl |
git log --oneline --decorate --graph |
High frequency pretty log with branch graph and colored tree structure |
glo / gls |
git log --oneline / git log --stat |
Minimal one-line log / log with per-commit file change statistics |
Tip
Forgot an abbreviation? Just type a at any time to list all abbreviations plus their expanded commands and descriptions.
Vi Mode: Fish supports a Vi-style editing mode, and this configuration enables it by default; the shortcuts below are split between normal mode and insert mode.
Normal Mode:
Enter Vi normal mode: press Esc or Ctrl+[.
| Shortcut | Function |
|---|---|
i/a |
Enter insert mode (before / after cursor) |
h/l |
Move cursor left / right |
k/j |
Previous / next command history item, filtered by current input |
w/b |
Next / previous word |
0/$ |
Beginning / end of line |
d |
Delete with a motion, such as dw or dd |
y |
Yank with a motion, such as yw or yy |
<Space>y |
Explicitly copy the entire current command line to the macOS system clipboard |
p |
Paste |
u |
Undo |
Ctrl+e |
In normal mode, open the current command line in the default editor (hx) fullscreen |
Insert Mode:
| Shortcut | Function |
|---|---|
Esc / Ctrl+[ |
Return to Vi normal mode |
Ctrl+a |
Jump to the beginning of the line |
Ctrl+e |
Jump to the end of the line |
Ctrl+f / Ctrl+b |
Move cursor right / left |
Ctrl+n / Ctrl+p |
Next / previous command history item |
Ctrl+h / Backspace |
Delete the character before the cursor |
Ctrl+d |
When the command line has content, deletes the character under the cursor; when empty, triggers a double-tap exit confirmation (press again within 500ms to actually exit, preventing accidental terminal or Zellij pane closure) |
Ctrl+u |
Delete from the cursor back to the beginning of the line |
Ctrl+k |
Delete from the cursor to the end of the line |
Ctrl+w |
Delete the word before the cursor |
Config file: ~/.config/helix/config.toml
Beginner guide: Helix Quick Start Guide (for Neovim users)
Modes: Normal, Insert, Select
Core shortcuts:
| Shortcut | Function |
|---|---|
i |
Enter insert mode |
Esc |
Return to normal mode |
v |
Enter / exit select mode |
h/j/k/l |
Left / down / up / right |
w/b |
Next / previous word |
gg/ge |
Start / end of file |
x |
Select current line |
y/p |
Copy / paste |
u/U |
Undo / redo |
/ |
Search |
n/N |
Next / previous match |
:w |
Save |
:q |
Quit |
:wq |
Save and quit |
LSP features:
| Shortcut | Function |
|---|---|
gd |
Go to definition |
gy |
Go to type definition |
gr |
Find references |
gi |
Go to implementation |
Space+k |
Show hover documentation |
Space+a |
Code actions |
Space+r |
Rename symbol |
Space+s |
Document symbol list |
Space+S |
Workspace symbol list |
Space+d |
Show diagnostics |
]d / [d |
Next / previous diagnostic |
Space+m |
Markdown preview (Glow) |
LSP configuration:
- Language config:
~/.config/helix/languages.toml - Health checks:
hx --healthorhx --health go - Install LSPs:
This project centrally manages language servers through
mise. See 5.5 Mise Tool Version Management for details. - Restart LSP:
:lsp-restart - View config:
:config-openopens config,:config-reloadreloads it
Core idea:
Abandon the traditional mess caused by global installs such as npm i -g, pip install, and go install, which pollute the system and create version conflicts.
This configuration unifies common runtimes (Go/Node/Bun/Python/Lua/Rust) together with language servers, formatters, and related developer toolchains, plus AI CLIs such as codex, kimi, claude-code, and opencode, under Mise management, achieving elegant isolation on two levels:
- Global disaster-proof baseline: The global config (
~/.config/mise/config.toml) declares fallback runtimes for common languages plus LSP / formatter toolchains that are mostly tracked at@latest, so opening an editor in any normal directory still gives you strong completion and formatting support. - Clean project-level sandbox: Inside a specific project,
mise usecan generate a directory-local.mise.tomlfor precise isolation.- For runtimes: strongly recommended to pin exact versions such as
node@16for consistent team builds. - For LSPs: it is usually recommended to stay on
@latestfor the newest syntax highlighting, hints, and performance optimizations. Only pin older LSP versions when a very old project breaks on the latest release.
- For runtimes: strongly recommended to pin exact versions such as
Config file: ~/.config/mise/config.toml
Note
Special note about certain LSPs:
Some lower-level tools have strong platform dependencies or very complex build chains, such as rust-analyzer and clangd. This configuration does not manage them via Mise. Installing them through brew is still recommended for the best stability and completion experience:
- Rust:
brew install rust-analyzer - C/C++:
brew install llvm(includesclangd)
Common commands:
| Command | Function |
|---|---|
mise install |
Install all missing tools declared by the current directory's .mise.toml or global config |
mise ls |
List currently active and installed tool versions |
mise ls-remote <tool> |
List all remote versions available for that tool |
mise use <tool>@<version> |
Generate .mise.toml in the current project and use a specific version |
mise use -g <tool>@<version> |
Change the global default version |
mise current <tool> |
Show the actual active version source (local or global) |
mise prune |
Free disk space by removing unused old caches |
mise doctor |
Diagnose environment variables and why something may not be taking effect |
Practical examples:
# Query available versions and install
mise ls-remote go
mise use -g go@latest
# Best practice inside a project: pin a runtime, keep the LSP latest
cd my-old-project
mise use node@16
mise use npm:@vtsls/language-server@latest
# Extreme case: the project is so old that the latest LSP breaks parsing,
# so you are forced to pin an older LSP version
cd my-ancient-project
mise use npm:@vtsls/language-server@1.0.0For Git bootstrap configuration such as user identity and multi-account isolation, see 4.7 Configure Git.
Config files:
~/.config/git/config: core behavior config (XDG standard location)~/.config/git/delta-theme.conf: Delta visual theme config, managed uniformly bycolorscheme~/.config/git/ignore: global ignore file (XDG standard location)
Core features:
- Delta integration: Uses
git-deltafor syntax-highlighted diffs with line numbers, side-by-side display, and color optimization;syntax-themeand Delta feature toggles are split into a standalonedelta-theme.confmanaged uniformly by thecolorschemescript. - Smart defaults:
pull.rebase = true: keep commit history linear and tidy.push.autoSetupRemote = true: associate remote tracking branches automatically.init.defaultBranch = main: usemainas the default branch name.rerere.enabled = true: remember conflict resolutions automatically to improve the rebase experience.
Common aliases:
| Alias | Command | Description |
|---|---|---|
git lg |
log --graph ... |
Show a pretty commit graph (compact) |
git lga |
log --graph ... |
Show a pretty commit graph (detailed, with time) |
git last |
log -1 HEAD |
Show the latest commit |
git cleanup |
branch --merged | xargs -n1 branch -d |
Clean up merged local branches |
# Install or reinstall
#
# -nv simulate the operation without actually doing it;
# --restow reinstall by recreating symlinks (delete first, then create);
# --target specifies the symlink target directory;
# --dir specifies the dotfiles source directory;
# --dotfiles converts dot- prefixed package names into hidden files starting with .
#
# Example:
stow -nv --restow --target=$HOME --dir=$HOME/dotfiles --dotfiles ghostty
# Uninstall
stow -nv --delete --target=$HOME --dir=$HOME/dotfiles --dotfiles ghosttyThese commands appear in ~/.local/bin after the bin package is stowed:
colorscheme [name]: Switch themes for Ghostty, Helix, Zellij, Btop, Bat, and Delta together. Without arguments, it shows the current theme and all available themes. Fourteen presets are built in (dracula,catppuccin,catppuccin-latte,rose-pine,tokyonight,gruvbox,gruvbox-light,kanagawa,nord,solarized,one-dark,everforest,everforest-light,dayfox);catppuccinis mapped consistently to theMacchiatovariant,catppuccin-lattemaps to theLattevariant,rose-pine,kanagawa,one-dark,everforest,everforest-light, anddayfoxall use repo-bundled theme assets for Bat / Delta,tokyonightuses a repo-bundled custom syntect theme for Bat plus the official Tokyo Night Delta feature, and thesolarizedpreset maps to the corresponding dark Solarized variant for each tool. Additional options include-i/--interactive(requiresfzf, starts on the current preset, applies the highlighted theme immediately, and exits on Enter orEsc),--current,--list, and--help. With the Git Clean Filter, switching themes does not dirty the repository.git-code-stats [today|week|month|all]: Show today's / this week's / this month's committed code volume for the current repository, including commit count, file-change count, insertions, deletions, and net linesgit-repo-stats [summary|activity|contributors|refs|storage]: Show a one-screen repository overview covering scale, activity, contributor rankings, hot paths, and.gitobject-store size;contributorscan sort by commit count or total changed lines and shows commits, files, insertions, deletions, and net lines; supports--scope head|default|all,--since 30d,--by commits|lines,--top 10, and--jsondot-theme-filter: Git internal filter, not for direct execution. Used with.gitattributesto automatically restore theme settings, Ghostty font size, Ghostty background opacity, and other local visual preferences to defaults duringgit add, decoupling those UI choices from tracked config state.font-size <1-200>: Set Ghostty font size; with the Git Clean Filter this does not dirty the dotfiles repositoryopacity <0.0-1.0>: Set Ghostty background opacity; with the Git Clean Filter this does not dirty the dotfiles repositoryaudio-volume: Volume control and output device switching (requiresswitchaudio-osx)android-mtp [gui|mtp-cli subcommand]: Helper for Android MTP connections on macOS. It depends on Homebrew-installedOpenMTP, briefly suppressesptpcamerad, which often steals the interface, and then delegates to OpenMTP's bundledmtp-cli. The recommended flow isandroid-mtp guibefore using drag-and-drop in the GUI; command-line usage such asandroid-mtp storage-list,android-mtp ls, andandroid-mtp get ...is also supportedpreview-md <file>: Preview a Markdown file in a floating Zellij pane (requiresglow)colors-print: Print the terminal 256-color paletteprint-256-hex-colors: Print hexadecimal values for the 256 colorsvalidate-configs [tool|all]: Validate config syntax and integrity (supports fish/git/zellij/helix/mise/ghostty/karabiner/aichat/go-musicfox)dot-update: One-shot aggregated update for all core dependencies, including Homebrew, Mise toolchains, Fisher plugins, and Helix Tree-sitter grammars
Tip
How changes take effect:
colorscheme: Zellij updates in real time. If Ghostty is already running, the script now triggers a config reload automatically so the new theme applies immediately; if Ghostty is not running, the theme takes effect the next time Ghostty starts. Helix still requires:config-reloadfor already opened buffers; Btop, Bat, and Delta take effect on the next run.colorscheme -i/--interactiveopens the preset list infzfand switches themes as the cursor focus moves, which is useful for rapid theme previewing. The first switch to a repo-bundled custom syntect theme (currentlytokyonight,rose-pine,kanagawa,one-dark,everforest,everforest-light, anddayfox) triggersbat cache --buildautomatically; for Delta,tokyonightenables the official Tokyo Night feature, whilerose-pine,kanagawa,one-dark,everforest,everforest-light, anddayfoxenable repo-bundled Delta features. If Ghostty auto-reload fails, pressCmd + Shift + ,manually.font-size/opacity: If Ghostty is already running, the script now triggers a config reload automatically so the new font size or opacity applies immediately; if Ghostty is not running, the change takes effect the next time Ghostty starts. If Ghostty auto-reload fails, pressCmd + Shift + ,manually; with the Git Clean Filter, these local visual preferences do not dirty the dotfiles repository.
Positioning: This project installs OrbStack through Homebrew and treats it as a lightweight, modern container and Linux development environment on macOS.
Current boundary: This repository currently does not manage OrbStack GUI preferences, VM parameters, networking, volumes, registry mirrors, or other in-app configuration. It only provides installation and terminal-side compatibility.
Why it relates to SSH: A Linux machine inside OrbStack can be treated like a local Linux host that is reachable over SSH. That means its terminal compatibility, connection style, and tooling integration are essentially the same class of problem as ordinary remote servers.
What is already integrated:
- Fish automatically adds
~/.orbstack/bintoPATH, so after installation you can use commands likedocker,docker compose, andorbdirectly. - Fish applies dynamic
TERM=xterm-256colordowngrading tossh/orb, preventingunknown terminal typeissues when connecting from Ghostty into SSH or OrbStack remote environments. - The
scommand now recursively resolvesIncludeentries in~/.ssh/config, so it can also discover OrbStack-generated SSH host configs.
How to integrate it into SSH:
- After first installation, open OrbStack once manually to ensure the background service has started.
- Add this line to
~/.ssh/config:Include ~/.orbstack/ssh/config - After that, you can treat OrbStack Linux machines exactly like normal SSH hosts, including selecting them through the repository's
scommand.
Most common usage patterns:
- If you mainly run containers, your daily workflow is usually just terminal commands like
docker ps,docker compose up -d, anddocker exec -it <container> /bin/bash. - If you need a full Linux development machine, create one in the OrbStack GUI, usually with
Ubuntu. - After creation, you can enter it directly with
orb -m <machine_name>or via SSH, for examplessh <user>@<machine_name>@orb. - Common scenarios include reproducing a more server-like Linux environment locally, isolating legacy project dependencies, or running databases and backend services inside an isolated machine.
- When you need to inspect containers, images, volumes, or Linux machine state, simply open the OrbStack GUI.
This project makes a series of intentional customizations on top of the default configuration of each tool. The table below summarizes all key deviations from official defaults, so you can quickly understand what is opinionated in these dotfiles.
| Change | Official default | This project | Why |
|---|---|---|---|
| Swap Caps Lock ↔ Left Control | Keep original positions | Swap them (excluding HHKB-layout keyboards) | Caps Lock is in a better position for frequent Ctrl usage; Emacs, Zellij, Helix, and Vim all rely heavily on Ctrl |
Right Command + a |
Unbound | Area screenshot (mapped to Cmd + Shift + 4) |
Uses the right-side Command key as a screenshot prefix without polluting high-frequency Ctrl / Alt terminal combos |
Right Command + f |
Unbound | Full-screen screenshot (mapped to Cmd + Shift + 3) |
Keeps full-screen capture in the same mental model and avoids memorizing the native macOS combo |
Right Command + r |
Unbound | Open screenshot / recording toolbar (mapped to Cmd + Shift + 5) |
Reuses one entrypoint for both capture and recording and covers more of the built-in macOS workflow |
Right Command + c |
Unbound | Area screenshot to clipboard (mapped to Ctrl + Cmd + Shift + 4) |
Sends captures straight to the clipboard for chat, docs, and multimodal AI input flows |
Right Command + s |
Unbound | Stop the built-in screen recording flow (mapped to Ctrl + Cmd + Esc) |
Adds a more ergonomic and consistent stop action for native macOS recording |
How to use these shortcuts:
Right Command + a: After pressing it, the pointer turns into crosshairs. Drag to select an area, then release to capture it. PressEscto cancel.- Window screenshot: press
Right Command + a, then pressSpaceonce to switch into window mode. Move the pointer onto the target window and click when it is highlighted. Right Command + f: Captures the screen immediately.Right Command + r: Opens the native macOS screenshot / recording toolbar. From there you can switch between area, window, or full-screen capture, full-screen or selected-area recording, and configure save location, timer, and mouse-pointer behavior.- Start recording: after choosing either full-screen recording or selected-portion recording in the toolbar, click
Recordto begin. For full-screen recording, Apple also documents clicking the screen to begin recording. In practice, pressingEnteralso starts recording, which is convenient for keyboard-driven use. - Stop recording: click the stop button in the menu bar, or press
Right Command + s. This maps to the native stop shortcutControl + Command + Esc. Right Command + c: Starts area-to-clipboard capture. Drag and release to capture, then paste directly into chat apps, documents, or multimodal AI inputs. No file is written by default.
What happens after a screenshot:
- If screenshot thumbnails are enabled in macOS, a temporary thumbnail appears in the lower-right corner. Click it to annotate, crop, share, or delete immediately.
- If you ignore the thumbnail, macOS auto-saves the screenshot to the current configured location, usually the Desktop unless you changed it in the
Cmd + Shift + 5toolbar. - To change the save location, disable the floating thumbnail, or adjust the timer, open the toolbar with
Right Command + rand useOptions. - After a recording stops, macOS shows a similar thumbnail. Click it to preview, trim, or share; otherwise the
.movfile is saved automatically.
- This repository installs
Shottrby default through Homebrew as the default integrated screenshot annotation tool. - If you want scrolling captures, OCR, pinned overlays, or a unified annotation UI, use
Shottrdirectly through its own global shortcuts. - Because Shottr global hotkeys intercept system-wide key events, the installer never silently overwrites your existing Shottr setup. Instead, it explicitly asks before writing the recommended values
Shift + Cmd + 1/2/A/S. If you want to apply them later, run"$HOME/dotfiles/bin/configure-shottr-hotkeys" --force - If macOS shows permission prompts the first time you launch
Shottror trigger its hotkeys, grant the requested permissions and then test the capture / OCR flow again. - Under Shottr's current licensing terms, personal use is free and commercial use requires a paid license.
Keybinding changes:
| Change | Official default | This project | Why |
|---|---|---|---|
Cmd + ; → Quick Terminal |
Unbound | global:cmd+;=toggle_quick_terminal |
Toggle the terminal globally with one shortcut |
Cmd + 1~9 |
Switch Ghostty tabs | unbind |
Frees those keys for Zellij tab switching |
| Copy on select | copy-on-select = true |
copy-on-select = clipboard |
Copy to both the primary selection and the system clipboard |
Behavior changes:
| Change | Official default | This project | Why |
|---|---|---|---|
| Window state restore | default |
window-save-state = never |
Avoid conflicts with Zellij session restore |
| Hide mouse while typing | false |
mouse-hide-while-typing = true |
Reduce visual distraction |
| Background blur | false |
background-blur = 25 |
Frosted-glass effect when using transparency |
| Unfocused split opacity | 0.7 |
unfocused-split-opacity = 0.3 |
More obvious distinction between focused and unfocused panes |
| Environment variable | None | env = GHOSTTY_RUNTIME=1 |
Lets Fish detect whether it is running inside Ghostty |
Architecture changes:
| Change | Official default | This project | Why |
|---|---|---|---|
| Keybinding system | Built-in defaults | keybinds clear-defaults=true rebuilt from scratch |
Streamline and unify Vim-style navigation, remove unused bindings |
| Default layout | default |
default_layout "dev-workspace" |
Use a custom general-purpose development workspace layout that inherits the launch directory |
| Session name | Random | session_name "main" |
Fixed session name for easy attach |
| Auto attach | false |
attach_to_session true |
Opening a new terminal attaches to the existing session automatically |
| Theme | default |
theme "dracula-pro" |
Custom Dracula variant that fixes selection color compatibility |
Keybinding enhancements:
| Change | Description |
|---|---|
Global Cmd + 1~9 tab switching |
No need to enter tab mode, enabled by Ghostty unbinding its native keys |
Add h/j/k/l navigation in all modes |
Panes, tabs, resize, move, and scroll all support Vim-style navigation |
Ctrl + a enters tmux compatibility mode |
Gives tmux users a muscle-memory compatibility layer |
Behavior changes:
| Change | Official default | This project | Why |
|---|---|---|---|
| Greeting | Show version info | fish_greeting "" (disabled) |
Keep terminal startup clean |
| Editing mode | Emacs mode | Hybrid Vi mode (Vi + Emacs insert) | Vi-first key style while preserving Emacs insert shortcuts such as Ctrl-a/e |
| Default editor | None | EDITOR=hx / VISUAL=hx |
Standardize on Helix |
| Pager | None | MANPAGER uses bat syntax highlighting |
Makes man pages easier to read |
| Homebrew auto-update | Enabled | HOMEBREW_NO_AUTO_UPDATE=1 |
Avoid waiting on updates during every package install |
| Fisher plugin path | ~/.config/fish |
~/.local/share/fisher |
Isolate third-party plugins and keep the config directory clean |
| Zellij auto-start | Disabled | Auto-start inside Ghostty | Ghostty handles auto-start natively; no need to manually type zellij, and falls back to Fish if uninstalled |
Keybinding changes:
| Change | Description |
|---|---|
Ctrl + d (insert/normal) |
When the command line is empty, requires a double-tap within 500ms to exit, preventing accidental terminal or Zellij pane closure; when the command line has content, retains the default delete-char behavior |
Ctrl + e (normal mode) |
Use Helix to edit the current command line fullscreen |
<Space>y (normal mode) |
Explicitly copy the full command line to the macOS system clipboard without changing the default yank semantics |
| Vi cursor shapes | normal=block, insert=line, replace=underscore |
Tide vi_mode indicator |
D → N to align with Vim community usage of N for Normal |
Keybinding changes:
| Change | Official default | This project | Why |
|---|---|---|---|
Ctrl + r |
Unbound | :reload reload current file |
Quickly reload files modified externally |
Space + m |
Unbound | Preview Markdown with glow | Render Markdown in a floating window |
Space + o/i |
Unbound | expand_selection / shrink_selection |
Replaces Alt-o/i to avoid Alt key conflicts |
Insert mode Ctrl + f/b/n/p/a/e |
Unbound | Emacs-style cursor movement | Move quickly without leaving insert mode |
j / k |
Move by visual line | Move by physical line (move_line_down/up) |
Works better with soft wrapping and line-number jumps, avoiding cases where 6j land in the wrong place |
gj / gk |
Move by physical line | Move by visual line (move_visual_line_down/up) |
Use these when you really want to move line by line visually |
Display changes:
| Change | Official default | This project | Why |
|---|---|---|---|
| Line numbers | Absolute | line-number = "relative" |
Works better with Vim motions |
| Cursor line / column highlight | Both disabled | cursorline = true / cursorcolumn = true |
Easier cursor positioning |
| Buffer line | never |
bufferline = "multiple" |
Show tabs when multiple files are open |
| Color mode indicator | false |
color-modes = true |
Different modes get different colors |
| Inlay hints | false |
display-inlay-hints = true |
Show inline type hints and similar info |
| End-of-line diagnostics | disable |
end-of-line-diagnostics = "warning" |
Display warnings directly at line end |
| Inline diagnostics | disable |
cursor-line = "warning" / other-lines = "warning" |
Show diagnostics inline across all lines |
| Cleanup on save | Both disabled | trim-final-newlines / trim-trailing-whitespace = true |
Keep files tidy |
| Soft wrap | Disabled | soft-wrap.enable = true |
Automatically wrap long lines |
| Change | Official default | This project | Why |
|---|---|---|---|
| Default editor | vim |
core.editor = hx |
Standardize on Helix |
| Pager | less |
core.pager = delta |
Syntax-highlighted diffs |
| Pull strategy | merge |
pull.rebase = true |
Keep history linear |
| Push behavior | Upstream must be set manually | push.autoSetupRemote = true |
No need for --set-upstream |
| Conflict style | merge |
merge.conflictstyle = diff3 |
Show base / ours / theirs simultaneously |
| Default branch name | master |
init.defaultBranch = main |
Modern community convention |
| Reuse recorded conflict resolution | Disabled | rerere.enabled = true |
Remember conflict resolutions automatically |
| User identity | Hardcoded in config | Injected via include local files |
Prevent sensitive identity data from entering the repo |
| Theme decoupling | Changing themes dirties the repo | Handled through Git Clean Filter automatically | Ensure local theme switches do not produce unstaged changes |
| Change | Official default | This project | Why |
|---|---|---|---|
| Tap-to-click | Disabled by default; a physical press is required to click | Enabled | Reduces finger strain and makes pointer interaction lighter |
| Force Click and haptic feedback | Enabled by default | Disabled | Avoids accidental lookup/preview actions and keeps interaction consistently tap-first |
| Three-finger drag | Disabled by default | Enabled | Makes text selection and app-window dragging easier without having to press down while dragging |
This project introduces a Makefile to standardize daily maintenance tasks and integrate installation, synchronization, validation, and cleanup.
| Command | Description |
|---|---|
make help |
Show the help menu (default) |
make install |
Run the install.sh installer |
make stow |
Create symlinks for all config files |
make unstow |
Remove all symlinks (uninstall configs) |
make restow |
Repair / rebuild all symlinks |
make stow-<package> |
Sync only one package, such as make stow-fish or make stow-ghostty |
make fish |
Set Fish as the default shell |
make plugins |
Update Fisher plugins |
make macos |
Apply macOS system preferences |
make validate |
Run full config validation, including tool checks |
make lint |
Run shellcheck on repository Shell scripts, including bootstrap.sh, install.sh, macos.sh, and bin/* |
make docs |
Generate or update the README TOC |
make update |
Pull remote code and update the entire core toolchain stack via dot-update |
If you encounter issues during installation or usage, please first refer to the solutions for these common high-frequency problems:
Q: How do I copy a command line to the macOS system clipboard without a mouse?
A: In the Fish interactive environment (which has Vi mode enabled by default), you can use the following precise keyboard-driven methods:
- Press
Esc(orCtrl+[) to enter Normal mode, then press<Space>y(Space followed by y). The entire current command line will be instantly copied to the system clipboard, and you can paste it anywhere usingCmd+v.
Q: How do I copy specific content from a Zellij pane without using a mouse?
A: In Zellij, you can achieve precise copying through the following keyboard combo:
- Press
Ctrl + sto enter Scroll mode.- Press
eto open the entire scrollback buffer of the current pane in the Helix editor.- Inside Helix, press
vto enter select mode, then use motion keys (orw/b,/for search) to select the exact text you want.- Press
<Space>yto yank the selected text directly to the macOS system clipboard.- Press
:qto exit Helix and seamlessly return to your Zellij pane.
Q: The VS Code integrated terminal fails to display icons properly, showing a bunch of "tofu blocks" / garbled text / question marks. How can I fix this?
A: This is usually because VS Code is not configured to use a Nerd Font with full icon support and ligatures. You can fix this by running
hx ~/Library/Application\ Support/Code/User/settings.jsonin your terminal (or opening User Settings JSON from the Command Palette), and adding the following lines:{ "terminal.integrated.fontFamily": "'JetBrainsMono Nerd Font', 'FiraCode Nerd Font', monospace", "editor.fontFamily": "'JetBrainsMono Nerd Font', 'FiraCode Nerd Font', Menlo, Monaco, 'Courier New', monospace", "editor.fontLigatures": true }
Q: In the VS Code integrated terminal, when using the zi command (or any fzf interactive prompt), pressing Ctrl + j/k/n/p outputs garbled text instead of navigating up or down. How do I fix this?
A: This happens because VS Code intercepts these control keys by default instead of passing them directly to the underlying shell applications. You can fix this by opening User Settings JSON from the Command Palette (or by running
hx ~/Library/Application\ Support/Code/User/settings.json), adding the following configuration, and restarting VS Code:{ "terminal.integrated.sendKeybindingsToShell": false }
Q: Many shortcuts in the terminal (like split screen, paging) suddenly stopped working?
A: This often happens because
Ctrl + gwas accidentally pressed, entering Zellij's Locking Mode. In locked mode, Zellij intercepts all of its own shortcut bindings to "pass them through" to internal programs without conflicts. Simply pressCtrl + gagain to unlock and return to normal.
Q: When using aichat shortcuts or commands, it prompts that the model cannot be found or network timeout?
A: Please check two things:
- Ensure you have correctly configured the model name (e.g.,
AICHAT_MODEL) and the corresponding API Key in~/.fish.local.fish. After configuring, be sure to runexec fishto reload the environment or restart the terminal.- If you are using local Ollama, make sure
ollama serveis running, the target model has been downloaded withollama pull <model>, and the model name is included inlocal-llm.modelsinside~/.config/aichat/config.yaml.- If the API of the model you are using is access-restricted (e.g., accessing OpenAI from certain regions), you may need to enable a global proxy in your terminal. This configuration has built-in
proxyandunproxyshortcuts to help you toggle the terminal proxy with one click.
Q: Why does Fish wait for a moment before showing the next prompt after a command finishes?
A: This usually is not Fish itself being slow. More often,
miseauto-activation is refreshing the environment for the current directory. If~/.config/mise/config.tomlor the current project's.mise.tomldeclares tools that are not installed yet,misemay become noticeably slow while checking them during the prompt phase.
- First run
mise currentto see which tools and versions are active in the current directory. If you see messages likeis specified ... but not installed, missing tools are usually the cause.- Then run
mise installto install the missing tools declared by the current directory and global config, and usemise lsto verify the installed state afterward.- If you only want to confirm whether
miseis the trigger, temporarily runenv MISE_FISH_AUTO_ACTIVATE=0 fishto start a new shell withoutmiseauto-activation. If the lag disappears, inspect yourmiseconfig next.- If the cause is still unclear, run
mise doctorto continue checking activation state and environment configuration.
Q: While writing code in the Helix editor, why is there no syntax hinting or code checking?
A: Helix relies on Language Servers (LSP) to provide intelligent completion capabilities. This project centrally manages LSPs via
mise:
- Make sure you have run
mise installin the terminal to fetch the latest LSP toolchain.- Type
:login Helix to read the logs, or runhx --healthin the terminal to verify if the LSP startup for the corresponding language reported any errors.- LSPs for certain languages (like C/C++ or Rust) are recommended to be independently installed using brew for optimal stability (e.g.,
brew install llvmorbrew install rust-analyzer).
This project would not exist without the flourishing modern open-source ecosystem. Special thanks to the following outstanding projects that form the foundation of this workflow:
- Homebrew / GNU Stow
- Ghostty / Zellij
- Fish / Fisher / Tide / fzf.fish
- Helix / Mise
- AIChat / GitHub CLI
- fzf / fd / zoxide / ripgrep
- bat / eza / git-delta / glow
- grc / shellcheck / btop / asciinema
- JetBrains Mono / Maple Mono / Geist Mono / Nerd Fonts
- Karabiner-Elements / OrbStack / switchaudio-osx
This project is released under the MIT License.
You are free to use, study, modify, and distribute this code as the starting point for building your own personal workflow.