@narumitw/pi-sync 0.49.14 → 0.50.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 (209) hide show
  1. package/README.md +134 -241
  2. package/dist/chunks/chunk-2B2ELUF5.ts +29 -0
  3. package/dist/chunks/chunk-2B2ELUF5.ts.map +7 -0
  4. package/dist/chunks/chunk-6LP3MCR2.ts +115 -0
  5. package/dist/chunks/chunk-6LP3MCR2.ts.map +7 -0
  6. package/dist/chunks/chunk-6UHKGALE.ts +134 -0
  7. package/dist/chunks/chunk-6UHKGALE.ts.map +7 -0
  8. package/dist/chunks/{chunk-RGJ6SOYR.ts → chunk-AUSDLD6F.ts} +4 -4
  9. package/dist/chunks/chunk-AUSDLD6F.ts.map +7 -0
  10. package/dist/chunks/{chunk-3NDD5QPM.ts → chunk-AXXS2BPA.ts} +9 -9
  11. package/dist/chunks/chunk-AXXS2BPA.ts.map +7 -0
  12. package/dist/chunks/{chunk-APZVBSM6.ts → chunk-C7XY5NF5.ts} +214 -365
  13. package/dist/chunks/chunk-C7XY5NF5.ts.map +7 -0
  14. package/dist/chunks/{chunk-4U6AFLJB.ts → chunk-CHH26D4F.ts} +3 -2
  15. package/dist/chunks/chunk-CHH26D4F.ts.map +7 -0
  16. package/dist/chunks/chunk-CPEHGXJV.ts +50 -0
  17. package/dist/chunks/chunk-CPEHGXJV.ts.map +7 -0
  18. package/dist/chunks/{chunk-5MQ76BK5.ts → chunk-D5X27LCA.ts} +2 -2
  19. package/dist/chunks/chunk-D5X27LCA.ts.map +7 -0
  20. package/dist/chunks/{chunk-LJDSHNS3.ts → chunk-GWMHAQG4.ts} +4 -4
  21. package/dist/chunks/chunk-GWMHAQG4.ts.map +7 -0
  22. package/dist/chunks/{chunk-FR6CVXAL.ts → chunk-HUDQATCL.ts} +25 -25
  23. package/dist/chunks/chunk-HUDQATCL.ts.map +7 -0
  24. package/dist/chunks/{chunk-YQ6UW7IF.ts → chunk-JAAKLL6M.ts} +2 -2
  25. package/dist/chunks/chunk-JAAKLL6M.ts.map +7 -0
  26. package/dist/chunks/chunk-JSYMGJQQ.ts +51 -0
  27. package/dist/chunks/chunk-JSYMGJQQ.ts.map +7 -0
  28. package/dist/chunks/chunk-L7TBO32M.ts +25 -0
  29. package/dist/chunks/chunk-L7TBO32M.ts.map +7 -0
  30. package/dist/chunks/{chunk-4F3W4CSC.ts → chunk-LCRI3ABW.ts} +22 -8
  31. package/dist/chunks/chunk-LCRI3ABW.ts.map +7 -0
  32. package/dist/chunks/chunk-LTBWGSBV.ts +12 -0
  33. package/dist/chunks/chunk-LTBWGSBV.ts.map +7 -0
  34. package/dist/chunks/chunk-NJFUIVYL.ts +17 -0
  35. package/dist/chunks/chunk-NJFUIVYL.ts.map +7 -0
  36. package/dist/chunks/{chunk-EWX3TPLQ.ts → chunk-OEX4ABY6.ts} +39 -37
  37. package/dist/chunks/chunk-OEX4ABY6.ts.map +7 -0
  38. package/dist/chunks/chunk-OUHGBFYF.ts +1255 -0
  39. package/dist/chunks/chunk-OUHGBFYF.ts.map +7 -0
  40. package/dist/chunks/{chunk-HGV24P4T.ts → chunk-Q2TUM5X6.ts} +3 -3
  41. package/dist/chunks/chunk-Q2TUM5X6.ts.map +7 -0
  42. package/dist/chunks/{chunk-BHJUISB4.ts → chunk-SL64RLXR.ts} +8 -6
  43. package/dist/chunks/chunk-SL64RLXR.ts.map +7 -0
  44. package/dist/chunks/chunk-T3HH3UEJ.ts +96 -0
  45. package/dist/chunks/chunk-T3HH3UEJ.ts.map +7 -0
  46. package/dist/chunks/chunk-TEN2ZBJV.ts +101 -0
  47. package/dist/chunks/chunk-TEN2ZBJV.ts.map +7 -0
  48. package/dist/chunks/chunk-THMF4BVL.ts +18 -0
  49. package/dist/chunks/chunk-THMF4BVL.ts.map +7 -0
  50. package/dist/chunks/{chunk-LONPFFNP.ts → chunk-X5OM6GXT.ts} +9 -7
  51. package/dist/chunks/chunk-X5OM6GXT.ts.map +7 -0
  52. package/dist/chunks/chunk-XBX4HBLG.ts +57 -0
  53. package/dist/chunks/chunk-XBX4HBLG.ts.map +7 -0
  54. package/dist/chunks/{chunk-5WJNWFTG.ts → chunk-YKJHPTGO.ts} +23 -55
  55. package/dist/chunks/chunk-YKJHPTGO.ts.map +7 -0
  56. package/dist/chunks/chunk-Z4DQF6L3.ts +12 -0
  57. package/dist/chunks/chunk-Z4DQF6L3.ts.map +7 -0
  58. package/dist/chunks/{chunk-F5QL2GPY.ts → chunk-ZI5MXMEX.ts} +5 -99
  59. package/dist/chunks/chunk-ZI5MXMEX.ts.map +7 -0
  60. package/dist/chunks/{file-selection-4UABOP44.ts → file-selection-FTTKAOYX.ts} +10 -7
  61. package/dist/chunks/file-selection-FTTKAOYX.ts.map +7 -0
  62. package/dist/chunks/{git-backend-E3UOUUOM.ts → git-backend-OQW46G2X.ts} +42 -17
  63. package/dist/chunks/git-backend-OQW46G2X.ts.map +7 -0
  64. package/dist/chunks/manager-result-dispatcher-U23ODD32.ts +22 -0
  65. package/dist/chunks/manager-ui-ABATHC36.ts +1680 -0
  66. package/dist/chunks/manager-ui-ABATHC36.ts.map +7 -0
  67. package/dist/chunks/remote-selection-ui-ZE3TY67S.ts +21 -0
  68. package/dist/chunks/{s3-backend-3QZGO2EF.ts → s3-backend-3P33LQHU.ts} +96 -18
  69. package/dist/chunks/s3-backend-3P33LQHU.ts.map +7 -0
  70. package/dist/chunks/{setup-switch-X3W3FALZ.ts → setup-switch-IS2PEZ3J.ts} +9 -9
  71. package/dist/chunks/setup-wizard-VB3VDOV5.ts +89 -0
  72. package/dist/chunks/setup-wizard-VB3VDOV5.ts.map +7 -0
  73. package/dist/chunks/{snapshot-WBSV4MQG.ts → snapshot-HKEWCVK6.ts} +7 -6
  74. package/dist/chunks/sync-inspection-FEP4L5TR.ts +20 -0
  75. package/dist/chunks/{sync-operations-MPAFVVHA.ts → sync-operations-ONJ27QRL.ts} +381 -299
  76. package/dist/chunks/sync-operations-ONJ27QRL.ts.map +7 -0
  77. package/dist/chunks/{sync-state-36H7ULG2.ts → sync-state-Q2FTXTW6.ts} +6 -6
  78. package/dist/chunks/sync-state-Q2FTXTW6.ts.map +7 -0
  79. package/dist/chunks/{webdav-backend-ZD5RHRIU.ts → webdav-backend-ECIKD7SO.ts} +26 -13
  80. package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +7 -0
  81. package/dist/index.ts +477 -342
  82. package/dist/index.ts.map +4 -4
  83. package/docs/settings.md +211 -0
  84. package/package.json +6 -8
  85. package/src/{backend-factory.ts → backends/backend-factory.ts} +4 -4
  86. package/src/backends/backend-identity.ts +40 -0
  87. package/src/backends/backend-types.ts +81 -0
  88. package/src/{git-backend.ts → backends/git/git-backend.ts} +43 -19
  89. package/src/{git-config.ts → backends/git/git-config.ts} +1 -0
  90. package/src/{git-storage.ts → backends/git/git-storage.ts} +2 -2
  91. package/src/{s3-backend.ts → backends/s3/s3-backend.ts} +45 -9
  92. package/src/{s3-client.ts → backends/s3/s3-client.ts} +56 -8
  93. package/src/{sync-backend.ts → backends/sync-backend.ts} +1 -1
  94. package/src/{webdav-backend.ts → backends/webdav/webdav-backend.ts} +22 -10
  95. package/src/{webdav-client.ts → backends/webdav/webdav-client.ts} +1 -1
  96. package/src/{webdav-config.ts → backends/webdav/webdav-config.ts} +1 -0
  97. package/src/commands/command-execution.ts +330 -0
  98. package/src/commands/command-handler.ts +201 -0
  99. package/src/commands/command-types.ts +18 -0
  100. package/src/{command.ts → commands/command.ts} +2 -1
  101. package/src/commands/setup-completions.ts +6 -0
  102. package/src/settings/config.ts +238 -0
  103. package/src/{settings-management.ts → settings/settings-management.ts} +40 -14
  104. package/src/settings/settings-store.ts +109 -0
  105. package/src/settings/settings-types.ts +128 -0
  106. package/src/settings/settings-validation.ts +443 -0
  107. package/src/snapshot/session-paths.ts +84 -0
  108. package/src/{snapshot-apply.ts → snapshot/snapshot-apply.ts} +3 -3
  109. package/src/{snapshot-codec.ts → snapshot/snapshot-codec.ts} +2 -2
  110. package/src/{snapshot-transaction.ts → snapshot/snapshot-transaction.ts} +5 -4
  111. package/src/snapshot/snapshot-types.ts +38 -0
  112. package/src/{snapshot.ts → snapshot/snapshot.ts} +39 -20
  113. package/src/state/json-file.ts +20 -0
  114. package/src/{lock.ts → state/lock.ts} +3 -2
  115. package/src/{operation-availability.ts → state/operation-availability.ts} +2 -2
  116. package/src/state/state-types.ts +20 -0
  117. package/src/state/sync-state-store.ts +62 -0
  118. package/src/sync/automatic-sync.ts +103 -0
  119. package/src/{remote-snapshot.ts → sync/remote-snapshot.ts} +27 -4
  120. package/src/{setup-switch.ts → sync/setup-switch.ts} +6 -7
  121. package/src/sync/signals.ts +10 -0
  122. package/src/sync/startup-check.ts +128 -0
  123. package/src/{sync-decision.ts → sync/sync-decision.ts} +6 -3
  124. package/src/sync/sync-error-guidance.ts +53 -0
  125. package/src/sync/sync-inspection.ts +51 -0
  126. package/src/sync/sync-loaders.ts +54 -0
  127. package/src/{sync-operations.ts → sync/sync-mutations.ts} +41 -330
  128. package/src/sync/sync-operations.ts +12 -0
  129. package/src/{sync-policy.ts → sync/sync-policy.ts} +2 -2
  130. package/src/sync/sync-queries.ts +235 -0
  131. package/src/{sync-state.ts → sync/sync-state.ts} +5 -4
  132. package/src/sync-extension.ts +64 -708
  133. package/src/sync.ts +8 -8
  134. package/src/types.ts +47 -284
  135. package/src/ui/automatic-sync-summary.ts +6 -0
  136. package/src/{cancellable-operation.ts → ui/cancellable-operation.ts} +4 -4
  137. package/src/{file-selection.ts → ui/file-selection.ts} +5 -3
  138. package/src/{manager-attention.ts → ui/manager-attention.ts} +5 -1
  139. package/src/{manager-recovery.ts → ui/manager-recovery.ts} +6 -2
  140. package/src/{manager-state.ts → ui/manager-state.ts} +35 -14
  141. package/src/ui/manager-ui.ts +386 -0
  142. package/src/{remote-selection-ui.ts → ui/remote-selection-ui.ts} +21 -13
  143. package/src/{secret-input.ts → ui/secret-input.ts} +22 -4
  144. package/src/{settings-ui.ts → ui/settings-ui.ts} +42 -11
  145. package/src/ui/setup/git-ui.ts +293 -0
  146. package/src/{s3-credentials-ui.ts → ui/setup/s3-credentials-ui.ts} +13 -21
  147. package/src/ui/setup/s3-ui.ts +217 -0
  148. package/src/ui/setup/setup-actions.ts +127 -0
  149. package/src/ui/setup/setup-location-ui.ts +167 -0
  150. package/src/ui/setup/setup-name-ui.ts +34 -0
  151. package/src/ui/setup/setup-prompts.ts +92 -0
  152. package/src/ui/setup/setup-review.ts +70 -0
  153. package/src/ui/setup/setup-switcher.ts +181 -0
  154. package/src/ui/setup/setup-wizard.ts +39 -0
  155. package/src/ui/setup/text-input.ts +78 -0
  156. package/src/ui/setup/webdav-ui.ts +330 -0
  157. package/src/{storage-connections-ui.ts → ui/storage-connections-ui.ts} +99 -69
  158. package/src/{sync-attention.ts → ui/sync-attention.ts} +75 -6
  159. package/src/{sync-format.ts → ui/sync-format.ts} +7 -13
  160. package/src/{sync-resolution-ui.ts → ui/sync-resolution-ui.ts} +4 -3
  161. package/src/{sync-setups-ui.ts → ui/sync-setups-ui.ts} +23 -10
  162. package/src/ui/terminal-text.ts +4 -0
  163. package/dist/chunks/chunk-3NDD5QPM.ts.map +0 -7
  164. package/dist/chunks/chunk-4F3W4CSC.ts.map +0 -7
  165. package/dist/chunks/chunk-4U6AFLJB.ts.map +0 -7
  166. package/dist/chunks/chunk-5MQ76BK5.ts.map +0 -7
  167. package/dist/chunks/chunk-5WJNWFTG.ts.map +0 -7
  168. package/dist/chunks/chunk-APZVBSM6.ts.map +0 -7
  169. package/dist/chunks/chunk-BHJUISB4.ts.map +0 -7
  170. package/dist/chunks/chunk-EWX3TPLQ.ts.map +0 -7
  171. package/dist/chunks/chunk-F5QL2GPY.ts.map +0 -7
  172. package/dist/chunks/chunk-FR6CVXAL.ts.map +0 -7
  173. package/dist/chunks/chunk-HGV24P4T.ts.map +0 -7
  174. package/dist/chunks/chunk-JNOOSPEW.ts +0 -82
  175. package/dist/chunks/chunk-JNOOSPEW.ts.map +0 -7
  176. package/dist/chunks/chunk-LJDSHNS3.ts.map +0 -7
  177. package/dist/chunks/chunk-LONPFFNP.ts.map +0 -7
  178. package/dist/chunks/chunk-RGJ6SOYR.ts.map +0 -7
  179. package/dist/chunks/chunk-W64NEHXT.ts +0 -167
  180. package/dist/chunks/chunk-W64NEHXT.ts.map +0 -7
  181. package/dist/chunks/chunk-YQ6UW7IF.ts.map +0 -7
  182. package/dist/chunks/file-selection-4UABOP44.ts.map +0 -7
  183. package/dist/chunks/git-backend-E3UOUUOM.ts.map +0 -7
  184. package/dist/chunks/manager-result-dispatcher-RNRQA3PW.ts +0 -18
  185. package/dist/chunks/manager-ui-QO2P4V3Z.ts +0 -2757
  186. package/dist/chunks/manager-ui-QO2P4V3Z.ts.map +0 -7
  187. package/dist/chunks/remote-selection-ui-LHSAGKGA.ts +0 -17
  188. package/dist/chunks/s3-backend-3QZGO2EF.ts.map +0 -7
  189. package/dist/chunks/sync-operations-MPAFVVHA.ts.map +0 -7
  190. package/dist/chunks/webdav-backend-ZD5RHRIU.ts.map +0 -7
  191. package/src/config.ts +0 -910
  192. package/src/git-ui.ts +0 -307
  193. package/src/manager-helpers.ts +0 -64
  194. package/src/manager-ui.ts +0 -997
  195. package/src/webdav-ui.ts +0 -421
  196. /package/dist/chunks/{manager-result-dispatcher-RNRQA3PW.ts.map → manager-result-dispatcher-U23ODD32.ts.map} +0 -0
  197. /package/dist/chunks/{remote-selection-ui-LHSAGKGA.ts.map → remote-selection-ui-ZE3TY67S.ts.map} +0 -0
  198. /package/dist/chunks/{setup-switch-X3W3FALZ.ts.map → setup-switch-IS2PEZ3J.ts.map} +0 -0
  199. /package/dist/chunks/{snapshot-WBSV4MQG.ts.map → snapshot-HKEWCVK6.ts.map} +0 -0
  200. /package/dist/chunks/{sync-state-36H7ULG2.ts.map → sync-inspection-FEP4L5TR.ts.map} +0 -0
  201. /package/src/{git-runner.ts → backends/git/git-runner.ts} +0 -0
  202. /package/src/{config-errors.ts → settings/config-errors.ts} +0 -0
  203. /package/src/{config-file.ts → settings/config-file.ts} +0 -0
  204. /package/src/{snapshot-paths.ts → snapshot/snapshot-paths.ts} +0 -0
  205. /package/src/{lock-policy.ts → state/lock-policy.ts} +0 -0
  206. /package/src/{lockfile-fs.ts → state/lockfile-fs.ts} +0 -0
  207. /package/src/{state-directory.ts → state/state-directory.ts} +0 -0
  208. /package/src/{sync-errors.ts → sync/sync-errors.ts} +0 -0
  209. /package/src/{manager-result-dispatcher.ts → ui/manager-result-dispatcher.ts} +0 -0
