@narumitw/pi-sync 0.49.15 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/README.md +49 -16
  2. package/dist/chunks/chunk-2B2ELUF5.ts +29 -0
  3. package/dist/chunks/chunk-2B2ELUF5.ts.map +7 -0
  4. package/dist/chunks/chunk-6LP3MCR2.ts +115 -0
  5. package/dist/chunks/chunk-6LP3MCR2.ts.map +7 -0
  6. package/dist/chunks/chunk-6UHKGALE.ts +134 -0
  7. package/dist/chunks/chunk-6UHKGALE.ts.map +7 -0
  8. package/dist/chunks/{chunk-JF6MZCT4.ts → chunk-AUSDLD6F.ts} +4 -4
  9. package/dist/chunks/chunk-AUSDLD6F.ts.map +7 -0
  10. package/dist/chunks/{chunk-X7SQ2EQM.ts → chunk-AXXS2BPA.ts} +9 -9
  11. package/dist/chunks/chunk-AXXS2BPA.ts.map +7 -0
  12. package/dist/chunks/{chunk-5T442OP3.ts → chunk-C7XY5NF5.ts} +210 -373
  13. package/dist/chunks/chunk-C7XY5NF5.ts.map +7 -0
  14. package/dist/chunks/{chunk-4U6AFLJB.ts → chunk-CHH26D4F.ts} +3 -2
  15. package/dist/chunks/chunk-CHH26D4F.ts.map +7 -0
  16. package/dist/chunks/chunk-CPEHGXJV.ts +50 -0
  17. package/dist/chunks/chunk-CPEHGXJV.ts.map +7 -0
  18. package/dist/chunks/{chunk-5MQ76BK5.ts → chunk-D5X27LCA.ts} +2 -2
  19. package/dist/chunks/chunk-D5X27LCA.ts.map +7 -0
  20. package/dist/chunks/{chunk-WSOBYHIZ.ts → chunk-GWMHAQG4.ts} +4 -4
  21. package/dist/chunks/chunk-GWMHAQG4.ts.map +7 -0
  22. package/dist/chunks/{chunk-5LZALPCD.ts → chunk-HUDQATCL.ts} +25 -25
  23. package/dist/chunks/chunk-HUDQATCL.ts.map +7 -0
  24. package/dist/chunks/{chunk-YQ6UW7IF.ts → chunk-JAAKLL6M.ts} +2 -2
  25. package/dist/chunks/chunk-JAAKLL6M.ts.map +7 -0
  26. package/dist/chunks/chunk-JSYMGJQQ.ts +51 -0
  27. package/dist/chunks/chunk-JSYMGJQQ.ts.map +7 -0
  28. package/dist/chunks/chunk-L7TBO32M.ts +25 -0
  29. package/dist/chunks/chunk-L7TBO32M.ts.map +7 -0
  30. package/dist/chunks/{chunk-DFXBKHGN.ts → chunk-LCRI3ABW.ts} +21 -8
  31. package/dist/chunks/chunk-LCRI3ABW.ts.map +7 -0
  32. package/dist/chunks/chunk-LTBWGSBV.ts +12 -0
  33. package/dist/chunks/chunk-LTBWGSBV.ts.map +7 -0
  34. package/dist/chunks/chunk-NJFUIVYL.ts +17 -0
  35. package/dist/chunks/chunk-NJFUIVYL.ts.map +7 -0
  36. package/dist/chunks/{chunk-HVUSHMVL.ts → chunk-OEX4ABY6.ts} +39 -37
  37. package/dist/chunks/chunk-OEX4ABY6.ts.map +7 -0
  38. package/dist/chunks/chunk-OUHGBFYF.ts +1255 -0
  39. package/dist/chunks/chunk-OUHGBFYF.ts.map +7 -0
  40. package/dist/chunks/{chunk-HGV24P4T.ts → chunk-Q2TUM5X6.ts} +3 -3
  41. package/dist/chunks/chunk-Q2TUM5X6.ts.map +7 -0
  42. package/dist/chunks/{chunk-GLQLN5GV.ts → chunk-SL64RLXR.ts} +8 -6
  43. package/dist/chunks/chunk-SL64RLXR.ts.map +7 -0
  44. package/dist/chunks/chunk-T3HH3UEJ.ts +96 -0
  45. package/dist/chunks/chunk-T3HH3UEJ.ts.map +7 -0
  46. package/dist/chunks/chunk-TEN2ZBJV.ts +101 -0
  47. package/dist/chunks/chunk-TEN2ZBJV.ts.map +7 -0
  48. package/dist/chunks/chunk-THMF4BVL.ts +18 -0
  49. package/dist/chunks/chunk-THMF4BVL.ts.map +7 -0
  50. package/dist/chunks/{chunk-44LPGQTI.ts → chunk-X5OM6GXT.ts} +9 -7
  51. package/dist/chunks/chunk-X5OM6GXT.ts.map +7 -0
  52. package/dist/chunks/chunk-XBX4HBLG.ts +57 -0
  53. package/dist/chunks/chunk-XBX4HBLG.ts.map +7 -0
  54. package/dist/chunks/{chunk-ARN2672F.ts → chunk-YKJHPTGO.ts} +23 -55
  55. package/dist/chunks/chunk-YKJHPTGO.ts.map +7 -0
  56. package/dist/chunks/chunk-Z4DQF6L3.ts +12 -0
  57. package/dist/chunks/chunk-Z4DQF6L3.ts.map +7 -0
  58. package/dist/chunks/{chunk-F5QL2GPY.ts → chunk-ZI5MXMEX.ts} +5 -99
  59. package/dist/chunks/chunk-ZI5MXMEX.ts.map +7 -0
  60. package/dist/chunks/{file-selection-RKEHALNQ.ts → file-selection-FTTKAOYX.ts} +10 -7
  61. package/dist/chunks/file-selection-FTTKAOYX.ts.map +7 -0
  62. package/dist/chunks/{git-backend-E3UOUUOM.ts → git-backend-OQW46G2X.ts} +42 -17
  63. package/dist/chunks/git-backend-OQW46G2X.ts.map +7 -0
  64. package/dist/chunks/manager-result-dispatcher-U23ODD32.ts +22 -0
  65. package/dist/chunks/manager-ui-ABATHC36.ts +1680 -0
  66. package/dist/chunks/manager-ui-ABATHC36.ts.map +7 -0
  67. package/dist/chunks/remote-selection-ui-ZE3TY67S.ts +21 -0
  68. package/dist/chunks/{s3-backend-552LXHE4.ts → s3-backend-3P33LQHU.ts} +96 -18
  69. package/dist/chunks/s3-backend-3P33LQHU.ts.map +7 -0
  70. package/dist/chunks/{setup-switch-5YDG5YGJ.ts → setup-switch-IS2PEZ3J.ts} +9 -9
  71. package/dist/chunks/setup-wizard-VB3VDOV5.ts +89 -0
  72. package/dist/chunks/setup-wizard-VB3VDOV5.ts.map +7 -0
  73. package/dist/chunks/{snapshot-2RYBUDRA.ts → snapshot-HKEWCVK6.ts} +7 -6
  74. package/dist/chunks/sync-inspection-FEP4L5TR.ts +20 -0
  75. package/dist/chunks/{sync-operations-3LLIHWZO.ts → sync-operations-ONJ27QRL.ts} +374 -294
  76. package/dist/chunks/sync-operations-ONJ27QRL.ts.map +7 -0
  77. package/dist/chunks/{sync-state-SRN6YRBK.ts → sync-state-Q2FTXTW6.ts} +6 -6
  78. package/dist/chunks/sync-state-Q2FTXTW6.ts.map +7 -0
  79. package/dist/chunks/{webdav-backend-ZD5RHRIU.ts → webdav-backend-ECIKD7SO.ts} +26 -13
  80. package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +7 -0
  81. package/dist/index.ts +477 -342
  82. package/dist/index.ts.map +4 -4
  83. package/docs/settings.md +30 -6
  84. package/package.json +1 -1
  85. package/src/{backend-factory.ts → backends/backend-factory.ts} +4 -4
  86. package/src/backends/backend-identity.ts +40 -0
  87. package/src/backends/backend-types.ts +81 -0
  88. package/src/{git-backend.ts → backends/git/git-backend.ts} +43 -19
  89. package/src/{git-config.ts → backends/git/git-config.ts} +1 -0
  90. package/src/{git-storage.ts → backends/git/git-storage.ts} +2 -2
  91. package/src/{s3-backend.ts → backends/s3/s3-backend.ts} +45 -9
  92. package/src/{s3-client.ts → backends/s3/s3-client.ts} +56 -8
  93. package/src/{sync-backend.ts → backends/sync-backend.ts} +1 -1
  94. package/src/{webdav-backend.ts → backends/webdav/webdav-backend.ts} +22 -10
  95. package/src/{webdav-client.ts → backends/webdav/webdav-client.ts} +1 -1
  96. package/src/{webdav-config.ts → backends/webdav/webdav-config.ts} +1 -0
  97. package/src/commands/command-execution.ts +330 -0
  98. package/src/commands/command-handler.ts +201 -0
  99. package/src/commands/command-types.ts +18 -0
  100. package/src/{command.ts → commands/command.ts} +2 -1
  101. package/src/commands/setup-completions.ts +6 -0
  102. package/src/settings/config.ts +238 -0
  103. package/src/{settings-management.ts → settings/settings-management.ts} +39 -14
  104. package/src/settings/settings-store.ts +109 -0
  105. package/src/settings/settings-types.ts +128 -0
  106. package/src/settings/settings-validation.ts +443 -0
  107. package/src/snapshot/session-paths.ts +84 -0
  108. package/src/{snapshot-apply.ts → snapshot/snapshot-apply.ts} +3 -3
  109. package/src/{snapshot-codec.ts → snapshot/snapshot-codec.ts} +2 -2
  110. package/src/{snapshot-transaction.ts → snapshot/snapshot-transaction.ts} +5 -4
  111. package/src/snapshot/snapshot-types.ts +38 -0
  112. package/src/{snapshot.ts → snapshot/snapshot.ts} +39 -20
  113. package/src/state/json-file.ts +20 -0
  114. package/src/{lock.ts → state/lock.ts} +3 -2
  115. package/src/{operation-availability.ts → state/operation-availability.ts} +2 -2
  116. package/src/state/state-types.ts +20 -0
  117. package/src/state/sync-state-store.ts +62 -0
  118. package/src/sync/automatic-sync.ts +103 -0
  119. package/src/{remote-snapshot.ts → sync/remote-snapshot.ts} +27 -4
  120. package/src/{setup-switch.ts → sync/setup-switch.ts} +6 -7
  121. package/src/sync/signals.ts +10 -0
  122. package/src/sync/startup-check.ts +128 -0
  123. package/src/{sync-decision.ts → sync/sync-decision.ts} +6 -3
  124. package/src/sync/sync-error-guidance.ts +53 -0
  125. package/src/sync/sync-inspection.ts +51 -0
  126. package/src/sync/sync-loaders.ts +54 -0
  127. package/src/{sync-operations.ts → sync/sync-mutations.ts} +34 -325
  128. package/src/sync/sync-operations.ts +12 -0
  129. package/src/{sync-policy.ts → sync/sync-policy.ts} +2 -2
  130. package/src/sync/sync-queries.ts +235 -0
  131. package/src/{sync-state.ts → sync/sync-state.ts} +5 -4
  132. package/src/sync-extension.ts +64 -708
  133. package/src/sync.ts +8 -8
  134. package/src/types.ts +47 -286
  135. package/src/ui/automatic-sync-summary.ts +6 -0
  136. package/src/{cancellable-operation.ts → ui/cancellable-operation.ts} +4 -4
  137. package/src/{file-selection.ts → ui/file-selection.ts} +5 -3
  138. package/src/{manager-attention.ts → ui/manager-attention.ts} +5 -1
  139. package/src/{manager-recovery.ts → ui/manager-recovery.ts} +6 -2
  140. package/src/{manager-state.ts → ui/manager-state.ts} +35 -14
  141. package/src/ui/manager-ui.ts +386 -0
  142. package/src/{remote-selection-ui.ts → ui/remote-selection-ui.ts} +21 -13
  143. package/src/{secret-input.ts → ui/secret-input.ts} +22 -4
  144. package/src/{settings-ui.ts → ui/settings-ui.ts} +17 -13
  145. package/src/ui/setup/git-ui.ts +293 -0
  146. package/src/{s3-credentials-ui.ts → ui/setup/s3-credentials-ui.ts} +13 -21
  147. package/src/ui/setup/s3-ui.ts +217 -0
  148. package/src/ui/setup/setup-actions.ts +127 -0
  149. package/src/ui/setup/setup-location-ui.ts +167 -0
  150. package/src/ui/setup/setup-name-ui.ts +34 -0
  151. package/src/ui/setup/setup-prompts.ts +92 -0
  152. package/src/ui/setup/setup-review.ts +70 -0
  153. package/src/ui/setup/setup-switcher.ts +181 -0
  154. package/src/ui/setup/setup-wizard.ts +39 -0
  155. package/src/ui/setup/text-input.ts +78 -0
  156. package/src/ui/setup/webdav-ui.ts +330 -0
  157. package/src/{storage-connections-ui.ts → ui/storage-connections-ui.ts} +99 -69
  158. package/src/{sync-attention.ts → ui/sync-attention.ts} +75 -6
  159. package/src/{sync-format.ts → ui/sync-format.ts} +7 -13
  160. package/src/{sync-resolution-ui.ts → ui/sync-resolution-ui.ts} +4 -3
  161. package/src/{sync-setups-ui.ts → ui/sync-setups-ui.ts} +23 -10
  162. package/src/ui/terminal-text.ts +4 -0
  163. package/dist/chunks/chunk-44LPGQTI.ts.map +0 -7
  164. package/dist/chunks/chunk-4U6AFLJB.ts.map +0 -7
  165. package/dist/chunks/chunk-5LZALPCD.ts.map +0 -7
  166. package/dist/chunks/chunk-5MQ76BK5.ts.map +0 -7
  167. package/dist/chunks/chunk-5T442OP3.ts.map +0 -7
  168. package/dist/chunks/chunk-ARN2672F.ts.map +0 -7
  169. package/dist/chunks/chunk-C7LXZ3UH.ts +0 -167
  170. package/dist/chunks/chunk-C7LXZ3UH.ts.map +0 -7
  171. package/dist/chunks/chunk-DFXBKHGN.ts.map +0 -7
  172. package/dist/chunks/chunk-E4COSV32.ts +0 -82
  173. package/dist/chunks/chunk-E4COSV32.ts.map +0 -7
  174. package/dist/chunks/chunk-F5QL2GPY.ts.map +0 -7
  175. package/dist/chunks/chunk-GLQLN5GV.ts.map +0 -7
  176. package/dist/chunks/chunk-HGV24P4T.ts.map +0 -7
  177. package/dist/chunks/chunk-HVUSHMVL.ts.map +0 -7
  178. package/dist/chunks/chunk-JF6MZCT4.ts.map +0 -7
  179. package/dist/chunks/chunk-WSOBYHIZ.ts.map +0 -7
  180. package/dist/chunks/chunk-X7SQ2EQM.ts.map +0 -7
  181. package/dist/chunks/chunk-YQ6UW7IF.ts.map +0 -7
  182. package/dist/chunks/file-selection-RKEHALNQ.ts.map +0 -7
  183. package/dist/chunks/git-backend-E3UOUUOM.ts.map +0 -7
  184. package/dist/chunks/manager-result-dispatcher-LWPYJAD5.ts +0 -18
  185. package/dist/chunks/manager-ui-NF4RUHBY.ts +0 -2785
  186. package/dist/chunks/manager-ui-NF4RUHBY.ts.map +0 -7
  187. package/dist/chunks/remote-selection-ui-NUF5P4ZJ.ts +0 -17
  188. package/dist/chunks/s3-backend-552LXHE4.ts.map +0 -7
  189. package/dist/chunks/sync-operations-3LLIHWZO.ts.map +0 -7
  190. package/dist/chunks/webdav-backend-ZD5RHRIU.ts.map +0 -7
  191. package/src/config.ts +0 -923
  192. package/src/git-ui.ts +0 -307
  193. package/src/manager-helpers.ts +0 -64
  194. package/src/manager-ui.ts +0 -997
  195. package/src/webdav-ui.ts +0 -421
  196. /package/dist/chunks/{manager-result-dispatcher-LWPYJAD5.ts.map → manager-result-dispatcher-U23ODD32.ts.map} +0 -0
  197. /package/dist/chunks/{remote-selection-ui-NUF5P4ZJ.ts.map → remote-selection-ui-ZE3TY67S.ts.map} +0 -0
  198. /package/dist/chunks/{setup-switch-5YDG5YGJ.ts.map → setup-switch-IS2PEZ3J.ts.map} +0 -0
  199. /package/dist/chunks/{snapshot-2RYBUDRA.ts.map → snapshot-HKEWCVK6.ts.map} +0 -0
  200. /package/dist/chunks/{sync-state-SRN6YRBK.ts.map → sync-inspection-FEP4L5TR.ts.map} +0 -0
  201. /package/src/{git-runner.ts → backends/git/git-runner.ts} +0 -0
  202. /package/src/{config-errors.ts → settings/config-errors.ts} +0 -0
  203. /package/src/{config-file.ts → settings/config-file.ts} +0 -0
  204. /package/src/{snapshot-paths.ts → snapshot/snapshot-paths.ts} +0 -0
  205. /package/src/{lock-policy.ts → state/lock-policy.ts} +0 -0
  206. /package/src/{lockfile-fs.ts → state/lockfile-fs.ts} +0 -0
  207. /package/src/{state-directory.ts → state/state-directory.ts} +0 -0
  208. /package/src/{sync-errors.ts → sync/sync-errors.ts} +0 -0
  209. /package/src/{manager-result-dispatcher.ts → ui/manager-result-dispatcher.ts} +0 -0
