@narumitw/pi-sync 0.50.3 → 0.51.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +4 -1
  2. package/dist/chunks/{chunk-6LP3MCR2.ts → chunk-2JZFHNLM.ts} +3 -5
  3. package/dist/chunks/chunk-2JZFHNLM.ts.map +7 -0
  4. package/dist/chunks/{chunk-LCRI3ABW.ts → chunk-2RZSQWBH.ts} +8 -19
  5. package/dist/chunks/chunk-2RZSQWBH.ts.map +7 -0
  6. package/dist/chunks/{chunk-IJ6FJFA5.ts → chunk-4AIHXRKE.ts} +11 -17
  7. package/dist/chunks/chunk-4AIHXRKE.ts.map +7 -0
  8. package/dist/chunks/{chunk-C7XY5NF5.ts → chunk-4MPIH46N.ts} +29 -51
  9. package/dist/chunks/chunk-4MPIH46N.ts.map +7 -0
  10. package/dist/chunks/{chunk-5B3FXBLE.ts → chunk-6546TS54.ts} +16 -20
  11. package/dist/chunks/chunk-6546TS54.ts.map +7 -0
  12. package/dist/chunks/{chunk-Q2TUM5X6.ts → chunk-6NT3YFGY.ts} +2 -2
  13. package/dist/chunks/chunk-6NT3YFGY.ts.map +7 -0
  14. package/dist/chunks/{chunk-2B2ELUF5.ts → chunk-7D5SF7P3.ts} +1 -1
  15. package/dist/chunks/chunk-7D5SF7P3.ts.map +7 -0
  16. package/dist/chunks/{chunk-YDWUZVPN.ts → chunk-7KHKWL3Q.ts} +28 -47
  17. package/dist/chunks/chunk-7KHKWL3Q.ts.map +7 -0
  18. package/dist/chunks/{chunk-ZI5MXMEX.ts → chunk-BVVP33U5.ts} +2 -4
  19. package/dist/chunks/chunk-BVVP33U5.ts.map +7 -0
  20. package/dist/chunks/{chunk-SZWBGPID.ts → chunk-DM4X4FIA.ts} +8 -13
  21. package/dist/chunks/chunk-DM4X4FIA.ts.map +7 -0
  22. package/dist/chunks/{chunk-SL64RLXR.ts → chunk-DZUSTGQV.ts} +8 -16
  23. package/dist/chunks/chunk-DZUSTGQV.ts.map +7 -0
  24. package/dist/chunks/{chunk-YHEMENKV.ts → chunk-E42GXD6D.ts} +13 -19
  25. package/dist/chunks/chunk-E42GXD6D.ts.map +7 -0
  26. package/dist/chunks/{chunk-Z4DQF6L3.ts → chunk-ECYCNZOE.ts} +1 -1
  27. package/dist/chunks/chunk-ECYCNZOE.ts.map +7 -0
  28. package/dist/chunks/{chunk-OEX4ABY6.ts → chunk-EEJPYTWE.ts} +11 -21
  29. package/dist/chunks/chunk-EEJPYTWE.ts.map +7 -0
  30. package/dist/chunks/{chunk-CPEHGXJV.ts → chunk-G4RUXRWZ.ts} +3 -5
  31. package/dist/chunks/chunk-G4RUXRWZ.ts.map +7 -0
  32. package/dist/chunks/{chunk-T2B5IU74.ts → chunk-HGJH56KR.ts} +4 -4
  33. package/dist/chunks/chunk-HGJH56KR.ts.map +7 -0
  34. package/dist/chunks/{chunk-JAAKLL6M.ts → chunk-IVXEDPAD.ts} +10 -34
  35. package/dist/chunks/chunk-IVXEDPAD.ts.map +7 -0
  36. package/dist/chunks/chunk-K3RIEL6J.ts +20 -0
  37. package/dist/chunks/chunk-K3RIEL6J.ts.map +7 -0
  38. package/dist/chunks/{chunk-LTBWGSBV.ts → chunk-MSSW6F3R.ts} +1 -1
  39. package/dist/chunks/chunk-MSSW6F3R.ts.map +7 -0
  40. package/dist/chunks/{chunk-T3HH3UEJ.ts → chunk-OVDTF5CH.ts} +4 -10
  41. package/dist/chunks/chunk-OVDTF5CH.ts.map +7 -0
  42. package/dist/chunks/{chunk-NJFUIVYL.ts → chunk-PWMWOJP4.ts} +1 -1
  43. package/dist/chunks/chunk-PWMWOJP4.ts.map +7 -0
  44. package/dist/chunks/{chunk-HUDQATCL.ts → chunk-Q32CQWPM.ts} +6 -8
  45. package/dist/chunks/chunk-Q32CQWPM.ts.map +7 -0
  46. package/dist/chunks/{chunk-ETTRJDNP.ts → chunk-QLSN7AGX.ts} +6 -13
  47. package/dist/chunks/chunk-QLSN7AGX.ts.map +7 -0
  48. package/dist/chunks/{chunk-THMF4BVL.ts → chunk-SWUXA3MH.ts} +1 -1
  49. package/dist/chunks/chunk-SWUXA3MH.ts.map +7 -0
  50. package/dist/chunks/{chunk-TEN2ZBJV.ts → chunk-T2SJD3N7.ts} +2 -4
  51. package/dist/chunks/chunk-T2SJD3N7.ts.map +7 -0
  52. package/dist/chunks/{chunk-AUSDLD6F.ts → chunk-T56RNRZJ.ts} +10 -26
  53. package/dist/chunks/chunk-T56RNRZJ.ts.map +7 -0
  54. package/dist/chunks/{chunk-DHVFS4QK.ts → chunk-UA7RXOIT.ts} +37 -90
  55. package/dist/chunks/chunk-UA7RXOIT.ts.map +7 -0
  56. package/dist/chunks/{chunk-D5X27LCA.ts → chunk-UETBKTDI.ts} +1 -1
  57. package/dist/chunks/chunk-UETBKTDI.ts.map +7 -0
  58. package/dist/chunks/{chunk-CHH26D4F.ts → chunk-YFADF5ZQ.ts} +7 -6
  59. package/dist/chunks/chunk-YFADF5ZQ.ts.map +7 -0
  60. package/dist/chunks/{chunk-CCSN3F47.ts → chunk-YPXGU5OR.ts} +4 -4
  61. package/dist/chunks/chunk-YPXGU5OR.ts.map +7 -0
  62. package/dist/chunks/{file-selection-FTTKAOYX.ts → file-selection-RAVHRYFG.ts} +14 -26
  63. package/dist/chunks/file-selection-RAVHRYFG.ts.map +7 -0
  64. package/dist/chunks/{git-backend-GMVS4PD4.ts → git-backend-IAANDL3K.ts} +41 -104
  65. package/dist/chunks/git-backend-IAANDL3K.ts.map +7 -0
  66. package/dist/chunks/manager-result-dispatcher-OTUMQM26.ts +23 -0
  67. package/dist/chunks/{manager-ui-IPAOAWZA.ts → manager-ui-763NEKJY.ts} +124 -251
  68. package/dist/chunks/manager-ui-763NEKJY.ts.map +7 -0
  69. package/dist/chunks/remote-selection-ui-TZTJF32Y.ts +22 -0
  70. package/dist/chunks/{s3-backend-66O4FGBH.ts → s3-backend-XGXUOPUN.ts} +23 -54
  71. package/dist/chunks/s3-backend-XGXUOPUN.ts.map +7 -0
  72. package/dist/chunks/{setup-switch-IS2PEZ3J.ts → setup-switch-5G2VNJHL.ts} +9 -9
  73. package/dist/chunks/{setup-wizard-JUJZYNNM.ts → setup-wizard-YIEWGX32.ts} +12 -12
  74. package/dist/chunks/setup-wizard-YIEWGX32.ts.map +7 -0
  75. package/dist/chunks/{snapshot-HKEWCVK6.ts → snapshot-JL5DFSXI.ts} +5 -5
  76. package/dist/chunks/sync-inspection-P3ENJLNF.ts +20 -0
  77. package/dist/chunks/{sync-operations-LBX3GKCM.ts → sync-operations-AR76EB6C.ts} +85 -201
  78. package/dist/chunks/sync-operations-AR76EB6C.ts.map +7 -0
  79. package/dist/chunks/{sync-state-Q2FTXTW6.ts → sync-state-VHJUZ4YG.ts} +6 -6
  80. package/dist/chunks/{webdav-backend-ECIKD7SO.ts → webdav-backend-3EGUONAT.ts} +20 -51
  81. package/dist/chunks/webdav-backend-3EGUONAT.ts.map +7 -0
  82. package/dist/index.ts +49 -74
  83. package/dist/index.ts.map +3 -3
  84. package/docs/settings.md +9 -0
  85. package/package.json +61 -62
  86. package/src/backends/backend-factory.ts +15 -15
  87. package/src/backends/backend-identity.ts +31 -31
  88. package/src/backends/backend-types.ts +42 -42
  89. package/src/backends/git/git-backend.ts +714 -788
  90. package/src/backends/git/git-config.ts +104 -111
  91. package/src/backends/git/git-runner.ts +230 -247
  92. package/src/backends/git/git-storage.ts +228 -245
  93. package/src/backends/s3/s3-backend.ts +408 -441
  94. package/src/backends/s3/s3-client.ts +288 -333
  95. package/src/backends/sync-backend.ts +65 -65
  96. package/src/backends/webdav/webdav-backend.ts +542 -589
  97. package/src/backends/webdav/webdav-client.ts +292 -316
  98. package/src/backends/webdav/webdav-config.ts +57 -68
  99. package/src/commands/command-execution.ts +270 -289
  100. package/src/commands/command-handler.ts +160 -163
  101. package/src/commands/command-types.ts +13 -13
  102. package/src/commands/command.ts +190 -196
  103. package/src/commands/setup-completions.ts +1 -1
  104. package/src/paths.ts +47 -47
  105. package/src/settings/config-errors.ts +4 -5
  106. package/src/settings/config-file.ts +473 -506
  107. package/src/settings/config.ts +171 -174
  108. package/src/settings/settings-management.ts +191 -216
  109. package/src/settings/settings-store.ts +63 -77
  110. package/src/settings/settings-types.ts +78 -75
  111. package/src/settings/settings-validation.ts +328 -364
  112. package/src/snapshot/session-paths.ts +41 -55
  113. package/src/snapshot/snapshot-apply.ts +197 -229
  114. package/src/snapshot/snapshot-codec.ts +29 -31
  115. package/src/snapshot/snapshot-paths.ts +4 -6
  116. package/src/snapshot/snapshot-transaction.ts +161 -170
  117. package/src/snapshot/snapshot-types.ts +24 -24
  118. package/src/snapshot/snapshot.ts +310 -343
  119. package/src/state/json-file.ts +11 -11
  120. package/src/state/lock.ts +226 -242
  121. package/src/state/lockfile-fs.ts +20 -20
  122. package/src/state/operation-availability.ts +36 -41
  123. package/src/state/state-directory.ts +171 -173
  124. package/src/state/state-types.ts +15 -15
  125. package/src/state/sync-state-store.ts +23 -23
  126. package/src/sync/automatic-sync.ts +75 -80
  127. package/src/sync/remote-snapshot.ts +52 -60
  128. package/src/sync/setup-switch.ts +99 -114
  129. package/src/sync/signals.ts +3 -5
  130. package/src/sync/startup-check.ts +97 -111
  131. package/src/sync/sync-decision.ts +76 -80
  132. package/src/sync/sync-error-guidance.ts +41 -48
  133. package/src/sync/sync-errors.ts +10 -10
  134. package/src/sync/sync-inspection.ts +29 -33
  135. package/src/sync/sync-loaders.ts +28 -36
  136. package/src/sync/sync-mutations.ts +510 -622
  137. package/src/sync/sync-operations.ts +7 -7
  138. package/src/sync/sync-policy.ts +296 -326
  139. package/src/sync/sync-queries.ts +175 -194
  140. package/src/sync/sync-state.ts +74 -100
  141. package/src/sync-extension.ts +89 -101
  142. package/src/sync.ts +26 -26
  143. package/src/types.ts +37 -37
  144. package/src/ui/automatic-sync-summary.ts +1 -3
  145. package/src/ui/cancellable-operation.ts +89 -101
  146. package/src/ui/file-selection.ts +250 -274
  147. package/src/ui/manager-attention.ts +51 -63
  148. package/src/ui/manager-recovery.ts +48 -57
  149. package/src/ui/manager-result-dispatcher.ts +62 -76
  150. package/src/ui/manager-state.ts +200 -232
  151. package/src/ui/manager-ui.ts +264 -362
  152. package/src/ui/remote-selection-ui.ts +547 -588
  153. package/src/ui/secret-input.ts +197 -205
  154. package/src/ui/settings-ui.ts +175 -172
  155. package/src/ui/setup/git-ui.ts +232 -261
  156. package/src/ui/setup/s3-credentials-ui.ts +58 -65
  157. package/src/ui/setup/s3-ui.ts +171 -195
  158. package/src/ui/setup/setup-actions.ts +91 -101
  159. package/src/ui/setup/setup-location-ui.ts +121 -137
  160. package/src/ui/setup/setup-name-ui.ts +27 -27
  161. package/src/ui/setup/setup-prompts.ts +65 -65
  162. package/src/ui/setup/setup-review.ts +58 -59
  163. package/src/ui/setup/setup-switcher.ts +156 -160
  164. package/src/ui/setup/setup-wizard.ts +28 -28
  165. package/src/ui/setup/text-input.ts +54 -58
  166. package/src/ui/setup/webdav-ui.ts +246 -293
  167. package/src/ui/storage-connections-ui.ts +317 -369
  168. package/src/ui/sync-attention.ts +148 -164
  169. package/src/ui/sync-format.ts +110 -121
  170. package/src/ui/sync-resolution-ui.ts +147 -153
  171. package/src/ui/sync-setups-ui.ts +218 -238
  172. package/src/ui/sync-status.ts +17 -0
  173. package/src/ui/terminal-text.ts +2 -2
  174. package/dist/chunks/chunk-2B2ELUF5.ts.map +0 -7
  175. package/dist/chunks/chunk-5B3FXBLE.ts.map +0 -7
  176. package/dist/chunks/chunk-6LP3MCR2.ts.map +0 -7
  177. package/dist/chunks/chunk-AUSDLD6F.ts.map +0 -7
  178. package/dist/chunks/chunk-C7XY5NF5.ts.map +0 -7
  179. package/dist/chunks/chunk-CCSN3F47.ts.map +0 -7
  180. package/dist/chunks/chunk-CHH26D4F.ts.map +0 -7
  181. package/dist/chunks/chunk-CPEHGXJV.ts.map +0 -7
  182. package/dist/chunks/chunk-D5X27LCA.ts.map +0 -7
  183. package/dist/chunks/chunk-DHVFS4QK.ts.map +0 -7
  184. package/dist/chunks/chunk-ETTRJDNP.ts.map +0 -7
  185. package/dist/chunks/chunk-HUDQATCL.ts.map +0 -7
  186. package/dist/chunks/chunk-IJ6FJFA5.ts.map +0 -7
  187. package/dist/chunks/chunk-JAAKLL6M.ts.map +0 -7
  188. package/dist/chunks/chunk-LCRI3ABW.ts.map +0 -7
  189. package/dist/chunks/chunk-LTBWGSBV.ts.map +0 -7
  190. package/dist/chunks/chunk-NJFUIVYL.ts.map +0 -7
  191. package/dist/chunks/chunk-OEX4ABY6.ts.map +0 -7
  192. package/dist/chunks/chunk-Q2TUM5X6.ts.map +0 -7
  193. package/dist/chunks/chunk-SL64RLXR.ts.map +0 -7
  194. package/dist/chunks/chunk-SZWBGPID.ts.map +0 -7
  195. package/dist/chunks/chunk-T2B5IU74.ts.map +0 -7
  196. package/dist/chunks/chunk-T3HH3UEJ.ts.map +0 -7
  197. package/dist/chunks/chunk-TEN2ZBJV.ts.map +0 -7
  198. package/dist/chunks/chunk-THMF4BVL.ts.map +0 -7
  199. package/dist/chunks/chunk-YDWUZVPN.ts.map +0 -7
  200. package/dist/chunks/chunk-YHEMENKV.ts.map +0 -7
  201. package/dist/chunks/chunk-Z4DQF6L3.ts.map +0 -7
  202. package/dist/chunks/chunk-ZI5MXMEX.ts.map +0 -7
  203. package/dist/chunks/file-selection-FTTKAOYX.ts.map +0 -7
  204. package/dist/chunks/git-backend-GMVS4PD4.ts.map +0 -7
  205. package/dist/chunks/manager-result-dispatcher-ASKGOSW5.ts +0 -22
  206. package/dist/chunks/manager-ui-IPAOAWZA.ts.map +0 -7
  207. package/dist/chunks/remote-selection-ui-QDNR7WEI.ts +0 -21
  208. package/dist/chunks/s3-backend-66O4FGBH.ts.map +0 -7
  209. package/dist/chunks/setup-wizard-JUJZYNNM.ts.map +0 -7
  210. package/dist/chunks/sync-inspection-CST2KL65.ts +0 -20
  211. package/dist/chunks/sync-operations-LBX3GKCM.ts.map +0 -7
  212. package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +0 -7
  213. /package/dist/chunks/{manager-result-dispatcher-ASKGOSW5.ts.map → manager-result-dispatcher-OTUMQM26.ts.map} +0 -0
  214. /package/dist/chunks/{remote-selection-ui-QDNR7WEI.ts.map → remote-selection-ui-TZTJF32Y.ts.map} +0 -0
  215. /package/dist/chunks/{setup-switch-IS2PEZ3J.ts.map → setup-switch-5G2VNJHL.ts.map} +0 -0
  216. /package/dist/chunks/{snapshot-HKEWCVK6.ts.map → snapshot-JL5DFSXI.ts.map} +0 -0
  217. /package/dist/chunks/{sync-inspection-CST2KL65.ts.map → sync-inspection-P3ENJLNF.ts.map} +0 -0
  218. /package/dist/chunks/{sync-state-Q2FTXTW6.ts.map → sync-state-VHJUZ4YG.ts.map} +0 -0
