silvery 0.17.0 → 0.17.2
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.
- package/dist/UPNG-AVSMjiFE.mjs +5076 -0
- package/dist/UPNG-AVSMjiFE.mjs.map +1 -0
- package/dist/__vite-browser-external-2447137e-D3GdsvS_.mjs +6 -0
- package/dist/__vite-browser-external-2447137e-D3GdsvS_.mjs.map +1 -0
- package/dist/animation-C_PTO0uH.mjs +304 -0
- package/dist/animation-C_PTO0uH.mjs.map +1 -0
- package/dist/ansi-CXLE_pt1.mjs +71 -0
- package/dist/ansi-CXLE_pt1.mjs.map +1 -0
- package/dist/ansi-zmNzgkPB.d.mts +49 -0
- package/dist/ansi-zmNzgkPB.d.mts.map +1 -0
- package/dist/apng-DCWY913R.mjs +3 -0
- package/dist/apng-ENBAJk-H.mjs +70 -0
- package/dist/apng-ENBAJk-H.mjs.map +1 -0
- package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
- package/dist/backend-CkIkIHR-.mjs +13396 -0
- package/dist/backend-CkIkIHR-.mjs.map +1 -0
- package/dist/backends-CkvbG3js.mjs +1181 -0
- package/dist/backends-CkvbG3js.mjs.map +1 -0
- package/dist/backends-CyJqNLeK.mjs +3 -0
- package/dist/chunk-BSw8zbkd.mjs +37 -0
- package/dist/cli-B-k7Bm56.mjs +4 -0
- package/dist/context-QreF3UHr.mjs +64 -0
- package/dist/context-QreF3UHr.mjs.map +1 -0
- package/dist/derive-D7bFJdfU.d.mts +28 -0
- package/dist/derive-D7bFJdfU.d.mts.map +1 -0
- package/dist/devtools-CscuKaDK.mjs +89 -0
- package/dist/devtools-CscuKaDK.mjs.map +1 -0
- package/dist/devtools-D4oGc6LY.mjs +2 -0
- package/dist/eta-DLiVPaSD.mjs +110 -0
- package/dist/eta-DLiVPaSD.mjs.map +1 -0
- package/dist/flexily-zero-adapter-DmG4Ge8t.mjs +3376 -0
- package/dist/flexily-zero-adapter-DmG4Ge8t.mjs.map +1 -0
- package/dist/flexily-zero-adapter-GHwEW11s.mjs +2 -0
- package/dist/gif-BaJNREpP.mjs +3 -0
- package/dist/gif-Bp6fIyN3.mjs +73 -0
- package/dist/gif-Bp6fIyN3.mjs.map +1 -0
- package/dist/gifenc-GiVCZ9-3.mjs +730 -0
- package/dist/gifenc-GiVCZ9-3.mjs.map +1 -0
- package/dist/image-Dx7gYjkq.mjs +346 -0
- package/dist/image-Dx7gYjkq.mjs.map +1 -0
- package/dist/index-CBcSpGSM.d.mts +3416 -0
- package/dist/index-CBcSpGSM.d.mts.map +1 -0
- package/dist/index-DCVL3jHo.d.mts +634 -0
- package/dist/index-DCVL3jHo.d.mts.map +1 -0
- package/dist/index-p-wBs_wH.d.mts +175 -0
- package/dist/index-p-wBs_wH.d.mts.map +1 -0
- package/dist/index.d.mts +7296 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +9399 -0
- package/dist/index.mjs.map +1 -0
- package/dist/key-mapping-BsUHe_nk.mjs +3 -0
- package/dist/key-mapping-DsyfLEdC.mjs +132 -0
- package/dist/key-mapping-DsyfLEdC.mjs.map +1 -0
- package/dist/layout-engine-B3dsnVLU.mjs +50 -0
- package/dist/layout-engine-B3dsnVLU.mjs.map +1 -0
- package/dist/layout-engine-D_lSR4i9.mjs +2 -0
- package/dist/multi-progress-C0-rkn86.d.mts +180 -0
- package/dist/multi-progress-C0-rkn86.d.mts.map +1 -0
- package/dist/multi-progress-CQVB9lES.mjs +219 -0
- package/dist/multi-progress-CQVB9lES.mjs.map +1 -0
- package/dist/node-Dedx-6xF.mjs +1085 -0
- package/dist/node-Dedx-6xF.mjs.map +1 -0
- package/dist/pipeline-DDOPrjuY.mjs +4387 -0
- package/dist/pipeline-DDOPrjuY.mjs.map +1 -0
- package/dist/progress-bar-COPSBlT9.mjs +155 -0
- package/dist/progress-bar-COPSBlT9.mjs.map +1 -0
- package/dist/reconciler-2lp5VXK7.mjs +16506 -0
- package/dist/reconciler-2lp5VXK7.mjs.map +1 -0
- package/dist/render-string-BXvxTg5P.mjs +201 -0
- package/dist/render-string-BXvxTg5P.mjs.map +1 -0
- package/dist/render-string-hvfpVtoP.mjs +2 -0
- package/dist/resvg-js-V6oMi8CY.mjs +203 -0
- package/dist/resvg-js-V6oMi8CY.mjs.map +1 -0
- package/dist/runtime-BjDHNTxJ.mjs +8723 -0
- package/dist/runtime-BjDHNTxJ.mjs.map +1 -0
- package/dist/runtime.d.mts +2 -0
- package/dist/runtime.mjs +3 -0
- package/dist/spinner-Cgej6Vnb.d.mts +127 -0
- package/dist/spinner-Cgej6Vnb.d.mts.map +1 -0
- package/dist/spinner-DSByknyx.mjs +298 -0
- package/dist/spinner-DSByknyx.mjs.map +1 -0
- package/dist/src-9B5k0JmY.mjs +1629 -0
- package/dist/src-9B5k0JmY.mjs.map +1 -0
- package/dist/src-C9f3hiVG.mjs +3620 -0
- package/dist/src-C9f3hiVG.mjs.map +1 -0
- package/dist/src-fJVbhdn-.mjs +816 -0
- package/dist/src-fJVbhdn-.mjs.map +1 -0
- package/dist/theme.d.mts +115 -0
- package/dist/theme.d.mts.map +1 -0
- package/dist/theme.mjs +8 -0
- package/dist/theme.mjs.map +1 -0
- package/dist/types-Bhj5QkIQ.mjs +13 -0
- package/dist/types-Bhj5QkIQ.mjs.map +1 -0
- package/dist/types-CDgkE-Rw.d.mts +241 -0
- package/dist/types-CDgkE-Rw.d.mts.map +1 -0
- package/dist/ui/animation.d.mts +2 -0
- package/dist/ui/animation.mjs +2 -0
- package/dist/ui/ansi.d.mts +2 -0
- package/dist/ui/ansi.mjs +2 -0
- package/dist/ui/cli.d.mts +5 -0
- package/dist/ui/cli.mjs +7 -0
- package/dist/ui/display.d.mts +35 -0
- package/dist/ui/display.d.mts.map +1 -0
- package/dist/ui/display.mjs +123 -0
- package/dist/ui/display.mjs.map +1 -0
- package/dist/ui/image.d.mts +2 -0
- package/dist/ui/image.mjs +2 -0
- package/dist/ui/input.d.mts +184 -0
- package/dist/ui/input.d.mts.map +1 -0
- package/dist/ui/input.mjs +285 -0
- package/dist/ui/input.mjs.map +1 -0
- package/dist/ui/progress.d.mts +249 -0
- package/dist/ui/progress.d.mts.map +1 -0
- package/dist/ui/progress.mjs +858 -0
- package/dist/ui/progress.mjs.map +1 -0
- package/dist/ui/react.d.mts +280 -0
- package/dist/ui/react.d.mts.map +1 -0
- package/dist/ui/react.mjs +413 -0
- package/dist/ui/react.mjs.map +1 -0
- package/dist/ui/utils.d.mts +86 -0
- package/dist/ui/utils.d.mts.map +1 -0
- package/dist/ui/utils.mjs +2 -0
- package/dist/ui/wrappers.d.mts +3 -0
- package/dist/ui/wrappers.mjs +2 -0
- package/dist/ui.d.mts +6 -0
- package/dist/ui.mjs +7 -0
- package/dist/useLatest-BMIYXd6e.d.mts +154 -0
- package/dist/useLatest-BMIYXd6e.d.mts.map +1 -0
- package/dist/useLayout-BG2cGl15.mjs +139 -0
- package/dist/useLayout-BG2cGl15.mjs.map +1 -0
- package/dist/with-text-input-CmHf_9d6.d.mts +284 -0
- package/dist/with-text-input-CmHf_9d6.d.mts.map +1 -0
- package/dist/wrapper-Dqh0zi2W.mjs +3527 -0
- package/dist/wrapper-Dqh0zi2W.mjs.map +1 -0
- package/dist/wrappers-hhL8EQ_n.mjs +810 -0
- package/dist/wrappers-hhL8EQ_n.mjs.map +1 -0
- package/dist/yoga-adapter-BJ9SOhTY.mjs +245 -0
- package/dist/yoga-adapter-BJ9SOhTY.mjs.map +1 -0
- package/dist/yoga-adapter-Daq6-dw1.mjs +2 -0
- package/package.json +48 -75
- package/CHANGELOG.md +0 -319
- package/dist/chalk.js +0 -4
- package/dist/index.js +0 -270
- package/dist/ink.js +0 -142
- package/dist/runtime.js +0 -135
- package/dist/theme.js +0 -7
- package/dist/ui/animation.js +0 -3
- package/dist/ui/ansi.js +0 -3
- package/dist/ui/cli.js +0 -9
- package/dist/ui/display.js +0 -4
- package/dist/ui/image.js +0 -4
- package/dist/ui/input.js +0 -3
- package/dist/ui/progress.js +0 -9
- package/dist/ui/react.js +0 -4
- package/dist/ui/utils.js +0 -3
- package/dist/ui/wrappers.js +0 -15
- package/dist/ui.js +0 -18
- package/src/index.ts +0 -73
- package/src/runtime.ts +0 -4
- package/src/theme.ts +0 -4
- package/src/ui/animation.ts +0 -2
- package/src/ui/ansi.ts +0 -2
- package/src/ui/cli.ts +0 -3
- package/src/ui/display.ts +0 -2
- package/src/ui/image.ts +0 -2
- package/src/ui/input.ts +0 -2
- package/src/ui/progress.ts +0 -2
- package/src/ui/react.ts +0 -2
- package/src/ui/utils.ts +0 -2
- package/src/ui/wrappers.ts +0 -2
- package/src/ui.ts +0 -4
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
import { a as chalk, r as Spinner } from "./spinner-DSByknyx.mjs";
|
|
2
|
+
import { f as isTTY, i as CURSOR_HIDE, m as write, s as CURSOR_SHOW, u as cursorUp } from "./ansi-CXLE_pt1.mjs";
|
|
3
|
+
import { t as ProgressBar } from "./progress-bar-COPSBlT9.mjs";
|
|
4
|
+
//#region packages/ag-react/src/ui/wrappers/with-spinner.ts
|
|
5
|
+
/**
|
|
6
|
+
* Wrap a promise with an animated spinner
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* // Simple usage
|
|
11
|
+
* const data = await withSpinner(fetchData(), "Loading data...");
|
|
12
|
+
*
|
|
13
|
+
* // With options
|
|
14
|
+
* const result = await withSpinner(
|
|
15
|
+
* processFiles(),
|
|
16
|
+
* "Processing...",
|
|
17
|
+
* { style: "arc", clearOnComplete: true }
|
|
18
|
+
* );
|
|
19
|
+
*
|
|
20
|
+
* // With dynamic text
|
|
21
|
+
* const result = await withSpinner(
|
|
22
|
+
* longOperation(),
|
|
23
|
+
* (elapsed) => `Processing... (${elapsed}s)`
|
|
24
|
+
* );
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
async function withSpinner(promise, text, options = {}) {
|
|
28
|
+
const spinner = new Spinner({
|
|
29
|
+
text: typeof text === "string" ? text : text(0),
|
|
30
|
+
style: options.style,
|
|
31
|
+
color: options.color
|
|
32
|
+
});
|
|
33
|
+
let timer = null;
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
spinner.start();
|
|
36
|
+
if (typeof text === "function") timer = setInterval(() => {
|
|
37
|
+
spinner.currentText = text(Math.floor((Date.now() - startTime) / 1e3));
|
|
38
|
+
}, 1e3);
|
|
39
|
+
try {
|
|
40
|
+
const result = await (typeof promise === "function" ? promise() : promise);
|
|
41
|
+
if (timer) clearInterval(timer);
|
|
42
|
+
if (options.clearOnComplete) spinner.stop();
|
|
43
|
+
else spinner.succeed();
|
|
44
|
+
return result;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (timer) clearInterval(timer);
|
|
47
|
+
spinner.fail(error instanceof Error ? error.message : "Failed");
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Attach a spinner to a promise for manual control
|
|
53
|
+
* Returns [result, spinner] tuple for custom control
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const [promise, spinner] = attachSpinner(fetchData(), "Loading...");
|
|
58
|
+
* spinner.text = "Still loading...";
|
|
59
|
+
* const result = await promise;
|
|
60
|
+
* spinner.succeed("Loaded!");
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
function attachSpinner(promise, text, options = {}) {
|
|
64
|
+
const spinner = new Spinner({
|
|
65
|
+
text,
|
|
66
|
+
style: options.style,
|
|
67
|
+
color: options.color
|
|
68
|
+
});
|
|
69
|
+
spinner.start();
|
|
70
|
+
async function wrapPromise() {
|
|
71
|
+
try {
|
|
72
|
+
return await promise;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
spinner.fail(error instanceof Error ? error.message : "Failed");
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return [wrapPromise(), spinner];
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region packages/ag-react/src/ui/wrappers/with-progress.ts
|
|
82
|
+
/**
|
|
83
|
+
* Wrap a function that takes a progress callback
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* // Wrap existing km sync API
|
|
88
|
+
* const result = await withProgress(
|
|
89
|
+
* (onProgress) => manager.syncFromFs(onProgress),
|
|
90
|
+
* {
|
|
91
|
+
* phases: {
|
|
92
|
+
* scanning: "Scanning files",
|
|
93
|
+
* reconciling: "Reconciling changes",
|
|
94
|
+
* rules: "Evaluating rules"
|
|
95
|
+
* }
|
|
96
|
+
* }
|
|
97
|
+
* );
|
|
98
|
+
*
|
|
99
|
+
* // Simple usage without phases
|
|
100
|
+
* await withProgress((onProgress) => rebuildState(onProgress));
|
|
101
|
+
*
|
|
102
|
+
* // With custom format
|
|
103
|
+
* await withProgress(
|
|
104
|
+
* (p) => processFiles(p),
|
|
105
|
+
* { format: ":phase :bar :percent" }
|
|
106
|
+
* );
|
|
107
|
+
*
|
|
108
|
+
* // Show loading immediately (showAfter: 0) or after delay
|
|
109
|
+
* await withProgress(
|
|
110
|
+
* (p) => slowOperation(p),
|
|
111
|
+
* { showAfter: 1000, initialMessage: "Loading..." }
|
|
112
|
+
* );
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
async function withProgress(fn, options = {}) {
|
|
116
|
+
const stream = process.stdout;
|
|
117
|
+
const isTty = isTTY(stream);
|
|
118
|
+
const bar = new ProgressBar({
|
|
119
|
+
format: options.format ?? (options.phases ? ":phase [:bar] :current/:total" : "[:bar] :current/:total :percent"),
|
|
120
|
+
phases: options.phases ?? {},
|
|
121
|
+
hideCursor: true
|
|
122
|
+
});
|
|
123
|
+
let lastPhase = null;
|
|
124
|
+
let started = false;
|
|
125
|
+
const showAfter = options.showAfter ?? 1e3;
|
|
126
|
+
const initialMessage = options.initialMessage ?? "Loading...";
|
|
127
|
+
let spinner = null;
|
|
128
|
+
let spinnerTimerId = null;
|
|
129
|
+
if (isTty) write(CURSOR_HIDE, stream);
|
|
130
|
+
if (isTty && showAfter >= 0) spinnerTimerId = setTimeout(() => {
|
|
131
|
+
if (!started) {
|
|
132
|
+
spinner = new Spinner({ text: initialMessage });
|
|
133
|
+
spinner.start();
|
|
134
|
+
}
|
|
135
|
+
}, showAfter);
|
|
136
|
+
const onProgress = (info) => {
|
|
137
|
+
if (spinner) {
|
|
138
|
+
spinner.stop();
|
|
139
|
+
spinner = null;
|
|
140
|
+
}
|
|
141
|
+
if (spinnerTimerId !== null) {
|
|
142
|
+
clearTimeout(spinnerTimerId);
|
|
143
|
+
spinnerTimerId = null;
|
|
144
|
+
}
|
|
145
|
+
if (info.phase && info.phase !== lastPhase) {
|
|
146
|
+
if (lastPhase !== null && isTty) write("\n", stream);
|
|
147
|
+
lastPhase = info.phase;
|
|
148
|
+
if (!started) {
|
|
149
|
+
bar.start(info.current, info.total);
|
|
150
|
+
started = true;
|
|
151
|
+
}
|
|
152
|
+
bar.setPhase(info.phase, {
|
|
153
|
+
current: info.current,
|
|
154
|
+
total: info.total
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
if (!started) {
|
|
158
|
+
bar.start(info.current, info.total);
|
|
159
|
+
started = true;
|
|
160
|
+
}
|
|
161
|
+
bar.update(info.current);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
try {
|
|
165
|
+
const result = await fn(onProgress);
|
|
166
|
+
if (spinnerTimerId !== null) clearTimeout(spinnerTimerId);
|
|
167
|
+
const pendingSpinner = spinner;
|
|
168
|
+
if (pendingSpinner) pendingSpinner.stop();
|
|
169
|
+
if (started) bar.stop(options.clearOnComplete);
|
|
170
|
+
if (isTty) write(CURSOR_SHOW, stream);
|
|
171
|
+
return result;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (spinnerTimerId !== null) clearTimeout(spinnerTimerId);
|
|
174
|
+
const errorSpinner = spinner;
|
|
175
|
+
if (errorSpinner) errorSpinner.stop();
|
|
176
|
+
if (started) bar.stop();
|
|
177
|
+
if (isTty) write(CURSOR_SHOW, stream);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Create a progress callback that can be passed to existing APIs
|
|
183
|
+
* Returns [callback, complete] tuple
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* const [onProgress, complete] = createProgressCallback({
|
|
188
|
+
* phases: { scanning: "Scanning", reconciling: "Reconciling" }
|
|
189
|
+
* });
|
|
190
|
+
*
|
|
191
|
+
* const result = await manager.syncFromFs(onProgress);
|
|
192
|
+
* complete();
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
function createProgressCallback(options = {}) {
|
|
196
|
+
const stream = process.stdout;
|
|
197
|
+
const isTty = isTTY(stream);
|
|
198
|
+
const bar = new ProgressBar({
|
|
199
|
+
format: options.format ?? (options.phases ? ":phase [:bar] :current/:total" : "[:bar] :current/:total :percent"),
|
|
200
|
+
phases: options.phases ?? {},
|
|
201
|
+
hideCursor: true
|
|
202
|
+
});
|
|
203
|
+
let lastPhase = null;
|
|
204
|
+
let started = false;
|
|
205
|
+
if (isTty) write(CURSOR_HIDE, stream);
|
|
206
|
+
const callback = (info) => {
|
|
207
|
+
if (info.phase && info.phase !== lastPhase) {
|
|
208
|
+
if (lastPhase !== null && isTty) write("\n", stream);
|
|
209
|
+
lastPhase = info.phase;
|
|
210
|
+
if (!started) {
|
|
211
|
+
bar.start(info.current, info.total);
|
|
212
|
+
started = true;
|
|
213
|
+
}
|
|
214
|
+
bar.setPhase(info.phase, {
|
|
215
|
+
current: info.current,
|
|
216
|
+
total: info.total
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
if (!started) {
|
|
220
|
+
bar.start(info.current, info.total);
|
|
221
|
+
started = true;
|
|
222
|
+
}
|
|
223
|
+
bar.update(info.current);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const complete = () => {
|
|
227
|
+
if (started) bar.stop(options.clearOnComplete);
|
|
228
|
+
if (isTty) write(CURSOR_SHOW, stream);
|
|
229
|
+
};
|
|
230
|
+
return [callback, complete];
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region packages/ag-react/src/ui/wrappers/wrap-generator.ts
|
|
234
|
+
/**
|
|
235
|
+
* Consume a progress generator while displaying progress
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```ts
|
|
239
|
+
* // Wrap existing generator (like evaluateAllRules())
|
|
240
|
+
* await wrapGenerator(evaluateAllRules(), "Evaluating rules");
|
|
241
|
+
*
|
|
242
|
+
* // With custom format
|
|
243
|
+
* await wrapGenerator(
|
|
244
|
+
* processItems(),
|
|
245
|
+
* ({ current, total }) => `Processing: ${current}/${total}`
|
|
246
|
+
* );
|
|
247
|
+
*
|
|
248
|
+
* // Get the generator's return value
|
|
249
|
+
* const result = await wrapGenerator(generatorWithReturn(), "Processing");
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
async function wrapGenerator(generator, textOrFormat, options = {}) {
|
|
253
|
+
const stream = process.stdout;
|
|
254
|
+
const isTty = isTTY(stream);
|
|
255
|
+
const label = typeof textOrFormat === "function" ? "" : textOrFormat;
|
|
256
|
+
const bar = new ProgressBar({
|
|
257
|
+
format: label ? `${label} [:bar] :current/:total :percent` : ":bar :current/:total :percent",
|
|
258
|
+
hideCursor: true
|
|
259
|
+
});
|
|
260
|
+
if (isTty) write(CURSOR_HIDE, stream);
|
|
261
|
+
let started = false;
|
|
262
|
+
let result;
|
|
263
|
+
try {
|
|
264
|
+
while (true) {
|
|
265
|
+
result = generator.next();
|
|
266
|
+
if (result.done) break;
|
|
267
|
+
const { current, total } = result.value;
|
|
268
|
+
if (!started) {
|
|
269
|
+
bar.start(current, total);
|
|
270
|
+
started = true;
|
|
271
|
+
} else bar.update(current);
|
|
272
|
+
}
|
|
273
|
+
if (started) bar.stop(options.clearOnComplete);
|
|
274
|
+
if (isTty) write(CURSOR_SHOW, stream);
|
|
275
|
+
return result.value;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (started) bar.stop();
|
|
278
|
+
if (isTty) write(CURSOR_SHOW, stream);
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Create an async iterable wrapper that shows progress
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```ts
|
|
287
|
+
* const items = [1, 2, 3, 4, 5];
|
|
288
|
+
* for await (const item of withIterableProgress(items, "Processing")) {
|
|
289
|
+
* await processItem(item);
|
|
290
|
+
* }
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
async function* withIterableProgress(iterable, label, options = {}) {
|
|
294
|
+
const stream = process.stdout;
|
|
295
|
+
const isTty = isTTY(stream);
|
|
296
|
+
const total = (Array.isArray(iterable) ? iterable : null)?.length ?? 0;
|
|
297
|
+
const bar = new ProgressBar({
|
|
298
|
+
format: `${label} [:bar] :current/:total :percent`,
|
|
299
|
+
total,
|
|
300
|
+
hideCursor: true
|
|
301
|
+
});
|
|
302
|
+
if (isTty) write(CURSOR_HIDE, stream);
|
|
303
|
+
let current = 0;
|
|
304
|
+
bar.start(0, total);
|
|
305
|
+
try {
|
|
306
|
+
for await (const item of iterable) {
|
|
307
|
+
yield item;
|
|
308
|
+
current++;
|
|
309
|
+
bar.update(current);
|
|
310
|
+
}
|
|
311
|
+
bar.stop(options.clearOnComplete);
|
|
312
|
+
if (isTty) write(CURSOR_SHOW, stream);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
bar.stop();
|
|
315
|
+
if (isTty) write(CURSOR_SHOW, stream);
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region packages/ag-react/src/ui/wrappers/wrap-emitter.ts
|
|
321
|
+
/**
|
|
322
|
+
* Track EventEmitter state changes with a spinner
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* const stop = wrapEmitter(syncManager, {
|
|
327
|
+
* initialText: "Starting sync...",
|
|
328
|
+
* events: {
|
|
329
|
+
* 'ready': { text: "Watcher ready", succeed: true },
|
|
330
|
+
* 'state-change': { getText: (s) => `State: ${s}` },
|
|
331
|
+
* 'error': { fail: true },
|
|
332
|
+
* 'idle': { stop: true }
|
|
333
|
+
* }
|
|
334
|
+
* });
|
|
335
|
+
*
|
|
336
|
+
* // Later, to stop manually
|
|
337
|
+
* stop();
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
function wrapEmitter(emitter, config) {
|
|
341
|
+
const spinner = new Spinner(config.initialText ?? "");
|
|
342
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
343
|
+
spinner.start();
|
|
344
|
+
for (const [eventName, eventConfig] of Object.entries(config.events)) {
|
|
345
|
+
const handler = (data) => {
|
|
346
|
+
if (eventConfig.getText) spinner.currentText = eventConfig.getText(data);
|
|
347
|
+
else if (eventConfig.text) spinner.currentText = eventConfig.text;
|
|
348
|
+
if (eventConfig.succeed) {
|
|
349
|
+
spinner.succeed();
|
|
350
|
+
cleanup();
|
|
351
|
+
} else if (eventConfig.fail) {
|
|
352
|
+
const message = data instanceof Error ? data.message : String(data ?? "Failed");
|
|
353
|
+
spinner.fail(message);
|
|
354
|
+
cleanup();
|
|
355
|
+
} else if (eventConfig.stop) {
|
|
356
|
+
spinner.stop();
|
|
357
|
+
cleanup();
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
handlers.set(eventName, handler);
|
|
361
|
+
emitter.on(eventName, handler);
|
|
362
|
+
}
|
|
363
|
+
function cleanup() {
|
|
364
|
+
for (const [eventName, handler] of handlers) emitter.off(eventName, handler);
|
|
365
|
+
handlers.clear();
|
|
366
|
+
}
|
|
367
|
+
return () => {
|
|
368
|
+
spinner.stop();
|
|
369
|
+
cleanup();
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Wait for an EventEmitter to emit a specific event
|
|
374
|
+
* Shows a spinner while waiting
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```ts
|
|
378
|
+
* await waitForEvent(syncManager, "ready", "Waiting for watcher...");
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
async function waitForEvent(emitter, eventName, text, options = {}) {
|
|
382
|
+
return new Promise((resolve, reject) => {
|
|
383
|
+
const spinner = new Spinner(text);
|
|
384
|
+
spinner.start();
|
|
385
|
+
let timer = null;
|
|
386
|
+
const cleanup = () => {
|
|
387
|
+
emitter.off(eventName, successHandler);
|
|
388
|
+
if (options.errorEvent) emitter.off(options.errorEvent, errorHandler);
|
|
389
|
+
if (timer) clearTimeout(timer);
|
|
390
|
+
};
|
|
391
|
+
const successHandler = (data) => {
|
|
392
|
+
cleanup();
|
|
393
|
+
spinner.succeed();
|
|
394
|
+
resolve(data);
|
|
395
|
+
};
|
|
396
|
+
const errorHandler = (error) => {
|
|
397
|
+
cleanup();
|
|
398
|
+
spinner.fail(error instanceof Error ? error.message : "Error");
|
|
399
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
400
|
+
};
|
|
401
|
+
emitter.once(eventName, successHandler);
|
|
402
|
+
if (options.errorEvent) emitter.once(options.errorEvent, errorHandler);
|
|
403
|
+
if (options.timeout) timer = setTimeout(() => {
|
|
404
|
+
cleanup();
|
|
405
|
+
spinner.fail("Timeout");
|
|
406
|
+
reject(/* @__PURE__ */ new Error(`Timeout waiting for ${eventName}`));
|
|
407
|
+
}, options.timeout);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region packages/ag-react/src/ui/wrappers/with-select.ts
|
|
412
|
+
/**
|
|
413
|
+
* withSelect - Interactive CLI selection list
|
|
414
|
+
*/
|
|
415
|
+
/**
|
|
416
|
+
* Display an interactive selection list in the terminal
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```ts
|
|
420
|
+
* // Simple usage
|
|
421
|
+
* const color = await withSelect("Choose a color:", [
|
|
422
|
+
* { label: "Red", value: "red" },
|
|
423
|
+
* { label: "Green", value: "green" },
|
|
424
|
+
* { label: "Blue", value: "blue" },
|
|
425
|
+
* ]);
|
|
426
|
+
*
|
|
427
|
+
* // With options
|
|
428
|
+
* const result = await withSelect(
|
|
429
|
+
* "Select item:",
|
|
430
|
+
* options,
|
|
431
|
+
* { initial: 2, maxVisible: 5 }
|
|
432
|
+
* );
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
async function withSelect(prompt, options, selectOptions = {}) {
|
|
436
|
+
const { initial = 0, maxVisible = 10 } = selectOptions;
|
|
437
|
+
const stream = process.stdout;
|
|
438
|
+
const stdin = process.stdin;
|
|
439
|
+
if (!isTTY(stream) || !stdin.isTTY) return options[initial]?.value ?? options[0].value;
|
|
440
|
+
return new Promise((resolve, reject) => {
|
|
441
|
+
let highlightIndex = Math.min(Math.max(0, initial), options.length - 1);
|
|
442
|
+
let linesRendered = 0;
|
|
443
|
+
stdin.setRawMode(true);
|
|
444
|
+
stdin.resume();
|
|
445
|
+
stdin.setEncoding("utf8");
|
|
446
|
+
write(CURSOR_HIDE, stream);
|
|
447
|
+
function render() {
|
|
448
|
+
if (linesRendered > 0) write(cursorUp(linesRendered), stream);
|
|
449
|
+
const scrollOffset = Math.max(0, Math.min(highlightIndex - Math.floor(maxVisible / 2), options.length - maxVisible));
|
|
450
|
+
const visibleCount = Math.min(maxVisible, options.length);
|
|
451
|
+
const visibleOptions = options.slice(scrollOffset, scrollOffset + visibleCount);
|
|
452
|
+
const hasMoreAbove = scrollOffset > 0;
|
|
453
|
+
const hasMoreBelow = scrollOffset + visibleCount < options.length;
|
|
454
|
+
write(`
|
|
455
|
+
let lines = 1;
|
|
456
|
+
if (hasMoreAbove) {
|
|
457
|
+
write(`
|
|
458
|
${chalk.dim("...")}[K\n`, stream);
|
|
459
|
+
lines++;
|
|
460
|
+
}
|
|
461
|
+
for (let i = 0; i < visibleOptions.length; i++) {
|
|
462
|
+
const option = visibleOptions[i];
|
|
463
|
+
const isHighlighted = scrollOffset + i === highlightIndex;
|
|
464
|
+
write(`
|
|
465
|
+
lines++;
|
|
466
|
+
}
|
|
467
|
+
if (hasMoreBelow) {
|
|
468
|
+
write(`
|
|
1
469
|
${chalk.dim("...")}[K\n`, stream);
|
|
470
|
+
lines++;
|
|
471
|
+
}
|
|
472
|
+
linesRendered = lines;
|
|
473
|
+
}
|
|
474
|
+
function cleanup() {
|
|
475
|
+
stdin.setRawMode(false);
|
|
476
|
+
stdin.pause();
|
|
477
|
+
stdin.removeListener("data", onKeypress);
|
|
478
|
+
write(CURSOR_SHOW, stream);
|
|
479
|
+
}
|
|
480
|
+
function onKeypress(key) {
|
|
481
|
+
key.charCodeAt(0);
|
|
482
|
+
if (key === "") {
|
|
483
|
+
cleanup();
|
|
484
|
+
reject(/* @__PURE__ */ new Error("User cancelled"));
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (key === "\r" || key === "\n") {
|
|
488
|
+
cleanup();
|
|
489
|
+
resolve(options[highlightIndex].value);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (key === "\x1B" && key.length === 1) {
|
|
493
|
+
cleanup();
|
|
494
|
+
reject(/* @__PURE__ */ new Error("User cancelled"));
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (key.startsWith("\x1B[")) {
|
|
498
|
+
const code = key.slice(2);
|
|
499
|
+
if (code === "A") {
|
|
500
|
+
highlightIndex = Math.max(0, highlightIndex - 1);
|
|
501
|
+
render();
|
|
502
|
+
} else if (code === "B") {
|
|
503
|
+
highlightIndex = Math.min(options.length - 1, highlightIndex + 1);
|
|
504
|
+
render();
|
|
505
|
+
}
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (key === "j" || key === "J") {
|
|
509
|
+
highlightIndex = Math.min(options.length - 1, highlightIndex + 1);
|
|
510
|
+
render();
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (key === "k" || key === "K") {
|
|
514
|
+
highlightIndex = Math.max(0, highlightIndex - 1);
|
|
515
|
+
render();
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (key === " ") {
|
|
519
|
+
cleanup();
|
|
520
|
+
resolve(options[highlightIndex].value);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
stdin.on("data", onKeypress);
|
|
525
|
+
render();
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Create a reusable select instance for multiple selections
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* ```ts
|
|
533
|
+
* const select = createSelect({
|
|
534
|
+
* maxVisible: 5,
|
|
535
|
+
* });
|
|
536
|
+
*
|
|
537
|
+
* const first = await select("Choose first:", options1);
|
|
538
|
+
* const second = await select("Choose second:", options2);
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
function createSelect(defaultOptions = {}) {
|
|
542
|
+
return (prompt, options, overrides = {}) => withSelect(prompt, options, {
|
|
543
|
+
...defaultOptions,
|
|
544
|
+
...overrides
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region packages/ag-react/src/ui/wrappers/with-text-input.ts
|
|
549
|
+
/**
|
|
550
|
+
* withTextInput - CLI wrapper for text input prompts
|
|
551
|
+
*/
|
|
552
|
+
/**
|
|
553
|
+
* Prompt for text input in the terminal
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* ```ts
|
|
557
|
+
* // Simple usage
|
|
558
|
+
* const name = await withTextInput("What is your name?");
|
|
559
|
+
*
|
|
560
|
+
* // With options
|
|
561
|
+
* const password = await withTextInput("Password:", { mask: "*" });
|
|
562
|
+
*
|
|
563
|
+
* // With validation
|
|
564
|
+
* const email = await withTextInput("Email:", {
|
|
565
|
+
* validate: (v) => v.includes("@") ? undefined : "Invalid email"
|
|
566
|
+
* });
|
|
567
|
+
*
|
|
568
|
+
* // With autocomplete
|
|
569
|
+
* const fruit = await withTextInput("Pick a fruit:", {
|
|
570
|
+
* autocomplete: ["apple", "banana", "cherry"]
|
|
571
|
+
* });
|
|
572
|
+
* ```
|
|
573
|
+
*/
|
|
574
|
+
async function withTextInput(prompt, options = {}) {
|
|
575
|
+
const stream = options.stream ?? process.stdout;
|
|
576
|
+
const inputStream = options.inputStream ?? process.stdin;
|
|
577
|
+
const isTty = isTTY(stream);
|
|
578
|
+
let value = options.defaultValue ?? "";
|
|
579
|
+
let cursorPosition = value.length;
|
|
580
|
+
let errorMessage;
|
|
581
|
+
if (inputStream.isTTY) inputStream.setRawMode(true);
|
|
582
|
+
inputStream.resume();
|
|
583
|
+
const render = () => {
|
|
584
|
+
const displayValue = options.mask ? options.mask.repeat(value.length) : value;
|
|
585
|
+
const suggestion = getAutocompleteSuggestion(value, options.autocomplete);
|
|
586
|
+
const suggestionSuffix = suggestion ? chalk.dim(suggestion.slice(value.length)) : "";
|
|
587
|
+
const beforeCursor = displayValue.slice(0, cursorPosition);
|
|
588
|
+
const cursorChar = displayValue[cursorPosition] ?? " ";
|
|
589
|
+
const afterCursor = displayValue.slice(cursorPosition + 1);
|
|
590
|
+
const inputDisplay = !value && options.placeholder ? chalk.dim(options.placeholder) + chalk.inverse(" ") : beforeCursor + chalk.inverse(cursorChar) + afterCursor + suggestionSuffix;
|
|
591
|
+
const errorDisplay = errorMessage ? chalk.red(` (${errorMessage})`) : "";
|
|
592
|
+
const line = `${chalk.cyan("?")} ${chalk.bold(prompt)} ${inputDisplay}${errorDisplay}`;
|
|
593
|
+
if (isTty) write(`
|
|
594
|
+
};
|
|
595
|
+
if (isTty) write(CURSOR_HIDE, stream);
|
|
596
|
+
render();
|
|
597
|
+
return new Promise((resolve, reject) => {
|
|
598
|
+
const cleanup = () => {
|
|
599
|
+
inputStream.removeListener("data", onData);
|
|
600
|
+
inputStream.removeListener("error", onError);
|
|
601
|
+
if (inputStream.isTTY) inputStream.setRawMode(false);
|
|
602
|
+
inputStream.pause();
|
|
603
|
+
if (isTty) write(CURSOR_SHOW, stream);
|
|
604
|
+
};
|
|
605
|
+
const submit = () => {
|
|
606
|
+
if (options.validate) {
|
|
607
|
+
const error = options.validate(value);
|
|
608
|
+
if (error) {
|
|
609
|
+
errorMessage = error;
|
|
610
|
+
render();
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
cleanup();
|
|
615
|
+
const displayValue = options.mask ? options.mask.repeat(value.length) : value;
|
|
616
|
+
write(`
|
|
617
|
+
resolve(value);
|
|
618
|
+
};
|
|
619
|
+
const onError = (err) => {
|
|
620
|
+
cleanup();
|
|
621
|
+
reject(err);
|
|
622
|
+
};
|
|
623
|
+
const onData = (data) => {
|
|
624
|
+
const input = data.toString();
|
|
625
|
+
errorMessage = void 0;
|
|
626
|
+
for (let i = 0; i < input.length; i++) {
|
|
627
|
+
const char = input[i];
|
|
628
|
+
const code = char.charCodeAt(0);
|
|
629
|
+
if (code === 13 || code === 10) {
|
|
630
|
+
submit();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (code === 3) {
|
|
634
|
+
cleanup();
|
|
635
|
+
write("\n", stream);
|
|
636
|
+
reject(/* @__PURE__ */ new Error("User aborted"));
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (code === 27) {
|
|
640
|
+
if (input[i + 1] === "[") {
|
|
641
|
+
const arrowCode = input[i + 2];
|
|
642
|
+
if (arrowCode === "D") {
|
|
643
|
+
cursorPosition = Math.max(0, cursorPosition - 1);
|
|
644
|
+
i += 2;
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (arrowCode === "C") {
|
|
648
|
+
cursorPosition = Math.min(value.length, cursorPosition + 1);
|
|
649
|
+
i += 2;
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (arrowCode === "H") {
|
|
653
|
+
cursorPosition = 0;
|
|
654
|
+
i += 2;
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (arrowCode === "F") {
|
|
658
|
+
cursorPosition = value.length;
|
|
659
|
+
i += 2;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
i += 2;
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
value = "";
|
|
666
|
+
cursorPosition = 0;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (code === 127 || code === 8) {
|
|
670
|
+
if (cursorPosition > 0) {
|
|
671
|
+
value = value.slice(0, cursorPosition - 1) + value.slice(cursorPosition);
|
|
672
|
+
cursorPosition--;
|
|
673
|
+
}
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
if (code === 4) {
|
|
677
|
+
if (cursorPosition < value.length) value = value.slice(0, cursorPosition) + value.slice(cursorPosition + 1);
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (code === 9) {
|
|
681
|
+
const suggestion = getAutocompleteSuggestion(value, options.autocomplete);
|
|
682
|
+
if (suggestion) {
|
|
683
|
+
value = suggestion;
|
|
684
|
+
cursorPosition = value.length;
|
|
685
|
+
}
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (code === 1) {
|
|
689
|
+
cursorPosition = 0;
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
if (code === 5) {
|
|
693
|
+
cursorPosition = value.length;
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (code === 21) {
|
|
697
|
+
value = value.slice(cursorPosition);
|
|
698
|
+
cursorPosition = 0;
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (code === 11) {
|
|
702
|
+
value = value.slice(0, cursorPosition);
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
if (code === 23) {
|
|
706
|
+
const before = value.slice(0, cursorPosition);
|
|
707
|
+
const after = value.slice(cursorPosition);
|
|
708
|
+
const trimmed = before.trimEnd();
|
|
709
|
+
const lastSpace = trimmed.lastIndexOf(" ");
|
|
710
|
+
const newBefore = lastSpace === -1 ? "" : trimmed.slice(0, lastSpace + 1);
|
|
711
|
+
value = newBefore + after;
|
|
712
|
+
cursorPosition = newBefore.length;
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
if (code >= 32 && code < 127) {
|
|
716
|
+
value = value.slice(0, cursorPosition) + char + value.slice(cursorPosition);
|
|
717
|
+
cursorPosition++;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (code > 127) {
|
|
721
|
+
value = value.slice(0, cursorPosition) + char + value.slice(cursorPosition);
|
|
722
|
+
cursorPosition++;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
render();
|
|
727
|
+
};
|
|
728
|
+
inputStream.on("data", onData);
|
|
729
|
+
inputStream.on("error", onError);
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Create a text input instance for manual control
|
|
734
|
+
*
|
|
735
|
+
* @example
|
|
736
|
+
* ```ts
|
|
737
|
+
* const input = createTextInput("Name:", { placeholder: "Enter name" });
|
|
738
|
+
* input.render();
|
|
739
|
+
*
|
|
740
|
+
* // Later, get the value
|
|
741
|
+
* const value = await input.waitForSubmit();
|
|
742
|
+
* ```
|
|
743
|
+
*/
|
|
744
|
+
function createTextInput(prompt, options = {}) {
|
|
745
|
+
const stream = options.stream ?? process.stdout;
|
|
746
|
+
const isTty = isTTY(stream);
|
|
747
|
+
let value = options.defaultValue ?? "";
|
|
748
|
+
let cursorPosition = value.length;
|
|
749
|
+
const render = () => {
|
|
750
|
+
const displayValue = options.mask ? options.mask.repeat(value.length) : value;
|
|
751
|
+
const suggestion = getAutocompleteSuggestion(value, options.autocomplete);
|
|
752
|
+
const suggestionSuffix = suggestion ? chalk.dim(suggestion.slice(value.length)) : "";
|
|
753
|
+
const beforeCursor = displayValue.slice(0, cursorPosition);
|
|
754
|
+
const cursorChar = displayValue[cursorPosition] ?? " ";
|
|
755
|
+
const afterCursor = displayValue.slice(cursorPosition + 1);
|
|
756
|
+
const inputDisplay = !value && options.placeholder ? chalk.dim(options.placeholder) + chalk.inverse(" ") : beforeCursor + chalk.inverse(cursorChar) + afterCursor + suggestionSuffix;
|
|
757
|
+
const line = `${chalk.cyan("?")} ${chalk.bold(prompt)} ${inputDisplay}`;
|
|
758
|
+
if (isTty) write(`
|
|
759
|
+
};
|
|
760
|
+
return {
|
|
761
|
+
get value() {
|
|
762
|
+
return value;
|
|
763
|
+
},
|
|
764
|
+
set value(v) {
|
|
765
|
+
value = v;
|
|
766
|
+
cursorPosition = Math.min(cursorPosition, v.length);
|
|
767
|
+
},
|
|
768
|
+
get cursorPosition() {
|
|
769
|
+
return cursorPosition;
|
|
770
|
+
},
|
|
771
|
+
set cursorPosition(pos) {
|
|
772
|
+
cursorPosition = Math.max(0, Math.min(value.length, pos));
|
|
773
|
+
},
|
|
774
|
+
render,
|
|
775
|
+
insert(char) {
|
|
776
|
+
value = value.slice(0, cursorPosition) + char + value.slice(cursorPosition);
|
|
777
|
+
cursorPosition += char.length;
|
|
778
|
+
},
|
|
779
|
+
backspace() {
|
|
780
|
+
if (cursorPosition > 0) {
|
|
781
|
+
value = value.slice(0, cursorPosition - 1) + value.slice(cursorPosition);
|
|
782
|
+
cursorPosition--;
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
delete() {
|
|
786
|
+
if (cursorPosition < value.length) value = value.slice(0, cursorPosition) + value.slice(cursorPosition + 1);
|
|
787
|
+
},
|
|
788
|
+
clear() {
|
|
789
|
+
value = "";
|
|
790
|
+
cursorPosition = 0;
|
|
791
|
+
},
|
|
792
|
+
acceptSuggestion() {
|
|
793
|
+
const suggestion = getAutocompleteSuggestion(value, options.autocomplete);
|
|
794
|
+
if (suggestion) {
|
|
795
|
+
value = suggestion;
|
|
796
|
+
cursorPosition = value.length;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Find a matching autocomplete suggestion for the current input
|
|
803
|
+
*/
|
|
804
|
+
function getAutocompleteSuggestion(value, autocomplete) {
|
|
805
|
+
if (!value || !autocomplete?.length) return;
|
|
806
|
+
const lowerValue = value.toLowerCase();
|
|
807
|
+
return autocomplete.find((item) => item.toLowerCase().startsWith(lowerValue) && item.length > value.length);
|
|
808
|
+
}
|
|
809
|
+
//#endregion
|
|
810
|
+
export { waitForEvent as a, wrapGenerator as c, attachSpinner as d, withSpinner as f, withSelect as i, createProgressCallback as l, withTextInput as n, wrapEmitter as o, createSelect as r, withIterableProgress as s, createTextInput as t, withProgress as u };
|
|
811
|
+
|
|
812
|
+
//# sourceMappingURL=wrappers-hhL8EQ_n.mjs.map
|