@narumitw/pi-sync 0.49.15 → 0.50.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/README.md +49 -16
  2. package/dist/chunks/attention-widget-Z7G3SYYJ.ts +9 -0
  3. package/dist/chunks/attention-widget-Z7G3SYYJ.ts.map +7 -0
  4. package/dist/chunks/chunk-2B2ELUF5.ts +29 -0
  5. package/dist/chunks/chunk-2B2ELUF5.ts.map +7 -0
  6. package/dist/chunks/{chunk-ARN2672F.ts → chunk-4CHTZMEO.ts} +23 -55
  7. package/dist/chunks/chunk-4CHTZMEO.ts.map +7 -0
  8. package/dist/chunks/chunk-6LP3MCR2.ts +115 -0
  9. package/dist/chunks/chunk-6LP3MCR2.ts.map +7 -0
  10. package/dist/chunks/{chunk-JF6MZCT4.ts → chunk-AUSDLD6F.ts} +4 -4
  11. package/dist/chunks/chunk-AUSDLD6F.ts.map +7 -0
  12. package/dist/chunks/{chunk-5T442OP3.ts → chunk-C7XY5NF5.ts} +210 -373
  13. package/dist/chunks/chunk-C7XY5NF5.ts.map +7 -0
  14. package/dist/chunks/chunk-CCSN3F47.ts +51 -0
  15. package/dist/chunks/chunk-CCSN3F47.ts.map +7 -0
  16. package/dist/chunks/{chunk-4U6AFLJB.ts → chunk-CHH26D4F.ts} +3 -2
  17. package/dist/chunks/chunk-CHH26D4F.ts.map +7 -0
  18. package/dist/chunks/chunk-CPEHGXJV.ts +50 -0
  19. package/dist/chunks/chunk-CPEHGXJV.ts.map +7 -0
  20. package/dist/chunks/{chunk-5MQ76BK5.ts → chunk-D5X27LCA.ts} +2 -2
  21. package/dist/chunks/chunk-D5X27LCA.ts.map +7 -0
  22. package/dist/chunks/{chunk-WSOBYHIZ.ts → chunk-ETTRJDNP.ts} +4 -4
  23. package/dist/chunks/chunk-ETTRJDNP.ts.map +7 -0
  24. package/dist/chunks/{chunk-44LPGQTI.ts → chunk-G4RNQQZS.ts} +9 -7
  25. package/dist/chunks/chunk-G4RNQQZS.ts.map +7 -0
  26. package/dist/chunks/{chunk-5LZALPCD.ts → chunk-HUDQATCL.ts} +25 -25
  27. package/dist/chunks/chunk-HUDQATCL.ts.map +7 -0
  28. package/dist/chunks/{chunk-X7SQ2EQM.ts → chunk-IJ6FJFA5.ts} +9 -9
  29. package/dist/chunks/chunk-IJ6FJFA5.ts.map +7 -0
  30. package/dist/chunks/{chunk-YQ6UW7IF.ts → chunk-JAAKLL6M.ts} +2 -2
  31. package/dist/chunks/chunk-JAAKLL6M.ts.map +7 -0
  32. package/dist/chunks/chunk-KW7YQEZN.ts +1255 -0
  33. package/dist/chunks/chunk-KW7YQEZN.ts.map +7 -0
  34. package/dist/chunks/{chunk-DFXBKHGN.ts → chunk-LCRI3ABW.ts} +21 -8
  35. package/dist/chunks/chunk-LCRI3ABW.ts.map +7 -0
  36. package/dist/chunks/chunk-LTBWGSBV.ts +12 -0
  37. package/dist/chunks/chunk-LTBWGSBV.ts.map +7 -0
  38. package/dist/chunks/chunk-NJFUIVYL.ts +17 -0
  39. package/dist/chunks/chunk-NJFUIVYL.ts.map +7 -0
  40. package/dist/chunks/{chunk-HVUSHMVL.ts → chunk-OEX4ABY6.ts} +39 -37
  41. package/dist/chunks/chunk-OEX4ABY6.ts.map +7 -0
  42. package/dist/chunks/{chunk-HGV24P4T.ts → chunk-Q2TUM5X6.ts} +3 -3
  43. package/dist/chunks/chunk-Q2TUM5X6.ts.map +7 -0
  44. package/dist/chunks/chunk-S7YBZR42.ts +146 -0
  45. package/dist/chunks/chunk-S7YBZR42.ts.map +7 -0
  46. package/dist/chunks/{chunk-GLQLN5GV.ts → chunk-SL64RLXR.ts} +8 -6
  47. package/dist/chunks/chunk-SL64RLXR.ts.map +7 -0
  48. package/dist/chunks/chunk-SZWBGPID.ts +57 -0
  49. package/dist/chunks/chunk-SZWBGPID.ts.map +7 -0
  50. package/dist/chunks/chunk-T2B5IU74.ts +25 -0
  51. package/dist/chunks/chunk-T2B5IU74.ts.map +7 -0
  52. package/dist/chunks/chunk-T3HH3UEJ.ts +96 -0
  53. package/dist/chunks/chunk-T3HH3UEJ.ts.map +7 -0
  54. package/dist/chunks/chunk-TEN2ZBJV.ts +101 -0
  55. package/dist/chunks/chunk-TEN2ZBJV.ts.map +7 -0
  56. package/dist/chunks/chunk-THMF4BVL.ts +18 -0
  57. package/dist/chunks/chunk-THMF4BVL.ts.map +7 -0
  58. package/dist/chunks/chunk-Z4DQF6L3.ts +12 -0
  59. package/dist/chunks/chunk-Z4DQF6L3.ts.map +7 -0
  60. package/dist/chunks/{chunk-F5QL2GPY.ts → chunk-ZI5MXMEX.ts} +5 -99
  61. package/dist/chunks/chunk-ZI5MXMEX.ts.map +7 -0
  62. package/dist/chunks/{file-selection-RKEHALNQ.ts → file-selection-FTTKAOYX.ts} +10 -7
  63. package/dist/chunks/file-selection-FTTKAOYX.ts.map +7 -0
  64. package/dist/chunks/{git-backend-E3UOUUOM.ts → git-backend-GMVS4PD4.ts} +39 -14
  65. package/dist/chunks/git-backend-GMVS4PD4.ts.map +7 -0
  66. package/dist/chunks/manager-result-dispatcher-FKIBM3GO.ts +22 -0
  67. package/dist/chunks/manager-ui-R3XPUFFS.ts +1680 -0
  68. package/dist/chunks/manager-ui-R3XPUFFS.ts.map +7 -0
  69. package/dist/chunks/remote-selection-ui-D7RBRJNL.ts +21 -0
  70. package/dist/chunks/{s3-backend-552LXHE4.ts → s3-backend-66O4FGBH.ts} +94 -16
  71. package/dist/chunks/s3-backend-66O4FGBH.ts.map +7 -0
  72. package/dist/chunks/{setup-switch-5YDG5YGJ.ts → setup-switch-IS2PEZ3J.ts} +9 -9
  73. package/dist/chunks/setup-wizard-IUB6ESEU.ts +89 -0
  74. package/dist/chunks/setup-wizard-IUB6ESEU.ts.map +7 -0
  75. package/dist/chunks/{snapshot-2RYBUDRA.ts → snapshot-HKEWCVK6.ts} +7 -6
  76. package/dist/chunks/sync-inspection-CST2KL65.ts +20 -0
  77. package/dist/chunks/{sync-operations-3LLIHWZO.ts → sync-operations-LBX3GKCM.ts} +369 -289
  78. package/dist/chunks/sync-operations-LBX3GKCM.ts.map +7 -0
  79. package/dist/chunks/{sync-state-SRN6YRBK.ts → sync-state-Q2FTXTW6.ts} +6 -6
  80. package/dist/chunks/sync-state-Q2FTXTW6.ts.map +7 -0
  81. package/dist/chunks/{webdav-backend-ZD5RHRIU.ts → webdav-backend-ECIKD7SO.ts} +26 -13
  82. package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +7 -0
  83. package/dist/index.ts +476 -341
  84. package/dist/index.ts.map +4 -4
  85. package/docs/settings.md +30 -6
  86. package/package.json +1 -1
  87. package/src/{backend-factory.ts → backends/backend-factory.ts} +4 -4
  88. package/src/backends/backend-identity.ts +40 -0
  89. package/src/backends/backend-types.ts +81 -0
  90. package/src/{git-backend.ts → backends/git/git-backend.ts} +43 -19
  91. package/src/{git-config.ts → backends/git/git-config.ts} +1 -0
  92. package/src/{git-storage.ts → backends/git/git-storage.ts} +2 -2
  93. package/src/{s3-backend.ts → backends/s3/s3-backend.ts} +45 -9
  94. package/src/{s3-client.ts → backends/s3/s3-client.ts} +56 -8
  95. package/src/{sync-backend.ts → backends/sync-backend.ts} +1 -1
  96. package/src/{webdav-backend.ts → backends/webdav/webdav-backend.ts} +22 -10
  97. package/src/{webdav-client.ts → backends/webdav/webdav-client.ts} +1 -1
  98. package/src/{webdav-config.ts → backends/webdav/webdav-config.ts} +1 -0
  99. package/src/commands/command-execution.ts +330 -0
  100. package/src/commands/command-handler.ts +201 -0
  101. package/src/commands/command-types.ts +18 -0
  102. package/src/{command.ts → commands/command.ts} +2 -1
  103. package/src/commands/setup-completions.ts +6 -0
  104. package/src/settings/config.ts +238 -0
  105. package/src/{settings-management.ts → settings/settings-management.ts} +39 -14
  106. package/src/settings/settings-store.ts +109 -0
  107. package/src/settings/settings-types.ts +128 -0
  108. package/src/settings/settings-validation.ts +443 -0
  109. package/src/snapshot/session-paths.ts +84 -0
  110. package/src/{snapshot-apply.ts → snapshot/snapshot-apply.ts} +3 -3
  111. package/src/{snapshot-codec.ts → snapshot/snapshot-codec.ts} +2 -2
  112. package/src/{snapshot-transaction.ts → snapshot/snapshot-transaction.ts} +5 -4
  113. package/src/snapshot/snapshot-types.ts +38 -0
  114. package/src/{snapshot.ts → snapshot/snapshot.ts} +39 -20
  115. package/src/state/json-file.ts +20 -0
  116. package/src/{lock.ts → state/lock.ts} +3 -2
  117. package/src/{operation-availability.ts → state/operation-availability.ts} +2 -2
  118. package/src/state/state-types.ts +20 -0
  119. package/src/state/sync-state-store.ts +62 -0
  120. package/src/sync/automatic-sync.ts +103 -0
  121. package/src/{remote-snapshot.ts → sync/remote-snapshot.ts} +27 -4
  122. package/src/{setup-switch.ts → sync/setup-switch.ts} +6 -7
  123. package/src/sync/signals.ts +10 -0
  124. package/src/sync/startup-check.ts +128 -0
  125. package/src/{sync-decision.ts → sync/sync-decision.ts} +6 -3
  126. package/src/sync/sync-error-guidance.ts +53 -0
  127. package/src/sync/sync-inspection.ts +51 -0
  128. package/src/sync/sync-loaders.ts +54 -0
  129. package/src/{sync-operations.ts → sync/sync-mutations.ts} +34 -325
  130. package/src/sync/sync-operations.ts +12 -0
  131. package/src/{sync-policy.ts → sync/sync-policy.ts} +2 -2
  132. package/src/sync/sync-queries.ts +235 -0
  133. package/src/{sync-state.ts → sync/sync-state.ts} +5 -4
  134. package/src/sync-extension.ts +64 -708
  135. package/src/sync.ts +8 -8
  136. package/src/types.ts +47 -286
  137. package/src/ui/attention-widget.ts +1 -0
  138. package/src/ui/automatic-sync-summary.ts +6 -0
  139. package/src/{cancellable-operation.ts → ui/cancellable-operation.ts} +4 -4
  140. package/src/{file-selection.ts → ui/file-selection.ts} +5 -3
  141. package/src/{manager-attention.ts → ui/manager-attention.ts} +5 -1
  142. package/src/{manager-recovery.ts → ui/manager-recovery.ts} +6 -2
  143. package/src/{manager-state.ts → ui/manager-state.ts} +35 -14
  144. package/src/ui/manager-ui.ts +386 -0
  145. package/src/{remote-selection-ui.ts → ui/remote-selection-ui.ts} +21 -13
  146. package/src/{secret-input.ts → ui/secret-input.ts} +22 -4
  147. package/src/{settings-ui.ts → ui/settings-ui.ts} +17 -13
  148. package/src/ui/setup/git-ui.ts +293 -0
  149. package/src/{s3-credentials-ui.ts → ui/setup/s3-credentials-ui.ts} +13 -21
  150. package/src/ui/setup/s3-ui.ts +217 -0
  151. package/src/ui/setup/setup-actions.ts +127 -0
  152. package/src/ui/setup/setup-location-ui.ts +167 -0
  153. package/src/ui/setup/setup-name-ui.ts +34 -0
  154. package/src/ui/setup/setup-prompts.ts +92 -0
  155. package/src/ui/setup/setup-review.ts +70 -0
  156. package/src/ui/setup/setup-switcher.ts +181 -0
  157. package/src/ui/setup/setup-wizard.ts +39 -0
  158. package/src/ui/setup/text-input.ts +78 -0
  159. package/src/ui/setup/webdav-ui.ts +330 -0
  160. package/src/{storage-connections-ui.ts → ui/storage-connections-ui.ts} +99 -69
  161. package/src/ui/sync-attention.ts +187 -0
  162. package/src/{sync-format.ts → ui/sync-format.ts} +7 -13
  163. package/src/{sync-resolution-ui.ts → ui/sync-resolution-ui.ts} +4 -3
  164. package/src/{sync-setups-ui.ts → ui/sync-setups-ui.ts} +23 -10
  165. package/src/ui/terminal-text.ts +4 -0
  166. package/dist/chunks/chunk-44LPGQTI.ts.map +0 -7
  167. package/dist/chunks/chunk-4U6AFLJB.ts.map +0 -7
  168. package/dist/chunks/chunk-5LZALPCD.ts.map +0 -7
  169. package/dist/chunks/chunk-5MQ76BK5.ts.map +0 -7
  170. package/dist/chunks/chunk-5T442OP3.ts.map +0 -7
  171. package/dist/chunks/chunk-ARN2672F.ts.map +0 -7
  172. package/dist/chunks/chunk-C7LXZ3UH.ts +0 -167
  173. package/dist/chunks/chunk-C7LXZ3UH.ts.map +0 -7
  174. package/dist/chunks/chunk-DFXBKHGN.ts.map +0 -7
  175. package/dist/chunks/chunk-E4COSV32.ts +0 -82
  176. package/dist/chunks/chunk-E4COSV32.ts.map +0 -7
  177. package/dist/chunks/chunk-F5QL2GPY.ts.map +0 -7
  178. package/dist/chunks/chunk-GLQLN5GV.ts.map +0 -7
  179. package/dist/chunks/chunk-HGV24P4T.ts.map +0 -7
  180. package/dist/chunks/chunk-HVUSHMVL.ts.map +0 -7
  181. package/dist/chunks/chunk-JF6MZCT4.ts.map +0 -7
  182. package/dist/chunks/chunk-WSOBYHIZ.ts.map +0 -7
  183. package/dist/chunks/chunk-X7SQ2EQM.ts.map +0 -7
  184. package/dist/chunks/chunk-YQ6UW7IF.ts.map +0 -7
  185. package/dist/chunks/file-selection-RKEHALNQ.ts.map +0 -7
  186. package/dist/chunks/git-backend-E3UOUUOM.ts.map +0 -7
  187. package/dist/chunks/manager-result-dispatcher-LWPYJAD5.ts +0 -18
  188. package/dist/chunks/manager-ui-NF4RUHBY.ts +0 -2785
  189. package/dist/chunks/manager-ui-NF4RUHBY.ts.map +0 -7
  190. package/dist/chunks/remote-selection-ui-NUF5P4ZJ.ts +0 -17
  191. package/dist/chunks/s3-backend-552LXHE4.ts.map +0 -7
  192. package/dist/chunks/sync-operations-3LLIHWZO.ts.map +0 -7
  193. package/dist/chunks/webdav-backend-ZD5RHRIU.ts.map +0 -7
  194. package/src/config.ts +0 -923
  195. package/src/git-ui.ts +0 -307
  196. package/src/manager-helpers.ts +0 -64
  197. package/src/manager-ui.ts +0 -997
  198. package/src/sync-attention.ts +0 -100
  199. package/src/webdav-ui.ts +0 -421
  200. /package/dist/chunks/{manager-result-dispatcher-LWPYJAD5.ts.map → manager-result-dispatcher-FKIBM3GO.ts.map} +0 -0
  201. /package/dist/chunks/{remote-selection-ui-NUF5P4ZJ.ts.map → remote-selection-ui-D7RBRJNL.ts.map} +0 -0
  202. /package/dist/chunks/{setup-switch-5YDG5YGJ.ts.map → setup-switch-IS2PEZ3J.ts.map} +0 -0
  203. /package/dist/chunks/{snapshot-2RYBUDRA.ts.map → snapshot-HKEWCVK6.ts.map} +0 -0
  204. /package/dist/chunks/{sync-state-SRN6YRBK.ts.map → sync-inspection-CST2KL65.ts.map} +0 -0
  205. /package/src/{git-runner.ts → backends/git/git-runner.ts} +0 -0
  206. /package/src/{config-errors.ts → settings/config-errors.ts} +0 -0
  207. /package/src/{config-file.ts → settings/config-file.ts} +0 -0
  208. /package/src/{snapshot-paths.ts → snapshot/snapshot-paths.ts} +0 -0
  209. /package/src/{lock-policy.ts → state/lock-policy.ts} +0 -0
  210. /package/src/{lockfile-fs.ts → state/lockfile-fs.ts} +0 -0
  211. /package/src/{state-directory.ts → state/state-directory.ts} +0 -0
  212. /package/src/{sync-errors.ts → sync/sync-errors.ts} +0 -0
  213. /package/src/{manager-result-dispatcher.ts → ui/manager-result-dispatcher.ts} +0 -0
