@narumitw/pi-sync 0.49.13 → 0.49.15

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 (56) hide show
  1. package/README.md +98 -238
  2. package/dist/chunks/{chunk-LONPFFNP.ts → chunk-44LPGQTI.ts} +4 -4
  3. package/dist/chunks/{chunk-FR6CVXAL.ts → chunk-5LZALPCD.ts} +4 -4
  4. package/dist/chunks/{chunk-APZVBSM6.ts → chunk-5T442OP3.ts} +18 -6
  5. package/dist/chunks/chunk-5T442OP3.ts.map +7 -0
  6. package/dist/chunks/{chunk-5WJNWFTG.ts → chunk-ARN2672F.ts} +5 -5
  7. package/dist/chunks/{chunk-W64NEHXT.ts → chunk-C7LXZ3UH.ts} +3 -3
  8. package/dist/chunks/{chunk-4F3W4CSC.ts → chunk-DFXBKHGN.ts} +3 -2
  9. package/dist/chunks/chunk-DFXBKHGN.ts.map +7 -0
  10. package/dist/chunks/{chunk-JNOOSPEW.ts → chunk-E4COSV32.ts} +3 -3
  11. package/dist/chunks/{chunk-BHJUISB4.ts → chunk-GLQLN5GV.ts} +3 -3
  12. package/dist/chunks/{chunk-EWX3TPLQ.ts → chunk-HVUSHMVL.ts} +2 -2
  13. package/dist/chunks/{chunk-RGJ6SOYR.ts → chunk-JF6MZCT4.ts} +2 -2
  14. package/dist/chunks/{chunk-LJDSHNS3.ts → chunk-WSOBYHIZ.ts} +2 -2
  15. package/dist/chunks/{chunk-3NDD5QPM.ts → chunk-X7SQ2EQM.ts} +4 -4
  16. package/dist/chunks/{file-selection-4UABOP44.ts → file-selection-RKEHALNQ.ts} +3 -3
  17. package/dist/chunks/manager-result-dispatcher-LWPYJAD5.ts +18 -0
  18. package/dist/chunks/{manager-ui-QO2P4V3Z.ts → manager-ui-NF4RUHBY.ts} +42 -14
  19. package/dist/chunks/manager-ui-NF4RUHBY.ts.map +7 -0
  20. package/dist/chunks/{remote-selection-ui-LHSAGKGA.ts → remote-selection-ui-NUF5P4ZJ.ts} +8 -8
  21. package/dist/chunks/{s3-backend-3QZGO2EF.ts → s3-backend-552LXHE4.ts} +2 -2
  22. package/dist/chunks/{setup-switch-X3W3FALZ.ts → setup-switch-5YDG5YGJ.ts} +6 -6
  23. package/dist/chunks/{snapshot-WBSV4MQG.ts → snapshot-2RYBUDRA.ts} +3 -3
  24. package/dist/chunks/{sync-operations-MPAFVVHA.ts → sync-operations-3LLIHWZO.ts} +15 -13
  25. package/dist/chunks/sync-operations-3LLIHWZO.ts.map +7 -0
  26. package/dist/chunks/{sync-state-36H7ULG2.ts → sync-state-SRN6YRBK.ts} +4 -4
  27. package/dist/index.ts +15 -15
  28. package/docs/settings.md +187 -0
  29. package/package.json +7 -9
  30. package/src/config.ts +16 -3
  31. package/src/settings-management.ts +1 -0
  32. package/src/settings-ui.ts +29 -2
  33. package/src/sync-operations.ts +7 -5
  34. package/src/types.ts +2 -0
  35. package/dist/chunks/chunk-4F3W4CSC.ts.map +0 -7
  36. package/dist/chunks/chunk-APZVBSM6.ts.map +0 -7
  37. package/dist/chunks/manager-result-dispatcher-RNRQA3PW.ts +0 -18
  38. package/dist/chunks/manager-ui-QO2P4V3Z.ts.map +0 -7
  39. package/dist/chunks/sync-operations-MPAFVVHA.ts.map +0 -7
  40. /package/dist/chunks/{chunk-LONPFFNP.ts.map → chunk-44LPGQTI.ts.map} +0 -0
  41. /package/dist/chunks/{chunk-FR6CVXAL.ts.map → chunk-5LZALPCD.ts.map} +0 -0
  42. /package/dist/chunks/{chunk-5WJNWFTG.ts.map → chunk-ARN2672F.ts.map} +0 -0
  43. /package/dist/chunks/{chunk-W64NEHXT.ts.map → chunk-C7LXZ3UH.ts.map} +0 -0
  44. /package/dist/chunks/{chunk-JNOOSPEW.ts.map → chunk-E4COSV32.ts.map} +0 -0
  45. /package/dist/chunks/{chunk-BHJUISB4.ts.map → chunk-GLQLN5GV.ts.map} +0 -0
  46. /package/dist/chunks/{chunk-EWX3TPLQ.ts.map → chunk-HVUSHMVL.ts.map} +0 -0
  47. /package/dist/chunks/{chunk-RGJ6SOYR.ts.map → chunk-JF6MZCT4.ts.map} +0 -0
  48. /package/dist/chunks/{chunk-LJDSHNS3.ts.map → chunk-WSOBYHIZ.ts.map} +0 -0
  49. /package/dist/chunks/{chunk-3NDD5QPM.ts.map → chunk-X7SQ2EQM.ts.map} +0 -0
  50. /package/dist/chunks/{file-selection-4UABOP44.ts.map → file-selection-RKEHALNQ.ts.map} +0 -0
  51. /package/dist/chunks/{manager-result-dispatcher-RNRQA3PW.ts.map → manager-result-dispatcher-LWPYJAD5.ts.map} +0 -0
  52. /package/dist/chunks/{remote-selection-ui-LHSAGKGA.ts.map → remote-selection-ui-NUF5P4ZJ.ts.map} +0 -0
  53. /package/dist/chunks/{s3-backend-3QZGO2EF.ts.map → s3-backend-552LXHE4.ts.map} +0 -0
  54. /package/dist/chunks/{setup-switch-X3W3FALZ.ts.map → setup-switch-5YDG5YGJ.ts.map} +0 -0
  55. /package/dist/chunks/{snapshot-WBSV4MQG.ts.map → snapshot-2RYBUDRA.ts.map} +0 -0
  56. /package/dist/chunks/{sync-state-36H7ULG2.ts.map → sync-state-SRN6YRBK.ts.map} +0 -0
