@narumitw/pi-sync 0.50.2 → 0.51.0

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 (218) hide show
  1. package/README.md +4 -1
  2. package/dist/chunks/{chunk-6LP3MCR2.ts → chunk-2JZFHNLM.ts} +3 -5
  3. package/dist/chunks/chunk-2JZFHNLM.ts.map +7 -0
  4. package/dist/chunks/{chunk-LCRI3ABW.ts → chunk-2RZSQWBH.ts} +8 -19
  5. package/dist/chunks/chunk-2RZSQWBH.ts.map +7 -0
  6. package/dist/chunks/{chunk-IJ6FJFA5.ts → chunk-4AIHXRKE.ts} +11 -17
  7. package/dist/chunks/chunk-4AIHXRKE.ts.map +7 -0
  8. package/dist/chunks/{chunk-C7XY5NF5.ts → chunk-4MPIH46N.ts} +29 -51
  9. package/dist/chunks/chunk-4MPIH46N.ts.map +7 -0
  10. package/dist/chunks/{chunk-5B3FXBLE.ts → chunk-6546TS54.ts} +16 -20
  11. package/dist/chunks/chunk-6546TS54.ts.map +7 -0
  12. package/dist/chunks/{chunk-Q2TUM5X6.ts → chunk-6NT3YFGY.ts} +2 -2
  13. package/dist/chunks/chunk-6NT3YFGY.ts.map +7 -0
  14. package/dist/chunks/{chunk-2B2ELUF5.ts → chunk-7D5SF7P3.ts} +1 -1
  15. package/dist/chunks/chunk-7D5SF7P3.ts.map +7 -0
  16. package/dist/chunks/{chunk-4CHTZMEO.ts → chunk-7KHKWL3Q.ts} +37 -55
  17. package/dist/chunks/chunk-7KHKWL3Q.ts.map +7 -0
  18. package/dist/chunks/{chunk-ZI5MXMEX.ts → chunk-BVVP33U5.ts} +2 -4
  19. package/dist/chunks/chunk-BVVP33U5.ts.map +7 -0
  20. package/dist/chunks/{chunk-SZWBGPID.ts → chunk-DM4X4FIA.ts} +8 -13
  21. package/dist/chunks/chunk-DM4X4FIA.ts.map +7 -0
  22. package/dist/chunks/{chunk-SL64RLXR.ts → chunk-DZUSTGQV.ts} +8 -16
  23. package/dist/chunks/chunk-DZUSTGQV.ts.map +7 -0
  24. package/dist/chunks/{chunk-G4RNQQZS.ts → chunk-E42GXD6D.ts} +13 -19
  25. package/dist/chunks/chunk-E42GXD6D.ts.map +7 -0
  26. package/dist/chunks/{chunk-Z4DQF6L3.ts → chunk-ECYCNZOE.ts} +1 -1
  27. package/dist/chunks/chunk-ECYCNZOE.ts.map +7 -0
  28. package/dist/chunks/{chunk-OEX4ABY6.ts → chunk-EEJPYTWE.ts} +11 -21
  29. package/dist/chunks/chunk-EEJPYTWE.ts.map +7 -0
  30. package/dist/chunks/{chunk-CPEHGXJV.ts → chunk-G4RUXRWZ.ts} +3 -5
  31. package/dist/chunks/chunk-G4RUXRWZ.ts.map +7 -0
  32. package/dist/chunks/{chunk-T2B5IU74.ts → chunk-HGJH56KR.ts} +4 -4
  33. package/dist/chunks/chunk-HGJH56KR.ts.map +7 -0
  34. package/dist/chunks/{chunk-JAAKLL6M.ts → chunk-IVXEDPAD.ts} +10 -34
  35. package/dist/chunks/chunk-IVXEDPAD.ts.map +7 -0
  36. package/dist/chunks/chunk-K3RIEL6J.ts +20 -0
  37. package/dist/chunks/chunk-K3RIEL6J.ts.map +7 -0
  38. package/dist/chunks/{chunk-LTBWGSBV.ts → chunk-MSSW6F3R.ts} +1 -1
  39. package/dist/chunks/chunk-MSSW6F3R.ts.map +7 -0
  40. package/dist/chunks/{chunk-T3HH3UEJ.ts → chunk-OVDTF5CH.ts} +4 -10
  41. package/dist/chunks/chunk-OVDTF5CH.ts.map +7 -0
  42. package/dist/chunks/{chunk-NJFUIVYL.ts → chunk-PWMWOJP4.ts} +1 -1
  43. package/dist/chunks/chunk-PWMWOJP4.ts.map +7 -0
  44. package/dist/chunks/{chunk-HUDQATCL.ts → chunk-Q32CQWPM.ts} +6 -8
  45. package/dist/chunks/chunk-Q32CQWPM.ts.map +7 -0
  46. package/dist/chunks/{chunk-ETTRJDNP.ts → chunk-QLSN7AGX.ts} +6 -13
  47. package/dist/chunks/chunk-QLSN7AGX.ts.map +7 -0
  48. package/dist/chunks/{chunk-THMF4BVL.ts → chunk-SWUXA3MH.ts} +1 -1
  49. package/dist/chunks/chunk-SWUXA3MH.ts.map +7 -0
  50. package/dist/chunks/{chunk-TEN2ZBJV.ts → chunk-T2SJD3N7.ts} +2 -4
  51. package/dist/chunks/chunk-T2SJD3N7.ts.map +7 -0
  52. package/dist/chunks/{chunk-AUSDLD6F.ts → chunk-T56RNRZJ.ts} +10 -26
  53. package/dist/chunks/chunk-T56RNRZJ.ts.map +7 -0
  54. package/dist/chunks/{chunk-KW7YQEZN.ts → chunk-UA7RXOIT.ts} +48 -100
  55. package/dist/chunks/chunk-UA7RXOIT.ts.map +7 -0
  56. package/dist/chunks/{chunk-D5X27LCA.ts → chunk-UETBKTDI.ts} +1 -1
  57. package/dist/chunks/chunk-UETBKTDI.ts.map +7 -0
  58. package/dist/chunks/{chunk-CHH26D4F.ts → chunk-YFADF5ZQ.ts} +7 -6
  59. package/dist/chunks/chunk-YFADF5ZQ.ts.map +7 -0
  60. package/dist/chunks/{chunk-CCSN3F47.ts → chunk-YPXGU5OR.ts} +4 -4
  61. package/dist/chunks/chunk-YPXGU5OR.ts.map +7 -0
  62. package/dist/chunks/{file-selection-FTTKAOYX.ts → file-selection-RAVHRYFG.ts} +14 -26
  63. package/dist/chunks/file-selection-RAVHRYFG.ts.map +7 -0
  64. package/dist/chunks/{git-backend-GMVS4PD4.ts → git-backend-IAANDL3K.ts} +41 -104
  65. package/dist/chunks/git-backend-IAANDL3K.ts.map +7 -0
  66. package/dist/chunks/manager-result-dispatcher-OTUMQM26.ts +23 -0
  67. package/dist/chunks/{manager-ui-5KLKVMRC.ts → manager-ui-763NEKJY.ts} +124 -251
  68. package/dist/chunks/manager-ui-763NEKJY.ts.map +7 -0
  69. package/dist/chunks/remote-selection-ui-TZTJF32Y.ts +22 -0
  70. package/dist/chunks/{s3-backend-66O4FGBH.ts → s3-backend-XGXUOPUN.ts} +23 -54
  71. package/dist/chunks/s3-backend-XGXUOPUN.ts.map +7 -0
  72. package/dist/chunks/{setup-switch-IS2PEZ3J.ts → setup-switch-5G2VNJHL.ts} +9 -9
  73. package/dist/chunks/{setup-wizard-IUB6ESEU.ts → setup-wizard-YIEWGX32.ts} +12 -12
  74. package/dist/chunks/setup-wizard-YIEWGX32.ts.map +7 -0
  75. package/dist/chunks/{snapshot-HKEWCVK6.ts → snapshot-JL5DFSXI.ts} +5 -5
  76. package/dist/chunks/sync-inspection-P3ENJLNF.ts +20 -0
  77. package/dist/chunks/{sync-operations-LBX3GKCM.ts → sync-operations-AR76EB6C.ts} +85 -201
  78. package/dist/chunks/sync-operations-AR76EB6C.ts.map +7 -0
  79. package/dist/chunks/{sync-state-Q2FTXTW6.ts → sync-state-VHJUZ4YG.ts} +6 -6
  80. package/dist/chunks/{webdav-backend-ECIKD7SO.ts → webdav-backend-3EGUONAT.ts} +20 -51
  81. package/dist/chunks/webdav-backend-3EGUONAT.ts.map +7 -0
  82. package/dist/index.ts +49 -74
  83. package/dist/index.ts.map +3 -3
  84. package/docs/settings.md +9 -0
  85. package/package.json +61 -62
  86. package/src/backends/backend-factory.ts +15 -15
  87. package/src/backends/backend-identity.ts +31 -31
  88. package/src/backends/backend-types.ts +42 -42
  89. package/src/backends/git/git-backend.ts +714 -788
  90. package/src/backends/git/git-config.ts +104 -111
  91. package/src/backends/git/git-runner.ts +230 -247
  92. package/src/backends/git/git-storage.ts +228 -245
  93. package/src/backends/s3/s3-backend.ts +408 -441
  94. package/src/backends/s3/s3-client.ts +288 -333
  95. package/src/backends/sync-backend.ts +65 -65
  96. package/src/backends/webdav/webdav-backend.ts +542 -589
  97. package/src/backends/webdav/webdav-client.ts +292 -316
  98. package/src/backends/webdav/webdav-config.ts +57 -68
  99. package/src/commands/command-execution.ts +270 -289
  100. package/src/commands/command-handler.ts +160 -163
  101. package/src/commands/command-types.ts +13 -13
  102. package/src/commands/command.ts +190 -196
  103. package/src/commands/setup-completions.ts +1 -1
  104. package/src/paths.ts +47 -47
  105. package/src/settings/config-errors.ts +4 -5
  106. package/src/settings/config-file.ts +473 -506
  107. package/src/settings/config.ts +171 -174
  108. package/src/settings/settings-management.ts +191 -216
  109. package/src/settings/settings-store.ts +63 -77
  110. package/src/settings/settings-types.ts +78 -75
  111. package/src/settings/settings-validation.ts +328 -364
  112. package/src/snapshot/session-paths.ts +41 -55
  113. package/src/snapshot/snapshot-apply.ts +197 -229
  114. package/src/snapshot/snapshot-codec.ts +29 -31
  115. package/src/snapshot/snapshot-paths.ts +4 -6
  116. package/src/snapshot/snapshot-transaction.ts +161 -170
  117. package/src/snapshot/snapshot-types.ts +24 -24
  118. package/src/snapshot/snapshot.ts +310 -343
  119. package/src/state/json-file.ts +11 -11
  120. package/src/state/lock.ts +226 -242
  121. package/src/state/lockfile-fs.ts +20 -20
  122. package/src/state/operation-availability.ts +36 -41
  123. package/src/state/state-directory.ts +171 -173
  124. package/src/state/state-types.ts +15 -15
  125. package/src/state/sync-state-store.ts +23 -23
  126. package/src/sync/automatic-sync.ts +75 -80
  127. package/src/sync/remote-snapshot.ts +52 -60
  128. package/src/sync/setup-switch.ts +99 -114
  129. package/src/sync/signals.ts +3 -5
  130. package/src/sync/startup-check.ts +97 -111
  131. package/src/sync/sync-decision.ts +76 -80
  132. package/src/sync/sync-error-guidance.ts +41 -48
  133. package/src/sync/sync-errors.ts +10 -10
  134. package/src/sync/sync-inspection.ts +29 -33
  135. package/src/sync/sync-loaders.ts +28 -36
  136. package/src/sync/sync-mutations.ts +510 -622
  137. package/src/sync/sync-operations.ts +7 -7
  138. package/src/sync/sync-policy.ts +296 -326
  139. package/src/sync/sync-queries.ts +175 -194
  140. package/src/sync/sync-state.ts +74 -100
  141. package/src/sync-extension.ts +89 -101
  142. package/src/sync.ts +26 -26
  143. package/src/types.ts +37 -37
  144. package/src/ui/automatic-sync-summary.ts +1 -3
  145. package/src/ui/cancellable-operation.ts +92 -112
  146. package/src/ui/file-selection.ts +250 -274
  147. package/src/ui/manager-attention.ts +51 -63
  148. package/src/ui/manager-recovery.ts +48 -57
  149. package/src/ui/manager-result-dispatcher.ts +62 -76
  150. package/src/ui/manager-state.ts +200 -232
  151. package/src/ui/manager-ui.ts +264 -362
  152. package/src/ui/remote-selection-ui.ts +547 -588
  153. package/src/ui/secret-input.ts +198 -214
  154. package/src/ui/settings-ui.ts +175 -172
  155. package/src/ui/setup/git-ui.ts +232 -261
  156. package/src/ui/setup/s3-credentials-ui.ts +58 -65
  157. package/src/ui/setup/s3-ui.ts +171 -195
  158. package/src/ui/setup/setup-actions.ts +91 -101
  159. package/src/ui/setup/setup-location-ui.ts +121 -137
  160. package/src/ui/setup/setup-name-ui.ts +27 -27
  161. package/src/ui/setup/setup-prompts.ts +65 -65
  162. package/src/ui/setup/setup-review.ts +58 -59
  163. package/src/ui/setup/setup-switcher.ts +156 -160
  164. package/src/ui/setup/setup-wizard.ts +28 -28
  165. package/src/ui/setup/text-input.ts +54 -58
  166. package/src/ui/setup/webdav-ui.ts +246 -293
  167. package/src/ui/storage-connections-ui.ts +317 -369
  168. package/src/ui/sync-attention.ts +148 -164
  169. package/src/ui/sync-format.ts +110 -121
  170. package/src/ui/sync-resolution-ui.ts +147 -153
  171. package/src/ui/sync-setups-ui.ts +218 -238
  172. package/src/ui/sync-status.ts +17 -0
  173. package/src/ui/terminal-text.ts +2 -2
  174. package/dist/chunks/chunk-2B2ELUF5.ts.map +0 -7
  175. package/dist/chunks/chunk-4CHTZMEO.ts.map +0 -7
  176. package/dist/chunks/chunk-5B3FXBLE.ts.map +0 -7
  177. package/dist/chunks/chunk-6LP3MCR2.ts.map +0 -7
  178. package/dist/chunks/chunk-AUSDLD6F.ts.map +0 -7
  179. package/dist/chunks/chunk-C7XY5NF5.ts.map +0 -7
  180. package/dist/chunks/chunk-CCSN3F47.ts.map +0 -7
  181. package/dist/chunks/chunk-CHH26D4F.ts.map +0 -7
  182. package/dist/chunks/chunk-CPEHGXJV.ts.map +0 -7
  183. package/dist/chunks/chunk-D5X27LCA.ts.map +0 -7
  184. package/dist/chunks/chunk-ETTRJDNP.ts.map +0 -7
  185. package/dist/chunks/chunk-G4RNQQZS.ts.map +0 -7
  186. package/dist/chunks/chunk-HUDQATCL.ts.map +0 -7
  187. package/dist/chunks/chunk-IJ6FJFA5.ts.map +0 -7
  188. package/dist/chunks/chunk-JAAKLL6M.ts.map +0 -7
  189. package/dist/chunks/chunk-KW7YQEZN.ts.map +0 -7
  190. package/dist/chunks/chunk-LCRI3ABW.ts.map +0 -7
  191. package/dist/chunks/chunk-LTBWGSBV.ts.map +0 -7
  192. package/dist/chunks/chunk-NJFUIVYL.ts.map +0 -7
  193. package/dist/chunks/chunk-OEX4ABY6.ts.map +0 -7
  194. package/dist/chunks/chunk-Q2TUM5X6.ts.map +0 -7
  195. package/dist/chunks/chunk-SL64RLXR.ts.map +0 -7
  196. package/dist/chunks/chunk-SZWBGPID.ts.map +0 -7
  197. package/dist/chunks/chunk-T2B5IU74.ts.map +0 -7
  198. package/dist/chunks/chunk-T3HH3UEJ.ts.map +0 -7
  199. package/dist/chunks/chunk-TEN2ZBJV.ts.map +0 -7
  200. package/dist/chunks/chunk-THMF4BVL.ts.map +0 -7
  201. package/dist/chunks/chunk-Z4DQF6L3.ts.map +0 -7
  202. package/dist/chunks/chunk-ZI5MXMEX.ts.map +0 -7
  203. package/dist/chunks/file-selection-FTTKAOYX.ts.map +0 -7
  204. package/dist/chunks/git-backend-GMVS4PD4.ts.map +0 -7
  205. package/dist/chunks/manager-result-dispatcher-FKIBM3GO.ts +0 -22
  206. package/dist/chunks/manager-ui-5KLKVMRC.ts.map +0 -7
  207. package/dist/chunks/remote-selection-ui-D7RBRJNL.ts +0 -21
  208. package/dist/chunks/s3-backend-66O4FGBH.ts.map +0 -7
  209. package/dist/chunks/setup-wizard-IUB6ESEU.ts.map +0 -7
  210. package/dist/chunks/sync-inspection-CST2KL65.ts +0 -20
  211. package/dist/chunks/sync-operations-LBX3GKCM.ts.map +0 -7
  212. package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +0 -7
  213. /package/dist/chunks/{manager-result-dispatcher-FKIBM3GO.ts.map → manager-result-dispatcher-OTUMQM26.ts.map} +0 -0
  214. /package/dist/chunks/{remote-selection-ui-D7RBRJNL.ts.map → remote-selection-ui-TZTJF32Y.ts.map} +0 -0
  215. /package/dist/chunks/{setup-switch-IS2PEZ3J.ts.map → setup-switch-5G2VNJHL.ts.map} +0 -0
  216. /package/dist/chunks/{snapshot-HKEWCVK6.ts.map → snapshot-JL5DFSXI.ts.map} +0 -0
  217. /package/dist/chunks/{sync-inspection-CST2KL65.ts.map → sync-inspection-P3ENJLNF.ts.map} +0 -0
  218. /package/dist/chunks/{sync-state-Q2FTXTW6.ts.map → sync-state-VHJUZ4YG.ts.map} +0 -0
