@narumitw/pi-sync 0.50.3 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +4 -1
  2. package/dist/chunks/{chunk-6LP3MCR2.ts → chunk-2JZFHNLM.ts} +3 -5
  3. package/dist/chunks/chunk-2JZFHNLM.ts.map +7 -0
  4. package/dist/chunks/{chunk-LCRI3ABW.ts → chunk-2RZSQWBH.ts} +8 -19
  5. package/dist/chunks/chunk-2RZSQWBH.ts.map +7 -0
  6. package/dist/chunks/{chunk-IJ6FJFA5.ts → chunk-4AIHXRKE.ts} +11 -17
  7. package/dist/chunks/chunk-4AIHXRKE.ts.map +7 -0
  8. package/dist/chunks/{chunk-C7XY5NF5.ts → chunk-4MPIH46N.ts} +29 -51
  9. package/dist/chunks/chunk-4MPIH46N.ts.map +7 -0
  10. package/dist/chunks/{chunk-5B3FXBLE.ts → chunk-6546TS54.ts} +16 -20
  11. package/dist/chunks/chunk-6546TS54.ts.map +7 -0
  12. package/dist/chunks/{chunk-Q2TUM5X6.ts → chunk-6NT3YFGY.ts} +2 -2
  13. package/dist/chunks/chunk-6NT3YFGY.ts.map +7 -0
  14. package/dist/chunks/{chunk-2B2ELUF5.ts → chunk-7D5SF7P3.ts} +1 -1
  15. package/dist/chunks/chunk-7D5SF7P3.ts.map +7 -0
  16. package/dist/chunks/{chunk-YDWUZVPN.ts → chunk-7KHKWL3Q.ts} +28 -47
  17. package/dist/chunks/chunk-7KHKWL3Q.ts.map +7 -0
  18. package/dist/chunks/{chunk-ZI5MXMEX.ts → chunk-BVVP33U5.ts} +2 -4
  19. package/dist/chunks/chunk-BVVP33U5.ts.map +7 -0
  20. package/dist/chunks/{chunk-SZWBGPID.ts → chunk-DM4X4FIA.ts} +8 -13
  21. package/dist/chunks/chunk-DM4X4FIA.ts.map +7 -0
  22. package/dist/chunks/{chunk-SL64RLXR.ts → chunk-DZUSTGQV.ts} +8 -16
  23. package/dist/chunks/chunk-DZUSTGQV.ts.map +7 -0
  24. package/dist/chunks/{chunk-YHEMENKV.ts → chunk-E42GXD6D.ts} +13 -19
  25. package/dist/chunks/chunk-E42GXD6D.ts.map +7 -0
  26. package/dist/chunks/{chunk-Z4DQF6L3.ts → chunk-ECYCNZOE.ts} +1 -1
  27. package/dist/chunks/chunk-ECYCNZOE.ts.map +7 -0
  28. package/dist/chunks/{chunk-OEX4ABY6.ts → chunk-EEJPYTWE.ts} +11 -21
  29. package/dist/chunks/chunk-EEJPYTWE.ts.map +7 -0
  30. package/dist/chunks/{chunk-CPEHGXJV.ts → chunk-G4RUXRWZ.ts} +3 -5
  31. package/dist/chunks/chunk-G4RUXRWZ.ts.map +7 -0
  32. package/dist/chunks/{chunk-T2B5IU74.ts → chunk-HGJH56KR.ts} +4 -4
  33. package/dist/chunks/chunk-HGJH56KR.ts.map +7 -0
  34. package/dist/chunks/{chunk-JAAKLL6M.ts → chunk-IVXEDPAD.ts} +10 -34
  35. package/dist/chunks/chunk-IVXEDPAD.ts.map +7 -0
  36. package/dist/chunks/chunk-K3RIEL6J.ts +20 -0
  37. package/dist/chunks/chunk-K3RIEL6J.ts.map +7 -0
  38. package/dist/chunks/{chunk-LTBWGSBV.ts → chunk-MSSW6F3R.ts} +1 -1
  39. package/dist/chunks/chunk-MSSW6F3R.ts.map +7 -0
  40. package/dist/chunks/{chunk-T3HH3UEJ.ts → chunk-OVDTF5CH.ts} +4 -10
  41. package/dist/chunks/chunk-OVDTF5CH.ts.map +7 -0
  42. package/dist/chunks/{chunk-NJFUIVYL.ts → chunk-PWMWOJP4.ts} +1 -1
  43. package/dist/chunks/chunk-PWMWOJP4.ts.map +7 -0
  44. package/dist/chunks/{chunk-HUDQATCL.ts → chunk-Q32CQWPM.ts} +6 -8
  45. package/dist/chunks/chunk-Q32CQWPM.ts.map +7 -0
  46. package/dist/chunks/{chunk-ETTRJDNP.ts → chunk-QLSN7AGX.ts} +6 -13
  47. package/dist/chunks/chunk-QLSN7AGX.ts.map +7 -0
  48. package/dist/chunks/{chunk-THMF4BVL.ts → chunk-SWUXA3MH.ts} +1 -1
  49. package/dist/chunks/chunk-SWUXA3MH.ts.map +7 -0
  50. package/dist/chunks/{chunk-TEN2ZBJV.ts → chunk-T2SJD3N7.ts} +2 -4
  51. package/dist/chunks/chunk-T2SJD3N7.ts.map +7 -0
  52. package/dist/chunks/{chunk-AUSDLD6F.ts → chunk-T56RNRZJ.ts} +10 -26
  53. package/dist/chunks/chunk-T56RNRZJ.ts.map +7 -0
  54. package/dist/chunks/{chunk-DHVFS4QK.ts → chunk-UA7RXOIT.ts} +37 -90
  55. package/dist/chunks/chunk-UA7RXOIT.ts.map +7 -0
  56. package/dist/chunks/{chunk-D5X27LCA.ts → chunk-UETBKTDI.ts} +1 -1
  57. package/dist/chunks/chunk-UETBKTDI.ts.map +7 -0
  58. package/dist/chunks/{chunk-CHH26D4F.ts → chunk-YFADF5ZQ.ts} +7 -6
  59. package/dist/chunks/chunk-YFADF5ZQ.ts.map +7 -0
  60. package/dist/chunks/{chunk-CCSN3F47.ts → chunk-YPXGU5OR.ts} +4 -4
  61. package/dist/chunks/chunk-YPXGU5OR.ts.map +7 -0
  62. package/dist/chunks/{file-selection-FTTKAOYX.ts → file-selection-RAVHRYFG.ts} +14 -26
  63. package/dist/chunks/file-selection-RAVHRYFG.ts.map +7 -0
  64. package/dist/chunks/{git-backend-GMVS4PD4.ts → git-backend-IAANDL3K.ts} +41 -104
  65. package/dist/chunks/git-backend-IAANDL3K.ts.map +7 -0
  66. package/dist/chunks/manager-result-dispatcher-OTUMQM26.ts +23 -0
  67. package/dist/chunks/{manager-ui-IPAOAWZA.ts → manager-ui-763NEKJY.ts} +124 -251
  68. package/dist/chunks/manager-ui-763NEKJY.ts.map +7 -0
  69. package/dist/chunks/remote-selection-ui-TZTJF32Y.ts +22 -0
  70. package/dist/chunks/{s3-backend-66O4FGBH.ts → s3-backend-XGXUOPUN.ts} +23 -54
  71. package/dist/chunks/s3-backend-XGXUOPUN.ts.map +7 -0
  72. package/dist/chunks/{setup-switch-IS2PEZ3J.ts → setup-switch-5G2VNJHL.ts} +9 -9
  73. package/dist/chunks/{setup-wizard-JUJZYNNM.ts → setup-wizard-YIEWGX32.ts} +12 -12
  74. package/dist/chunks/setup-wizard-YIEWGX32.ts.map +7 -0
  75. package/dist/chunks/{snapshot-HKEWCVK6.ts → snapshot-JL5DFSXI.ts} +5 -5
  76. package/dist/chunks/sync-inspection-P3ENJLNF.ts +20 -0
  77. package/dist/chunks/{sync-operations-LBX3GKCM.ts → sync-operations-AR76EB6C.ts} +85 -201
  78. package/dist/chunks/sync-operations-AR76EB6C.ts.map +7 -0
  79. package/dist/chunks/{sync-state-Q2FTXTW6.ts → sync-state-VHJUZ4YG.ts} +6 -6
  80. package/dist/chunks/{webdav-backend-ECIKD7SO.ts → webdav-backend-3EGUONAT.ts} +20 -51
  81. package/dist/chunks/webdav-backend-3EGUONAT.ts.map +7 -0
  82. package/dist/index.ts +49 -74
  83. package/dist/index.ts.map +3 -3
  84. package/docs/settings.md +9 -0
  85. package/package.json +61 -62
  86. package/src/backends/backend-factory.ts +15 -15
  87. package/src/backends/backend-identity.ts +31 -31
  88. package/src/backends/backend-types.ts +42 -42
  89. package/src/backends/git/git-backend.ts +714 -788
  90. package/src/backends/git/git-config.ts +104 -111
  91. package/src/backends/git/git-runner.ts +230 -247
  92. package/src/backends/git/git-storage.ts +228 -245
  93. package/src/backends/s3/s3-backend.ts +408 -441
  94. package/src/backends/s3/s3-client.ts +288 -333
  95. package/src/backends/sync-backend.ts +65 -65
  96. package/src/backends/webdav/webdav-backend.ts +542 -589
  97. package/src/backends/webdav/webdav-client.ts +292 -316
  98. package/src/backends/webdav/webdav-config.ts +57 -68
  99. package/src/commands/command-execution.ts +270 -289
  100. package/src/commands/command-handler.ts +160 -163
  101. package/src/commands/command-types.ts +13 -13
  102. package/src/commands/command.ts +190 -196
  103. package/src/commands/setup-completions.ts +1 -1
  104. package/src/paths.ts +47 -47
  105. package/src/settings/config-errors.ts +4 -5
  106. package/src/settings/config-file.ts +473 -506
  107. package/src/settings/config.ts +171 -174
  108. package/src/settings/settings-management.ts +191 -216
  109. package/src/settings/settings-store.ts +63 -77
  110. package/src/settings/settings-types.ts +78 -75
  111. package/src/settings/settings-validation.ts +328 -364
  112. package/src/snapshot/session-paths.ts +41 -55
  113. package/src/snapshot/snapshot-apply.ts +197 -229
  114. package/src/snapshot/snapshot-codec.ts +29 -31
  115. package/src/snapshot/snapshot-paths.ts +4 -6
  116. package/src/snapshot/snapshot-transaction.ts +161 -170
  117. package/src/snapshot/snapshot-types.ts +24 -24
  118. package/src/snapshot/snapshot.ts +310 -343
  119. package/src/state/json-file.ts +11 -11
  120. package/src/state/lock.ts +226 -242
  121. package/src/state/lockfile-fs.ts +20 -20
  122. package/src/state/operation-availability.ts +36 -41
  123. package/src/state/state-directory.ts +171 -173
  124. package/src/state/state-types.ts +15 -15
  125. package/src/state/sync-state-store.ts +23 -23
  126. package/src/sync/automatic-sync.ts +75 -80
  127. package/src/sync/remote-snapshot.ts +52 -60
  128. package/src/sync/setup-switch.ts +99 -114
  129. package/src/sync/signals.ts +3 -5
  130. package/src/sync/startup-check.ts +97 -111
  131. package/src/sync/sync-decision.ts +76 -80
  132. package/src/sync/sync-error-guidance.ts +41 -48
  133. package/src/sync/sync-errors.ts +10 -10
  134. package/src/sync/sync-inspection.ts +29 -33
  135. package/src/sync/sync-loaders.ts +28 -36
  136. package/src/sync/sync-mutations.ts +510 -622
  137. package/src/sync/sync-operations.ts +7 -7
  138. package/src/sync/sync-policy.ts +296 -326
  139. package/src/sync/sync-queries.ts +175 -194
  140. package/src/sync/sync-state.ts +74 -100
  141. package/src/sync-extension.ts +89 -101
  142. package/src/sync.ts +26 -26
  143. package/src/types.ts +37 -37
  144. package/src/ui/automatic-sync-summary.ts +1 -3
  145. package/src/ui/cancellable-operation.ts +89 -101
  146. package/src/ui/file-selection.ts +250 -274
  147. package/src/ui/manager-attention.ts +51 -63
  148. package/src/ui/manager-recovery.ts +48 -57
  149. package/src/ui/manager-result-dispatcher.ts +62 -76
  150. package/src/ui/manager-state.ts +200 -232
  151. package/src/ui/manager-ui.ts +264 -362
  152. package/src/ui/remote-selection-ui.ts +547 -588
  153. package/src/ui/secret-input.ts +197 -205
  154. package/src/ui/settings-ui.ts +175 -172
  155. package/src/ui/setup/git-ui.ts +232 -261
  156. package/src/ui/setup/s3-credentials-ui.ts +58 -65
  157. package/src/ui/setup/s3-ui.ts +171 -195
  158. package/src/ui/setup/setup-actions.ts +91 -101
  159. package/src/ui/setup/setup-location-ui.ts +121 -137
  160. package/src/ui/setup/setup-name-ui.ts +27 -27
  161. package/src/ui/setup/setup-prompts.ts +65 -65
  162. package/src/ui/setup/setup-review.ts +58 -59
  163. package/src/ui/setup/setup-switcher.ts +156 -160
  164. package/src/ui/setup/setup-wizard.ts +28 -28
  165. package/src/ui/setup/text-input.ts +54 -58
  166. package/src/ui/setup/webdav-ui.ts +246 -293
  167. package/src/ui/storage-connections-ui.ts +317 -369
  168. package/src/ui/sync-attention.ts +148 -164
  169. package/src/ui/sync-format.ts +110 -121
  170. package/src/ui/sync-resolution-ui.ts +147 -153
  171. package/src/ui/sync-setups-ui.ts +218 -238
  172. package/src/ui/sync-status.ts +17 -0
  173. package/src/ui/terminal-text.ts +2 -2
  174. package/dist/chunks/chunk-2B2ELUF5.ts.map +0 -7
  175. package/dist/chunks/chunk-5B3FXBLE.ts.map +0 -7
  176. package/dist/chunks/chunk-6LP3MCR2.ts.map +0 -7
  177. package/dist/chunks/chunk-AUSDLD6F.ts.map +0 -7
  178. package/dist/chunks/chunk-C7XY5NF5.ts.map +0 -7
  179. package/dist/chunks/chunk-CCSN3F47.ts.map +0 -7
  180. package/dist/chunks/chunk-CHH26D4F.ts.map +0 -7
  181. package/dist/chunks/chunk-CPEHGXJV.ts.map +0 -7
  182. package/dist/chunks/chunk-D5X27LCA.ts.map +0 -7
  183. package/dist/chunks/chunk-DHVFS4QK.ts.map +0 -7
  184. package/dist/chunks/chunk-ETTRJDNP.ts.map +0 -7
  185. package/dist/chunks/chunk-HUDQATCL.ts.map +0 -7
  186. package/dist/chunks/chunk-IJ6FJFA5.ts.map +0 -7
  187. package/dist/chunks/chunk-JAAKLL6M.ts.map +0 -7
  188. package/dist/chunks/chunk-LCRI3ABW.ts.map +0 -7
  189. package/dist/chunks/chunk-LTBWGSBV.ts.map +0 -7
  190. package/dist/chunks/chunk-NJFUIVYL.ts.map +0 -7
  191. package/dist/chunks/chunk-OEX4ABY6.ts.map +0 -7
  192. package/dist/chunks/chunk-Q2TUM5X6.ts.map +0 -7
  193. package/dist/chunks/chunk-SL64RLXR.ts.map +0 -7
  194. package/dist/chunks/chunk-SZWBGPID.ts.map +0 -7
  195. package/dist/chunks/chunk-T2B5IU74.ts.map +0 -7
  196. package/dist/chunks/chunk-T3HH3UEJ.ts.map +0 -7
  197. package/dist/chunks/chunk-TEN2ZBJV.ts.map +0 -7
  198. package/dist/chunks/chunk-THMF4BVL.ts.map +0 -7
  199. package/dist/chunks/chunk-YDWUZVPN.ts.map +0 -7
  200. package/dist/chunks/chunk-YHEMENKV.ts.map +0 -7
  201. package/dist/chunks/chunk-Z4DQF6L3.ts.map +0 -7
  202. package/dist/chunks/chunk-ZI5MXMEX.ts.map +0 -7
  203. package/dist/chunks/file-selection-FTTKAOYX.ts.map +0 -7
  204. package/dist/chunks/git-backend-GMVS4PD4.ts.map +0 -7
  205. package/dist/chunks/manager-result-dispatcher-ASKGOSW5.ts +0 -22
  206. package/dist/chunks/manager-ui-IPAOAWZA.ts.map +0 -7
  207. package/dist/chunks/remote-selection-ui-QDNR7WEI.ts +0 -21
  208. package/dist/chunks/s3-backend-66O4FGBH.ts.map +0 -7
  209. package/dist/chunks/setup-wizard-JUJZYNNM.ts.map +0 -7
  210. package/dist/chunks/sync-inspection-CST2KL65.ts +0 -20
  211. package/dist/chunks/sync-operations-LBX3GKCM.ts.map +0 -7
  212. package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +0 -7
  213. /package/dist/chunks/{manager-result-dispatcher-ASKGOSW5.ts.map → manager-result-dispatcher-OTUMQM26.ts.map} +0 -0
  214. /package/dist/chunks/{remote-selection-ui-QDNR7WEI.ts.map → remote-selection-ui-TZTJF32Y.ts.map} +0 -0
  215. /package/dist/chunks/{setup-switch-IS2PEZ3J.ts.map → setup-switch-5G2VNJHL.ts.map} +0 -0
  216. /package/dist/chunks/{snapshot-HKEWCVK6.ts.map → snapshot-JL5DFSXI.ts.map} +0 -0
  217. /package/dist/chunks/{sync-inspection-CST2KL65.ts.map → sync-inspection-P3ENJLNF.ts.map} +0 -0
  218. /package/dist/chunks/{sync-state-Q2FTXTW6.ts.map → sync-state-VHJUZ4YG.ts.map} +0 -0
