@narumitw/pi-sync 0.49.14 → 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 +134 -241
  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-RGJ6SOYR.ts → chunk-AUSDLD6F.ts} +4 -4
  9. package/dist/chunks/chunk-AUSDLD6F.ts.map +7 -0
  10. package/dist/chunks/{chunk-3NDD5QPM.ts → chunk-AXXS2BPA.ts} +9 -9
  11. package/dist/chunks/chunk-AXXS2BPA.ts.map +7 -0
  12. package/dist/chunks/{chunk-APZVBSM6.ts → chunk-C7XY5NF5.ts} +214 -365
  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-LJDSHNS3.ts → chunk-GWMHAQG4.ts} +4 -4
  21. package/dist/chunks/chunk-GWMHAQG4.ts.map +7 -0
  22. package/dist/chunks/{chunk-FR6CVXAL.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-4F3W4CSC.ts → chunk-LCRI3ABW.ts} +22 -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-EWX3TPLQ.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-BHJUISB4.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-LONPFFNP.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-5WJNWFTG.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-4UABOP44.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-3QZGO2EF.ts → s3-backend-3P33LQHU.ts} +96 -18
  69. package/dist/chunks/s3-backend-3P33LQHU.ts.map +7 -0
  70. package/dist/chunks/{setup-switch-X3W3FALZ.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-WBSV4MQG.ts → snapshot-HKEWCVK6.ts} +7 -6
  74. package/dist/chunks/sync-inspection-FEP4L5TR.ts +20 -0
  75. package/dist/chunks/{sync-operations-MPAFVVHA.ts → sync-operations-ONJ27QRL.ts} +381 -299
  76. package/dist/chunks/sync-operations-ONJ27QRL.ts.map +7 -0
  77. package/dist/chunks/{sync-state-36H7ULG2.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 +211 -0
  84. package/package.json +6 -8
  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} +40 -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} +41 -330
  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 -284
  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} +42 -11
  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-3NDD5QPM.ts.map +0 -7
  164. package/dist/chunks/chunk-4F3W4CSC.ts.map +0 -7
  165. package/dist/chunks/chunk-4U6AFLJB.ts.map +0 -7
  166. package/dist/chunks/chunk-5MQ76BK5.ts.map +0 -7
  167. package/dist/chunks/chunk-5WJNWFTG.ts.map +0 -7
  168. package/dist/chunks/chunk-APZVBSM6.ts.map +0 -7
  169. package/dist/chunks/chunk-BHJUISB4.ts.map +0 -7
  170. package/dist/chunks/chunk-EWX3TPLQ.ts.map +0 -7
  171. package/dist/chunks/chunk-F5QL2GPY.ts.map +0 -7
  172. package/dist/chunks/chunk-FR6CVXAL.ts.map +0 -7
  173. package/dist/chunks/chunk-HGV24P4T.ts.map +0 -7
  174. package/dist/chunks/chunk-JNOOSPEW.ts +0 -82
  175. package/dist/chunks/chunk-JNOOSPEW.ts.map +0 -7
  176. package/dist/chunks/chunk-LJDSHNS3.ts.map +0 -7
  177. package/dist/chunks/chunk-LONPFFNP.ts.map +0 -7
  178. package/dist/chunks/chunk-RGJ6SOYR.ts.map +0 -7
  179. package/dist/chunks/chunk-W64NEHXT.ts +0 -167
  180. package/dist/chunks/chunk-W64NEHXT.ts.map +0 -7
  181. package/dist/chunks/chunk-YQ6UW7IF.ts.map +0 -7
  182. package/dist/chunks/file-selection-4UABOP44.ts.map +0 -7
  183. package/dist/chunks/git-backend-E3UOUUOM.ts.map +0 -7
  184. package/dist/chunks/manager-result-dispatcher-RNRQA3PW.ts +0 -18
  185. package/dist/chunks/manager-ui-QO2P4V3Z.ts +0 -2757
  186. package/dist/chunks/manager-ui-QO2P4V3Z.ts.map +0 -7
  187. package/dist/chunks/remote-selection-ui-LHSAGKGA.ts +0 -17
  188. package/dist/chunks/s3-backend-3QZGO2EF.ts.map +0 -7
  189. package/dist/chunks/sync-operations-MPAFVVHA.ts.map +0 -7
  190. package/dist/chunks/webdav-backend-ZD5RHRIU.ts.map +0 -7
  191. package/src/config.ts +0 -910
  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-RNRQA3PW.ts.map → manager-result-dispatcher-U23ODD32.ts.map} +0 -0
  197. /package/dist/chunks/{remote-selection-ui-LHSAGKGA.ts.map → remote-selection-ui-ZE3TY67S.ts.map} +0 -0
  198. /package/dist/chunks/{setup-switch-X3W3FALZ.ts.map → setup-switch-IS2PEZ3J.ts.map} +0 -0
  199. /package/dist/chunks/{snapshot-WBSV4MQG.ts.map → snapshot-HKEWCVK6.ts.map} +0 -0
  200. /package/dist/chunks/{sync-state-36H7ULG2.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,162 +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(selectedName, setup, connectionName, connection, settings.onSwitch);
593
- }
594
- async function loadPartialConfig(setupName) {
595
- const config = await loadConfig(setupName);
596
- return {
597
- setupName: config.setupName,
598
- ...storageReviewFromConfig(config),
599
- include: [...config.include],
600
- automatic: config.automatic,
601
- onSwitch: config.onSwitch
602
- };
603
- }
604
- function syncSetupStorageReview(setupName, setup, connectionName, connection) {
605
- return storageReviewFromConfig(
606
- resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH)
607
- );
608
- }
609
- function syncSetupReviewIdentity(setupName, setup, connectionName, connection) {
610
- return syncConfigReviewIdentity(
611
- resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH)
612
- );
613
- }
614
- function syncConfigReviewIdentity(config) {
615
- return JSON.stringify([
616
- config.setupName,
617
- config.connectionName,
618
- backendIdentityCoordinates(config),
619
- config.include,
620
- config.automatic
621
- ]);
622
- }
623
- function syncConfigReviewFingerprint(config) {
624
- return createHash("sha256").update(syncConfigReviewIdentity(config)).digest("hex");
625
- }
626
- function storageReviewFromConfig(config) {
627
- return {
628
- connectionName: config.connectionName,
629
- storageKind: config.backend.type,
630
- storagePath: config.storagePath,
631
- ...config.backend.type === "s3" ? { bucket: config.backend.destination.bucket } : config.backend.type === "git" ? { branch: config.backend.destination.branch } : {}
632
- };
633
- }
634
- async function configuredSyncSetupNames() {
635
- const settings = await readLocalConfigObject();
636
- return settings ? Object.keys(settings.syncSetups).sort((left, right) => left.localeCompare(right)) : [];
637
- }
638
- async function loadOnSwitch() {
639
- return (await requireSettings()).onSwitch;
640
- }
641
504
  function normalizeOnSwitch(value) {
642
505
  if (value === "ask-before-pull" || value === "pull-after-switch" || value === "switch-only") {
643
506
  return value;
@@ -646,75 +509,6 @@ function normalizeOnSwitch(value) {
646
509
  'Invalid pi-sync settings: onSwitch must be "ask-before-pull", "pull-after-switch", or "switch-only".'
647
510
  );
648
511
  }
649
- function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitch) {
650
- const storagePath = normalizeStoragePath(setup.storage.path);
651
- const namespace = storagePath.slice(storagePath.lastIndexOf("/") + 1);
652
- const include = normalizeSyncInclude(setup.sync.include);
653
- const common = {
654
- setupName,
655
- connectionName,
656
- storagePath,
657
- snapshotIdentity: namespace,
658
- include,
659
- automatic: setup.sync.automatic,
660
- onSwitch
661
- };
662
- if (connection.type === "git") {
663
- return {
664
- ...common,
665
- backend: {
666
- type: "git",
667
- profile: { kind: "git", remote: normalizeGitRemote(connection.remote) },
668
- destination: {
669
- branch: normalizeGitBranch(setup.storage.branch),
670
- directory: normalizeGitDirectory(storagePath),
671
- namespace
672
- }
673
- }
674
- };
675
- }
676
- if (connection.type === "webdav") {
677
- return {
678
- ...common,
679
- backend: {
680
- type: "webdav",
681
- profile: {
682
- kind: "webdav",
683
- url: normalizeWebDavUrl(connection.url),
684
- username: connection.credentials.username,
685
- password: connection.credentials.password
686
- },
687
- destination: { path: normalizeWebDavPath(storagePath), namespace }
688
- }
689
- };
690
- }
691
- return {
692
- ...common,
693
- backend: {
694
- type: "s3",
695
- profile: {
696
- kind: isCloudflareR2Endpoint(connection.endpoint) ? "r2" : "s3-compatible",
697
- endpoint: normalizeS3Endpoint(connection.endpoint),
698
- region: requiredString(connection.region, "S3 region"),
699
- accessKeyId: connection.credentials.accessKeyId,
700
- secretAccessKey: connection.credentials.secretAccessKey,
701
- sessionToken: optionalString(connection.credentials.sessionToken, "S3 session token")
702
- },
703
- destination: {
704
- bucket: normalizeS3Bucket(setup.storage.bucket),
705
- prefix: storagePath,
706
- namespace
707
- }
708
- }
709
- };
710
- }
711
- async function requireSettings() {
712
- const settings = await readLocalConfigObject();
713
- if (!settings) {
714
- throw new Error(`Missing pi-sync settings. Use /sync setup or create ${localConfigPath()}.`);
715
- }
716
- return settings;
717
- }
718
512
  function validateSettingsDocument(value) {
719
513
  if (value.version !== 3) {
720
514
  throw new Error(
@@ -744,6 +538,9 @@ function validateSettingsDocument(value) {
744
538
  "top level"
745
539
  );
746
540
  normalizeOnSwitch(value.onSwitch);
541
+ if (value.skipSecretScan !== void 0 && typeof value.skipSecretScan !== "boolean") {
542
+ throw new Error("Invalid pi-sync settings: skipSecretScan must be boolean.");
543
+ }
747
544
  const storageConnections = requireNamedObjectMap(
748
545
  value.storageConnections,
749
546
  "storageConnections",
@@ -966,12 +763,12 @@ function requireRecord(value, field) {
966
763
  return value;
967
764
  }
968
765
  function validateConfigName(value, field) {
969
- 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)) {
970
767
  throw new Error(`Invalid pi-sync settings: invalid ${field} name.`);
971
768
  }
972
769
  }
973
770
  function requiredString(value, field) {
974
- if (typeof value !== "string" || !value.trim() || hasControlCharacter2(value)) {
771
+ if (typeof value !== "string" || !value.trim() || hasControlCharacter(value)) {
975
772
  throw new Error(`Invalid pi-sync settings: ${field} must be a non-empty string.`);
976
773
  }
977
774
  return value.trim();
@@ -991,21 +788,22 @@ function optionalCanonicalReference(value, field) {
991
788
  return normalized;
992
789
  }
993
790
  function requiredSecret(value, field) {
994
- if (typeof value !== "string" || !value || hasControlCharacter2(value)) {
791
+ if (typeof value !== "string" || !value || hasControlCharacter(value)) {
995
792
  throw new Error(`Invalid pi-sync settings: ${field} must be configured.`);
996
793
  }
997
794
  return value;
998
795
  }
999
796
  function optionalString(value, field) {
1000
797
  if (value === void 0) return void 0;
1001
- if (typeof value !== "string" || hasControlCharacter2(value)) {
798
+ if (typeof value !== "string" || hasControlCharacter(value)) {
1002
799
  throw new Error(`Invalid pi-sync settings: ${field} must be a string.`);
1003
800
  }
1004
801
  return value.trim() || void 0;
1005
802
  }
1006
803
  function normalizeStoragePath(value) {
804
+ if (value.trim() === "." || value.trim() === "./") return "./";
1007
805
  const normalized = value.trim().replace(/^\/+|\/+$/gu, "");
1008
- 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 === "..")) {
1009
807
  throw new Error("Invalid pi-sync settings: storage.path must be a safe relative path.");
1010
808
  }
1011
809
  return normalized;
@@ -1031,98 +829,46 @@ function normalizeS3Bucket(value) {
1031
829
  }
1032
830
  return bucket;
1033
831
  }
1034
- 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;
1035
838
  try {
1036
- const url = new URL(endpoint.trim());
1037
- url.hostname = url.hostname.toLowerCase();
1038
- url.pathname = url.pathname.replace(/\/+$/gu, "");
1039
- return url.toString().replace(/\/$/u, "");
839
+ const hostname = new URL(value).hostname.toLowerCase();
840
+ return hostname === "r2.cloudflarestorage.com" || hostname.endsWith(".r2.cloudflarestorage.com");
1040
841
  } catch {
1041
- return endpoint.trim();
842
+ return false;
1042
843
  }
1043
844
  }
1044
- async function configuredSessionDir() {
1045
- const settings = await readJsonIfExists(
1046
- path2.join(agentDir(), "settings.json")
1047
- );
1048
- return settings?.sessionDir ? expandHome(settings.sessionDir) : void 0;
1049
- }
1050
- async function sessionDirForApply(ctx, snapshot) {
1051
- const contextSessionDir = sessionDirFromContext(ctx);
1052
- const localSessionDir = await configuredSessionDir();
1053
- if (contextSessionDir && path2.resolve(contextSessionDir) !== path2.resolve(localSessionDir ?? "")) {
1054
- return contextSessionDir;
1055
- }
1056
- return sessionDirFromSnapshot(snapshot) ?? contextSessionDir;
845
+ function ownRecord(value) {
846
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1057
847
  }
1058
- function sessionDirFromSnapshot(snapshot) {
1059
- const settingsFile = snapshot.files.find((file) => file.path === "settings.json");
1060
- if (!settingsFile) return void 0;
1061
- try {
1062
- const settings = JSON.parse(
1063
- 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()}.`
1064
856
  );
1065
- return settings.sessionDir ? expandHome(settings.sessionDir) : void 0;
1066
- } catch {
1067
- return void 0;
1068
857
  }
858
+ return settings;
1069
859
  }
1070
- function decodeBase64Strict(value, filePath) {
1071
- if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value) || value.length % 4 !== 0) {
1072
- throw new Error(`Invalid base64 content in snapshot file: ${filePath}`);
1073
- }
1074
- return Buffer.from(value, "base64");
1075
- }
1076
- async function readStateForConfig(config) {
1077
- return await readJsonIfExists(statePathForConfig(config)) ?? {
1078
- version: STATE_VERSION,
1079
- profile: config.snapshotIdentity,
1080
- lastFileHashes: {}
1081
- };
860
+ async function configuredSyncSetupNames() {
861
+ const settings = await readLocalConfigObject();
862
+ return settings ? Object.keys(settings.syncSetups).sort((left, right) => left.localeCompare(right)) : [];
1082
863
  }
1083
- async function writeStateForConfig(config, state) {
1084
- await writeJson(statePathForConfig(config), state);
1085
- }
1086
- function statePathForConfig(config) {
1087
- const identity = backendIdentityCoordinates(config);
1088
- const hash = createHash("sha256").update(identity).digest("hex").slice(0, 16);
1089
- return path2.join(stateDir(), "setups", `${config.backend.type}-${hash}.state.json`);
1090
- }
1091
- function backendIdentityCoordinates(config) {
1092
- switch (config.backend.type) {
1093
- case "s3":
1094
- return JSON.stringify([
1095
- "s3",
1096
- normalizeEndpointIdentity(config.backend.profile.endpoint),
1097
- config.backend.destination.bucket,
1098
- config.storagePath
1099
- ]);
1100
- case "git":
1101
- return JSON.stringify([
1102
- "git",
1103
- normalizeGitRemoteIdentity(config.backend.profile.remote),
1104
- config.backend.destination.branch,
1105
- config.storagePath
1106
- ]);
1107
- case "webdav":
1108
- return JSON.stringify([
1109
- "webdav",
1110
- normalizeWebDavIdentityUrl(config.backend.profile.url),
1111
- config.backend.profile.username,
1112
- config.storagePath
1113
- ]);
1114
- }
1115
- }
1116
- function agentDir() {
1117
- return getAgentDir2();
1118
- }
1119
- function expandHome(value) {
1120
- return value === "~" || value.startsWith("~/") ? path2.join(os.homedir(), value.slice(2)) : value;
864
+ async function loadOnSwitch() {
865
+ return (await requireSettings()).onSwitch;
1121
866
  }
1122
867
  function localConfigTemplate() {
1123
868
  return {
1124
869
  version: 3,
1125
870
  onSwitch: DEFAULT_ON_SWITCH,
871
+ skipSecretScan: false,
1126
872
  storageConnections: {},
1127
873
  syncSetups: {}
1128
874
  };
@@ -1137,7 +883,6 @@ async function readLocalConfigDocument() {
1137
883
  async function readLocalConfigObject() {
1138
884
  return (await readLocalConfigDocument())?.parsed;
1139
885
  }
1140
- var configUpdateQueue = Promise.resolve();
1141
886
  function updateLocalConfig(update, signal) {
1142
887
  const operation = configUpdateQueue.then(() => {
1143
888
  signal?.throwIfAborted();
@@ -1152,27 +897,149 @@ function updateLocalConfig(update, signal) {
1152
897
  async function performLocalConfigUpdate(update, signal) {
1153
898
  return updateLocalConfigDocument(localConfigTemplate(), update, validateSettingsDocument, signal);
1154
899
  }
1155
- function lockPath() {
1156
- 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);
1157
905
  }
1158
- async function ensureStateDir() {
1159
- 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);
1160
911
  }
1161
- async function readJsonIfExists(filePath) {
1162
- try {
1163
- return JSON.parse(await fs2.readFile(filePath, "utf8"));
1164
- } catch (error) {
1165
- if (error.code === "ENOENT") return void 0;
1166
- 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
+ );
1167
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
+ );
1168
952
  }
1169
- async function writeJson(filePath, value) {
1170
- await fs2.mkdir(path2.dirname(filePath), { recursive: true });
1171
- const temp = `${filePath}.${process.pid}.${randomUUID2()}.tmp`;
1172
- await fs2.writeFile(temp, `${JSON.stringify(value, null, " ")}
1173
- `, { mode: 384 });
1174
- if (process.platform !== "win32") await fs2.chmod(temp, 384);
1175
- await fs2.rename(temp, filePath);
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
+ ]);
966
+ }
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
+ };
1176
1043
  }
1177
1044
  function sessionTokenWarnings(config) {
1178
1045
  if (!isCloudflareR2Endpoint(config.endpoint) || !config.sessionToken) return [];
@@ -1186,53 +1053,35 @@ function syncSessionsWarnings(config) {
1186
1053
  "sessions: included; Pi session JSONL can contain prompts, tool output, file paths, images, and secrets. Sync sessions only to storage you trust."
1187
1054
  ];
1188
1055
  }
1189
- function isCloudflareR2Endpoint(endpoint) {
1190
- const value = endpoint?.trim();
1191
- if (!value) return false;
1192
- try {
1193
- const hostname = new URL(value).hostname.toLowerCase();
1194
- return hostname === "r2.cloudflarestorage.com" || hostname.endsWith(".r2.cloudflarestorage.com");
1195
- } catch {
1196
- return false;
1197
- }
1198
- }
1199
- function hasControlCharacter2(value) {
1200
- return /[\u0000-\u001f\u007f-\u009f]/u.test(value);
1201
- }
1202
1056
 
1203
1057
  export {
1204
1058
  localConfigPath,
1205
1059
  activeLocalConfigPath,
1206
1060
  consumeLocalConfigMigrationNotice,
1207
1061
  createLocalConfigDocument,
1208
- normalizeWebDavUrl,
1209
- normalizeWebDavPath,
1210
- validateWebDavNamespace,
1211
- validateWebDavCredentials,
1212
- isMissingConfigError,
1213
- loadConfig,
1214
- loadPartialConfig,
1215
- syncSetupStorageReview,
1216
- syncSetupReviewIdentity,
1217
- syncConfigReviewIdentity,
1218
- syncConfigReviewFingerprint,
1219
- configuredSyncSetupNames,
1220
- loadOnSwitch,
1221
1062
  normalizeOnSwitch,
1222
1063
  effectiveSyncSetupRemoteIdentity,
1223
1064
  validateConfigName,
1224
- configuredSessionDir,
1225
- sessionDirForApply,
1226
- readStateForConfig,
1227
- writeStateForConfig,
1228
- agentDir,
1065
+ requiredString,
1066
+ normalizeStoragePath,
1067
+ normalizeS3Endpoint,
1068
+ normalizeS3Bucket,
1069
+ isCloudflareR2Endpoint,
1070
+ ownRecord,
1071
+ configuredSyncSetupNames,
1072
+ loadOnSwitch,
1229
1073
  localConfigTemplate,
1230
1074
  readLocalConfigObject,
1231
1075
  updateLocalConfig,
1232
- lockPath,
1233
- ensureStateDir,
1076
+ loadConfig,
1077
+ loadConfigForCheck,
1078
+ loadPartialConfig,
1079
+ syncSetupStorageReview,
1080
+ syncSetupReviewIdentity,
1081
+ syncConfigReviewIdentity,
1082
+ syncConfigReviewFingerprint,
1083
+ syncCheckConfigFingerprint,
1234
1084
  sessionTokenWarnings,
1235
- syncSessionsWarnings,
1236
- isCloudflareR2Endpoint
1085
+ syncSessionsWarnings
1237
1086
  };
1238
- //# sourceMappingURL=chunk-APZVBSM6.ts.map
1087
+ //# sourceMappingURL=chunk-C7XY5NF5.ts.map