package/README.md CHANGED
@@ -2,19 +2,18 @@
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
- Sync selected Pi settings and content across machines through Git, WebDAV, Cloudflare R2, or another S3-compatible store.
6
-
7
- Reusable storage connections hold credentials, while named sync setups define the exact remote path, included content, and automatic-sync behavior.
5
+ Pi Sync synchronizes selected Pi settings and content across machines through Git, WebDAV, Cloudflare R2, or another S3-compatible store.
6
+ Reusable storage connections hold credentials, while named sync setups define exactly what to sync, where to store it, and whether to sync automatically.
8
7
 
9
8
  ## ✨ Features
10
9
 
11
- - Manages Git, WebDAV, Cloudflare R2, and general S3-compatible backends through `/sync`.
12
- - Separates reusable storage connections from named sync setups and exact reviewed remote paths.
13
- - Selects Pi roots, safe agent-relative paths, and the privacy-sensitive sessions root through one ordered include list.
10
+ - Manages Git, WebDAV, Cloudflare R2, and general S3-compatible storage through `/sync`.
11
+ - Separates reusable storage connections from named setups with exact reviewed remote paths.
12
+ - Uses one ordered list for Pi roots, safe agent-relative paths, and privacy-sensitive sessions.
14
13
  - Protects changes with immutable snapshots, secret scanning, locks, conflict checks, pull backups, transactional apply, and recovery journals.
15
- - Keeps snapshot selection portable and credential-free across environments.
14
+ - Keeps snapshot selection portable and free of credentials.
16
15
  - Writes settings atomically, preserves unknown fields, rejects stale edits, and fails closed on unsafe configuration.
17
- - Loads a generated split runtime while preserving lazy UI and backend chunks.
16
+ - Loads a generated split runtime with lazy UI and backend chunks.
18
17
 
19
18
  ## 📦 Install
20
19
 
@@ -35,19 +34,21 @@ npm --workspace @narumitw/pi-sync run build
35
34
  pi -e ./packages/pi-sync
36
35
  ```
37
36
 
38
- The package declares `dist/index.ts`, so the build command must finish before Pi loads the local package directory.
37
+ The package declares `dist/index.ts`, so an unbuilt checkout must run the build before Pi loads the package directory.
39
38
 
40
- An unbuilt checkout still declares `dist/index.ts`, but the generated entrypoint and chunks do not exist until the build completes.
39
+ Extensions run with Pi's permissions, so install only packages from sources you trust.
41
40
 
42
41
  ## 🚀 Quick start
43
42
 
44
- Run `/sync`, choose **Set up sync**, and review the storage connection, exact remote path, included content, automatic-sync choice, and masked credentials before saving.
43
+ Run `/sync` and choose **Set up sync**.
44
+ Before saving, review the storage connection, exact remote path, included content, automatic-sync choice, and masked credentials.
45
+
45
46
  Buckets and remote repositories must already exist.
46
47
  Git uses existing non-interactive SSH or credential-helper configuration and never stores Git credentials.
47
48
 
48
49
  ## 🧭 Manager, conflicts, and recovery
49
50
 
50
- The manager shows local state without contacting remote storage:
51
+ The `/sync` manager shows local state without contacting remote storage:
51
52
 
52
53
  ```text
53
54
  Current sync setup: home
