Symlinks Manager¶
Cross-platform dotfiles symlink manager with layered architecture.
Commands¶
All symlinks commands are run via Task from the dotfiles root directory. The tool uses uv run internally for project-local execution.
task symlinks:link¶
Deploy symlinks for current platform (common + platform layers). Additive only - creates new symlinks without removing existing ones.
Use when adding new dotfiles to create their symlinks without disturbing existing ones.
task symlinks:relink¶
Complete refresh - removes all symlinks and recreates them.
Use after removing files from dotfiles repo or when you need a clean slate.
task symlinks:check¶
Verify symlink integrity.
Shows broken symlinks in home directory.
task symlinks:show¶
Display current symlinks.
task symlinks:unlink¶
Remove all symlinks.
Direct Usage (Advanced)¶
If needed, run the tool directly from dotfiles root:
uv run symlinks link common
uv run symlinks link macos
uv run symlinks relink macos
uv run symlinks check
Pass -v/--verbose before the subcommand to show individual file operations:
uv run symlinks -v link macos # Show each file as it's linked
uv run symlinks -v relink macos # Show each file during full refresh
Architecture¶
The symlinks tool uses a layered architecture: common base + platform overlay.
Common base (common/):
- Shared configs across all platforms
- .zshrc, .config/nvim, .config/tmux, etc.
- Linked first
Platform overlay (macos/, wsl/, archlinux/, linux/):
- Platform-specific configs
- Overrides or extends common configs
- Linked second (can override common)
- Optional per layer: a minimal platform like
linuxships only a shell overlay (shell/linux/) and noconfigs/linux/orapps/linux/.linkandrelinkskip a missing layer instead of erroring; only a platform absent from every layer (a typo) fails.
Conflict handling:
- File vs file: Platform overlay wins
- Directory vs directory: Merged (both symlinked)
- File vs directory: Error (must resolve manually)
Special Directory Handling¶
The symlinks manager maps apps/ and shell/ to specific target directories rather than $HOME, using the same create_symlinks function with a custom target_dir.
Shell scripts (apps/common/menu, apps/common/notes, etc.):
- Symlinked to
~/.local/bin/ - Examples:
menu,notes,patterns,aws-profiles
Shell source files (shell/common/functions.sh, shell/common/aliases.sh, shell/{platform}/{platform}.sh):
- Symlinked to
~/.local/shell/ - Common:
functions.sh+aliases.shon all platforms - Platform-specific:
macos.sh,archlinux.sh,wsl.sh,linux.sh - These are shell code (functions + aliases), not config —
~/.local/shell/is intentional
Go apps (toolbox, sesh):
- Installed from GitHub via
go install(defined inpackages.yml) - Development in
~/tools/toolbox/ - NOT managed by symlinks - binaries go to
~/go/bin/
Personal CLI tools (theme, font):
- Installed via custom installers that clone to
~/.local/share/ - Symlink
~/.local/share/{tool}/bin/{tool}→~/.local/bin/{tool} - Development in
~/tools/theme/,~/tools/font/ - NOT managed by symlinks manager - have their own installers
Usage¶
The symlinks tool runs via uv run from the dotfiles root directory. Use Task commands for the best experience:
task symlinks:link # Create symlinks (additive, safe)
task symlinks:relink # Full refresh (removes + recreates)
task symlinks:check # Verify symlinks
task symlinks:show # Display current symlinks
No installation required - uv run executes the tool in-place.
When to Link vs Relink¶
Use task symlinks:link (additive, safe):
- Adding new files to dotfiles repo
- Adding new dotfile directories
- After fresh install or setup
Use task symlinks:relink (full refresh):
- Removing files from dotfiles repo
- Moving files between directories
- Fixing broken or stale symlinks
- When you need a clean slate
- Changing platform (macos → wsl, etc.)
- Symlink errors or broken links
Symptom of outdated symlinks: "module not found" errors in Neovim after creating new files in common/.config/nvim/lua/ directories.
Testing¶
The symlinks tool has comprehensive pytest test suite.
cd ~/dotfiles/symlinks
uv run python -m pytest tests/ -v # Run all tests
uv run python -m pytest tests/test_core.py # Unit tests
uv run python -m pytest tests/test_integration.py # Integration tests
Tests cover:
- Link creation and unlinking
- Conflict detection
- Platform overlay logic
- Cross-platform path resolution
- Edge cases (loops, permissions)
Configuration¶
Exclusion patterns in symlinks/core.py (EXCLUDE_PATTERNS constant):
Excluded by default:
.git/directories.DS_Storefiles__pycache__/directories.pytest_cache/directories.venv/virtual environments
Platform-specific exclusions: Each platform config can define additional exclusions.
Critical Bugs to Avoid¶
Substring Matching¶
Problem: Pattern .git/ incorrectly excluded .gitconfig
Fix: Check for /.git/ or starts with .git/, not substring match
See: docs/learnings/directory-pattern-matching.md
Relative Path Calculation¶
Problem: Manual path calculation broke 122 symlinks
Fix: Use Python stdlib Path.relative_to(walk_up=True) (Python 3.12+)
See: docs/learnings/relative-path-calculation.md
Cross-Platform Files¶
Problem: Some files needed on all platforms weren't symlinked
Fix: Test edge cases - .gitconfig, .gitignore, .gitattributes should NEVER be excluded
See: docs/learnings/cross-platform-symlink-considerations.md
Troubleshooting¶
Symlinks not created:
- Run with verbose flag:
uv run symlinks -v link macos - Check for permission errors
- Verify source files exist in dotfiles repo
Broken symlinks:
- Run
task symlinks:checkto find them - Remove:
find ~ -type l ! -exec test -e {} \; -delete - Re-run:
task symlinks:link
File conflicts:
- Manual resolution required
- Check conflict error message for paths
- Decide: keep existing file or use dotfiles version
- Move existing file to backup, then relink
Module not found in Neovim:
- Added new files in
common/.config/nvim/lua/? - Run:
task symlinks:link - Restart Neovim
Development¶
The package lives at symlinks/ in the repo root as part of the main dotfiles project — cli.py for the Typer interface, core.py for all logic. Dependencies (typer, rich) and entry point are defined in the root pyproject.toml. Run from anywhere with uv run symlinks.
Dependencies: typer (CLI framework), rich (console output)
Python version: 3.12+ (requires Path.relative_to(walk_up=True))