@narumitw/pi-sync 0.28.0 β†’ 0.29.1

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.
package/README.md CHANGED
@@ -2,26 +2,26 @@
2
2
 
3
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
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.
5
+ `@narumitw/pi-sync` syncs selected Pi configuration through Cloudflare R2 or other S3-compatible object storage. Named sync targets such as `home` and `work` can reuse storage profiles such as `r2` and `s3`.
6
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. Conversation/session syncing is opt-in because session JSONL can contain prompts, tool output, paths, screenshots, and secrets. Cross-machine pushes use a best-effort remote re-read guard because R2 rejected conditional `latest.json` writes during testing.
7
+ The extension uses immutable snapshot bundles, a `latest.json` publication pointer, local locking, secret scanning, pre-apply backups, and recoverable local apply transactions. Conversation/session syncing remains opt-in because session JSONL can contain prompts, tool output, paths, screenshots, and secrets.
8
8
 
9
9
  ## ✨ Features
10
10
 
11
- - Opens every pi-sync action from the bare `/sync` menu and keeps direct `help`, `init`, `config`, `files`, `status`, `diff`, `doctor`, `push`, `pull`, `sync`, `history`, `rollback`, and `unlock` subcommands for automation and advanced flags.
12
- - Uses `/sync files` to choose which top-level Pi files and recursive directory groups are managed by this machine.
13
- - Syncs selected, allowlisted Pi configuration from `~/.pi/agent`:
11
+ - Opens a goal-oriented `/sync` manager showing the current target, storage, auto-sync, session scope, and relevant next actions.
12
+ - Supports multiple named **sync targets** and reusable R2/S3 **storage profiles**.
13
+ - Switches targets without syncing or modifying files; only the current target runs automatic startup/shutdown sync.
14
+ - Previews concrete local or remote file changes before push, pull, force resolution, or rollback.
15
+ - Uses transactional synced-content drafts with explicit Save, Discard, and Continue editing choices.
16
+ - Keeps direct `help`, `use`, `init`, `config`, `files`, `status`, `diff`, `doctor`, `push`, `pull`, `sync`, `history`, `rollback`, and `unlock` routes for compatibility and automation.
17
+ - Syncs allowlisted Pi configuration from the Pi agent directory:
14
18
  - `settings.json`, `keybindings.json`, `models.json`, `AGENTS.md`, and `APPEND_SYSTEM.md`
15
19
  - recursive `skills/`, `prompts/`, `themes/`, and `extensions/` groups
16
20
  - safe top-level files selected through `extraFiles`
17
- - optionally denylist-filtered `sessions/**/*.jsonl` when `syncSessions` is enabled
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 `/sync 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.
21
+ - optionally denylist-filtered `sessions/**/*.jsonl`
22
+ - Preserves files outside the addressed target's selection locally and in remote uploads.
23
+ - Creates backups before pull/rollback and journals multi-file applies so failures roll back; interrupted transactions recover before the next sync.
24
+ - Refuses common secret patterns, unsafe paths, symlink escapes, live-lock removal, and unreviewed conflict overwrites.
25
25
 
26
26
  ## πŸ“¦ Install
27
27
 
@@ -35,212 +35,297 @@ Try without installing permanently:
35
35
  pi -e npm:@narumitw/pi-sync
36
36
  ```
37
37
 
38
- Try this package locally from the repository root:
38
+ Try this package from a local checkout:
39
39
 
40
40
  ```bash
41
41
  pi -e ./extensions/pi-sync
42
42
  ```
43
43
 
44
- ## βš™οΈ Configuration
44
+ ## πŸš€ Quick start
45
45
 
46
- Run:
46
+ Run the manager:
47
47
 
48
48
  ```text
49
- /sync init
49
+ /sync
50
50
  ```
51
51
 
52
- Then edit:
53
-
54
- ```text
55
- ~/.pi/agent/pi-sync.local.json
56
- ```
52
+ When pi-sync is not configured, choose **Set up sync**. The TUI guides you through:
57
53
 
