@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
package/dist/index.ts CHANGED
@@ -2,8 +2,28 @@
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
4
  createSyncAttentionController,
5
+ observationMatchesConfig,
5
6
  syncAttentionMatchesConfig
6
- } from "./chunks/chunk-JNOOSPEW.ts";
7
+ } from "./chunks/chunk-6UHKGALE.ts";
8
+ import {
9
+ recoverSnapshotTransactionsOnStartup
10
+ } from "./chunks/chunk-AXXS2BPA.ts";
11
+ import {
12
+ unlock,
13
+ withLock
14
+ } from "./chunks/chunk-GWMHAQG4.ts";
15
+ import {
16
+ combineSignals,
17
+ throwIfAborted
18
+ } from "./chunks/chunk-NJFUIVYL.ts";
19
+ import {
20
+ ensureStateDir,
21
+ readStateForConfig,
22
+ syncStateFingerprint
23
+ } from "./chunks/chunk-JSYMGJQQ.ts";
24
+ import {
25
+ automaticSyncSummary
26
+ } from "./chunks/chunk-Z4DQF6L3.ts";
7
27
  import {
8
28
  completeSyncArguments,
9
29
  parseOptions,
@@ -12,290 +32,51 @@ import {
12
32
  splitArgs,
13
33
  usage,
14
34
  validateCommandOptions
15
- } from "./chunks/chunk-4U6AFLJB.ts";
35
+ } from "./chunks/chunk-CHH26D4F.ts";
16
36
  import {
17
- recoverSnapshotTransactionsOnStartup
18
- } from "./chunks/chunk-3NDD5QPM.ts";
37
+ migrateLegacyStateDirectory,
38
+ stateDirectoryMigrationNotice,
39
+ withStateDirectoryAccess
40
+ } from "./chunks/chunk-ZI5MXMEX.ts";
19
41
  import {
20
- unlock,
21
- withLock
22
- } from "./chunks/chunk-LJDSHNS3.ts";
42
+ syncErrorGuidance
43
+ } from "./chunks/chunk-CPEHGXJV.ts";
23
44
  import {
24
- SetupPullRequiresUiError,
25
- errorMessage,
26
- isSyncDecisionRequiredError
27
- } from "./chunks/chunk-W64NEHXT.ts";
28
- import "./chunks/chunk-RGJ6SOYR.ts";
29
- import "./chunks/chunk-EWX3TPLQ.ts";
45
+ safeTerminalText
46
+ } from "./chunks/chunk-LTBWGSBV.ts";
30
47
  import {
31
48
  activeLocalConfigPath,
32
49
  configuredSyncSetupNames,
33
50
  consumeLocalConfigMigrationNotice,
34
51
  createLocalConfigDocument,
35
- ensureStateDir,
36
- isMissingConfigError,
37
52
  loadConfig,
53
+ loadConfigForCheck,
38
54
  loadPartialConfig,
39
55
  localConfigPath,
40
56
  localConfigTemplate,
41
57
  readLocalConfigObject,
42
- readStateForConfig,
43
58
  sessionTokenWarnings,
59
+ syncCheckConfigFingerprint,
44
60
  syncSessionsWarnings
45
- } from "./chunks/chunk-APZVBSM6.ts";
61
+ } from "./chunks/chunk-C7XY5NF5.ts";
62
+ import "./chunks/chunk-6LP3MCR2.ts";
63
+ import "./chunks/chunk-TEN2ZBJV.ts";
46
64
  import {
47
- migrateLegacyStateDirectory,
48
- stateDirectoryMigrationNotice,
49
- withStateDirectoryAccess
50
- } from "./chunks/chunk-F5QL2GPY.ts";
65
+ SetupPullRequiresUiError,
66
+ errorMessage,
67
+ isSyncDecisionRequiredError
68
+ } from "./chunks/chunk-2B2ELUF5.ts";
69
+ import "./chunks/chunk-THMF4BVL.ts";
70
+ import {
71
+ snapshotOptionsForContext
72
+ } from "./chunks/chunk-T3HH3UEJ.ts";
51
73
  import {
52
74
  RemoteSelectionMismatchError,
53
75
  formatRemoteSelectionMismatch
54
- } from "./chunks/chunk-YQ6UW7IF.ts";
76
+ } from "./chunks/chunk-JAAKLL6M.ts";
55
77
 
56
- // src/sync-extension.ts
78
+ // src/commands/command-execution.ts
57
79
  var STATUS_KEY = "sync";