@@ -1,33 +1,8 @@
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
- formatRemoteSelectionStatus,
6
- readSnapshotForHead,
7
- requireCompatibleRemoteSelection
8
- } from "./chunk-FR6CVXAL.ts";
9
- import {
10
- applySnapshotTransaction,
11
- recoverPendingSnapshotTransactions
12
- } from "./chunk-3NDD5QPM.ts";
13
- import {
14
- inspectLock,
15
- isLockGuardHeld,
16
- isStaleLock,
17
- withLock
18
- } from "./chunk-LJDSHNS3.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-W64NEHXT.ts";
4
+ inspectSync
5
+ } from "./chunk-XBX4HBLG.ts";
31
6
  import {
32
7
  canPullRemoteSessionsOnFirstSync,
33
8
  canPullRemoteSettingsOnFirstSync,
@@ -40,7 +15,56 @@ import {
40
15
  snapshotHashesMatchState,
41
16
  snapshotsMatch,
42
17
  syncPolicyChanged
43
- } from "./chunk-RGJ6SOYR.ts";
18
+ } from "./chunk-AUSDLD6F.ts";
19
+ import {
20
+ applySnapshotTransaction,
21
+ recoverPendingSnapshotTransactions
22
+ } from "./chunk-AXXS2BPA.ts";
23
+ import {
24
+ inspectLock,
25
+ isLockGuardHeld,
26
+ isStaleLock,
27
+ withLock
28
+ } from "./chunk-GWMHAQG4.ts";
29
+ import {
30
+ throwIfAborted
31
+ } from "./chunk-NJFUIVYL.ts";
32
+ import {
33
+ readStateForConfig,
34
+ writeStateForConfig
35
+ } from "./chunk-JSYMGJQQ.ts";
36
+ import {
37
+ encodeSnapshot
38
+ } from "./chunk-Q2TUM5X6.ts";
39
+ import {
40
+ stateDir
41
+ } from "./chunk-ZI5MXMEX.ts";
42
+ import {
43
+ expectedRemoteHead
44
+ } from "./chunk-D5X27LCA.ts";
45
+ import {
46
+ formatRemoteSelectionStatus,
47
+ readRemoteSnapshot,
48
+ readSnapshotForHead,
49
+ requireCompatibleRemoteSelection
50
+ } from "./chunk-HUDQATCL.ts";
51
+ import {
52
+ createSyncBackend
53
+ } from "./chunk-L7TBO32M.ts";
54
+ import {
55
+ safeTerminalText
56
+ } from "./chunk-LTBWGSBV.ts";
57
+ import {
58
+ loadConfig,
59
+ syncConfigReviewIdentity,
60
+ syncSessionsWarnings
61
+ } from "./chunk-C7XY5NF5.ts";
62
+ import "./chunk-6LP3MCR2.ts";
63
+ import "./chunk-TEN2ZBJV.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-EWX3TPLQ.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-APZVBSM6.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));
@@ -586,12 +553,14 @@ async function push(ctx, options, input, factory = createSyncBackend) {
586
553
  remoteForUpload,
587
554
  options.signal
588
555
  );
