@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
@@ -2,240 +2,234 @@ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { CommandArgumentCompletion, CommandOptions } from "./command-types.js";
3
3
 
4
4
  const YES_FLAG_COMPLETIONS: readonly CommandArgumentCompletion[] = [
5
- { value: "--yes", label: "--yes", description: "Skip confirmation prompts" },
6
- { value: "-y", label: "-y", description: "Skip confirmation prompts" },
5
+ { value: "--yes", label: "--yes", description: "Skip confirmation prompts" },
6
+ { value: "-y", label: "-y", description: "Skip confirmation prompts" },
7
7
  ];
8
8
  export const SYNC_COMMANDS = [
9
- { name: "help", description: "Show command usage" },
10
- { name: "use", description: "Switch the current sync setup", usageSuffix: " <setup>" },
11
- { name: "init", description: "Create local config template" },
12
- { name: "config", description: "Show resolved configuration" },
13
- { name: "files", description: "Choose included content" },
14
- { name: "status", description: "Show sync status" },
15
- { name: "diff", description: "Show local/remote diff" },
16
- { name: "doctor", description: "Check config, secrets, and lock state" },
17
- { name: "push", description: "Upload local settings" },
18
- { name: "pull", description: "Apply remote settings" },
19
- { name: "sync", description: "Push or pull as needed" },
20
- { name: "history", description: "Show recent remote snapshots" },
21
- { name: "rollback", description: "Apply a previous snapshot", usageSuffix: " <snapshot>" },
22
- { name: "migrate-state", description: "Move legacy state into pi-sync/" },
23
- { name: "unlock", description: "Remove a stale local lock", usageSuffix: " --stale" },
9
+ { name: "help", description: "Show command usage" },
10
+ { name: "use", description: "Switch the current sync setup", usageSuffix: " <setup>" },
11
+ { name: "init", description: "Create local config template" },
12
+ { name: "config", description: "Show resolved configuration" },
13
+ { name: "files", description: "Choose included content" },
14
+ { name: "status", description: "Show sync status" },
15
+ { name: "diff", description: "Show local/remote diff" },
16
+ { name: "doctor", description: "Check config, secrets, and lock state" },
17
+ { name: "push", description: "Upload local settings" },
18
+ { name: "pull", description: "Apply remote settings" },
19
+ { name: "sync", description: "Push or pull as needed" },
20
+ { name: "history", description: "Show recent remote snapshots" },
21
+ { name: "rollback", description: "Apply a previous snapshot", usageSuffix: " <snapshot>" },
22
+ { name: "migrate-state", description: "Move legacy state into pi-sync/" },
23
+ { name: "unlock", description: "Remove a stale local lock", usageSuffix: " --stale" },
24
24
  ] as const;
25
25
 
26
26
  export type SyncCommandName = (typeof SYNC_COMMANDS)[number]["name"];
27
27
 
28
- const SYNC_COMMAND_COMPLETIONS: readonly CommandArgumentCompletion[] = SYNC_COMMANDS.map(
29
- ({ name, description }) => ({ value: name, label: name, description }),
30
- );
28
+ const SYNC_COMMAND_COMPLETIONS: readonly CommandArgumentCompletion[] = SYNC_COMMANDS.map(({ name, description }) => ({
29
+ value: name,
30
+ label: name,
31
+ description,
32
+ }));
31
33
  let setupCompletionNames: string[] = [];
32
34
 
33
35
  export function setSyncSetupCompletions(names: readonly string[]) {
34
- setupCompletionNames = [...new Set(names)].sort((left, right) => left.localeCompare(right));
36
+ setupCompletionNames = [...new Set(names)].sort((left, right) => left.localeCompare(right));
35
37
  }
36
38
  const SETUP_FLAG_COMPLETION = {
37
- value: "--setup",
38
- label: "--setup",
39
- description: "Address a sync setup without switching",
39
+ value: "--setup",
40
+ label: "--setup",
41
+ description: "Address a sync setup without switching",
40
42
  } as const;