58
- If `PI_CODING_AGENT_DIR` is set, pi-sync uses that directory instead of `~/.pi/agent` for config, state, backups, and synced files.
54
+ 1. Cloudflare R2 or another S3-compatible service
55
+ 2. `Personal / Home`, `Work`, or a custom target purpose
56
+ 3. an endpoint and, for S3, the existing bucket name
57
+ 4. a recommended remote location or advanced customization
58
+ 5. environment credentials or a private settings-file template
59
+ 6. a synced-content preset
60
+ 7. an exact setup preview and **Save setup** confirmation
59
61
 
60
- Example:
62
+ For a first Cloudflare R2 target, the recommended location requires no raw path questions:
61
63
 
62
64
  ```json
63
65
  {
64
- "endpoint": "https://<account-id>.r2.cloudflarestorage.com",
66
+ "profile": "r2",
65
67
  "bucket": "pi-sync",
66
- "region": "auto",
67
- "accessKeyId": "<access-key-id>",
68
- "secretAccessKey": "<secret-access-key>",
69
- "profile": "default",
70
68
  "prefix": "pi-sync",
71
- "autoSync": true,
72
- "syncFiles": [
73
- "settings.json",
74
- "keybindings.json",
75
- "models.json",
76
- "AGENTS.md",
77
- "APPEND_SYSTEM.md",
78
- "skills",
79
- "prompts",
80
- "themes",
81
- "extensions"
82
- ],
83
- "syncSessions": false,
84
- "extraFiles": []
69
+ "namespace": "home"
85
70
  }
86
71
  ```
87
72
 
88
- Environment variables override the local config file:
73
+ The R2 bucket must already exist; pi-sync never creates buckets. Generic S3 setup asks for one existing, potentially globally unique bucket and derives storage profile `s3`, prefix `pi-sync`, and namespace `home` or `work`. **Customize remote location** retains direct control over profile name, bucket, prefix, and namespace.
89
74
 