@@ -1,17 +1,24 @@
1
1
  // @generated by scripts/build-runtime.mjs; do not edit.
2
2
  // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
3
+ import {
4
+ backendIdentityCoordinates,
5
+ normalizeEndpointIdentity,
6
+ normalizeWebDavIdentityUrl,
7
+ normalizeWebDavPath,
8
+ normalizeWebDavUrl,
9
+ validateWebDavCredentials
10
+ } from "./chunk-6LP3MCR2.ts";
3
11
  import {
4
12
  normalizeGitBranch,
5
13
  normalizeGitDirectory,
6
14
  normalizeGitRemote,
7
- normalizeGitRemoteIdentity,
8
- stateDir
9
- } from "./chunk-F5QL2GPY.ts";
15
+ normalizeGitRemoteIdentity
16
+ } from "./chunk-TEN2ZBJV.ts";
10
17
  import {
11
18
  normalizeSyncInclude
12
- } from "./chunk-YQ6UW7IF.ts";
19
+ } from "./chunk-JAAKLL6M.ts";
13
20
 
14
- // src/config-file.ts
21
+ // src/settings/config-file.ts
15
22
  import { randomUUID } from "node:crypto";
16
23
  import {
17
24
  mkdir,
@@ -72,6 +79,15 @@ function consumeLocalConfigMigrationNotice() {
72
79
  configMigrationNotices.delete(configPath);
73
80
  return notice;
74
81
  }
82
+ async function readActiveLocalConfigDocumentForRepair() {
83
+ const canonicalPath = localConfigPath();
84
+ const legacyPath = legacyLocalConfigPath();
85
+ return withLocalConfigReadLockIfNeeded(async () => {
86
+ const filePath = await pathExists(canonicalPath) ? canonicalPath : legacyPath;
87
+ const snapshot = await readConfigSnapshotIfExists(filePath);
88
+ return snapshot ? { path: filePath, ...snapshot } : void 0;
89
+ });
90
+ }
75
91
  async function readMigratingLocalConfigDocument(validateForMigration) {
76
92
  return withLocalConfigReadLockIfNeeded(async () => {
77
93
  const configPath = await prepareLocalConfigPath(validateForMigration);
@@ -482,169 +498,9 @@ function recordConfigMigrationNotice(configPath, notice) {
482
498
  if (!configMigrationNotices.has(configPath)) configMigrationNotices.set(configPath, notice);
483
499
  }
484
500
 
485
- // src/config.ts
486
- import { createHash, randomUUID as randomUUID2 } from "node:crypto";
487
- import fs2 from "node:fs/promises";
488
- import os from "node:os";
501
+ // src/settings/settings-validation.ts
489
502
  import path2 from "node:path";
490
- import {
491
- getAgentDir as getAgentDir2
492
- } from "@earendil-works/pi-coding-agent";
493
-
494
- // src/webdav-config.ts
495
- var DEFAULT_PATH = "pi-sync";
496
- function normalizeWebDavIdentityUrl(value) {
497
- try {
498
- const url = new URL(value.trim());
499
- url.username = "";
500
- url.password = "";
501
- url.search = "";
502
- url.hash = "";
503
- url.pathname = `${url.pathname.replace(/\/+$/u, "")}/`;
504
- return url.toString();
505
- } catch {
506
- return value.trim();
507
- }
508
- }
509
- function normalizeWebDavUrl(value) {
510
- const normalized = normalizeOptionalString(value);
511
- if (!normalized) return void 0;
512
- let url;
513
- try {
514
- url = new URL(normalized);
515
- } catch {
516
- throw new Error("Invalid pi-sync WebDAV URL.");
517
- }
518
- if (url.username || url.password || url.search || url.hash) {
519
- throw new Error(
520
- "Invalid pi-sync WebDAV URL: credentials, query, and fragment are not allowed."
521
- );
522
- }
523
- const loopback = url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "[::1]" || url.hostname === "::1";
524
- if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
525
- throw new Error("Invalid pi-sync WebDAV URL: HTTPS is required except for loopback.");
526
- }
527
- url.pathname = `${url.pathname.replace(/\/+$/u, "")}/`;
528
- return url.toString();
529
- }
530
- function normalizeWebDavPath(value) {
531
- const normalized = trimSlashes(normalizeOptionalString(value) ?? DEFAULT_PATH);
532
- if (!normalized || normalized.includes("\\") || hasControlCharacter(normalized) || normalized.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
533
- throw new Error("Invalid pi-sync WebDAV path.");
534
- }
535
- return normalized;
536
- }
537
- function validateWebDavNamespace(value) {
538
- if (value === "." || value === ".." || value.includes("/") || value.includes("\\") || hasControlCharacter(value)) {
539
- throw new Error("Invalid pi-sync WebDAV namespace.");
540
- }
541
- }
542
- function validateWebDavCredentials(username, password) {
543
- if (username.includes(":") || hasControlCharacter(username) || password !== void 0 && hasControlCharacter(password)) {
544
- throw new Error("Invalid pi-sync WebDAV credentials.");
545
- }
546
- }
547
- function normalizeOptionalString(value) {
548
- const normalized = value?.trim();
549
- return normalized || void 0;
550
- }
551
- function trimSlashes(value) {
552
- return value.replace(/^\/+|\/+$/gu, "");
553
- }
554
- function hasControlCharacter(value) {
555
- return [...value].some((character) => {
556
- const code = character.codePointAt(0) ?? 0;
557
- return code < 32 || code >= 127 && code <= 159;
558
- });
559
- }
560
-
561
- // src/config-errors.ts
562
- function isMissingConfigError(error) {
563
- return error instanceof Error && (error.message.startsWith("Missing pi-sync settings.") || error.message === "No sync setups are configured.");
564
- }
565
-
566
- // src/config.ts
567
- var STATE_VERSION = 2;
568
503
  var DEFAULT_ON_SWITCH = "ask-before-pull";
569
- function sessionDirFromContext(ctx) {
570
- const manager = ctx.sessionManager;
571
- if (manager.usesDefaultSessionDir?.call(manager)) return void 0;
572
- return typeof manager.getSessionDir === "function" ? manager.getSessionDir.call(manager) : void 0;
573
- }
574
- async function loadConfig(setupName) {
575
- const settings = await requireSettings();
576
- const selectedName = setupName ?? settings.activeSyncSetup;
577
- if (!selectedName) throw new Error("No sync setups are configured.");
578
- validateConfigName(selectedName, "sync setup");
579
- const setup = ownObject(settings.syncSetups, selectedName);
580
- if (!setup)
581
- throw new Error(`Invalid pi-sync settings: sync setup \u201C${selectedName}\u201D was not found.`);
582
- const connectionName = setup.storage.connection;
583
- const connection = ownObject(
584
- settings.storageConnections,
585
- connectionName
586
- );
587
- if (!connection) {
588
- throw new Error(
589
- `Invalid pi-sync settings: sync setup \u201C${selectedName}\u201D references missing storage connection \u201C${connectionName}\u201D.`
590
- );
591
- }
592
- return resolveSyncConfig(
593
- selectedName,
594
- setup,
595
- connectionName,
596
- connection,
597
- settings.onSwitch,
598
- settings.skipSecretScan ?? false
599
- );
600
- }
601
- async function loadPartialConfig(setupName) {
602
- const config = await loadConfig(setupName);
603
- return {
604
- setupName: config.setupName,
605
- ...storageReviewFromConfig(config),
606
- include: [...config.include],
607
- automatic: config.automatic,
608
- onSwitch: config.onSwitch
609
- };
610
- }
611
- function syncSetupStorageReview(setupName, setup, connectionName, connection) {
612
- return storageReviewFromConfig(
613
- resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false)
614
- );
615
- }
616
- function syncSetupReviewIdentity(setupName, setup, connectionName, connection) {
617
- return syncConfigReviewIdentity(
618
- resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false)
619
- );
620
- }
621
- function syncConfigReviewIdentity(config) {
622
- return JSON.stringify([
623
- config.setupName,
624
- config.connectionName,
625
- backendIdentityCoordinates(config),
626
- config.include,
627
- config.automatic
628
- ]);
629
- }
630
- function syncConfigReviewFingerprint(config) {
631
- return createHash("sha256").update(syncConfigReviewIdentity(config)).digest("hex");
632
- }
633
- function storageReviewFromConfig(config) {
634
- return {
635
- connectionName: config.connectionName,
636
- storageKind: config.backend.type,
637
- storagePath: config.storagePath,
638
- ...config.backend.type === "s3" ? { bucket: config.backend.destination.bucket } : config.backend.type === "git" ? { branch: config.backend.destination.branch } : {}
639
- };
640
- }
641
- async function configuredSyncSetupNames() {
642
- const settings = await readLocalConfigObject();
643
- return settings ? Object.keys(settings.syncSetups).sort((left, right) => left.localeCompare(right)) : [];
644
- }
645
- async function loadOnSwitch() {
646
- return (await requireSettings()).onSwitch;
647
- }
648
504
  function normalizeOnSwitch(value) {
649
505
  if (value === "ask-before-pull" || value === "pull-after-switch" || value === "switch-only") {
650
506
  return value;
@@ -653,76 +509,6 @@ function normalizeOnSwitch(value) {
653
509
  'Invalid pi-sync settings: onSwitch must be "ask-before-pull", "pull-after-switch", or "switch-only".'
654
510
  );
655
511
  }
656
- function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitch, skipSecretScan) {
657
- const storagePath = normalizeStoragePath(setup.storage.path);
658
- const namespace = storagePath.slice(storagePath.lastIndexOf("/") + 1);
659
- const include = normalizeSyncInclude(setup.sync.include);
660
- const common = {
661
- setupName,
662
- connectionName,
663
- storagePath,
664
- snapshotIdentity: namespace,
665
- include,
666
- automatic: setup.sync.automatic,
667
- onSwitch,
668
- skipSecretScan
669
- };
670
- if (connection.type === "git") {
671
- return {
672
- ...common,
673
- backend: {
674
- type: "git",
675
- profile: { kind: "git", remote: normalizeGitRemote(connection.remote) },
676
- destination: {
677
- branch: normalizeGitBranch(setup.storage.branch),
678
- directory: normalizeGitDirectory(storagePath),
679
- namespace
680
- }
681
- }
682
- };
683
- }
684
- if (connection.type === "webdav") {
685
- return {
686
- ...common,
687
- backend: {
688
- type: "webdav",
689
- profile: {
690
- kind: "webdav",
691
- url: normalizeWebDavUrl(connection.url),
692
- username: connection.credentials.username,
693
- password: connection.credentials.password
694
- },
695
- destination: { path: normalizeWebDavPath(storagePath), namespace }
696
- }
697
- };
698
- }
699
- return {
700
- ...common,
701
- backend: {
702
- type: "s3",
703
- profile: {
704
- kind: isCloudflareR2Endpoint(connection.endpoint) ? "r2" : "s3-compatible",
705
- endpoint: normalizeS3Endpoint(connection.endpoint),
706
- region: requiredString(connection.region, "S3 region"),
707
- accessKeyId: connection.credentials.accessKeyId,
708
- secretAccessKey: connection.credentials.secretAccessKey,
709
- sessionToken: optionalString(connection.credentials.sessionToken, "S3 session token")
710
- },
711
- destination: {
712
- bucket: normalizeS3Bucket(setup.storage.bucket),
713
- prefix: storagePath,
714
- namespace
715
- }
716
- }
717
- };
718
- }
719
- async function requireSettings() {
720
- const settings = await readLocalConfigObject();
721
- if (!settings) {
722
- throw new Error(`Missing pi-sync settings. Use /sync setup or create ${localConfigPath()}.`);
723
- }
724
- return settings;
725
- }
726
512
  function validateSettingsDocument(value) {
727
513
  if (value.version !== 3) {
728
514
  throw new Error(
@@ -977,12 +763,12 @@ function requireRecord(value, field) {
977
763
  return value;
978
764
  }
979
765
  function validateConfigName(value, field) {
980
- if (!value.trim() || value !== value.trim() || value.length > 100 || value === "__proto__" || value === "prototype" || value === "constructor" || hasControlCharacter2(value)) {
766
+ if (!value.trim() || value !== value.trim() || value.length > 100 || value === "__proto__" || value === "prototype" || value === "constructor" || hasControlCharacter(value)) {
981
767
  throw new Error(`Invalid pi-sync settings: invalid ${field} name.`);
982
768
  }
983
769
  }
984
770
  function requiredString(value, field) {
985
- if (typeof value !== "string" || !value.trim() || hasControlCharacter2(value)) {
771
+ if (typeof value !== "string" || !value.trim() || hasControlCharacter(value)) {
986
772
  throw new Error(`Invalid pi-sync settings: ${field} must be a non-empty string.`);
987
773
  }
988
774
  return value.trim();
@@ -1002,21 +788,22 @@ function optionalCanonicalReference(value, field) {
1002
788
  return normalized;
1003
789
  }
1004
790
  function requiredSecret(value, field) {
1005
- if (typeof value !== "string" || !value || hasControlCharacter2(value)) {
791
+ if (typeof value !== "string" || !value || hasControlCharacter(value)) {
1006
792
  throw new Error(`Invalid pi-sync settings: ${field} must be configured.`);
1007
793
  }
1008
794
  return value;
1009
795
  }
1010
796
  function optionalString(value, field) {
1011
797
  if (value === void 0) return void 0;
1012
- if (typeof value !== "string" || hasControlCharacter2(value)) {
798
+ if (typeof value !== "string" || hasControlCharacter(value)) {
1013
799
  throw new Error(`Invalid pi-sync settings: ${field} must be a string.`);
1014
800
  }
1015
801
  return value.trim() || void 0;
1016
802
  }
1017
803
  function normalizeStoragePath(value) {
804
+ if (value.trim() === "." || value.trim() === "./") return "./";
1018
805
  const normalized = value.trim().replace(/^\/+|\/+$/gu, "");
1019
- if (!normalized || normalized.length > 1024 || normalized.startsWith("-") || normalized.includes("\\") || hasControlCharacter2(normalized) || normalized.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
806
+ if (!normalized || normalized.length > 1024 || normalized.startsWith("-") || normalized.includes("\\") || hasControlCharacter(normalized) || normalized.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
1020
807
  throw new Error("Invalid pi-sync settings: storage.path must be a safe relative path.");
1021
808
  }
1022
809
  return normalized;
@@ -1042,93 +829,40 @@ function normalizeS3Bucket(value) {
1042
829
  }
1043
830
  return bucket;
1044
831
  }
1045
- function normalizeEndpointIdentity(endpoint) {
832
+ function hasControlCharacter(value) {
833
+ return /[\u0000-\u001f\u007f-\u009f]/u.test(value);
834
+ }
835
+ function isCloudflareR2Endpoint(endpoint) {
836
+ const value = endpoint?.trim();
837
+ if (!value) return false;
1046
838
  try {
1047
- const url = new URL(endpoint.trim());
1048
- url.hostname = url.hostname.toLowerCase();
1049
- url.pathname = url.pathname.replace(/\/+$/gu, "");
1050
- return url.toString().replace(/\/$/u, "");
839
+ const hostname = new URL(value).hostname.toLowerCase();
840
+ return hostname === "r2.cloudflarestorage.com" || hostname.endsWith(".r2.cloudflarestorage.com");
1051
841
  } catch {
1052
- return endpoint.trim();
842
+ return false;
1053
843
  }
1054
844
  }
1055
- async function configuredSessionDir() {
1056
- const settings = await readJsonIfExists(
1057
- path2.join(agentDir(), "settings.json")
1058
- );
1059
- return settings?.sessionDir ? expandHome(settings.sessionDir) : void 0;
1060
- }
1061
- async function sessionDirForApply(ctx, snapshot) {
1062
- const contextSessionDir = sessionDirFromContext(ctx);
1063
- const localSessionDir = await configuredSessionDir();
1064
- if (contextSessionDir && path2.resolve(contextSessionDir) !== path2.resolve(localSessionDir ?? "")) {
1065
- return contextSessionDir;
1066
- }
1067
- return sessionDirFromSnapshot(snapshot) ?? contextSessionDir;
845
+ function ownRecord(value) {
846
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1068
847
  }
1069
- function sessionDirFromSnapshot(snapshot) {
1070
- const settingsFile = snapshot.files.find((file) => file.path === "settings.json");
1071
- if (!settingsFile) return void 0;
1072
- try {
1073
- const settings = JSON.parse(
1074
- decodeBase64Strict(settingsFile.contentBase64, settingsFile.path).toString("utf8")
848
+
849
+ // src/settings/settings-store.ts
850
+ var configUpdateQueue = Promise.resolve();
851
+ async function requireSettings() {
852
+ const settings = await readLocalConfigObject();
853
+ if (!settings) {
854
+ throw new Error(
855
+ `Missing pi-sync settings. Open /sync and choose Set up sync, or create ${localConfigPath()}.`
1075
856
  );
1076
- return settings.sessionDir ? expandHome(settings.sessionDir) : void 0;
1077
- } catch {
1078
- return void 0;
1079
- }
1080
- }
1081
- function decodeBase64Strict(value, filePath) {
1082
- if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value) || value.length % 4 !== 0) {
1083
- throw new Error(`Invalid base64 content in snapshot file: ${filePath}`);
1084
857
  }
1085
- return Buffer.from(value, "base64");
858
+ return settings;
1086
859
  }
1087
- async function readStateForConfig(config) {
1088
- return await readJsonIfExists(statePathForConfig(config)) ?? {
1089
- version: STATE_VERSION,
1090
- profile: config.snapshotIdentity,
1091
- lastFileHashes: {}
1092
- };
860
+ async function configuredSyncSetupNames() {
861
+ const settings = await readLocalConfigObject();
862
+ return settings ? Object.keys(settings.syncSetups).sort((left, right) => left.localeCompare(right)) : [];
1093
863
  }
1094
- async function writeStateForConfig(config, state) {
1095
- await writeJson(statePathForConfig(config), state);
1096
- }
1097
- function statePathForConfig(config) {
1098
- const identity = backendIdentityCoordinates(config);
1099
- const hash = createHash("sha256").update(identity).digest("hex").slice(0, 16);
1100
- return path2.join(stateDir(), "setups", `${config.backend.type}-${hash}.state.json`);
1101
- }
1102
- function backendIdentityCoordinates(config) {
1103
- switch (config.backend.type) {
1104
- case "s3":
1105
- return JSON.stringify([
1106
- "s3",
1107
- normalizeEndpointIdentity(config.backend.profile.endpoint),
1108
- config.backend.destination.bucket,
1109
- config.storagePath
1110
- ]);
1111
- case "git":
1112
- return JSON.stringify([
1113
- "git",
1114
- normalizeGitRemoteIdentity(config.backend.profile.remote),
1115
- config.backend.destination.branch,
1116
- config.storagePath
1117
- ]);
1118
- case "webdav":
1119
- return JSON.stringify([
1120
- "webdav",
1121
- normalizeWebDavIdentityUrl(config.backend.profile.url),
1122
- config.backend.profile.username,
1123
- config.storagePath
1124
- ]);
1125
- }
1126
- }
1127
- function agentDir() {
1128
- return getAgentDir2();
1129
- }
1130
- function expandHome(value) {
1131
- return value === "~" || value.startsWith("~/") ? path2.join(os.homedir(), value.slice(2)) : value;
864
+ async function loadOnSwitch() {
865
+ return (await requireSettings()).onSwitch;
1132
866
  }
1133
867
  function localConfigTemplate() {
1134
868
  return {
@@ -1149,7 +883,6 @@ async function readLocalConfigDocument() {
1149
883
  async function readLocalConfigObject() {
1150
884
  return (await readLocalConfigDocument())?.parsed;
1151
885
  }
1152
- var configUpdateQueue = Promise.resolve();
1153
886
  function updateLocalConfig(update, signal) {
1154
887
  const operation = configUpdateQueue.then(() => {
1155
888
  signal?.throwIfAborted();
@@ -1164,27 +897,149 @@ function updateLocalConfig(update, signal) {
1164
897
  async function performLocalConfigUpdate(update, signal) {
1165
898
  return updateLocalConfigDocument(localConfigTemplate(), update, validateSettingsDocument, signal);
1166
899
  }
1167
- function lockPath() {
1168
- return path2.join(stateDir(), "lock");
900
+
901
+ // src/settings/config.ts
902
+ import { createHash } from "node:crypto";
903
+ async function loadConfig(setupName) {
904
+ return configFromSettings(await requireSettings(), setupName);
1169
905
  }
1170
- async function ensureStateDir() {
1171
- await fs2.mkdir(stateDir(), { recursive: true });
906
+ async function loadConfigForCheck() {
907
+ const document = await readActiveLocalConfigDocumentForRepair();
908
+ if (!document) throw new Error(`Missing pi-sync settings: ${localConfigPath()}`);
909
+ validateSettingsDocument(document.parsed);
910
+ return configFromSettings(document.parsed);
1172
911
  }
1173
- async function readJsonIfExists(filePath) {
1174
- try {
1175
- return JSON.parse(await fs2.readFile(filePath, "utf8"));
1176
- } catch (error) {
1177
- if (error.code === "ENOENT") return void 0;
1178
- throw error;
912
+ function configFromSettings(settings, setupName) {
913
+ const selectedName = setupName ?? settings.activeSyncSetup;
914
+ if (!selectedName) throw new Error("No sync setups are configured.");
915
+ validateConfigName(selectedName, "sync setup");
916
+ const setup = ownObject(settings.syncSetups, selectedName);
917
+ if (!setup)
918
+ throw new Error(`Invalid pi-sync settings: sync setup \u201C${selectedName}\u201D was not found.`);
919
+ const connectionName = setup.storage.connection;
920
+ const connection = ownObject(
921
+ settings.storageConnections,
922
+ connectionName
923
+ );
924
+ if (!connection) {
925
+ throw new Error(
926
+ `Invalid pi-sync settings: sync setup \u201C${selectedName}\u201D references missing storage connection \u201C${connectionName}\u201D.`
927
+ );
1179
928
  }
929
+ return resolveSyncConfig(
930
+ selectedName,
931
+ setup,
932
+ connectionName,
933
+ connection,
934
+ settings.onSwitch,
935
+ settings.skipSecretScan ?? false
936
+ );
937
+ }
938
+ async function loadPartialConfig(setupName) {
939
+ const config = await loadConfig(setupName);
940
+ return {
941
+ setupName: config.setupName,
942
+ ...storageReviewFromConfig(config),
943
+ include: [...config.include],
944
+ automatic: config.automatic,
945
+ onSwitch: config.onSwitch
946
+ };
947
+ }
948
+ function syncSetupStorageReview(setupName, setup, connectionName, connection) {
949
+ return storageReviewFromConfig(
950
+ resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false)
951
+ );
952
+ }
953
+ function syncSetupReviewIdentity(setupName, setup, connectionName, connection) {
954
+ return syncConfigReviewIdentity(
955
+ resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false)
956
+ );
957
+ }
958
+ function syncConfigReviewIdentity(config) {
959
+ return JSON.stringify([
960
+ config.setupName,
961
+ config.connectionName,
962
+ backendIdentityCoordinates(config),
963
+ config.include,
964
+ config.automatic
965
+ ]);
1180
966
  }
1181
- async function writeJson(filePath, value) {
1182
- await fs2.mkdir(path2.dirname(filePath), { recursive: true });
1183
- const temp = `${filePath}.${process.pid}.${randomUUID2()}.tmp`;
1184
- await fs2.writeFile(temp, `${JSON.stringify(value, null, " ")}
1185
- `, { mode: 384 });
1186
- if (process.platform !== "win32") await fs2.chmod(temp, 384);
1187
- await fs2.rename(temp, filePath);
967
+ function syncConfigReviewFingerprint(config) {
968
+ return createHash("sha256").update(syncConfigReviewIdentity(config)).digest("hex");
969
+ }
970
+ function syncCheckConfigFingerprint(config) {
971
+ return createHash("sha256").update(JSON.stringify(config)).digest("hex");
972
+ }
973
+ function storageReviewFromConfig(config) {
974
+ return {
975
+ connectionName: config.connectionName,
976
+ storageKind: config.backend.type,
977
+ storagePath: config.storagePath,
978
+ ...config.backend.type === "s3" ? { bucket: config.backend.destination.bucket } : config.backend.type === "git" ? { branch: config.backend.destination.branch } : {}
979
+ };
980
+ }
981
+ function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitch, skipSecretScan) {
982
+ const storagePath = normalizeStoragePath(setup.storage.path);
983
+ const namespace = storagePath === "./" ? "root" : storagePath.slice(storagePath.lastIndexOf("/") + 1);
984
+ const include = normalizeSyncInclude(setup.sync.include);
985
+ const common = {
986
+ setupName,
987
+ connectionName,
988
+ storagePath,
989
+ snapshotIdentity: namespace,
990
+ include,
991
+ automatic: setup.sync.automatic,
992
+ onSwitch,
993
+ skipSecretScan
994
+ };
995
+ if (connection.type === "git") {
996
+ return {
997
+ ...common,
998
+ backend: {
999
+ type: "git",
1000
+ profile: { kind: "git", remote: normalizeGitRemote(connection.remote) },
1001
+ destination: {
1002
+ branch: normalizeGitBranch(setup.storage.branch),
1003
+ directory: normalizeGitDirectory(storagePath),
1004
+ namespace
1005
+ }
1006
+ }
1007
+ };
1008
+ }
1009
+ if (connection.type === "webdav") {
1010
+ return {
1011
+ ...common,
1012
+ backend: {
1013
+ type: "webdav",
1014
+ profile: {
1015
+ kind: "webdav",
1016
+ url: normalizeWebDavUrl(connection.url),
1017
+ username: connection.credentials.username,
1018
+ password: connection.credentials.password
1019
+ },
1020
+ destination: { path: normalizeWebDavPath(storagePath), namespace }
1021
+ }
1022
+ };
1023
+ }
1024
+ return {
1025
+ ...common,
1026
+ backend: {
1027
+ type: "s3",
1028
+ profile: {
1029
+ kind: isCloudflareR2Endpoint(connection.endpoint) ? "r2" : "s3-compatible",
1030
+ endpoint: normalizeS3Endpoint(connection.endpoint),
1031
+ region: requiredString(connection.region, "S3 region"),
1032
+ accessKeyId: connection.credentials.accessKeyId,
1033
+ secretAccessKey: connection.credentials.secretAccessKey,
1034
+ sessionToken: optionalString(connection.credentials.sessionToken, "S3 session token")
1035
+ },
1036
+ destination: {
1037
+ bucket: normalizeS3Bucket(setup.storage.bucket),
1038
+ prefix: storagePath,
1039
+ namespace
1040
+ }
1041
+ }
1042
+ };
1188
1043
  }
1189
1044
  function sessionTokenWarnings(config) {
1190
1045
  if (!isCloudflareR2Endpoint(config.endpoint) || !config.sessionToken) return [];
@@ -1198,53 +1053,35 @@ function syncSessionsWarnings(config) {
1198
1053
  "sessions: included; Pi session JSONL can contain prompts, tool output, file paths, images, and secrets. Sync sessions only to storage you trust."
1199
1054
  ];
1200
1055
  }
1201
- function isCloudflareR2Endpoint(endpoint) {
1202
- const value = endpoint?.trim();
1203
- if (!value) return false;
1204
- try {
1205
- const hostname = new URL(value).hostname.toLowerCase();
1206
- return hostname === "r2.cloudflarestorage.com" || hostname.endsWith(".r2.cloudflarestorage.com");
1207
- } catch {
1208
- return false;
1209
- }
1210
- }
1211
- function hasControlCharacter2(value) {
1212
- return /[\u0000-\u001f\u007f-\u009f]/u.test(value);
1213
- }
1214
1056
 
1215
1057
  export {
1216
1058
  localConfigPath,
1217
1059
  activeLocalConfigPath,
1218
1060
  consumeLocalConfigMigrationNotice,
1219
1061
  createLocalConfigDocument,
1220
- normalizeWebDavUrl,
1221
- normalizeWebDavPath,
1222
- validateWebDavNamespace,
1223
- validateWebDavCredentials,
1224
- isMissingConfigError,
1225
- loadConfig,
1226
- loadPartialConfig,
1227
- syncSetupStorageReview,
1228
- syncSetupReviewIdentity,
1229
- syncConfigReviewIdentity,
1230
- syncConfigReviewFingerprint,
1231
- configuredSyncSetupNames,
1232
- loadOnSwitch,
1233
1062
  normalizeOnSwitch,
1234
1063
  effectiveSyncSetupRemoteIdentity,
1235
1064
  validateConfigName,
1236
- configuredSessionDir,
1237
- sessionDirForApply,
1238
- readStateForConfig,
1239
- writeStateForConfig,
1240
- agentDir,
1065
+ requiredString,
1066
+ normalizeStoragePath,
1067
+ normalizeS3Endpoint,
1068
+ normalizeS3Bucket,
1069
+ isCloudflareR2Endpoint,
1070
+ ownRecord,
1071
+ configuredSyncSetupNames,
1072
+ loadOnSwitch,
1241
1073
  localConfigTemplate,
1242
1074
  readLocalConfigObject,
1243
1075
  updateLocalConfig,
1244
- lockPath,
1245
- ensureStateDir,
1076
+ loadConfig,
1077
+ loadConfigForCheck,
1078
+ loadPartialConfig,
1079
+ syncSetupStorageReview,
1080
+ syncSetupReviewIdentity,
1081
+ syncConfigReviewIdentity,
1082
+ syncConfigReviewFingerprint,
1083
+ syncCheckConfigFingerprint,
1246
1084
  sessionTokenWarnings,
1247
- syncSessionsWarnings,
1248
- isCloudflareR2Endpoint
1085
+ syncSessionsWarnings
1249
1086
  };
1250
- //# sourceMappingURL=chunk-5T442OP3.ts.map
1087
+ //# sourceMappingURL=chunk-C7XY5NF5.ts.map