@@ -1,229 +1,221 @@
1
1
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
  import {
3
- CURSOR_MARKER,
4
- decodeKittyPrintable,
5
- type Focusable,
6
- Key,
7
- type KeybindingsManager,
8
- matchesKey,
9
- Text,
10
- truncateToWidth,
3
+ CURSOR_MARKER,
4
+ decodeKittyPrintable,
5
+ type Focusable,
6
+ Key,
7
+ type KeybindingsManager,
8
+ matchesKey,
9
+ Text,
10
+ truncateToWidth,
11
11
  } from "@earendil-works/pi-tui";
12
12
  import { formatInteractionHints, runCustomInteraction } from "@narumitw/pi-tui-kit";
13
13
 
14
14
  const MASK = "•";
15
15
 
16
16
  export async function promptSecret(
17
- ctx: ExtensionCommandContext,
18
- title: string,
19
- options: { required?: boolean; signal?: AbortSignal } = { required: true },
17
+ ctx: ExtensionCommandContext,
18
+ title: string,
19
+ options: { required?: boolean; signal?: AbortSignal } = { required: true },
20
20
  ) {
21
- if (ctx.mode !== "tui" || options.signal?.aborted) return undefined;
22
- const result = await runCustomInteraction<string | undefined>(ctx, {
23
- signal: options.signal,
24
- isCurrent: () => !options.signal?.aborted,
25
- create: ({ tui, theme, keybindings, complete }) => {
26
- const heading = new Text("", 0, 0);
27
- const hint = new Text("", 0, 0);
28
- const interactionHint = formatInteractionHints(keybindings, [
29
- {
30
- keys: keybindings.getKeys("tui.input.submit").filter((key) => !hasControlCharacter(key)),
31
- label: "continue",
32
- },
33
- {
34
- keys: [
35
- ...keybindings.getKeys("tui.select.cancel").filter((key) => !hasControlCharacter(key)),
36
- "ctrl+c",
37
- ],
38
- label: "cancel",
39
- },
40
- ]);
41
- const applyTheme = () => {
42
- heading.setText(theme.fg("accent", theme.bold(title)));
43
- hint.setText(theme.fg("dim", `${interactionHint} • Input is hidden`));
44
- };
45
- applyTheme();
46
- const input = new MaskedInput(keybindings);
47
- const component: Focusable & {
48
- render(width: number): string[];
49
- invalidate(): void;
50
- handleInput(data: string): void;
51
- dispose(): void;
52
- } = {
53
- get focused() {
54
- return input.focused;
55
- },
56
- set focused(focused: boolean) {
57
- input.focused = focused;
58
- },
59
- render(width: number) {
60
- const safeWidth = Math.max(1, width);
61
- return [
62
- ...heading.render(safeWidth),
63
- ...input.render(safeWidth),
64
- ...hint.render(safeWidth),
65
- ].map((line) => truncateToWidth(line, safeWidth));
66
- },
67
- invalidate() {
68
- applyTheme();
69
- heading.invalidate();
70
- input.invalidate();
71
- hint.invalidate();
72
- },
73
- handleInput(data: string) {
74
- if (matchesKey(data, Key.ctrl("c"))) {
75
- complete(undefined);
76
- } else if (input.isPasting || data.includes("\u001b[200~")) {
77
- input.handleInput(data);
78
- } else if (keybindings.matches(data, "tui.select.cancel")) {
79
- complete(undefined);
80
- } else if (keybindings.matches(data, "tui.input.submit")) {
81
- if (options.required !== false && input.getValue().length === 0) {
82
- ctx.ui.notify(`${title} is required. Enter a value, or cancel.`, "warning");
83
- } else if (hasControlCharacter(input.getValue())) {
84
- ctx.ui.notify(
85
- `${title} contains control characters. Remove them or re-enter the value, then continue.`,
86
- "warning",
87
- );
88
- } else complete(input.getValue());
89
- } else input.handleInput(data);
90
- tui.requestRender();
91
- },
92
- dispose() {
93
- input.clear();
94
- },
95
- };
96
- return component;
97
- },
98
- });
99
- if (result.kind === "error") throw result.error;
100
- if (result.kind !== "completed" || result.value === undefined) return undefined;
101
- const value = result.value;
102
- if (options.required !== false && value.length === 0) {
103
- ctx.ui.notify(`${title} is required.`, "warning");
104
- return undefined;
105
- }
106
- return value;
21
+ if (ctx.mode !== "tui" || options.signal?.aborted) return undefined;
22
+ const result = await runCustomInteraction<string | undefined>(ctx, {
23
+ signal: options.signal,
24
+ isCurrent: () => !options.signal?.aborted,
25
+ create: ({ tui, theme, keybindings, complete }) => {
26
+ const heading = new Text("", 0, 0);
27
+ const hint = new Text("", 0, 0);
28
+ const interactionHint = formatInteractionHints(keybindings, [
29
+ {
30
+ keys: keybindings.getKeys("tui.input.submit").filter((key) => !hasControlCharacter(key)),
31
+ label: "continue",
32
+ },
33
+ {
34
+ keys: [...keybindings.getKeys("tui.select.cancel").filter((key) => !hasControlCharacter(key)), "ctrl+c"],
35
+ label: "cancel",
36
+ },
37
+ ]);
38
+ const applyTheme = () => {
39
+ heading.setText(theme.fg("accent", theme.bold(title)));
40
+ hint.setText(theme.fg("dim", `${interactionHint} • Input is hidden`));
41
+ };
42
+ applyTheme();
43
+ const input = new MaskedInput(keybindings);
44
+ const component: Focusable & {
45
+ render(width: number): string[];
46
+ invalidate(): void;
47
+ handleInput(data: string): void;
48
+ dispose(): void;
49
+ } = {
50
+ get focused() {
51
+ return input.focused;
52
+ },
53
+ set focused(focused: boolean) {
54
+ input.focused = focused;
55
+ },
56
+ render(width: number) {
57
+ const safeWidth = Math.max(1, width);
58
+ return [...heading.render(safeWidth), ...input.render(safeWidth), ...hint.render(safeWidth)].map((line) =>
59
+ truncateToWidth(line, safeWidth),
60
+ );
61
+ },
62
+ invalidate() {
63
+ applyTheme();
64
+ heading.invalidate();
65
+ input.invalidate();
66
+ hint.invalidate();
67
+ },
68
+ handleInput(data: string) {
69
+ if (matchesKey(data, Key.ctrl("c"))) {
70
+ complete(undefined);
71
+ } else if (input.isPasting || data.includes("\u001b[200~")) {
72
+ input.handleInput(data);
73
+ } else if (keybindings.matches(data, "tui.select.cancel")) {
74
+ complete(undefined);
75
+ } else if (keybindings.matches(data, "tui.input.submit")) {
76
+ if (options.required !== false && input.getValue().length === 0) {
77
+ ctx.ui.notify(`${title} is required. Enter a value, or cancel.`, "warning");
78
+ } else if (hasControlCharacter(input.getValue())) {
79
+ ctx.ui.notify(
80
+ `${title} contains control characters. Remove them or re-enter the value, then continue.`,
81
+ "warning",
82
+ );
83
+ } else complete(input.getValue());
84
+ } else input.handleInput(data);
85
+ tui.requestRender();
86
+ },
87
+ dispose() {
88
+ input.clear();
89
+ },
90
+ };
91
+ return component;
92
+ },
93
+ });
94
+ if (result.kind === "error") throw result.error;
95
+ if (result.kind !== "completed" || result.value === undefined) return undefined;
96
+ const value = result.value;
97
+ if (options.required !== false && value.length === 0) {
98
+ ctx.ui.notify(`${title} is required.`, "warning");
99
+ return undefined;
100
+ }
101
+ return value;
107
102
  }