58
- var AUTO_SYNC_OPTIONS = {
59
- yes: true,
60
- force: false,
61
- stale: false,
62
- silent: true,
63
- reload: false,
64
- auto: true,
65
- args: []
66
- };
67
- function sync(pi, dependencies = {}) {
68
- const loaders = {
69
- setupSwitch: cachedModuleLoader(
70
- dependencies.loadSetupSwitch ?? (() => import("./chunks/setup-switch-X3W3FALZ.ts"))
71
- ),
72
- snapshot: cachedModuleLoader(dependencies.loadSnapshot ?? (() => import("./chunks/snapshot-WBSV4MQG.ts"))),
73
- syncState: cachedModuleLoader(dependencies.loadSyncState ?? (() => import("./chunks/sync-state-36H7ULG2.ts"))),
74
- operations: cachedModuleLoader(
75
- dependencies.loadSyncOperations ?? (() => import("./chunks/sync-operations-MPAFVVHA.ts"))
76
- )
77
- };
78
- const attention = createSyncAttentionController();
79
- let sessionAbort = new AbortController();
80
- let shutdownAbort;
81
- pi.registerCommand("sync", {
82
- description: "Sync Pi settings through Git, WebDAV, R2, or S3-compatible storage",
83
- getArgumentCompletions: completeSyncArguments,
84
- handler: async (args, ctx) => {
85
- if (!ctx.hasUI) {
86
- throw new Error(
87
- "/sync requires TUI or RPC mode so results and safety prompts are observable."
88
- );
89
- }
90
- const run = () => handleCommand(args, ctx, sessionAbort.signal, loaders, attention);
91
- if (splitArgs(args)[0] === "migrate-state") await run();
92
- else await withStateDirectoryAccess(run);
93
- }
94
- });
95
- pi.on("session_start", async (_event, ctx) => {
96
- shutdownAbort?.abort(new DOMException("Session replaced", "AbortError"));
97
- shutdownAbort = void 0;
98
- sessionAbort.abort(new DOMException("Session replaced", "AbortError"));
99
- sessionAbort = new AbortController();
100
- const signal = sessionAbort.signal;
101
- attention.reset(ctx);
102
- let decision;
103
- try {
104
- decision = await withStateDirectoryAccess(() => startSession(ctx, signal, loaders));
105
- } catch (error) {
106
- if (signal.aborted) return;
107
- ctx.ui.notify(`pi-sync state access failed: ${errorMessage(error)}`, "error");
108
- return;
109
- }
110
- if (!decision || signal.aborted) return;
111
- attention.set(decision, "sync");
112
- if (ctx.mode !== "tui") {
113
- ctx.ui.notify(
114
- `pi-sync auto sync skipped: ${formatRemoteSelectionMismatch(
115
- decision.setupName,
116
- decision.localInclude,
117
- decision.remoteInclude
118
- )}
119
- RPC review is read-only.`,
120
- "warning"
121
- );
122
- } else if (attention.markOffered()) {
123
- await resolveSelectionAttention(ctx, attention, signal, loaders, {
124
- cancelLabel: "Later",
125
- withStateAccess: withStateDirectoryAccess
126
- });
127
- }
128
- if (!signal.aborted) attention.publish(ctx);
129
- });
130
- pi.on("session_shutdown", async (event, ctx) => {
131
- sessionAbort.abort(new DOMException("Session shut down", "AbortError"));
132
- attention.reset(ctx);
133
- shutdownAbort?.abort(new DOMException("Session shut down again", "AbortError"));
134
- const controller = new AbortController();
135
- shutdownAbort = controller;
136
- const signal = combineSignals(controller.signal, AbortSignal.timeout(3e4));
137
- const reason = typeof event === "object" && event ? event.reason : void 0;
138
- try {
139
- if (reason !== "reload") {
140
- await withStateDirectoryAccess(async () => {
141
- if (signal.aborted) return;
142
- await autoPushSessions(ctx, signal, loaders);
143
- });
144
- }
145
- } catch (error) {
146
- if (!signal.aborted) {
147
- ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
148
- }
149
- } finally {
150
- if (shutdownAbort === controller) shutdownAbort = void 0;
151
- }
152
- if (signal.aborted) return;
153
- ctx.ui.setStatus(STATUS_KEY, void 0);
154
- });
155
- }
156
- async function startSession(ctx, signal, loaders) {
157
- if (signal.aborted) return;
158
- try {
159
- const migrationNotice2 = stateDirectoryMigrationNotice();
160
- if (migrationNotice2) ctx.ui.notify(migrationNotice2, "warning");
161
- } catch (error) {
162
- ctx.ui.notify(`pi-sync state directory requires attention: ${errorMessage(error)}`, "error");
163
- return;
164
- }
165
- try {
166
- await recoverSnapshotTransactionsOnStartup();
167
- if (signal.aborted) return;
168
- } catch (error) {
169
- if (signal.aborted) return;
170
- ctx.ui.notify(`pi-sync recovery required: ${errorMessage(error)}`, "error");
171
- return;
172
- }
173
- try {
174
- setSyncSetupCompletions(await configuredSyncSetupNames());
175
- if (signal.aborted) return;
176
- } catch {
177
- if (signal.aborted) return;
178
- setSyncSetupCompletions([]);
179
- }
180
- const migrationNotice = consumeLocalConfigMigrationNotice();
181
- if (migrationNotice) ctx.ui.notify(migrationNotice, "warning");
182
- if (signal.aborted) return;
183
- return autoSync(ctx, signal, loaders);
184
- }
185
- async function handleCommand(rawArgs, ctx, sessionSignal, loaders, attention) {
186
- if (!rawArgs.trim()) {
187
- try {
188
- const { showSyncManager } = await import("./chunks/manager-ui-QO2P4V3Z.ts");
189
- if (sessionSignal.aborted) return;
190
- await showSyncManager(
191
- ctx,
192
- (route, signal, onCommit, target) => executeCommand(
193
- route,
194
- ctx,
195
- combineSignals(sessionSignal, signal),
196
- loaders,
197
- onCommit,
198
- target
199
- ),
200
- sessionSignal,
201
- {
202
- getAttention: () => attention.current(),
203
- onSelectionResolved: (expected) => {
204
- if (attention.current() === expected) attention.clear(ctx);
205
- }
206
- }
207
- );
208
- } catch (error) {
209
- if (sessionSignal.aborted) return;
210
- ctx.ui.setStatus(STATUS_KEY, void 0);
211
- ctx.ui.notify(errorMessage(error), "error");
212
- }
213
- if (!sessionSignal.aborted) attention.publish(ctx);
214
- return;
215
- }
216
- const result = await executeCommand(rawArgs, ctx, sessionSignal, loaders);
217
- if (result.kind === "decision-required") {
218
- ctx.ui.notify(result.decision.directMessage, "error");
219
- } else if (result.kind === "remote-selection-required") {
220
- const origin = directSelectionOrigin(rawArgs);
221
- if (origin) attention.set(result.decision, origin);
222
- const deterministic = splitArgs(rawArgs).some((arg) => arg === "--yes" || arg === "-y");
223
- if (origin && ctx.mode === "tui" && !deterministic) {
224
- await resolveSelectionAttention(ctx, attention, sessionSignal, loaders);
225
- } else {
226
- ctx.ui.notify(
227
- formatRemoteSelectionMismatch(
228
- result.decision.setupName,
229
- result.decision.localInclude,
230
- result.decision.remoteInclude
231
- ),
232
- "error"
233
- );
234
- }
235
- }
236
- await clearAttentionAfterCompletedOperation(rawArgs, result, ctx, attention, sessionSignal);
237
- await reconcileSelectionAttention(ctx, attention, sessionSignal);
238
- if (!sessionSignal.aborted) attention.publish(ctx);
239
- }
240
- async function clearAttentionAfterCompletedOperation(rawArgs, result, ctx, attention, signal) {
241
- if (result.kind !== "completed" || result.outcome === "cancelled" || signal.aborted) return;
242
- const [command, ...rest] = splitArgs(rawArgs);
243
- if (command !== "sync" && command !== "pull" && command !== "push") return;
244
- const current = attention.current();
245
- if (!current) return;
246
- try {
247
- const options = parseOptions(rest);
248
- const setupName = options.setup ?? (await loadConfig()).setupName;
249
- if (signal.aborted || attention.current() !== current) return;
250
- if (current.decision.setupName === setupName) attention.clear(ctx);
251
- } catch {
252
- }
253
- }
254
- async function reconcileSelectionAttention(ctx, attention, signal) {
255
- const current = attention.current();
256
- if (!current || signal.aborted) return;
257
- try {
258
- const config = await loadConfig(current.decision.setupName);
259
- if (signal.aborted || attention.current() !== current) return;
260
- if (!syncAttentionMatchesConfig(current, config)) attention.clear(ctx);
261
- } catch {
262
- if (!signal.aborted && attention.current() === current) attention.clear(ctx);
263
- }
264
- }
265
- function directSelectionOrigin(rawArgs) {
266
- const command = splitArgs(rawArgs)[0];
267
- return command === "sync" || command === "pull" || command === "push" ? command : void 0;
268
- }
269
- async function resolveSelectionAttention(ctx, attention, signal, loaders, options = {}) {
270
- const current = attention.current();
271
- if (!current || signal.aborted) return;
272
- const { dispatchManagerResult } = await import("./chunks/manager-result-dispatcher-RNRQA3PW.ts");
273
- if (signal.aborted || attention.current() !== current) return;
274
- await dispatchManagerResult(
275
- ctx,
276
- { kind: "remote-selection-required", decision: current.decision },
277
- current.origin,
278
- (route, actionSignal, onCommit, target) => {
279
- const execute = () => executeRecoveryCommand(
280
- route,
281
- ctx,
282
- combineSignals(signal, actionSignal),
283
- loaders,
284
- onCommit,
285
- target
286
- );
287
- return options.withStateAccess ? options.withStateAccess(execute) : execute();
288
- },
289
- signal,
290
- {
291
- cancelLabel: options.cancelLabel,
292
- withStateAccess: options.withStateAccess,
293
- onSelectionResolved: () => {
294
- if (attention.current() === current) attention.clear(ctx);
295
- }
296
- }
297
- );
298
- }
299
80
  async function executeRecoveryCommand(rawArgs, ctx, signal, loaders, onCommit, setup) {
300
81
  try {
301
82
  const [subcommand, ...rest] = splitArgs(rawArgs);
@@ -330,7 +111,7 @@ async function executeRecoveryCommand(rawArgs, ctx, signal, loaders, onCommit, s
330
111
  if (isSyncDecisionRequiredError(error)) {
331
112
  return { kind: "decision-required", decision: error.decision };
332
113
  }
333
- ctx.ui.notify(errorMessage(error), "error");
114
+ ctx.ui.notify(syncErrorGuidance(error), "error");
334
115
  return { kind: "failed" };
335
116
  }
336
117
  }
@@ -374,7 +155,7 @@ async function executeCommand(rawArgs, ctx, signal, loaders, onCommit, setup) {
374
155
  await showConfig(ctx, options);
375
156
  return { kind: "completed" };
376
157
  case "files": {
377
- const { showFileSelection } = await import("./chunks/file-selection-4UABOP44.ts");
158
+ const { showFileSelection } = await import("./chunks/file-selection-FTTKAOYX.ts");
378
159
  throwIfAborted(options.signal);
379
160
  await showFileSelection(ctx, options.setup, options.signal);
380
161
  return { kind: "completed" };
@@ -449,7 +230,7 @@ ${usage()}`, "warning");
449
230
  if (isSyncDecisionRequiredError(error)) {
450
231
  return { kind: "decision-required", decision: error.decision };
451
232
  }
452
- ctx.ui.notify(errorMessage(error), "error");
233
+ ctx.ui.notify(syncErrorGuidance(error), "error");
453
234
  return { kind: "failed" };
454
235
  }
455
236
  }
@@ -476,63 +257,6 @@ async function migrateStateDirectory(ctx, options) {
476
257
  }
477
258
  ctx.ui.notify(result.message, result.status === "migrated" ? "info" : "warning");
478
259
  }
479
- async function autoSync(ctx, signal, loaders) {
480
- try {
481
- const partial = await loadPartialConfig();
482
- throwIfAborted(signal);
483
- if (!partial.automatic) return;
484
- await ensureStateDir();
485
- throwIfAborted(signal);
486
- await loadConfig();
487
- throwIfAborted(signal);
488
- const operations = await loaders.operations();
489
- throwIfAborted(signal);
490
- await withLock("auto-sync", () => {
491
- throwIfAborted(signal);
492
- return operations.syncBoth(ctx, { ...AUTO_SYNC_OPTIONS, signal });
493
- });
494
- } catch (error) {
495
- if (signal.aborted || isMissingConfigError(error)) return;
496
- ctx.ui.setStatus(STATUS_KEY, void 0);
497
- if (error instanceof RemoteSelectionMismatchError) return error.decision;
498
- ctx.ui.notify(`pi-sync auto sync skipped: ${errorMessage(error)}`, "warning");
499
- }
500
- }
501
- async function autoPushSessions(ctx, signal, loaders) {
502
- try {
503
- const partial = await loadPartialConfig();
504
- throwIfAborted(signal);
505
- if (!partial.automatic) return;
506
- if (!partial.include.includes("sessions")) return;
507
- await ensureStateDir();
508
- throwIfAborted(signal);
509
- const config = await loadConfig();
510
- throwIfAborted(signal);
511
- if (!config.include.includes("sessions")) return;
512
- const [operations, snapshotModule, syncStateModule] = await Promise.all([
513
- loaders.operations(),
514
- loaders.snapshot(),
515
- loaders.syncState()
516
- ]);
517
- throwIfAborted(signal);
518
- await withLock("auto-session-push", async () => {
519
- throwIfAborted(signal);
520
- const state = await readStateForConfig(config);
521
- throwIfAborted(signal);
522
- const local = await snapshotModule.createSnapshot(
523
- config.snapshotIdentity,
524
- snapshotOptionsForContext(ctx, config)
525
- );
526
- throwIfAborted(signal);
527
- if (!syncStateModule.hasLocalChanges(local, state, config)) return;
528
- await operations.push(ctx, { ...AUTO_SYNC_OPTIONS, signal }, { config, state, local });
529
- });
530
- } catch (error) {
531
- if (signal.aborted || isMissingConfigError(error)) return;
532
- ctx.ui.setStatus(STATUS_KEY, void 0);
533
- ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
534
- }
535
- }
536
260
  async function initConfig(ctx, signal) {
537
261
  const configPath = localConfigPath();
538
262
  if (await readLocalConfigObject()) {
@@ -540,7 +264,7 @@ async function initConfig(ctx, signal) {
540
264
  return;
541
265
  }
542
266
  if (ctx.mode === "tui") {
543
- const { showSetupWizard } = await import("./chunks/manager-ui-QO2P4V3Z.ts");
267
+ const { showSetupWizard } = await import("./chunks/setup-wizard-VB3VDOV5.ts");
544
268
  throwIfAborted(signal);
545
269
  await showSetupWizard(ctx, signal);
546
270
  return;
@@ -565,7 +289,7 @@ async function showConfig(ctx, options) {
565
289
  `storage connection: ${config.connectionName}`,
566
290
  ...storageLines,
567
291
  `storage path: ${config.storagePath}`,
568
- `automatic sync: ${config.automatic ? "enabled" : "disabled"}`,
292
+ `automatic sync: ${automaticSyncSummary(config.automatic)}`,
569
293
  `included content: ${config.include.join(", ") || "none"}`,
570
294
  `sessions: ${config.include.includes("sessions") ? "included" : "not included"}`,
571
295
  `settings file: ${localConfigPath()}`,
@@ -628,27 +352,342 @@ function displayWebDavUrl(value, username) {
628
352
  return "invalid (value hidden)";
629
353
  }
630
354
  }
631
- function throwIfAborted(signal) {
632
- if (!signal?.aborted) return;
633
- throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
355
+
356
+ // src/commands/command-handler.ts
357
+ var STATUS_KEY2 = "sync";
358
+ async function handleCommand(rawArgs, ctx, sessionSignal, loaders, attention) {
359
+ await reconcileObservation(attention, sessionSignal);
360
+ if (sessionSignal.aborted) return;
361
+ const run = (route, signal, onCommit, target) => executeCommand(
362
+ route,
363
+ ctx,
364
+ combineSignals(sessionSignal, signal),
365
+ loaders,
366
+ observationCommitCallback(attention, sessionSignal, onCommit),
367
+ target
368
+ );
369
+ if (!rawArgs.trim()) {
370
+ try {
371
+ const { showSyncManager } = await import("./chunks/manager-ui-ABATHC36.ts");
372
+ if (sessionSignal.aborted) return;
373
+ await showSyncManager(ctx, run, sessionSignal, {
374
+ getAttention: () => attention.current(),
375
+ getObservation: () => attention.observation(),
376
+ onObservationInvalidated: () => attention.clearObservation(),
377
+ onSelectionResolved: (expected) => {
378
+ if (attention.current() === expected) attention.clear(ctx);
379
+ }
380
+ });
381
+ } catch (error) {
382
+ if (sessionSignal.aborted) return;
383
+ ctx.ui.setStatus(STATUS_KEY2, void 0);
384
+ ctx.ui.notify(errorMessage(error), "error");
385
+ }
386
+ await reconcileObservation(attention, sessionSignal);
387
+ if (!sessionSignal.aborted) attention.publish(ctx);
388
+ return;
389
+ }
390
+ const result = await run(rawArgs);
391
+ if (result.kind === "decision-required") {
392
+ ctx.ui.notify(result.decision.directMessage, "error");
393
+ } else if (result.kind === "remote-selection-required") {
394
+ const origin = directSelectionOrigin(rawArgs);
395
+ if (origin) attention.set(result.decision, origin);
396
+ const deterministic = splitArgs(rawArgs).some((arg) => arg === "--yes" || arg === "-y");
397
+ if (origin && ctx.mode === "tui" && !deterministic) {
398
+ await resolveSelectionAttention(ctx, attention, sessionSignal, loaders);
399
+ } else {
400
+ ctx.ui.notify(
401
+ formatRemoteSelectionMismatch(
402
+ result.decision.setupName,
403
+ result.decision.localInclude,
404
+ result.decision.remoteInclude
405
+ ),
406
+ "error"
407
+ );
408
+ }
409
+ }
410
+ await clearAttentionAfterCompletedOperation(rawArgs, result, ctx, attention, sessionSignal);
411
+ await reconcileSelectionAttention(ctx, attention, sessionSignal);
412
+ await reconcileObservation(attention, sessionSignal);
413
+ if (!sessionSignal.aborted) attention.publish(ctx);
414
+ }
415
+ function observationCommitCallback(attention, sessionSignal, onCommit) {
416
+ const observed = attention.observation();
417
+ return () => {
418
+ if (!sessionSignal.aborted && attention.observation() === observed)
419
+ attention.clearObservation();
420
+ onCommit?.();
421
+ };
634
422
  }
635
- function combineSignals(primary, secondary) {
636
- return secondary ? AbortSignal.any([primary, secondary]) : primary;
423
+ async function reconcileObservation(attention, signal) {
424
+ const observed = attention.observation();
425
+ if (!observed || signal.aborted) return;
426
+ try {
427
+ const config = await loadConfig();
428
+ if (signal.aborted || attention.observation() !== observed) return;
429
+ if (!observationMatchesConfig(observed, config)) {
430
+ attention.clearObservation();
431
+ return;
432
+ }
433
+ const state = await readStateForConfig(config);
434
+ if (signal.aborted || attention.observation() !== observed) return;
435
+ if (syncStateFingerprint(state) !== observed.inspection.stateIdentity)
436
+ attention.clearObservation();
437
+ } catch {
438
+ if (!signal.aborted && attention.observation() === observed) attention.clearObservation();
439
+ }
637
440
  }
638
- function snapshotOptionsForContext(ctx, config) {
441
+ async function clearAttentionAfterCompletedOperation(rawArgs, result, ctx, attention, signal) {
442
+ if (result.kind !== "completed" || result.outcome === "cancelled" || signal.aborted) return;
443
+ const [command, ...rest] = splitArgs(rawArgs);
444
+ if (command !== "sync" && command !== "pull" && command !== "push") return;
445
+ const current = attention.current();
446
+ if (!current) return;
447
+ try {
448
+ const options = parseOptions(rest);
449
+ const setupName = options.setup ?? (await loadConfig()).setupName;
450
+ if (signal.aborted || attention.current() !== current) return;
451
+ if (current.decision.setupName === setupName) attention.clear(ctx);
452
+ } catch {
453
+ }
454
+ }
455
+ async function reconcileSelectionAttention(ctx, attention, signal) {
456
+ const current = attention.current();
457
+ if (!current || signal.aborted) return;
458
+ try {
459
+ const config = await loadConfig(current.decision.setupName);
460
+ if (signal.aborted || attention.current() !== current) return;
461
+ if (!syncAttentionMatchesConfig(current, config)) attention.clear(ctx);
462
+ } catch {
463
+ if (!signal.aborted && attention.current() === current) attention.clear(ctx);
464
+ }
465
+ }
466
+ function directSelectionOrigin(rawArgs) {
467
+ const command = splitArgs(rawArgs)[0];
468
+ return command === "sync" || command === "pull" || command === "push" ? command : void 0;
469
+ }
470
+ async function resolveSelectionAttention(ctx, attention, signal, loaders, options = {}) {
471
+ const current = attention.current();
472
+ if (!current || signal.aborted) return;
473
+ const { dispatchManagerResult } = await import("./chunks/manager-result-dispatcher-U23ODD32.ts");
474
+ if (signal.aborted || attention.current() !== current) return;
475
+ await dispatchManagerResult(
476
+ ctx,
477
+ { kind: "remote-selection-required", decision: current.decision },
478
+ current.origin,
479
+ (route, actionSignal, onCommit, target) => {
480
+ const execute = () => executeRecoveryCommand(
481
+ route,
482
+ ctx,
483
+ combineSignals(signal, actionSignal),
484
+ loaders,
485
+ observationCommitCallback(attention, signal, onCommit),
486
+ target
487
+ );
488
+ return options.withStateAccess ? options.withStateAccess(execute) : execute();
489
+ },
490
+ signal,
491
+ {
492
+ cancelLabel: options.cancelLabel,
493
+ withStateAccess: options.withStateAccess,
494
+ onSelectionResolved: () => {
495
+ if (attention.current() === current) attention.clear(ctx);
496
+ }
497
+ }
498
+ );
499
+ }
500
+
501
+ // src/settings/config-errors.ts
502
+ function isMissingConfigError(error) {
503
+ return error instanceof Error && (error.message.startsWith("Missing pi-sync settings.") || error.message === "No sync setups are configured.");
504
+ }
505
+
506
+ // src/sync/automatic-sync.ts
507
+ var AUTO_SYNC_OPTIONS = {
508
+ yes: true,
509
+ force: false,
510
+ stale: false,
511
+ silent: true,
512
+ reload: false,
513
+ auto: true,
514
+ args: []
515
+ };
516
+ var STATUS_KEY3 = "sync";
517
+ async function startSession(ctx, signal) {
518
+ throwIfAborted(signal);
519
+ const stateNotice = stateDirectoryMigrationNotice();
520
+ if (stateNotice && ctx.hasUI) ctx.ui.notify(stateNotice, "warning");
521
+ await recoverSnapshotTransactionsOnStartup();
522
+ throwIfAborted(signal);
523
+ try {
524
+ const names = await configuredSyncSetupNames();
525
+ throwIfAborted(signal);
526
+ setSyncSetupCompletions(names);
527
+ } catch {
528
+ if (signal.aborted) return;
529
+ setSyncSetupCompletions([]);
530
+ }
531
+ const migrationNotice = consumeLocalConfigMigrationNotice();
532
+ if (migrationNotice && ctx.hasUI) ctx.ui.notify(migrationNotice, "warning");
533
+ throwIfAborted(signal);
534
+ if (!ctx.hasUI) return;
535
+ try {
536
+ const config = await loadConfig();
537
+ throwIfAborted(signal);
538
+ return config.automatic ? config : void 0;
539
+ } catch (error) {
540
+ throwIfAborted(signal);
541
+ if (!isMissingConfigError(error)) {
542
+ ctx.ui.notify(
543
+ `pi-sync startup check skipped: ${safeTerminalText(errorMessage(error))}`,
544
+ "warning"
545
+ );
546
+ }
547
+ }
548
+ }
549
+ async function autoPushSessions(ctx, signal, loaders) {
550
+ try {
551
+ const partial = await loadPartialConfig();
552
+ throwIfAborted(signal);
553
+ if (!partial.automatic) return;
554
+ if (!partial.include.includes("sessions")) return;
555
+ await ensureStateDir();
556
+ throwIfAborted(signal);
557
+ const config = await loadConfig();
558
+ throwIfAborted(signal);
559
+ if (!config.include.includes("sessions")) return;
560
+ const [operations, snapshotModule, syncStateModule] = await Promise.all([
561
+ loaders.operations(),
562
+ loaders.snapshot(),
563
+ loaders.syncState()
564
+ ]);
565
+ throwIfAborted(signal);
566
+ await withLock("auto-session-push", async () => {
567
+ throwIfAborted(signal);
568
+ const state = await readStateForConfig(config);
569
+ throwIfAborted(signal);
570
+ const local = await snapshotModule.createSnapshot(config.snapshotIdentity, {
571
+ ...snapshotOptionsForContext(ctx, config),
572
+ signal
573
+ });
574
+ throwIfAborted(signal);
575
+ if (!syncStateModule.hasLocalChanges(local, state, config)) return;
576
+ await operations.push(ctx, { ...AUTO_SYNC_OPTIONS, signal }, { config, state, local });
577
+ });
578
+ } catch (error) {
579
+ if (signal.aborted || isMissingConfigError(error)) return;
580
+ ctx.ui.setStatus(STATUS_KEY3, void 0);
581
+ ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
582
+ }
583
+ }
584
+
585
+ // src/sync/startup-check.ts
586
+ var STATUS_KEY4 = "sync";
587
+ function createStartupCheck(loaders, attention, timeoutMs = 3e4) {
588
+ let active;
639
589
  return {
640
- include: config.include,
641
- sessionDir: sessionDirFromContext(ctx)
590
+ start(ctx, sessionSignal, initialConfig) {
591
+ if (!ctx.hasUI || sessionSignal.aborted) return;
592
+ if (active) throw new Error("Previous startup check has not been drained.");
593
+ const controller = new AbortController();
594
+ const signal = combineSignals(sessionSignal, controller.signal);
595
+ const task = { controller, owner: ctx.sessionManager, promise: Promise.resolve() };
596
+ active = task;
597
+ const isCurrent = () => !sessionSignal.aborted && active === task && task.owner === ctx.sessionManager;
598
+ const timer = setTimeout(
599
+ () => controller.abort(new DOMException("Startup check timed out", "TimeoutError")),
600
+ timeoutMs
601
+ );
602
+ let config;
603
+ let checking = false;
604
+ task.promise = Promise.resolve().then(async () => {
605
+ try {
606
+ throwIfAborted(signal);
607
+ config = initialConfig ?? await loadConfigForCheck();
608
+ throwIfAborted(signal);
609
+ if (!config.automatic) return;
610
+ const captured = config;
611
+ const identity = syncCheckConfigFingerprint(captured);
612
+ checking = true;
613
+ ctx.ui.setStatus(STATUS_KEY4, "checking in background");
614
+ const { inspectSync } = await loaders.inspection();
615
+ throwIfAborted(signal);
616
+ const currentConfig = await loadConfigForCheck();
617
+ throwIfAborted(signal);
618
+ if (syncCheckConfigFingerprint(currentConfig) !== identity) return;
619
+ const inspection = await withStateDirectoryAccess(() => {
620
+ throwIfAborted(signal);
621
+ return withLock("startup-check", () => {
622
+ throwIfAborted(signal);
623
+ return inspectSync(captured, snapshotOptionsForContext(ctx, captured), signal);
624
+ });
625
+ });
626
+ throwIfAborted(signal);
627
+ const latest = await loadConfigForCheck();
628
+ throwIfAborted(signal);
629
+ if (syncCheckConfigFingerprint(latest) !== identity) return;
630
+ const state = await withStateDirectoryAccess(() => readStateForConfig(latest));
631
+ throwIfAborted(signal);
632
+ if (syncStateFingerprint(state) !== inspection.stateIdentity || !isCurrent()) return;
633
+ attention.observe({
634
+ setupName: captured.setupName,
635
+ configIdentity: identity,
636
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
637
+ inspection
638
+ });
639
+ if (ctx.mode === "rpc") attention.notifyObservation(ctx);
640
+ } catch (error) {
641
+ if (!isCurrent() || sessionSignal.aborted || isMissingConfigError(error)) return;
642
+ if (signal.aborted && controller.signal.reason?.name !== "TimeoutError") return;
643
+ if (config) {
644
+ const latest = await loadConfigForCheck().catch(() => void 0);
645
+ if (!isCurrent() || signal.aborted && controller.signal.reason?.name !== "TimeoutError")
646
+ return;
647
+ if (!latest || syncCheckConfigFingerprint(latest) !== syncCheckConfigFingerprint(config))
648
+ return;
649
+ }
650
+ ctx.ui.notify(
651
+ `pi-sync startup check skipped: ${safeTerminalText(errorMessage(error))}. Run /sync status to retry. No startup transfer was performed.`,
652
+ "warning"
653
+ );
654
+ } finally {
655
+ clearTimeout(timer);
656
+ if (isCurrent() && checking) attention.publish(ctx);
657
+ if (active === task) active = void 0;
658
+ }
659
+ }).catch(() => {
660
+ clearTimeout(timer);
661
+ if (active === task) active = void 0;
662
+ });
663
+ },
664
+ async stop() {
665
+ const task = active;
666
+ if (!task) return;
667
+ task.controller.abort(new DOMException("Startup check cancelled", "AbortError"));
668
+ await task.promise;
669
+ }
642
670
  };
643
671
  }
644
- function sessionDirFromContext(ctx) {
645
- const manager = ctx.sessionManager;
646
- const usesDefaultSessionDir = manager.usesDefaultSessionDir;
647
- if (typeof usesDefaultSessionDir === "function" && usesDefaultSessionDir.call(manager)) {
648
- return void 0;
649
- }
650
- const getSessionDir = manager.getSessionDir;
651
- return typeof getSessionDir === "function" ? getSessionDir.call(manager) : void 0;
672
+
673
+ // src/sync/sync-loaders.ts
674
+ function createSyncLoaders(dependencies) {
675
+ const loaders = {
676
+ inspection: cachedModuleLoader(
677
+ dependencies.loadSyncInspection ?? (() => import("./chunks/sync-inspection-FEP4L5TR.ts"))
678
+ ),
679
+ setupSwitch: cachedModuleLoader(
680
+ dependencies.loadSetupSwitch ?? (() => import("./chunks/setup-switch-IS2PEZ3J.ts"))
681
+ ),
682
+ snapshot: cachedModuleLoader(
683
+ dependencies.loadSnapshot ?? (() => import("./chunks/snapshot-HKEWCVK6.ts"))
684
+ ),
685
+ syncState: cachedModuleLoader(dependencies.loadSyncState ?? (() => import("./chunks/sync-state-Q2FTXTW6.ts"))),
686
+ operations: cachedModuleLoader(
687
+ dependencies.loadSyncOperations ?? (() => import("./chunks/sync-operations-ONJ27QRL.ts"))
688
+ )
689
+ };
690
+ return loaders;
652
691
  }
653
692
  function cachedModuleLoader(load) {
654
693
  let pending;
@@ -662,6 +701,102 @@ function cachedModuleLoader(load) {
662
701
  return pending;
663
702
  };
664
703
  }
704
+
705
+ // src/sync-extension.ts
706
+ var STATUS_KEY5 = "sync";
707
+ function sync(pi, dependencies = {}) {
708
+ const loaders = createSyncLoaders(dependencies);
709
+ const attention = createSyncAttentionController();
710
+ const check = createStartupCheck(loaders, attention);
711
+ let sessionAbort = new AbortController();
712
+ let shutdownAbort;
713
+ let initialization = Promise.resolve(void 0);
714
+ pi.registerCommand("sync", {
715
+ description: "Sync Pi settings through Git, WebDAV, R2, or S3-compatible storage",
716
+ getArgumentCompletions: completeSyncArguments,
717
+ handler: async (args, ctx) => {
718
+ if (!ctx.hasUI) {
719
+ throw new Error(
720
+ "/sync requires TUI or RPC mode so results and safety prompts are observable."
721
+ );
722
+ }
723
+ const signal = sessionAbort.signal;
724
+ const ready = await initialization;
725
+ if (signal.aborted) return;
726
+ await check.stop();
727
+ if (signal.aborted) return;
728
+ const command = splitArgs(args)[0];
729
+ if (ready === false && command !== "help" && command !== "unlock" && command !== "migrate-state") {
730
+ ctx.ui.notify(
731
+ "pi-sync recovery required. Repair the startup error, then /reload before syncing. Use /sync help for recovery guidance.",
732
+ "error"
733
+ );
734
+ return;
735
+ }
736
+ const run = () => handleCommand(args, ctx, signal, loaders, attention);
737
+ if (command === "migrate-state" || command === "help") await run();
738
+ else await withStateDirectoryAccess(run);
739
+ }
740
+ });
741
+ pi.on("session_start", async (_event, ctx) => {
742
+ shutdownAbort?.abort(new DOMException("Session replaced", "AbortError"));
743
+ shutdownAbort = void 0;
744
+ sessionAbort.abort(new DOMException("Session replaced", "AbortError"));
745
+ sessionAbort = new AbortController();
746
+ const signal = sessionAbort.signal;
747
+ attention.reset(ctx);
748
+ const previous = initialization;
749
+ initialization = (async () => {
750
+ await previous;
751
+ if (signal.aborted) return false;
752
+ await check.stop();
753
+ if (signal.aborted) return false;
754
+ try {
755
+ const config = await withStateDirectoryAccess(() => startSession(ctx, signal));
756
+ return signal.aborted ? false : config;
757
+ } catch (error) {
758
+ if (!signal.aborted && ctx.hasUI) {
759
+ ctx.ui.notify(
760
+ `pi-sync recovery required: ${safeTerminalText(errorMessage(error))}`,
761
+ "error"
762
+ );
763
+ }
764
+ return false;
765
+ }
766
+ })();
767
+ const ready = await initialization;
768
+ if (ready && !signal.aborted) check.start(ctx, signal, ready);
769
+ });
770
+ pi.on("session_shutdown", async (event, ctx) => {
771
+ sessionAbort.abort(new DOMException("Session shut down", "AbortError"));
772
+ attention.reset(ctx);
773
+ shutdownAbort?.abort(new DOMException("Session shut down again", "AbortError"));
774
+ const controller = new AbortController();
775
+ shutdownAbort = controller;
776
+ const signal = combineSignals(controller.signal, AbortSignal.timeout(3e4));
777
+ const reason = typeof event === "object" && event ? event.reason : void 0;
778
+ try {
779
+ const ready = await initialization;
780
+ if (signal.aborted) return;
781
+ await check.stop();
782
+ if (signal.aborted) return;
783
+ if (ready !== false && reason !== "reload") {
784
+ await withStateDirectoryAccess(async () => {
785
+ if (signal.aborted) return;
786
+ await autoPushSessions(ctx, signal, loaders);
787
+ });
788
+ }
789
+ } catch (error) {
790
+ if (!signal.aborted) {
791
+ ctx.ui.notify(`pi-sync session push skipped: ${errorMessage(error)}`, "warning");
792
+ }
793
+ } finally {
794
+ if (shutdownAbort === controller) shutdownAbort = void 0;
795
+ }
796
+ if (signal.aborted) return;
797
+ ctx.ui.setStatus(STATUS_KEY5, void 0);
798
+ });
799
+ }
665
800
  export {
666
801
  sync as default
667
802
  };