41
43
  const SYNC_FLAG_COMPLETIONS: Record<string, readonly CommandArgumentCompletion[]> = {
42
- config: [SETUP_FLAG_COMPLETION],
43
- files: [SETUP_FLAG_COMPLETION],
44
- status: [SETUP_FLAG_COMPLETION],
45
- diff: [SETUP_FLAG_COMPLETION],
46
- doctor: [SETUP_FLAG_COMPLETION],
47
- push: [
48
- ...YES_FLAG_COMPLETIONS,
49
- { value: "--force", label: "--force", description: "Overwrite visible remote changes" },
50
- SETUP_FLAG_COMPLETION,
51
- ],
52
- pull: [
53
- ...YES_FLAG_COMPLETIONS,
54
- { value: "--force", label: "--force", description: "Overwrite local changes" },
55
- SETUP_FLAG_COMPLETION,
56
- ],
57
- sync: [
58
- ...YES_FLAG_COMPLETIONS,
59
- { value: "--force", label: "--force", description: "Resolve conflicts by forcing action" },
60
- SETUP_FLAG_COMPLETION,
61
- ],
62
- history: [SETUP_FLAG_COMPLETION],
63
- rollback: [...YES_FLAG_COMPLETIONS, SETUP_FLAG_COMPLETION],
64
- "migrate-state": YES_FLAG_COMPLETIONS,
65
- unlock: [{ value: "--stale", label: "--stale", description: "Remove only a stale lock" }],
44
+ config: [SETUP_FLAG_COMPLETION],
45
+ files: [SETUP_FLAG_COMPLETION],
46
+ status: [SETUP_FLAG_COMPLETION],
47
+ diff: [SETUP_FLAG_COMPLETION],
48
+ doctor: [SETUP_FLAG_COMPLETION],
49
+ push: [
50
+ ...YES_FLAG_COMPLETIONS,
51
+ { value: "--force", label: "--force", description: "Overwrite visible remote changes" },
52
+ SETUP_FLAG_COMPLETION,
53
+ ],
54
+ pull: [
55
+ ...YES_FLAG_COMPLETIONS,
56
+ { value: "--force", label: "--force", description: "Overwrite local changes" },
57
+ SETUP_FLAG_COMPLETION,
58
+ ],
59
+ sync: [
60
+ ...YES_FLAG_COMPLETIONS,
61
+ { value: "--force", label: "--force", description: "Resolve conflicts by forcing action" },
62
+ SETUP_FLAG_COMPLETION,
63
+ ],
64
+ history: [SETUP_FLAG_COMPLETION],
65
+ rollback: [...YES_FLAG_COMPLETIONS, SETUP_FLAG_COMPLETION],
66
+ "migrate-state": YES_FLAG_COMPLETIONS,
67
+ unlock: [{ value: "--stale", label: "--stale", description: "Remove only a stale lock" }],
66
68
  };
67
69
 