package/README.md CHANGED
@@ -131,7 +131,7 @@ Then choose one reviewed direction:
131
131
  Cancelling a preparation or confirmation returns to conflict resolution with no side effects.
132
132
  Back returns to the sync manager, and Ctrl+C closes the complete flow.
133
133
 
134
- Startup checks never open a dialog. With a sync baseline and no content-list mismatch, ordinary one-sided changes appear only as **local changes pending** or **remote changes pending** in status. A persistent widget above the editor is reserved for both sides changing, first sync with an existing remote snapshot, a differing content list (including order-only differences), or a missing remote snapshot despite an existing baseline. Both sides changing is a reason to review, not a confirmed file conflict.
134
+ Startup checks never open a dialog. While checking, TUI and RPC status show **sync ...**. With a sync baseline and no content-list mismatch, they use **sync ⇡** for local changes to push and **sync ⇣** for remote changes to pull. Review conditions use **sync ⇕**; in TUI, a persistent widget above the editor also identifies both sides changing, first sync with an existing remote snapshot, a differing content list (including order-only differences), or a missing remote snapshot despite an existing baseline. Both sides changing is a reason to review, not a confirmed file conflict.
135
135
  No selected content and first sync with an empty remote stay quiet outside `/sync`, which provides setup or initialization guidance. Legacy remote metadata without an authoritative content list does not independently trigger a widget; the baseline and change conditions still apply. An included-content mismatch puts **Review synced content (recommended)** in the manager, where a fresh review verifies the remote snapshot before offering changes.
