@narumitw/pi-sync

Pi extension that syncs Pi configuration through Cloudflare R2 or S3-compatible storage.

Packages

Package details

extension

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

npm Pi extension License: MIT

@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 /pisync command with status, diff, push, pull, sync, history, rollback, doctor, and unlock subcommands.
  • Syncs allowlisted Pi configuration from ~/.pi/agent:
    • settings.json
    • keybindings.json
    • models.json
    • AGENTS.md
    • skills/, prompts/, themes/, and extensions/
  • Stores each remote version as an immutable gzip-compressed JSON snapshot bundle.
  • Updates remote state through latest.json after re-reading remote state to reject already-visible remote changes.
  • Creates local backups before pull and rollback under ~/.pi/agent/.pisync/backups/.
  • Runs /pisync sync automatically on Pi startup when R2/S3 config is present.
  • Uses a local exclusive lock at ~/.pi/agent/.pisync/lock for 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, and node_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 pull or /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 .pisync state.
  • If another Pi process is already syncing on the same machine, destructive commands stop at the local lock. /pisync unlock --stale is 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.