cli-menu-kit 0.1.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 +21 -0
- package/README.md +716 -0
- package/dist/api.d.ts +158 -0
- package/dist/api.js +128 -0
- package/dist/components/display/headers.d.ts +32 -0
- package/dist/components/display/headers.js +84 -0
- package/dist/components/display/index.d.ts +8 -0
- package/dist/components/display/index.js +31 -0
- package/dist/components/display/messages.d.ts +41 -0
- package/dist/components/display/messages.js +90 -0
- package/dist/components/display/progress.d.ts +41 -0
- package/dist/components/display/progress.js +82 -0
- package/dist/components/display/summary.d.ts +33 -0
- package/dist/components/display/summary.js +82 -0
- package/dist/components/inputs/index.d.ts +8 -0
- package/dist/components/inputs/index.js +15 -0
- package/dist/components/inputs/language-input.d.ts +11 -0
- package/dist/components/inputs/language-input.js +104 -0
- package/dist/components/inputs/modify-field.d.ts +11 -0
- package/dist/components/inputs/modify-field.js +42 -0
- package/dist/components/inputs/number-input.d.ts +11 -0
- package/dist/components/inputs/number-input.js +143 -0
- package/dist/components/inputs/text-input.d.ts +11 -0
- package/dist/components/inputs/text-input.js +114 -0
- package/dist/components/menus/boolean-menu.d.ts +11 -0
- package/dist/components/menus/boolean-menu.js +169 -0
- package/dist/components/menus/checkbox-menu.d.ts +11 -0
- package/dist/components/menus/checkbox-menu.js +161 -0
- package/dist/components/menus/index.d.ts +7 -0
- package/dist/components/menus/index.js +13 -0
- package/dist/components/menus/radio-menu.d.ts +11 -0
- package/dist/components/menus/radio-menu.js +158 -0
- package/dist/components.d.ts +55 -0
- package/dist/components.js +166 -0
- package/dist/core/colors.d.ts +64 -0
- package/dist/core/colors.js +139 -0
- package/dist/core/keyboard.d.ts +124 -0
- package/dist/core/keyboard.js +185 -0
- package/dist/core/renderer.d.ts +74 -0
- package/dist/core/renderer.js +217 -0
- package/dist/core/terminal.d.ts +89 -0
- package/dist/core/terminal.js +170 -0
- package/dist/features/commands.d.ts +57 -0
- package/dist/features/commands.js +161 -0
- package/dist/features/wizard.d.ts +62 -0
- package/dist/features/wizard.js +112 -0
- package/dist/i18n/languages/en.d.ts +5 -0
- package/dist/i18n/languages/en.js +54 -0
- package/dist/i18n/languages/zh.d.ts +5 -0
- package/dist/i18n/languages/zh.js +54 -0
- package/dist/i18n/registry.d.ts +36 -0
- package/dist/i18n/registry.js +82 -0
- package/dist/i18n/types.d.ts +65 -0
- package/dist/i18n/types.js +5 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +104 -0
- package/dist/input.d.ts +40 -0
- package/dist/input.js +211 -0
- package/dist/menu-core.d.ts +52 -0
- package/dist/menu-core.js +201 -0
- package/dist/menu-multi.d.ts +21 -0
- package/dist/menu-multi.js +119 -0
- package/dist/menu-single.d.ts +18 -0
- package/dist/menu-single.js +138 -0
- package/dist/menu.d.ts +66 -0
- package/dist/menu.js +78 -0
- package/dist/types/display.types.d.ts +57 -0
- package/dist/types/display.types.js +5 -0
- package/dist/types/input.types.d.ts +88 -0
- package/dist/types/input.types.js +5 -0
- package/dist/types/layout.types.d.ts +56 -0
- package/dist/types/layout.types.js +36 -0
- package/dist/types/menu.types.d.ts +85 -0
- package/dist/types/menu.types.js +5 -0
- package/dist/types.d.ts +49 -0
- package/dist/types.js +5 -0
- package/package.json +35 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI Menu Kit - Main Entry Point
|
|
4
|
+
* A comprehensive, modular menu system for Node.js CLI applications
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.applyGradient = exports.createGradient = exports.colors = exports.getCurrentLanguageMap = exports.getAvailableLanguages = exports.registerLanguage = exports.t = exports.setLanguage = exports.getCurrentLanguage = exports.showCommandHelp = exports.getAvailableCommands = exports.handleCommand = exports.parseCommand = exports.isCommand = exports.clearCustomCommands = exports.unregisterCommand = exports.registerCommand = exports.createWizard = exports.runWizard = exports.createSimpleSummary = exports.createSummaryTable = exports.renderSummaryTable = exports.createMessage = exports.showQuestion = exports.showInfo = exports.showWarning = exports.showError = exports.showSuccess = exports.renderMessage = exports.createStageSeparator = exports.createStageHeader = exports.createProgressIndicator = exports.renderStageSeparator = exports.renderStageHeader = exports.renderProgressIndicator = exports.createAsciiHeader = exports.createSimpleHeader = exports.renderAsciiHeader = exports.renderSimpleHeader = exports.showModifyField = exports.showLanguageSelector = exports.showNumberInput = exports.showTextInput = exports.showBooleanMenu = exports.showCheckboxMenu = exports.showRadioMenu = exports.default = exports.wizard = exports.input = exports.menu = void 0;
|
|
25
|
+
exports.LAYOUT_PRESETS = exports.KEY_CODES = exports.colorize = void 0;
|
|
26
|
+
// Export unified API
|
|
27
|
+
var api_js_1 = require("./api.js");
|
|
28
|
+
Object.defineProperty(exports, "menu", { enumerable: true, get: function () { return api_js_1.menuAPI; } });
|
|
29
|
+
Object.defineProperty(exports, "input", { enumerable: true, get: function () { return api_js_1.inputAPI; } });
|
|
30
|
+
Object.defineProperty(exports, "wizard", { enumerable: true, get: function () { return api_js_1.wizardAPI; } });
|
|
31
|
+
var api_js_2 = require("./api.js");
|
|
32
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(api_js_2).default; } });
|
|
33
|
+
// Export menu components
|
|
34
|
+
var index_js_1 = require("./components/menus/index.js");
|
|
35
|
+
Object.defineProperty(exports, "showRadioMenu", { enumerable: true, get: function () { return index_js_1.showRadioMenu; } });
|
|
36
|
+
Object.defineProperty(exports, "showCheckboxMenu", { enumerable: true, get: function () { return index_js_1.showCheckboxMenu; } });
|
|
37
|
+
Object.defineProperty(exports, "showBooleanMenu", { enumerable: true, get: function () { return index_js_1.showBooleanMenu; } });
|
|
38
|
+
// Export input components
|
|
39
|
+
var index_js_2 = require("./components/inputs/index.js");
|
|
40
|
+
Object.defineProperty(exports, "showTextInput", { enumerable: true, get: function () { return index_js_2.showTextInput; } });
|
|
41
|
+
Object.defineProperty(exports, "showNumberInput", { enumerable: true, get: function () { return index_js_2.showNumberInput; } });
|
|
42
|
+
Object.defineProperty(exports, "showLanguageSelector", { enumerable: true, get: function () { return index_js_2.showLanguageSelector; } });
|
|
43
|
+
Object.defineProperty(exports, "showModifyField", { enumerable: true, get: function () { return index_js_2.showModifyField; } });
|
|
44
|
+
// Export display components
|
|
45
|
+
var index_js_3 = require("./components/display/index.js");
|
|
46
|
+
Object.defineProperty(exports, "renderSimpleHeader", { enumerable: true, get: function () { return index_js_3.renderSimpleHeader; } });
|
|
47
|
+
Object.defineProperty(exports, "renderAsciiHeader", { enumerable: true, get: function () { return index_js_3.renderAsciiHeader; } });
|
|
48
|
+
Object.defineProperty(exports, "createSimpleHeader", { enumerable: true, get: function () { return index_js_3.createSimpleHeader; } });
|
|
49
|
+
Object.defineProperty(exports, "createAsciiHeader", { enumerable: true, get: function () { return index_js_3.createAsciiHeader; } });
|
|
50
|
+
Object.defineProperty(exports, "renderProgressIndicator", { enumerable: true, get: function () { return index_js_3.renderProgressIndicator; } });
|
|
51
|
+
Object.defineProperty(exports, "renderStageHeader", { enumerable: true, get: function () { return index_js_3.renderStageHeader; } });
|
|
52
|
+
Object.defineProperty(exports, "renderStageSeparator", { enumerable: true, get: function () { return index_js_3.renderStageSeparator; } });
|
|
53
|
+
Object.defineProperty(exports, "createProgressIndicator", { enumerable: true, get: function () { return index_js_3.createProgressIndicator; } });
|
|
54
|
+
Object.defineProperty(exports, "createStageHeader", { enumerable: true, get: function () { return index_js_3.createStageHeader; } });
|
|
55
|
+
Object.defineProperty(exports, "createStageSeparator", { enumerable: true, get: function () { return index_js_3.createStageSeparator; } });
|
|
56
|
+
Object.defineProperty(exports, "renderMessage", { enumerable: true, get: function () { return index_js_3.renderMessage; } });
|
|
57
|
+
Object.defineProperty(exports, "showSuccess", { enumerable: true, get: function () { return index_js_3.showSuccess; } });
|
|
58
|
+
Object.defineProperty(exports, "showError", { enumerable: true, get: function () { return index_js_3.showError; } });
|
|
59
|
+
Object.defineProperty(exports, "showWarning", { enumerable: true, get: function () { return index_js_3.showWarning; } });
|
|
60
|
+
Object.defineProperty(exports, "showInfo", { enumerable: true, get: function () { return index_js_3.showInfo; } });
|
|
61
|
+
Object.defineProperty(exports, "showQuestion", { enumerable: true, get: function () { return index_js_3.showQuestion; } });
|
|
62
|
+
Object.defineProperty(exports, "createMessage", { enumerable: true, get: function () { return index_js_3.createMessage; } });
|
|
63
|
+
Object.defineProperty(exports, "renderSummaryTable", { enumerable: true, get: function () { return index_js_3.renderSummaryTable; } });
|
|
64
|
+
Object.defineProperty(exports, "createSummaryTable", { enumerable: true, get: function () { return index_js_3.createSummaryTable; } });
|
|
65
|
+
Object.defineProperty(exports, "createSimpleSummary", { enumerable: true, get: function () { return index_js_3.createSimpleSummary; } });
|
|
66
|
+
// Export features
|
|
67
|
+
var wizard_js_1 = require("./features/wizard.js");
|
|
68
|
+
Object.defineProperty(exports, "runWizard", { enumerable: true, get: function () { return wizard_js_1.runWizard; } });
|
|
69
|
+
Object.defineProperty(exports, "createWizard", { enumerable: true, get: function () { return wizard_js_1.createWizard; } });
|
|
70
|
+
var commands_js_1 = require("./features/commands.js");
|
|
71
|
+
Object.defineProperty(exports, "registerCommand", { enumerable: true, get: function () { return commands_js_1.registerCommand; } });
|
|
72
|
+
Object.defineProperty(exports, "unregisterCommand", { enumerable: true, get: function () { return commands_js_1.unregisterCommand; } });
|
|
73
|
+
Object.defineProperty(exports, "clearCustomCommands", { enumerable: true, get: function () { return commands_js_1.clearCustomCommands; } });
|
|
74
|
+
Object.defineProperty(exports, "isCommand", { enumerable: true, get: function () { return commands_js_1.isCommand; } });
|
|
75
|
+
Object.defineProperty(exports, "parseCommand", { enumerable: true, get: function () { return commands_js_1.parseCommand; } });
|
|
76
|
+
Object.defineProperty(exports, "handleCommand", { enumerable: true, get: function () { return commands_js_1.handleCommand; } });
|
|
77
|
+
Object.defineProperty(exports, "getAvailableCommands", { enumerable: true, get: function () { return commands_js_1.getAvailableCommands; } });
|
|
78
|
+
Object.defineProperty(exports, "showCommandHelp", { enumerable: true, get: function () { return commands_js_1.showCommandHelp; } });
|
|
79
|
+
// Export i18n
|
|
80
|
+
var registry_js_1 = require("./i18n/registry.js");
|
|
81
|
+
Object.defineProperty(exports, "getCurrentLanguage", { enumerable: true, get: function () { return registry_js_1.getCurrentLanguage; } });
|
|
82
|
+
Object.defineProperty(exports, "setLanguage", { enumerable: true, get: function () { return registry_js_1.setLanguage; } });
|
|
83
|
+
Object.defineProperty(exports, "t", { enumerable: true, get: function () { return registry_js_1.t; } });
|
|
84
|
+
Object.defineProperty(exports, "registerLanguage", { enumerable: true, get: function () { return registry_js_1.registerLanguage; } });
|
|
85
|
+
Object.defineProperty(exports, "getAvailableLanguages", { enumerable: true, get: function () { return registry_js_1.getAvailableLanguages; } });
|
|
86
|
+
Object.defineProperty(exports, "getCurrentLanguageMap", { enumerable: true, get: function () { return registry_js_1.getCurrentLanguageMap; } });
|
|
87
|
+
// Export core utilities (for advanced users)
|
|
88
|
+
var colors_js_1 = require("./core/colors.js");
|
|
89
|
+
Object.defineProperty(exports, "colors", { enumerable: true, get: function () { return colors_js_1.colors; } });
|
|
90
|
+
Object.defineProperty(exports, "createGradient", { enumerable: true, get: function () { return colors_js_1.createGradient; } });
|
|
91
|
+
Object.defineProperty(exports, "applyGradient", { enumerable: true, get: function () { return colors_js_1.applyGradient; } });
|
|
92
|
+
Object.defineProperty(exports, "colorize", { enumerable: true, get: function () { return colors_js_1.colorize; } });
|
|
93
|
+
var keyboard_js_1 = require("./core/keyboard.js");
|
|
94
|
+
Object.defineProperty(exports, "KEY_CODES", { enumerable: true, get: function () { return keyboard_js_1.KEY_CODES; } });
|
|
95
|
+
var layout_types_js_1 = require("./types/layout.types.js");
|
|
96
|
+
Object.defineProperty(exports, "LAYOUT_PRESETS", { enumerable: true, get: function () { return layout_types_js_1.LAYOUT_PRESETS; } });
|
|
97
|
+
// Legacy exports (for backward compatibility)
|
|
98
|
+
__exportStar(require("./types"), exports);
|
|
99
|
+
__exportStar(require("./components"), exports);
|
|
100
|
+
__exportStar(require("./menu-core"), exports);
|
|
101
|
+
__exportStar(require("./menu-single"), exports);
|
|
102
|
+
__exportStar(require("./menu-multi"), exports);
|
|
103
|
+
__exportStar(require("./input"), exports);
|
|
104
|
+
__exportStar(require("./menu"), exports);
|
package/dist/input.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Menu Kit - Input Components
|
|
3
|
+
* Handles user input interactions
|
|
4
|
+
*/
|
|
5
|
+
export interface InputOptions {
|
|
6
|
+
lang?: 'zh' | 'en';
|
|
7
|
+
defaultValue?: string;
|
|
8
|
+
indent?: string;
|
|
9
|
+
validator?: (input: string) => boolean | string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Ask for user text input with optional default value and validation
|
|
13
|
+
*
|
|
14
|
+
* @param prompt - Prompt text
|
|
15
|
+
* @param options - Input options
|
|
16
|
+
* @returns User input or default value
|
|
17
|
+
*/
|
|
18
|
+
export declare function askInput(prompt: string, options?: InputOptions): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Ask Yes/No question with interactive selection
|
|
21
|
+
*
|
|
22
|
+
* @param prompt - Question prompt
|
|
23
|
+
* @param options - Options
|
|
24
|
+
* @returns true for Yes, false for No
|
|
25
|
+
*/
|
|
26
|
+
export declare function askYesNo(prompt: string, options?: {
|
|
27
|
+
lang?: 'zh' | 'en';
|
|
28
|
+
defaultYes?: boolean;
|
|
29
|
+
}): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Ask for number input with validation
|
|
32
|
+
*
|
|
33
|
+
* @param prompt - Prompt text
|
|
34
|
+
* @param options - Input options
|
|
35
|
+
* @returns Number input
|
|
36
|
+
*/
|
|
37
|
+
export declare function askNumber(prompt: string, options?: InputOptions & {
|
|
38
|
+
min?: number;
|
|
39
|
+
max?: number;
|
|
40
|
+
}): Promise<number>;
|
package/dist/input.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI Menu Kit - Input Components
|
|
4
|
+
* Handles user input interactions
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.askInput = askInput;
|
|
8
|
+
exports.askYesNo = askYesNo;
|
|
9
|
+
exports.askNumber = askNumber;
|
|
10
|
+
const components_1 = require("./components");
|
|
11
|
+
/**
|
|
12
|
+
* Ask for user text input with optional default value and validation
|
|
13
|
+
*
|
|
14
|
+
* @param prompt - Prompt text
|
|
15
|
+
* @param options - Input options
|
|
16
|
+
* @returns User input or default value
|
|
17
|
+
*/
|
|
18
|
+
async function askInput(prompt, options = {}) {
|
|
19
|
+
const { lang = 'zh', defaultValue = '', indent = ' ', validator } = options;
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const stdin = process.stdin;
|
|
22
|
+
const stdout = process.stdout;
|
|
23
|
+
// Show prompt with grayed default value hint
|
|
24
|
+
const promptLine = `${indent}${prompt}: `;
|
|
25
|
+
const defaultHint = defaultValue
|
|
26
|
+
? `${components_1.theme.muted}(${lang === 'zh' ? '默认' : 'default'}: ${defaultValue})${components_1.colors.reset} `
|
|
27
|
+
: '';
|
|
28
|
+
stdout.write(promptLine);
|
|
29
|
+
stdout.write(defaultHint);
|
|
30
|
+
// Hide cursor to avoid visual artifacts
|
|
31
|
+
stdout.write('\x1b[?25l');
|
|
32
|
+
let input = '';
|
|
33
|
+
stdin.setRawMode(true);
|
|
34
|
+
stdin.resume();
|
|
35
|
+
stdin.setEncoding('utf8');
|
|
36
|
+
const onData = (key) => {
|
|
37
|
+
// Ctrl+C
|
|
38
|
+
if (key === '\u0003') {
|
|
39
|
+
stdin.setRawMode(false);
|
|
40
|
+
stdin.removeListener('data', onData);
|
|
41
|
+
// Clear current line
|
|
42
|
+
stdout.write('\r\x1b[K');
|
|
43
|
+
// Show cursor
|
|
44
|
+
stdout.write('\x1b[?25h');
|
|
45
|
+
// Show goodbye message
|
|
46
|
+
(0, components_1.showGoodbye)(lang);
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
// Enter
|
|
50
|
+
if (key === '\r' || key === '\n') {
|
|
51
|
+
const result = input.trim() || defaultValue;
|
|
52
|
+
// Validate if validator provided
|
|
53
|
+
if (validator) {
|
|
54
|
+
const validationResult = validator(result);
|
|
55
|
+
if (validationResult !== true) {
|
|
56
|
+
// Show error and continue input
|
|
57
|
+
const errorMsg = typeof validationResult === 'string'
|
|
58
|
+
? validationResult
|
|
59
|
+
: (lang === 'zh' ? '输入无效' : 'Invalid input');
|
|
60
|
+
stdout.write(`\n${indent}${components_1.colors.red}${errorMsg}${components_1.colors.reset}\n`);
|
|
61
|
+
stdout.write(promptLine);
|
|
62
|
+
stdout.write(defaultHint);
|
|
63
|
+
stdout.write(' ');
|
|
64
|
+
stdout.write(input);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
stdin.setRawMode(false);
|
|
69
|
+
stdin.removeListener('data', onData);
|
|
70
|
+
// Clear the entire input line
|
|
71
|
+
stdout.write('\r\x1b[K');
|
|
72
|
+
// Show cursor
|
|
73
|
+
stdout.write('\x1b[?25h');
|
|
74
|
+
resolve(result);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Backspace / Delete
|
|
78
|
+
if (key === '\u007F' || key === '\b') {
|
|
79
|
+
if (input.length > 0) {
|
|
80
|
+
input = input.slice(0, -1);
|
|
81
|
+
// Clear line and redraw
|
|
82
|
+
stdout.write('\r\x1b[K');
|
|
83
|
+
stdout.write(promptLine);
|
|
84
|
+
stdout.write(defaultHint);
|
|
85
|
+
stdout.write(' ');
|
|
86
|
+
stdout.write(input);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Ignore escape sequences (arrow keys, etc.)
|
|
91
|
+
if (key.charCodeAt(0) === 27) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Normal character input
|
|
95
|
+
if (key.charCodeAt(0) >= 32) {
|
|
96
|
+
input += key;
|
|
97
|
+
// Clear line and redraw to handle multi-byte characters properly
|
|
98
|
+
stdout.write('\r\x1b[K');
|
|
99
|
+
stdout.write(promptLine);
|
|
100
|
+
stdout.write(defaultHint);
|
|
101
|
+
stdout.write(' ');
|
|
102
|
+
stdout.write(input);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
stdin.on('data', onData);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Ask Yes/No question with interactive selection
|
|
110
|
+
*
|
|
111
|
+
* @param prompt - Question prompt
|
|
112
|
+
* @param options - Options
|
|
113
|
+
* @returns true for Yes, false for No
|
|
114
|
+
*/
|
|
115
|
+
async function askYesNo(prompt, options = {}) {
|
|
116
|
+
const { lang = 'zh', defaultYes = true } = options;
|
|
117
|
+
const optionLabels = lang === 'zh' ? ['是', '否'] : ['Yes', 'No'];
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
let selectedIndex = defaultYes ? 0 : 1;
|
|
120
|
+
const stdin = process.stdin;
|
|
121
|
+
stdin.setRawMode(true);
|
|
122
|
+
stdin.resume();
|
|
123
|
+
stdin.setEncoding('utf8');
|
|
124
|
+
// Hide cursor
|
|
125
|
+
process.stdout.write('\x1b[?25l');
|
|
126
|
+
const render = () => {
|
|
127
|
+
// Clear current line
|
|
128
|
+
process.stdout.write('\r\x1b[K');
|
|
129
|
+
// Render prompt with question symbol
|
|
130
|
+
process.stdout.write(` ${components_1.symbols.warning.color}${components_1.symbols.warning.icon} ${components_1.colors.reset}${prompt} `);
|
|
131
|
+
// Render options
|
|
132
|
+
optionLabels.forEach((option, index) => {
|
|
133
|
+
const isSelected = index === selectedIndex;
|
|
134
|
+
if (isSelected) {
|
|
135
|
+
process.stdout.write(`${components_1.theme.active}${option}${components_1.colors.reset}`);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
process.stdout.write(`${components_1.theme.muted}${option}${components_1.colors.reset}`);
|
|
139
|
+
}
|
|
140
|
+
if (index < optionLabels.length - 1) {
|
|
141
|
+
process.stdout.write(` ${components_1.theme.muted}|${components_1.colors.reset} `);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
const cleanup = (result) => {
|
|
146
|
+
stdin.setRawMode(false);
|
|
147
|
+
stdin.removeListener('data', onKeyPress);
|
|
148
|
+
process.stdout.write('\r\x1b[K'); // Clear current line
|
|
149
|
+
process.stdout.write('\x1b[?25h'); // Show cursor
|
|
150
|
+
resolve(result);
|
|
151
|
+
};
|
|
152
|
+
const onKeyPress = (key) => {
|
|
153
|
+
if (key === '\u001b[C') { // Right arrow
|
|
154
|
+
selectedIndex = (selectedIndex + 1) % optionLabels.length;
|
|
155
|
+
render();
|
|
156
|
+
}
|
|
157
|
+
else if (key === '\u001b[D') { // Left arrow
|
|
158
|
+
selectedIndex = (selectedIndex - 1 + optionLabels.length) % optionLabels.length;
|
|
159
|
+
render();
|
|
160
|
+
}
|
|
161
|
+
else if (key === '\r') { // Enter
|
|
162
|
+
cleanup(selectedIndex === 0); // 0 = Yes/是, 1 = No/否
|
|
163
|
+
}
|
|
164
|
+
else if (key === '\u001b' || key === '\u001b[') { // Esc
|
|
165
|
+
cleanup(false); // Default to No on Esc
|
|
166
|
+
}
|
|
167
|
+
else if (key === '\u0003') { // Ctrl+C
|
|
168
|
+
stdin.setRawMode(false);
|
|
169
|
+
stdin.removeListener('data', onKeyPress);
|
|
170
|
+
// Clear current line
|
|
171
|
+
process.stdout.write('\r\x1b[K');
|
|
172
|
+
// Show cursor
|
|
173
|
+
process.stdout.write('\x1b[?25h');
|
|
174
|
+
// Show goodbye message
|
|
175
|
+
(0, components_1.showGoodbye)(lang);
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Ignore all other keys - re-render to clear any echo
|
|
180
|
+
render();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
stdin.on('data', onKeyPress);
|
|
184
|
+
render();
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Ask for number input with validation
|
|
189
|
+
*
|
|
190
|
+
* @param prompt - Prompt text
|
|
191
|
+
* @param options - Input options
|
|
192
|
+
* @returns Number input
|
|
193
|
+
*/
|
|
194
|
+
async function askNumber(prompt, options = {}) {
|
|
195
|
+
const { lang = 'zh', min, max } = options;
|
|
196
|
+
const validator = (input) => {
|
|
197
|
+
const num = parseFloat(input);
|
|
198
|
+
if (isNaN(num)) {
|
|
199
|
+
return lang === 'zh' ? '请输入有效的数字' : 'Please enter a valid number';
|
|
200
|
+
}
|
|
201
|
+
if (min !== undefined && num < min) {
|
|
202
|
+
return lang === 'zh' ? `数字不能小于 ${min}` : `Number cannot be less than ${min}`;
|
|
203
|
+
}
|
|
204
|
+
if (max !== undefined && num > max) {
|
|
205
|
+
return lang === 'zh' ? `数字不能大于 ${max}` : `Number cannot be greater than ${max}`;
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
};
|
|
209
|
+
const result = await askInput(prompt, { ...options, validator });
|
|
210
|
+
return parseFloat(result);
|
|
211
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Menu Kit - Core Utilities
|
|
3
|
+
* Shared rendering and terminal control functions
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Terminal control utilities
|
|
7
|
+
*/
|
|
8
|
+
export interface TerminalState {
|
|
9
|
+
stdin: NodeJS.ReadStream;
|
|
10
|
+
renderedLines: number;
|
|
11
|
+
isFirstRender: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function setupTerminal(): TerminalState;
|
|
14
|
+
export declare function cleanupTerminal(state: TerminalState, onData: (key: string) => void): void;
|
|
15
|
+
export declare function moveCursorUp(state: TerminalState): void;
|
|
16
|
+
/**
|
|
17
|
+
* Header rendering
|
|
18
|
+
*/
|
|
19
|
+
export declare function renderHeader(title: string | undefined): number;
|
|
20
|
+
/**
|
|
21
|
+
* Option rendering for single-select menus
|
|
22
|
+
*/
|
|
23
|
+
export interface RenderOptionConfig {
|
|
24
|
+
index: number;
|
|
25
|
+
isSelected: boolean;
|
|
26
|
+
showNumber?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare function renderSingleOption(option: string | {
|
|
29
|
+
label: string;
|
|
30
|
+
value?: any;
|
|
31
|
+
}, config: RenderOptionConfig): void;
|
|
32
|
+
/**
|
|
33
|
+
* Keyboard navigation helpers
|
|
34
|
+
*/
|
|
35
|
+
export declare function handleVerticalNavigation(key: string, currentIndex: number, maxIndex: number): number | null;
|
|
36
|
+
export declare function handleNumberInput(key: string, maxNumber: number): number | null;
|
|
37
|
+
export declare function handleLetterInput(key: string, options: Array<string | {
|
|
38
|
+
label: string;
|
|
39
|
+
value?: any;
|
|
40
|
+
}>): number | null;
|
|
41
|
+
/**
|
|
42
|
+
* Input prompt rendering
|
|
43
|
+
*/
|
|
44
|
+
export declare function renderInputPrompt(text: string, showCursor?: boolean): void;
|
|
45
|
+
/**
|
|
46
|
+
* Hints rendering
|
|
47
|
+
*/
|
|
48
|
+
export declare function renderHints(hintText: string): number;
|
|
49
|
+
/**
|
|
50
|
+
* Graceful exit with goodbye message
|
|
51
|
+
*/
|
|
52
|
+
export declare function exitWithGoodbye(state: TerminalState, onData: (key: string) => void, showGoodbyeFn: () => void): void;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI Menu Kit - Core Utilities
|
|
4
|
+
* Shared rendering and terminal control functions
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.setupTerminal = setupTerminal;
|
|
8
|
+
exports.cleanupTerminal = cleanupTerminal;
|
|
9
|
+
exports.moveCursorUp = moveCursorUp;
|
|
10
|
+
exports.renderHeader = renderHeader;
|
|
11
|
+
exports.renderSingleOption = renderSingleOption;
|
|
12
|
+
exports.handleVerticalNavigation = handleVerticalNavigation;
|
|
13
|
+
exports.handleNumberInput = handleNumberInput;
|
|
14
|
+
exports.handleLetterInput = handleLetterInput;
|
|
15
|
+
exports.renderInputPrompt = renderInputPrompt;
|
|
16
|
+
exports.renderHints = renderHints;
|
|
17
|
+
exports.exitWithGoodbye = exitWithGoodbye;
|
|
18
|
+
const components_1 = require("./components");
|
|
19
|
+
function setupTerminal() {
|
|
20
|
+
const stdin = process.stdin;
|
|
21
|
+
stdin.setRawMode(true);
|
|
22
|
+
stdin.resume();
|
|
23
|
+
stdin.setEncoding('utf8');
|
|
24
|
+
// Hide cursor
|
|
25
|
+
process.stdout.write('\x1b[?25l');
|
|
26
|
+
return {
|
|
27
|
+
stdin,
|
|
28
|
+
renderedLines: 0,
|
|
29
|
+
isFirstRender: true
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function cleanupTerminal(state, onData) {
|
|
33
|
+
state.stdin.setRawMode(false);
|
|
34
|
+
state.stdin.removeListener('data', onData);
|
|
35
|
+
// Clear rendered content
|
|
36
|
+
if (state.renderedLines > 0) {
|
|
37
|
+
process.stdout.write(`\x1b[${state.renderedLines}A`);
|
|
38
|
+
process.stdout.write('\x1b[J');
|
|
39
|
+
}
|
|
40
|
+
// Show cursor
|
|
41
|
+
process.stdout.write('\x1b[?25h');
|
|
42
|
+
}
|
|
43
|
+
function moveCursorUp(state) {
|
|
44
|
+
if (!state.isFirstRender && state.renderedLines > 0) {
|
|
45
|
+
process.stdout.write(`\x1b[${state.renderedLines}A`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Header rendering
|
|
50
|
+
*/
|
|
51
|
+
function renderHeader(title) {
|
|
52
|
+
if (!title)
|
|
53
|
+
return 0;
|
|
54
|
+
const titleLines = title.split('\n');
|
|
55
|
+
titleLines.forEach(line => {
|
|
56
|
+
process.stdout.write('\x1b[2K\r');
|
|
57
|
+
console.log(` ${components_1.theme.primary}${line}${components_1.colors.reset}`);
|
|
58
|
+
});
|
|
59
|
+
process.stdout.write('\x1b[2K');
|
|
60
|
+
console.log();
|
|
61
|
+
return titleLines.length + 1; // +1 for empty line
|
|
62
|
+
}
|
|
63
|
+
function renderSingleOption(option, config) {
|
|
64
|
+
const { index, isSelected, showNumber = true } = config;
|
|
65
|
+
process.stdout.write('\x1b[2K\r');
|
|
66
|
+
const prefix = isSelected ? `${components_1.theme.active}❯ ` : ' ';
|
|
67
|
+
const numColor = isSelected ? components_1.theme.active : components_1.theme.primary;
|
|
68
|
+
const titleColor = isSelected ? components_1.theme.active : components_1.theme.title;
|
|
69
|
+
if (typeof option === 'string') {
|
|
70
|
+
// Check if option already has number prefix
|
|
71
|
+
const numMatch = option.match(/^(\d+\.\s*)(.+)$/);
|
|
72
|
+
if (numMatch) {
|
|
73
|
+
const num = numMatch[1];
|
|
74
|
+
const rest = numMatch[2];
|
|
75
|
+
const descMatch = rest.match(/^([^-]+)(\s*-\s*.+)?$/);
|
|
76
|
+
if (descMatch && descMatch[2]) {
|
|
77
|
+
const title = descMatch[1];
|
|
78
|
+
const desc = descMatch[2];
|
|
79
|
+
console.log(`${prefix}${numColor}${num}${titleColor}${title}${components_1.theme.muted}${desc}${components_1.colors.reset}`);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log(`${prefix}${numColor}${num}${titleColor}${rest}${components_1.colors.reset}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// No number prefix, add one if showNumber is true
|
|
87
|
+
const match = option.match(/^([^-]+)(\s*-\s*.+)?$/);
|
|
88
|
+
if (match && match[2]) {
|
|
89
|
+
const title = match[1];
|
|
90
|
+
const desc = match[2];
|
|
91
|
+
const numPrefix = showNumber ? `${numColor}${index + 1}.${components_1.colors.reset} ` : '';
|
|
92
|
+
console.log(`${prefix}${numPrefix}${titleColor}${title}${components_1.theme.muted}${desc}${components_1.colors.reset}`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const numPrefix = showNumber ? `${numColor}${index + 1}.${components_1.colors.reset} ` : '';
|
|
96
|
+
console.log(`${prefix}${numPrefix}${titleColor}${option}${components_1.colors.reset}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (option.label) {
|
|
101
|
+
// Handle MenuOption objects
|
|
102
|
+
const match = option.label.match(/^([^.]+\.\s*)([^-]+)(\s*-\s*.+)?$/);
|
|
103
|
+
if (match) {
|
|
104
|
+
const num = match[1];
|
|
105
|
+
const title = match[2];
|
|
106
|
+
const desc = match[3] || '';
|
|
107
|
+
console.log(`${prefix}${numColor}${num}${titleColor}${title}${components_1.theme.muted}${desc}${components_1.colors.reset}`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const numPrefix = showNumber ? `${numColor}${index + 1}.${components_1.colors.reset} ` : '';
|
|
111
|
+
console.log(`${prefix}${numPrefix}${titleColor}${option.label}${components_1.colors.reset}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Keyboard navigation helpers
|
|
117
|
+
*/
|
|
118
|
+
function handleVerticalNavigation(key, currentIndex, maxIndex) {
|
|
119
|
+
if (key === '\u001b[A') {
|
|
120
|
+
// Up arrow
|
|
121
|
+
return currentIndex > 0 ? currentIndex - 1 : maxIndex;
|
|
122
|
+
}
|
|
123
|
+
else if (key === '\u001b[B') {
|
|
124
|
+
// Down arrow
|
|
125
|
+
return currentIndex < maxIndex ? currentIndex + 1 : 0;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
function handleNumberInput(key, maxNumber) {
|
|
130
|
+
if (key.match(/^[0-9]$/)) {
|
|
131
|
+
const num = parseInt(key);
|
|
132
|
+
if (num >= 1 && num <= maxNumber) {
|
|
133
|
+
return num - 1; // Convert to 0-based index
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
function handleLetterInput(key, options) {
|
|
139
|
+
if (key.match(/^[a-zA-Z]$/)) {
|
|
140
|
+
const letter = key.toUpperCase();
|
|
141
|
+
for (let i = 0; i < options.length; i++) {
|
|
142
|
+
const option = options[i];
|
|
143
|
+
if (typeof option !== 'string' && option.label) {
|
|
144
|
+
const match = option.label.match(/^([A-Z])\./);
|
|
145
|
+
if (match && match[1] === letter) {
|
|
146
|
+
return i;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Input prompt rendering
|
|
155
|
+
*/
|
|
156
|
+
function renderInputPrompt(text, showCursor = true) {
|
|
157
|
+
process.stdout.write('\x1b[2K');
|
|
158
|
+
console.log();
|
|
159
|
+
process.stdout.write('\x1b[2K\r');
|
|
160
|
+
process.stdout.write(` ${components_1.theme.muted}${text}${components_1.colors.reset}`);
|
|
161
|
+
if (showCursor) {
|
|
162
|
+
process.stdout.write(`${components_1.theme.active}_${components_1.colors.reset}`);
|
|
163
|
+
}
|
|
164
|
+
console.log();
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Hints rendering
|
|
168
|
+
*/
|
|
169
|
+
function renderHints(hintText) {
|
|
170
|
+
if (!hintText)
|
|
171
|
+
return 0;
|
|
172
|
+
process.stdout.write('\x1b[2K');
|
|
173
|
+
console.log();
|
|
174
|
+
const indent = ' ';
|
|
175
|
+
const indentedHint = hintText.split('\n').map(line => indent + line).join('\n');
|
|
176
|
+
const hintLinesArray = indentedHint.split('\n');
|
|
177
|
+
hintLinesArray.forEach(line => {
|
|
178
|
+
process.stdout.write('\x1b[2K\r');
|
|
179
|
+
console.log(line);
|
|
180
|
+
});
|
|
181
|
+
return 1 + hintLinesArray.length; // +1 for empty line before hints
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Graceful exit with goodbye message
|
|
185
|
+
*/
|
|
186
|
+
function exitWithGoodbye(state, onData, showGoodbyeFn) {
|
|
187
|
+
// Clean up terminal
|
|
188
|
+
state.stdin.setRawMode(false);
|
|
189
|
+
state.stdin.removeListener('data', onData);
|
|
190
|
+
// Clear rendered content
|
|
191
|
+
if (state.renderedLines > 0) {
|
|
192
|
+
process.stdout.write(`\x1b[${state.renderedLines}A`);
|
|
193
|
+
process.stdout.write('\x1b[J');
|
|
194
|
+
}
|
|
195
|
+
// Show cursor
|
|
196
|
+
process.stdout.write('\x1b[?25h');
|
|
197
|
+
// Show goodbye message
|
|
198
|
+
showGoodbyeFn();
|
|
199
|
+
// Exit
|
|
200
|
+
process.exit(0);
|
|
201
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Menu Kit - Multi Select Menu
|
|
3
|
+
* Interactive multi-select menu with checkboxes
|
|
4
|
+
*/
|
|
5
|
+
import { MultiSelectConfig } from './types';
|
|
6
|
+
/**
|
|
7
|
+
* Interactive multi-select menu with checkboxes
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Arrow keys (↑/↓) to navigate
|
|
11
|
+
* - Space to toggle selection
|
|
12
|
+
* - A to select all
|
|
13
|
+
* - I to invert selection
|
|
14
|
+
* - Enter to confirm
|
|
15
|
+
* - Shows ◉ for selected items, ○ for unselected items
|
|
16
|
+
*
|
|
17
|
+
* @param options - Menu options
|
|
18
|
+
* @param config - Configuration object
|
|
19
|
+
* @returns Array of selected indices
|
|
20
|
+
*/
|
|
21
|
+
export declare function selectMultiMenu(options: string[], config?: MultiSelectConfig): Promise<number[]>;
|