@@ -58,33 +59,38 @@ Remote status: Not checked
58
59
  ```
59
60
 
60
61
  Primary actions include **Sync now**, **Switch sync setup**, **Status & changes**, **Settings**, and **More…**.
61
- When pi-sync has already detected a synced-content-list mismatch, the manager instead shows **Sync status: Review needed**, puts **Review synced content (recommended)** first, and keeps **Sync now** visibly unavailable until the choice is reviewed.
62
- Opening the manager still performs no new remote request.
63
- The standard manager also owns setup and connection lists/details plus history and recovery.
64
- Secondary screens provide **Back**; Escape goes back and Ctrl+C closes the flow.
65
- Specialized operation and masked-credential prompts show the effective cancellation bindings and retain Ctrl+C as a hard-cancel input when Back is remapped.
66
- Destructive, credential-bearing, and externally visible operations retain exact specialized previews and confirmations.
62
+
63
+ After Pi Sync detects an included-content mismatch, the manager shows **Sync status: Review needed** and puts **Review synced content (recommended)** first.
64
+ **Sync now** remains unavailable until you review the mismatch.
65
+ Opening the manager does not make another remote request.
66
+ The manager also provides setup and connection details, history, and recovery.
67
+
68
+ On secondary screens, **Back** and Escape return to the previous screen, while Ctrl+C closes the flow.
69
+ Specialized operation and masked-credential prompts show the effective cancellation bindings and keep Ctrl+C as a hard-cancel input when Back is remapped.
70
+ Destructive, credential-bearing, and externally visible operations show exact previews and confirmations.
67
71
 
68
72
  ### Restore sync access
69
73
 
70
- When an operation is currently running, the manager shows its command and process ID, keeps sync and settings changes unavailable, and puts **Refresh operation status** first.
71
- When lock metadata is still protected by an active guard, the manager explains that another pi-sync process may be starting or finishing and asks the user to wait before refreshing.
74
+ While an operation is running, the manager shows its command and process ID, disables sync and settings changes, and puts **Refresh operation status** first.
75
+ When an active guard still protects lock metadata, the manager asks you to wait because another Pi Sync process may be starting or finishing.
72
76
  It never offers lock removal while an owner or guard may still be active.
73
77
 
74
- When a previous operation appears to have stopped without releasing its local lock, the manager shows **Sync paused** and puts **Restore sync access… (recommended)** first instead of hiding recovery inside **History & recovery…**.
75
- Unreadable metadata receives a stronger warning because pi-sync cannot verify who owns it.
76
- Close every other Pi session that may still be syncing, review the local-lock-only confirmation, then choose **Remove local lock and continue**.
77
- The guarded recovery path rechecks metadata and ownership before removal, changes no settings, local files, sync state, or remote data, and returns directly to the normal manager when access is restored.
78
+ If a stopped operation leaves its local lock behind, the manager shows **Sync paused** and puts **Restore sync access… (recommended)** first.
79
+ Unreadable metadata receives a stronger warning because Pi Sync cannot verify its owner.
80
+ Close every other Pi session that may still be syncing, review the local-lock-only confirmation, and choose **Remove local lock and continue**.
81
+ Before removal, the guarded recovery path rechecks metadata and ownership.
82
+
83
+ Successful recovery changes no settings, local files, sync state, or remote data and returns to the normal manager.
78
84
  Cancellation leaves the lock unchanged.
79
- If ownership changes or a guard is still expiring, pi-sync refuses removal and keeps actionable status available for refresh or retry.
80
- The deterministic fallback remains `/sync unlock --stale` after the same no-other-sync verification.
85
+ If ownership changes or a guard is still expiring, Pi Sync refuses removal and keeps refresh or retry available.
86
+ After the same no-other-sync verification, `/sync unlock --stale` provides a deterministic fallback.
81
87
 
82
88
  ### Resolve conflicts in the manager
83
89
 
84
- When **Sync now**, **Pull from remote…**, or **Push to remote…** finds changes that require a human direction choice, the manager opens **Resolve sync conflict** instead of ending at a command-only error.
85
- The flow names the current sync setup and explains whether local content, remote content, or the included-content policy changed.
90
+ When **Sync now**, **Pull from remote…**, or **Push to remote…** requires a direction choice, the manager opens **Resolve sync conflict**.
91
+ The flow names the current setup and explains whether local content, remote content, or the included-content policy changed.
86
92
 
87
- An explicit remote content-list mismatch now opens **Synced content differs** inside the same manager flow.
93
+ An explicit remote content-list mismatch opens **Synced content differs** in the same manager flow.
88
94
  Nothing changes until you choose an action.
89
95
  Choose **Review all paths (recommended)** first to compare exact remote-only paths, device-only paths, and both ordered lists.
90
96
  If membership matches but order differs, the review says that only ordering differs.
@@ -102,7 +108,7 @@ Choose **Review differences (recommended)** in an ordinary file-direction confli
102
108
  Then choose one reviewed direction:
103
109
 
104
110
  - **Keep local content and replace remote…** uses the existing forced-push path.
105
- It scans managed local files for secrets, shows the exact remote publication effect, re-reads a changed remote head, and asks again if the reviewed plan changed.
111
+ It applies the [secret-scan setting](./docs/settings.md#secret-scanning), shows the exact remote publication effect, re-reads a changed remote head, and asks again if the reviewed plan changed.
106
112
  - **Use remote content and replace local…** uses the existing forced-pull path.
107
113
  It shows exact local writes and deletions, protects the live session, and creates a local backup before applying.
108
114
  - First sync uses **Use local as initial source…** and **Use remote as initial source…** labels.
@@ -119,69 +125,33 @@ It clears after verified resolution, invalidation, session replacement, or shutd
119
125
  Interactive TUI `/sync sync`, `/sync pull`, and `/sync push` routes without `--yes` open the same review flow when they detect the mismatch.
120
126
  Explicit `--yes` routes remain non-interactive and report exact remote-only, device-only, or order-only guidance while leaving visible attention for later review.
121
127
  Shutdown automatic sync never opens a dialog because Pi is exiting.
122
- RPC startup and direct routes remain read-only and notification-based, while print and JSON modes remain unsupported for `/sync` because their UI output is not observable.
128
+ RPC startup mismatch review remains read-only and notification-based.
129
+ Print and JSON modes do not support `/sync` because UI output is not observable there.
123
130
 
124
131
  ## ⚙️ Settings
125
132
 
126
- The canonical private user file is:
133
+ Run `/sync` → **Set up sync** to create the canonical private user file at `<getAgentDir()>/pi-sync.json` (normally `~/.pi/agent/pi-sync.json`).
134
+ Use **Settings** to manage an existing setup.
135
+ Missing settings stay unconfigured without creating files or locks.
136
+ **Settings → Skip secret scan** defaults to **Off**; enable it only after reviewing the destination and selected content because it disables push scanning for every setup.
137
+ See [Secret scanning](./docs/settings.md#secret-scanning) for the setting and diagnostic behavior.
127
138
 
128
- ```text
129
- ~/.pi/agent/pi-sync.json
130
- ```
131
-
132
- Pi's configured agent directory replaces `~/.pi/agent` when applicable.
133
- Missing settings load as unconfigured without creating an agent directory, file, temporary, or lock.
134
- An explicit setup creates the file atomically.
135
- On POSIX, pi-sync creates and replaces it with mode `0600`.
136
- Pi-sync processes coordinate settings access through `pi-sync.json.mutation-lock`; lock-unaware editors are outside that serialization boundary and should not save the file while a pi-sync settings operation is running.
137
- Credentials stay in this canonical private file and are never shown in menus, reviews, status, notifications, errors, logs, or completion metadata.
138
-
139
- An existing private `pi-sync.local.json` containing a valid version 3 document is copied byte-for-byte to `pi-sync.json`; the old file remains as a recovery copy.
140
- If both paths exist, `pi-sync.json` wins and the other path remains untouched.
141
-
142
- ### Complete version 3 example
139
+ A minimal Git setup uses an existing private remote and keeps automatic sync off:
143
140
 
144
141
  ```json
