@narumitw/pi-sync
Pi extension that syncs Pi configuration through Git, WebDAV, 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.50.0- Published
- Sep 6, 2026
- Downloads
- 6,088/mo · 662/wk
- Author
- narumitw
- License
- MIT
- Types
- extension
- Size
- 1.9 MB
- Dependencies
- 3 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./dist/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
☁️ pi-sync — Sync Pi Settings Across Machines
Pi Sync synchronizes selected Pi settings and content across machines through Git, WebDAV, Cloudflare R2, or another S3-compatible store. Reusable storage connections hold credentials, while named sync setups define exactly what to sync, where to store it, and whether to sync automatically.
✨ Features
- Manages Git, WebDAV, Cloudflare R2, and general S3-compatible storage through
/sync. - Separates reusable storage connections from named setups with exact reviewed remote paths.
- Uses one ordered list for Pi roots, safe agent-relative paths, and privacy-sensitive sessions.
- Protects changes with immutable snapshots, secret scanning, locks, conflict checks, pull backups, transactional apply, and recovery journals.
- Keeps snapshot selection portable and free of credentials.
- Writes settings atomically, preserves unknown fields, rejects stale edits, and fails closed on unsafe configuration.
- Loads a generated split runtime with lazy UI and backend chunks.
📦 Install
pi install npm:@narumitw/pi-sync
Try without installing permanently:
pi -e npm:@narumitw/pi-sync
Build the generated runtime and try a local checkout:
npm --workspace @narumitw/pi-sync run build
pi -e ./packages/pi-sync
The package declares dist/index.ts, so an unbuilt checkout must run the build before Pi loads the package directory.
Extensions run with Pi's permissions, so install only packages from sources you trust.
🚀 Quick start
Run /sync and choose Set up sync.
Name the setup once; its first storage connection uses the same name.
Input prompts show examples or an explicit default that you can accept by submitting an empty value; cancelling never accepts a default.
Remote URLs, endpoints, usernames, and credentials require your own values, not the displayed examples.
All new setups default to storage path ./: the Git repository root, the WebDAV collection URL, or the selected R2/S3 bucket root.
Git also suggests branch main.
Use different folders or prefixes for independent setups sharing a WebDAV collection or bucket; existing settings and paths are unchanged.
If another configured setup already uses the chosen location, setup asks for a different path before the content selection and review; Git requires a different branch, even when the directory differs.
Every new setup asks for included content and automatic sync; automatic sync is off unless you enable it. Recommended includes nine Pi roots; Minimal includes settings.json and AGENTS.md. Sessions stay off unless explicitly included with a privacy acknowledgement.
Before saving, scroll through the exact destination, every included path, automatic-sync choice, and hidden-credential summary. R2/S3 always asks for an existing bucket; an example bucket name is not a default.
Invalid fields ask for a correction without restarting setup. A temporary local save failure keeps the review for another Save; changed settings require reopening and reviewing the current values. Saving only changes local settings, not remote data. A connection saved through Add a new storage connection… remains saved if you later cancel the setup.
Buckets and remote repositories must already exist.
Git uses existing non-interactive SSH or credential-helper configuration and never stores Git credentials.
It owns the entire selected branch: use an empty repository or a new branch if main already contains unrelated content.
At ./, Git stores manifest.json and files/ at the repository root; it does not modify your local working tree.
WebDAV and R2/S3 store latest.json, history.json, and snapshots/ directly under their selected root, not under a literal ./ prefix.
🧭 Manager, conflicts, and recovery
The /sync manager shows local state without contacting remote storage:
Current sync setup: home
Storage: Cloudflare R2 · r2 · personal-pi
Included: 5 built-in groups · 0 extra files · Sessions off
Automatic sync: On (startup check; shutdown pushes selected content if sessions included)
Remote status: Not checked
Primary actions include Sync now, Switch sync setup, Status & changes, Settings, and More….
After Pi Sync detects an included-content mismatch, the manager shows Sync status: Review needed and puts Review synced content (recommended) first. Sync now remains unavailable until you review the mismatch. Opening the manager does not make another remote request. Under More…, Storage connections holds reusable server addresses and sign-in details; Sync setups holds content, destination, and automatic-sync choices. Edit storage location… changes only bucket, branch, or path, not the connection or included content. Use Settings for the current setup's content and automatic sync; make another setup current before changing those settings.
More… → Check setup (also /sync doctor) reports configuration and backend-specific access checks. S3/R2 performs a read-only request: success does not prove write access or snapshot validity, and an ambiguous 404 does not prove a bucket is missing. Check the indicated credentials, address, bucket, or path before retrying. Git checks remote reads and the local cache, not write access. WebDAV uses an isolated write/cleanup probe and repairs the active snapshot's history entry if missing. The existing History & recovery route remains available.
On secondary menus, Back and Escape return to the previous screen. In setup inputs and save reviews, cancellation (including Ctrl+C) discards the current unsaved draft and returns to its owning menu. Session replacement or shutdown closes the owning flow. Specialized operation and masked-credential prompts show the effective cancellation bindings and keep Ctrl+C as a hard-cancel input when Back is remapped. Destructive, credential-bearing, and externally visible operations show exact previews and confirmations.
Restore sync access
While an operation is running, the manager shows its command and process ID, disables sync and settings changes, and puts Refresh operation status first. When an active guard still protects lock metadata, the manager asks you to wait because another Pi Sync process may be starting or finishing. It never offers lock removal while an owner or guard may still be active.
If a stopped operation leaves its local lock behind, the manager shows Sync paused and puts Restore sync access… (recommended) first. Unreadable metadata receives a stronger warning because Pi Sync cannot verify its owner. Close every other Pi session that may still be syncing, review the local-lock-only confirmation, and choose Remove local lock and continue. Before removal, the guarded recovery path rechecks metadata and ownership.
Successful recovery changes no settings, local files, sync state, or remote data and returns to the normal manager.
Cancellation leaves the lock unchanged.
If ownership changes or a guard is still expiring, Pi Sync refuses removal and keeps refresh or retry available.
After the same no-other-sync verification, /sync unlock --stale provides a deterministic fallback.
Resolve conflicts in the manager
When Sync now, Pull from remote…, or Push to remote… requires a direction choice, the manager opens Resolve sync conflict. The flow names the current setup and explains whether local content, remote content, or the included-content policy changed.
An explicit remote content-list mismatch opens Synced content differs in the same manager flow. Nothing changes until you choose an action. Choose Review all paths (recommended) first to compare exact remote-only paths, device-only paths, and both ordered lists. If membership matches but order differs, the review says that only ordering differs. Then choose one action:
- Use remote content list revalidates the remote snapshot and reviewed local setup before saving only
sync.include. Remote content list saved confirms that no files were pulled and offers a separate Continue Sync/Pull/Push now… action plus Done. Continue starts a fresh operation and exact preview for the captured setup rather than resuming stale work. - Keep this device's content list and update remote… opens the existing
push --forcepreparation and exact confirmation without--yes. Cancelling preparation or confirmation returns to the content-list choice without changing remote data. - Cancel returns to the manager without changing settings, files, remote data, or sync state.
Choose Review differences (recommended) in an ordinary file-direction conflict to inspect the exact affected paths without changing local files, remote data, or sync state. Then choose one reviewed direction:
- Keep local content and replace remote… uses the existing forced-push path. It applies the secret-scan setting, shows the exact remote publication effect, re-reads a changed remote head, and asks again if the reviewed plan changed.
- Use remote content and replace local… uses the existing forced-pull path. It shows exact local writes and deletions, protects the live session, and creates a local backup before applying.
- First sync uses Use local as initial source… and Use remote as initial source… labels. An empty remote offers Push local content… only.
Cancelling a preparation or confirmation returns to conflict resolution with no side effects. Back returns to the sync manager, and Ctrl+C closes the complete flow.
Startup checks never open a dialog. Detected changes leave a compact hint above the editor; open /sync when ready. An included-content mismatch puts Review synced content (recommended) in the manager, where a fresh review verifies the remote snapshot before offering changes.
Check results are advisory observations against the last sync baseline, not proof of a file conflict or current equality. Opening the manager uses local information and shows when the check completed; it does not contact remote storage. Transfer actions recheck current content.
Attention stays in memory and is invalidated by relevant settings/state changes or a foreground transfer's commit boundary, and cleared on session replacement or shutdown. Cancelling a review or a failure before commit preserves a still-valid hint.
Interactive TUI /sync sync, /sync pull, and /sync push routes without --yes open the same review flow when they detect the mismatch.
Explicit --yes routes remain non-interactive and report exact remote-only, device-only, or order-only guidance while leaving visible attention for later review.
Shutdown automatic sync never opens a dialog because Pi is exiting.
RPC startup check results use nonblocking notifications; included-content review remains read-only.
Print and JSON modes do not support /sync because UI output is not observable there.
⚙️ Settings
Run /sync → Set up sync to create the canonical private user file at <getAgentDir()>/pi-sync.json (normally ~/.pi/agent/pi-sync.json).
Use Settings to manage an existing setup.
Missing settings stay unconfigured without creating files or locks.
Background startup checks
With Automatic sync enabled, session startup schedules a background check instead of waiting for a transfer. Pi remains usable while Git, WebDAV, R2, or S3 checks local hashes and remote metadata. Unchanged results are quiet; changes or failures provide /sync guidance without opening a dialog. Use Sync now to start a reviewed transfer, or /sync status to retry a check. Foreground /sync cancels and drains the background check before starting.
Checks run once per session start, including /reload, new, resumed, and forked sessions, in TUI and RPC only. Print/JSON skip startup checks. There is no polling or automatic check retry loop. The overall deadline is 30 seconds, followed by underlying cleanup where needed; Git process termination and temporary-ref cleanup can take additional time. Git checks can still fetch objects and update the extension's private bare cache, but never push, apply managed files, update the sync baseline, or reload Pi.
Compatibility change: the existing sync.automatic boolean and Off default are unchanged, but On no longer performs startup push/pull. Startup is not guaranteed to use the latest remote content. Local transaction recovery remains an awaited safety barrier and can restore interrupted file changes before use; existing legacy settings-file initialization is also retained.
Shutdown behavior is unchanged: when Automatic sync is On and sessions are included, pi-sync can automatically push the selected content, not only session files. This also applies to headless modes; shutdown never opens a dialog, and /reload skips this push. Turning the setting Off disables both future startup checks and automatic shutdown pushes.
Settings → Skip secret scan (all setups) defaults to Off; enable it only after reviewing the destination and selected content because it disables push scanning for every setup. See Secret scanning for the setting and diagnostic behavior.
A minimal Git setup uses an existing private remote and keeps automatic sync off:
{
"version": 3,
"activeSyncSetup": "home",
"onSwitch": "ask-before-pull",
"skipSecretScan": false,
"storageConnections": {
"github": {
"type": "git",
"remote": "git@github.com:owner/private-pi-sync.git"
}
},
"syncSetups": {
"home": {
"storage": {
"connection": "github",
"branch": "pi-sync/home",
"path": "pi-sync/home"
},
"sync": {
"include": ["settings.json", "AGENTS.md"],
"automatic": false
}
}
}
}
Setup saves are atomic and private (0600 on POSIX), preserve unknown fields, and coordinate across pi-sync processes.
Do not save from a lock-unaware editor during a pi-sync settings operation.
Malformed, invalid, unsupported, symlinked, or concurrently changed documents remain untouched.
Version 1, version 2, and non-empty unversioned settings require manual recovery rather than automatic migration.
Adding sessions can upload prompts, tool output, paths, images, and secrets; interactive flows require a privacy acknowledgement.
Startup checks report included-content differences without changing anything. Transfers retain their existing included-content policy checks instead of silently expanding local scope.
Read the settings reference for complete S3/R2, Git, and WebDAV examples, backend fields, included-content rules, legacy paths, and recovery steps.
💬 Commands
| Command | Purpose |
|---|---|
/sync |
Set up storage, manage synced content, and review sync operations or recovery. |
/sync help |
Show command usage. |
/sync use <setup> |
Switch the active local sync setup, following its switch policy. |
/sync init |
Create a local configuration template. |
/sync config |
Show resolved configuration. |
/sync files |
List included local files. |
/sync status |
Compare local and remote snapshot state. |
/sync diff |
Show local and remote differences. |
/sync doctor |
Check configuration, connectivity, and backend safety. |
/sync push |
Publish local content to remote storage. |
/sync pull |
Back up local content, then apply the remote snapshot. |
/sync sync |
Choose a safe sync direction or require conflict review. |
/sync history |
Browse remote snapshots and review a rollback. |
/sync rollback <snapshot-id> |
Back up local content, apply a historical snapshot, and republish it remotely. |
/sync migrate-state |
Migrate the legacy local state directory. |
/sync unlock --stale |
Recover an abandoned local lock after guarded ownership checks. |
All routes support TUI and RPC; RPC settings and included-content screens are read-only.
Print and JSON modes reject /sync.
Unknown commands or flags, trailing values, and missing setup/snapshot values are rejected, including the former version 2 setup-addressing flag.
--setup <name>targets a setup without switching it onconfig,files,status,diff,doctor,push,pull,sync,history, androllback.--yes(alias:-y) skips confirmation onpush,pull,sync,rollback, andmigrate-state; use only after reviewing the affected content.--forceletspushorpullaccept content conflicts without disabling backend concurrency protection. It is also accepted bysync, which still requires a direction choice for divergent content, and byrollback, where it has no additional effect.--staleis accepted only byunlockand is required to remove a stale lock.
Push and rollback publish data externally; pull and rollback can replace or delete local managed files. Review Settings for included-content privacy and Manager, conflicts, and recovery before forcing a direction, skipping confirmation, or removing a lock.
🔄 Backend and recovery model
| Backend | Publication guarantee | Authentication | Remote path |
|---|---|---|---|
| Git | Exact expected-ref lease | Existing SSH/configured credential helper | <branch>:<storage.path> |
| WebDAV | Verified strong conditional requests | Private settings username/app password | <url>/<storage.path> |
| R2/S3 | Read-check-write-verify | Private settings credentials | <bucket>/<storage.path> |
Git requires Git 2.30 or newer and a SHA-1-format remote repository.
HTTPS userinfo, URL passwords, local paths, file, git, ext, and remote-helper transports are rejected.
When editing a Git setup, changing its storage path also requires a new owned branch so the existing branch remains readable at its reviewed path.
The private bare cache under <agent-dir>/pi-sync/git/ is rebuildable.
WebDAV requires HTTPS except loopback tests.
URL credentials, query strings, fragments, unsafe redirects, weak/missing ETags, and ignored conditional headers fail closed.
/sync doctor verifies collection and conditional-write behavior with an isolated probe, then repairs a missing active-snapshot history entry.
S3/R2 stages immutable bundles, rechecks the visible head before publication, and verifies afterward.
Unlike Git/WebDAV, generic S3 does not provide an atomic compare-and-swap for latest.json; status review remains important for simultaneous writers.
Before pull or rollback, pi-sync writes a backup under <agent-dir>/pi-sync/backups/.
Apply preflights paths and checksums, journals all mutations, restores the prior state after failures, and recovers interrupted journals on startup.
Removing a local setup or connection never deletes remote data.
The operational state root is <agent-dir>/pi-sync/.
An existing installation continues using <agent-dir>/.pisync/ and shows migration guidance on startup; it is never moved merely because no sync is active.
Close every other Pi process, then run /sync migrate-state and confirm the review (or pass --yes for an already reviewed RPC workflow).
The command serializes against every upgraded state/cache user and atomically renames .pisync/ only when no legacy sync lock or guard is active.
Close Pi instances running older pi-sync versions during the migration because they do not understand the migration guard.
If both roots exist, or either root is a symlink or non-directory, pi-sync refuses stateful work instead of merging, following, or deleting data.
Preserve both roots before manual recovery; with every Pi process closed and no destination conflict, rollback is an atomic rename from pi-sync/ back to .pisync/.
🔒 Security and privacy
- Canonical, legacy, temporary, and recovery settings paths are denied from snapshots; both
pi-sync/and.pisync/state roots are permanently denied. - Push scans managed local content for common secret patterns unless the user explicitly enables Skip secret scan;
/sync doctoralways retains the diagnostic scan. - Remote snapshot references, checksums, paths, manifests, response sizes, and publication revisions are validated.
- Symlink parents, path escapes, duplicate paths, and unsafe file/directory replacement fail before local mutation.
- Live locks block mutation; stale recovery rechecks process and guard ownership.
- Cancellation aborts preparation and dialogs. Publication/apply commit boundaries finish with bounded signals and report ambiguous outcomes explicitly.
- Terminal-bound names, paths, metadata, and errors are control-character sanitized.
♿ Terminal accessibility
Pi exposes terminal components rather than a semantic or ARIA tree.
Release checks cover textual state, keyboard operation, Escape and Back behavior, control escaping, and narrow rendering.
Critical meaning appears in text such as (current), Review needed, No startup transfer, Warning, Invalid, Saved, Cancelled, and Applied.
Color is supplementary, and the attention widget is informational rather than interactive.
🗂️ Package layout
packages/pi-sync/
├── src/
│ ├── index.ts # Thin Pi entrypoint
│ ├── sync-extension.ts # Registration and session lifecycle ownership
│ ├── commands/ # Parsing, execution, and attention dispatch
│ ├── settings/ # Schema, validation, resolution, and persistence
│ ├── sync/ # Queries, mutations, policy, and lazy loaders
│ ├── snapshot/ # Collection, session paths, apply, and recovery
│ ├── backends/ # Contract and Git, S3, and WebDAV transports
│ ├── state/ # Sync-state persistence, locks, and migration
│ └── ui/ # Manager, settings, reviews, and setup flows
├── dist/ # Generated Jiti runtime
├── scripts/build-runtime.mjs # Runtime builder
├── docs/ # Published reference documentation
└── test/ # Behavior and lifecycle coverage
The generated runtime is built from src/index.ts and does not import back into src.
Internal modules use direct imports from their owners; src/sync.ts and src/types.ts retain compatibility exports.
Backend transports do not depend on UI, and first-use operation and setup implementations remain lazy.
🔎 Keywords
Pi extension, Pi coding agent, settings sync, Git, WebDAV, Nextcloud, Cloudflare R2, S3-compatible storage, storage connections, sync setups, snapshot sync, dotfiles sync.