@phibar/dotsloth
v0.4.0
Published
Sync your dots, be a sloth - Dev environment sync CLI for macOS
Maintainers
Readme
dotsloth
Sync your dots, be a sloth. A macOS CLI that keeps your development environment — git identities, dotfiles, secrets, env files and Claude Code config — in sync across machines via iCloud Drive and the macOS Keychain.
What it does
- Git identities per organization. Generates
~/.gitconfigwithincludeIfrules so repos under~/github/<org>/automatically use the right name, email and signing key. Every command that changes config re-applies it immediately. - Dotfiles via iCloud.
~/.gitconfig,~/.zprofileand~/.ssh/configare symlinked to versions stored in iCloud Drive. - Secrets in the Keychain. Sensitive exports are pulled out of shell profiles and stored encrypted, never in a synced plaintext file.
- Env files.
.envand friends are gitignored by design, so git never saves them.dotsloth envbacks them up and restores them after a reinstall. - Claude Code. Share
settings.json, project memory and conversation history between machines. - Mail. Back up Mail rules and signatures and recreate them on a new machine, with a checklist for the accounts that need a manual sign-in.
- Pre-reinstall safety.
dotsloth doctorrefuses to say "safe to wipe" while anything would be lost. - Periodic sync. A launchd agent keeps everything current.
- Web UI.
dotsloth uiopens a local web interface for all of the above.
Quick start
npm install -g @phibar/dotsloth
dotsloth init
dotsloth org add phibar --email [email protected] --username phibar
dotsloth clone [email protected]:phibar/some-repo.gitWeb UI
dotsloth uiStarts a local server, prints a sign-in link and opens it in your browser. Everything the CLI does is there: a dashboard with sync, organizations and cloning, a config editor, secrets, env files, doctor, Mail, Claude Code and the periodic sync. Press Ctrl+C to stop it.