90
- ```bash
91
- export PI_SYNC_ENDPOINT="https://<account-id>.r2.cloudflarestorage.com"
92
- export PI_SYNC_BUCKET="pi-sync"
93
- export PI_SYNC_REGION="auto"
94
- export PI_SYNC_ACCESS_KEY_ID="..."
95
- export PI_SYNC_SECRET_ACCESS_KEY="..."
96
- export PI_SYNC_SESSION_TOKEN="..." # optional, for temporary credentials that require a session token
97
- export PI_SYNC_PROFILE="default"
98
- export PI_SYNC_PREFIX="pi-sync"
99
- export PI_SYNC_AUTO_SYNC="true"
100
- export PI_SYNC_SESSIONS="false" # opt in with true to sync Pi conversation JSONL files
75
+ Pi's extension input API does not provide masked secret entry, so pi-sync never asks for a secret in an unmasked dialog. Use existing environment credentials or add credentials manually to the private settings file. Secret values are never shown in menus, status, warnings, or errors.
76
+
77
+ A configured manager shows:
78
+
79
+ ```text
80
+ Current target: home
81
+ Storage: Cloudflare R2 Β· personal-pi
82
+ Auto-sync: On Β· Sessions: Off
83
+ Last known sync: Remote not checked
84
+ ```
85
+
86
+ Its primary actions are:
87
+
88
+ - **Sync now** β€” conservatively decide whether to push, pull, or do nothing
89
+ - **Switch target** β€” preview and change the current target without syncing
90
+ - **Status & changes** β€” perform a cancellable read-only remote check
91
+ - **Settings** β€” control automatic sync and synced content for the current target
92
+ - **Manage targets & storage** β€” add, edit, or remove local target/profile definitions
93
+ - **History & recovery** β€” browse snapshots, preview rollback, diagnose setup, or recover a stale lock
94
+ - **Help**
95
+
96
+ Escape exits the main menu. Secondary menus provide Back; dirty synced-content drafts provide Save, Discard, and Continue editing. Cancellation before publication has no side effects.
97
+
98
+ ## βš™οΈ Settings
99
+
100
+ The private user settings file is:
101
+
102
+ ```text
103
+ ${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-sync.local.json
101
104
  ```
102
105
 
103
- Run `/sync files` in TUI mode to edit `syncFiles`, `syncSessions`, and `extraFiles` with a searchable selector. Enter or Space toggles an item and Escape closes the screen. Each successful change is saved immediately to `pi-sync.local.json`, preserving unknown fields through an atomic replacement with `0600` permissions on POSIX. No network sync starts until the next manual `push`, `pull`, or `sync`, or the existing automatic sync lifecycle.
106
+ A version 2 example:
104
107
 
105
- `syncFiles` accepts the built-in names shown above and has no environment-variable override. Directory names select the complete recursive group. Omitting `syncFiles` preserves the legacy default of selecting every built-in item; an empty array is valid and means this machine manages none of them. Unknown values make configuration validation fail instead of silently narrowing the selection.
108
+ ```json
109
+ {
110
+ "version": 2,
111
+ "activeTarget": "home",
112
+ "profiles": {
113
+ "r2": {
114
+ "kind": "r2",
115
+ "endpoint": "https://<account-id>.r2.cloudflarestorage.com",
116
+ "region": "auto",
117
+ "accessKeyId": "<access-key-id>",
118
+ "secretAccessKey": "<secret-access-key>"
119
+ },
120
+ "s3": {
121
+ "kind": "s3-compatible",
122
+ "endpoint": "https://s3.example.com",
123
+ "region": "ap-northeast-1",
124
+ "accessKeyId": "<access-key-id>",
125
+ "secretAccessKey": "<secret-access-key>"
126
+ }
127
+ },
128
+ "targets": {
129
+ "home": {
130
+ "profile": "r2",
131
+ "bucket": "personal-pi",
132
+ "prefix": "pi-sync",
133
+ "namespace": "home",
134
+ "autoSync": true,
135
+ "syncFiles": ["settings.json", "skills", "prompts", "themes"],
136
+ "syncSessions": false,
137
+ "extraFiles": []
138
+ },
139
+ "work": {
140
+ "profile": "s3",
141
+ "bucket": "company-pi",
142
+ "prefix": "developers/narumi",
143
+ "namespace": "work",
144
+ "autoSync": true,
145
+ "syncFiles": ["AGENTS.md", "prompts"],
146
+ "syncSessions": false,
147
+ "extraFiles": []
148
+ }
149
+ }
150
+ }
151
+ ```
106
152
 
107
- The selector always shows built-in items, even when they are absent locally, so another machine can provide them on pull. It also shows safe top-level regular files found locally and configured `extraFiles` that may only exist remotely. It excludes directories, symlinks, reserved names, and denylisted names. You can still edit `extraFiles` manually to select additional safe top-level file names.
153
+ ### Terminology
108
154
 
109
- An unselected item is unmanaged by this machine: pi-sync does not collect, compare, pull, overwrite, or delete its local content, and pushes preserve its existing remote snapshot content for other machines. This applies to built-ins, directory groups, sessions, and extra files. Selecting nothing therefore does not delete unmanaged local or remote files.
155
+ - A **storage profile** owns connection/authentication fields: `kind`, `endpoint`, `region`, `accessKeyId`, `secretAccessKey`, and optional `sessionToken`.
156
+ - A **sync target** references a storage profile and owns `bucket`, `prefix`, `namespace`, `autoSync`, and its synced-content policy.
157
+ - `activeTarget` is used by bare commands and automatic sync.
158
+ - `namespace` is the old flat `profile` concept. The remote layout and snapshot wire field remain named `profiles`/`profile` for compatibility.
110
159
 
111
- `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.
160
+ When adding another target with the same storage profile, pi-sync recommends reusing the current target's bucket and prefix while deriving a separate namespace from the new target name. For example, `home` and `work` may both use profile `r2`, bucket `pi-sync`, and prefix `pi-sync`, producing `pi-sync/profiles/home/` and `pi-sync/profiles/work/`.
112
161
 
113
- 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.
162
+ Two targets may intentionally overlap local files, but only one is automatic. pi-sync rejects duplicate effective target remote identitiesβ€”including deprecated bucket/prefix/namespace environment overridesβ€”to prevent independent local states from controlling the same remote pointer. Removing a target/profile removes local configuration only; it never deletes buckets or snapshots. A referenced profile cannot be removed, and users must switch away before removing one of several current targets.
114
163
 
115
- 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.
164
+ ### Synced content
116
165
 
117
- ### Session syncing
166
+ The **Settings β†’ Choose synced content** screen edits a draft. Enter or Space toggles, typing searches, and Escape opens the Save/Discard/Continue decision. A single Save atomically updates the target while preserving unknown fields and POSIX `0600` permissions. Saving settings never starts network sync.
118
167
 
119
- `syncSessions` defaults to `false`. Set it to `true`, or set `PI_SYNC_SESSIONS=true`, to include Pi's configured session JSONL files in snapshots. pi-sync uses `PI_CODING_AGENT_SESSION_DIR`, Pi's `sessionDir` setting, or the default `${PI_CODING_AGENT_DIR:-~/.pi/agent}/sessions/**/*.jsonl` storage. Empty or misspelled `PI_SYNC_SESSIONS` values stay disabled. Only JSONL session files are included; other session-directory files and denylisted paths such as `.env*`, `.pisync`, `node_modules`, and names containing `token` or `secret` are ignored.
168
+ Omitting `syncFiles` preserves the legacy default of every built-in item. An empty array is valid and means the target manages nothing; **Sync now** reports that state instead of publishing an empty sync. Unknown built-in names fail validation.
120
169
 
121
- Session sync is snapshot-based, not live collaboration. If the same session changes on two machines, `/sync sync` uses the same conflict rules as settings sync and skips when both local and remote changed. Run `/sync diff`, then choose `/sync pull --force` or `/sync push --force` if needed.
170
+ Unselected items are unmanaged: pi-sync does not compare, pull, overwrite, or delete their local content, and pushes preserve their existing remote content.
122
171
 
123
- When both `autoSync` and `syncSessions` are enabled, pi-sync syncs on startup and attempts a quiet session push on shutdown when local files changed. Startup session pulls happen after Pi has already selected the current session, so restart Pi or resume a pulled session to use newly synced conversations. If the remote changed first, the shutdown push is skipped with a warning instead of overwriting it.
172
+ ### Environment precedence and deprecation
124
173
 
125
- Session files can contain prompts, model output, tool results, file paths, images, and secrets. Use trusted R2/S3 storage, keep credentials local, and recover local files from `${PI_CODING_AGENT_DIR:-~/.pi/agent}/.pisync/backups/` if a pull or rollback overwrites something unexpectedly.
174
+ The existing `PI_SYNC_*` family still works in this release with its existing highest precedence, but it is deprecated and will be removed in a future major version. pi-sync shows variable namesβ€”not valuesβ€”and migration guidance when any are active.
126
175
 
127
- ## πŸš€ Usage
176
+ Move these fields into a storage profile:
128
177
 
129
- Run `/sync` without arguments to open the interactive menu containing every pi-sync action. The **files β€” Choose synced files** action opens the persistent selector. Choosing rollback asks for the snapshot id before showing the existing confirmation. Cancelling either action leaves unmodified state unchanged; file-selection changes that were already saved are not rolled back when the screen closes.
178
+ - `PI_SYNC_ENDPOINT`
179
+ - `PI_SYNC_REGION`
180
+ - `PI_SYNC_ACCESS_KEY_ID`
181
+ - `PI_SYNC_SECRET_ACCESS_KEY`
182
+ - `PI_SYNC_SESSION_TOKEN`
130
183
 
131
- ```text
132
- /sync
133
- ```
184
+ Move these fields into a sync target:
185
+
186
+ - `PI_SYNC_BUCKET`
187
+ - `PI_SYNC_PROFILE` β†’ `namespace`
188
+ - `PI_SYNC_PREFIX`
189
+ - `PI_SYNC_AUTO_SYNC`
190
+ - `PI_SYNC_SESSIONS`
191
+
192
+ `PI_SYNC_PROFILE` continues to mean only the remote namespace during the deprecation period; it never selects a storage profile or current target.
193
+
194
+ Standard compatibility aliases remain below `PI_SYNC_*` precedence:
195
+
196
+ - `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_REGION`
197
+ - `R2_ENDPOINT`, `R2_BUCKET`
198
+
199
+ Environment-overridden fields are read-only in interactive settings. Cloudflare R2 static keys commonly reject `X-Amz-Security-Token`; pi-sync retains its one-time retry without the configured token for that R2 response.
200
+
201
+ ### Legacy flat settings
202
+
203
+ Existing flat `pi-sync.local.json` files continue to work unchanged as a synthetic `default` target/profile. The old flat `profile` remains the remote namespace.
134
204
 
135
- The same actions remain available as direct routes for automation, non-interactive use, and advanced flags:
205
+ Adding a second target offers an explicit migration preview. On confirmation pi-sync:
206
+
207
+ 1. takes an exact private backup of the original JSON bytes;
208
+ 2. preserves unknown top-level fields;
209
+ 3. maps existing connection and policy fields without changing the remote destination;
210
+ 4. adopts the existing local sync state for the migrated target;
211
+ 5. publishes the version 2 file atomically.
212
+
213
+ Malformed, invalid, symlinked, or concurrently changed settings are never overwritten automatically. Version 2 settings require the multi-target pi-sync release or newer. For a manual downgrade, restore the exact `.legacy-<timestamp>.bak` flat-config backup; no remote migration or rollback is needed.
214
+
215
+ ## πŸ’¬ Commands
216
+
217
+ The menu is the preferred interactive workflow. Existing deterministic routes remain available:
136
218
 
137
219
  ```text
138
220
  /sync help
221
+ /sync use <target>
139
222
  /sync init
140
- /sync config
141
- /sync files
142
- /sync status
143
- /sync diff
144
- /sync doctor
145
- /sync push
146
- /sync pull
147
- /sync sync
148
- /sync history
149
- /sync rollback <snapshot-id>
223
+ /sync config [--target <name>]
224
+ /sync files [--target <name>]
225
+ /sync status [--target <name>]
226
+ /sync diff [--target <name>]
227
+ /sync doctor [--target <name>]
228
+ /sync push [--target <name>]
229
+ /sync pull [--target <name>]
230
+ /sync sync [--target <name>]
231
+ /sync history [--target <name>]
232
+ /sync rollback <snapshot-id> [--target <name>]
150
233
  /sync unlock --stale
151
234
  ```
152
235
 
153
- Bare `/sync` reports command usage when an interactive UI is unavailable; use a direct route for the desired operation. Outside TUI mode, `/sync files` does not open a custom component and instead reports the effective selection, config path, and manual editing fields. When `PI_SYNC_SESSIONS` is set, the sessions row is read-only and identifies the environment override.
236
+ Flags:
154
237
 
155
- Useful flags:
238
+ - `--target <name>` addresses a target without switching it.
239
+ - `--yes` / `-y` skips an explicit direct command's confirmation.
240
+ - `--force` resolves a reviewed conflict for push/pull/sync.
241
+ - `--stale` requests guarded stale-lock recovery.
156
242
 
157
- - `--yes` / `-y`: skip confirmation prompts.
158
- - `--force`: allow push or pull when both local and remote state changed.
159
- - `--stale`: remove a stale local lock with `/sync unlock --stale`.
243
+ Unknown flags, unexpected values, and missing target/snapshot values are rejected. Argument completion includes configured target names after session start.
160
244
 
161
- ## πŸ”„ Automatic sync
245
+ TUI mode provides the full manager and custom settings components. RPC uses Pi's dialog/notification protocol and does not call TUI-only components. Print/JSON modes cannot display extension UI; use explicit direct routes for compatible automation and do not expect notification-only status output.
162
246
 
163
- `autoSync` defaults to `true`. When Pi starts, pi-sync runs the same conservative decision logic as `/sync sync`:
164
-
165
- - only local changed or remote is empty β†’ push
166
- - first sync with existing local settings and an identical remote snapshot β†’ initialize local sync state without rewriting files
167
- - first sync with existing local settings and a different remote snapshot β†’ skip and show a warning so you manually choose `/sync pull` or `/sync push`
168
- - only remote changed after an established sync β†’ pull with a backup
169
- - both local and remote changed after a previous sync β†’ skip and show a warning
170
- - no config present β†’ do nothing
171
-
172
- Disable startup sync with either:
173
-
174
- ```json
175
- {
176
- "autoSync": false
177
- }
178
- ```
247
+ ## πŸ”„ Sync and recovery model
179
248
 
180
- or:
249
+ For the current target, startup auto-sync uses conservative decisions:
181
250
 
182
- ```bash
183
- export PI_SYNC_AUTO_SYNC=false
184
- ```
251
+ - local changed or remote empty β†’ preview/confirm manually, or push in the existing quiet automatic path
252
+ - identical first sync β†’ initialize local state
253
+ - safe remote-only change β†’ pull with backup
254
+ - first-sync mismatch or both changed β†’ stop and require review
255
+ - no selected content β†’ report/skip
185
256
 
186
- ## 🧠 Sync model
257
+ Switching targets changes only `activeTarget`; it never starts sync. The next startup uses the new target's `autoSync`, or the user can choose **Sync now** immediately.
187
258
 
188
- Remote layout:
259
+ Remote layout remains compatible:
189
260
 
190
- ```txt
191
- pi-sync/
261
+ ```text
262
+ <prefix>/
192
263
  └── profiles/
193
- └── default/
264
+ └── <namespace>/
194
265
  β”œβ”€β”€ latest.json
195
266
  β”œβ”€β”€ history.json
196
267
  └── snapshots/
197
- └── 2026-05-21T12-00-00-000Z-abcd1234.json.gz
268
+ └── <snapshot-id>.json.gz
198
269
  ```
199
270
 
200
- 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.
271
+ Snapshot upload is staging; `latest.json` is the active publication boundary. A failed history update after publication is reported as β€œsnapshot active, history needs repair” instead of falsely claiming no remote change.
272
+
273
+ Before pull/rollback, pi-sync writes a backup under `.pisync/backups/`. It then preflights all paths, stages a private transaction journal, applies changes, and restores every affected path if a later mutation fails. An interrupted journal is recovered before the next session/snapshot apply. Filesystem-wide replacement cannot be one OS primitive, so the guarantee is a complete previous or complete new accepted state after rollback/recoveryβ€”not an unrecoverable partial accepted state.
274
+
275
+ R2/S3 does not provide the true cross-machine compare-and-swap used here. pi-sync re-reads `latest.json` immediately before publication and verifies afterward, but simultaneous writers can still race. Review status before important forced updates.
201
276
 
202
- 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 `/sync status` before important manual pushes if you use multiple machines heavily.
277
+ ## πŸ”’ Session syncing
278
+
279
+ `syncSessions` defaults to `false`. Enabling it includes configured Pi session JSONL files and makes the privacy warning visible in settings, status, previews, and confirmations.
280
+
281
+ Only JSONL session files are included. Denylisted names and paths such as `.env*`, `.pisync`, `node_modules`, `token`, and `secret` are ignored. The currently open session file is protected during pull; restart Pi or resume a pulled session to use newly synced conversations.
282
+
283
+ Sessions can contain prompts, model output, tool results, file paths, images, and secrets. Use only storage you trust.
203
284
 
204
285
  ## πŸ›‘οΈ Safety notes
205
286
 
206
- - 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.
207
- - Unselected built-in files, directory groups, sessions, and extra files are unmanaged locally and preserved remotely; selecting nothing does not delete them.
208
- - With `syncSessions`/`PI_SYNC_SESSIONS` disabled, pi-sync does not collect or apply local Pi sessions, though settings-only pushes may preserve session files already present in remote snapshots. It never syncs OAuth state, npm caches, `.env`, `.env.local`, `node_modules`, or `.pisync` state.
209
- - If another Pi process is already syncing on the same machine, destructive commands stop at the local lock. `/sync unlock --stale` is intended for locks whose process is gone or invalid.
210
- - If another machine's update is visible before this machine updates `latest.json`, push is rejected unless you explicitly use `--force`.
211
- - Pull and rollback create backups before writing local files, then preflight deletes and writes before mutating the local settings tree.
212
- - Pull and rollback refuse to follow symlinked parent paths during snapshot apply and refuse to overwrite a symlink or directory with file content.
287
+ - Credentials stay local and are never included in snapshots.
288
+ - Push refuses common secret patterns in locally managed files.
289
+ - Pull/rollback reject unsafe paths, duplicate paths, checksum mismatches, symlink parents, and file/directory replacement hazards before mutation.
290
+ - Unmanaged local/remote files remain preserved.
291
+ - A live local lock disables destructive work. Stale/unreadable lock recovery retains process-liveness and guard checks.
292
+ - Force operations remain explicit direct/conflict-recovery actions and retain exact confirmations.
293
+ - Terminal-bound config, remote metadata, machine names, paths, and errors are control-character sanitized.
294
+
295
+ ## β™Ώ Conditional terminal accessibility smoke
296
+
297
+ Pi exposes terminal components rather than a semantic/ARIA tree, so automated tests verify textual state, keyboard paths, control-character escaping, and 32/60/100-column fitting rather than claiming semantic announcements. When validating a release in a supported environment:
298
+
299
+ 1. open `/sync` using only the keyboard and verify arrows, Enter, Space, search typing, paging where available, Back, and Escape;
300
+ 2. enter a Unicode target/profile name with an IME and confirm focus returns to Pi after closing;
301
+ 3. check the main, warning, preview, saved, cancelled, and invalid states with the terminal screen reader in use;
302
+ 4. repeat under a light and dark Pi theme and at 32, 60, and 100 columns.
303
+
304
+ Critical meaning is always present in text such as `(current)`, `Warning`, `Invalid`, `Saved`, `Cancelled`, or `Applied`; color is supplementary.
213
305
 
214
306
  ## πŸ—‚οΈ Package layout
215
307
 
216
- ```txt
308
+ ```text
217
309
  extensions/pi-sync/
218
310
  β”œβ”€β”€ src/
219
- β”‚ β”œβ”€β”€ index.ts # Pi package entrypoint
220
- β”‚ β”œβ”€β”€ sync.ts # Extension registration and command orchestration
221
- β”‚ β”œβ”€β”€ file-selection.ts # Persistent SettingsList selector
222
- β”‚ β”œβ”€β”€ sync-policy.ts # Shared built-in catalog and path policy
223
- β”‚ └── *.ts # Package-local config, snapshot, path, and S3 modules
311
+ β”‚ β”œβ”€β”€ index.ts # Thin Pi entrypoint
312
+ β”‚ β”œβ”€β”€ sync.ts # Command and lifecycle orchestration
313
+ β”‚ β”œβ”€β”€ manager-ui.ts # Goal-oriented menus and setup/management flows
314
+ β”‚ β”œβ”€β”€ file-selection.ts # Transactional synced-content editor
315
+ β”‚ β”œβ”€β”€ settings-management.ts # Profiles, targets, migration, and atomic saves
316
+ β”‚ β”œβ”€β”€ snapshot-transaction.ts # Local apply journal, rollback, and recovery
317
+ β”‚ └── *.ts # Config, policy, snapshot, S3, lock, and format modules
318
+ β”œβ”€β”€ test/
224
319
  β”œβ”€β”€ README.md
225
320
  β”œβ”€β”€ LICENSE
226
321
  β”œβ”€β”€ tsconfig.json
227
322
  └── package.json
228
323
  ```
229
324
 
230
- `index.ts` is the Pi entrypoint and forwards to `sync.ts`; the other source modules are internal. The package exposes its Pi extension through `package.json`:
231
-
232
- ```json
233
- {
234
- "pi": {
235
- "extensions": ["./src/index.ts"]
236
- }
237
- }
238
- ```
239
-
240
325
  ## πŸ”Ž Keywords
241
326
 
242
- Pi extension, Pi coding agent, settings sync, Cloudflare R2, S3-compatible storage, snapshot sync, dotfiles sync.
327
+ Pi extension, Pi coding agent, settings sync, Cloudflare R2, S3-compatible storage, multi-profile sync, sync targets, snapshot sync, dotfiles sync.
243
328
 
244
329
  ## πŸ“„ License
245
330
 
246
- MIT. See [`LICENSE`](./LICENSE).
331
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-sync",
3
- "version": "0.28.0",
3
+ "version": "0.29.1",
4
4
  "description": "Pi extension that syncs Pi configuration through Cloudflare R2 or S3-compatible storage.",
5
5
  "type": "module",
6
6
  "license": "MIT",