136
136
  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.
137
137
  The widget has no expiry timer. 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. A newer observation replaces the presentation, clearing the widget when only status or no reminder is needed. Cancelling a review or a failure before commit preserves a still-valid observation and its appropriate presentation.
@@ -158,6 +158,8 @@ Checks run once per session start, including `/reload`, new, resumed, and forked
158
158
 
159
159
  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.
160
160
 
161
+ **Settings → Show status (all setups)** defaults to **On**. Turning it Off immediately clears and suppresses pi-sync status text for background checks, transfers, and review attention; widgets and notifications remain available.
162
+
161
163
  **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.
162
164
  See [Secret scanning](./docs/settings.md#secret-scanning) for the setting and diagnostic behavior.
163
165
 
@@ -169,6 +171,7 @@ A minimal Git setup uses an existing private remote and keeps automatic sync off
169
171
  "activeSyncSetup": "home",
170
172
  "onSwitch": "ask-before-pull",
171
173
  "skipSecretScan": false,
174
+ "showStatus": true,
172
175
  "storageConnections": {
173
176
  "github": {
174
177
  "type": "git",
@@ -2,7 +2,7 @@
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
4
  normalizeGitRemoteIdentity
5
- } from "./chunk-TEN2ZBJV.ts";
5
+ } from "./chunk-T2SJD3N7.ts";
6
6
 
7
7
  // src/backends/webdav/webdav-config.ts
8
8
  var DEFAULT_PATH = "pi-sync";
@@ -29,9 +29,7 @@ function normalizeWebDavUrl(value) {
29
29
  throw new Error("Invalid pi-sync WebDAV URL.");
30
30
  }
31
31
  if (url.username || url.password || url.search || url.hash) {
32
- throw new Error(
33
- "Invalid pi-sync WebDAV URL: credentials, query, and fragment are not allowed."
34
- );
32
+ throw new Error("Invalid pi-sync WebDAV URL: credentials, query, and fragment are not allowed.");
35
33
  }
36
34
  const loopback = url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "[::1]" || url.hostname === "::1";
37
35
  if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
@@ -112,4 +110,4 @@ export {
112
110
  backendIdentityCoordinates,
113
111
  normalizeEndpointIdentity
114
112
  };
115
- //# sourceMappingURL=chunk-6LP3MCR2.ts.map
113
+ //# sourceMappingURL=chunk-2JZFHNLM.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/backends/webdav/webdav-config.ts", "../../src/backends/backend-identity.ts"],
4
+ "sourcesContent": ["const DEFAULT_PATH = \"pi-sync\";\n\nexport function normalizeWebDavIdentityUrl(value: string) {\n try {\n const url = new URL(value.trim());\n url.username = \"\";\n url.password = \"\";\n url.search = \"\";\n url.hash = \"\";\n url.pathname = `${url.pathname.replace(/\\/+$/u, \"\")}/`;\n return url.toString();\n } catch {\n return value.trim();\n }\n}\n\nexport function normalizeWebDavUrl(value: string | undefined) {\n const normalized = normalizeOptionalString(value);\n if (!normalized) return undefined;\n let url: URL;\n try {\n url = new URL(normalized);\n } catch {\n throw new Error(\"Invalid pi-sync WebDAV URL.\");\n }\n if (url.username || url.password || url.search || url.hash) {\n throw new Error(\"Invalid pi-sync WebDAV URL: credentials, query, and fragment are not allowed.\");\n }\n const loopback =\n url.hostname === \"127.0.0.1\" || url.hostname === \"localhost\" || url.hostname === \"[::1]\" || url.hostname === \"::1\";\n if (url.protocol !== \"https:\" && !(url.protocol === \"http:\" && loopback)) {\n throw new Error(\"Invalid pi-sync WebDAV URL: HTTPS is required except for loopback.\");\n }\n url.pathname = `${url.pathname.replace(/\\/+$/u, \"\")}/`;\n return url.toString();\n}\n\nexport function normalizeWebDavPath(value: string | undefined) {\n if (value?.trim() === \".\" || value?.trim() === \"./\") return \"./\";\n const normalized = trimSlashes(normalizeOptionalString(value) ?? DEFAULT_PATH);\n if (\n !normalized ||\n normalized.includes(\"\\\\\") ||\n hasControlCharacter(normalized) ||\n normalized.split(\"/\").some((segment) => !segment || segment === \".\" || segment === \"..\")\n ) {\n throw new Error(\"Invalid pi-sync WebDAV path.\");\n }\n return normalized;\n}\n\nexport function validateWebDavNamespace(value: string) {\n if (value === \".\" || value === \"..\" || value.includes(\"/\") || value.includes(\"\\\\\") || hasControlCharacter(value)) {\n throw new Error(\"Invalid pi-sync WebDAV namespace.\");\n }\n}\n\nexport function validateWebDavCredentials(username: string, password?: string) {\n if (\n username.includes(\":\") ||\n hasControlCharacter(username) ||\n (password !== undefined && hasControlCharacter(password))\n ) {\n throw new Error(\"Invalid pi-sync WebDAV credentials.\");\n }\n}\n\nfunction normalizeOptionalString(value: string | undefined) {\n const normalized = value?.trim();\n return normalized || undefined;\n}\n\nfunction trimSlashes(value: string) {\n return value.replace(/^\\/+|\\/+$/gu, \"\");\n}\n\nfunction hasControlCharacter(value: string) {\n return [...value].some((character) => {\n const code = character.codePointAt(0) ?? 0;\n return code < 0x20 || (code >= 0x7f && code <= 0x9f);\n });\n}\n", "import type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { normalizeGitRemoteIdentity } from \"./git/git-config.js\";\nimport { normalizeWebDavIdentityUrl } from \"./webdav/webdav-config.js\";\n\nexport function backendIdentityCoordinates(config: AnySyncConfig) {\n switch (config.backend.type) {\n case \"s3\":\n return JSON.stringify([\n \"s3\",\n normalizeEndpointIdentity(config.backend.profile.endpoint),\n config.backend.destination.bucket,\n config.storagePath,\n ]);\n case \"git\":\n return JSON.stringify([\n \"git\",\n normalizeGitRemoteIdentity(config.backend.profile.remote),\n config.backend.destination.branch,\n config.storagePath,\n ]);\n case \"webdav\":\n return JSON.stringify([\n \"webdav\",\n normalizeWebDavIdentityUrl(config.backend.profile.url),\n config.backend.profile.username,\n config.storagePath,\n ]);\n }\n}\n\nexport function normalizeEndpointIdentity(endpoint: string) {\n try {\n const url = new URL(endpoint.trim());\n url.hostname = url.hostname.toLowerCase();\n url.pathname = url.pathname.replace(/\\/+$/gu, \"\");\n return url.toString().replace(/\\/$/u, \"\");\n } catch {\n return endpoint.trim();\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -6,10 +6,10 @@ import {
6
6
  syncSetupStorageReview,
7
7
  updateLocalConfig,
8
8
  validateConfigName
9
- } from "./chunk-C7XY5NF5.ts";
9
+ } from "./chunk-4MPIH46N.ts";
10
10
  import {
11
11
  normalizeSyncInclude
12
- } from "./chunk-JAAKLL6M.ts";
12
+ } from "./chunk-IVXEDPAD.ts";
13
13
 