145
142
  {
146
143
  "version": 3,
147
144
  "activeSyncSetup": "home",
148
145
  "onSwitch": "ask-before-pull",
146
+ "skipSecretScan": false,
149
147
  "storageConnections": {
150
- "r2": {
151
- "type": "s3",
152
- "endpoint": "https://example.r2.cloudflarestorage.com",
153
- "region": "auto",
154
- "credentials": {
155
- "accessKeyId": "<access-key-id>",
156
- "secretAccessKey": "<secret-access-key>"
157
- }
158
- },
159
148
  "github": {
160
149
  "type": "git",
161
150
  "remote": "git@github.com:owner/private-pi-sync.git"
162
- },
163
- "nextcloud": {
164
- "type": "webdav",
165
- "url": "https://cloud.example.com/remote.php/dav/files/user",
166
- "credentials": {
167
- "username": "user",
168
- "password": "<app-password>"
169
- }
170
151
  }
171
152
  },
172
153
  "syncSetups": {
173
154
  "home": {
174
- "storage": {
175
- "connection": "r2",
176
- "bucket": "personal-pi",
177
- "path": "pi-sync/home"
178
- },
179
- "sync": {
180
- "include": ["settings.json", "AGENTS.md", "skills", "prompts", "themes"],
181
- "automatic": true
182
- }
183
- },
184
- "git-backup": {
185
155
  "storage": {
186
156
  "connection": "github",
187
157
  "branch": "pi-sync/home",
@@ -191,136 +161,53 @@ If both paths exist, `pi-sync.json` wins and the other path remains untouched.
191
161
  "include": ["settings.json", "AGENTS.md"],
192
162
  "automatic": false
193
163
  }
194
- },
195
- "webdav-backup": {
196
- "storage": {
197
- "connection": "nextcloud",
198
- "path": "pi-sync/home"
199
- },
200
- "sync": {
201
- "include": ["settings.json", "sessions"],
202
- "automatic": false
203
- }
204
164
  }
205
165
  }
206
166
  }
