@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
@@ -5,189 +5,192 @@ import { localConfigPath } from "../settings/config-file.js";
5
5
  import { updateSyncSetup } from "../settings/settings-management.js";
6
6
  import { updateLocalConfig } from "../settings/settings-store.js";
7
7
  import {
8
- SETUP_SWITCH_ACTION_OPTIONS,
9
- saveOnSwitch,
10
- setupSwitchActionFromLabel,
11
- setupSwitchActionLabel,
8
+ SETUP_SWITCH_ACTION_OPTIONS,
9
+ saveOnSwitch,
10
+ setupSwitchActionFromLabel,
11
+ setupSwitchActionLabel,
12
12
  } from "../sync/setup-switch.js";
13
13
  import type { RunRoute } from "./cancellable-operation.js";
14
14
  import { dispatchManagerResult } from "./manager-result-dispatcher.js";
15
15
  import { AUTOMATIC_SYNC_DESCRIPTION } from "./setup/setup-prompts.js";
16
+ import { configureSyncStatus } from "./sync-status.js";
16
17
  import { safeTerminalText } from "./terminal-text.js";
17
18
 
18
19
  export type SyncSettingsRoute = RunRoute;
19
20
 
20
21
  export async function showSyncSettings(
21
- ctx: ExtensionCommandContext,
22
- runRoute: SyncSettingsRoute,
23
- signal?: AbortSignal,
22
+ ctx: ExtensionCommandContext,
23
+ runRoute: SyncSettingsRoute,
24
+ signal?: AbortSignal,
24
25
  ) {
25
- if (ctx.mode !== "tui") {
26
- ctx.ui.notify(`Edit pi-sync settings manually: ${safeTerminalText(localConfigPath())}`, "info");
27
- return;
28
- }
29
- const initial = await loadConfig();
30
- if (signal?.aborted) return;
31
- const setupName = initial.setupName;
32
- type Action = "automatic" | "skip-secret-scan" | "on-switch" | "include" | "remote-include";
33
- const menu = defineMenu<
34
- Awaited<ReturnType<typeof loadConfig>>,
35
- "settings",
36
- Action,
37
- ExtensionCommandContext
38
- >({
39
- start: "settings",
40
- screens: {
41
- settings: ({ state }) => ({
42
- kind: "settings",
43
- title: "Pi Sync Settings",
44
- lines: [
45
- `Sync setup: ${safeTerminalText(state.setupName)} · Storage connection: ${safeTerminalText(state.connectionName)}`,
46
- ],
47
- items: [
48
- {
49
- id: "automatic",
50
- label: "Automatic sync",
51
- description: AUTOMATIC_SYNC_DESCRIPTION,
52
- currentValue: state.automatic ? "On" : "Off",
53
- values: ["On", "Off"],
54
- action: "automatic",
55
- },
56
- {
57
- id: "skipSecretScan",
58
- label: "Skip secret scan (all setups)",
59
- description:
60
- "All setups: allow pushes without checking managed local files for possible secrets.",
61
- currentValue: state.skipSecretScan ? "On" : "Off",
62
- values: ["On", "Off"],
63
- action: "skip-secret-scan",
64
- },
65
- {
66
- id: "onSwitch",
67
- label: "After switching setup (all setups)",
68
- description:
69
- "All setups: ask before a pull review, open it automatically, or switch without pulling.",
70
- currentValue: setupSwitchActionLabel(state.onSwitch),
71
- values: SETUP_SWITCH_ACTION_OPTIONS.map(({ label }) => label),
72
- action: "on-switch",
73
- },
74
- {
75
- id: "include",
76
- label: "Included content",
77
- description: `${state.include.length} selected path${state.include.length === 1 ? "" : "s"}. Choose which paths this setup syncs.`,
78
- currentValue: "Open editor",
79
- action: "include",
80
- },
81
- {
82
- id: "remoteInclude",
83
- label: "Compare synced content",
84
- description: "Review this device and remote content lists before choosing either one.",
85
- currentValue: "Review",
86
- action: "remote-include",
87
- },
88
- ],
89
- }),
90
- },
91
- actions: {
92
- automatic: async ({ value, signal: actionSignal }) => {
93
- const automatic = value === "On";
94
- const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
95
- try {
96
- const latest = await loadConfig(setupName);
97
- if (mutationSignal.aborted) return { kind: "rejected" };
98
- if (latest.automatic === automatic) return { kind: "stay" };
99
- await updateSyncSetup(
100
- setupName,
101
- (setup) => ({ ...setup, sync: { ...setup.sync, automatic } }),
102
- { signal: mutationSignal },
103
- );
104
- if (mutationSignal.aborted) return { kind: "rejected" };
105
- ctx.ui.notify(
106
- `Automatic sync ${automatic ? "enabled" : "disabled"} for “${safeTerminalText(setupName)}”.`,
107
- "info",
108
- );
109
- return { kind: "stay" };
110
- } catch (error) {
111
- if (!mutationSignal.aborted) notifySaveFailure(ctx, error);
112
- return { kind: "rejected" };
113
- }
114
- },
115
- "skip-secret-scan": async ({ value, signal: actionSignal }) => {
116
- const skipSecretScan = value === "On";
117
- const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
118
- try {
119
- const latest = await loadConfig(setupName);
120
- if (mutationSignal.aborted) return { kind: "rejected" };
121
- if (latest.skipSecretScan === skipSecretScan) return { kind: "stay" };
122
- await updateLocalConfig((settings) => ({ ...settings, skipSecretScan }), mutationSignal);
123
- if (mutationSignal.aborted) return { kind: "rejected" };
124
- ctx.ui.notify(
125
- `Secret scan ${skipSecretScan ? "disabled" : "enabled"} for pushes in all setups.`,
126
- "info",
127
- );
128
- return { kind: "stay" };
129
- } catch (error) {
130
- if (!mutationSignal.aborted) notifySaveFailure(ctx, error);
131
- return { kind: "rejected" };
132
- }
133
- },
134
- "on-switch": async ({ value, signal: actionSignal }) => {
135
- const action = value ? setupSwitchActionFromLabel(value) : undefined;
136
- if (!action) return { kind: "rejected" };
137
- const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
138
- try {
139
- const latest = await loadConfig(setupName);
140
- if (mutationSignal.aborted) return { kind: "rejected" };
141
- if (latest.onSwitch === action) return { kind: "stay" };
142
- await saveOnSwitch(action, mutationSignal);
143
- if (mutationSignal.aborted) return { kind: "rejected" };
144
- ctx.ui.notify(`After switching setup: ${value}.`, "info");
145
- return { kind: "stay" };
146
- } catch (error) {
147
- if (!mutationSignal.aborted) notifySaveFailure(ctx, error);
148
- return { kind: "rejected" };
149
- }
150
- },
151
- include: async ({ signal: actionSignal }) => {
152
- const editorSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
153
- await runRoute("files", editorSignal, undefined, setupName);
154
- return editorSignal.aborted ? { kind: "rejected" } : { kind: "stay" };
155
- },
156
- "remote-include": async ({ signal: actionSignal }) => {
157
- const reviewSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
158
- const { showRemoteSelectionReview } = await import("./remote-selection-ui.js");
159
- if (reviewSignal.aborted) return { kind: "rejected" };
160
- const review = await showRemoteSelectionReview(ctx, setupName, reviewSignal, undefined, {
161
- origin: "settings",
162
- runRoute,
163
- });
164
- if (reviewSignal.aborted) return { kind: "rejected" };
165
- if (review.kind === "route-result") {
166
- const disposition = await dispatchManagerResult(
167
- ctx,
168
- review.result,
169
- review.route,
170
- runRoute,
171
- reviewSignal,
172
- );
173
- return disposition.kind === "close" ? { kind: "close" } : { kind: "stay" };
174
- }
175
- return review.kind === "closed" || review.kind === "stale"
176
- ? { kind: "close" }
177
- : { kind: "stay" };
178
- },
179
- },
180
- });
181
- await runMenu(ctx, menu, {
182
- getState: () => loadConfig(setupName),
183
- signal,
184
- isCurrent: () => !signal?.aborted,
185
- });
26
+ if (ctx.mode !== "tui") {
27
+ ctx.ui.notify(`Edit pi-sync settings manually: ${safeTerminalText(localConfigPath())}`, "info");
28
+ return;
29
+ }
30
+ const initial = await loadConfig();
31
+ if (signal?.aborted) return;
32
+ const setupName = initial.setupName;
33
+ type Action = "automatic" | "skip-secret-scan" | "show-status" | "on-switch" | "include" | "remote-include";
34
+ const menu = defineMenu<Awaited<ReturnType<typeof loadConfig>>, "settings", Action, ExtensionCommandContext>({
35
+ start: "settings",
36
+ screens: {
37
+ settings: ({ state }) => ({
38
+ kind: "settings",
39
+ title: "Pi Sync Settings",
40
+ lines: [
41
+ `Sync setup: ${safeTerminalText(state.setupName)} · Storage connection: ${safeTerminalText(state.connectionName)}`,
42
+ ],
43
+ items: [
44
+ {
45
+ id: "automatic",
46
+ label: "Automatic sync",
47
+ description: AUTOMATIC_SYNC_DESCRIPTION,
48
+ currentValue: state.automatic ? "On" : "Off",
49
+ values: ["On", "Off"],
50
+ action: "automatic",
51
+ },
52
+ {
53
+ id: "skipSecretScan",
54
+ label: "Skip secret scan (all setups)",
55
+ description: "All setups: allow pushes without checking managed local files for possible secrets.",
56
+ currentValue: state.skipSecretScan ? "On" : "Off",
57
+ values: ["On", "Off"],
58
+ action: "skip-secret-scan",
59
+ },
60
+ {
61
+ id: "showStatus",
62
+ label: "Show status (all setups)",
63
+ description: "All setups: show sync progress and attention in Pi status.",
64
+ currentValue: state.showStatus ? "On" : "Off",
65
+ values: ["On", "Off"],
66
+ action: "show-status",
67
+ },
68
+ {
69
+ id: "onSwitch",
70
+ label: "After switching setup (all setups)",
71
+ description: "All setups: ask before a pull review, open it automatically, or switch without pulling.",
72
+ currentValue: setupSwitchActionLabel(state.onSwitch),
73
+ values: SETUP_SWITCH_ACTION_OPTIONS.map(({ label }) => label),
74
+ action: "on-switch",
75
+ },
76
+ {
77
+ id: "include",
78
+ label: "Included content",
79
+ description: `${state.include.length} selected path${state.include.length === 1 ? "" : "s"}. Choose which paths this setup syncs.`,
80
+ currentValue: "Open editor",
81
+ action: "include",
82
+ },
83
+ {
84
+ id: "remoteInclude",
85
+ label: "Compare synced content",
86
+ description: "Review this device and remote content lists before choosing either one.",
87
+ currentValue: "Review",
88
+ action: "remote-include",
89
+ },
90
+ ],
91
+ }),
92
+ },
93
+ actions: {
94
+ automatic: async ({ value, signal: actionSignal }) => {
95
+ const automatic = value === "On";
96
+ const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
97
+ try {
98
+ const latest = await loadConfig(setupName);
99
+ if (mutationSignal.aborted) return { kind: "rejected" };
100
+ if (latest.automatic === automatic) return { kind: "stay" };
101
+ await updateSyncSetup(setupName, (setup) => ({ ...setup, sync: { ...setup.sync, automatic } }), {
102
+ signal: mutationSignal,
103
+ });
104
+ if (mutationSignal.aborted) return { kind: "rejected" };
105
+ ctx.ui.notify(
106
+ `Automatic sync ${automatic ? "enabled" : "disabled"} for “${safeTerminalText(setupName)}”.`,
107
+ "info",
108
+ );
109
+ return { kind: "stay" };
110
+ } catch (error) {
111
+ if (!mutationSignal.aborted) notifySaveFailure(ctx, error);
112
+ return { kind: "rejected" };
113
+ }
114
+ },
115
+ "skip-secret-scan": async ({ value, signal: actionSignal }) => {
116
+ const skipSecretScan = value === "On";
117
+ const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
118
+ try {
119
+ const latest = await loadConfig(setupName);
120
+ if (mutationSignal.aborted) return { kind: "rejected" };
121
+ if (latest.skipSecretScan === skipSecretScan) return { kind: "stay" };
122
+ await updateLocalConfig((settings) => ({ ...settings, skipSecretScan }), mutationSignal);
123
+ if (mutationSignal.aborted) return { kind: "rejected" };
124
+ ctx.ui.notify(`Secret scan ${skipSecretScan ? "disabled" : "enabled"} for pushes in all setups.`, "info");
125
+ return { kind: "stay" };
126
+ } catch (error) {
127
+ if (!mutationSignal.aborted) notifySaveFailure(ctx, error);
128
+ return { kind: "rejected" };
129
+ }
130
+ },
131
+ "show-status": async ({ value, signal: actionSignal }) => {
132
+ const showStatus = value === "On";
133
+ const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
134
+ try {
135
+ const latest = await loadConfig(setupName);
136
+ if (mutationSignal.aborted) return { kind: "rejected" };
137
+ if (latest.showStatus === showStatus) return { kind: "stay" };
138
+ await updateLocalConfig((settings) => ({ ...settings, showStatus }), mutationSignal);
139
+ if (mutationSignal.aborted) return { kind: "rejected" };
140
+ configureSyncStatus(ctx, showStatus);
141
+ ctx.ui.notify(`Pi Sync status ${showStatus ? "enabled" : "disabled"} for all setups.`, "info");
142
+ return { kind: "stay" };
143
+ } catch (error) {
144
+ if (!mutationSignal.aborted) notifySaveFailure(ctx, error);
145
+ return { kind: "rejected" };
146
+ }
147
+ },
148
+ "on-switch": async ({ value, signal: actionSignal }) => {
149
+ const action = value ? setupSwitchActionFromLabel(value) : undefined;
150
+ if (!action) return { kind: "rejected" };
151
+ const mutationSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
152
+ try {
153
+ const latest = await loadConfig(setupName);
154
+ if (mutationSignal.aborted) return { kind: "rejected" };
155
+ if (latest.onSwitch === action) return { kind: "stay" };
156
+ await saveOnSwitch(action, mutationSignal);
157
+ if (mutationSignal.aborted) return { kind: "rejected" };
158
+ ctx.ui.notify(`After switching setup: ${value}.`, "info");
159
+ return { kind: "stay" };
160
+ } catch (error) {
161
+ if (!mutationSignal.aborted) notifySaveFailure(ctx, error);
162
+ return { kind: "rejected" };
163
+ }
164
+ },
165
+ include: async ({ signal: actionSignal }) => {
166
+ const editorSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
167
+ await runRoute("files", editorSignal, undefined, setupName);
168
+ return editorSignal.aborted ? { kind: "rejected" } : { kind: "stay" };
169
+ },
170
+ "remote-include": async ({ signal: actionSignal }) => {
171
+ const reviewSignal = signal ? AbortSignal.any([signal, actionSignal]) : actionSignal;
172
+ const { showRemoteSelectionReview } = await import("./remote-selection-ui.js");
173
+ if (reviewSignal.aborted) return { kind: "rejected" };
174
+ const review = await showRemoteSelectionReview(ctx, setupName, reviewSignal, undefined, {
175
+ origin: "settings",
176
+ runRoute,
177
+ });
178
+ if (reviewSignal.aborted) return { kind: "rejected" };
179
+ if (review.kind === "route-result") {
180
+ const disposition = await dispatchManagerResult(ctx, review.result, review.route, runRoute, reviewSignal);
181
+ return disposition.kind === "close" ? { kind: "close" } : { kind: "stay" };
182
+ }
183
+ return review.kind === "closed" || review.kind === "stale" ? { kind: "close" } : { kind: "stay" };
184
+ },
185
+ },
186
+ });
187
+ await runMenu(ctx, menu, {
188
+ getState: () => loadConfig(setupName),
189
+ signal,
190
+ isCurrent: () => !signal?.aborted,
191
+ });
186
192
  }
187
193
 
188
194
  function notifySaveFailure(ctx: ExtensionCommandContext, error: unknown) {
189
- ctx.ui.notify(
190
- `Pi Sync settings save failed: ${error instanceof Error ? error.message : String(error)}`,
191
- "error",
192
- );
195
+ ctx.ui.notify(`Pi Sync settings save failed: ${error instanceof Error ? error.message : String(error)}`, "error");
193
196
  }