@narumitw/pi-sync
Pi extension that syncs Pi configuration through Cloudflare R2 or S3-compatible storage.
Package details
Install @narumitw/pi-sync from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@narumitw/pi-sync- Package
@narumitw/pi-sync- Version
0.1.31- Published
- May 21, 2026
- Downloads
- not available
- Author
- narumitw
- License
- MIT
- Types
- extension
- Size
- 49.9 KB
- Dependencies
- 0 dependencies · 0 peers
Pi manifest JSON
{
"extensions": [
"./src/sync.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
☁️ pi-sync — R2/S3 Pi Settings Sync
@narumitw/pi-sync is a native Pi coding agent extension that syncs selected Pi configuration through Cloudflare R2 or other S3-compatible object storage.
It syncs automatically by default when Pi starts, then uses immutable snapshot bundles, a latest.json pointer, local locking, secret scanning, and pre-apply backups. Cross-machine pushes use a best-effort remote re-read guard because R2 rejected conditional latest.json writes during testing.
✨ Features
- Adds a
/pisynccommand withstatus,diff,push,pull,sync,history,rollback,doctor, andunlocksubcommands. - Syncs allowlisted Pi configuration from
~/.pi/agent:settings.jsonkeybindings.jsonmodels.jsonAGENTS.mdskills/,prompts/,themes/, andextensions/
- Stores each remote version as an immutable gzip-compressed JSON snapshot bundle.
- Updates remote state through
latest.jsonafter re-reading remote state to reject already-visible remote changes. - Creates local backups before
pullandrollbackunder~/.pi/agent/.pisync/backups/. - Runs
/pisync syncautomatically on Pi startup when R2/S3 config is present. - Uses a local exclusive lock at
~/.pi/agent/.pisync/lockfor destructive sync operations and only treats locks as stale after checking process liveness. - Refuses to push common secret patterns and denylisted paths such as
.env,.env.local, token/secret files,.pisync,.git, andnode_modules. - Preflights snapshot apply operations before mutating local files, refuses symlink path escapes, and rejects writes over symlinks or directories.
📦 Install
pi install npm:@narumitw/pi-sync
Try without installing permanently:
pi -e npm:@narumitw/pi-sync
Try this package locally from the repository root:
pi -e ./extensions/pi-sync
⚙️ Configuration
Run:
/pisync init
Then edit:
~/.pi/agent/pi-sync.local.json
Example:
{
"endpoint": "https://<account-id>.r2.cloudflarestorage.com",
"bucket": "pi-sync",
"region": "auto",
"accessKeyId": "<access-key-id>",
"secretAccessKey": "<secret-access-key>",
"profile": "default",
"prefix": "pi-sync",
"autoSync": true
}
Environment variables override the local config file:
export PI_SYNC_ENDPOINT="https://<account-id>.r2.cloudflarestorage.com"
export PI_SYNC_BUCKET="pi-sync"
export PI_SYNC_REGION="auto"
export PI_SYNC_ACCESS_KEY_ID="..."
export PI_SYNC_SECRET_ACCESS_KEY="..."
export PI_SYNC_SESSION_TOKEN="..." # optional, for temporary credentials that require a session token
export PI_SYNC_PROFILE="default"
export PI_SYNC_PREFIX="pi-sync"
export PI_SYNC_AUTO_SYNC="true"
PI_SYNC_ACCESS_KEY_ID, PI_SYNC_SECRET_ACCESS_KEY, and PI_SYNC_SESSION_TOKEN are local-only credentials. Do not put them in files that pi-sync syncs. PI_SYNC_SESSION_TOKEN is optional and only needed for temporary credentials such as AWS STS, AWS SSO, assumed roles, or S3-compatible providers that issue short-lived credentials.
Cloudflare R2 static access keys do not use a session token and usually reject requests signed with X-Amz-Security-Token. R2 temporary credentials that require a token are still supported. For R2 endpoints (*.r2.cloudflarestorage.com), pi-sync first sends the configured session token; if R2 rejects it with InvalidArgument: X-Amz-Security-Token, pi-sync retries that request once without the token and omits the token for the rest of the same command after a successful retry.
pi-sync also reads AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_REGION, R2_ENDPOINT, and R2_BUCKET as compatibility aliases when the matching PI_SYNC_* variable is not set.
🚀 Usage
/pisync config
/pisync doctor
/pisync status
/pisync diff
/pisync push
/pisync pull
/pisync sync
/pisync history
/pisync rollback <snapshot-id>
/pisync unlock --stale
Useful flags:
--yes/-y: skip confirmation prompts.--force: allow push or pull when both local and remote state changed.--stale: remove a stale local lock with/pisync unlock --stale.
🔄 Automatic sync
autoSync defaults to true. When Pi starts, pi-sync runs the same conservative decision logic as /pisync sync:
- only local changed or remote is empty → push
- first sync with existing local settings and an identical remote snapshot → initialize local sync state without rewriting files
- first sync with existing local settings and a different remote snapshot → skip and show a warning so you manually choose
/pisync pullor/pisync push - only remote changed after an established sync → pull with a backup
- both local and remote changed after a previous sync → skip and show a warning
- no config present → do nothing
Disable startup sync with either:
{
"autoSync": false
}
or:
export PI_SYNC_AUTO_SYNC=false
🧠 Sync model
Remote layout:
pi-sync/
└── profiles/
└── default/
├── latest.json
├── history.json
└── snapshots/
└── 2026-05-21T12-00-00-000Z-abcd1234.json.gz
Each snapshot contains the selected file tree and SHA-256 hashes. latest.json points to the active snapshot. Rollback applies an older snapshot locally and moves latest.json back to that snapshot.
Before updating latest.json, pi-sync re-reads the current pointer and rejects the push if it already differs from the version seen at the start of the command. This prevents overwriting changes that are visible before the final write. It is not a true atomic cross-machine compare-and-swap on R2, so two machines that push at the same instant can still race; run /pisync status before important manual pushes if you use multiple machines heavily.
🛡️ Safety notes
- pi-sync auto-syncs on startup by default, but skips instead of overwriting when first-run local settings and a remote snapshot both exist, or when both local and remote changed after a previous sync.
- pi-sync does not sync Pi sessions, OAuth state, npm caches,
.env,.env.local,node_modules, or.pisyncstate. - If another Pi process is already syncing on the same machine, destructive commands stop at the local lock.
/pisync unlock --staleis intended for locks whose process is gone or invalid. - If another machine's update is visible before this machine updates
latest.json, push is rejected unless you explicitly use--force. - Pull and rollback create backups before writing local files, then preflight deletes and writes before mutating the local settings tree.
- Pull and rollback refuse to follow symlinked parent paths during snapshot apply and refuse to overwrite a symlink or directory with file content.
🗂️ Package layout
extensions/pi-sync/
├── src/
│ └── sync.ts
├── README.md
├── LICENSE
├── tsconfig.json
└── package.json
The package exposes its Pi extension through package.json:
{
"pi": {
"extensions": ["./src/sync.ts"]
}
}
🔎 Keywords
Pi extension, Pi coding agent, settings sync, Cloudflare R2, S3-compatible storage, snapshot sync, dotfiles sync.
📄 License
MIT. See LICENSE.