doti
A fast, interactive TUI for managing your dotfiles
Copy-based sync · Secret scanning · No symlink mess
Why doti?
Most dotfile managers either scatter symlinks across your system or require you to learn a DSL. doti takes a different approach:
- Your configs live in a
home/directory that mirrors$HOME - Changes are copied, never symlinked -- your system files stay untouched until you say so
- An interactive TUI lets you browse, diff, and sync files with a few keystrokes
- A built-in secret scanner catches leaked keys before you push
Features
| Interactive TUI | Browse tracked and untracked files in a tree view with live diffs |
| Copy-based sync | No symlinks, no magic -- explicit file copies in either direction |
| Inline diffs | Side-by-side diff of repo vs system changes, right in the terminal |
| Secret scanning | Detects API keys, tokens, passwords, and private keys before they leak |
| Lazy loading | Untracked directories scanned on demand for fast startup |
| Backup on apply | Optionally back up system files before overwriting |
| Run from anywhere | Config file remembers your repo path after doti init |
Installation
From source
Requires: Rust toolchain (1.85+)
From crates.io
Quick start
# 1. Go to your dotfiles repo and initialize
# 2. Launch the TUI (works from anywhere after init)
doti init creates a config at ~/.config/doti/config.toml and ensures the home/ directory exists in your repo.
Usage
Commands
| Command | Description |
|---|---|
doti |
Launch the interactive TUI |
doti init |
Set up config (run from your dotfiles repo) |
doti scan |
Scan for leaked secrets |
doti help |
Show help |
TUI overview
The TUI has two tabs:
Tracked -- files in your repo, compared against the system:
| Icon | Status | Meaning |
|---|---|---|
✓ |
Synced | Repo and system files are identical |
⚠ |
Differs | Content has changed -- diff shown in panel |
✗ |
Absent | In repo but missing from system |
Untracked -- system files not yet in the repo. Directories load lazily when expanded.
Key bindings
Navigation
| Key | Action |
|---|---|
j / k , ↑ / ↓ |
Move cursor |
g / G |
Jump to top / bottom |
h / ← |
Collapse directory or go to parent |
→ / Space |
Expand directory |
Enter |
Expand directory / open action popup |
Tab |
Switch between Tracked and Untracked |
Tracked tab
| Key | Action |
|---|---|
l |
Apply -- copy repo to system |
p |
Pull -- copy system to repo |
b |
Backup system file, then apply |
d |
Delete from repo (system untouched) |
Untracked tab
| Key | Action |
|---|---|
a |
Add -- copy system file into repo |
Panel & general
| Key | Action |
|---|---|
J / K |
Scroll diff / preview |
PgDn / PgUp |
Scroll faster |
r |
Refresh all |
? |
Toggle help overlay |
q / Esc |
Quit |
How it works
Your dotfiles repo has a home/ directory that mirrors $HOME:
my-dotfiles/
└── home/
├── .config/
│ ├── hypr/hyprland.conf → ~/.config/hypr/hyprland.conf
│ ├── kitty/kitty.conf → ~/.config/kitty/kitty.conf
│ └── fish/config.fish → ~/.config/fish/config.fish
├── .bashrc → ~/.bashrc
└── .gitconfig → ~/.gitconfig
| Action | Direction | System files |
|---|---|---|
| Add | system → repo | Untouched |
| Pull | system → repo | Untouched |
| Apply | repo → system | Overwritten |
| Backup + Apply | repo → system | Backed up first |
| Delete | removes from repo | Untouched |
Configuration
Config location: ~/.config/doti/config.toml
[]
= "~/dotfiles"
If you run doti inside a git repo, it uses that repo directly. Otherwise it falls back to the config file. This lets you run doti from anywhere after doti init.
Secret scanning
Scans all git-tracked files for:
- Private keys (
-----BEGIN ... PRIVATE KEY) - AWS access keys (
AKIA...) - GitHub / GitLab tokens
- OpenAI / Anthropic API keys
- Slack tokens
- Generic secrets (passwords, API keys with assigned values)
Binary files and .gitignore'd paths are skipped automatically.
Exits with code 1 if secrets are found -- useful in CI or as a pre-commit hook.
Project structure
src/
├── main.rs Entry point & CLI routing
├── lib.rs Library root
├── config.rs Config file loading & saving
├── link.rs File sync operations (copy, compare)
├── scan.rs Directory scanning & tracking
├── tree.rs Tree data structure for UI
├── ui.rs Ratatui TUI rendering
├── secrets.rs Secret scanning rules
└── app/
├── mod.rs Core application state & event loop
├── input.rs Keyboard input handling
├── ops.rs File operations (add, apply, pull, delete)
├── panel.rs Diff panel logic
└── popup.rs Action popup handling
Contributing
Contributions welcome! Feel free to open issues or submit pull requests.