14
14
  // src/settings/settings-management.ts
15
15
  import { isDeepStrictEqual } from "node:util";
@@ -77,15 +77,12 @@ async function updateStorageConnection(name, update, expectedSetups, signal) {
77
77
  async function addSyncSetup(name, setup, signal, expectedConnection) {
78
78
  validateConfigName(name, "sync setup");
79
79
  await updateSettings((settings) => {
80
- if (Object.hasOwn(settings.syncSetups, name))
81
- throw new Error(`Sync setup already exists: ${name}`);
80
+ if (Object.hasOwn(settings.syncSetups, name)) throw new Error(`Sync setup already exists: ${name}`);
82
81
  if (!Object.hasOwn(settings.storageConnections, setup.storage.connection)) {
83
82
  throw new Error(`Storage connection not found: ${setup.storage.connection}`);
84
83
  }
85
84
  if (expectedConnection && !isDeepStrictEqual(settings.storageConnections[setup.storage.connection], expectedConnection)) {
86
- throw new SyncSetupReviewChangedError(
87
- "Storage connection changed while the setup review was open; reopen it."
88
- );
85
+ throw new SyncSetupReviewChangedError("Storage connection changed while the setup review was open; reopen it.");
89
86
  }
90
87
  const nextSetups = { ...settings.syncSetups, [name]: structuredClone(setup) };
91
88
  assertUniqueLocations(nextSetups, settings.storageConnections);
@@ -109,22 +106,14 @@ async function updateSyncSetup(name, update, options = {}) {
109
106
  if (options.expectedStorage) {
110
107
  const connectionName = setup.storage.connection;
111
108
  const connection = settings.storageConnections[connectionName];
112
- if (!connection || !sameStorageReview(
113
- syncSetupStorageReview(name, setup, connectionName, connection),
114
- options.expectedStorage
115
- )) {
109
+ if (!connection || !sameStorageReview(syncSetupStorageReview(name, setup, connectionName, connection), options.expectedStorage)) {
116
110
  throw new SyncSetupReviewChangedError(
117
111
  `Sync setup \u201C${name}\u201D storage changed while it was open; reopen it and review the current storage location.`
118
112
  );
119
113
  }
120
114
  }
121
- if (options.expectedConnection && !isDeepStrictEqual(
122
- settings.storageConnections[setup.storage.connection],
123
- options.expectedConnection
124
- )) {
125
- throw new SyncSetupReviewChangedError(
126
- "Storage connection changed while the setup review was open; reopen it."
127
- );
115
+ if (options.expectedConnection && !isDeepStrictEqual(settings.storageConnections[setup.storage.connection], options.expectedConnection)) {
116
+ throw new SyncSetupReviewChangedError("Storage connection changed while the setup review was open; reopen it.");
128
117
  }
129
118
  const nextSetup = update(structuredClone(setup));
130
119
  if (!Object.hasOwn(settings.storageConnections, nextSetup.storage.connection)) {
@@ -206,4 +195,4 @@ export {
206
195
  removeSyncSetup,
207
196
  removeStorageConnection
208
197
  };
209
- //# sourceMappingURL=chunk-LCRI3ABW.ts.map
198
+ //# sourceMappingURL=chunk-2RZSQWBH.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/settings/settings-management.ts"],
4
+ "sourcesContent": ["import { isDeepStrictEqual } from \"node:util\";\nimport { normalizeSyncInclude } from \"../sync/sync-policy.js\";\nimport { type SyncSetupStorageReview, syncSetupStorageReview } from \"./config.js\";\nimport { localConfigPath } from \"./config-file.js\";\nimport { updateLocalConfig } from \"./settings-store.js\";\nimport type { PiSyncSettingsV3, StorageConnectionSettings, SyncSetupSettings } from \"./settings-types.js\";\nimport { effectiveSyncSetupRemoteIdentity, validateConfigName } from \"./settings-validation.js\";\n\nexport class SyncSetupReviewChangedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SyncSetupReviewChangedError\";\n }\n}\n\nexport async function saveNewV3Settings(\n input: {\n setupName: string;\n connectionName: string;\n connection: StorageConnectionSettings;\n setup: SyncSetupSettings;\n },\n signal?: AbortSignal,\n) {\n validateConfigName(input.setupName, \"sync setup\");\n validateConfigName(input.connectionName, \"storage connection\");\n const settings: PiSyncSettingsV3 = {\n version: 3,\n activeSyncSetup: input.setupName,\n onSwitch: \"ask-before-pull\",\n skipSecretScan: false,\n storageConnections: { [input.connectionName]: structuredClone(input.connection) },\n syncSetups: {\n [input.setupName]: {\n ...structuredClone(input.setup),\n storage: { ...input.setup.storage, connection: input.connectionName },\n },\n },\n };\n return updateLocalConfig((current) => {\n if (Object.keys(current.storageConnections).length || Object.keys(current.syncSetups).length) {\n throw new Error(`Settings already exist: ${localConfigPath()}`);\n }\n return { ...current, ...settings };\n }, signal);\n}\n\nexport async function addStorageConnection(name: string, connection: StorageConnectionSettings, signal?: AbortSignal) {\n validateConfigName(name, \"storage connection\");\n await updateSettings((settings) => {\n if (Object.hasOwn(settings.storageConnections, name)) {\n throw new Error(`Storage connection already exists: ${name}`);\n }\n return {\n ...settings,\n storageConnections: {\n ...settings.storageConnections,\n [name]: structuredClone(connection),\n },\n };\n }, signal);\n}\n\nexport async function updateStorageConnection(\n name: string,\n update: (connection: StorageConnectionSettings) => StorageConnectionSettings,\n expectedSetups?: readonly string[],\n signal?: AbortSignal,\n) {\n validateConfigName(name, \"storage connection\");\n await updateSettings((settings) => {\n const connection = settings.storageConnections[name];\n if (!connection) throw new Error(`Storage connection not found: ${name}`);\n const currentSetups = referencingSetupNames(settings.syncSetups, name);\n if (expectedSetups && !sameNames(currentSetups, expectedSetups)) {\n throw new Error(\n `Storage connection \u201C${name}\u201D usage changed while it was open; reopen it and review the affected sync setups.`,\n );\n }\n const nextConnection = update(structuredClone(connection));\n const nextConnections = { ...settings.storageConnections, [name]: nextConnection };\n assertUniqueLocations(settings.syncSetups, nextConnections);\n return { ...settings, storageConnections: nextConnections };\n }, signal);\n}\n\nexport async function addSyncSetup(\n name: string,\n setup: SyncSetupSettings,\n signal?: AbortSignal,\n expectedConnection?: StorageConnectionSettings,\n) {\n validateConfigName(name, \"sync setup\");\n await updateSettings((settings) => {\n if (Object.hasOwn(settings.syncSetups, name)) throw new Error(`Sync setup already exists: ${name}`);\n if (!Object.hasOwn(settings.storageConnections, setup.storage.connection)) {\n throw new Error(`Storage connection not found: ${setup.storage.connection}`);\n }\n if (\n expectedConnection &&\n !isDeepStrictEqual(settings.storageConnections[setup.storage.connection], expectedConnection)\n ) {\n throw new SyncSetupReviewChangedError(\"Storage connection changed while the setup review was open; reopen it.\");\n }\n const nextSetups = { ...settings.syncSetups, [name]: structuredClone(setup) };\n assertUniqueLocations(nextSetups, settings.storageConnections);\n return {\n ...settings,\n syncSetups: nextSetups,\n ...(settings.activeSyncSetup ? {} : { activeSyncSetup: name }),\n };\n }, signal);\n}\n\nexport async function updateSyncSetup(\n name: string,\n update: (setup: SyncSetupSettings) => SyncSetupSettings,\n options: {\n expectedStorage?: SyncSetupStorageReview;\n expectedInclude?: readonly string[];\n expectedConnection?: StorageConnectionSettings;\n signal?: AbortSignal;\n } = {},\n) {\n validateConfigName(name, \"sync setup\");\n await updateSettings((settings) => {\n const setup = settings.syncSetups[name];\n if (!setup) throw new Error(`Sync setup not found: ${name}`);\n if (options.expectedInclude && !sameNames(normalizeSyncInclude(setup.sync.include), options.expectedInclude)) {\n throw new SyncSetupReviewChangedError(\n `Sync setup \u201C${name}\u201D included content changed while it was open; reopen it and review the current selection.`,\n );\n }\n if (options.expectedStorage) {\n const connectionName = setup.storage.connection;\n const connection = settings.storageConnections[connectionName];\n if (\n !connection ||\n !sameStorageReview(syncSetupStorageReview(name, setup, connectionName, connection), options.expectedStorage)\n ) {\n throw new SyncSetupReviewChangedError(\n `Sync setup \u201C${name}\u201D storage changed while it was open; reopen it and review the current storage location.`,\n );\n }\n }\n if (\n options.expectedConnection &&\n !isDeepStrictEqual(settings.storageConnections[setup.storage.connection], options.expectedConnection)\n ) {\n throw new SyncSetupReviewChangedError(\"Storage connection changed while the setup review was open; reopen it.\");\n }\n const nextSetup = update(structuredClone(setup));\n if (!Object.hasOwn(settings.storageConnections, nextSetup.storage.connection)) {\n throw new Error(`Storage connection not found: ${nextSetup.storage.connection}`);\n }\n const nextSetups = { ...settings.syncSetups, [name]: nextSetup };\n assertUniqueLocations(nextSetups, settings.storageConnections);\n return { ...settings, syncSetups: nextSetups };\n }, options.signal);\n}\n\nexport async function removeSyncSetup(name: string, signal?: AbortSignal) {\n validateConfigName(name, \"sync setup\");\n await updateSettings((settings) => {\n if (!Object.hasOwn(settings.syncSetups, name)) throw new Error(`Sync setup not found: ${name}`);\n const isCurrent = settings.activeSyncSetup === name;\n if (isCurrent && Object.keys(settings.syncSetups).length > 1) {\n throw new Error(\"Switch to another sync setup before removing the current setup.\");\n }\n const syncSetups = { ...settings.syncSetups };\n delete syncSetups[name];\n const next = { ...settings, syncSetups };\n if (isCurrent) delete next.activeSyncSetup;\n return next;\n }, signal);\n}\n\nexport async function removeStorageConnection(name: string, signal?: AbortSignal) {\n validateConfigName(name, \"storage connection\");\n await updateSettings((settings) => {\n const referenced = referencingSetupNames(settings.syncSetups, name)[0];\n if (referenced) {\n throw new Error(`Storage connection \u201C${name}\u201D is used by sync setup \u201C${referenced}\u201D.`);\n }\n if (!Object.hasOwn(settings.storageConnections, name)) {\n throw new Error(`Storage connection not found: ${name}`);\n }\n const storageConnections = { ...settings.storageConnections };\n delete storageConnections[name];\n return { ...settings, storageConnections };\n }, signal);\n}\n\nasync function updateSettings(update: (settings: PiSyncSettingsV3) => PiSyncSettingsV3, signal?: AbortSignal) {\n return updateLocalConfig((settings) => {\n if (settings.version !== 3) {\n throw new Error(\"Storage connections and sync setups require version 3 pi-sync settings.\");\n }\n return update(settings);\n }, signal);\n}\n\nfunction referencingSetupNames(setups: Record<string, SyncSetupSettings>, connection: string) {\n return Object.entries(setups)\n .filter(([, setup]) => setup.storage.connection === connection)\n .map(([name]) => name)\n .sort((left, right) => left.localeCompare(right));\n}\n\nfunction assertUniqueLocations(\n setups: Record<string, SyncSetupSettings>,\n connections: Record<string, StorageConnectionSettings>,\n) {\n const identities = new Map<string, string>();\n for (const [name, setup] of Object.entries(setups)) {\n const connection = connections[setup.storage.connection];\n if (!connection) throw new Error(`Storage connection not found: ${setup.storage.connection}`);\n const identity = effectiveSyncSetupRemoteIdentity(setup, connection);\n const previous = identities.get(identity);\n if (previous) {\n throw new Error(`Sync setup \u201C${name}\u201D duplicates the storage location of \u201C${previous}\u201D.`);\n }\n identities.set(identity, name);\n }\n}\n\nfunction sameNames(left: readonly string[], right: readonly string[]) {\n return left.length === right.length && left.every((name, index) => name === right[index]);\n}\n\nfunction sameStorageReview(left: SyncSetupStorageReview, right: SyncSetupStorageReview) {\n return (\n left.connectionName === right.connectionName &&\n left.storageKind === right.storageKind &&\n left.storagePath === right.storagePath &&\n left.bucket === right.bucket &&\n left.branch === right.branch\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -2,20 +2,20 @@
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
4
  withLock
5
- } from "./chunk-ETTRJDNP.ts";
5
+ } from "./chunk-QLSN7AGX.ts";
6
6
  import {
7
7
  stateDir
8
- } from "./chunk-ZI5MXMEX.ts";
8
+ } from "./chunk-BVVP33U5.ts";
9
9
  import {
10
10
  sessionStorageRoot
11
- } from "./chunk-THMF4BVL.ts";
11
+ } from "./chunk-SWUXA3MH.ts";
12
12
  import {
13
13
  agentDir
14
- } from "./chunk-T3HH3UEJ.ts";
14
+ } from "./chunk-OVDTF5CH.ts";
15
15
  import {
16
16
  assertWithinRoot,
17
17
  isPathInside
18
- } from "./chunk-JAAKLL6M.ts";
18
+ } from "./chunk-IVXEDPAD.ts";
19
19
 
20
20
  // src/snapshot/snapshot-transaction.ts
21
21
  import { randomUUID } from "node:crypto";
@@ -129,21 +129,15 @@ async function prepareTransaction(plan, sessionDir) {
129
129
  sessionRoot,
130
130
  entries
131
131
  };
132
- await fs.writeFile(
133
- path.join(directory, "journal.json"),
134
- `${JSON.stringify(journal, null, " ")}
135
- `,
136
- {
137
- mode: 384
138
- }
139
- );
132
+ await fs.writeFile(path.join(directory, "journal.json"), `${JSON.stringify(journal, null, " ")}
133
+ `, {
134
+ mode: 384
135
+ });
140
136
  return { directory, journal };
141
137
  }
142
138
  async function restoreTransaction(directory, journal) {
143
139
  validateJournal(directory, journal);
144
- for (const entry of [...journal.entries].sort(
145
- (left, right) => right.target.length - left.target.length
146
- )) {
140
+ for (const entry of [...journal.entries].sort((left, right) => right.target.length - left.target.length)) {
147
141
  await fs.rm(entry.target, { recursive: true, force: true });
148
142
  }
149
143
  for (const entry of journal.entries) {
@@ -200,4 +194,4 @@ export {
200
194
  recoverSnapshotTransactionsOnStartup,
201
195
  recoverPendingSnapshotTransactions
202
196
  };
203
- //# sourceMappingURL=chunk-IJ6FJFA5.ts.map
197
+ //# sourceMappingURL=chunk-4AIHXRKE.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/snapshot/snapshot-transaction.ts"],
4
+ "sourcesContent": ["import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { assertWithinRoot, isPathInside } from \"../paths.js\";\nimport { withLock } from \"../state/lock.js\";\nimport { stateDir } from \"../state/state-directory.js\";\nimport { agentDir } from \"./session-paths.js\";\nimport { sessionStorageRoot } from \"./snapshot-paths.js\";\nimport type { SnapshotApplyPlan } from \"./snapshot-types.js\";\n\nconst JOURNAL_VERSION = 1;\n\ninterface TransactionEntry {\n target: string;\n backupName: string;\n kind: \"missing\" | \"file\" | \"directory\" | \"symlink\";\n linkTarget?: string;\n}\n\ninterface TransactionJournal {\n version: number;\n root: string;\n sessionRoot?: string;\n entries: TransactionEntry[];\n}\n\nexport async function applySnapshotTransaction(plan: SnapshotApplyPlan, options: { sessionDir?: string } = {}) {\n await recoverPendingSnapshotTransactions();\n const transaction = await prepareTransaction(plan, options.sessionDir);\n try {\n for (const target of plan.deletes) {\n await fs.rm(target, { force: true, recursive: true });\n }\n for (const item of plan.writes) {\n await fs.mkdir(path.dirname(item.target), { recursive: true });\n await fs.writeFile(item.target, item.content);\n }\n await fs.rm(transaction.directory, { recursive: true, force: true });\n } catch (error) {\n try {\n await restoreTransaction(transaction.directory, transaction.journal);\n } catch (recoveryError) {\n throw new AggregateError(\n [error, recoveryError],\n `Snapshot apply failed and automatic recovery also failed. Transaction retained at ${transaction.directory}.`,\n );\n }\n throw error;\n }\n}\n\nexport async function recoverSnapshotTransactionsOnStartup() {\n if (!(await pendingTransactionEntries()).some((entry) => entry.isDirectory())) return;\n await withLock(\"recovery\", recoverPendingSnapshotTransactions, { reclaimStale: true });\n}\n\nexport async function recoverPendingSnapshotTransactions() {\n const directory = transactionRoot();\n const entries = await pendingTransactionEntries();\n for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {\n if (!entry.isDirectory()) continue;\n const transactionDirectory = path.join(directory, entry.name);\n const journalPath = path.join(transactionDirectory, \"journal.json\");\n let journal: TransactionJournal;\n try {\n journal = JSON.parse(await fs.readFile(journalPath, \"utf8\")) as TransactionJournal;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n await fs.rm(transactionDirectory, { recursive: true, force: true });\n continue;\n }\n throw new Error(`Cannot recover malformed pi-sync transaction: ${journalPath}`, {\n cause: error,\n });\n }\n await restoreTransaction(transactionDirectory, journal);\n }\n}\n\nasync function pendingTransactionEntries() {\n try {\n return await fs.readdir(transactionRoot(), { withFileTypes: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw error;\n }\n}\n\nasync function prepareTransaction(plan: SnapshotApplyPlan, sessionDir?: string) {\n const root = path.resolve(agentDir());\n const sessionRoot = sessionDir ? path.resolve(sessionStorageRoot(root, sessionDir)) : undefined;\n const directory = path.join(transactionRoot(), randomUUID());\n const backupDirectory = path.join(directory, \"before\");\n await fs.mkdir(backupDirectory, { recursive: true, mode: 0o700 });\n const targets = [...new Set([...plan.deletes, ...plan.writes.map((item) => item.target)])].sort();\n const entries: TransactionEntry[] = [];\n for (let index = 0; index < targets.length; index += 1) {\n const target = targets[index];\n if (!target) continue;\n assertAllowedTarget(root, sessionRoot, target);\n const backupName = `${index}`;\n const backupPath = path.join(backupDirectory, backupName);\n try {\n const stat = await fs.lstat(target);\n if (stat.isSymbolicLink()) {\n entries.push({\n target,\n backupName,\n kind: \"symlink\",\n linkTarget: await fs.readlink(target),\n });\n } else if (stat.isDirectory()) {\n await fs.cp(target, backupPath, {\n recursive: true,\n dereference: false,\n preserveTimestamps: true,\n });\n entries.push({ target, backupName, kind: \"directory\" });\n } else if (stat.isFile()) {\n await fs.copyFile(target, backupPath);\n await fs.chmod(backupPath, stat.mode);\n entries.push({ target, backupName, kind: \"file\" });\n } else {\n throw new Error(`Unsupported existing snapshot target: ${target}`);\n }\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\" && code !== \"ENOTDIR\") throw error;\n entries.push({ target, backupName, kind: \"missing\" });\n }\n }\n const journal: TransactionJournal = {\n version: JOURNAL_VERSION,\n root,\n sessionRoot,\n entries,\n };\n await fs.writeFile(path.join(directory, \"journal.json\"), `${JSON.stringify(journal, null, \"\\t\")}\\n`, {\n mode: 0o600,\n });\n return { directory, journal };\n}\n\nasync function restoreTransaction(directory: string, journal: TransactionJournal) {\n validateJournal(directory, journal);\n for (const entry of [...journal.entries].sort((left, right) => right.target.length - left.target.length)) {\n await fs.rm(entry.target, { recursive: true, force: true });\n }\n for (const entry of journal.entries) {\n if (entry.kind === \"missing\") continue;\n await fs.mkdir(path.dirname(entry.target), { recursive: true });\n const backupPath = path.join(directory, \"before\", entry.backupName);\n assertWithinRoot(directory, backupPath);\n if (entry.kind === \"file\") await fs.copyFile(backupPath, entry.target);\n else if (entry.kind === \"directory\") {\n await fs.cp(backupPath, entry.target, {\n recursive: true,\n dereference: false,\n preserveTimestamps: true,\n });\n } else if (entry.kind === \"symlink\" && entry.linkTarget !== undefined) {\n await fs.symlink(entry.linkTarget, entry.target);\n }\n }\n await fs.rm(directory, { recursive: true, force: true });\n}\n\nfunction validateJournal(directory: string, journal: TransactionJournal) {\n if (journal.version !== JOURNAL_VERSION || !Array.isArray(journal.entries)) {\n throw new Error(`Unsupported pi-sync transaction journal: ${directory}`);\n }\n const expectedRoot = path.resolve(agentDir());\n if (path.resolve(journal.root) !== expectedRoot) {\n throw new Error(`Transaction root no longer matches the Pi agent directory: ${directory}`);\n }\n for (const entry of journal.entries) {\n if (\n !entry ||\n typeof entry.target !== \"string\" ||\n typeof entry.backupName !== \"string\" ||\n !/^\\d+$/u.test(entry.backupName)\n ) {\n throw new Error(`Invalid pi-sync transaction entry: ${directory}`);\n }\n assertAllowedTarget(journal.root, journal.sessionRoot, entry.target);\n }\n}\n\nfunction assertAllowedTarget(root: string, sessionRoot: string | undefined, target: string) {\n const resolved = path.resolve(target);\n if (isPathInside(root, resolved)) {\n assertWithinRoot(root, resolved);\n return;\n }\n if (sessionRoot && isPathInside(sessionRoot, resolved)) {\n assertWithinRoot(sessionRoot, resolved);\n return;\n }\n throw new Error(`Transaction target is outside configured roots: ${target}`);\n}\n\nfunction transactionRoot() {\n return path.join(stateDir(), \"transactions\");\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -7,16 +7,16 @@ import {
7
7
  normalizeWebDavPath,
8
8
  normalizeWebDavUrl,
9
9
  validateWebDavCredentials
10
- } from "./chunk-6LP3MCR2.ts";
10
+ } from "./chunk-2JZFHNLM.ts";
11
11
  import {
12
12
  normalizeGitBranch,
13
13
  normalizeGitDirectory,
14
14
  normalizeGitRemote,
15
15
  normalizeGitRemoteIdentity
16
- } from "./chunk-TEN2ZBJV.ts";
16
+ } from "./chunk-T2SJD3N7.ts";
17
17
  import {
18
18
  normalizeSyncInclude
19
- } from "./chunk-JAAKLL6M.ts";
19
+ } from "./chunk-IVXEDPAD.ts";
20
20
 
21
21
  // src/settings/config-file.ts
22
22
  import { randomUUID } from "node:crypto";
@@ -212,11 +212,7 @@ async function prepareLocalConfigPath(validateForMigration) {
212
212
  return legacyPath;
213
213
  }
214
214
  if (!await configSnapshotStillMatches(legacyPath, legacy)) {
215
- const removed = await quarantineAndRemoveConfigIfMatchesUnlocked(
216
- canonicalPath,
217
- installedIdentity,
218
- legacy.bytes
219
- );
215
+ const removed = await quarantineAndRemoveConfigIfMatchesUnlocked(canonicalPath, installedIdentity, legacy.bytes);
220
216
  recordConfigMigrationNotice(
221
217
  canonicalPath,
222
218
  removed ? `${LEGACY_CONFIG_FILE_NAME} changed during migration; the stale ${CONFIG_FILE_NAME} copy was removed and the legacy settings were used for this session.` : `${LEGACY_CONFIG_FILE_NAME} changed during migration, but ${CONFIG_FILE_NAME} was replaced concurrently and takes precedence.`
@@ -339,11 +335,7 @@ async function installPrivateConfigExclusively(filePath, bytes) {
339
335
  await publishedHandle?.close().catch(() => void 0);
340
336
  }
341
337
  if (publicationError) {
342
- await quarantineAndRemoveConfigIfMatchesUnlocked(
343
- filePath,
344
- { dev: installed.dev, ino: installed.ino },
345
- bytes
346
- );
338
+ await quarantineAndRemoveConfigIfMatchesUnlocked(filePath, { dev: installed.dev, ino: installed.ino }, bytes);
347
339
  throw publicationError;
348
340
  }
349
341
  return { dev: installed.dev, ino: installed.ino };
@@ -541,6 +533,9 @@ function validateSettingsDocument(value) {
541
533
  if (value.skipSecretScan !== void 0 && typeof value.skipSecretScan !== "boolean") {
542
534
  throw new Error("Invalid pi-sync settings: skipSecretScan must be boolean.");
543
535
  }
536
+ if (value.showStatus !== void 0 && typeof value.showStatus !== "boolean") {
537
+ throw new Error("Invalid pi-sync settings: showStatus must be boolean.");
538
+ }
544
539
  const storageConnections = requireNamedObjectMap(
545
540
  value.storageConnections,
546
541
  "storageConnections",
@@ -567,9 +562,7 @@ function validateSettingsDocument(value) {
567
562
  throw new Error("Invalid pi-sync settings: empty syncSetups cannot have activeSyncSetup.");
568
563
  }
569
564
  } else if (!activeSyncSetup || !Object.hasOwn(syncSetups, activeSyncSetup)) {
570
- throw new Error(
571
- "Invalid pi-sync settings: activeSyncSetup must reference an existing own-property sync setup."
572
- );
565
+ throw new Error("Invalid pi-sync settings: activeSyncSetup must reference an existing own-property sync setup.");
573
566
  }
574
567
  validateUniqueRemoteSyncSetups(syncSetups, storageConnections);
575
568
  return value;
@@ -597,17 +590,12 @@ function validateStorageConnection(name, value) {
597
590
  }
598
591
  return;
599
592
  }
600
- const credentials = requireRecord(
601
- value.credentials,
602
- `credentials for storage connection \u201C${name}\u201D`
603
- );
593
+ const credentials = requireRecord(value.credentials, `credentials for storage connection \u201C${name}\u201D`);
604
594
  if (type === "webdav") {
605
595
  normalizeWebDavUrl(requiredString(value.url, `WebDAV URL for \u201C${name}\u201D`));
606
596
  const username = requiredString(credentials.username, `WebDAV username for \u201C${name}\u201D`);
607
597
  const password = requiredSecret(credentials.password, `WebDAV password for \u201C${name}\u201D`);
608
- if (["accessKeyId", "secretAccessKey", "sessionToken"].some(
609
- (field) => Object.hasOwn(credentials, field)
610
- )) {
598
+ if (["accessKeyId", "secretAccessKey", "sessionToken"].some((field) => Object.hasOwn(credentials, field))) {
611
599
  throw new Error(`Invalid pi-sync settings: WebDAV credentials for \u201C${name}\u201D mix fields.`);
612
600
  }
613
601
  validateWebDavCredentials(username, password);
@@ -642,11 +630,7 @@ function validateSyncSetup(name, value, connections) {
642
630
  );
643
631
  const storage = requireRecord(value.storage, `storage for sync setup \u201C${name}\u201D`);
644
632
  const sync = requireRecord(value.sync, `sync policy for sync setup \u201C${name}\u201D`);
645
- rejectLegacyFields(
646
- storage,
647
- ["profile", "prefix", "directory", "namespace"],
648
- `storage for sync setup \u201C${name}\u201D`
649
- );
633
+ rejectLegacyFields(storage, ["profile", "prefix", "directory", "namespace"], `storage for sync setup \u201C${name}\u201D`);
650
634
  rejectLegacyFields(
651
635
  sync,
652
636
  ["autoSync", "syncFiles", "syncSessions", "extraFiles"],
@@ -681,9 +665,7 @@ function validateSyncSetup(name, value, connections) {
681
665
  }
682
666
  normalizeSyncInclude(sync.include);
683
667
  if (typeof sync.automatic !== "boolean") {
684
- throw new Error(
685
- `Invalid pi-sync settings: sync setup \u201C${name}\u201D sync.automatic must be boolean.`
686
- );
668
+ throw new Error(`Invalid pi-sync settings: sync setup \u201C${name}\u201D sync.automatic must be boolean.`);
687
669
  }
688
670
  }
689
671
  function mixedSetupError(type, name) {
@@ -736,9 +718,7 @@ function effectiveSyncSetupRemoteIdentity(setup, connection) {
736
718
  function rejectLegacyFields(value, fields, context) {
737
719
  const field = fields.find((candidate) => Object.hasOwn(value, candidate));
738
720
  if (field) {
739
- throw new Error(
740
- `Invalid pi-sync settings: ${context} contains unsupported version 1/2 field \u201C${field}\u201D.`
741
- );
721
+ throw new Error(`Invalid pi-sync settings: ${context} contains unsupported version 1/2 field \u201C${field}\u201D.`);
742
722
  }
743
723
  }
744
724
  function requireNamedObjectMap(value, field, itemLabel) {
@@ -851,9 +831,7 @@ var configUpdateQueue = Promise.resolve();
851
831
  async function requireSettings() {
852
832
  const settings = await readLocalConfigObject();
853
833
  if (!settings) {
854
- throw new Error(
855
- `Missing pi-sync settings. Open /sync and choose Set up sync, or create ${localConfigPath()}.`
856
- );
834
+ throw new Error(`Missing pi-sync settings. Open /sync and choose Set up sync, or create ${localConfigPath()}.`);
857
835
  }
858
836
  return settings;
859
837
  }
@@ -869,6 +847,7 @@ function localConfigTemplate() {
869
847
  version: 3,
870
848
  onSwitch: DEFAULT_ON_SWITCH,
871
849
  skipSecretScan: false,
850
+ showStatus: true,
872
851
  storageConnections: {},
873
852
  syncSetups: {}
874
853
  };
@@ -914,13 +893,9 @@ function configFromSettings(settings, setupName) {
914
893
  if (!selectedName) throw new Error("No sync setups are configured.");
915
894
  validateConfigName(selectedName, "sync setup");
916
895
  const setup = ownObject(settings.syncSetups, selectedName);
917
- if (!setup)
918
- throw new Error(`Invalid pi-sync settings: sync setup \u201C${selectedName}\u201D was not found.`);
896
+ if (!setup) throw new Error(`Invalid pi-sync settings: sync setup \u201C${selectedName}\u201D was not found.`);
919
897
  const connectionName = setup.storage.connection;
920
- const connection = ownObject(
921
- settings.storageConnections,
922
- connectionName
923
- );
898
+ const connection = ownObject(settings.storageConnections, connectionName);
924
899
  if (!connection) {
925
900
  throw new Error(
926
901
  `Invalid pi-sync settings: sync setup \u201C${selectedName}\u201D references missing storage connection \u201C${connectionName}\u201D.`
@@ -932,7 +907,8 @@ function configFromSettings(settings, setupName) {
932
907
  connectionName,
933
908
  connection,
934
909
  settings.onSwitch,
935
- settings.skipSecretScan ?? false
910
+ settings.skipSecretScan ?? false,
911
+ settings.showStatus ?? true
936
912
  );
937
913
  }
938
914
  async function loadPartialConfig(setupName) {
@@ -942,17 +918,18 @@ async function loadPartialConfig(setupName) {
942
918
  ...storageReviewFromConfig(config),
943
919
  include: [...config.include],
944
920
  automatic: config.automatic,
945
- onSwitch: config.onSwitch
921
+ onSwitch: config.onSwitch,
922
+ showStatus: config.showStatus
946
923
  };
947
924
  }
948
925
  function syncSetupStorageReview(setupName, setup, connectionName, connection) {
949
926
  return storageReviewFromConfig(
950
- resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false)
927
+ resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false, true)
951
928
  );
952
929
  }
953
930
  function syncSetupReviewIdentity(setupName, setup, connectionName, connection) {
954
931
  return syncConfigReviewIdentity(
955
- resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false)
932
+ resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false, true)
956
933
  );
957
934
  }
958
935
  function syncConfigReviewIdentity(config) {
@@ -968,7 +945,7 @@ function syncConfigReviewFingerprint(config) {
968
945
  return createHash("sha256").update(syncConfigReviewIdentity(config)).digest("hex");
969
946
  }
970
947
  function syncCheckConfigFingerprint(config) {
971
- return createHash("sha256").update(JSON.stringify(config)).digest("hex");
948
+ return createHash("sha256").update(JSON.stringify({ ...config, showStatus: void 0 })).digest("hex");
972
949
  }
973
950
  function storageReviewFromConfig(config) {
974
951
  return {
@@ -978,7 +955,7 @@ function storageReviewFromConfig(config) {
978
955
  ...config.backend.type === "s3" ? { bucket: config.backend.destination.bucket } : config.backend.type === "git" ? { branch: config.backend.destination.branch } : {}
979
956
  };
980
957
  }
981
- function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitch, skipSecretScan) {
958
+ function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitch, skipSecretScan, showStatus) {
982
959
  const storagePath = normalizeStoragePath(setup.storage.path);
983
960
  const namespace = storagePath === "./" ? "root" : storagePath.slice(storagePath.lastIndexOf("/") + 1);
984
961
  const include = normalizeSyncInclude(setup.sync.include);
@@ -990,7 +967,8 @@ function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitc
990
967
  include,
991
968
  automatic: setup.sync.automatic,
992
969
  onSwitch,
993
- skipSecretScan
970
+ skipSecretScan,
971
+ showStatus
994
972
  };
995
973
  if (connection.type === "git") {
996
974
  return {
@@ -1084,4 +1062,4 @@ export {
1084
1062
  sessionTokenWarnings,
1085
1063
  syncSessionsWarnings
1086
1064
  };
1087
- //# sourceMappingURL=chunk-C7XY5NF5.ts.map
1065
+ //# sourceMappingURL=chunk-4MPIH46N.ts.map