589
- const secrets = scanSnapshot(local);
590
- if (secrets.length > 0) {
591
- throw new Error(
592
- `Refusing to push possible secrets:
556
+ if (!config.skipSecretScan) {
557
+ const secrets = scanSnapshot(local);
558
+ if (secrets.length > 0) {
559
+ throw new Error(
560
+ `Refusing to push possible secrets:
593
561
  ${secrets.map((s) => `- ${s}`).join("\n")}`
594
- );
562
+ );
563
+ }
595
564
  }
596
565
  if (!await confirmPush(ctx, options, config, backend, local, upload, head, remoteForUpload)) {
597
566
  return "cancelled";
@@ -657,7 +626,7 @@ async function pull(ctx, options, factory = createSyncBackend) {
657
626
  const config = await loadConfig(options.setup);
658
627
  throwIfAborted(options.signal);
659
628
  ctx.ui.setStatus(STATUS_KEY, `pulling ${config.setupName}`);
660
- const backend = await backendFor(config, factory);
629
+ const backend = await factory(config);
661
630
  const state = await readStateForConfig(config);
662
631
  throwIfAborted(options.signal);
663
632
  const local = await createSnapshot(
@@ -746,7 +715,7 @@ async function pull(ctx, options, factory = createSyncBackend) {
746
715
  async function syncBoth(ctx, options, factory = createSyncBackend) {
747
716
  const config = await loadConfig(options.setup);
748
717
  throwIfAborted(options.signal);
749
- const backend = await backendFor(config, factory);
718
+ const backend = await factory(config);
750
719
  const state = await readStateForConfig(config);
751
720
  throwIfAborted(options.signal);
752
721
  const local = await createSnapshot(
@@ -859,52 +828,12 @@ async function syncBoth(ctx, options, factory = createSyncBackend) {
859
828
  }
860
829
  if (!options.silent) ctx.ui.notify("pi-sync is already up to date.", "info");
861
830
  }
862
- async function history(ctx, options, factory = createSyncBackend) {
863
- const config = await loadConfig(options.setup);
864
- throwIfAborted(options.signal);
865
- const backend = await backendFor(config, factory);
866
- const snapshots = (await backend.listHistory(options.signal)).slice(-20).reverse();
867
- throwIfAborted(options.signal);
868
- if (snapshots.length === 0) {
869
- ctx.ui.notify("No remote pi-sync history found.", "info");
870
- return;
871
- }
872
- const currentSnapshot = snapshots[0]?.snapshotId;
873
- if (ctx.mode === "tui") {
874
- const labels = snapshots.map(
875
- (item, index2) => `${index2 + 1}. ${item.createdAt} \xB7 ${safeTerminalText(item.machine)} \xB7 ${item.snapshotId}${item.snapshotId === currentSnapshot ? " (current)" : ""}${item.syncSessions ? " \xB7 sessions" : ""}`
876
- );
877
- const selected = await ctx.ui.select(
878
- `History for sync setup \u201C${safeTerminalText(config.setupName)}\u201D
879
-
880
- Choose a snapshot to preview rollback.`,
881
- [...labels, "Back"]
882
- );
883
- if (!selected || selected === "Back") return;
884
- throwIfAborted(options.signal);
885
- const index = labels.indexOf(selected);
886
- const snapshot = snapshots[index];
887
- if (!snapshot) return;
888
- await withLock(
889
- "rollback",
890
- () => rollback(ctx, { ...options, args: [snapshot.snapshotRef], yes: false }, factory, {
891
- backendIdentity: backend.identity,
892
- setup: config.setupName
893
- })
894
- );
895
- return;
896
- }
897
- ctx.ui.notify(
898
- snapshots.map((item) => `${item.snapshotRef} ${item.createdAt} ${safeTerminalText(item.machine)}`).join("\n"),
899
- "info"
900
- );
901
- }
902
831
  async function rollback(ctx, options, factory = createSyncBackend, expectedSelection) {
903
832
  const target = options.args[0];
904
833
  if (!target) throw new Error("Usage: /sync rollback <snapshot-id> [--yes]");
905
834
  const config = await loadConfig(options.setup);
906
835
  throwIfAborted(options.signal);
907
- const backend = await backendFor(config, factory);
836
+ const backend = await factory(config);
908
837
  if (expectedSelection && (backend.identity !== expectedSelection.backendIdentity || config.setupName !== expectedSelection.setup)) {
909
838
  throw new Error(
910
839
  "Sync setup or storage location changed while history was open; reopen history and retry."
@@ -996,21 +925,6 @@ function protectedSessionPaths(ctx) {
996
925
  const snapshotPath = sessionFile ? sessionSnapshotPathFromAbsolute(sessionFile, sessionDirFromContext(ctx)) : void 0;
997
926
  return snapshotPath ? /* @__PURE__ */ new Set([snapshotPath]) : /* @__PURE__ */ new Set();
998
927
  }
999
- function snapshotOptionsForContext(ctx, config) {
1000
- return {
1001
- include: config.include,
1002
- sessionDir: sessionDirFromContext(ctx)
1003
- };
1004
- }
1005
- function sessionDirFromContext(ctx) {
1006
- const manager = ctx.sessionManager;
1007
- const usesDefaultSessionDir = manager.usesDefaultSessionDir;
1008
- if (typeof usesDefaultSessionDir === "function" && usesDefaultSessionDir.call(manager)) {
1009
- return void 0;
1010
- }
1011
- const getSessionDir = manager.getSessionDir;
1012
- return typeof getSessionDir === "function" ? getSessionDir.call(manager) : void 0;
1013
- }
1014
928
  async function maybeReload(ctx, signal) {
1015
929
  if (signal?.aborted || !("reload" in ctx)) return;
1016
930
  if (ctx.hasUI && await ctx.ui.confirm(
@@ -1040,21 +954,6 @@ async function snapshotForUpload(backend, config, local, head, remote, signal, o
1040
954
  }
1041
955
  return mergeRemotePreservedFiles(local, snapshot, config);
1042
956
  }
1043
- async function readRemoteSnapshot(backend, config, signal, options = {}) {
1044
- const head = await backend.readHead(signal);
1045
- if (!head) return { head: void 0, snapshot: void 0, selectionState: void 0 };
1046
- const snapshot = await readSnapshotForHead(backend, head, signal);
1047
- const selectionState = inspectRemoteSelection(config.include, snapshot);
1048
- if (!options.allowSelectionDifference && selectionState.kind === "different") {
1049
- const configIdentity = syncConfigReviewFingerprint(config);
1050
- throw remoteSelectionMismatch(config, selectionState.include, configIdentity);
1051
- }
1052
- return {
1053
- head,
1054
- snapshot: filterSnapshotForConfigPolicy(snapshot, config),
1055
- selectionState
1056
- };
1057
- }
1058
957
  async function confirmPush(ctx, options, config, backend, local, upload, head, remote, title = snapshotIncludesSessions(upload) ? "Push pi settings and sessions?" : "Push pi settings?") {
1059
958
  throwIfAborted(options.signal);
1060
959
  if (options.yes) return true;
@@ -1075,10 +974,6 @@ async function confirmPush(ctx, options, config, backend, local, upload, head, r
1075
974
  ctx.ui.notify("Push cancelled.", "info");
1076
975
  return false;
1077
976
  }
1078
- function throwIfAborted(signal) {
1079
- if (!signal?.aborted) return;
1080
- throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
1081
- }
1082
977
  function sameRemoteHead(backend, left, right) {
1083
978
  if (!left || !right) return left === right;
1084
979
  return backend.sameRevision(left.revision, right.revision);
@@ -1096,6 +991,193 @@ async function backupLocal(profile, options = {}, signal) {
1096
991
  await fs2.writeFile(backupPath, encoded, { signal });
1097
992
  return backupPath;
1098
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
+ }
1099
1181
  export {
1100
1182
  PublicationStatePersistenceError,
1101
1183
  RollbackPublicationError,
@@ -1109,4 +1191,4 @@ export {
1109
1191
  status,
1110
1192
  syncBoth
1111
1193
  };
1112
- //# sourceMappingURL=sync-operations-MPAFVVHA.ts.map
1194
+ //# sourceMappingURL=sync-operations-ONJ27QRL.ts.map