@topcli/prompts 1.10.0 → 1.11.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.
- package/LICENSE +1 -1
- package/README.md +20 -5
- package/dist/index.cjs +81 -53
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +80 -53
- package/package.json +12 -13
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|

|
|
5
5
|
[](https://github.com/TopCli/prompts/commit-activity)
|
|
6
6
|
[](https://github.com/TopCli/prompts/blob/main/LICENSE)
|
|
7
|
+
[](https://ossf.github.io/scorecard-visualizer/#/projects/github.com/TopCli/prompts)
|
|
7
8
|

|
|
9
|
+
|
|
10
|
+
<img src="./public/topcli.gif" alt="demo">
|
|
8
11
|
</div>
|
|
9
12
|
|
|
10
13
|
## Requirements
|
|
@@ -52,18 +55,21 @@ Simple prompt, similar to `rl.question()` with an improved UI.
|
|
|
52
55
|
|
|
53
56
|
Use `options.secure` if you need to hide both input and answer.
|
|
54
57
|
|
|
55
|
-
Use `options.validators` to handle user input.
|
|
56
|
-
|
|
57
58
|
Use `options.signal` to set an `AbortSignal` (throws a [AbortError](#aborterror)).
|
|
58
59
|
|
|
60
|
+
Use `options.validators` to handle user input.
|
|
61
|
+
|
|
59
62
|
**Example**
|
|
60
63
|
|
|
61
64
|
```js
|
|
62
65
|
const packageName = await question('Package name', {
|
|
63
66
|
validators: [
|
|
64
67
|
{
|
|
65
|
-
validate: (value) =>
|
|
66
|
-
|
|
68
|
+
validate: (value) => {
|
|
69
|
+
if (!existsSync(join(process.cwd(), value))) {
|
|
70
|
+
return `Folder ${value} already exists`
|
|
71
|
+
}
|
|
72
|
+
}
|
|
67
73
|
}
|
|
68
74
|
]
|
|
69
75
|
});
|
|
@@ -124,7 +130,10 @@ Use `options.signal` to set an `AbortSignal` (throws a [AbortError](#aborterror)
|
|
|
124
130
|
confirm(message: string, options?: ConfirmOptions): Promise<boolean>
|
|
125
131
|
```
|
|
126
132
|
|
|
127
|
-
Boolean prompt,
|
|
133
|
+
Boolean prompt, default to `options.initial` (`false`).
|
|
134
|
+
|
|
135
|
+
> [!TIP]
|
|
136
|
+
> You can answer pressing <kbd>Y</kbd> or <kbd>N</kbd>
|
|
128
137
|
|
|
129
138
|
Use `options.signal` to set an `AbortSignal` (throws a [AbortError](#aborterror)).
|
|
130
139
|
|
|
@@ -221,6 +230,12 @@ export interface ConfirmOptions extends SharedOptions {
|
|
|
221
230
|
}
|
|
222
231
|
```
|
|
223
232
|
|
|
233
|
+
## Contributing
|
|
234
|
+
|
|
235
|
+
Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
|
|
236
|
+
|
|
237
|
+
Open an issue if you want to provide feedback such as bug reports or enchancements.
|
|
238
|
+
|
|
224
239
|
## Contributors
|
|
225
240
|
|
|
226
241
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -40,7 +41,7 @@ module.exports = __toCommonJS(prompts_exports);
|
|
|
40
41
|
|
|
41
42
|
// src/prompts/question.ts
|
|
42
43
|
var import_node_os2 = require("os");
|
|
43
|
-
var
|
|
44
|
+
var import_node_util2 = require("util");
|
|
44
45
|
var import_wcwidth = __toESM(require("@topcli/wcwidth"), 1);
|
|
45
46
|
|
|
46
47
|
// src/prompts/abstract.ts
|
|
@@ -54,7 +55,7 @@ var import_node_process = __toESM(require("process"), 1);
|
|
|
54
55
|
var kAnsiRegex = ansiRegex();
|
|
55
56
|
function ansiRegex({ onlyFirst = false } = {}) {
|
|
56
57
|
const pattern = [
|
|
57
|
-
// eslint-disable-next-line max-len
|
|
58
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
58
59
|
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
|
|
59
60
|
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
|
|
60
61
|
].join("|");
|
|
@@ -146,6 +147,12 @@ var AbstractPrompt = class _AbstractPrompt extends import_node_events.default {
|
|
|
146
147
|
if (typeof message !== "string") {
|
|
147
148
|
throw new TypeError(`message must be string, ${typeof message} given.`);
|
|
148
149
|
}
|
|
150
|
+
if (!output.isTTY) {
|
|
151
|
+
Object.assign(output, {
|
|
152
|
+
moveCursor: () => void 0,
|
|
153
|
+
clearScreenDown: () => void 0
|
|
154
|
+
});
|
|
155
|
+
}
|
|
149
156
|
this.stdin = input;
|
|
150
157
|
this.stdout = output;
|
|
151
158
|
this.message = message;
|
|
@@ -176,6 +183,9 @@ var AbstractPrompt = class _AbstractPrompt extends import_node_events.default {
|
|
|
176
183
|
}
|
|
177
184
|
this.emit("error", new AbortError("Prompt aborted"));
|
|
178
185
|
};
|
|
186
|
+
if (this.signal.aborted) {
|
|
187
|
+
this.#signalHandler();
|
|
188
|
+
}
|
|
179
189
|
this.signal.addEventListener("abort", this.#signalHandler, { once: true });
|
|
180
190
|
}
|
|
181
191
|
}
|
|
@@ -204,7 +214,7 @@ var AbstractPrompt = class _AbstractPrompt extends import_node_events.default {
|
|
|
204
214
|
};
|
|
205
215
|
|
|
206
216
|
// src/constants.ts
|
|
207
|
-
var
|
|
217
|
+
var import_node_util = require("util");
|
|
208
218
|
var kMainSymbols = {
|
|
209
219
|
tick: "\u2714",
|
|
210
220
|
cross: "\u2716",
|
|
@@ -224,20 +234,45 @@ var kFallbackSymbols = {
|
|
|
224
234
|
inactive: "(-)"
|
|
225
235
|
};
|
|
226
236
|
var kSymbols = isUnicodeSupported() || process.env.CI ? kMainSymbols : kFallbackSymbols;
|
|
227
|
-
var kPointer =
|
|
237
|
+
var kPointer = (0, import_node_util.styleText)("gray", kSymbols.pointer);
|
|
228
238
|
var SYMBOLS = {
|
|
229
|
-
QuestionMark:
|
|
230
|
-
Tick:
|
|
231
|
-
Cross:
|
|
239
|
+
QuestionMark: (0, import_node_util.styleText)(["blue", "bold"], "?"),
|
|
240
|
+
Tick: (0, import_node_util.styleText)(["green", "bold"], kSymbols.tick),
|
|
241
|
+
Cross: (0, import_node_util.styleText)(["red", "bold"], kSymbols.cross),
|
|
232
242
|
Pointer: kPointer,
|
|
233
243
|
Previous: kSymbols.previous,
|
|
234
244
|
Next: kSymbols.next,
|
|
235
245
|
ShowCursor: "\x1B[?25h",
|
|
236
246
|
HideCursor: "\x1B[?25l",
|
|
237
|
-
Active:
|
|
238
|
-
Inactive:
|
|
247
|
+
Active: (0, import_node_util.styleText)("cyan", kSymbols.active),
|
|
248
|
+
Inactive: (0, import_node_util.styleText)("gray", kSymbols.inactive)
|
|
239
249
|
};
|
|
240
250
|
|
|
251
|
+
// src/validators.ts
|
|
252
|
+
function required() {
|
|
253
|
+
return {
|
|
254
|
+
validate: (input) => {
|
|
255
|
+
const isValid2 = Array.isArray(input) ? input.length > 0 : Boolean(input);
|
|
256
|
+
return isValid2 ? null : { isValid: isValid2, error: "required" };
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function isValid(result) {
|
|
261
|
+
if (typeof result === "object") {
|
|
262
|
+
return result?.isValid !== false;
|
|
263
|
+
}
|
|
264
|
+
if (typeof result === "string") {
|
|
265
|
+
return result.length > 0;
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
function resultError(result) {
|
|
270
|
+
if (typeof result === "object") {
|
|
271
|
+
return result.error;
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
|
|
241
276
|
// src/prompts/question.ts
|
|
242
277
|
var QuestionPrompt = class extends AbstractPrompt {
|
|
243
278
|
defaultValue;
|
|
@@ -277,16 +312,16 @@ var QuestionPrompt = class extends AbstractPrompt {
|
|
|
277
312
|
});
|
|
278
313
|
}
|
|
279
314
|
#getQuestionQuery() {
|
|
280
|
-
return `${
|
|
315
|
+
return `${(0, import_node_util2.styleText)("bold", `${SYMBOLS.QuestionMark} ${this.message}${this.tip}`)} ${this.questionSuffixError}`;
|
|
281
316
|
}
|
|
282
317
|
#setQuestionSuffixError(error) {
|
|
283
|
-
const suffix =
|
|
318
|
+
const suffix = (0, import_node_util2.styleText)("red", `[${error}] `);
|
|
284
319
|
this.questionSuffixError = suffix;
|
|
285
320
|
}
|
|
286
321
|
#writeAnswer() {
|
|
287
322
|
const prefix = this.answer ? SYMBOLS.Tick : SYMBOLS.Cross;
|
|
288
|
-
const answer =
|
|
289
|
-
this.write(`${prefix} ${
|
|
323
|
+
const answer = (0, import_node_util2.styleText)("yellow", this.#secure ? "CONFIDENTIAL" : this.answer ?? "");
|
|
324
|
+
this.write(`${prefix} ${(0, import_node_util2.styleText)("bold", this.message)} ${SYMBOLS.Pointer} ${answer}${import_node_os2.EOL}`);
|
|
290
325
|
}
|
|
291
326
|
#onQuestionAnswer() {
|
|
292
327
|
const questionLineCount = Math.ceil(
|
|
@@ -295,9 +330,9 @@ var QuestionPrompt = class extends AbstractPrompt {
|
|
|
295
330
|
this.stdout.moveCursor(-this.stdout.columns, -questionLineCount);
|
|
296
331
|
this.stdout.clearScreenDown();
|
|
297
332
|
for (const validator of this.#validators) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
this.#setQuestionSuffixError(
|
|
333
|
+
const validationResult = validator.validate(this.answer);
|
|
334
|
+
if (isValid(validationResult) === false) {
|
|
335
|
+
this.#setQuestionSuffixError(resultError(validationResult));
|
|
301
336
|
this.answerBuffer = this.#question();
|
|
302
337
|
return;
|
|
303
338
|
}
|
|
@@ -334,7 +369,7 @@ var QuestionPrompt = class extends AbstractPrompt {
|
|
|
334
369
|
|
|
335
370
|
// src/prompts/confirm.ts
|
|
336
371
|
var import_node_os3 = require("os");
|
|
337
|
-
var
|
|
372
|
+
var import_node_util3 = require("util");
|
|
338
373
|
var import_wcwidth2 = __toESM(require("@topcli/wcwidth"), 1);
|
|
339
374
|
var kToggleKeys = /* @__PURE__ */ new Set([
|
|
340
375
|
"left",
|
|
@@ -365,8 +400,8 @@ var ConfirmPrompt = class extends AbstractPrompt {
|
|
|
365
400
|
this.selectedValue = initial;
|
|
366
401
|
}
|
|
367
402
|
#getHint() {
|
|
368
|
-
const Yes =
|
|
369
|
-
const No =
|
|
403
|
+
const Yes = (0, import_node_util3.styleText)(["cyan", "bold", "underline"], "Yes");
|
|
404
|
+
const No = (0, import_node_util3.styleText)(["cyan", "bold", "underline"], "No");
|
|
370
405
|
return this.selectedValue ? `${Yes}/No` : `Yes/${No}`;
|
|
371
406
|
}
|
|
372
407
|
#render() {
|
|
@@ -412,7 +447,7 @@ var ConfirmPrompt = class extends AbstractPrompt {
|
|
|
412
447
|
this.stdin.off("keypress", this.#boundKeyPressEvent);
|
|
413
448
|
}
|
|
414
449
|
#getQuestionQuery() {
|
|
415
|
-
const query =
|
|
450
|
+
const query = (0, import_node_util3.styleText)("bold", `${SYMBOLS.QuestionMark} ${this.message}`);
|
|
416
451
|
return `${query} ${this.#getHint()}`;
|
|
417
452
|
}
|
|
418
453
|
#onQuestionAnswer() {
|
|
@@ -422,7 +457,7 @@ var ConfirmPrompt = class extends AbstractPrompt {
|
|
|
422
457
|
-Math.floor((0, import_wcwidth2.default)(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns)
|
|
423
458
|
);
|
|
424
459
|
this.stdout.clearScreenDown();
|
|
425
|
-
this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${
|
|
460
|
+
this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${(0, import_node_util3.styleText)("bold", this.message)}${import_node_os3.EOL}`);
|
|
426
461
|
}
|
|
427
462
|
confirm() {
|
|
428
463
|
return new Promise(async (resolve, reject) => {
|
|
@@ -454,7 +489,7 @@ var ConfirmPrompt = class extends AbstractPrompt {
|
|
|
454
489
|
|
|
455
490
|
// src/prompts/select.ts
|
|
456
491
|
var import_node_os4 = require("os");
|
|
457
|
-
var
|
|
492
|
+
var import_node_util4 = require("util");
|
|
458
493
|
var import_wcwidth3 = __toESM(require("@topcli/wcwidth"), 1);
|
|
459
494
|
var kRequiredChoiceProperties = ["label", "value"];
|
|
460
495
|
var SelectPrompt = class extends AbstractPrompt {
|
|
@@ -561,15 +596,15 @@ var SelectPrompt = class extends AbstractPrompt {
|
|
|
561
596
|
this.longestChoice < 10 ? this.longestChoice : 0
|
|
562
597
|
);
|
|
563
598
|
const formattedDescription = choice.description ? ` - ${choice.description}` : "";
|
|
564
|
-
const
|
|
565
|
-
const str = `${prefix}${
|
|
599
|
+
const styles = isChoiceSelected ? ["white", "bold"] : ["gray"];
|
|
600
|
+
const str = `${prefix}${(0, import_node_util4.styleText)(styles, `${formattedLabel}${formattedDescription}`)}${import_node_os4.EOL}`;
|
|
566
601
|
this.write(str);
|
|
567
602
|
}
|
|
568
603
|
}
|
|
569
604
|
#showAnsweredQuestion(choice) {
|
|
570
605
|
const symbolPrefix = choice === "" ? SYMBOLS.Cross : SYMBOLS.Tick;
|
|
571
|
-
const prefix = `${symbolPrefix} ${
|
|
572
|
-
const formattedChoice =
|
|
606
|
+
const prefix = `${symbolPrefix} ${(0, import_node_util4.styleText)("bold", this.message)} ${SYMBOLS.Pointer}`;
|
|
607
|
+
const formattedChoice = (0, import_node_util4.styleText)("yellow", typeof choice === "string" ? choice : choice.label);
|
|
573
608
|
this.write(`${prefix} ${formattedChoice}${import_node_os4.EOL}`);
|
|
574
609
|
}
|
|
575
610
|
#onProcessExit() {
|
|
@@ -591,9 +626,9 @@ var SelectPrompt = class extends AbstractPrompt {
|
|
|
591
626
|
const label = typeof choice === "string" ? choice : choice.label;
|
|
592
627
|
const value = typeof choice === "string" ? choice : choice.value;
|
|
593
628
|
for (const validator of this.#validators) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
render({ error });
|
|
629
|
+
const validationResult = validator.validate(value);
|
|
630
|
+
if (isValid(validationResult) === false) {
|
|
631
|
+
render({ error: resultError(validationResult) });
|
|
597
632
|
return;
|
|
598
633
|
}
|
|
599
634
|
}
|
|
@@ -680,16 +715,16 @@ var SelectPrompt = class extends AbstractPrompt {
|
|
|
680
715
|
#showQuestion(error = null) {
|
|
681
716
|
let hint = "";
|
|
682
717
|
if (error) {
|
|
683
|
-
hint = ` ${hint.length > 0 ? " " : ""}${
|
|
718
|
+
hint = ` ${hint.length > 0 ? " " : ""}${(0, import_node_util4.styleText)(["red", "bold"], `[${error}]`)}`;
|
|
684
719
|
}
|
|
685
|
-
this.questionMessage = `${SYMBOLS.QuestionMark} ${
|
|
720
|
+
this.questionMessage = `${SYMBOLS.QuestionMark} ${(0, import_node_util4.styleText)("bold", this.message)}${hint}`;
|
|
686
721
|
this.write(`${this.questionMessage}${import_node_os4.EOL}`);
|
|
687
722
|
}
|
|
688
723
|
};
|
|
689
724
|
|
|
690
725
|
// src/prompts/multiselect.ts
|
|
691
726
|
var import_node_os5 = require("os");
|
|
692
|
-
var
|
|
727
|
+
var import_node_util5 = require("util");
|
|
693
728
|
var import_wcwidth4 = __toESM(require("@topcli/wcwidth"), 1);
|
|
694
729
|
var kRequiredChoiceProperties2 = ["label", "value"];
|
|
695
730
|
var MultiselectPrompt = class extends AbstractPrompt {
|
|
@@ -818,15 +853,15 @@ var MultiselectPrompt = class extends AbstractPrompt {
|
|
|
818
853
|
this.longestChoice < 10 ? this.longestChoice : 0
|
|
819
854
|
);
|
|
820
855
|
const formattedDescription = choice.description ? ` - ${choice.description}` : "";
|
|
821
|
-
const
|
|
822
|
-
const str = `${prefix} ${
|
|
856
|
+
const styles = isChoiceActive ? ["white", "bold"] : ["gray"];
|
|
857
|
+
const str = `${prefix} ${(0, import_node_util5.styleText)(styles, `${formattedLabel}${formattedDescription}`)}${import_node_os5.EOL}`;
|
|
823
858
|
this.write(str);
|
|
824
859
|
}
|
|
825
860
|
}
|
|
826
861
|
#showAnsweredQuestion(choices, isAgentAnswer = false) {
|
|
827
862
|
const prefixSymbol = this.selectedIndexes.size === 0 && !isAgentAnswer ? SYMBOLS.Cross : SYMBOLS.Tick;
|
|
828
|
-
const prefix = `${prefixSymbol} ${
|
|
829
|
-
const formattedChoice =
|
|
863
|
+
const prefix = `${prefixSymbol} ${(0, import_node_util5.styleText)("bold", this.message)} ${SYMBOLS.Pointer}`;
|
|
864
|
+
const formattedChoice = (0, import_node_util5.styleText)("yellow", choices);
|
|
830
865
|
this.write(`${prefix}${choices ? ` ${formattedChoice}` : ""}${import_node_os5.EOL}`);
|
|
831
866
|
}
|
|
832
867
|
#onProcessExit() {
|
|
@@ -862,9 +897,9 @@ var MultiselectPrompt = class extends AbstractPrompt {
|
|
|
862
897
|
return typeof choice === "string" ? choice : choice.value;
|
|
863
898
|
});
|
|
864
899
|
for (const validator of this.#validators) {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
render({ error });
|
|
900
|
+
const validationResult = validator.validate(values);
|
|
901
|
+
if (isValid(validationResult) === false) {
|
|
902
|
+
render({ error: resultError(validationResult) });
|
|
868
903
|
return;
|
|
869
904
|
}
|
|
870
905
|
}
|
|
@@ -949,26 +984,19 @@ var MultiselectPrompt = class extends AbstractPrompt {
|
|
|
949
984
|
});
|
|
950
985
|
}
|
|
951
986
|
#showQuestion(error = null) {
|
|
952
|
-
let hint = this.#showHint ?
|
|
953
|
-
|
|
954
|
-
|
|
987
|
+
let hint = this.#showHint ? (0, import_node_util5.styleText)(
|
|
988
|
+
"gray",
|
|
989
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
990
|
+
`(Press ${(0, import_node_util5.styleText)("bold", "<Ctrl+A>")} to toggle all, ${(0, import_node_util5.styleText)("bold", "<Left/Right>")} to toggle, ${(0, import_node_util5.styleText)("bold", "<Return>")} to submit)`
|
|
955
991
|
) : "";
|
|
956
992
|
if (error) {
|
|
957
|
-
hint += `${hint.length > 0 ? " " : ""}${
|
|
993
|
+
hint += `${hint.length > 0 ? " " : ""}${(0, import_node_util5.styleText)(["red", "bold"], `[${error}]`)}`;
|
|
958
994
|
}
|
|
959
|
-
this.questionMessage = `${SYMBOLS.QuestionMark} ${
|
|
995
|
+
this.questionMessage = `${SYMBOLS.QuestionMark} ${(0, import_node_util5.styleText)("bold", this.message)}${hint.length > 0 ? ` ${hint}` : ""}`;
|
|
960
996
|
this.write(`${this.questionMessage}${import_node_os5.EOL}`);
|
|
961
997
|
}
|
|
962
998
|
};
|
|
963
999
|
|
|
964
|
-
// src/validators.ts
|
|
965
|
-
function required() {
|
|
966
|
-
return {
|
|
967
|
-
validate: (input) => Array.isArray(input) ? input.length > 0 : Boolean(input),
|
|
968
|
-
error: () => "required"
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
|
|
972
1000
|
// index.ts
|
|
973
1001
|
async function question(message, options = {}) {
|
|
974
1002
|
return new QuestionPrompt({ ...options, message }).question();
|
|
@@ -977,7 +1005,7 @@ async function select(message, options) {
|
|
|
977
1005
|
const selectPrompt = new SelectPrompt({ ...options, message });
|
|
978
1006
|
return selectPrompt.select();
|
|
979
1007
|
}
|
|
980
|
-
async function confirm(message, options) {
|
|
1008
|
+
async function confirm(message, options = {}) {
|
|
981
1009
|
const confirmPrompt = new ConfirmPrompt({ ...options, message });
|
|
982
1010
|
return confirmPrompt.confirm();
|
|
983
1011
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
type ValidResponseObject = {
|
|
2
|
+
isValid?: true;
|
|
3
|
+
};
|
|
4
|
+
type InvalidResponseObject = {
|
|
5
|
+
isValid: false;
|
|
6
|
+
error: string;
|
|
7
|
+
};
|
|
8
|
+
type ValidationResponse = InvalidResponse | ValidResponse;
|
|
9
|
+
type InvalidResponse = string | InvalidResponseObject;
|
|
10
|
+
type ValidResponse = null | undefined | true | ValidResponseObject;
|
|
1
11
|
interface PromptValidator<T = string | string[] | boolean> {
|
|
2
|
-
validate: (input: T) =>
|
|
3
|
-
error: (input: T) => string;
|
|
12
|
+
validate: (input: T) => ValidationResponse;
|
|
4
13
|
}
|
|
5
14
|
declare function required<T = string | string[] | boolean>(): PromptValidator<T>;
|
|
6
15
|
|
|
@@ -80,7 +89,7 @@ interface SelectOptions extends AbstractPromptOptions {
|
|
|
80
89
|
|
|
81
90
|
declare function question(message: string, options?: Omit<QuestionOptions, "message">): Promise<string>;
|
|
82
91
|
declare function select(message: string, options: Omit<SelectOptions, "message">): Promise<string>;
|
|
83
|
-
declare function confirm(message: string, options
|
|
92
|
+
declare function confirm(message: string, options?: Omit<ConfirmOptions, "message">): Promise<boolean>;
|
|
84
93
|
declare function multiselect(message: string, options: Omit<MultiselectOptions, "message">): Promise<string[]>;
|
|
85
94
|
|
|
86
95
|
export { type AbstractPromptOptions, type Choice, type ConfirmOptions, type MultiselectOptions, PromptAgent, type PromptValidator, type QuestionOptions, type SelectOptions, confirm, multiselect, question, required, select };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
type ValidResponseObject = {
|
|
2
|
+
isValid?: true;
|
|
3
|
+
};
|
|
4
|
+
type InvalidResponseObject = {
|
|
5
|
+
isValid: false;
|
|
6
|
+
error: string;
|
|
7
|
+
};
|
|
8
|
+
type ValidationResponse = InvalidResponse | ValidResponse;
|
|
9
|
+
type InvalidResponse = string | InvalidResponseObject;
|
|
10
|
+
type ValidResponse = null | undefined | true | ValidResponseObject;
|
|
1
11
|
interface PromptValidator<T = string | string[] | boolean> {
|
|
2
|
-
validate: (input: T) =>
|
|
3
|
-
error: (input: T) => string;
|
|
12
|
+
validate: (input: T) => ValidationResponse;
|
|
4
13
|
}
|
|
5
14
|
declare function required<T = string | string[] | boolean>(): PromptValidator<T>;
|
|
6
15
|
|
|
@@ -80,7 +89,7 @@ interface SelectOptions extends AbstractPromptOptions {
|
|
|
80
89
|
|
|
81
90
|
declare function question(message: string, options?: Omit<QuestionOptions, "message">): Promise<string>;
|
|
82
91
|
declare function select(message: string, options: Omit<SelectOptions, "message">): Promise<string>;
|
|
83
|
-
declare function confirm(message: string, options
|
|
92
|
+
declare function confirm(message: string, options?: Omit<ConfirmOptions, "message">): Promise<boolean>;
|
|
84
93
|
declare function multiselect(message: string, options: Omit<MultiselectOptions, "message">): Promise<string[]>;
|
|
85
94
|
|
|
86
95
|
export { type AbstractPromptOptions, type Choice, type ConfirmOptions, type MultiselectOptions, PromptAgent, type PromptValidator, type QuestionOptions, type SelectOptions, confirm, multiselect, question, required, select };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/prompts/question.ts
|
|
2
2
|
import { EOL as EOL2 } from "os";
|
|
3
|
-
import
|
|
3
|
+
import { styleText as styleText2 } from "util";
|
|
4
4
|
import wcwidth from "@topcli/wcwidth";
|
|
5
5
|
|
|
6
6
|
// src/prompts/abstract.ts
|
|
@@ -14,7 +14,7 @@ import process2 from "process";
|
|
|
14
14
|
var kAnsiRegex = ansiRegex();
|
|
15
15
|
function ansiRegex({ onlyFirst = false } = {}) {
|
|
16
16
|
const pattern = [
|
|
17
|
-
// eslint-disable-next-line max-len
|
|
17
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
18
18
|
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
|
|
19
19
|
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
|
|
20
20
|
].join("|");
|
|
@@ -106,6 +106,12 @@ var AbstractPrompt = class _AbstractPrompt extends EventEmitter {
|
|
|
106
106
|
if (typeof message !== "string") {
|
|
107
107
|
throw new TypeError(`message must be string, ${typeof message} given.`);
|
|
108
108
|
}
|
|
109
|
+
if (!output.isTTY) {
|
|
110
|
+
Object.assign(output, {
|
|
111
|
+
moveCursor: () => void 0,
|
|
112
|
+
clearScreenDown: () => void 0
|
|
113
|
+
});
|
|
114
|
+
}
|
|
109
115
|
this.stdin = input;
|
|
110
116
|
this.stdout = output;
|
|
111
117
|
this.message = message;
|
|
@@ -136,6 +142,9 @@ var AbstractPrompt = class _AbstractPrompt extends EventEmitter {
|
|
|
136
142
|
}
|
|
137
143
|
this.emit("error", new AbortError("Prompt aborted"));
|
|
138
144
|
};
|
|
145
|
+
if (this.signal.aborted) {
|
|
146
|
+
this.#signalHandler();
|
|
147
|
+
}
|
|
139
148
|
this.signal.addEventListener("abort", this.#signalHandler, { once: true });
|
|
140
149
|
}
|
|
141
150
|
}
|
|
@@ -164,7 +173,7 @@ var AbstractPrompt = class _AbstractPrompt extends EventEmitter {
|
|
|
164
173
|
};
|
|
165
174
|
|
|
166
175
|
// src/constants.ts
|
|
167
|
-
import
|
|
176
|
+
import { styleText } from "util";
|
|
168
177
|
var kMainSymbols = {
|
|
169
178
|
tick: "\u2714",
|
|
170
179
|
cross: "\u2716",
|
|
@@ -184,20 +193,45 @@ var kFallbackSymbols = {
|
|
|
184
193
|
inactive: "(-)"
|
|
185
194
|
};
|
|
186
195
|
var kSymbols = isUnicodeSupported() || process.env.CI ? kMainSymbols : kFallbackSymbols;
|
|
187
|
-
var kPointer =
|
|
196
|
+
var kPointer = styleText("gray", kSymbols.pointer);
|
|
188
197
|
var SYMBOLS = {
|
|
189
|
-
QuestionMark:
|
|
190
|
-
Tick:
|
|
191
|
-
Cross:
|
|
198
|
+
QuestionMark: styleText(["blue", "bold"], "?"),
|
|
199
|
+
Tick: styleText(["green", "bold"], kSymbols.tick),
|
|
200
|
+
Cross: styleText(["red", "bold"], kSymbols.cross),
|
|
192
201
|
Pointer: kPointer,
|
|
193
202
|
Previous: kSymbols.previous,
|
|
194
203
|
Next: kSymbols.next,
|
|
195
204
|
ShowCursor: "\x1B[?25h",
|
|
196
205
|
HideCursor: "\x1B[?25l",
|
|
197
|
-
Active:
|
|
198
|
-
Inactive:
|
|
206
|
+
Active: styleText("cyan", kSymbols.active),
|
|
207
|
+
Inactive: styleText("gray", kSymbols.inactive)
|
|
199
208
|
};
|
|
200
209
|
|
|
210
|
+
// src/validators.ts
|
|
211
|
+
function required() {
|
|
212
|
+
return {
|
|
213
|
+
validate: (input) => {
|
|
214
|
+
const isValid2 = Array.isArray(input) ? input.length > 0 : Boolean(input);
|
|
215
|
+
return isValid2 ? null : { isValid: isValid2, error: "required" };
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function isValid(result) {
|
|
220
|
+
if (typeof result === "object") {
|
|
221
|
+
return result?.isValid !== false;
|
|
222
|
+
}
|
|
223
|
+
if (typeof result === "string") {
|
|
224
|
+
return result.length > 0;
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
function resultError(result) {
|
|
229
|
+
if (typeof result === "object") {
|
|
230
|
+
return result.error;
|
|
231
|
+
}
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
201
235
|
// src/prompts/question.ts
|
|
202
236
|
var QuestionPrompt = class extends AbstractPrompt {
|
|
203
237
|
defaultValue;
|
|
@@ -237,16 +271,16 @@ var QuestionPrompt = class extends AbstractPrompt {
|
|
|
237
271
|
});
|
|
238
272
|
}
|
|
239
273
|
#getQuestionQuery() {
|
|
240
|
-
return `${
|
|
274
|
+
return `${styleText2("bold", `${SYMBOLS.QuestionMark} ${this.message}${this.tip}`)} ${this.questionSuffixError}`;
|
|
241
275
|
}
|
|
242
276
|
#setQuestionSuffixError(error) {
|
|
243
|
-
const suffix =
|
|
277
|
+
const suffix = styleText2("red", `[${error}] `);
|
|
244
278
|
this.questionSuffixError = suffix;
|
|
245
279
|
}
|
|
246
280
|
#writeAnswer() {
|
|
247
281
|
const prefix = this.answer ? SYMBOLS.Tick : SYMBOLS.Cross;
|
|
248
|
-
const answer =
|
|
249
|
-
this.write(`${prefix} ${
|
|
282
|
+
const answer = styleText2("yellow", this.#secure ? "CONFIDENTIAL" : this.answer ?? "");
|
|
283
|
+
this.write(`${prefix} ${styleText2("bold", this.message)} ${SYMBOLS.Pointer} ${answer}${EOL2}`);
|
|
250
284
|
}
|
|
251
285
|
#onQuestionAnswer() {
|
|
252
286
|
const questionLineCount = Math.ceil(
|
|
@@ -255,9 +289,9 @@ var QuestionPrompt = class extends AbstractPrompt {
|
|
|
255
289
|
this.stdout.moveCursor(-this.stdout.columns, -questionLineCount);
|
|
256
290
|
this.stdout.clearScreenDown();
|
|
257
291
|
for (const validator of this.#validators) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
this.#setQuestionSuffixError(
|
|
292
|
+
const validationResult = validator.validate(this.answer);
|
|
293
|
+
if (isValid(validationResult) === false) {
|
|
294
|
+
this.#setQuestionSuffixError(resultError(validationResult));
|
|
261
295
|
this.answerBuffer = this.#question();
|
|
262
296
|
return;
|
|
263
297
|
}
|
|
@@ -294,7 +328,7 @@ var QuestionPrompt = class extends AbstractPrompt {
|
|
|
294
328
|
|
|
295
329
|
// src/prompts/confirm.ts
|
|
296
330
|
import { EOL as EOL3 } from "os";
|
|
297
|
-
import
|
|
331
|
+
import { styleText as styleText3 } from "util";
|
|
298
332
|
import wcwidth2 from "@topcli/wcwidth";
|
|
299
333
|
var kToggleKeys = /* @__PURE__ */ new Set([
|
|
300
334
|
"left",
|
|
@@ -325,8 +359,8 @@ var ConfirmPrompt = class extends AbstractPrompt {
|
|
|
325
359
|
this.selectedValue = initial;
|
|
326
360
|
}
|
|
327
361
|
#getHint() {
|
|
328
|
-
const Yes =
|
|
329
|
-
const No =
|
|
362
|
+
const Yes = styleText3(["cyan", "bold", "underline"], "Yes");
|
|
363
|
+
const No = styleText3(["cyan", "bold", "underline"], "No");
|
|
330
364
|
return this.selectedValue ? `${Yes}/No` : `Yes/${No}`;
|
|
331
365
|
}
|
|
332
366
|
#render() {
|
|
@@ -372,7 +406,7 @@ var ConfirmPrompt = class extends AbstractPrompt {
|
|
|
372
406
|
this.stdin.off("keypress", this.#boundKeyPressEvent);
|
|
373
407
|
}
|
|
374
408
|
#getQuestionQuery() {
|
|
375
|
-
const query =
|
|
409
|
+
const query = styleText3("bold", `${SYMBOLS.QuestionMark} ${this.message}`);
|
|
376
410
|
return `${query} ${this.#getHint()}`;
|
|
377
411
|
}
|
|
378
412
|
#onQuestionAnswer() {
|
|
@@ -382,7 +416,7 @@ var ConfirmPrompt = class extends AbstractPrompt {
|
|
|
382
416
|
-Math.floor(wcwidth2(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns)
|
|
383
417
|
);
|
|
384
418
|
this.stdout.clearScreenDown();
|
|
385
|
-
this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${
|
|
419
|
+
this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${styleText3("bold", this.message)}${EOL3}`);
|
|
386
420
|
}
|
|
387
421
|
confirm() {
|
|
388
422
|
return new Promise(async (resolve, reject) => {
|
|
@@ -414,7 +448,7 @@ var ConfirmPrompt = class extends AbstractPrompt {
|
|
|
414
448
|
|
|
415
449
|
// src/prompts/select.ts
|
|
416
450
|
import { EOL as EOL4 } from "os";
|
|
417
|
-
import
|
|
451
|
+
import { styleText as styleText4 } from "util";
|
|
418
452
|
import wcwidth3 from "@topcli/wcwidth";
|
|
419
453
|
var kRequiredChoiceProperties = ["label", "value"];
|
|
420
454
|
var SelectPrompt = class extends AbstractPrompt {
|
|
@@ -521,15 +555,15 @@ var SelectPrompt = class extends AbstractPrompt {
|
|
|
521
555
|
this.longestChoice < 10 ? this.longestChoice : 0
|
|
522
556
|
);
|
|
523
557
|
const formattedDescription = choice.description ? ` - ${choice.description}` : "";
|
|
524
|
-
const
|
|
525
|
-
const str = `${prefix}${
|
|
558
|
+
const styles = isChoiceSelected ? ["white", "bold"] : ["gray"];
|
|
559
|
+
const str = `${prefix}${styleText4(styles, `${formattedLabel}${formattedDescription}`)}${EOL4}`;
|
|
526
560
|
this.write(str);
|
|
527
561
|
}
|
|
528
562
|
}
|
|
529
563
|
#showAnsweredQuestion(choice) {
|
|
530
564
|
const symbolPrefix = choice === "" ? SYMBOLS.Cross : SYMBOLS.Tick;
|
|
531
|
-
const prefix = `${symbolPrefix} ${
|
|
532
|
-
const formattedChoice =
|
|
565
|
+
const prefix = `${symbolPrefix} ${styleText4("bold", this.message)} ${SYMBOLS.Pointer}`;
|
|
566
|
+
const formattedChoice = styleText4("yellow", typeof choice === "string" ? choice : choice.label);
|
|
533
567
|
this.write(`${prefix} ${formattedChoice}${EOL4}`);
|
|
534
568
|
}
|
|
535
569
|
#onProcessExit() {
|
|
@@ -551,9 +585,9 @@ var SelectPrompt = class extends AbstractPrompt {
|
|
|
551
585
|
const label = typeof choice === "string" ? choice : choice.label;
|
|
552
586
|
const value = typeof choice === "string" ? choice : choice.value;
|
|
553
587
|
for (const validator of this.#validators) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
render({ error });
|
|
588
|
+
const validationResult = validator.validate(value);
|
|
589
|
+
if (isValid(validationResult) === false) {
|
|
590
|
+
render({ error: resultError(validationResult) });
|
|
557
591
|
return;
|
|
558
592
|
}
|
|
559
593
|
}
|
|
@@ -640,16 +674,16 @@ var SelectPrompt = class extends AbstractPrompt {
|
|
|
640
674
|
#showQuestion(error = null) {
|
|
641
675
|
let hint = "";
|
|
642
676
|
if (error) {
|
|
643
|
-
hint = ` ${hint.length > 0 ? " " : ""}${
|
|
677
|
+
hint = ` ${hint.length > 0 ? " " : ""}${styleText4(["red", "bold"], `[${error}]`)}`;
|
|
644
678
|
}
|
|
645
|
-
this.questionMessage = `${SYMBOLS.QuestionMark} ${
|
|
679
|
+
this.questionMessage = `${SYMBOLS.QuestionMark} ${styleText4("bold", this.message)}${hint}`;
|
|
646
680
|
this.write(`${this.questionMessage}${EOL4}`);
|
|
647
681
|
}
|
|
648
682
|
};
|
|
649
683
|
|
|
650
684
|
// src/prompts/multiselect.ts
|
|
651
685
|
import { EOL as EOL5 } from "os";
|
|
652
|
-
import
|
|
686
|
+
import { styleText as styleText5 } from "util";
|
|
653
687
|
import wcwidth4 from "@topcli/wcwidth";
|
|
654
688
|
var kRequiredChoiceProperties2 = ["label", "value"];
|
|
655
689
|
var MultiselectPrompt = class extends AbstractPrompt {
|
|
@@ -778,15 +812,15 @@ var MultiselectPrompt = class extends AbstractPrompt {
|
|
|
778
812
|
this.longestChoice < 10 ? this.longestChoice : 0
|
|
779
813
|
);
|
|
780
814
|
const formattedDescription = choice.description ? ` - ${choice.description}` : "";
|
|
781
|
-
const
|
|
782
|
-
const str = `${prefix} ${
|
|
815
|
+
const styles = isChoiceActive ? ["white", "bold"] : ["gray"];
|
|
816
|
+
const str = `${prefix} ${styleText5(styles, `${formattedLabel}${formattedDescription}`)}${EOL5}`;
|
|
783
817
|
this.write(str);
|
|
784
818
|
}
|
|
785
819
|
}
|
|
786
820
|
#showAnsweredQuestion(choices, isAgentAnswer = false) {
|
|
787
821
|
const prefixSymbol = this.selectedIndexes.size === 0 && !isAgentAnswer ? SYMBOLS.Cross : SYMBOLS.Tick;
|
|
788
|
-
const prefix = `${prefixSymbol} ${
|
|
789
|
-
const formattedChoice =
|
|
822
|
+
const prefix = `${prefixSymbol} ${styleText5("bold", this.message)} ${SYMBOLS.Pointer}`;
|
|
823
|
+
const formattedChoice = styleText5("yellow", choices);
|
|
790
824
|
this.write(`${prefix}${choices ? ` ${formattedChoice}` : ""}${EOL5}`);
|
|
791
825
|
}
|
|
792
826
|
#onProcessExit() {
|
|
@@ -822,9 +856,9 @@ var MultiselectPrompt = class extends AbstractPrompt {
|
|
|
822
856
|
return typeof choice === "string" ? choice : choice.value;
|
|
823
857
|
});
|
|
824
858
|
for (const validator of this.#validators) {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
render({ error });
|
|
859
|
+
const validationResult = validator.validate(values);
|
|
860
|
+
if (isValid(validationResult) === false) {
|
|
861
|
+
render({ error: resultError(validationResult) });
|
|
828
862
|
return;
|
|
829
863
|
}
|
|
830
864
|
}
|
|
@@ -909,26 +943,19 @@ var MultiselectPrompt = class extends AbstractPrompt {
|
|
|
909
943
|
});
|
|
910
944
|
}
|
|
911
945
|
#showQuestion(error = null) {
|
|
912
|
-
let hint = this.#showHint ?
|
|
913
|
-
|
|
914
|
-
|
|
946
|
+
let hint = this.#showHint ? styleText5(
|
|
947
|
+
"gray",
|
|
948
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
949
|
+
`(Press ${styleText5("bold", "<Ctrl+A>")} to toggle all, ${styleText5("bold", "<Left/Right>")} to toggle, ${styleText5("bold", "<Return>")} to submit)`
|
|
915
950
|
) : "";
|
|
916
951
|
if (error) {
|
|
917
|
-
hint += `${hint.length > 0 ? " " : ""}${
|
|
952
|
+
hint += `${hint.length > 0 ? " " : ""}${styleText5(["red", "bold"], `[${error}]`)}`;
|
|
918
953
|
}
|
|
919
|
-
this.questionMessage = `${SYMBOLS.QuestionMark} ${
|
|
954
|
+
this.questionMessage = `${SYMBOLS.QuestionMark} ${styleText5("bold", this.message)}${hint.length > 0 ? ` ${hint}` : ""}`;
|
|
920
955
|
this.write(`${this.questionMessage}${EOL5}`);
|
|
921
956
|
}
|
|
922
957
|
};
|
|
923
958
|
|
|
924
|
-
// src/validators.ts
|
|
925
|
-
function required() {
|
|
926
|
-
return {
|
|
927
|
-
validate: (input) => Array.isArray(input) ? input.length > 0 : Boolean(input),
|
|
928
|
-
error: () => "required"
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
|
|
932
959
|
// index.ts
|
|
933
960
|
async function question(message, options = {}) {
|
|
934
961
|
return new QuestionPrompt({ ...options, message }).question();
|
|
@@ -937,7 +964,7 @@ async function select(message, options) {
|
|
|
937
964
|
const selectPrompt = new SelectPrompt({ ...options, message });
|
|
938
965
|
return selectPrompt.select();
|
|
939
966
|
}
|
|
940
|
-
async function confirm(message, options) {
|
|
967
|
+
async function confirm(message, options = {}) {
|
|
941
968
|
const confirmPrompt = new ConfirmPrompt({ ...options, message });
|
|
942
969
|
return confirmPrompt.confirm();
|
|
943
970
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topcli/prompts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Node.js user input library for command-line interfaces.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsup index.ts --format cjs,esm --dts --clean",
|
|
@@ -31,22 +31,21 @@
|
|
|
31
31
|
"license": "ISC",
|
|
32
32
|
"type": "module",
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@
|
|
35
|
-
"@
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"esmock": "^2.6.
|
|
39
|
-
"glob": "^
|
|
40
|
-
"tsup": "^8.
|
|
41
|
-
"tsx": "^4.
|
|
42
|
-
"typescript": "^5.
|
|
34
|
+
"@openally/config.eslint": "^1.0.0",
|
|
35
|
+
"@openally/config.typescript": "^1.0.3",
|
|
36
|
+
"@types/node": "^22.3.0",
|
|
37
|
+
"c8": "^10.1.2",
|
|
38
|
+
"esmock": "^2.6.7",
|
|
39
|
+
"glob": "^11.0.0",
|
|
40
|
+
"tsup": "^8.2.4",
|
|
41
|
+
"tsx": "^4.17.0",
|
|
42
|
+
"typescript": "^5.5.4"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@topcli/wcwidth": "^1.0.1"
|
|
46
|
-
"kleur": "^4.1.5"
|
|
45
|
+
"@topcli/wcwidth": "^1.0.1"
|
|
47
46
|
},
|
|
48
47
|
"engines": {
|
|
49
|
-
"node": ">=
|
|
48
|
+
"node": "^20.12.0 || >=21.7.0"
|
|
50
49
|
},
|
|
51
50
|
"bugs": {
|
|
52
51
|
"url": "https://github.com/TopCli/prompts/issues"
|