108
103
 
109
104
  class MaskedInput implements Focusable {
110
- focused = false;
111
- private value: string[] = [];
112
- private cursor = 0;
113
- private paste = "";
114
- private pasting = false;
105
+ focused = false;
106
+ private value: string[] = [];
107
+ private cursor = 0;
108
+ private paste = "";
109
+ private pasting = false;
115
110
 
116
- constructor(private readonly keybindings: KeybindingsManager) {}
111
+ constructor(private readonly keybindings: KeybindingsManager) {}
117
112
 
118
- get isPasting() {
119
- return this.pasting;
120
- }
113
+ get isPasting() {
114
+ return this.pasting;
115
+ }
121
116
 
122
- getValue() {
123
- return this.value.join("");
124
- }
117
+ getValue() {
118
+ return this.value.join("");
119
+ }
125
120
 
126
- handleInput(data: string) {
127
- if (data.includes("\u001b[200~")) {
128
- this.pasting = true;
129
- this.paste = "";
130
- data = data.replace("\u001b[200~", "");
131
- }
132
- if (this.pasting) {
133
- this.paste += data;
134
- const end = this.paste.indexOf("\u001b[201~");
135
- if (end < 0) return;
136
- const pasted = this.paste
137
- .slice(0, end)
138
- .replace(/[\r\n]/gu, "")
139
- .replace(/\t/gu, " ");
140
- this.insert(pasted);
141
- const remaining = this.paste.slice(end + 6);
142
- this.paste = "";
143
- this.pasting = false;
144
- if (remaining) this.handleInput(remaining);
145
- return;
146
- }
147
- if (this.keybindings.matches(data, "tui.editor.deleteCharBackward")) {
148
- if (this.cursor > 0) this.value.splice(--this.cursor, 1);
149
- return;
150
- }
151
- if (this.keybindings.matches(data, "tui.editor.deleteCharForward")) {
152
- if (this.cursor < this.value.length) this.value.splice(this.cursor, 1);
153
- return;
154
- }
155
- if (this.keybindings.matches(data, "tui.editor.cursorLeft")) {
156
- this.cursor = Math.max(0, this.cursor - 1);
157
- return;
158
- }
159
- if (this.keybindings.matches(data, "tui.editor.cursorRight")) {
160
- this.cursor = Math.min(this.value.length, this.cursor + 1);
161
- return;
162
- }
163
- if (this.keybindings.matches(data, "tui.editor.cursorLineStart")) {
164
- this.cursor = 0;
165
- return;
166
- }
167
- if (this.keybindings.matches(data, "tui.editor.cursorLineEnd")) {
168
- this.cursor = this.value.length;
169
- return;
170
- }
171
- if (this.keybindings.matches(data, "tui.editor.deleteToLineStart")) {
172
- this.value.splice(0, this.cursor);
173
- this.cursor = 0;
174
- return;
175
- }
176
- if (this.keybindings.matches(data, "tui.editor.deleteToLineEnd")) {
177
- this.value.splice(this.cursor);
178
- return;
179
- }
180
- const printable = decodeKittyPrintable(data) ?? data;
181
- if (!hasControlCharacter(printable)) this.insert(printable);
182
- }
121
+ handleInput(data: string) {
122
+ if (data.includes("\u001b[200~")) {
123
+ this.pasting = true;
124
+ this.paste = "";
125
+ data = data.replace("\u001b[200~", "");
126
+ }
127
+ if (this.pasting) {
128
+ this.paste += data;
129
+ const end = this.paste.indexOf("\u001b[201~");
130
+ if (end < 0) return;
131
+ const pasted = this.paste
132
+ .slice(0, end)
133
+ .replace(/[\r\n]/gu, "")
134
+ .replace(/\t/gu, " ");
135
+ this.insert(pasted);
136
+ const remaining = this.paste.slice(end + 6);
137
+ this.paste = "";
138
+ this.pasting = false;
139
+ if (remaining) this.handleInput(remaining);
140
+ return;
141
+ }
142
+ if (this.keybindings.matches(data, "tui.editor.deleteCharBackward")) {
143
+ if (this.cursor > 0) this.value.splice(--this.cursor, 1);
144
+ return;
145
+ }
146
+ if (this.keybindings.matches(data, "tui.editor.deleteCharForward")) {
147
+ if (this.cursor < this.value.length) this.value.splice(this.cursor, 1);
148
+ return;
149
+ }
150
+ if (this.keybindings.matches(data, "tui.editor.cursorLeft")) {
151
+ this.cursor = Math.max(0, this.cursor - 1);
152
+ return;
153
+ }
154
+ if (this.keybindings.matches(data, "tui.editor.cursorRight")) {
155
+ this.cursor = Math.min(this.value.length, this.cursor + 1);
156
+ return;
157
+ }
158
+ if (this.keybindings.matches(data, "tui.editor.cursorLineStart")) {
159
+ this.cursor = 0;
160
+ return;
161
+ }
162
+ if (this.keybindings.matches(data, "tui.editor.cursorLineEnd")) {
163
+ this.cursor = this.value.length;
164
+ return;
165
+ }
166
+ if (this.keybindings.matches(data, "tui.editor.deleteToLineStart")) {
167
+ this.value.splice(0, this.cursor);
168
+ this.cursor = 0;
169
+ return;
170
+ }
171
+ if (this.keybindings.matches(data, "tui.editor.deleteToLineEnd")) {
172
+ this.value.splice(this.cursor);
173
+ return;
174
+ }
175
+ const printable = decodeKittyPrintable(data) ?? data;
176
+ if (!hasControlCharacter(printable)) this.insert(printable);
177
+ }
183
178
 
184
- render(width: number) {
185
- const prompt = "> ";
186
- const available = width - prompt.length;
187
- if (available <= 0) return [truncateToWidth(prompt, Math.max(1, width))];
188
- const contentWidth = Math.max(0, available - 1);
189
- let start = 0;
190
- if (this.value.length > contentWidth) {
191
- start = Math.max(
192
- 0,
193
- Math.min(this.cursor - Math.floor(contentWidth / 2), this.value.length - contentWidth),
194
- );
195
- }
196
- const end = Math.min(this.value.length, start + contentWidth);
197
- const visibleCursor = Math.max(0, Math.min(this.cursor - start, end - start));
198
- const masks = Array.from({ length: end - start }, () => MASK);
199
- const before = masks.slice(0, visibleCursor).join("");
200
- const atCursor = visibleCursor < masks.length ? MASK : " ";
201
- const after = masks.slice(visibleCursor + (visibleCursor < masks.length ? 1 : 0)).join("");
202
- const marker = this.focused ? CURSOR_MARKER : "";
203
- const line = `${prompt}${before}${marker}\u001b[7m${atCursor}\u001b[27m${after}`;
204
- return [truncateToWidth(line, width, "")];
205
- }
179
+ render(width: number) {
180
+ const prompt = "> ";
181
+ const available = width - prompt.length;
182
+ if (available <= 0) return [truncateToWidth(prompt, Math.max(1, width))];
183
+ const contentWidth = Math.max(0, available - 1);
184
+ let start = 0;
185
+ if (this.value.length > contentWidth) {
186
+ start = Math.max(0, Math.min(this.cursor - Math.floor(contentWidth / 2), this.value.length - contentWidth));
187
+ }
188
+ const end = Math.min(this.value.length, start + contentWidth);
189
+ const visibleCursor = Math.max(0, Math.min(this.cursor - start, end - start));
190
+ const masks = Array.from({ length: end - start }, () => MASK);
191
+ const before = masks.slice(0, visibleCursor).join("");
192
+ const atCursor = visibleCursor < masks.length ? MASK : " ";
193
+ const after = masks.slice(visibleCursor + (visibleCursor < masks.length ? 1 : 0)).join("");
194
+ const marker = this.focused ? CURSOR_MARKER : "";
195
+ const line = `${prompt}${before}${marker}\u001b[7m${atCursor}\u001b[27m${after}`;
196
+ return [truncateToWidth(line, width, "")];
197
+ }
206
198
 
207
- invalidate() {}
199
+ invalidate() {}
208
200
 
209
- clear() {
210
- this.value.fill("");
211
- this.value = [];
212
- this.paste = "";
213
- this.cursor = 0;
214
- this.pasting = false;
215
- }
201
+ clear() {
202
+ this.value.fill("");
203
+ this.value = [];
204
+ this.paste = "";
205
+ this.cursor = 0;
206
+ this.pasting = false;
207
+ }
216
208
 
217
- private insert(value: string) {
218
- const characters = Array.from(value);
219
- this.value.splice(this.cursor, 0, ...characters);
220
- this.cursor += characters.length;
221
- }
209
+ private insert(value: string) {
210
+ const characters = Array.from(value);
211
+ this.value.splice(this.cursor, 0, ...characters);
212
+ this.cursor += characters.length;
213
+ }
222
214
  }
223
215
 
224
216
  function hasControlCharacter(value: string) {
225
- return [...value].some((character) => {
226
- const code = character.codePointAt(0) ?? 0;
227
- return code < 0x20 || (code >= 0x7f && code <= 0x9f);
228
- });
217
+ return [...value].some((character) => {
218
+ const code = character.codePointAt(0) ?? 0;
219
+ return code < 0x20 || (code >= 0x7f && code <= 0x9f);
220
+ });
229
221
  }