207
167
  ```
208
168
 
209
- Cloudflare R2 is persisted as `"type": "s3"`; R2 is a setup preset, not another schema type.
210
- Temporary S3 credentials may additionally include `credentials.sessionToken`.
211
-
212
- ### Required backend shapes
213
-
214
- - **S3/R2 connection:** `type`, `endpoint`, `region`, and `credentials.accessKeyId` / `credentials.secretAccessKey`.
215
- - **S3/R2 setup storage:** `connection`, `bucket`, and complete relative `path`; `branch` is rejected.
216
- - **Git connection:** `type` and a credential-free SSH or HTTPS `remote`.
217
- - **Git setup storage:** `connection`, `branch`, and complete repository `path`; `bucket` is rejected.
218
- - **WebDAV connection:** `type`, HTTPS `url`, and `credentials.username` / `credentials.password`.
219
- - **WebDAV setup storage:** `connection` and complete relative `path`; `bucket` and `branch` are rejected.
220
-
221
- Every setup requires `sync.include` and explicit `sync.automatic`.
222
- `activeSyncSetup` must reference an own-property setup when any setups exist and must be absent when the setup catalog is empty.
223
- A referenced connection cannot be removed.
224
- The current setup must be switched before removal.
225
- Two setups cannot resolve to the same normalized backend location.
226
-
227
- `onSwitch` accepts:
228
-
229
- - `ask-before-pull` — switch, then ask in TUI whether to start a reviewed pull;
230
- - `pull-after-switch` — require observable UI and start the normal reviewed pull;
231
- - `switch-only` — switch without reading or applying remote content.
232
-
233
- ### Included content
234
-
235
- `sync.include` is ordered and duplicate-free.
236
- Supported Pi roots are:
237
-
238
- ```text
239
- settings.json, keybindings.json, models.json, AGENTS.md, APPEND_SYSTEM.md,
240
- skills, prompts, themes, extensions, sessions
241
- ```
242
-
243
- Safe agent-relative custom files or directories may also be included.
244
- Absolute paths, `..`, backslashes, controls, denied secret/settings paths, duplicate case variants, and ambiguous nested paths under reserved roots are rejected.
245
-
246
- An empty array is valid.
247
- It means no useful transfer is selected: **Sync now** reports the condition and does not claim that the setup is up to date.
248
- Unselected content remains unmanaged locally and is preserved when republishing existing remote snapshots.
249
-
250
- The Included Content editor is a standard bounded multi-select backed by one in-memory draft.
251
- Toggles never write settings.
252
- **Add custom path…** accepts a safe agent-relative file or directory even when it does not exist locally yet, allowing a new environment to select content that exists only in the remote snapshot.
253
- Leaving the editor opens an exact Include/Exclude review with **Save changes**, **Discard changes**, and **Continue editing**; only reviewed Save publishes, while Continue preserves the draft and Discard/cancellation preserves the settings bytes.
254
- RPC remains a read-only summary with the manual `sync.include` path.
255
-
256
- Every new snapshot stores the normalized included-content selection separately from the files that happened to exist.
257
- This preserves selected-but-missing paths without syncing `pi-sync.json`, storage credentials, automatic-sync preferences, or setup names.
258
- **Settings → Compare synced content** opens the same review-first flow as a manager operation when local and remote lists differ.
259
- Adoption revalidates the remote head, immutable snapshot, reviewed storage coordinates, and local include list before one atomic settings update.
260
- The saved state changes only `sync.include`, preserves unknown settings fields, and never pulls files or writes sync state.
261
- Its explicit Continue action starts a fresh **Sync now** route, while **Done** leaves the reviewed settings change saved without implying a file operation.
262
- Keeping this device's list opens the reviewed force-push path and explicitly replaces the remote policy while preserving eligible unmanaged remote files.
263
-
264
- Automatic sync and pull, including forced pull, pause on an explicit remote-policy difference rather than silently expanding local scope.
265
- Status reports matches and exact local-only/remote-only paths.
266
- Old snapshots remain readable.
267
- Because they have no authoritative selection, pi-sync offers only a clearly labeled read-only partial discovery from safe remote file roots; selected-but-missing and preserved-unmanaged intent cannot be reconstructed.
268
- Use **Add custom path…** for any needed path.
269
-
270
- Adding `sessions` requires a privacy acknowledgement in interactive flows.
271
- Session JSONL can contain prompts, tool output, file paths, images, and secrets.
272
- Automatic apply protects the currently open session file; restart Pi or resume a pulled session to use newly synchronized conversations.
273
-
274
- ### Unsupported old settings and recovery
275
-
276
- Version 1, version 2, and non-empty unversioned documents are intentionally unsupported by this breaking schema reset. pi-sync does **not** migrate, partially interpret, downgrade, or overwrite them.
277
- Automatic sync pauses and reports an actionable version 3 error without displaying secrets.
278
-
279
- Recovery:
280
-
281
- 1. retain the old file byte-for-byte;
282
- 2. move it aside manually;
283
- 3. create a new version 3 document or run the setup manager;
284
- 4. run `/sync doctor`, inspect the exact storage path, and review the first pull or push;
285
- 5. restore the retained file and a compatible older package only if rolling back.
286
-
169
+ Setup saves are atomic and private (`0600` on POSIX), preserve unknown fields, and coordinate across pi-sync processes.
170
+ Do not save from a lock-unaware editor during a pi-sync settings operation.
287
171
  Malformed, invalid, unsupported, symlinked, or concurrently changed documents remain untouched.
288
- Failed UI saves keep the previous file and displayed/effective state.
289
-
290
- ## 💬 Commands
291
-
292
- The menu is preferred, while deterministic routes remain available:
172
+ Version 1, version 2, and non-empty unversioned settings require manual recovery rather than automatic migration.
293
173
 
294
- ```text
295
- /sync help
296
- /sync use <setup>
297
- /sync init
298
- /sync config [--setup <name>]
299
- /sync files [--setup <name>]
300
- /sync status [--setup <name>]
301
- /sync diff [--setup <name>]
302
- /sync doctor [--setup <name>]
303
- /sync push [--setup <name>]
304
- /sync pull [--setup <name>]
305
- /sync sync [--setup <name>]
306
- /sync history [--setup <name>]
307
- /sync rollback <snapshot-id> [--setup <name>]
308
- /sync migrate-state [--yes]
309
- /sync unlock --stale
310
- ```
174
+ Adding `sessions` can upload prompts, tool output, paths, images, and secrets; interactive flows require a privacy acknowledgement.
175
+ Automatic sync and pull pause when the remote included-content policy differs instead of silently expanding local scope.
311
176
 
312
- - `--setup <name>` addresses a setup without switching it.
313
- - `--yes` / `-y` skips a direct route's confirmation.
314
- - `--force` accepts a reviewed content conflict but never disables backend concurrency protection.
315
- - `--stale` requests guarded stale-lock recovery.
177
+ Read the [settings reference](./docs/settings.md) for complete S3/R2, Git, and WebDAV examples, backend fields, included-content rules, legacy paths, and recovery steps.
316
178
 
317
- The former version 2 setup-addressing flag is rejected.
318
- Unknown flags, unknown commands, trailing values, and missing setup/snapshot values are rejected.
319
- Completion includes known setup names and preserves preceding command tokens.
179
+ ## 💬 Commands
320
180
 
321
- TUI mode uses standard manager, settings, resource, and included-content screens plus specialized secret, wizard, confirmation, and commit-aware loader components.
322
- RPC uses Pi's supported dialog/notification protocol and does not gain settings mutation.
323
- Print and JSON modes never enter TUI-only screens; unsupported interactive routes reject or remain protocol-safe rather than relying on no-op output.
181
+ | Command | Purpose |
182
+ | --- | --- |
183
+ | `/sync` | Set up storage, manage synced content, and review sync operations or recovery. |
184
+ | `/sync help` | Show command usage. |
185
+ | `/sync use <setup>` | Switch the active local sync setup, following its switch policy. |
186
+ | `/sync init` | Create a local configuration template. |
187
+ | `/sync config` | Show resolved configuration. |
188
+ | `/sync files` | List included local files. |
189
+ | `/sync status` | Compare local and remote snapshot state. |
190
+ | `/sync diff` | Show local and remote differences. |
191
+ | `/sync doctor` | Check configuration, connectivity, and backend safety. |
192
+ | `/sync push` | Publish local content to remote storage. |
193
+ | `/sync pull` | Back up local content, then apply the remote snapshot. |
194
+ | `/sync sync` | Choose a safe sync direction or require conflict review. |
195
+ | `/sync history` | Browse remote snapshots and review a rollback. |
196
+ | `/sync rollback <snapshot-id>` | Back up local content, apply a historical snapshot, and republish it remotely. |
197
+ | `/sync migrate-state` | Migrate the legacy local state directory. |
198
+ | `/sync unlock --stale` | Recover an abandoned local lock after guarded ownership checks. |
199
+
200
+ All routes support TUI and RPC; RPC settings and included-content screens are read-only.
201
+ Print and JSON modes reject `/sync`.
202
+ Unknown commands or flags, trailing values, and missing setup/snapshot values are rejected, including the former version 2 setup-addressing flag.
203
+
204
+ - `--setup <name>` targets a setup without switching it on `config`, `files`, `status`, `diff`, `doctor`, `push`, `pull`, `sync`, `history`, and `rollback`.
205
+ - `--yes` (alias: `-y`) skips confirmation on `push`, `pull`, `sync`, `rollback`, and `migrate-state`; use only after reviewing the affected content.
206
+ - `--force` lets `push` or `pull` accept content conflicts without disabling backend concurrency protection. It is also accepted by `sync`, which still requires a direction choice for divergent content, and by `rollback`, where it has no additional effect.
207
+ - `--stale` is accepted only by `unlock` and is required to remove a stale lock.
208
+
209
+ Push and rollback publish data externally; pull and rollback can replace or delete local managed files.
210
+ Review [Settings](#-settings) for included-content privacy and [Manager, conflicts, and recovery](#-manager-conflicts-and-recovery) before forcing a direction, skipping confirmation, or removing a lock.
324
211
 
325
212
  ## 🔄 Backend and recovery model
326
213
 
@@ -357,7 +244,7 @@ Preserve both roots before manual recovery; with every Pi process closed and no
357
244
  ## 🔒 Security and privacy
358
245
 
359
246
  - Canonical, legacy, temporary, and recovery settings paths are denied from snapshots; both `pi-sync/` and `.pisync/` state roots are permanently denied.
360
- - Push scans managed local content for common secret patterns.
247
+ - Push scans managed local content for common secret patterns unless the user explicitly enables **Skip secret scan**; `/sync doctor` always retains the diagnostic scan.
361
248
  - Remote snapshot references, checksums, paths, manifests, response sizes, and publication revisions are validated.
362
249
  - Symlink parents, path escapes, duplicate paths, and unsafe file/directory replacement fail before local mutation.
363
250
  - Live locks block mutation; stale recovery rechecks process and guard ownership.
@@ -365,59 +252,32 @@ Preserve both roots before manual recovery; with every Pi process closed and no
365
252
  Publication/apply commit boundaries finish with bounded signals and report ambiguous outcomes explicitly.
366
253
  - Terminal-bound names, paths, metadata, and errors are control-character sanitized.
367
254
 
368
- ## ♿ Conditional terminal accessibility smoke
255
+ ## ♿ Terminal accessibility
369
256
 
370
- Pi exposes terminal components rather than a semantic/ARIA tree.
371
- Release validation checks textual state, keyboard operation, Escape/Back, control escaping, and narrow rendering.
372
- Critical meaning appears in text such as `(current)`, `Review needed`, `Later`, `Warning`, `Invalid`, `Saved`, `Cancelled`, and `Applied`; color is supplementary.
373
- The attention widget is informational and does not pretend to be a clickable control.
257
+ Pi exposes terminal components rather than a semantic or ARIA tree.
258
+ Release checks cover textual state, keyboard operation, Escape and Back behavior, control escaping, and narrow rendering.
259
+ Critical meaning appears in text such as `(current)`, `Review needed`, `Later`, `Warning`, `Invalid`, `Saved`, `Cancelled`, and `Applied`.
260
+ Color is supplementary, and the attention widget is informational rather than interactive.
374
261
 
375
262
  ## 🗂️ Package layout
376
263
 
377
264
  ```text