68
70
  export function splitArgs(input: string) {
69
- return (
70
- input.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((arg) => arg.replace(/^['"]|['"]$/g, "")) ??
71
- []
72
- );
71
+ return input.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((arg) => arg.replace(/^['"]|['"]$/g, "")) ?? [];
73
72
  }
74
73
 
75
74
  export function parseOptions(args: string[]): CommandOptions {
76
- const values: string[] = [];
77
- let yes = false;
78
- let force = false;
79
- let stale = false;
80
- let setup: string | undefined;
81
- for (let index = 0; index < args.length; index += 1) {
82
- const arg = args[index];
83
- if (arg === "--yes" || arg === "-y") yes = true;
84
- else if (arg === "--force") force = true;
85
- else if (arg === "--stale") stale = true;
86
- else if (arg === "--setup") {
87
- const name = args[index + 1];
88
- if (!name || name.startsWith("-")) throw new Error("--setup requires a sync setup name.");
89
- if (setup !== undefined) throw new Error("--setup may be provided only once.");
90
- setup = name;
91
- index += 1;
92
- } else if (arg.startsWith("-")) {
93
- throw new Error(`Unknown sync option: ${arg}`);
94
- } else values.push(arg);
95
- }
96
- return {
97
- yes,
98
- force,
99
- stale,
100
- silent: false,
101
- reload: true,
102
- auto: false,
103
- ...(setup === undefined ? {} : { setup }),
104
- args: values,
105
- };
75
+ const values: string[] = [];
76
+ let yes = false;
77
+ let force = false;
78
+ let stale = false;
79
+ let setup: string | undefined;
80
+ for (let index = 0; index < args.length; index += 1) {
81
+ const arg = args[index];
82
+ if (arg === "--yes" || arg === "-y") yes = true;
83
+ else if (arg === "--force") force = true;
84
+ else if (arg === "--stale") stale = true;
85
+ else if (arg === "--setup") {
86
+ const name = args[index + 1];
87
+ if (!name || name.startsWith("-")) throw new Error("--setup requires a sync setup name.");
88
+ if (setup !== undefined) throw new Error("--setup may be provided only once.");
89
+ setup = name;
90
+ index += 1;
91
+ } else if (arg.startsWith("-")) {
92
+ throw new Error(`Unknown sync option: ${arg}`);
93
+ } else values.push(arg);
94
+ }
95
+ return {
96
+ yes,
97
+ force,
98
+ stale,
99
+ silent: false,
100
+ reload: true,
101
+ auto: false,
102
+ ...(setup === undefined ? {} : { setup }),
103
+ args: values,
104
+ };
106
105
  }
107
106
 
108
107
  export function validateCommandOptions(command: string, options: CommandOptions) {
109
- const setupAllowed = new Set([
110
- "config",
111
- "files",
112
- "status",
113
- "diff",
114
- "doctor",
115
- "push",
116
- "pull",
117
- "sync",
118
- "history",
119
- "rollback",
120
- ]);
121
- if (options.setup && !setupAllowed.has(command)) {
122
- throw new Error(`--setup is not supported by /sync ${command}.`);
123
- }
124
- if (options.yes && !["push", "pull", "sync", "rollback", "migrate-state"].includes(command)) {
125
- throw new Error(`Confirmation/force options are not supported by /sync ${command}.`);
126
- }
127
- if (options.force && !["push", "pull", "sync", "rollback"].includes(command)) {
128
- throw new Error(`Confirmation/force options are not supported by /sync ${command}.`);
129
- }
130
- if (options.stale && command !== "unlock") {
131
- throw new Error(`--stale is not supported by /sync ${command}.`);
132
- }
133
- const expectedValues = command === "rollback" || command === "use" ? 1 : 0;
134
- if (options.args.length !== expectedValues) {
135
- if (command === "rollback")
136
- throw new Error("Usage: /sync rollback <snapshot-id> [--yes] [--setup <name>]");
137
- if (command === "use") throw new Error("Usage: /sync use <setup>");
138
- throw new Error(`Unexpected argument for /sync ${command}: ${options.args.join(" ")}`);
139
- }
108
+ const setupAllowed = new Set([
109
+ "config",
110
+ "files",
111
+ "status",
112
+ "diff",
113
+ "doctor",
114
+ "push",
115
+ "pull",
116
+ "sync",
117
+ "history",
118
+ "rollback",
119
+ ]);
120
+ if (options.setup && !setupAllowed.has(command)) {
121
+ throw new Error(`--setup is not supported by /sync ${command}.`);
122
+ }
123
+ if (options.yes && !["push", "pull", "sync", "rollback", "migrate-state"].includes(command)) {
124
+ throw new Error(`Confirmation/force options are not supported by /sync ${command}.`);
125
+ }
126
+ if (options.force && !["push", "pull", "sync", "rollback"].includes(command)) {
127
+ throw new Error(`Confirmation/force options are not supported by /sync ${command}.`);
128
+ }
129
+ if (options.stale && command !== "unlock") {
130
+ throw new Error(`--stale is not supported by /sync ${command}.`);
131
+ }
132
+ const expectedValues = command === "rollback" || command === "use" ? 1 : 0;
133
+ if (options.args.length !== expectedValues) {
134
+ if (command === "rollback") throw new Error("Usage: /sync rollback <snapshot-id> [--yes] [--setup <name>]");
135
+ if (command === "use") throw new Error("Usage: /sync use <setup>");
136
+ throw new Error(`Unexpected argument for /sync ${command}: ${options.args.join(" ")}`);
137
+ }
140
138
  }
141
139
 
142
140
  export function completeSyncArguments(argumentPrefix: string): CommandArgumentCompletion[] | null {
143
- const prefix = argumentPrefix.trimStart();
144
- if (prefix === "") return [...SYNC_COMMAND_COMPLETIONS];
145
-
146
- const trailingSpace = /\s$/.test(prefix);
147
- const tokens = splitArgs(prefix);
148
- if (tokens.length === 0) return [...SYNC_COMMAND_COMPLETIONS];
149
-
150
- const [command] = tokens;
151
- if (tokens.length === 1 && !trailingSpace) {
152
- const matches = SYNC_COMMAND_COMPLETIONS.filter((item) => item.value.startsWith(command));
153
- return matches.length > 0 ? [...matches] : null;
154
- }
155
-
156
- const args = tokens.slice(1);
157
- if (command === "use") {
158
- if (args.length > 1 || (trailingSpace && args.length > 0)) return null;
159
- return completeSetupValue(prefix, trailingSpace ? "" : (args[0] ?? ""));
160
- }
161
- const setupFlagIndex = args.lastIndexOf("--setup");
162
- if (setupFlagIndex >= 0 && setupFlagIndex === args.length - (trailingSpace ? 1 : 2)) {
163
- const currentSetup = trailingSpace ? "" : (args.at(-1) ?? "");
164
- if (!currentSetup.startsWith("-")) return completeSetupValue(prefix, currentSetup);
165
- }
166
-
167
- const flagCompletions = SYNC_FLAG_COMPLETIONS[command];
168
- if (!flagCompletions) return null;
169
-
170
- const completedArgs = trailingSpace ? args : args.slice(0, -1);
171
- const completedValues = completedArgs.filter((arg) => !arg.startsWith("-"));
172
- if (command === "rollback" ? completedValues.length > 1 : completedValues.length > 0) {
173
- return null;
174
- }
175
-
176
- const current = trailingSpace ? "" : (args.at(-1) ?? "");
177
- if (current && !current.startsWith("-")) return null;
178
-
179
- const currentRaw = trailingSpace ? "" : (prefix.match(/\S+$/)?.[0] ?? "");
180
- const completionPrefix = trailingSpace
181
- ? prefix
182
- : prefix.slice(0, prefix.length - currentRaw.length);
183
- const matches = flagCompletions.filter((item) => item.value.startsWith(current));
184
- return matches.length > 0
185
- ? matches.map((item) => ({ ...item, value: `${completionPrefix}${item.value}` }))
186
- : null;
141
+ const prefix = argumentPrefix.trimStart();
142
+ if (prefix === "") return [...SYNC_COMMAND_COMPLETIONS];
143
+
144
+ const trailingSpace = /\s$/.test(prefix);
145
+ const tokens = splitArgs(prefix);
146
+ if (tokens.length === 0) return [...SYNC_COMMAND_COMPLETIONS];
147
+
148
+ const [command] = tokens;
149
+ if (tokens.length === 1 && !trailingSpace) {
150
+ const matches = SYNC_COMMAND_COMPLETIONS.filter((item) => item.value.startsWith(command));
151
+ return matches.length > 0 ? [...matches] : null;
152
+ }
153
+
154
+ const args = tokens.slice(1);
155
+ if (command === "use") {
156
+ if (args.length > 1 || (trailingSpace && args.length > 0)) return null;
157
+ return completeSetupValue(prefix, trailingSpace ? "" : (args[0] ?? ""));
158
+ }
159
+ const setupFlagIndex = args.lastIndexOf("--setup");
160
+ if (setupFlagIndex >= 0 && setupFlagIndex === args.length - (trailingSpace ? 1 : 2)) {
161
+ const currentSetup = trailingSpace ? "" : (args.at(-1) ?? "");
162
+ if (!currentSetup.startsWith("-")) return completeSetupValue(prefix, currentSetup);
163
+ }
164
+
165
+ const flagCompletions = SYNC_FLAG_COMPLETIONS[command];
166
+ if (!flagCompletions) return null;
167
+
168
+ const completedArgs = trailingSpace ? args : args.slice(0, -1);
169
+ const completedValues = completedArgs.filter((arg) => !arg.startsWith("-"));
170
+ if (command === "rollback" ? completedValues.length > 1 : completedValues.length > 0) {
171
+ return null;
172
+ }
173
+
174
+ const current = trailingSpace ? "" : (args.at(-1) ?? "");
175
+ if (current && !current.startsWith("-")) return null;
176
+
177
+ const currentRaw = trailingSpace ? "" : (prefix.match(/\S+$/)?.[0] ?? "");
178
+ const completionPrefix = trailingSpace ? prefix : prefix.slice(0, prefix.length - currentRaw.length);
179
+ const matches = flagCompletions.filter((item) => item.value.startsWith(current));
180
+ return matches.length > 0 ? matches.map((item) => ({ ...item, value: `${completionPrefix}${item.value}` })) : null;
187
181
  }
188
182
 
189
183
  function completeSetupValue(prefix: string, current: string) {
190
- const currentRaw = current ? (prefix.match(/\S+$/u)?.[0] ?? "") : "";
191
- const completionPrefix = currentRaw ? prefix.slice(0, prefix.length - currentRaw.length) : prefix;
192
- const matches = setupCompletionNames.filter((name) => name.startsWith(current));
193
- return matches.length > 0
194
- ? matches.map((name) => ({
195
- value: `${completionPrefix}${name}`,
196
- label: name,
197
- description: "Sync setup",
198
- }))
199
- : null;
184
+ const currentRaw = current ? (prefix.match(/\S+$/u)?.[0] ?? "") : "";
185
+ const completionPrefix = currentRaw ? prefix.slice(0, prefix.length - currentRaw.length) : prefix;
186
+ const matches = setupCompletionNames.filter((name) => name.startsWith(current));
187
+ return matches.length > 0
188
+ ? matches.map((name) => ({
189
+ value: `${completionPrefix}${name}`,
190
+ label: name,
191
+ description: "Sync setup",
192
+ }))
193
+ : null;
200
194
  }
201
195
 
202
196
  export function syncMenuOptions() {
203
- return SYNC_COMMANDS.map(({ name, description }) => `${name} — ${description}`);
197
+ return SYNC_COMMANDS.map(({ name, description }) => `${name} — ${description}`);
204
198
  }
205
199
 
206
200
  export function syncCommandFromMenuOption(option: string): SyncCommandName | undefined {
207
- return SYNC_COMMANDS.find(({ name, description }) => option === `${name} — ${description}`)?.name;
201
+ return SYNC_COMMANDS.find(({ name, description }) => option === `${name} — ${description}`)?.name;
208
202
  }
209
203
 
210
204
  export async function resolveSyncCommand(input: string, ctx: ExtensionCommandContext) {
211
- const [subcommand, ...rest] = splitArgs(input);
212
- if (subcommand) return { subcommand, rest };
213
- if (!ctx.hasUI) {
214
- ctx.ui.notify(usage(), "info");
215
- return undefined;
216
- }
217
-
218
- const selectedOption = await ctx.ui.select("pi-sync", syncMenuOptions());
219
- const selected = selectedOption ? syncCommandFromMenuOption(selectedOption) : undefined;
220
- if (!selected) return undefined;
221
- if (selected !== "rollback") return { subcommand: selected, rest: [] };
222
-
223
- const target = (await ctx.ui.input("Rollback snapshot", "snapshot id"))?.trim();
224
- if (!target) {
225
- ctx.ui.notify("Rollback cancelled.", "info");
226
- return undefined;
227
- }
228
- return { subcommand: selected, rest: [target] };
205
+ const [subcommand, ...rest] = splitArgs(input);
206
+ if (subcommand) return { subcommand, rest };
207
+ if (!ctx.hasUI) {
208
+ ctx.ui.notify(usage(), "info");
209
+ return undefined;
210
+ }
211
+
212
+ const selectedOption = await ctx.ui.select("pi-sync", syncMenuOptions());
213
+ const selected = selectedOption ? syncCommandFromMenuOption(selectedOption) : undefined;
214
+ if (!selected) return undefined;
215
+ if (selected !== "rollback") return { subcommand: selected, rest: [] };
216
+
217
+ const target = (await ctx.ui.input("Rollback snapshot", "snapshot id"))?.trim();
218
+ if (!target) {
219
+ ctx.ui.notify("Rollback cancelled.", "info");
220
+ return undefined;
221
+ }
222
+ return { subcommand: selected, rest: [target] };
229
223
  }
230
224
 
231
225
  export function usage() {
232
- const commands = SYNC_COMMANDS.map(
233
- (command) => `${command.name}${"usageSuffix" in command ? command.usageSuffix : ""}`,
234
- ).join(", ");
235
- return [
236
- "Usage: /sync <command>",
237
- `Commands: ${commands}`,
238
- "Automatic sync: background startup checks only; use /sync to review and transfer. At shutdown, selected content is pushed automatically only when sessions are included. Startup checks run in TUI/RPC, not print/JSON.",
239
- "Settings: use /sync init or edit storage connections and sync setups in ~/.pi/agent/pi-sync.json (or the configured Pi agent directory). Version 1 and version 2 settings are unsupported and are never rewritten.",
240
- ].join("\n");
226
+ const commands = SYNC_COMMANDS.map(
227
+ (command) => `${command.name}${"usageSuffix" in command ? command.usageSuffix : ""}`,
228
+ ).join(", ");
229
+ return [
230
+ "Usage: /sync <command>",
231
+ `Commands: ${commands}`,
232
+ "Automatic sync: background startup checks only; use /sync to review and transfer. At shutdown, selected content is pushed automatically only when sessions are included. Startup checks run in TUI/RPC, not print/JSON.",
233
+ "Settings: use /sync init or edit storage connections and sync setups in ~/.pi/agent/pi-sync.json (or the configured Pi agent directory). Version 1 and version 2 settings are unsupported and are never rewritten.",
234
+ ].join("\n");
241
235
  }
@@ -2,5 +2,5 @@ import { configuredSyncSetupNames } from "../settings/settings-store.js";
2
2
  import { setSyncSetupCompletions } from "./command.js";
3
3
 
4
4
  export async function refreshTargetCompletions() {
5
- setSyncSetupCompletions(await configuredSyncSetupNames());
5
+ setSyncSetupCompletions(await configuredSyncSetupNames());
6
6
  }
package/src/paths.ts CHANGED
@@ -1,78 +1,78 @@
1
1
  import path from "node:path";
2
2
 
3
3
  export function isDeniedPath(relativePath: string) {
4
- const normalized = toPosix(relativePath);
5
- const lower = normalized.toLowerCase();
6
- const segments = lower.split("/");
7
- const base = path.posix.basename(lower);
8
- return (
9
- segments.includes("node_modules") ||
10
- segments.includes(".git") ||
11
- segments.includes(".pisync") ||
12
- segments.includes("pi-sync") ||
13
- segments.includes(".pi-sync-state-migration.lock") ||
14
- base === ".env" ||
15
- base.startsWith(".env.") ||
16
- base.endsWith(".env") ||
17
- base.includes("secret") ||
18
- base.includes("token") ||
19
- base === "pi-sync.json" ||
20
- base.startsWith("pi-sync.json.") ||
21
- base.startsWith(".pi-sync.json.") ||
22
- base === "pi-sync.local.json" ||
23
- base.startsWith("pi-sync.local.json.") ||
24
- base.startsWith(".pi-sync.local.json.")
25
- );
4
+ const normalized = toPosix(relativePath);
5
+ const lower = normalized.toLowerCase();
6
+ const segments = lower.split("/");
7
+ const base = path.posix.basename(lower);
8
+ return (
9
+ segments.includes("node_modules") ||
10
+ segments.includes(".git") ||
11
+ segments.includes(".pisync") ||
12
+ segments.includes("pi-sync") ||
13
+ segments.includes(".pi-sync-state-migration.lock") ||
14
+ base === ".env" ||
15
+ base.startsWith(".env.") ||
16
+ base.endsWith(".env") ||
17
+ base.includes("secret") ||
18
+ base.includes("token") ||
19
+ base === "pi-sync.json" ||
20
+ base.startsWith("pi-sync.json.") ||
21
+ base.startsWith(".pi-sync.json.") ||
22
+ base === "pi-sync.local.json" ||
23
+ base.startsWith("pi-sync.local.json.") ||
24
+ base.startsWith(".pi-sync.local.json.")
25
+ );
26
26
  }
27
27
 
28
28
  export function isPathInside(parent: string, child: string) {
29
- const relative = path.relative(path.resolve(parent), path.resolve(child));
30
- return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
29
+ const relative = path.relative(path.resolve(parent), path.resolve(child));
30
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
31
31
  }
32
32
 
33
33
  export function safeJoin(root: string, relativePath: string) {
34
- const target = path.resolve(root, relativePath);
35
- assertWithinRoot(root, target, relativePath);
36
- return target;
34
+ const target = path.resolve(root, relativePath);
35
+ assertWithinRoot(root, target, relativePath);
36
+ return target;
37
37
  }
38
38
 
39
39
  export function assertWithinRoot(root: string, target: string, label = target) {
40
- const resolvedRoot = path.resolve(root);
41
- const resolvedTarget = path.resolve(target);
42
- if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path.sep}`)) {
43
- throw new Error(`Unsafe path in snapshot: ${label}`);
44
- }
40
+ const resolvedRoot = path.resolve(root);
41
+ const resolvedTarget = path.resolve(target);
42
+ if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path.sep}`)) {
43
+ throw new Error(`Unsafe path in snapshot: ${label}`);
44
+ }
45
45
  }
46
46
 
47
47
  export function encodeKey(key: string) {
48
- return key.split("/").map(encodeURIComponent).join("/");
48
+ return key.split("/").map(encodeURIComponent).join("/");
49
49
  }
50
50
 
51
51
  export function posixJoin(...parts: string[]) {
52
- return parts
53
- .map((part) => trimSlashes(part))
54
- .filter(Boolean)
55
- .join("/");
52
+ return parts
53
+ .map((part) => trimSlashes(part))
54
+ .filter(Boolean)
55
+ .join("/");
56
56
  }
57
57
 
58
58
  export function parentPaths(relativePath: string) {
59
- const results: string[] = [];
60
- let index = relativePath.lastIndexOf("/");
61
- while (index > 0) {
62
- results.push(relativePath.slice(0, index));
63
- index = relativePath.lastIndexOf("/", index - 1);
64
- }
65
- return results;
59
+ const results: string[] = [];
60
+ let index = relativePath.lastIndexOf("/");
61
+ while (index > 0) {
62
+ results.push(relativePath.slice(0, index));
63
+ index = relativePath.lastIndexOf("/", index - 1);
64
+ }
65
+ return results;
66
66
  }
67
67
 
68
68
  export function toPosix(value: string) {
69
- return value.split(path.sep).join("/");
69
+ return value.split(path.sep).join("/");
70
70
  }
71
71
 
72
72
  function trimSlashes(value: string) {
73
- return value.replace(/^\/+|\/+$/g, "");
73
+ return value.replace(/^\/+|\/+$/g, "");
74
74
  }
75
75
 
76
76
  export function safeName(value: string) {
77
- return value.replace(/[^A-Za-z0-9._-]/g, "_");
77
+ return value.replace(/[^A-Za-z0-9._-]/g, "_");
78
78
  }
@@ -1,7 +1,6 @@
1
1
  export function isMissingConfigError(error: unknown) {
2
- return (
3
- error instanceof Error &&
4
- (error.message.startsWith("Missing pi-sync settings.") ||
5
- error.message === "No sync setups are configured.")
6
- );
2
+ return (
3
+ error instanceof Error &&
4
+ (error.message.startsWith("Missing pi-sync settings.") || error.message === "No sync setups are configured.")
5
+ );
7
6
  }