doti 0.1.0

A fast, interactive TUI dotfiles manager with copy-based sync and secret scanning
Documentation
<div align="center">

# doti

### A fast, interactive TUI for managing your dotfiles

Copy-based sync &middot; Secret scanning &middot; No symlink mess

[![Crates.io](https://img.shields.io/crates/v/doti?style=flat-square&logo=rust&logoColor=white&color=orange)](https://crates.io/crates/doti)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)

<br>

<img src="assets/demo.gif" alt="doti demo" width="720">

<br>

</div>

## 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]https://rustup.rs (1.85+)

```bash
git clone https://github.com/volcmen/doti.git
cd doti
cargo install --path .
```

### From crates.io

```bash
cargo install doti
```

## Quick start

```bash
# 1. Go to your dotfiles repo and initialize
cd ~/my-dotfiles
doti init

# 2. Launch the TUI (works from anywhere after init)
doti
```

`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

<details>
<summary>Full key reference (press <code>?</code> in TUI)</summary>

#### 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 |

</details>

## 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`

```toml
[repo]
path = "~/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

```bash
doti scan
```

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.

```bash
cargo run -- help       # Show CLI help
cargo run               # Launch TUI in dev mode
cargo run -- scan       # Test secret scanning
```

## License

[MIT](LICENSE)