Encrypt and sync your Kilo CLI 1.x profiles across machines through a private git repository.
kilo-sync packs your Kilo configuration files into a compressed tarball, encrypts it
with a passphrase (AES-256-GCM or age),
commits the ciphertext to a private git repo, and lets you pull and apply it on any
other machine β with a full backup of whatever it overwrites.
- βοΈ How it works
- π What gets synced
- π₯ Install
- π οΈ Configuration
- π Typical workflow
- π Command reference
- π Passphrase
- π‘οΈ Encryption
- π» Development
- π Docs
graph LR
subgraph Machine A
A[~/.config/kilo/kilo.jsonc] -->|kilo-sync export| C((Encrypted .tar.bin))
B[~/.config/kilo/rules/] -->|kilo-sync export| C
end
subgraph Private Git Repo
C --> D[Commit & Push]
end
subgraph Machine B
D -->|kilo-sync import --pull| E((Review Staging))
E -->|kilo-sync apply --latest| F[Restores all files]
end
π€ Export collects your Kilo files β tars them β encrypts with your passphrase β
writes profiles/<name>.tar.kilosync.bin + profiles/<name>.meta.json to your
local clone β optionally commits and pushes.
π₯ Import pulls (optionally) β decrypts β extracts into
~/.kilo/import-staging/<timestamp>/ for your review.
β
Apply copies files from staging to their live locations, backing up any existing
files first under ~/.kilo/backups/<timestamp>/.
The following files are collected automatically (paths are relative to $HOME):
| Archive path | Live location | Description |
|---|---|---|
π config/kilo/kilo.jsonc |
~/.config/kilo/kilo.jsonc |
Main Kilo config |
π config/kilo/opencode.json |
~/.config/kilo/opencode.json |
OpenCode config |
π config/kilo/opencode.jsonc |
~/.config/kilo/opencode.jsonc |
OpenCode config (JSONC) |
π config/kilo/rules/** |
~/.config/kilo/rules/ |
All rule files |
π config/kilo/skills/** |
~/.config/kilo/skills/ |
All skill files |
π share/kilo/auth.json |
~/.local/share/kilo/auth.json |
Auth tokens |
π kilo-home/instructions.md |
~/.kilo/instructions.md |
Global instructions |
βοΈ kilo-home/.env |
~/.kilo/.env |
Environment overrides |
β include/** |
~/... |
Any extras from paths.include |
Note
node_modules/ is always skipped. auth.json is optional β a warning is printed
if it is missing, but the export still succeeds.
Additional files can be added via paths.include globs; files can be removed from
the bundle with paths.exclude patterns (see Configuration).
The installer creates a self-contained Python venv at
~/.local/share/kilo-sync-venv and symlinks the kilo-sync binary into
~/.local/bin. This approach works on Ubuntu 24.04, Debian 12, and macOS without
requiring sudo, pip, or pipx.
On a fresh Ubuntu or Debian server, python3-venv may not be present:
sudo apt-get install -y python3-venv
# Or use the bundled helper:
sudo bash scripts/ensure-python.shTip
Skip this step on macOS or any machine where python3 -m venv already works.
π Public repo β tracks latest main branch:
curl -fsSL "https://raw.githubusercontent.com/cptnfren/kilo-sync/main/scripts/install.sh" | bashπ Private repo β via SSH (no wheel required):
KILO_SYNC_SOURCE=ssh bash <(curl -fsSL "https://raw.githubusercontent.com/cptnfren/kilo-sync/main/scripts/install.sh")π Upgrade in place β re-run the same curl | bash command. The venv is updated
and the symlink is refreshed.
ποΈ Uninstall:
rm -rf ~/.local/share/kilo-sync-venv
rm ~/.local/bin/kilo-syncSee docs/PUBLISHING.md for Ansible, cloud-init, and full install reference.
Copy the example config and edit it:
cp kilo-sync.yaml.example ~/.kilo/kilo-sync.yamlImportant
Two separate repos: kilo-sync uses two distinct git repositories:
- π§° Tool repo (
cptnfren/kilo-sync, public) β this repo, containing the code andinstall.sh. - ποΈ Data repo (your own private repo, e.g.
YOUR_USERNAME/kilo-sync-data) β where your encrypted profile blobs are stored. You create this repo yourself; it can be named anything.
π― Minimal config (~/.kilo/kilo-sync.yaml):
local_clone: ~/repos/kilo-sync-data # path to YOUR private data repo
profile: default # profile name (letters, digits, . _ -)π§ Full config with all options:
# Path to YOUR private data repo (not the kilo-sync tool repo)
local_clone: ~/repos/kilo-sync-data
profile: default
# Optional: informational only, records where your data repo lives
github_repo: YOUR_USERNAME/kilo-sync-data
# Encryption backend: omit for auto (age if on PATH, else python AES-GCM)
# encryption: python # force built-in AES-256-GCM
# encryption: age # force age (must be on PATH)
# How many import-staging directories to keep (default: 3)
keep_staging: 3
# Maximum tarball size before warning (default: 50 MB)
max_tar_bytes: 52428800
# Include the kilo-sync source tree itself in the bundle (default: false)
include_kilo_sync: false
# Extra files/directories to include (globs relative to $HOME)
paths:
include:
- .gitconfig
- .ssh/config
- .config/starship.toml
exclude:
- "include/.ssh/id_*" # never sync private keys
# Override default Kilo data locations (rarely needed)
# kilo_config_dir: /custom/path/.config/kilo
# kilo_data_dir: /custom/path/.local/share/kilo
# kilo_home_dir: /custom/path/.kiloNote
The local_clone path is expanded (supports ~) and resolved. It must be an
existing directory with a .git subdirectory.
# 1. Create a private git repo on GitHub for your encrypted data (NOT this tool repo)
# e.g. github.com/YOUR_USERNAME/kilo-sync-data (private, can be any name)
git clone [email protected]:YOUR_USERNAME/kilo-sync-data.git ~/repos/kilo-sync-data
# 2. Write your config
cat > ~/.kilo/kilo-sync.yaml <<'EOF'
local_clone: ~/repos/kilo-sync-data
profile: default
EOF
# 3. Export and publish in one step
kilo-sync export --publish
# β prompts for a passphrase
# β writes profiles/default.tar.kilosync.bin + profiles/default.meta.json
# β commits and pushes to origin# 1. Clone your private data repo (the same one used on machine A)
git clone [email protected]:YOUR_USERNAME/kilo-sync-data.git ~/repos/kilo-sync-data
# 2. Write the same config
cat > ~/.kilo/kilo-sync.yaml <<'EOF'
local_clone: ~/repos/kilo-sync-data
profile: default
EOF
# 3. Decrypt into staging (safe β nothing is written to live paths yet)
kilo-sync import --pull
# 4. Preview what will change
kilo-sync apply --latest --dry-run
# 5. Apply for real
kilo-sync apply --latest# After changing your Kilo config on machine A:
kilo-sync export --publish
# On machine B, pull and apply:
kilo-sync import --pull && kilo-sync apply --latest# Using env var:
KILO_SYNC_PASSPHRASE="my-passphrase" kilo-sync export --publish
# Or pipe via stdin:
echo "my-passphrase" | kilo-sync export --publishAll commands accept
--config PATHto use a non-default config file (default:~/.kilo/kilo-sync.yaml).
kilo-sync export [--profile NAME] [--publish] [--strict-size] [--force]| Flag | Description |
|---|---|
--profile NAME |
Override the profile name from config |
--publish |
After encrypting, git add, git commit, and git push |
--strict-size |
Error (not just warn) if tarball exceeds max_tar_bytes |
--force |
Re-encrypt even if the plaintext tarball is unchanged |
Tip
If the profile content hasn't changed since the last export, the tool prints
No profile changes to export and skips re-encryption (the ciphertext and
passphrase aren't touched). Use --force to generate a new ciphertext with a
fresh random salt.
With --publish, the working tree outside profiles/ must be clean (no
uncommitted changes). The commit message is:
kilo-sync: profile <name> <UTC timestamp>
kilo-sync import [--profile NAME] [--pull] [--dry-run]| Flag | Description |
|---|---|
--profile NAME |
Override the profile name from config |
--pull |
Run git pull --ff-only on local_clone before importing |
--dry-run |
Decrypt and diff against live paths, but write nothing |
Staging directories are created at ~/.kilo/import-staging/<timestamp_uuid>/.
After import, a diff table is printed showing staged vs. live file sizes and mtimes.
kilo-sync apply (--latest | --staging PATH) [--dry-run]| Flag | Description |
|---|---|
--latest |
Use the most-recently-modified staging directory |
--staging PATH |
Use a specific staging directory |
--dry-run |
Print what would be copied/backed up, but change nothing |
Important
Before overwriting any live file, the existing version is backed up to
~/.kilo/backups/<timestamp>/<relative-path>. Permissions: 0o644 for most
files, 0o600 for auth.json.
kilo-sync verify [--profile NAME]Decrypts the ciphertext and verifies both ciphertext_sha256 and
plaintext_sha256 from the meta file. Exits 0 on success, 1 on any
mismatch. Does not write anything to disk.
kilo-sync staging prune [--keep N] [--dry-run]| Flag | Description |
|---|---|
--keep N |
Number of newest directories to keep (default: value of keep_staging from config, which defaults to 3) |
--dry-run |
Print directories that would be removed, but don't remove them |
Directories are sorted by mtime (newest first); the oldest beyond N are deleted.
kilo-sync --version # print version and exit
kilo-sync -V # short form
kilo-sync --help # usage summary
kilo-sync <cmd> --help # per-command helpkilo-sync resolves the passphrase in this order:
- Interactive TTY β
getpassprompts once when stdin is a terminal. - Environment variable β
KILO_SYNC_PASSPHRASE(evaluated first in non-tty mode). - Piped stdin β first line read from stdin when not a tty and the env var is not set.
Warning
Security note: the env var may be visible in /proc/<pid>/environ on shared
machines. Prefer piped stdin or an interactive prompt on untrusted hosts.
| Backend | How it works | When it's used |
|---|---|---|
python |
PBKDF2-HMAC-SHA256 (600 000 iterations) β AES-256-GCM | Default when age is not on PATH |
age |
age passphrase mode |
When age binary is on PATH, or encryption: age is set |
Set encryption: python in config to force the built-in engine even when age is
installed. The ciphertext format is auto-detected on import/verify.
The file extension reveals the engine used:
- π
profiles/<name>.tar.kilosync.binβ Python AES-GCM - π
profiles/<name>.tar.ageβage
cd /path/to/kilo-sync
pip install -e ".[dev]"
pytest -q # run all tests
pytest -v # verbose outputRequirements: Python 3.10+, cryptography >= 42, PyYAML >= 6.
Optional: age on PATH.
- π docs/USAGE.md β detailed usage guide and workflow scenarios
- π docs/PUBLISHING.md β releases, GitHub Actions, server deployment