| Flag | |
|---|---|
| --port <n> | Listen on a fixed port (default: a free one) |
| --no-open | Print the link without opening the browser |
Security model. The server can reach your Keychain, config and every repository, so it only answers the browser tab it opened:
- It listens on
127.0.0.1only. - The printed link carries a random token. Opening it swaps the token for an
HttpOnly, SameSite=Strict session cookie and removes it from the address
bar; every other request needs that cookie. The link stays valid while this
dotsloth uiruns (so you can sign in again after closing the browser) - treat it like a password, and stop the server when you are done. - Requests with a foreign
Host(DNS rebinding) or, for anything that changes state, a foreignOrigin(CSRF) are refused. - Secret values are fetched one at a time when you click Reveal or Copy; there is no way to read several at once. Env file contents are never sent.
- Deleting an organization's repositories requires typing its name, and the server checks it again.
- Mail is only contacted when you ask: it is driven via AppleScript, and macOS asks once for permission to control it.
Reinstalling your Mac
See BOOTSTRAP.md for the full ordered procedure. The short version — before you wipe:
dotsloth doctor # exits non-zero while anything would be lost
dotsloth env push
dotsloth claude memory push
brctl log --wait --shorten # a file in iCloud is not a backup until uploadedAnd on the new machine: Homebrew → node → iCloud sign-in → dotsloth → SSH key →
dotsloth sync → clone → dotsloth env pull.
Where things live
| What | Where |
|---|---|
| Config | ~/Library/Mobile Documents/com~apple~CloudDocs/development/dotsloth/config.json |
| Dotfiles | .../development/dotsloth/dotfiles/ |
| Org gitconfigs | .../development/dotsloth/organizations/ |
| Env files | .../development/dotsloth/envs/<org>/<repo>/ |
| Claude Code | .../development/dotsloth/claude/ |
| Secrets | macOS Keychain, account dotsloth |
| Repos | ~/github/<org>/<repo> |
Development
npm install
npm run build # CLI (tsc) and web app (vite) into dist/
npm test # mocha + vitest + biome
./bin/run.js <command>Working on the web app with hot reload:
npm run dev:uiIt starts dotsloth ui from source, then Vite with the server's sign-in link
(so /api is proxied with the session cookie), and opens
http://localhost:5173. Ctrl+C stops both. npm run dev:web starts only Vite;
it reads the link from DOTSLOTH_UI_URL.
The code is layered so the CLI and the web UI share one implementation:
src/core holds the logic (typed results, no prompts or printing),
src/commands and src/cli are the terminal front end, src/server is the
web API, and web/ is the React app.
Usage
$ npm install -g @phibar/dotsloth
$ dotsloth COMMAND
running command...
$ dotsloth (--version)
@phibar/dotsloth/0.4.0 linux-x64 node-v24.21.0
$ dotsloth --help [COMMAND]
USAGE
$ dotsloth COMMAND
...Commands
dotsloth claude history [DIRECTION]dotsloth claude linkdotsloth claude memory [DIRECTION]dotsloth claude statusdotsloth clone URLdotsloth daemon installdotsloth daemon statusdotsloth daemon uninstalldotsloth doctordotsloth env listdotsloth env pulldotsloth env pushdotsloth help [COMMAND]dotsloth initdotsloth mail exportdotsloth mail restoredotsloth mail statusdotsloth org add [NAME]dotsloth org listdotsloth org remove NAMEdotsloth org update [NAME]dotsloth secret add NAMEdotsloth secret exportdotsloth secret get NAMEdotsloth secret listdotsloth secret loaddotsloth secret remove NAMEdotsloth statusdotsloth syncdotsloth ui
dotsloth claude history [DIRECTION]
Share Claude Code conversation history between machines
USAGE
$ dotsloth claude history [DIRECTION] [--dry-run] [--retention <value>]
ARGUMENTS
[DIRECTION] (status|push|pull) [default: status] status | push | pull
FLAGS
--dry-run Show what would change without writing
--retention=<value> [default: 90] Only sync sessions touched within this many days
DESCRIPTION
Share Claude Code conversation history between machines
EXAMPLES
$ dotsloth claude history
$ dotsloth claude history push
$ dotsloth claude history pull --retention 30See code: src/commands/claude/history.ts
dotsloth claude link
Share Claude Code settings between machines via the dotsloth store
USAGE
$ dotsloth claude link [--dry-run]
FLAGS
--dry-run Show what would change without writing
DESCRIPTION
Share Claude Code settings between machines via the dotsloth store
EXAMPLES
$ dotsloth claude link
$ dotsloth claude link --dry-runSee code: src/commands/claude/link.ts
dotsloth claude memory [DIRECTION]
Share Claude Code project memory between machines
USAGE
$ dotsloth claude memory [DIRECTION] [--dry-run]
ARGUMENTS
[DIRECTION] (status|push|pull) [default: status] status | push | pull
FLAGS
--dry-run Show what would change without writing
DESCRIPTION
Share Claude Code project memory between machines
EXAMPLES
$ dotsloth claude memory
$ dotsloth claude memory push
$ dotsloth claude memory pullSee code: src/commands/claude/memory.ts
dotsloth claude status
Show which Claude Code config is shared between machines
USAGE
$ dotsloth claude status
DESCRIPTION
Show which Claude Code config is shared between machines
EXAMPLES
$ dotsloth claude statusSee code: src/commands/claude/status.ts
dotsloth clone URL
Clone a repository to the correct organization folder
USAGE
$ dotsloth clone URL [-o <value>]
ARGUMENTS
URL Repository URL to clone
FLAGS
-o, --org=<value> Override organization (use a different org than detected)
DESCRIPTION
Clone a repository to the correct organization folder
EXAMPLES
$ dotsloth clone [email protected]:phibar/some-repo.git
$ dotsloth clone https://github.com/ipfs/kubo
$ dotsloth clone [email protected]:fork/repo.git --org phibarSee code: src/commands/clone.ts
dotsloth daemon install
Install the periodic sync agent (runs dotsloth sync on a schedule)
USAGE
$ dotsloth daemon install [--interval <value>]
FLAGS
--interval=<value> [default: 86400] Seconds between syncs (default: once a day)
DESCRIPTION
Install the periodic sync agent (runs dotsloth sync on a schedule)
EXAMPLES
$ dotsloth daemon install
$ dotsloth daemon install --interval 3600See code: src/commands/daemon/install.ts
dotsloth daemon status
Show whether periodic sync is installed and when it last ran
USAGE
$ dotsloth daemon status
DESCRIPTION
Show whether periodic sync is installed and when it last ran
EXAMPLES
$ dotsloth daemon statusSee code: src/commands/daemon/status.ts
dotsloth daemon uninstall
Remove the periodic sync agent
USAGE
$ dotsloth daemon uninstall
DESCRIPTION
Remove the periodic sync agent
EXAMPLES
$ dotsloth daemon uninstallSee code: src/commands/daemon/uninstall.ts
dotsloth doctor
Check whether anything would be lost if this machine were wiped
USAGE
$ dotsloth doctor [--offline]
FLAGS
--offline Skip GitHub PR lookups (faster, less precise)
DESCRIPTION
Check whether anything would be lost if this machine were wiped
EXAMPLES
$ dotsloth doctor
$ dotsloth doctor --offlineSee code: src/commands/doctor.ts
dotsloth env list
List env files found in your repos and whether they are backed up
USAGE
$ dotsloth env list [--unsaved]
FLAGS
--unsaved Only show files that are not yet backed up
DESCRIPTION
List env files found in your repos and whether they are backed up
EXAMPLES
$ dotsloth env list
$ dotsloth env list --unsavedSee code: src/commands/env/list.ts
dotsloth env pull
Restore env files from the dotsloth store into your repos
USAGE
$ dotsloth env pull [--dry-run] [-f]
FLAGS
-f, --force Overwrite local files that differ
--dry-run Show what would be restored without writing
DESCRIPTION
Restore env files from the dotsloth store into your repos
EXAMPLES
$ dotsloth env pull
$ dotsloth env pull --forceSee code: src/commands/env/pull.ts
dotsloth env push
Copy env files from your repos into the dotsloth store
USAGE
$ dotsloth env push [--dry-run] [-f]
FLAGS
-f, --force Overwrite store entries that differ
--dry-run Show what would be copied without writing
DESCRIPTION
Copy env files from your repos into the dotsloth store
EXAMPLES
$ dotsloth env push
$ dotsloth env push --dry-runSee code: src/commands/env/push.ts
dotsloth help [COMMAND]
Display help for dotsloth.
USAGE
$ dotsloth help [COMMAND...] [-n]
ARGUMENTS
[COMMAND...] Command to show help for.
FLAGS
-n, --nested-commands Include all nested commands in the output.
DESCRIPTION
Display help for dotsloth.See code: @oclif/plugin-help
dotsloth init
Initialize dotsloth on this machine
USAGE
$ dotsloth init [-f] [--skip-secrets] [--skip-ssh]
FLAGS
-f, --force Overwrite existing configuration
--skip-secrets Skip secrets extraction from zprofile
--skip-ssh Skip SSH keychain setup
DESCRIPTION
Initialize dotsloth on this machine
EXAMPLES
$ dotsloth initSee code: src/commands/init.ts
dotsloth mail export
Export Mail accounts, rules and signatures to the dotsloth store
USAGE
$ dotsloth mail export [--dry-run]
FLAGS
--dry-run Show what would be exported without writing
DESCRIPTION
Export Mail accounts, rules and signatures to the dotsloth store
EXAMPLES
$ dotsloth mail export
$ dotsloth mail export --dry-runSee code: src/commands/mail/export.ts
dotsloth mail restore
Recreate Mail signatures and rules, and list the accounts to re-add
USAGE
$ dotsloth mail restore [--accounts-only] [--dry-run]
FLAGS
--accounts-only Only print the account checklist
--dry-run Show what would be recreated without changing Mail
DESCRIPTION
Recreate Mail signatures and rules, and list the accounts to re-add
EXAMPLES
$ dotsloth mail restore
$ dotsloth mail restore --dry-runSee code: src/commands/mail/restore.ts
dotsloth mail status
Compare Mail on this machine with what is in the dotsloth store
USAGE
$ dotsloth mail status
DESCRIPTION
Compare Mail on this machine with what is in the dotsloth store
EXAMPLES
$ dotsloth mail statusSee code: src/commands/mail/status.ts
dotsloth org add [NAME]
Add a new organization configuration
USAGE
$ dotsloth org add [NAME] [-e <value>] [-u <value>]
ARGUMENTS
[NAME] Organization name (e.g., phibar)
FLAGS
-e, --email=<value> Git email for this organization
-u, --username=<value> Git username for this organization
DESCRIPTION
Add a new organization configuration
EXAMPLES
$ dotsloth org add
$ dotsloth org add phibar --email [email protected] --username phibarSee code: src/commands/org/add.ts
dotsloth org list
List configured organizations
USAGE
$ dotsloth org list [--json]
FLAGS
--json Output as JSON
DESCRIPTION
List configured organizations
EXAMPLES
$ dotsloth org listSee code: src/commands/org/list.ts
dotsloth org remove NAME
Remove an organization configuration
USAGE
$ dotsloth org remove NAME [--delete-repos] [-f]
ARGUMENTS
NAME Organization name to remove
FLAGS
-f, --force Skip confirmation
--delete-repos Also delete the repository folder
DESCRIPTION
Remove an organization configuration
EXAMPLES
$ dotsloth org remove phibarSee code: src/commands/org/remove.ts
dotsloth org update [NAME]
Update an organization configuration
USAGE
$ dotsloth org update [NAME] [-e <value>] [-u <value>]
ARGUMENTS
[NAME] Organization name to update
FLAGS
-e, --email=<value> New git email
-u, --username=<value> New git username
DESCRIPTION
Update an organization configuration
EXAMPLES
$ dotsloth org update
$ dotsloth org update phibar
$ dotsloth org update phibar --email [email protected]See code: src/commands/org/update.ts
dotsloth secret add NAME
Add or update a secret in macOS Keychain
USAGE
$ dotsloth secret add NAME [-v <value>]
ARGUMENTS
NAME Secret name (e.g., AWS_ACCESS_KEY_ID)
FLAGS
-v, --value=<value> Secret value (not recommended - use prompt instead)
DESCRIPTION
Add or update a secret in macOS Keychain
EXAMPLES
$ dotsloth secret add AWS_ACCESS_KEY_ID
$ dotsloth secret add OPENAI_API_KEY --value sk-...See code: src/commands/secret/add.ts
dotsloth secret export
Export all secrets (for debugging/migration)
USAGE
$ dotsloth secret export [-f shell|json]
FLAGS
-f, --format=<option> [default: shell] Output format
<options: shell|json>
DESCRIPTION
Export all secrets (for debugging/migration)
EXAMPLES
$ dotsloth secret exportSee code: src/commands/secret/export.ts
dotsloth secret get NAME
Get a secret value from Keychain
USAGE
$ dotsloth secret get NAME
ARGUMENTS
NAME Secret name to retrieve
DESCRIPTION
Get a secret value from Keychain
EXAMPLES
$ dotsloth secret get AWS_ACCESS_KEY_IDSee code: src/commands/secret/get.ts
dotsloth secret list
List all secret names stored in Keychain
USAGE
$ dotsloth secret list
DESCRIPTION
List all secret names stored in Keychain
EXAMPLES
$ dotsloth secret listSee code: src/commands/secret/list.ts
dotsloth secret load
Output export statements for all secrets (for use in shell profile)
USAGE
$ dotsloth secret load
DESCRIPTION
Output export statements for all secrets (for use in shell profile)
EXAMPLES
$ dotsloth secret load
eval "$(dotsloth secret load)"See code: src/commands/secret/load.ts
dotsloth secret remove NAME
Remove a secret from Keychain
USAGE
$ dotsloth secret remove NAME [-f]
ARGUMENTS
NAME Secret name to remove
FLAGS
-f, --force Skip confirmation
DESCRIPTION
Remove a secret from Keychain
EXAMPLES
$ dotsloth secret remove AWS_ACCESS_KEY_IDSee code: src/commands/secret/remove.ts
dotsloth status
Show current dotsloth configuration status
USAGE
$ dotsloth status [-v]
FLAGS
-v, --verbose Show detailed status
DESCRIPTION
Show current dotsloth configuration status
EXAMPLES
$ dotsloth statusSee code: src/commands/status.ts
dotsloth sync
Sync configurations from iCloud Drive
USAGE
$ dotsloth sync [--dry-run] [-f]
FLAGS
-f, --force Force overwrite local files
--dry-run Show what would be synced without making changes
DESCRIPTION
Sync configurations from iCloud Drive
EXAMPLES
$ dotsloth sync
$ dotsloth sync --dry-runSee code: src/commands/sync.ts
dotsloth ui
Manage dotsloth in the browser (local web interface)
USAGE
$ dotsloth ui [--open] [--port <value>]
FLAGS
--[no-]open Open the browser
--port=<value> Port to listen on (default: a free one)
DESCRIPTION
Manage dotsloth in the browser (local web interface)
EXAMPLES
$ dotsloth ui
$ dotsloth ui --port 4321 --no-openSee code: src/commands/ui.ts