@@ -1,33 +1,17 @@
1
1
  // @generated by scripts/build-runtime.mjs; do not edit.
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
- createSyncBackend,
5
4
  formatRemoteSelectionStatus,
5
+ readRemoteSnapshot,
6
6
  readSnapshotForHead,
7
7
  requireCompatibleRemoteSelection
8
- } from "./chunk-5LZALPCD.ts";
8
+ } from "./chunk-HUDQATCL.ts";
9
9
  import {
10
- applySnapshotTransaction,
11
- recoverPendingSnapshotTransactions
12
- } from "./chunk-X7SQ2EQM.ts";
10
+ inspectSync
11
+ } from "./chunk-SZWBGPID.ts";
13
12
  import {
14
- inspectLock,
15
- isLockGuardHeld,
16
- isStaleLock,
17
- withLock
18
- } from "./chunk-WSOBYHIZ.ts";
19
- import {
20
- SyncDecisionRequiredError,
21
- countPreservedRemoteFiles,
22
- errorMessage,
23
- formatDiff,
24
- formatPullSummary,
25
- formatPushSummary,
26
- formatRollbackSummary,
27
- formatSnapshotOnlyDiff,
28
- publicationCapabilityDescription,
29
- safeTerminalText
30
- } from "./chunk-C7LXZ3UH.ts";
13
+ createSyncBackend
14
+ } from "./chunk-T2B5IU74.ts";
31
15
  import {
32
16
  canPullRemoteSessionsOnFirstSync,
33
17
  canPullRemoteSettingsOnFirstSync,
@@ -40,7 +24,47 @@ import {
40
24
  snapshotHashesMatchState,
41
25
  snapshotsMatch,
42
26
  syncPolicyChanged
43
- } from "./chunk-JF6MZCT4.ts";
27
+ } from "./chunk-AUSDLD6F.ts";
28
+ import {
29
+ applySnapshotTransaction,
30
+ recoverPendingSnapshotTransactions
31
+ } from "./chunk-IJ6FJFA5.ts";
32
+ import {
33
+ inspectLock,
34
+ isLockGuardHeld,
35
+ isStaleLock,
36
+ withLock
37
+ } from "./chunk-ETTRJDNP.ts";
38
+ import {
39
+ throwIfAborted
40
+ } from "./chunk-NJFUIVYL.ts";
41
+ import {
42
+ readStateForConfig,
43
+ writeStateForConfig
44
+ } from "./chunk-CCSN3F47.ts";
45
+ import {
46
+ safeTerminalText
47
+ } from "./chunk-LTBWGSBV.ts";
48
+ import {
49
+ loadConfig,
50
+ syncConfigReviewIdentity,
51
+ syncSessionsWarnings
52
+ } from "./chunk-C7XY5NF5.ts";
53
+ import "./chunk-6LP3MCR2.ts";
54
+ import {
55
+ encodeSnapshot
56
+ } from "./chunk-Q2TUM5X6.ts";
57
+ import {
58
+ stateDir
59
+ } from "./chunk-ZI5MXMEX.ts";
60
+ import "./chunk-TEN2ZBJV.ts";
61
+ import {
62
+ expectedRemoteHead
63
+ } from "./chunk-D5X27LCA.ts";
64
+ import {
65
+ SyncDecisionRequiredError,
66
+ errorMessage
67
+ } from "./chunk-2B2ELUF5.ts";
44
68
  import {
45
69
  createSnapshot,
46
70
  filterSnapshotForConfigPolicy,
@@ -50,30 +74,19 @@ import {
50
74
  regenerateSnapshotIdentity,
51
75
  scanSnapshot,
52
76
  sessionSnapshotPathFromAbsolute,
53
- sessionStorageRoot,
54
77
  snapshotIncludesSessions,
55
78
  snapshotTarget,
56
79
  snapshotWithoutSessions
57
- } from "./chunk-HVUSHMVL.ts";
80
+ } from "./chunk-OEX4ABY6.ts";
81
+ import {
82
+ sessionStorageRoot
83
+ } from "./chunk-THMF4BVL.ts";
58
84
  import {
59
85
  agentDir,
60
- loadConfig,
61
- readStateForConfig,
62
86
  sessionDirForApply,
63
- syncConfigReviewFingerprint,
64
- syncConfigReviewIdentity,
65
- syncSessionsWarnings,
66
- writeStateForConfig
67
- } from "./chunk-5T442OP3.ts";
68
- import {
69
- encodeSnapshot
70
- } from "./chunk-HGV24P4T.ts";
71
- import {
72
- stateDir
73
- } from "./chunk-F5QL2GPY.ts";
74
- import {
75
- expectedRemoteHead
76
- } from "./chunk-5MQ76BK5.ts";
87
+ sessionDirFromContext,
88
+ snapshotOptionsForContext
89
+ } from "./chunk-T3HH3UEJ.ts";
77
90
  import {
78
91
  assertWithinRoot,
79
92
  includeFromSelectionConfig,
@@ -81,16 +94,15 @@ import {
81
94
  isDeniedPath,
82
95
  isPathInside,
83
96
  parentPaths,
84
- remoteSelectionMismatch,
85
97
  safeJoin,
86
98
  toPosix
87
- } from "./chunk-YQ6UW7IF.ts";
99
+ } from "./chunk-JAAKLL6M.ts";
88
100
 