@@ -1,15 +1,15 @@
1
1
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
  import {
3
- normalizeWebDavPath,
4
- normalizeWebDavUrl,
5
- validateWebDavCredentials,
3
+ normalizeWebDavPath,
4
+ normalizeWebDavUrl,
5
+ validateWebDavCredentials,
6
6
  } from "../../backends/webdav/webdav-config.js";
7
7
  import {
8
- addStorageConnection,
9
- addSyncSetup,
10
- saveNewV3Settings,
11
- updateStorageConnection,
12
- updateSyncSetup,
8
+ addStorageConnection,
9
+ addSyncSetup,
10
+ saveNewV3Settings,
11
+ updateStorageConnection,
12
+ updateSyncSetup,
13
13
  } from "../../settings/settings-management.js";
14
14
  import type { PartialConfig } from "../../settings/settings-types.js";
15
15
  import { automaticSyncSummary } from "../automatic-sync-summary.js";
@@ -17,314 +17,267 @@ import { promptSecret } from "../secret-input.js";
17
17
  import { safeTerminalText as safe } from "../terminal-text.js";
18
18
  import { promptAvailableSetupStorage } from "./setup-location-ui.js";
19
19
  import {
20
- chooseAutomaticSync,
21
- chooseSessions,
22
- chooseSetupContent,
23
- includedContentLines,
24
- promptResourceName,
25
- readSetupConnection,
20
+ chooseAutomaticSync,
21
+ chooseSessions,
22
+ chooseSetupContent,
23
+ includedContentLines,
24
+ promptResourceName,
25
+ readSetupConnection,
26
26
  } from "./setup-prompts.js";
