Commands
All commands are available via:
bunx @dotformat/cli <command>
# or from a clone:
bun bin/dotfiles.ts <command>Quick Reference
| Command | Purpose | Key Flags |
|---|---|---|
collect | Machine snapshot → .dotf report | --no-redact, --slim, -o |
backup | Copy config files → structured directory | --archive, --only, --skip, --no-redact, -o |
scan | Standalone sensitivity scan | path argument |
restore | Restore backup → live locations | --pick, --dry-run |
diff | Backup vs live system | --section |
status | Quick backup summary | — |
compare | Diff two .dotf files | positional file paths |
list | Query section from latest report | fuzzy section name |
collect
Generate a structured .dotf machine snapshot. This is the primary "what's on my machine?" command.
dotfiles collect [--no-redact] [--slim] [-o path]Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--no-redact | boolean | false | Include sensitive values as-is, skip all redaction |
--slim | boolean | false | Truncate content sections to 10 lines. Appends ... (N more lines) to truncated sections. Reduces output by ~65% — useful for feeding to AI tools |
-o <path> | string | auto | Custom output directory |
Behavior
- Resolves output directory (see Output Path Logic)
- Runs all collectors in parallel via
Promise.allSettled— one collector failing does not abort the report - Merges all fulfilled results into a single sections map
- If redaction is enabled (default):
- Scans each content section for sensitive patterns
- Drops sections where action is
skip(e.g., private keys) - Applies
[REDACTED]replacements where action isredact
- If
--slimis enabled, truncates content sections to 10 lines - Stringifies to
.dotfformat via@dotformat/core - Writes to
<hostname>-YYYYMMDDHHMMSS.dotf - Prints sensitivity report if any findings
Collectors
The collect command runs these collectors in parallel:
| Collector | Source | Type |
|---|---|---|
collectMeta | hostname, OS, date | Hand-written |
registryCollector(registryEntries) | All 23 registry entries | Generated from registry |
collectSsh | ~/.ssh/config parsed | Hand-written (structured items) |
collectOllama | ollama list output | Hand-written (command) |
collectApps | /Applications, Raycast, AltTab | Hand-written (macOS only) |
collectHomebrew | brew list --formula/--cask | Hand-written (macOS only) |
Example Output
$ dotfiles collect
Report saved to: reports/MacBook-Pro-20260407143022.dotf
⚠ Sensitivity report:
HIGH npm.config auth token — redacted
MEDIUM ssh.config IP address — included
1 items redacted. Use --no-redact to include all.$ dotfiles collect --slim -o /tmp
Report saved to: /tmp/MacBook-Pro-20260407143022.dotfbackup
Copy real config files into a structured directory tree. Unlike collect (which produces a single text file), backup creates actual file copies organized by category.
dotfiles backup [--no-redact] [--archive] [--only <categories>] [--skip <categories>] [-o path]Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--no-redact | boolean | false | Skip sensitivity redaction |
--archive | boolean | false | After creating backup dir, compress to .tar.gz via system tar, then remove the directory |
--only <list> | comma-separated | all | Include only these categories (e.g., --only ai,shell) |
--skip <list> | comma-separated | none | Exclude these categories (e.g., --skip editor,npm) |
-o <path> | string | auto | Custom output directory |
Available Categories
ai, shell, git, editor, terminal, ssh, npm, bun
Behavior
- Resolves output directory
- Creates timestamped backup directory:
backup-<hostname>-YYYYMMDDHHMMSS/ - Filters backup sources by
--only/--skip - For each source entry:
- File entries: reads file → scans for sensitivity → applies custom redaction if defined → applies pattern redaction → writes to backup dest
- Dir entries: copies all files recursively (using
Bun.Globwithdot: truefor dotfiles)
- If
--archive: runstar czfon the backup dir, removes the original directory - Prints summary: file count per category
- Prints sensitivity report
Example Output
$ dotfiles backup --only ai,shell
Backup saved to: reports/backup-MacBook-Pro-20260407143200
8 files across: ai (7), shell (1)
$ dotfiles backup --archive
Archive saved to: reports/backup-MacBook-Pro-20260407143200.tar.gz
15 files across: ai (7), shell (1), git (3), editor (2), terminal (1), bun (1)Special Redaction
Some entries have custom redaction functions in addition to pattern-based scanning:
| Entry | Custom Redaction |
|---|---|
ssh.config | redactSshConfig() — replaces HostName values with [REDACTED] |
npm.config | redactNpmTokens() — replaces _authToken=... values with [REDACTED] |
scan
Standalone sensitivity scanner. Useful for checking any file or directory for secrets before sharing.
dotfiles scan [path]Arguments
| Argument | Default | Description |
|---|---|---|
path | . (current directory) | File or directory to scan |
Behavior
- Single file: scans the file, reports findings
- Directory: recursively scans all files using
Bun.Glob('**/*')withdot: true- Skips
node_modules/and.git/paths - Skips files larger than 1 MiB
- Skips
- Findings are sorted by severity (HIGH → MEDIUM → LOW)
- Each finding shows: line number, severity, pattern label, and matched text (truncated to 40 chars)
Example Output
$ dotfiles scan ~/.ssh/config
~/.ssh/config
L3 [MEDIUM] IP address: 192.168.1.***...
L7 [MEDIUM] email address: user@exam...
⚠ Sensitivity report:
MEDIUM ~/.ssh/config IP address — redacted
1 items redacted. Use --no-redact to include all.restore
Restore backed-up files to their original locations on the machine. Full safety features: dry run, interactive picker, pre-restore snapshots, conflict prompts.
dotfiles restore <backup-path> [--pick] [--dry-run]Arguments & Flags
| Argument/Flag | Type | Required | Description |
|---|---|---|---|
<backup-path> | string | yes | Path to a backup directory |
--pick | boolean | no | Interactive category selection (checkbox UI) |
--dry-run | boolean | no | Preview the restore plan without writing any files |
Restore Plan
Before any writes, the CLI builds a restore plan by scanning the backup directory and mapping each file to its target location:
| Status | Meaning | Behavior |
|---|---|---|
new | File exists in backup but not on machine | Write directly |
same | Backup content matches machine content (via Bun.hash()) | Skip silently |
conflict | Both exist but differ | Prompt user |
redacted | Backup file contains [REDACTED] | Skip with message |
Conflict Prompt
When a file exists on both sides with different content:
| Key | Action |
|---|---|
o | Overwrite this file |
s | Skip this file |
d | Show inline diff, then ask again |
a | Overwrite all remaining conflicts |
l | Skip all remaining conflicts |
Pre-Restore Snapshot
Before overwriting any conflicting files, the CLI automatically saves the current versions of those files to a pre-restore-YYYYMMDDHHMMSS/ directory. This snapshot uses the same backup format — you can restore from it with dotfiles restore.
.local Override Pattern
If a backup contains shell/.zshrc.local, it restores to ~/.zshrc.local — the .local suffix maps to the same target directory as the base file. This supports the common pattern of having machine-specific overrides alongside shared configs.
Category Picker (--pick)
Shows a checkbox list of available categories with file counts. Select which categories to restore:
$ dotfiles restore ./backup --pick
? Select categories to restore:
[x] ai (7 files)
[ ] shell (1 file)
[x] git (3 files)
[ ] editor (2 files)Example Output
$ dotfiles restore ./backup --dry-run
Dry run — no files will be changed:
[NEW] editor/zed/settings.json → ~/.config/zed/settings.json
[CONFLICT] shell/.zshrc → ~/.zshrc
[SAME] git/.gitconfig → ~/.gitconfig
[REDACTED] ssh/config → ~/.ssh/config
4 files total: 1 new, 1 conflicts, 1 unchanged, 1 redacted (skipped)diff
Compare the backup state against the current live system. Answers: "what changed since last backup?"
dotfiles diff [path] [--section <name>]Arguments & Flags
| Argument/Flag | Type | Default | Description |
|---|---|---|---|
[path] | string | auto-detect | Backup directory path. If omitted, finds the latest backup-* directory |
--section <name> | string | all | Filter to a specific category (e.g., ai, shell, git) |
Behavior
- Builds a restore plan (same as
restore) to determine file statuses - Groups entries by category
- Color-codes output (TTY-aware — no colors in pipes):
- yellow: modified (content differs)
- green: unchanged
- blue: new (in backup, missing on machine)
- gray: redacted
- Uses
Bun.color(name, "ansi-256")for terminal colors
Auto-Find Latest Backup
If no path is given, diff searches the resolved output directory for directories matching backup-*, sorted by name (descending), and uses the first match.
Example Output
$ dotfiles diff
Comparing backup against live system:
ai/
ai/claude/settings.json — modified
ai/claude/CLAUDE.md — unchanged
ai/cursor/mcp.json — unchanged
shell/
shell/.zshrc — modified
git/
git/.gitconfig — unchanged
5 files: 2 modified, 3 unchanged$ dotfiles diff --section ai
Comparing backup against live system:
ai/
ai/claude/settings.json — modified
ai/claude/CLAUDE.md — unchanged
2 files: 1 modified, 1 unchangedScope Note
diff compares only files represented in the backup. It does not discover new files that exist on the machine but were not captured in the backup. For full discovery, run collect and use compare.
status
Quick summary of backup state — like git status for your configs.
dotfiles statusBehavior
- Finds the latest backup directory (same logic as
diff) - Calculates backup age from directory modification time
- Builds restore plan to count statuses
- Prints summary
Age Display
| Age | Display |
|---|---|
| < 60 minutes | Nm ago |
| < 24 hours | Nh ago |
| >= 24 hours | Nd ago |
Example Output
$ dotfiles status
Last backup: 2h ago (backup-MacBook-Pro-20260407120000)
15 files tracked: 2 modified, 13 unchanged
Modified since backup:
shell/.zshrc
ai/claude/settings.json$ dotfiles status
Last backup: 5m ago (backup-MacBook-Pro-20260407143200)
15 files tracked: 0 modified, 15 unchanged
Everything up to date.compare
Structured diff between two .dotf report files. Powered by @dotformat/core's compare() and formatDiff().
dotfiles compare [file1] [file2]Arguments
| Argument | Required | Description |
|---|---|---|
file1 | no | Path to first .dotf file |
file2 | no | Path to second .dotf file |
If both are omitted, compare finds the two newest .dotf files in <cwd>/reports/ (sorted by modification time).
Behavior
- Parses both files via
@dotformat/core'sparse() - Computes structured diff via
compare() - Formats with
formatDiff()with color enabled - Labels are derived from filenames (without
.dotfextension)
Example
$ dotfiles compare reports/MacBook-20260401.dotf reports/MacBook-20260407.dotfWARNING
compare only looks in <cwd>/reports/ when auto-detecting files. It does not search ~/Downloads or other directories. Provide explicit paths if your reports are elsewhere.
list
Print a section from the most recent .dotf report. Supports fuzzy matching on section names.
dotfiles list <section>Arguments
| Argument | Required | Description |
|---|---|---|
section | yes | Section name or partial match |
Fuzzy Matching
The query is matched against section names using two strategies:
- Substring match:
brewmatchesapps.brew.formulaeandapps.brew.casks - Dot-segment match:
claudematchesai.claude.settings,ai.claude.skills,ai.claude.md
All matching sections are printed in .dotf format.
Available Sections
These are the section IDs used in .dotf reports:
| Section | Source |
|---|---|
meta | Hostname, OS, date |
ai.claude.settings | Claude permissions, plugins |
ai.claude.skills | Claude skills directory listing |
ai.claude.md | CLAUDE.md content |
ai.cursor.mcp | Cursor MCP config |
ai.cursor.skills | Cursor skills listing |
ai.gemini.settings | Gemini preferences |
ai.gemini.skills | Gemini skills listing |
ai.gemini.md | GEMINI.md content |
ai.windsurf.mcp | Windsurf MCP config |
ai.windsurf.skills | Windsurf skills listing |
ai.ollama.models | Ollama model list (name, size, modified) |
shell.zshrc | .zshrc content |
git.config | .gitconfig content |
git.ignore | .gitignore_global content |
gh.config | GitHub CLI config |
editor.zed | Zed settings |
editor.cursor | Cursor editor settings |
editor.nvim | Neovim init.lua |
editor.vimrc | .vimrc |
terminal.p10k | P10k metadata (exists, line count) |
terminal.tmux | tmux.conf content |
ssh.hosts | SSH hosts table (host, hostname, identity) |
ssh.config | SSH config content |
npm.config | .npmrc content |
bun.config | .bunfig.toml content |
apps.raycast | Raycast installed status |
apps.alttab | AltTab installed + prefs |
apps.macos | macOS /Applications listing |
apps.brew.formulae | Homebrew formulae list |
apps.brew.casks | Homebrew casks list |
Example
$ dotfiles list brew
[apps.brew.formulae]
bat
eza
fd
fzf
...
[apps.brew.casks]
alt-tab
firefox
raycast
...$ dotfiles list claude
[ai.claude.settings]
enabledPlugins = ...
permissions = ...
[ai.claude.skills]
superskill.md
web-dev.mdIf no sections match:
$ dotfiles list foo
No sections matching "foo".
Available sections: meta, ai.claude.settings, ai.claude.skills, ...Output Path Logic
All commands that write files share the same output directory resolution:
| Priority | Condition | Output |
|---|---|---|
| 1 | -o <path> flag provided | Explicit path |
| 2 | .git/HEAD exists in current working directory | <cwd>/reports/ |
| 3 | Otherwise (global/standalone run) | ~/Downloads |
This means:
- Inside a cloned repo: reports and backups go to
reports/— ideal for committing to git - Running globally (via
bunx): outputs go to~/Downloads— easy to find and share - Explicit
-o: always wins