89
- // src/sync-operations.ts
101
+ // src/sync/sync-mutations.ts
90
102
  import fs2 from "node:fs/promises";
91
103
  import path2 from "node:path";
92
104
 
93
- // src/snapshot-apply.ts
105
+ // src/snapshot/snapshot-apply.ts
94
106
  import { createHash } from "node:crypto";
95
107
  import fs from "node:fs/promises";
96
108
  import path from "node:path";
@@ -297,7 +309,125 @@ async function assertNoSymlinkParents(root, target) {
297
309
  }
298
310
  }
299
311
 
300
- // src/sync-decision.ts
312
+ // src/ui/sync-format.ts
313
+ function formatDiff(local, remote) {
314
+ const localMap = fileHashMap(local);
315
+ const remoteMap = fileHashMap(remote);
316
+ const allPaths = [.../* @__PURE__ */ new Set([...Object.keys(localMap), ...Object.keys(remoteMap)])].sort();
317
+ const lines = [
318
+ `local: ${local.files.length} files`,
319
+ `remote: ${remote.id} (${remote.files.length} files)`,
320
+ ""
321
+ ];
322
+ let changed = 0;
323
+ for (const filePath of allPaths) {
324
+ if (!localMap[filePath]) {
325
+ lines.push(`Remote only: ${filePath}`);
326
+ changed += 1;
327
+ } else if (!remoteMap[filePath]) {
328
+ lines.push(`Local only: ${filePath}`);
329
+ changed += 1;
330
+ } else if (localMap[filePath] !== remoteMap[filePath]) {
331
+ lines.push(`Different: ${filePath}`);
332
+ changed += 1;
333
+ }
334
+ }
335
+ if (changed === 0) lines.push("No file differences.");
336
+ return lines.join("\n");
337
+ }
338
+ function formatSnapshotOnlyDiff(title, snapshot) {
339
+ return [`${title}: ${snapshot.id}`, ...snapshot.files.map((file) => `Add: ${file.path}`)].join(
340
+ "\n"
341
+ );
342
+ }
343
+ function formatPushSummary(config, destination, upload, head, preservedRemoteFileCount = 0, remote) {
344
+ return [
345
+ `Sync setup: ${safeTerminalText(config.setupName)}`,
346
+ `Storage location: ${safeTerminalText(destination)}`,
347
+ `Upload ${upload.files.length} files from ${safeTerminalText(agentDir())}.`,
348
+ `Sessions: ${upload.syncSessions ? "included \u2014 may contain private conversations" : "not included"}`,
349
+ head ? `Remote latest: ${head.snapshotId}` : "Remote latest: empty",
350
+ "Publication effect: the backend's active head will reference the new immutable snapshot.",
351
+ ...remote && inspectRemoteSelection(config.include, remote).kind === "different" ? [
352
+ "Included-content policy effect: replace the differing remote selection with this setup's local selection."
353
+ ] : [],
354
+ formatPublicationPreview(remote, upload),
355
+ preservedRemoteFileCount > 0 ? `Possible secrets in locally managed files were scanned before this prompt; ${preservedRemoteFileCount} preserved remote file(s) were not rescanned.` : "Possible secrets were scanned before this prompt."
356
+ ].join("\n");
357
+ }
358
+ function formatApplyPreview(local, remote) {
359
+ return formatDirectionalChanges(local, remote, {
360
+ add: "Add locally",
361
+ update: "Update locally",
362
+ remove: "Remove locally"
363
+ });
364
+ }
365
+ function formatPullSummary(config, destination, local, remote, protectedSessionCount) {
366
+ return [
367
+ `Sync setup: ${safeTerminalText(config.setupName)}`,
368
+ `Storage location: ${safeTerminalText(destination)}`,
369
+ `Snapshot: ${safeTerminalText(remote.id)}`,
370
+ `Sessions: ${remote.syncSessions ? "included \u2014 may contain private conversations" : "not included"}`,
371
+ `Protected live sessions: ${protectedSessionCount || "none"}`,
372
+ formatApplyPreview(local, remote),
373
+ "A local backup is created before these writes/deletes. The remote active snapshot is unchanged."
374
+ ].join("\n");
375
+ }
376
+ function formatRollbackSummary(config, destination, local, remote, requestedSnapshot, protectedSessionCount) {
377
+ return [
378
+ `Sync setup: ${safeTerminalText(config.setupName)}`,
379
+ `Storage location: ${safeTerminalText(destination)}`,
380
+ `Snapshot: ${safeTerminalText(requestedSnapshot)}`,
381
+ `Sessions: ${remote.syncSessions ? "included \u2014 may contain private conversations" : "not included"}`,
382
+ `Protected live sessions: ${protectedSessionCount || "none"}`,
383
+ formatApplyPreview(local, remote),
384
+ "A local backup is created before applying; the backend's active head will change."
385
+ ].join("\n");
386
+ }
387
+ function formatPublicationPreview(remote, upload) {
388
+ if (!remote) {
389
+ return ["Remote is empty.", ...upload.files.map((file) => `Add remotely: ${file.path}`)].join(
390
+ "\n"
391
+ );
392
+ }
393
+ return formatDirectionalChanges(remote, upload, {
394
+ add: "Add remotely",
395
+ update: "Update remotely",
396
+ remove: "Remove remotely"
397
+ });
398
+ }
399
+ function formatDirectionalChanges(before, after, labels) {
400
+ const beforeMap = fileHashMap(before);
401
+ const afterMap = fileHashMap(after);
402
+ const paths = [.../* @__PURE__ */ new Set([...Object.keys(beforeMap), ...Object.keys(afterMap)])].sort();
403
+ const lines = [];
404
+ for (const filePath of paths) {
405
+ if (!beforeMap[filePath]) lines.push(`${labels.add}: ${filePath}`);
406
+ else if (!afterMap[filePath]) lines.push(`${labels.remove}: ${filePath}`);
407
+ else if (beforeMap[filePath] !== afterMap[filePath])
408
+ lines.push(`${labels.update}: ${filePath}`);
409
+ }
410
+ if (lines.length === 0) lines.push("No file changes.");
411
+ return lines.join("\n");
412
+ }
413
+ function countPreservedRemoteFiles(local, upload) {
414
+ const localPaths = new Set(local.files.map((file) => file.path));
415
+ return upload.files.filter((file) => !localPaths.has(file.path)).length;
416
+ }
417
+ function publicationCapabilityDescription(capability) {
418
+ switch (capability) {
419
+ case "lease-protected":
420
+ return "lease-protected (exact expected-ref update)";
421
+ case "atomic-conditional":
422
+ return "atomic-conditional (verified atomic precondition)";
423
+ case "conditional-required":
424
+ return "conditional-required (read-only until atomic preconditions are verified)";
425
+ case "read-check-write-verify":
426
+ return "read-check-write-verify (visible races rejected; simultaneous unseen races remain possible)";
427
+ }
428
+ }
429
+
430
+ // src/sync/sync-decision.ts
301
431
  function createSyncDecision(options) {
302
432
  const { config, state, local, remote, kind } = options;
303
433
  const policyChanged = Boolean(state.lastAppliedSnapshot) && syncPolicyChanged(state, config);
@@ -344,10 +474,9 @@ function safeList(values) {
344
474
  return values.length > 0 ? values.map(safeTerminalText).join(", ") : "none";
345
475
  }
346
476
 
347
- // src/sync-operations.ts
477
+ // src/sync/sync-mutations.ts
348
478
  var STATUS_KEY = "sync";
349
479
  var VERSION = 1;
350
- var DEFAULT_PROFILE = "default";
351
480
  var POST_LOCAL_COMMIT_TIMEOUT_MS = 3e4;
352
481
  var PublicationStatePersistenceError = class extends Error {
353
482
  head;
@@ -373,173 +502,11 @@ var RollbackPublicationError = class extends Error {
373
502
  this.backupPath = backupPath;
374
503
  }
375
504
  };
376
- function backendFor(config, factory) {
377
- return factory(config);
378
- }
379
- async function status(ctx, options, factory = createSyncBackend) {
380
- const config = await loadConfig(options.setup);
381
- throwIfAborted(options.signal);
382
- ctx.ui.setStatus(STATUS_KEY, `checking ${config.setupName}`);
383
- const backend = await backendFor(config, factory);
384
- const local = await createSnapshot(
385
- config.snapshotIdentity,
386
- snapshotOptionsForContext(ctx, config)
387
- );
388
- throwIfAborted(options.signal);
389
- const state = await readStateForConfig(config);
390
- throwIfAborted(options.signal);
391
- const head = await backend.readHead(options.signal);
392
- throwIfAborted(options.signal);
393
- const selectionState = head ? inspectRemoteSelection(config.include, { selection: head.selection, files: [] }) : void 0;
394
- const localChanged = hasLocalChanges(local, state, config);
395
- const remoteText = head ? `remote: ${head.snapshotId} from ${head.machine} at ${head.createdAt}` : "remote: empty";
396
- const remoteChanged = remoteChangedSinceState(
397
- head,
398
- state,
399
- config,
400
- (left, right) => backend.sameRevision(left, right)
401
- );
402
- const warnings = syncSessionsWarnings(config);
403
- ctx.ui.setStatus(STATUS_KEY, void 0);
404
- ctx.ui.notify(
405
- [
406
- `sync setup: ${config.setupName}`,
407
- `storage connection: ${config.connectionName}`,
408
- `storage location: ${safeTerminalText(backend.destination)}`,
409
- `publication safety: ${publicationCapabilityDescription(backend.capability)}`,
410
- `included content: ${config.include.join(", ") || "none"}`,
411
- `sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
412
- remoteText,
413
- formatRemoteSelectionStatus(selectionState),
414
- `local files: ${local.files.length}`,
415
- `local changed since last sync: ${localChanged ? "yes" : "no"}`,
416
- `remote changed since last sync: ${remoteChanged ? "yes" : "no"}`,
417
- ...warnings
418
- ].join("\n"),
419
- localChanged || remoteChanged || selectionState?.kind === "different" || warnings.length > 0 ? "warning" : "info"
420
- );
421
- }
422
- async function diff(ctx, options, factory = createSyncBackend) {
423
- const config = await loadConfig(options.setup);
424
- throwIfAborted(options.signal);
425
- ctx.ui.setStatus(STATUS_KEY, `checking ${config.setupName}`);
426
- const backend = await backendFor(config, factory);
427
- const local = await createSnapshot(
428
- config.snapshotIdentity,
429
- snapshotOptionsForContext(ctx, config)
430
- );
431
- throwIfAborted(options.signal);
432
- const { snapshot: remote, selectionState } = await readRemoteSnapshot(
433
- backend,
434
- config,
435
- options.signal,
436
- { allowSelectionDifference: true }
437
- );
438
- throwIfAborted(options.signal);
439
- ctx.ui.setStatus(STATUS_KEY, void 0);
440
- const warnings = syncSessionsWarnings(config);
441
- const header = [
442
- `sync setup: ${config.setupName}`,
443
- `storage connection: ${config.connectionName}`,
444
- `storage location: ${safeTerminalText(backend.destination)}`,
445
- `included content: ${config.include.join(", ") || "none"}`,
446
- `sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
447
- formatRemoteSelectionStatus(selectionState),
448
- ...warnings
449
- ].join("\n");
450
- const level = warnings.length > 0 || selectionState?.kind === "different" ? "warning" : "info";
451
- if (!remote) {
452
- ctx.ui.notify(
453
- `${header}
454
-
455
- ${formatSnapshotOnlyDiff("Remote is empty. Local push would upload", local)}`,
456
- level
457
- );
458
- return;
459
- }
460
- ctx.ui.notify(`${header}
461
-
462
- ${formatDiff(local, remote)}`, level);
463
- }
464
- async function doctor(ctx, options, factory = createSyncBackend) {
465
- const messages = [];
466
- let level = "info";
467
- let snapshotOptions = {};
468
- let profile = DEFAULT_PROFILE;
469
- let backend;
470
- let backendSummary = [];
471
- try {
472
- const config = await loadConfig(options.setup);
473
- throwIfAborted(options.signal);
474
- backend = await backendFor(config, factory);
475
- profile = config.snapshotIdentity;
476
- snapshotOptions = snapshotOptionsForContext(ctx, config);
477
- messages.push(
478
- `config: ok (sync setup ${config.setupName})`,
479
- `included content: ${config.include.join(", ") || "none"}`,
480
- `sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`
481
- );
482
- backendSummary = [
483
- `storage location: ${safeTerminalText(backend.destination)}`,
484
- `publication safety: ${publicationCapabilityDescription(backend.capability)}`
485
- ];
486
- const warnings = syncSessionsWarnings(config);
487
- if (warnings.length > 0) {
488
- level = "warning";
489
- messages.push(...warnings);
490
- }
491
- } catch (error) {
492
- throwIfAborted(options.signal);
493
- level = "warning";
494
- messages.push(`config: ${errorMessage(error)}`);
495
- }
496
- const local = await createSnapshot(profile, snapshotOptions);
497
- throwIfAborted(options.signal);
498
- const secrets = scanSnapshot(local);
499
- if (secrets.length > 0) {
500
- level = "warning";
501
- messages.push("secret scan: possible secrets found:");
502
- messages.push(...secrets.map((secret) => `- ${secret}`));
503
- } else {
504
- messages.push(`secret scan: ok (${local.files.length} files checked)`);
505
- }
506
- const lock = await inspectLock();
507
- throwIfAborted(options.signal);
508
- if (lock.status === "valid" && isStaleLock(lock.lock)) {
509
- level = "warning";
510
- messages.push(
511
- `lock: stale (pid ${lock.lock.pid}); run /sync unlock after verifying no sync is running`
512
- );
513
- } else if (lock.status === "valid") {
514
- messages.push(`lock: held by pid ${lock.lock.pid} since ${lock.lock.startedAt}`);
515
- } else if (lock.status === "unreadable") {
516
- level = "warning";
517
- messages.push(
518
- "lock: unreadable; use /sync unlock --stale only after verifying no sync is running"
519
- );
520
- } else if (await isLockGuardHeld()) {
521
- throwIfAborted(options.signal);
522
- level = "warning";
523
- messages.push("lock: guard active while metadata is missing or still being initialized");
524
- } else {
525
- messages.push("lock: free");
526
- }
527
- if (backend) {
528
- messages.push(...backendSummary);
529
- const diagnostics = await backend.diagnose(options.signal);
530
- throwIfAborted(options.signal);
531
- for (const diagnostic of diagnostics) {
532
- messages.push(diagnostic.message);
533
- if (diagnostic.level !== "info") level = "warning";
534
- }
535
- }
536
- ctx.ui.notify(messages.join("\n"), level);
537
- }
538
505
  async function push(ctx, options, input, factory = createSyncBackend) {
539
506
  const config = input?.config ?? await loadConfig(options.setup);
540
507
  throwIfAborted(options.signal);
541
508
  ctx.ui.setStatus(STATUS_KEY, `pushing ${config.setupName}`);
542
- const backend = input?.backend ?? await backendFor(config, factory);
509
+ const backend = input?.backend ?? await factory(config);
543
510
  const state = input?.state ?? await readStateForConfig(config);
544
511
  throwIfAborted(options.signal);
545
512
  const local = input?.local ?? await createSnapshot(config.snapshotIdentity, snapshotOptionsForContext(ctx, config));
@@ -659,7 +626,7 @@ async function pull(ctx, options, factory = createSyncBackend) {
659
626
  const config = await loadConfig(options.setup);
660
627
  throwIfAborted(options.signal);
661
628
  ctx.ui.setStatus(STATUS_KEY, `pulling ${config.setupName}`);
662
- const backend = await backendFor(config, factory);
629
+ const backend = await factory(config);
663
630
  const state = await readStateForConfig(config);
664
631
  throwIfAborted(options.signal);
665
632
  const local = await createSnapshot(
@@ -748,7 +715,7 @@ async function pull(ctx, options, factory = createSyncBackend) {
748
715
  async function syncBoth(ctx, options, factory = createSyncBackend) {
749
716
  const config = await loadConfig(options.setup);
750
717
  throwIfAborted(options.signal);
751
- const backend = await backendFor(config, factory);
718
+ const backend = await factory(config);
752
719
  const state = await readStateForConfig(config);
753
720
  throwIfAborted(options.signal);
754
721
  const local = await createSnapshot(
@@ -861,52 +828,12 @@ async function syncBoth(ctx, options, factory = createSyncBackend) {
861
828
  }
862
829
  if (!options.silent) ctx.ui.notify("pi-sync is already up to date.", "info");
863
830
  }
864
- async function history(ctx, options, factory = createSyncBackend) {
865
- const config = await loadConfig(options.setup);
866
- throwIfAborted(options.signal);
867
- const backend = await backendFor(config, factory);
868
- const snapshots = (await backend.listHistory(options.signal)).slice(-20).reverse();
869
- throwIfAborted(options.signal);
870
- if (snapshots.length === 0) {
871
- ctx.ui.notify("No remote pi-sync history found.", "info");
872
- return;
873
- }
874
- const currentSnapshot = snapshots[0]?.snapshotId;
875
- if (ctx.mode === "tui") {
876
- const labels = snapshots.map(
877
- (item, index2) => `${index2 + 1}. ${item.createdAt} \xB7 ${safeTerminalText(item.machine)} \xB7 ${item.snapshotId}${item.snapshotId === currentSnapshot ? " (current)" : ""}${item.syncSessions ? " \xB7 sessions" : ""}`
878
- );
879
- const selected = await ctx.ui.select(
880
- `History for sync setup \u201C${safeTerminalText(config.setupName)}\u201D
881
-
882
- Choose a snapshot to preview rollback.`,
883
- [...labels, "Back"]
884
- );
885
- if (!selected || selected === "Back") return;
886
- throwIfAborted(options.signal);
887
- const index = labels.indexOf(selected);
888
- const snapshot = snapshots[index];
889
- if (!snapshot) return;
890
- await withLock(
891
- "rollback",
892
- () => rollback(ctx, { ...options, args: [snapshot.snapshotRef], yes: false }, factory, {
893
- backendIdentity: backend.identity,
894
- setup: config.setupName
895
- })
896
- );
897
- return;
898
- }
899
- ctx.ui.notify(
900
- snapshots.map((item) => `${item.snapshotRef} ${item.createdAt} ${safeTerminalText(item.machine)}`).join("\n"),
901
- "info"
902
- );
903
- }
904
831
  async function rollback(ctx, options, factory = createSyncBackend, expectedSelection) {
905
832
  const target = options.args[0];
906
833
  if (!target) throw new Error("Usage: /sync rollback <snapshot-id> [--yes]");
907
834
  const config = await loadConfig(options.setup);
908
835
  throwIfAborted(options.signal);
909
- const backend = await backendFor(config, factory);
836
+ const backend = await factory(config);
910
837
  if (expectedSelection && (backend.identity !== expectedSelection.backendIdentity || config.setupName !== expectedSelection.setup)) {
911
838
  throw new Error(
912
839
  "Sync setup or storage location changed while history was open; reopen history and retry."
@@ -998,21 +925,6 @@ function protectedSessionPaths(ctx) {
998
925
  const snapshotPath = sessionFile ? sessionSnapshotPathFromAbsolute(sessionFile, sessionDirFromContext(ctx)) : void 0;
999
926
  return snapshotPath ? /* @__PURE__ */ new Set([snapshotPath]) : /* @__PURE__ */ new Set();
1000
927
  }
1001
- function snapshotOptionsForContext(ctx, config) {
1002
- return {
1003
- include: config.include,
1004
- sessionDir: sessionDirFromContext(ctx)
1005
- };
1006
- }
1007
- function sessionDirFromContext(ctx) {
1008
- const manager = ctx.sessionManager;
1009
- const usesDefaultSessionDir = manager.usesDefaultSessionDir;
1010
- if (typeof usesDefaultSessionDir === "function" && usesDefaultSessionDir.call(manager)) {
1011
- return void 0;
1012
- }
1013
- const getSessionDir = manager.getSessionDir;
1014
- return typeof getSessionDir === "function" ? getSessionDir.call(manager) : void 0;
1015
- }
1016
928
  async function maybeReload(ctx, signal) {
1017
929
  if (signal?.aborted || !("reload" in ctx)) return;
1018
930
  if (ctx.hasUI && await ctx.ui.confirm(
@@ -1042,21 +954,6 @@ async function snapshotForUpload(backend, config, local, head, remote, signal, o
1042
954
  }
1043
955
  return mergeRemotePreservedFiles(local, snapshot, config);
1044
956
  }
1045
- async function readRemoteSnapshot(backend, config, signal, options = {}) {
1046
- const head = await backend.readHead(signal);
1047
- if (!head) return { head: void 0, snapshot: void 0, selectionState: void 0 };
1048
- const snapshot = await readSnapshotForHead(backend, head, signal);
1049
- const selectionState = inspectRemoteSelection(config.include, snapshot);
1050
- if (!options.allowSelectionDifference && selectionState.kind === "different") {
1051
- const configIdentity = syncConfigReviewFingerprint(config);
1052
- throw remoteSelectionMismatch(config, selectionState.include, configIdentity);
1053
- }
1054
- return {
1055
- head,
1056
- snapshot: filterSnapshotForConfigPolicy(snapshot, config),
1057
- selectionState
1058
- };
1059
- }
1060
957
  async function confirmPush(ctx, options, config, backend, local, upload, head, remote, title = snapshotIncludesSessions(upload) ? "Push pi settings and sessions?" : "Push pi settings?") {
1061
958
  throwIfAborted(options.signal);
1062
959
  if (options.yes) return true;
@@ -1077,10 +974,6 @@ async function confirmPush(ctx, options, config, backend, local, upload, head, r
1077
974
  ctx.ui.notify("Push cancelled.", "info");
1078
975
  return false;
1079
976
  }
1080
- function throwIfAborted(signal) {
1081
- if (!signal?.aborted) return;
1082
- throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
1083
- }
1084
977
  function sameRemoteHead(backend, left, right) {
1085
978
  if (!left || !right) return left === right;
1086
979
  return backend.sameRevision(left.revision, right.revision);
@@ -1098,6 +991,193 @@ async function backupLocal(profile, options = {}, signal) {
1098
991
  await fs2.writeFile(backupPath, encoded, { signal });
1099
992
  return backupPath;
1100
993
  }
994
+
995
+ // src/sync/sync-queries.ts
996
+ var STATUS_KEY2 = "sync";
997
+ var DEFAULT_PROFILE = "default";
998
+ async function status(ctx, options, factory = createSyncBackend) {
999
+ const config = await loadConfig(options.setup);
1000
+ throwIfAborted(options.signal);
1001
+ ctx.ui.setStatus(STATUS_KEY2, `checking ${config.setupName}`);
1002
+ const { head, selectionState, localChanged, remoteChanged, localFiles, destination, capability } = await inspectSync(config, snapshotOptionsForContext(ctx, config), options.signal, factory);
1003
+ throwIfAborted(options.signal);
1004
+ const remoteText = head ? `remote: ${head.snapshotId} from ${head.machine} at ${head.createdAt}` : "remote: empty";
1005
+ const warnings = syncSessionsWarnings(config);
1006
+ ctx.ui.setStatus(STATUS_KEY2, void 0);
1007
+ ctx.ui.notify(
1008
+ [
1009
+ `sync setup: ${config.setupName}`,
1010
+ `storage connection: ${config.connectionName}`,
1011
+ `storage location: ${safeTerminalText(destination)}`,
1012
+ `publication safety: ${publicationCapabilityDescription(capability)}`,
1013
+ `included content: ${config.include.join(", ") || "none"}`,
1014
+ `sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
1015
+ remoteText,
1016
+ formatRemoteSelectionStatus(selectionState),
1017
+ `local files: ${localFiles}`,
1018
+ `local changed since last sync: ${localChanged ? "yes" : "no"}`,
1019
+ `remote changed since last sync: ${remoteChanged ? "yes" : "no"}`,
1020
+ ...warnings
1021
+ ].join("\n"),
1022
+ localChanged || remoteChanged || selectionState?.kind === "different" || warnings.length > 0 ? "warning" : "info"
1023
+ );
1024
+ }
1025
+ async function diff(ctx, options, factory = createSyncBackend) {
1026
+ const config = await loadConfig(options.setup);
1027
+ throwIfAborted(options.signal);
1028
+ ctx.ui.setStatus(STATUS_KEY2, `checking ${config.setupName}`);
1029
+ const backend = await factory(config);
1030
+ const local = await createSnapshot(
1031
+ config.snapshotIdentity,
1032
+ snapshotOptionsForContext(ctx, config)
1033
+ );
1034
+ throwIfAborted(options.signal);
1035
+ const { snapshot: remote, selectionState } = await readRemoteSnapshot(
1036
+ backend,
1037
+ config,
1038
+ options.signal,
1039
+ { allowSelectionDifference: true }
1040
+ );
1041
+ throwIfAborted(options.signal);
1042
+ ctx.ui.setStatus(STATUS_KEY2, void 0);
1043
+ const warnings = syncSessionsWarnings(config);
1044
+ const header = [
1045
+ `sync setup: ${config.setupName}`,
1046
+ `storage connection: ${config.connectionName}`,
1047
+ `storage location: ${safeTerminalText(backend.destination)}`,
1048
+ `included content: ${config.include.join(", ") || "none"}`,
1049
+ `sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
1050
+ formatRemoteSelectionStatus(selectionState),
1051
+ ...warnings
1052
+ ].join("\n");
1053
+ const level = warnings.length > 0 || selectionState?.kind === "different" ? "warning" : "info";
1054
+ if (!remote) {
1055
+ ctx.ui.notify(
1056
+ `${header}
1057
+
1058
+ ${formatSnapshotOnlyDiff("Remote is empty. Local push would upload", local)}`,
1059
+ level
1060
+ );
1061
+ return;
1062
+ }
1063
+ ctx.ui.notify(`${header}
1064
+
1065
+ ${formatDiff(local, remote)}`, level);
1066
+ }
1067
+ async function doctor(ctx, options, factory = createSyncBackend) {
1068
+ const messages = [];
1069
+ let level = "info";
1070
+ let snapshotOptions = {};
1071
+ let profile = DEFAULT_PROFILE;
1072
+ let backend;
1073
+ let backendSummary = [];
1074
+ try {
1075
+ const config = await loadConfig(options.setup);
1076
+ throwIfAborted(options.signal);
1077
+ backend = await factory(config);
1078
+ profile = config.snapshotIdentity;
1079
+ snapshotOptions = snapshotOptionsForContext(ctx, config);
1080
+ messages.push(
1081
+ `config: ok (sync setup ${config.setupName})`,
1082
+ `included content: ${config.include.join(", ") || "none"}`,
1083
+ `sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`
1084
+ );
1085
+ backendSummary = [
1086
+ `storage location: ${safeTerminalText(backend.destination)}`,
1087
+ `publication safety: ${publicationCapabilityDescription(backend.capability)}`
1088
+ ];
1089
+ const warnings = syncSessionsWarnings(config);
1090
+ if (warnings.length > 0) {
1091
+ level = "warning";
1092
+ messages.push(...warnings);
1093
+ }
1094
+ } catch (error) {
1095
+ throwIfAborted(options.signal);
1096
+ level = "warning";
1097
+ messages.push(`config: ${errorMessage(error)}`);
1098
+ }
1099
+ const local = await createSnapshot(profile, snapshotOptions);
1100
+ throwIfAborted(options.signal);
1101
+ const secrets = scanSnapshot(local);
1102
+ if (secrets.length > 0) {
1103
+ level = "warning";
1104
+ messages.push("secret scan: possible secrets found:");
1105
+ messages.push(...secrets.map((secret) => `- ${secret}`));
1106
+ } else {
1107
+ messages.push(`secret scan: ok (${local.files.length} files checked)`);
1108
+ }
1109
+ const lock = await inspectLock();
1110
+ throwIfAborted(options.signal);
1111
+ if (lock.status === "valid" && isStaleLock(lock.lock)) {
1112
+ level = "warning";
1113
+ messages.push(
1114
+ `lock: stale (pid ${lock.lock.pid}); run /sync unlock --stale after verifying no sync is running`
1115
+ );
1116
+ } else if (lock.status === "valid") {
1117
+ messages.push(`lock: held by pid ${lock.lock.pid} since ${lock.lock.startedAt}`);
1118
+ } else if (lock.status === "unreadable") {
1119
+ level = "warning";
1120
+ messages.push(
1121
+ "lock: unreadable; use /sync unlock --stale only after verifying no sync is running"
1122
+ );
1123
+ } else if (await isLockGuardHeld()) {
1124
+ throwIfAborted(options.signal);
1125
+ level = "warning";
1126
+ messages.push("lock: guard active while metadata is missing or still being initialized");
1127
+ } else {
1128
+ messages.push("lock: free");
1129
+ }
1130
+ if (backend) {
1131
+ messages.push(...backendSummary);
1132
+ const diagnostics = await backend.diagnose(options.signal);
1133
+ throwIfAborted(options.signal);
1134
+ for (const diagnostic of diagnostics) {
1135
+ messages.push(diagnostic.message);
1136
+ if (diagnostic.level !== "info") level = "warning";
1137
+ }
1138
+ }
1139
+ ctx.ui.notify(messages.join("\n"), level);
1140
+ }
1141
+ async function history(ctx, options, factory = createSyncBackend) {
1142
+ const config = await loadConfig(options.setup);
1143
+ throwIfAborted(options.signal);
1144
+ const backend = await factory(config);
1145
+ const snapshots = (await backend.listHistory(options.signal)).slice(-20).reverse();
1146
+ throwIfAborted(options.signal);
1147
+ if (snapshots.length === 0) {
1148
+ ctx.ui.notify("No remote pi-sync history found.", "info");
1149
+ return;
1150
+ }
1151
+ const currentSnapshot = snapshots[0]?.snapshotId;
1152
+ if (ctx.mode === "tui") {
1153
+ const labels = snapshots.map(
1154
+ (item, index2) => `${index2 + 1}. ${item.createdAt} \xB7 ${safeTerminalText(item.machine)} \xB7 ${item.snapshotId}${item.snapshotId === currentSnapshot ? " (current)" : ""}${item.syncSessions ? " \xB7 sessions" : ""}`
1155
+ );
1156
+ const selected = await ctx.ui.select(
1157
+ `History for sync setup \u201C${safeTerminalText(config.setupName)}\u201D
1158
+
1159
+ Choose a snapshot to preview rollback.`,
1160
+ [...labels, "Back"]
1161
+ );
1162
+ if (!selected || selected === "Back") return;
1163
+ throwIfAborted(options.signal);
1164
+ const index = labels.indexOf(selected);
1165
+ const snapshot = snapshots[index];
1166
+ if (!snapshot) return;
1167
+ await withLock(
1168
+ "rollback",
1169
+ () => rollback(ctx, { ...options, args: [snapshot.snapshotRef], yes: false }, factory, {
1170
+ backendIdentity: backend.identity,
1171
+ setup: config.setupName
1172
+ })
1173
+ );
1174
+ return;
1175
+ }
1176
+ ctx.ui.notify(
1177
+ snapshots.map((item) => `${item.snapshotRef} ${item.createdAt} ${safeTerminalText(item.machine)}`).join("\n"),
1178
+ "info"
1179
+ );
1180
+ }
1101
1181
  export {
1102
1182
  PublicationStatePersistenceError,
1103
1183
  RollbackPublicationError,
@@ -1111,4 +1191,4 @@ export {
1111
1191
  status,
1112
1192
  syncBoth
1113
1193
  };
1114
- //# sourceMappingURL=sync-operations-3LLIHWZO.ts.map
1194
+ //# sourceMappingURL=sync-operations-LBX3GKCM.ts.map