378
265
  packages/pi-sync/
379
- ├── dist/ # Generated split TypeScript runtime loaded through Pi's Jiti loader
380
- ├── scripts/
381
- │ └── build-runtime.mjs # Deterministic bundler and eager-boundary validator
382
- ├── src/
383
- │ ├── index.ts
384
- │ ├── sync-extension.ts # Lightweight Pi entry runtime and cached lazy loaders
385
- │ ├── sync.ts # Compatibility barrel for package-local helpers
386
- │ ├── sync-errors.ts # Lightweight setup/decision error contracts
387
- │ ├── config.ts
388
- │ ├── config-file.ts
389
- │ ├── state-directory.ts
390
- │ ├── settings-management.ts
391
- │ ├── manager-ui.ts
392
- │ ├── manager-state.ts
393
- │ ├── manager-recovery.ts
394
- │ ├── operation-availability.ts
395
- │ ├── manager-attention.ts
396
- │ ├── sync-attention.ts
397
- │ ├── storage-connections-ui.ts
398
- │ ├── sync-setups-ui.ts
399
- │ ├── file-selection.ts
400
- │ ├── remote-selection-ui.ts
401
- │ ├── remote-snapshot.ts
402
- │ ├── sync-operations.ts
403
- │ ├── sync-backend.ts
404
- │ ├── backend-factory.ts # lazy selected-backend loader
405
- │ ├── s3-backend.ts
406
- │ ├── webdav-backend.ts
407
- │ ├── git-backend.ts
408
- │ ├── snapshot-paths.ts # Eager recovery-safe session path helpers
409
- │ ├── snapshot.ts # Loaded only for snapshot operations/session push
410
- │ └── *.ts
411
- ├── test/
412
- ├── README.md
413
- ├── LICENSE
414
- └── package.json
266
+ ├── src/ # Backends, settings, snapshots, and recovery modules
267
+ │ ├── index.ts # Thin Pi entrypoint
268
+ │ └── sync-extension.ts # Sync lifecycle and lazy loading
269
+ ├── dist/ # Generated Jiti runtime
270
+ ├── scripts/build-runtime.mjs # Runtime builder
271
+ ├── docs/ # Published reference documentation
272
+ └── test/ # Behavior and lifecycle coverage
415
273
  ```
416
274
 
275
+ The generated runtime is built from `src/index.ts` and does not import back into `src`.
276
+
417
277
  ## 🔎 Keywords
418
278
 
419
279
  Pi extension, Pi coding agent, settings sync, Git, WebDAV, Nextcloud, Cloudflare R2, S3-compatible storage, storage connections, sync setups, snapshot sync, dotfiles sync.
420
280
 
421
281
  ## 📄 License
422
282
 
423
- MIT
283
+ [MIT](./LICENSE)
@@ -3,15 +3,15 @@
3
3
  import {
4
4
  runCancellableOperation,
5
5
  showRemoteSelectionReview
6
- } from "./chunk-5WJNWFTG.ts";
6
+ } from "./chunk-ARN2672F.ts";
7
7
  import {
8
8
  errorMessage,
9
9
  safeTerminalText
10
- } from "./chunk-W64NEHXT.ts";
10
+ } from "./chunk-C7LXZ3UH.ts";
11
11
  import {
12
12
  loadConfig,
13
13
  syncConfigReviewIdentity
14
- } from "./chunk-APZVBSM6.ts";
14
+ } from "./chunk-5T442OP3.ts";
15
15
 
16
16
  // src/sync-resolution-ui.ts
17
17
  import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
@@ -210,4 +210,4 @@ async function dispatchManagerResult(ctx, initialResult, origin, runRoute, signa
210
210
  export {
211
211
  dispatchManagerResult
212
212
  };
213
- //# sourceMappingURL=chunk-LONPFFNP.ts.map
213
+ //# sourceMappingURL=chunk-44LPGQTI.ts.map
@@ -2,10 +2,10 @@
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
4
  safeTerminalText
5
- } from "./chunk-W64NEHXT.ts";
5
+ } from "./chunk-C7LXZ3UH.ts";
6
6
  import {
7
7
  syncConfigReviewFingerprint
8
- } from "./chunk-APZVBSM6.ts";
8
+ } from "./chunk-5T442OP3.ts";
9
9
  import {
10
10
  inspectRemoteSelection,
11
11
  remoteSelectionMismatch,
@@ -17,7 +17,7 @@ import {
17
17
  var createSyncBackend = async (config) => {
18
18
  switch (config.backend.type) {
19
19
  case "s3": {
20
- const { S3SyncBackend } = await import("./s3-backend-3QZGO2EF.ts");
20
+ const { S3SyncBackend } = await import("./s3-backend-552LXHE4.ts");
21
21
  return new S3SyncBackend(config.backend);
22
22
  }
23
23
  case "webdav": {
@@ -78,4 +78,4 @@ export {
78
78
  requireCompatibleRemoteSelection,
79
79
  formatRemoteSelectionStatus
80
80
  };
81
- //# sourceMappingURL=chunk-FR6CVXAL.ts.map
81
+ //# sourceMappingURL=chunk-5LZALPCD.ts.map
@@ -589,7 +589,14 @@ async function loadConfig(setupName) {
589
589
  `Invalid pi-sync settings: sync setup \u201C${selectedName}\u201D references missing storage connection \u201C${connectionName}\u201D.`
590
590
  );
591
591
  }
592
- return resolveSyncConfig(selectedName, setup, connectionName, connection, settings.onSwitch);
592
+ return resolveSyncConfig(
593
+ selectedName,
594
+ setup,
595
+ connectionName,
596
+ connection,
597
+ settings.onSwitch,
598
+ settings.skipSecretScan ?? false
599
+ );
593
600
  }
594
601
  async function loadPartialConfig(setupName) {
595
602
  const config = await loadConfig(setupName);
@@ -603,12 +610,12 @@ async function loadPartialConfig(setupName) {
603
610
  }
604
611
  function syncSetupStorageReview(setupName, setup, connectionName, connection) {
605
612
  return storageReviewFromConfig(
606
- resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH)
613
+ resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false)
607
614
  );
608
615
  }
609
616
  function syncSetupReviewIdentity(setupName, setup, connectionName, connection) {
610
617
  return syncConfigReviewIdentity(
611
- resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH)
618
+ resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false)
612
619
  );
613
620
  }
614
621
  function syncConfigReviewIdentity(config) {
@@ -646,7 +653,7 @@ function normalizeOnSwitch(value) {
646
653
  'Invalid pi-sync settings: onSwitch must be "ask-before-pull", "pull-after-switch", or "switch-only".'
647
654
  );
648
655
  }
649
- function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitch) {
656
+ function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitch, skipSecretScan) {
650
657
  const storagePath = normalizeStoragePath(setup.storage.path);
651
658
  const namespace = storagePath.slice(storagePath.lastIndexOf("/") + 1);
652
659
  const include = normalizeSyncInclude(setup.sync.include);
@@ -657,7 +664,8 @@ function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitc
657
664
  snapshotIdentity: namespace,
658
665
  include,
659
666
  automatic: setup.sync.automatic,
660
- onSwitch
667
+ onSwitch,
668
+ skipSecretScan
661
669
  };
662
670
  if (connection.type === "git") {
663
671
  return {
@@ -744,6 +752,9 @@ function validateSettingsDocument(value) {
744
752
  "top level"
745
753
  );
746
754
  normalizeOnSwitch(value.onSwitch);
755
+ if (value.skipSecretScan !== void 0 && typeof value.skipSecretScan !== "boolean") {
756
+ throw new Error("Invalid pi-sync settings: skipSecretScan must be boolean.");
757
+ }
747
758
  const storageConnections = requireNamedObjectMap(
748
759
  value.storageConnections,
749
760
  "storageConnections",
@@ -1123,6 +1134,7 @@ function localConfigTemplate() {
1123
1134
  return {
1124
1135
  version: 3,
1125
1136
  onSwitch: DEFAULT_ON_SWITCH,
1137
+ skipSecretScan: false,
1126
1138
  storageConnections: {},
1127
1139
  syncSetups: {}
1128
1140
  };
@@ -1235,4 +1247,4 @@ export {
1235
1247
  syncSessionsWarnings,
1236
1248
  isCloudflareR2Endpoint
1237
1249
  };
1238
- //# sourceMappingURL=chunk-APZVBSM6.ts.map
1250
+ //# sourceMappingURL=chunk-5T442OP3.ts.map