27
27
  import { saveReviewedDraft } from "./setup-review.js";
28
28
  import { promptTextInput } from "./text-input.js";
29
29
 
30
- export async function showWebDavSetup(
31
- ctx: ExtensionCommandContext,
32
- targetName: string,
33
- signal?: AbortSignal,
34
- ) {
35
- const url = await promptWebDavUrl(ctx, signal);
36
- if (!url) return false;
37
- const username = await promptUsername(ctx, signal);
38
- if (!username) return false;
39
- const password = await promptSecret(ctx, "WebDAV password", { signal });
40
- signal?.throwIfAborted();
41
- if (password === undefined) return false;
42
- const remotePath = await chooseDestination(ctx, signal);
43
- if (!remotePath) return false;
44
- const include = await chooseSetupContent(ctx, signal);
45
- if (!include) return false;
46
- const automatic = await chooseAutomaticSync(ctx, signal);
47
- if (automatic === undefined) return false;
48
- const sessions = await chooseSessions(ctx, signal);
49
- if (sessions === undefined) return false;
50
- const saved = await saveReviewedDraft(
51
- ctx,
52
- "Review WebDAV setup",
53
- [
54
- `Sync setup: ${safe(targetName)}`,
55
- `Storage connection: ${safe(targetName)} (WebDAV)`,
56
- `URL: ${safe(url)}`,
57
- `Storage location: ${safe(remotePath)}`,
58
- "Username and password: stored privately (values hidden)",
59
- ...includedContentLines(include, sessions),
60
- `Automatic sync: ${automaticSyncSummary(automatic)}`,
61
- "Check setup tests whether this server can safely save sync updates using a temporary probe.",
62
- "Saving does not contact remote storage or start syncing.",
63
- ],
64
- "Save setup",
65
- (saveSignal) =>
66
- saveNewV3Settings(
67
- {
68
- setupName: targetName,
69
- connectionName: targetName,
70
- connection: { type: "webdav", url, credentials: { username, password } },
71
- setup: {
72
- storage: { connection: targetName, path: remotePath },
73
- sync: { include: [...include, ...(sessions ? ["sessions"] : [])], automatic },
74
- },
75
- },
76
- saveSignal,
77
- ),
78
- signal,
79
- );
80
- if (saved && !signal?.aborted)
81
- ctx.ui.notify(`Sync setup “${safe(targetName)}” saved. Choose Sync now to start.`, "info");
82
- return saved;
30
+ export async function showWebDavSetup(ctx: ExtensionCommandContext, targetName: string, signal?: AbortSignal) {
31
+ const url = await promptWebDavUrl(ctx, signal);
32
+ if (!url) return false;
33
+ const username = await promptUsername(ctx, signal);
34
+ if (!username) return false;
35
+ const password = await promptSecret(ctx, "WebDAV password", { signal });
36
+ signal?.throwIfAborted();
37
+ if (password === undefined) return false;
38
+ const remotePath = await chooseDestination(ctx, signal);
39
+ if (!remotePath) return false;
40
+ const include = await chooseSetupContent(ctx, signal);
41
+ if (!include) return false;
42
+ const automatic = await chooseAutomaticSync(ctx, signal);
43
+ if (automatic === undefined) return false;
44
+ const sessions = await chooseSessions(ctx, signal);
45
+ if (sessions === undefined) return false;
46
+ const saved = await saveReviewedDraft(
47
+ ctx,
48
+ "Review WebDAV setup",
49
+ [
50
+ `Sync setup: ${safe(targetName)}`,
51
+ `Storage connection: ${safe(targetName)} (WebDAV)`,
52
+ `URL: ${safe(url)}`,
53
+ `Storage location: ${safe(remotePath)}`,
54
+ "Username and password: stored privately (values hidden)",
55
+ ...includedContentLines(include, sessions),
56
+ `Automatic sync: ${automaticSyncSummary(automatic)}`,
57
+ "Check setup tests whether this server can safely save sync updates using a temporary probe.",
58
+ "Saving does not contact remote storage or start syncing.",
59
+ ],
60
+ "Save setup",
61
+ (saveSignal) =>
62
+ saveNewV3Settings(
63
+ {
64
+ setupName: targetName,
65
+ connectionName: targetName,
66
+ connection: { type: "webdav", url, credentials: { username, password } },
67
+ setup: {
68
+ storage: { connection: targetName, path: remotePath },
69
+ sync: { include: [...include, ...(sessions ? ["sessions"] : [])], automatic },
70
+ },
71
+ },
72
+ saveSignal,
73
+ ),
74
+ signal,
75
+ );
76
+ if (saved && !signal?.aborted)
77
+ ctx.ui.notify(`Sync setup “${safe(targetName)}” saved. Choose Sync now to start.`, "info");
78
+ return saved;
83
79
  }
84
80
 
85
81
  export async function showAddWebDavTarget(
86
- ctx: ExtensionCommandContext,
87
- name: string,
88
- profile: string,
89
- signal?: AbortSignal,
82
+ ctx: ExtensionCommandContext,
83
+ name: string,
84
+ profile: string,
85
+ signal?: AbortSignal,
90
86
  ) {
91
- const remotePath = await chooseDestination(ctx, signal);
92
- if (!remotePath) return false;
93
- const storage = await promptAvailableSetupStorage(
94
- ctx,
95
- { connection: profile, path: remotePath },
96
- signal,
97
- );
98
- if (!storage) return false;
99
- const include = await chooseSetupContent(ctx, signal);
100
- if (!include) return false;
101
- const automatic = await chooseAutomaticSync(ctx, signal);
102
- if (automatic === undefined) return false;
103
- const connection = await readSetupConnection(profile, signal);
104
- if (connection.type !== "webdav") throw new Error("Storage connection changed; reopen setup.");
105
- const saved = await saveReviewedDraft(
106
- ctx,
107
- "Review WebDAV sync setup",
108
- [
109
- `Sync setup: ${safe(name)}`,
110
- `Storage connection: ${safe(profile)}`,
111
- `URL: ${safe(connection.url)}`,
112
- `Storage location: ${safe(storage.path)}`,
113
- ...includedContentLines(include),
114
- `Automatic sync: ${automaticSyncSummary(automatic)}`,
115
- "Adding this setup does not sync or modify remote data.",
116
- ],
117
- "Add sync setup",
118
- (saveSignal) =>
119
- addSyncSetup(name, { storage, sync: { include, automatic } }, saveSignal, connection),
120
- signal,
121
- );
122
- if (saved && !signal?.aborted) ctx.ui.notify(`Added sync setup “${safe(name)}”.`, "info");
123
- return saved;
87
+ const remotePath = await chooseDestination(ctx, signal);
88
+ if (!remotePath) return false;
89
+ const storage = await promptAvailableSetupStorage(ctx, { connection: profile, path: remotePath }, signal);
90
+ if (!storage) return false;
91
+ const include = await chooseSetupContent(ctx, signal);
92
+ if (!include) return false;
93
+ const automatic = await chooseAutomaticSync(ctx, signal);
94
+ if (automatic === undefined) return false;
95
+ const connection = await readSetupConnection(profile, signal);
96
+ if (connection.type !== "webdav") throw new Error("Storage connection changed; reopen setup.");
97
+ const saved = await saveReviewedDraft(
98
+ ctx,
99
+ "Review WebDAV sync setup",
100
+ [
101
+ `Sync setup: ${safe(name)}`,
102
+ `Storage connection: ${safe(profile)}`,
103
+ `URL: ${safe(connection.url)}`,
104
+ `Storage location: ${safe(storage.path)}`,
105
+ ...includedContentLines(include),
106
+ `Automatic sync: ${automaticSyncSummary(automatic)}`,
107
+ "Adding this setup does not sync or modify remote data.",
108
+ ],
109
+ "Add sync setup",
110
+ (saveSignal) => addSyncSetup(name, { storage, sync: { include, automatic } }, saveSignal, connection),
111
+ signal,
112
+ );
113
+ if (saved && !signal?.aborted) ctx.ui.notify(`Added sync setup “${safe(name)}”.`, "info");
114
+ return saved;
124
115
  }
125
116
 
126
- export async function showEditWebDavTarget(
127
- ctx: ExtensionCommandContext,
128
- partial: PartialConfig,
129
- signal?: AbortSignal,
130
- ) {
131
- const remotePath = await chooseDestination(ctx, signal, partial.storagePath);
132
- if (!remotePath) return false;
133
- const connection = await readSetupConnection(partial.connectionName, signal);
134
- if (connection.type !== "webdav") throw new Error("Storage connection changed; reopen setup.");
135
- const saved = await saveReviewedDraft(
136
- ctx,
137
- "Review sync setup",
138
- [
139
- `Sync setup: ${safe(partial.setupName)}`,
140
- `Storage connection: ${safe(partial.connectionName)}`,
141
- `URL: ${safe(connection.url)}`,
142
- `Storage path: ${safe(partial.storagePath)} → ${safe(remotePath)}`,
143
- "Saving changes the future storage location only; it does not move or delete remote data.",
144
- ],
145
- "Save sync setup",
146
- (saveSignal) =>
147
- updateSyncSetup(
148
- partial.setupName,
149
- (setup) => ({
150
- ...setup,
151
- storage: { ...setup.storage, path: remotePath },
152
- }),
153
- { expectedStorage: partial, expectedConnection: connection, signal: saveSignal },
154
- ),
155
- signal,
156
- );
157
- if (saved && !signal?.aborted)
158
- ctx.ui.notify(`Saved sync setup “${safe(partial.setupName)}”.`, "info");
159
- return saved;
117
+ export async function showEditWebDavTarget(ctx: ExtensionCommandContext, partial: PartialConfig, signal?: AbortSignal) {
118
+ const remotePath = await chooseDestination(ctx, signal, partial.storagePath);
119
+ if (!remotePath) return false;
120
+ const connection = await readSetupConnection(partial.connectionName, signal);
121
+ if (connection.type !== "webdav") throw new Error("Storage connection changed; reopen setup.");
122
+ const saved = await saveReviewedDraft(
123
+ ctx,
124
+ "Review sync setup",
125
+ [
126
+ `Sync setup: ${safe(partial.setupName)}`,
127
+ `Storage connection: ${safe(partial.connectionName)}`,
128
+ `URL: ${safe(connection.url)}`,
129
+ `Storage path: ${safe(partial.storagePath)} → ${safe(remotePath)}`,
130
+ "Saving changes the future storage location only; it does not move or delete remote data.",
131
+ ],
132
+ "Save sync setup",
133
+ (saveSignal) =>
134
+ updateSyncSetup(
135
+ partial.setupName,
136
+ (setup) => ({
137
+ ...setup,
138
+ storage: { ...setup.storage, path: remotePath },
139
+ }),
140
+ { expectedStorage: partial, expectedConnection: connection, signal: saveSignal },
141
+ ),
142
+ signal,
143
+ );
144
+ if (saved && !signal?.aborted) ctx.ui.notify(`Saved sync setup “${safe(partial.setupName)}”.`, "info");
145
+ return saved;
160
146
  }
161
147
 
162
- export async function showAddWebDavStorageProfile(
163
- ctx: ExtensionCommandContext,
164
- signal?: AbortSignal,
165
- ) {
166
- const name = await promptResourceName(ctx, "storage connection", "webdav", signal, true);
167
- if (!name) return false;
168
- const url = await promptWebDavUrl(ctx, signal);
169
- if (!url) return false;
170
- const username = await promptUsername(ctx, signal);
171
- if (!username) return false;
172
- const password = await promptSecret(ctx, "WebDAV password", { signal });
173
- signal?.throwIfAborted();
174
- if (password === undefined) return false;
175
- const saved = await saveReviewedDraft(
176
- ctx,
177
- "Review storage connection",
178
- [
179
- `Name: ${safe(name)}`,
180
- "Type: WebDAV",
181
- `URL: ${safe(url)}`,
182
- "Username and password: stored privately (values hidden)",
183
- "Adding a connection does not contact the server or start syncing.",
184
- ],
185
- "Add storage connection",
186
- (saveSignal) =>
187
- addStorageConnection(
188
- name,
189
- { type: "webdav", url, credentials: { username, password } },
190
- saveSignal,
191
- ),
192
- signal,
193
- );
194
- if (saved && !signal?.aborted) ctx.ui.notify(`Added storage connection “${safe(name)}”.`, "info");
195
- return saved;
148
+ export async function showAddWebDavStorageProfile(ctx: ExtensionCommandContext, signal?: AbortSignal) {
149
+ const name = await promptResourceName(ctx, "storage connection", "webdav", signal, true);
150
+ if (!name) return false;
151
+ const url = await promptWebDavUrl(ctx, signal);
152
+ if (!url) return false;
153
+ const username = await promptUsername(ctx, signal);
154
+ if (!username) return false;
155
+ const password = await promptSecret(ctx, "WebDAV password", { signal });
156
+ signal?.throwIfAborted();
157
+ if (password === undefined) return false;
158
+ const saved = await saveReviewedDraft(
159
+ ctx,
160
+ "Review storage connection",
161
+ [
162
+ `Name: ${safe(name)}`,
163
+ "Type: WebDAV",
164
+ `URL: ${safe(url)}`,
165
+ "Username and password: stored privately (values hidden)",
166
+ "Adding a connection does not contact the server or start syncing.",
167
+ ],
168
+ "Add storage connection",
169
+ (saveSignal) =>
170
+ addStorageConnection(name, { type: "webdav", url, credentials: { username, password } }, saveSignal),
171
+ signal,
172
+ );
173
+ if (saved && !signal?.aborted) ctx.ui.notify(`Added storage connection “${safe(name)}”.`, "info");
174
+ return saved;
196
175
  }
197
176
 
198
177
  export async function showEditWebDavStorageProfile(
199
- ctx: ExtensionCommandContext,
200
- name: string,
201
- profile: Record<string, unknown>,
202
- signal?: AbortSignal,
203
- affectedSetups?: string[],
178
+ ctx: ExtensionCommandContext,
179
+ name: string,
180
+ profile: Record<string, unknown>,
181
+ signal?: AbortSignal,
182
+ affectedSetups?: string[],
204
183
  ) {
205
- const url = await promptWebDavUrl(
206
- ctx,
207
- signal,
208
- typeof profile.url === "string" ? profile.url : undefined,
209
- );
210
- if (!url) return false;
211
- const username = await promptUsername(ctx, signal);
212
- if (!username) return false;
213
- const hasPassword = typeof profile.password === "string" && profile.password.length > 0;
214
- const passwordAction = hasPassword
215
- ? await select(
216
- ctx,
217
- "WebDAV password",
218
- ["Keep current password", "Replace password", "Cancel"],
219
- signal,
220
- )
221
- : "Replace password";
222
- if (!passwordAction || passwordAction === "Cancel") return false;
223
- const replacePassword = passwordAction === "Replace password";
224
- const password = replacePassword
225
- ? await promptSecret(ctx, "New WebDAV password", { signal })
226
- : undefined;
227
- signal?.throwIfAborted();
228
- if (replacePassword && password === undefined) return false;
229
- const saved = await saveReviewedDraft(
230
- ctx,
231
- "Review storage connection",
232
- [
233
- `Storage connection: ${safe(name)}`,
234
- `URL: ${safe(String(profile.url))} → ${safe(url)}`,
235
- "Username: stored privately (value hidden)",
236
- `Password: ${replacePassword ? "will be replaced" : "unchanged"} (value hidden)`,
237
- `Affected sync setups: ${affectedSetups?.length ? affectedSetups.map(safe).join(", ") : "None"}`,
238
- "Saving changes future storage access for every affected setup; it does not move remote data.",
239
- ],
240
- "Save storage connection",
241
- (saveSignal) =>
242
- updateStorageConnection(
243
- name,
244
- (current) => {
245
- if (
246
- current.type !== "webdav" ||
247
- current.url !== profile.url ||
248
- current.credentials.username !== profile.username ||
249
- current.credentials.password !== profile.password
250
- )
251
- throw new Error("Storage connection changed while it was open; reopen it.");
252
- return {
253
- ...current,
254
- url,
255
- credentials: {
256
- ...current.credentials,
257
- username,
258
- password: password ?? current.credentials.password,
259
- },
260
- };
261
- },
262
- affectedSetups,
263
- saveSignal,
264
- ),
265
- signal,
266
- );
267
- if (saved && !signal?.aborted) ctx.ui.notify(`Saved storage connection “${safe(name)}”.`, "info");
268
- return saved;
184
+ const url = await promptWebDavUrl(ctx, signal, typeof profile.url === "string" ? profile.url : undefined);
185
+ if (!url) return false;
186
+ const username = await promptUsername(ctx, signal);
187
+ if (!username) return false;
188
+ const hasPassword = typeof profile.password === "string" && profile.password.length > 0;
189
+ const passwordAction = hasPassword
190
+ ? await select(ctx, "WebDAV password", ["Keep current password", "Replace password", "Cancel"], signal)
191
+ : "Replace password";
192
+ if (!passwordAction || passwordAction === "Cancel") return false;
193
+ const replacePassword = passwordAction === "Replace password";
194
+ const password = replacePassword ? await promptSecret(ctx, "New WebDAV password", { signal }) : undefined;
195
+ signal?.throwIfAborted();
196
+ if (replacePassword && password === undefined) return false;
197
+ const saved = await saveReviewedDraft(
198
+ ctx,
199
+ "Review storage connection",
200
+ [
201
+ `Storage connection: ${safe(name)}`,
202
+ `URL: ${safe(String(profile.url))} → ${safe(url)}`,
203
+ "Username: stored privately (value hidden)",
204
+ `Password: ${replacePassword ? "will be replaced" : "unchanged"} (value hidden)`,
205
+ `Affected sync setups: ${affectedSetups?.length ? affectedSetups.map(safe).join(", ") : "None"}`,
206
+ "Saving changes future storage access for every affected setup; it does not move remote data.",
207
+ ],
208
+ "Save storage connection",
209
+ (saveSignal) =>
210
+ updateStorageConnection(
211
+ name,
212
+ (current) => {
213
+ if (
214
+ current.type !== "webdav" ||
215
+ current.url !== profile.url ||
216
+ current.credentials.username !== profile.username ||
217
+ current.credentials.password !== profile.password
218
+ )
219
+ throw new Error("Storage connection changed while it was open; reopen it.");
220
+ return {
221
+ ...current,
222
+ url,
223
+ credentials: {
224
+ ...current.credentials,
225
+ username,
226
+ password: password ?? current.credentials.password,
227
+ },
228
+ };
229
+ },
230
+ affectedSetups,
231
+ saveSignal,
232
+ ),
233
+ signal,
234
+ );
235
+ if (saved && !signal?.aborted) ctx.ui.notify(`Saved storage connection “${safe(name)}”.`, "info");
236
+ return saved;
269
237
  }
270
238
 
271
- async function chooseDestination(
272
- ctx: ExtensionCommandContext,
273
- signal?: AbortSignal,
274
- current = "./",
275
- ) {
276
- return promptTextInput(
277
- ctx,
278
- "WebDAV storage path\n\nFolder relative to the collection URL, not your local filesystem.\n./ uses the collection root. Use different folders for independent setups.",
279
- { defaultValue: current, validate: normalizeWebDavPath },
280
- signal,
281
- );
239
+ async function chooseDestination(ctx: ExtensionCommandContext, signal?: AbortSignal, current = "./") {
240
+ return promptTextInput(
241
+ ctx,
242
+ "WebDAV storage path\n\nFolder relative to the collection URL, not your local filesystem.\n./ uses the collection root. Use different folders for independent setups.",
243
+ { defaultValue: current, validate: normalizeWebDavPath },
244
+ signal,
245
+ );
282
246
  }
283
247
 
284
248
  async function promptUsername(ctx: ExtensionCommandContext, signal?: AbortSignal) {
285
- return promptTextInput(
286
- ctx,
287
- "WebDAV username\n\nEnter the account username; stored values remain hidden.",
288
- {
289
- example: "user",
290
- validate: (value) => {
291
- validateWebDavCredentials(value);
292
- return value;
293
- },
294
- },
295
- signal,
296
- );
249
+ return promptTextInput(
250
+ ctx,
251
+ "WebDAV username\n\nEnter the account username; stored values remain hidden.",
252
+ {
253
+ example: "user",
254
+ validate: (value) => {
255
+ validateWebDavCredentials(value);
256
+ return value;
257
+ },
258
+ },
259
+ signal,
260
+ );
297
261
  }
298
262
 
299
- async function promptWebDavUrl(
300
- ctx: ExtensionCommandContext,
301
- signal?: AbortSignal,
302
- current?: string,
303
- ) {
304
- return promptTextInput(
305
- ctx,
306
- "WebDAV collection URL\n\nUse the HTTPS WebDAV URL from your provider, not its web login page.",
307
- {
308
- ...(current
309
- ? { defaultValue: current }
310
- : { example: "https://cloud.example.com/remote.php/dav/files/user" }),
311
- validate: (value) => {
312
- const url = normalizeWebDavUrl(value);
313
- if (!url) throw new Error("WebDAV URL is required.");
314
- return url;
315
- },
316
- },
317
- signal,
318
- );
263
+ async function promptWebDavUrl(ctx: ExtensionCommandContext, signal?: AbortSignal, current?: string) {
264
+ return promptTextInput(
265
+ ctx,
266
+ "WebDAV collection URL\n\nUse the HTTPS WebDAV URL from your provider, not its web login page.",
267
+ {
268
+ ...(current ? { defaultValue: current } : { example: "https://cloud.example.com/remote.php/dav/files/user" }),
269
+ validate: (value) => {
270
+ const url = normalizeWebDavUrl(value);
271
+ if (!url) throw new Error("WebDAV URL is required.");
272
+ return url;
273
+ },
274
+ },
275
+ signal,
276
+ );
319
277
  }
320
278
 
321
- async function select(
322
- ctx: ExtensionCommandContext,
323
- title: string,
324
- options: string[],
325
- signal?: AbortSignal,
326
- ) {
327
- const value = await ctx.ui.select(title, options, { signal });
328
- signal?.throwIfAborted();
329
- return value;
279
+ async function select(ctx: ExtensionCommandContext, title: string, options: string[], signal?: AbortSignal) {
280
+ const value = await ctx.ui.select(title, options, { signal });
281
+ signal?.throwIfAborted();
282
+ return value;
330
283
  }