@narumitw/pi-sync 0.50.3 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +4 -1
  2. package/dist/chunks/{chunk-6LP3MCR2.ts → chunk-2JZFHNLM.ts} +3 -5
  3. package/dist/chunks/chunk-2JZFHNLM.ts.map +7 -0
  4. package/dist/chunks/{chunk-LCRI3ABW.ts → chunk-2RZSQWBH.ts} +8 -19
  5. package/dist/chunks/chunk-2RZSQWBH.ts.map +7 -0
  6. package/dist/chunks/{chunk-IJ6FJFA5.ts → chunk-4AIHXRKE.ts} +11 -17
  7. package/dist/chunks/chunk-4AIHXRKE.ts.map +7 -0
  8. package/dist/chunks/{chunk-C7XY5NF5.ts → chunk-4MPIH46N.ts} +29 -51
  9. package/dist/chunks/chunk-4MPIH46N.ts.map +7 -0
  10. package/dist/chunks/{chunk-5B3FXBLE.ts → chunk-6546TS54.ts} +16 -20
  11. package/dist/chunks/chunk-6546TS54.ts.map +7 -0
  12. package/dist/chunks/{chunk-Q2TUM5X6.ts → chunk-6NT3YFGY.ts} +2 -2
  13. package/dist/chunks/chunk-6NT3YFGY.ts.map +7 -0
  14. package/dist/chunks/{chunk-2B2ELUF5.ts → chunk-7D5SF7P3.ts} +1 -1
  15. package/dist/chunks/chunk-7D5SF7P3.ts.map +7 -0
  16. package/dist/chunks/{chunk-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
@@ -3,82 +3,75 @@ import { promptSecret } from "../secret-input.js";
3
3
  import { requiredValueInput } from "./text-input.js";
4
4
 
5
5
  export interface ChosenS3Credentials {
6
- profileFields: { accessKeyId?: string; secretAccessKey?: string };
7
- summary: string;
8
- ready: boolean;
9
- replace?: boolean;
6
+ profileFields: { accessKeyId?: string; secretAccessKey?: string };
7
+ summary: string;
8
+ ready: boolean;
9
+ replace?: boolean;
10
10
  }
11
11
 
12
12
  export async function chooseS3CredentialUpdate(
13
- ctx: ExtensionCommandContext,
14
- profile: Record<string, unknown>,
15
- signal?: AbortSignal,
13
+ ctx: ExtensionCommandContext,
14
+ profile: Record<string, unknown>,
15
+ signal?: AbortSignal,
16
16
  ) {
17
- const hasStored =
18
- typeof profile.accessKeyId === "string" &&
19
- profile.accessKeyId.length > 0 &&
20
- typeof profile.secretAccessKey === "string" &&
21
- profile.secretAccessKey.length > 0;
22
- if (hasStored) {
23
- const action = await ctx.ui.select(
24
- "Credentials",
25
- ["Keep current credentials", "Replace credentials", "Cancel"],
26
- { signal },
27
- );
28
- throwIfAborted(signal);
29
- if (!action || action === "Cancel") return undefined;
30
- if (action === "Keep current credentials") {
31
- return { profileFields: {}, summary: "Unchanged (values hidden)", ready: true };
32
- }
33
- }
34
- const selected = await chooseS3Credentials(ctx, signal);
35
- return selected ? { ...selected, replace: true } : undefined;
17
+ const hasStored =
18
+ typeof profile.accessKeyId === "string" &&
19
+ profile.accessKeyId.length > 0 &&
20
+ typeof profile.secretAccessKey === "string" &&
21
+ profile.secretAccessKey.length > 0;
22
+ if (hasStored) {
23
+ const action = await ctx.ui.select("Credentials", ["Keep current credentials", "Replace credentials", "Cancel"], {
24
+ signal,
25
+ });
26
+ throwIfAborted(signal);
27
+ if (!action || action === "Cancel") return undefined;
28
+ if (action === "Keep current credentials") {
29
+ return { profileFields: {}, summary: "Unchanged (values hidden)", ready: true };
30
+ }
31
+ }
32
+ const selected = await chooseS3Credentials(ctx, signal);
33
+ return selected ? { ...selected, replace: true } : undefined;
36
34
  }
37
35
 
38
- export function applyS3CredentialUpdate(
39
- profile: Record<string, unknown>,
40
- credentials: ChosenS3Credentials,
41
- ) {
42
- const next = { ...profile };
43
- if (credentials.replace) {
44
- delete next.accessKeyId;
45
- delete next.secretAccessKey;
46
- delete next.sessionToken;
47
- }
48
- return { ...next, ...credentials.profileFields };
36
+ export function applyS3CredentialUpdate(profile: Record<string, unknown>, credentials: ChosenS3Credentials) {
37
+ const next = { ...profile };
38
+ if (credentials.replace) {
39
+ delete next.accessKeyId;
40
+ delete next.secretAccessKey;
41
+ delete next.sessionToken;
42
+ }
43
+ return { ...next, ...credentials.profileFields };
49
44
  }
50
45
 
51
46
  export async function chooseS3Credentials(
52
- ctx: ExtensionCommandContext,
53
- signal?: AbortSignal,
47
+ ctx: ExtensionCommandContext,
48
+ signal?: AbortSignal,
54
49
  ): Promise<ChosenS3Credentials | undefined> {
55
- const choice = await ctx.ui.select(
56
- "Credentials\n\nCredentials are stored in the private pi-sync settings file. Secret values are masked during input and never shown afterward.",
57
- ["Store credentials privately", "Cancel"],
58
- { signal },
59
- );
60
- throwIfAborted(signal);
61
- if (choice !== "Store credentials privately") return undefined;
62
- const accessKeyId = await requiredValueInput(
63
- ctx,
64
- "Access key ID\n\nUse the S3 API access key ID issued by your provider, not the secret access key.",
65
- "access-key-id",
66
- signal,
67
- );
68
- if (!accessKeyId) return undefined;
69
- const secretAccessKey = await promptSecret(ctx, "Secret access key", { signal });
70
- throwIfAborted(signal);
71
- if (secretAccessKey === undefined) return undefined;
72
- return {
73
- profileFields: { accessKeyId, secretAccessKey },
74
- summary: "Stored privately (values hidden)",
75
- ready: true,
76
- };
50
+ const choice = await ctx.ui.select(
51
+ "Credentials\n\nCredentials are stored in the private pi-sync settings file. Secret values are masked during input and never shown afterward.",
52
+ ["Store credentials privately", "Cancel"],
53
+ { signal },
54
+ );
55
+ throwIfAborted(signal);
56
+ if (choice !== "Store credentials privately") return undefined;
57
+ const accessKeyId = await requiredValueInput(
58
+ ctx,
59
+ "Access key ID\n\nUse the S3 API access key ID issued by your provider, not the secret access key.",
60
+ "access-key-id",
61
+ signal,
62
+ );
63
+ if (!accessKeyId) return undefined;
64
+ const secretAccessKey = await promptSecret(ctx, "Secret access key", { signal });
65
+ throwIfAborted(signal);
66
+ if (secretAccessKey === undefined) return undefined;
67
+ return {
68
+ profileFields: { accessKeyId, secretAccessKey },
69
+ summary: "Stored privately (values hidden)",
70
+ ready: true,
71
+ };
77
72
  }
78
73
 
79
74
  function throwIfAborted(signal?: AbortSignal) {
80
- if (!signal?.aborted) return;
81
- throw signal.reason instanceof Error
82
- ? signal.reason
83
- : new DOMException("The operation was aborted", "AbortError");
75
+ if (!signal?.aborted) return;
76
+ throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
84
77
  }
@@ -1,217 +1,193 @@
1
1
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
  import { refreshTargetCompletions } from "../../commands/setup-completions.js";
3
- import {
4
- addSyncSetup,
5
- saveNewV3Settings,
6
- updateSyncSetup,
7
- } from "../../settings/settings-management.js";
3
+ import { addSyncSetup, saveNewV3Settings, updateSyncSetup } from "../../settings/settings-management.js";
8
4
  import type { PartialConfig } from "../../settings/settings-types.js";
9
- import {
10
- normalizeS3Bucket,
11
- normalizeS3Endpoint,
12
- normalizeStoragePath,
13
- } from "../../settings/settings-validation.js";
5
+ import { normalizeS3Bucket, normalizeS3Endpoint, normalizeStoragePath } from "../../settings/settings-validation.js";
14
6
  import { automaticSyncSummary } from "../automatic-sync-summary.js";
15
7
  import { safeTerminalText } from "../terminal-text.js";
16
8
  import { chooseS3Credentials } from "./s3-credentials-ui.js";
17
9
  import {
18
- chooseAdditionalRemoteLocation,
19
- chooseInitialRemoteLocation,
20
- promptAvailableSetupStorage,
10
+ chooseAdditionalRemoteLocation,
11
+ chooseInitialRemoteLocation,
12
+ promptAvailableSetupStorage,
21
13
  } from "./setup-location-ui.js";
22
14
  import {
23
- chooseAutomaticSync,
24
- chooseSessions,
25
- chooseSetupContent,
26
- includedContentLines,
27
- readSetupConnection,
15
+ chooseAutomaticSync,
16
+ chooseSessions,
17
+ chooseSetupContent,
18
+ includedContentLines,
19
+ readSetupConnection,
28
20
  } from "./setup-prompts.js";
29
21
  import { saveReviewedDraft } from "./setup-review.js";
30
22
  import { requiredInput, requiredValueInput } from "./text-input.js";
31
23
 
32
24
  export async function showS3Setup(
33
- ctx: ExtensionCommandContext,
34
- preset: string,
35
- targetName: string,
36
- signal?: AbortSignal,
25
+ ctx: ExtensionCommandContext,
26
+ preset: string,
27
+ targetName: string,
28
+ signal?: AbortSignal,
37
29
  ) {
38
- const endpoint = await requiredValueInput(
39
- ctx,
40
- preset === "Cloudflare R2"
41
- ? "Cloudflare R2 endpoint\n\nCopy the S3 API endpoint from your R2 account; replace <account-id>."
42
- : "S3-compatible endpoint\n\nUse your provider's S3 API URL, not its web console.",
43
- preset === "Cloudflare R2"
44
- ? "https://<account-id>.r2.cloudflarestorage.com"
45
- : "https://s3.example.com",
46
- signal,
47
- normalizeS3Endpoint,
48
- );
49
- if (!endpoint) return false;
50
- const region =
51
- preset === "Cloudflare R2"
52
- ? "auto"
53
- : await requiredInput(
54
- ctx,
55
- "Storage region\n\nUse the region assigned to your bucket by the provider.",
56
- "us-east-1",
57
- signal,
58
- );
59
- if (!region) return false;
60
- const location = await chooseInitialRemoteLocation(ctx, preset, targetName, signal);
61
- if (!location) return false;
62
- const credentials = await chooseS3Credentials(ctx, signal);
63
- if (!credentials) return false;
64
- const include = await chooseSetupContent(ctx, signal);
65
- if (!include) return false;
66
- const automatic = await chooseAutomaticSync(ctx, signal);
67
- if (automatic === undefined) return false;
68
- const sessions = await chooseSessions(ctx, signal);
69
- if (sessions === undefined) return false;
70
- const saved = await saveReviewedDraft(
71
- ctx,
72
- "Review sync setup",
73
- [
74
- `Sync setup: ${safeTerminalText(targetName)}`,
75
- `Storage connection: ${safeTerminalText(location.connectionName)} (${preset})`,
76
- `Endpoint: ${safeTerminalText(endpoint)}`,
77
- `Region: ${safeTerminalText(region)}`,
78
- `Bucket: ${safeTerminalText(location.bucket)}`,
79
- `Storage location: ${safeTerminalText(location.path)}`,
80
- ...includedContentLines(include, sessions),
81
- `Automatic sync: ${automaticSyncSummary(automatic)}`,
82
- `Credentials: ${credentials.summary}`,
83
- "The bucket must already exist. Saving does not contact remote storage or start syncing.",
84
- ],
85
- "Save sync setup",
86
- (saveSignal) =>
87
- saveNewV3Settings(
88
- {
89
- setupName: targetName,
90
- connectionName: location.connectionName,
91
- connection: {
92
- type: "s3",
93
- endpoint,
94
- region,
95
- credentials: {
96
- accessKeyId: credentials.profileFields.accessKeyId ?? "",
97
- secretAccessKey: credentials.profileFields.secretAccessKey ?? "",
98
- },
99
- },
100
- setup: {
101
- storage: {
102
- connection: location.connectionName,
103
- bucket: location.bucket,
104
- path: location.path,
105
- },
106
- sync: { include: [...include, ...(sessions ? ["sessions"] : [])], automatic },
107
- },
108
- },
109
- saveSignal,
110
- ),
111
- signal,
112
- );
113
- if (!saved || signal?.aborted) return false;
114
- await refreshTargetCompletions();
115
- if (!signal?.aborted)
116
- ctx.ui.notify(
117
- `Sync setup “${safeTerminalText(targetName)}” saved. Choose Sync now to start.`,
118
- "info",
119
- );
120
- return true;
30
+ const endpoint = await requiredValueInput(
31
+ ctx,
32
+ preset === "Cloudflare R2"
33
+ ? "Cloudflare R2 endpoint\n\nCopy the S3 API endpoint from your R2 account; replace <account-id>."
34
+ : "S3-compatible endpoint\n\nUse your provider's S3 API URL, not its web console.",
35
+ preset === "Cloudflare R2" ? "https://<account-id>.r2.cloudflarestorage.com" : "https://s3.example.com",
36
+ signal,
37
+ normalizeS3Endpoint,
38
+ );
39
+ if (!endpoint) return false;
40
+ const region =
41
+ preset === "Cloudflare R2"
42
+ ? "auto"
43
+ : await requiredInput(
44
+ ctx,
45
+ "Storage region\n\nUse the region assigned to your bucket by the provider.",
46
+ "us-east-1",
47
+ signal,
48
+ );
49
+ if (!region) return false;
50
+ const location = await chooseInitialRemoteLocation(ctx, preset, targetName, signal);
51
+ if (!location) return false;
52
+ const credentials = await chooseS3Credentials(ctx, signal);
53
+ if (!credentials) return false;
54
+ const include = await chooseSetupContent(ctx, signal);
55
+ if (!include) return false;
56
+ const automatic = await chooseAutomaticSync(ctx, signal);
57
+ if (automatic === undefined) return false;
58
+ const sessions = await chooseSessions(ctx, signal);
59
+ if (sessions === undefined) return false;
60
+ const saved = await saveReviewedDraft(
61
+ ctx,
62
+ "Review sync setup",
63
+ [
64
+ `Sync setup: ${safeTerminalText(targetName)}`,
65
+ `Storage connection: ${safeTerminalText(location.connectionName)} (${preset})`,
66
+ `Endpoint: ${safeTerminalText(endpoint)}`,
67
+ `Region: ${safeTerminalText(region)}`,
68
+ `Bucket: ${safeTerminalText(location.bucket)}`,
69
+ `Storage location: ${safeTerminalText(location.path)}`,
70
+ ...includedContentLines(include, sessions),
71
+ `Automatic sync: ${automaticSyncSummary(automatic)}`,
72
+ `Credentials: ${credentials.summary}`,
73
+ "The bucket must already exist. Saving does not contact remote storage or start syncing.",
74
+ ],
75
+ "Save sync setup",
76
+ (saveSignal) =>
77
+ saveNewV3Settings(
78
+ {
79
+ setupName: targetName,
80
+ connectionName: location.connectionName,
81
+ connection: {
82
+ type: "s3",
83
+ endpoint,
84
+ region,
85
+ credentials: {
86
+ accessKeyId: credentials.profileFields.accessKeyId ?? "",
87
+ secretAccessKey: credentials.profileFields.secretAccessKey ?? "",
88
+ },
89
+ },
90
+ setup: {
91
+ storage: {
92
+ connection: location.connectionName,
93
+ bucket: location.bucket,
94
+ path: location.path,
95
+ },
96
+ sync: { include: [...include, ...(sessions ? ["sessions"] : [])], automatic },
97
+ },
98
+ },
99
+ saveSignal,
100
+ ),
101
+ signal,
102
+ );
103
+ if (!saved || signal?.aborted) return false;
104
+ await refreshTargetCompletions();
105
+ if (!signal?.aborted)
106
+ ctx.ui.notify(`Sync setup “${safeTerminalText(targetName)}” saved. Choose Sync now to start.`, "info");
107
+ return true;
121
108
  }
122
109
 
123
110
  export async function showAddS3Target(
124
- ctx: ExtensionCommandContext,
125
- raw: Record<string, unknown>,
126
- profile: string,
127
- name: string,
128
- signal?: AbortSignal,
111
+ ctx: ExtensionCommandContext,
112
+ raw: Record<string, unknown>,
113
+ profile: string,
114
+ name: string,
115
+ signal?: AbortSignal,
129
116
  ) {
130
- const location = await chooseAdditionalRemoteLocation(ctx, raw, profile, name, signal);
131
- if (!location) return;
132
- const storage = await promptAvailableSetupStorage(
133
- ctx,
134
- { connection: profile, ...location },
135
- signal,
136
- );
137
- if (!storage) return;
138
- const include = await chooseSetupContent(ctx, signal);
139
- if (!include) return;
140
- const automatic = await chooseAutomaticSync(ctx, signal);
141
- if (automatic === undefined) return;
142
- const connection = await readSetupConnection(profile, signal);
143
- if (connection.type !== "s3") throw new Error("Storage connection changed; reopen setup.");
144
- const saved = await saveReviewedDraft(
145
- ctx,
146
- "Review new sync setup",
147
- [
148
- `Sync setup: ${safeTerminalText(name)}`,
149
- `Storage connection: ${safeTerminalText(profile)}`,
150
- `Endpoint: ${safeTerminalText(connection.endpoint)}`,
151
- `Bucket: ${safeTerminalText(storage.bucket)}`,
152
- `Storage location: ${safeTerminalText(storage.path)}`,
153
- ...includedContentLines(include),
154
- `Automatic sync: ${automaticSyncSummary(automatic)}`,
155
- "Only the current setup is checked at startup. Shutdown can push selected content when sessions are included.",
156
- "The bucket must already exist. Adding this setup does not sync or modify remote data.",
157
- ],
158
- "Add sync setup",
159
- (saveSignal) =>
160
- addSyncSetup(name, { storage, sync: { include, automatic } }, saveSignal, connection),
161
- signal,
162
- );
163
- if (!saved || signal?.aborted) return;
164
- await refreshTargetCompletions();
165
- if (!signal?.aborted) ctx.ui.notify(`Added sync setup “${safeTerminalText(name)}”.`, "info");
117
+ const location = await chooseAdditionalRemoteLocation(ctx, raw, profile, name, signal);
118
+ if (!location) return;
119
+ const storage = await promptAvailableSetupStorage(ctx, { connection: profile, ...location }, signal);
120
+ if (!storage) return;
121
+ const include = await chooseSetupContent(ctx, signal);
122
+ if (!include) return;
123
+ const automatic = await chooseAutomaticSync(ctx, signal);
124
+ if (automatic === undefined) return;
125
+ const connection = await readSetupConnection(profile, signal);
126
+ if (connection.type !== "s3") throw new Error("Storage connection changed; reopen setup.");
127
+ const saved = await saveReviewedDraft(
128
+ ctx,
129
+ "Review new sync setup",
130
+ [
131
+ `Sync setup: ${safeTerminalText(name)}`,
132
+ `Storage connection: ${safeTerminalText(profile)}`,
133
+ `Endpoint: ${safeTerminalText(connection.endpoint)}`,
134
+ `Bucket: ${safeTerminalText(storage.bucket)}`,
135
+ `Storage location: ${safeTerminalText(storage.path)}`,
136
+ ...includedContentLines(include),
137
+ `Automatic sync: ${automaticSyncSummary(automatic)}`,
138
+ "Only the current setup is checked at startup. Shutdown can push selected content when sessions are included.",
139
+ "The bucket must already exist. Adding this setup does not sync or modify remote data.",
140
+ ],
141
+ "Add sync setup",
142
+ (saveSignal) => addSyncSetup(name, { storage, sync: { include, automatic } }, saveSignal, connection),
143
+ signal,
144
+ );
145
+ if (!saved || signal?.aborted) return;
146
+ await refreshTargetCompletions();
147
+ if (!signal?.aborted) ctx.ui.notify(`Added sync setup “${safeTerminalText(name)}”.`, "info");
166
148
  }
167
149
 
168
- export async function showEditS3Target(
169
- ctx: ExtensionCommandContext,
170
- partial: PartialConfig,
171
- signal?: AbortSignal,
172
- ) {
173
- const bucket = await requiredInput(
174
- ctx,
175
- "Bucket\n\nUse an existing bucket; pi-sync will not create it.",
176
- partial.bucket ?? "pi-sync",
177
- signal,
178
- normalizeS3Bucket,
179
- );
180
- if (!bucket) return;
181
- const storagePath = await requiredInput(
182
- ctx,
183
- "Storage path\n\nPath inside the bucket, not your local filesystem. ./ uses the bucket root.",
184
- partial.storagePath,
185
- signal,
186
- normalizeStoragePath,
187
- );
188
- if (!storagePath) return;
189
- const connection = await readSetupConnection(partial.connectionName, signal);
190
- if (connection.type !== "s3") throw new Error("Storage connection changed; reopen setup.");
191
- const saved = await saveReviewedDraft(
192
- ctx,
193
- "Review sync setup",
194
- [
195
- `Sync setup: ${safeTerminalText(partial.setupName)}`,
196
- `Storage connection: ${safeTerminalText(partial.connectionName)}`,
197
- `Endpoint: ${safeTerminalText(connection.endpoint)}`,
198
- `Bucket: ${safeTerminalText(partial.bucket ?? "missing")} → ${safeTerminalText(bucket)}`,
199
- `Storage path: ${safeTerminalText(partial.storagePath)} → ${safeTerminalText(storagePath)}`,
200
- "Saving changes the future storage location only; it does not move or delete remote data.",
201
- ],
202
- "Save sync setup",
203
- (saveSignal) =>
204
- updateSyncSetup(
205
- partial.setupName,
206
- (setup) => {
207
- if (typeof setup.storage.bucket !== "string")
208
- throw new Error("Sync setup storage type changed; reopen it.");
209
- return { ...setup, storage: { ...setup.storage, bucket, path: storagePath } };
210
- },
211
- { expectedStorage: partial, expectedConnection: connection, signal: saveSignal },
212
- ),
213
- signal,
214
- );
215
- if (saved && !signal?.aborted)
216
- ctx.ui.notify(`Saved sync setup “${safeTerminalText(partial.setupName)}”.`, "info");
150
+ export async function showEditS3Target(ctx: ExtensionCommandContext, partial: PartialConfig, signal?: AbortSignal) {
151
+ const bucket = await requiredInput(
152
+ ctx,
153
+ "Bucket\n\nUse an existing bucket; pi-sync will not create it.",
154
+ partial.bucket ?? "pi-sync",
155
+ signal,
156
+ normalizeS3Bucket,
157
+ );
158
+ if (!bucket) return;
159
+ const storagePath = await requiredInput(
160
+ ctx,
161
+ "Storage path\n\nPath inside the bucket, not your local filesystem. ./ uses the bucket root.",
162
+ partial.storagePath,
163
+ signal,
164
+ normalizeStoragePath,
165
+ );
166
+ if (!storagePath) return;
167
+ const connection = await readSetupConnection(partial.connectionName, signal);
168
+ if (connection.type !== "s3") throw new Error("Storage connection changed; reopen setup.");
169
+ const saved = await saveReviewedDraft(
170
+ ctx,
171
+ "Review sync setup",
172
+ [
173
+ `Sync setup: ${safeTerminalText(partial.setupName)}`,
174
+ `Storage connection: ${safeTerminalText(partial.connectionName)}`,
175
+ `Endpoint: ${safeTerminalText(connection.endpoint)}`,
176
+ `Bucket: ${safeTerminalText(partial.bucket ?? "missing")} → ${safeTerminalText(bucket)}`,
177
+ `Storage path: ${safeTerminalText(partial.storagePath)} → ${safeTerminalText(storagePath)}`,
178
+ "Saving changes the future storage location only; it does not move or delete remote data.",
179
+ ],
180
+ "Save sync setup",
181
+ (saveSignal) =>
182
+ updateSyncSetup(
183
+ partial.setupName,
184
+ (setup) => {
185
+ if (typeof setup.storage.bucket !== "string") throw new Error("Sync setup storage type changed; reopen it.");
186
+ return { ...setup, storage: { ...setup.storage, bucket, path: storagePath } };
187
+ },
188
+ { expectedStorage: partial, expectedConnection: connection, signal: saveSignal },
189
+ ),
190
+ signal,
191
+ );
192
+ if (saved && !signal?.aborted) ctx.ui.notify(`Saved sync setup “${safeTerminalText(partial.setupName)}”.`, "info");
217
193
  }