Tether CLI Documentation
Tether is an open source tool that automatically syncs your development environment—dotfiles and packages—across multiple machines with end-to-end encryption.
Key Features
- Automatic Sync — Background daemon watches for changes and syncs in real-time
- End-to-End Encryption — AES-256-GCM encryption with passphrase-protected keys
- Project Configs — Sync .env files and IDE settings across machines by Git remote
- Package Manager Support — Syncs Homebrew, npm, pnpm, bun, and gem packages
- Secret Detection — Prevents accidental syncing of API keys and credentials
- Git-Backed — Full version history and rollback capabilities
- Team Secrets — Share encrypted secrets across teams with age encryption
Installation
Homebrew (recommended)
brew tap paddo-tech/tap brew install tether-cli
Requirements
- Platform: macOS (Linux coming soon)
- Shell: zsh or bash
- Git: Configured with GitHub, GitLab, or self-hosted Git server
Quick Start
Get up and running in under a minute:
1. Initialize
tether init
Follow the prompts to configure your Git repository and encryption passphrase.
2. Verify Setup
tether status
You should see the daemon running and your synced files listed.
3. On Another Machine
brew tap paddo-tech/tap && brew install tether-cli tether init --repo git@github.com:username/dotfiles.git
Enter the same passphrase. Your configs sync automatically.
How It Works
Architecture
Tether uses a Git repository as the sync backend. When you make changes to dotfiles or install packages, the background daemon detects these changes, encrypts your dotfiles with AES-256-GCM, and pushes them to your private Git repository.
On your other machines, Tether pulls these changes and applies them automatically.
Sync Flow
- Daemon detects file changes (every 5 minutes or manual
tether sync) - Secret scanner checks for credentials
- Files are encrypted (if enabled)
- Changes committed and pushed to Git
- Other machines pull and decrypt
Data Layout
Local Tether data lives in ~/.tether/:
~/.tether/ ├── config.toml # Configuration ├── state.json # Sync state (hashes, timestamps) ├── sync/ # Git repository clone ├── daemon.pid # Daemon process ID ├── daemon.log # Daemon logs └── ignore # Secret scanning patterns
The sync repository structure:
sync/ ├── dotfiles/ # Encrypted dotfiles ├── configs/ # Config directories ├── projects/ # Project-local configs ├── manifests/ # Package manifests └── machines/ # Per-machine state
Commands
tether init
Initialize Tether on this machine.
tether init [--repo <URL>] [--no-daemon]
--repo <URL>— Git repository URL (interactive if omitted)--no-daemon— Don't start daemon after init
tether sync
Manually trigger a sync.
tether sync [--dry-run] [--force]
--dry-run— Show what would be synced--force— Skip conflict prompts
tether status
Show current sync status, daemon state, and synced files.
tether status
tether diff
Show differences between machines.
tether diff [--machine <NAME>]
--machine <NAME>— Compare with specific machine
tether daemon
Control the background daemon.
tether daemon <start|stop|restart|logs|install|uninstall>
start— Start the daemonstop— Stop the daemonrestart— Restart the daemonlogs— View last 50 log linesinstall— Install launchd service (auto-start on login)uninstall— Remove launchd service
tether machines
Manage machines in sync network.
tether machines <list|rename|remove>
list— List all machinesrename <OLD> <NEW>— Rename a machineremove <NAME>— Remove a machine
tether ignore
Manage ignore patterns and per-machine exclusions.
# Secret scanning patterns tether ignore add <PATTERN> tether ignore list tether ignore remove <PATTERN> # Per-machine file exclusions tether ignore dotfile <FILE> tether ignore project <PROJECT> <PATH> tether ignore sync-list tether ignore sync-remove <FILE>
tether config
Manage configuration.
tether config get <KEY> tether config set <KEY> <VALUE> tether config edit tether config dotfiles
get <KEY>— Get config value (supports nested keys likepackages.brew.enabled)set <KEY> <VALUE>— Set config valueedit— Open config in editordotfiles— Interactive UI for managing synced files
tether team
Manage team sync repositories, secrets, and project sharing.
# Team management tether team setup # Interactive wizard tether team add <URL> # Add team repo tether team list # List teams tether team status # Show status tether team enable/disable # Toggle sync # Organization mapping tether team orgs add <ORG> # Map GitHub org to team tether team orgs list # List mapped orgs tether team orgs remove <ORG> # Unmap org # Team secrets (age encryption) tether team secrets add-recipient <KEY> # Add member's public key tether team secrets list-recipients # List recipients tether team secrets set <NAME> # Set a secret tether team secrets get <NAME> # Get a secret tether team secrets list # List all secrets # Project secrets tether team projects add <FILE> # Share project secret tether team projects list # List shared secrets tether team projects migrate # Auto-migrate to team tether team projects purge-personal # Remove personal copies
tether resolve
Resolve file conflicts.
tether resolve [FILE]
Interactive resolution: keep local, use remote, merge, or skip.
tether unlock
Unlock encryption key with passphrase.
tether unlock
tether lock
Clear cached encryption key.
tether lock
tether upgrade
Upgrade all installed packages across enabled package managers.
tether upgrade
Configuration
Configuration lives at ~/.tether/config.toml:
[backend] url = "git@github.com:username/dotfiles.git" [security] encrypt_dotfiles = true scan_secrets = true [dotfiles] files = [".zshrc", ".gitconfig", ".vimrc"] dirs = [".config/nvim", ".ssh/config"] [project_configs] enabled = true search_paths = ["~/Projects", "~/Work"] patterns = [".env.local", ".claude/settings.json"] [packages.brew] enabled = true [packages.npm] enabled = true [packages.pnpm] enabled = false [packages.bun] enabled = false [packages.gem] enabled = false
Key Configuration Options
| Key | Description |
|---|---|
| backend.url | Git repository URL |
| security.encrypt_dotfiles | Enable AES-256-GCM encryption |
| security.scan_secrets | Enable secret detection before sync |
| dotfiles.files | List of individual dotfiles to sync |
| dotfiles.dirs | List of directories to sync |
| project_configs.enabled | Sync project-local configs |
| packages.*.enabled | Enable specific package manager sync |
Package Managers
Tether syncs global packages across machines. Enable managers in config:
tether config set packages.brew.enabled true tether config set packages.npm.enabled true
Homebrew
Syncs formulae, casks, and taps via Brewfile format. Casks include desktop apps like VS Code, Slack, Microsoft Office, and most macOS applications installed via brew install --cask.
# Manifest: manifests/Brewfile tap "homebrew/cask" brew "ripgrep" brew "fd" cask "visual-studio-code" cask "slack" cask "1password"
npm
Syncs global npm packages.
# Manifest: manifests/npm.txt typescript eslint prettier
pnpm / bun / gem
Same format as npm—one package per line.
Encryption & Security
How Encryption Works
- On first init, a random 256-bit key is generated
- Key is encrypted with your passphrase using
age - Encrypted key is stored in the Git repo (safe to share)
- Dotfiles are encrypted with AES-256-GCM before commit
- On other machines, enter passphrase to decrypt the key
Secret Detection
Before syncing, Tether scans for common secret patterns:
- AWS credentials (
AKIA...) - GitHub tokens (
ghp_...,gho_...) - Generic patterns (
API_KEY=,password=)
Add ignore patterns for false positives:
tether ignore add 'EXAMPLE_KEY='
Unlocking
If the daemon can't decrypt (key not cached), run:
tether unlock
To clear the cached key:
tether lock
Team Sync
Share configurations and encrypted secrets across a team.
Setup
# Interactive setup wizard tether team setup # Or add manually tether team add git@github.com:org/team-dotfiles.git
This clones the team repo and creates symlinks to team config files.
How It Works
- Team configs are symlinked to their target locations
- If you have write access, your changes sync back
- Read-only access means you receive updates only
- Auto-inject adds
sourcelines to your personal dotfiles (optional)
Team Secrets
Share encrypted secrets using age public-key cryptography:
# Add your public key as a recipient tether team secrets add-recipient age1... # Set a secret (encrypted for all recipients) tether team secrets set API_KEY # Get a secret tether team secrets get API_KEY
Organization Mapping
Map GitHub organizations to teams for automatic project secret sharing:
# Map org to team tether team orgs add github.com/acme-corp # Project secrets from that org are now shared with the team tether team projects list
Project Secrets
Share .env files and project configs across team members:
# Share a project secret tether team projects add .env # Auto-migrate secrets from mapped orgs tether team projects migrate # Remove personal copies (use team version) tether team projects purge-personal
Managing Teams
tether team list # Show all teams tether team switch other # Switch active team tether team disable # Disable without removing tether team remove # Remove team completely
Troubleshooting
Daemon not running
tether daemon start tether daemon logs
Sync conflicts
tether resolve
Or force one direction:
tether sync --force
Encryption issues
# Unlock with passphrase tether unlock # Check if key exists ls ~/.tether/sync/.tether-key.age
"Secret detected" blocking sync
Either remove the secret or add an ignore pattern:
tether ignore add 'MY_SAFE_PATTERN='
Reset everything
tether daemon stop rm -rf ~/.tether tether init
View full logs
cat ~/.tether/daemon.log