@narumitw/pi-sync 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +191 -0
  3. package/package.json +41 -0
  4. package/src/sync.ts +1188 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 narumiruna
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # ☁️ pi-sync — R2/S3 Pi Settings Sync
2
+
3
+ [![npm](https://img.shields.io/npm/v/@narumitw/pi-sync)](https://www.npmjs.com/package/@narumitw/pi-sync) [![Pi extension](https://img.shields.io/badge/Pi-extension-blue)](https://pi.dev) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
4
+
5
+ `@narumitw/pi-sync` is a native [Pi coding agent](https://pi.dev) extension that syncs selected Pi configuration through Cloudflare R2 or other S3-compatible object storage.
6
+
7
+ 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.
8
+
9
+ ## ✨ Features
10
+
11
+ - Adds a `/pisync` command with `status`, `diff`, `push`, `pull`, `sync`, `history`, `rollback`, `doctor`, and `unlock` subcommands.
12
+ - Syncs allowlisted Pi configuration from `~/.pi/agent`:
13
+ - `settings.json`
14
+ - `keybindings.json`
15
+ - `models.json`
16
+ - `AGENTS.md`
17
+ - `skills/`, `prompts/`, `themes/`, and `extensions/`
18
+ - Stores each remote version as an immutable gzip-compressed JSON snapshot bundle.
19
+ - Updates remote state through `latest.json` after re-reading remote state to reject already-visible remote changes.
20
+ - Creates local backups before `pull` and `rollback` under `~/.pi/agent/.pisync/backups/`.
21
+ - Runs `/pisync sync` automatically on Pi startup when R2/S3 config is present.
22
+ - Uses a local exclusive lock at `~/.pi/agent/.pisync/lock` for destructive sync operations and only treats locks as stale after checking process liveness.
23
+ - Refuses to push common secret patterns and denylisted paths such as `.env`, `.env.local`, token/secret files, `.pisync`, `.git`, and `node_modules`.
24
+ - Preflights snapshot apply operations before mutating local files, refuses symlink path escapes, and rejects writes over symlinks or directories.
25
+
26
+ ## 📦 Install
27
+
28
+ ```bash
29
+ pi install npm:@narumitw/pi-sync
30
+ ```
31
+
32
+ Try without installing permanently:
33
+
34
+ ```bash
35
+ pi -e npm:@narumitw/pi-sync
36
+ ```
37
+
38
+ Try this package locally from the repository root:
39
+
40
+ ```bash
41
+ pi -e ./extensions/pi-sync
42
+ ```
43
+
44
+ ## ⚙️ Configuration
45
+
46
+ Run:
47
+
48
+ ```text
49
+ /pisync init
50
+ ```
51
+
52
+ Then edit:
53
+
54
+ ```text
55
+ ~/.pi/agent/pi-sync.local.json
56
+ ```
57
+
58
+ Example:
59
+
60
+ ```json
61
+ {
62
+ "endpoint": "https://<account-id>.r2.cloudflarestorage.com",
63
+ "bucket": "pi-sync",
64
+ "region": "auto",
65
+ "accessKeyId": "<access-key-id>",
66
+ "secretAccessKey": "<secret-access-key>",
67
+ "sessionToken": "<optional-session-token>",
68
+ "profile": "default",
69
+ "prefix": "pi-sync",
70
+ "autoSync": true
71
+ }
72
+ ```
73
+
74
+ Environment variables override the local config file:
75
+
76
+ ```bash
77
+ export PI_SYNC_ENDPOINT="https://<account-id>.r2.cloudflarestorage.com"
78
+ export PI_SYNC_BUCKET="pi-sync"
79
+ export PI_SYNC_REGION="auto"
80
+ export PI_SYNC_ACCESS_KEY_ID="..."
81
+ export PI_SYNC_SECRET_ACCESS_KEY="..."
82
+ export PI_SYNC_SESSION_TOKEN="..." # optional, for temporary STS/SSO credentials
83
+ export PI_SYNC_PROFILE="default"
84
+ export PI_SYNC_PREFIX="pi-sync"
85
+ export PI_SYNC_AUTO_SYNC="true"
86
+ ```
87
+
88
+ `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, or assumed roles. 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.
89
+
90
+ ## 🚀 Usage
91
+
92
+ ```text
93
+ /pisync config
94
+ /pisync doctor
95
+ /pisync status
96
+ /pisync diff
97
+ /pisync push
98
+ /pisync pull
99
+ /pisync sync
100
+ /pisync history
101
+ /pisync rollback <snapshot-id>
102
+ /pisync unlock --stale
103
+ ```
104
+
105
+ Useful flags:
106
+
107
+ - `--yes` / `-y`: skip confirmation prompts.
108
+ - `--force`: allow push or pull when both local and remote state changed.
109
+ - `--stale`: remove a stale local lock with `/pisync unlock --stale`.
110
+
111
+ ## 🔄 Automatic sync
112
+
113
+ `autoSync` defaults to `true`. When Pi starts, pi-sync runs the same conservative decision logic as `/pisync sync`:
114
+
115
+ - only local changed or remote is empty → push
116
+ - first sync with existing local settings and an identical remote snapshot → initialize local sync state without rewriting files
117
+ - 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`
118
+ - only remote changed after an established sync → pull with a backup
119
+ - both local and remote changed after a previous sync → skip and show a warning
120
+ - no config present → do nothing
121
+
122
+ Disable startup sync with either:
123
+
124
+ ```json
125
+ {
126
+ "autoSync": false
127
+ }
128
+ ```
129
+
130
+ or:
131
+
132
+ ```bash
133
+ export PI_SYNC_AUTO_SYNC=false
134
+ ```
135
+
136
+ ## 🧠 Sync model
137
+
138
+ Remote layout:
139
+
140
+ ```txt
141
+ pi-sync/
142
+ └── profiles/
143
+ └── default/
144
+ ├── latest.json
145
+ ├── history.json
146
+ └── snapshots/
147
+ └── 2026-05-21T12-00-00-000Z-abcd1234.json.gz
148
+ ```
149
+
150
+ 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.
151
+
152
+ 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.
153
+
154
+ ## 🛡️ Safety notes
155
+
156
+ - 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.
157
+ - pi-sync does not sync Pi sessions, OAuth state, npm caches, `.env`, `.env.local`, `node_modules`, or `.pisync` state.
158
+ - 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.
159
+ - If another machine's update is visible before this machine updates `latest.json`, push is rejected unless you explicitly use `--force`.
160
+ - Pull and rollback create backups before writing local files, then preflight deletes and writes before mutating the local settings tree.
161
+ - Pull and rollback refuse to follow symlinked parent paths during snapshot apply and refuse to overwrite a symlink or directory with file content.
162
+
163
+ ## 🗂️ Package layout
164
+
165
+ ```txt
166
+ extensions/pi-sync/
167
+ ├── src/
168
+ │ └── sync.ts
169
+ ├── README.md
170
+ ├── LICENSE
171
+ ├── tsconfig.json
172
+ └── package.json
173
+ ```
174
+
175
+ The package exposes its Pi extension through `package.json`:
176
+
177
+ ```json
178
+ {
179
+ "pi": {
180
+ "extensions": ["./src/sync.ts"]
181
+ }
182
+ }
183
+ ```
184
+
185
+ ## 🔎 Keywords
186
+
187
+ Pi extension, Pi coding agent, settings sync, Cloudflare R2, S3-compatible storage, snapshot sync, dotfiles sync.
188
+
189
+ ## 📄 License
190
+
191
+ MIT. See [`LICENSE`](./LICENSE).
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@narumitw/pi-sync",
3
+ "version": "0.1.29",
4
+ "description": "Pi extension that syncs Pi configuration through Cloudflare R2 or S3-compatible storage.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "private": false,
8
+ "keywords": [
9
+ "pi-package",
10
+ "pi-extension",
11
+ "pi",
12
+ "sync",
13
+ "r2",
14
+ "s3"
15
+ ],
16
+ "files": [
17
+ "src",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "pi": {
22
+ "extensions": [
23
+ "./src/sync.ts"
24
+ ]
25
+ },
26
+ "scripts": {
27
+ "check": "biome check . && npm run typecheck",
28
+ "format": "biome check --write .",
29
+ "typecheck": "tsc --noEmit"
30
+ },
31
+ "devDependencies": {
32
+ "@biomejs/biome": "2.4.14",
33
+ "@mariozechner/pi-coding-agent": "0.73.0",
34
+ "typescript": "6.0.3"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/narumiruna/pi-extensions",
39
+ "directory": "extensions/pi-sync"
40
+ }
41
+ }