Files
ccs/bin/utils/prompt.js
T
Kai (Tam Nhu) TranandGitHub f40e9647ec feat(cli): comprehensive UX improvements for v3.5.0 (#7)
Implements 6-phase CLI UX improvement plan with comprehensive error handling, 
interactive features, and cross-platform consistency.

### Added
- Shell auto-completion (bash, zsh, PowerShell, Fish)
- Error codes (E101-E901) with documentation URLs
- Fuzzy matching "Did you mean?" suggestions (Levenshtein distance)
- Progress indicators (doctor [n/9] counter, GLMT proxy spinner)
- Interactive confirmation prompts with --yes/-y automation flag
- JSON output format (--json) for auth commands
- Impact display (session count, paths) before destructive operations
- Comprehensive test suite (15 tests, 100% pass rate)
- Complete error documentation in docs/errors/
- Cross-platform `--shell-completion` command

### Changed
- Error boxes: Unicode (╔═╗) → ASCII (===) for compatibility
- JSON output uses CCS version instead of schema version
- Help text includes EXAMPLES section across platforms
- Test suite properly counts test cases (not assertions)

### Fixed
- --yes flag bug (returned false instead of true)
- Help text consistency (added Uninstall section to bash)
- Test pass rate calculation (excludes skipped tests)
- Help section comparison (locale-specific sort)

### Testing
- 13/13 tests passing (2 legitimately skipped)
- Cross-platform verified (Node.js, bash, PowerShell)
- All error codes documented and tested
2025-11-15 01:26:50 -05:00

135 lines
3.5 KiB
JavaScript

'use strict';
const readline = require('readline');
/**
* Interactive Prompt Utilities (NO external dependencies)
*
* Features:
* - TTY detection (auto-confirm in non-TTY)
* - --yes flag support for automation
* - --no-input flag support for CI
* - Safe defaults (N for destructive actions)
* - Input validation with retry
*/
class InteractivePrompt {
/**
* Ask for confirmation
* @param {string} message - Confirmation message
* @param {Object} options - Options
* @param {boolean} options.default - Default value (true=Yes, false=No)
* @returns {Promise<boolean>} User confirmation
*/
static async confirm(message, options = {}) {
const { default: defaultValue = false } = options;
// Check for --yes flag (automation) - always returns true
if (process.env.CCS_YES === '1' || process.argv.includes('--yes') || process.argv.includes('-y')) {
return true;
}
// Check for --no-input flag (CI)
if (process.env.CCS_NO_INPUT === '1' || process.argv.includes('--no-input')) {
throw new Error('Interactive input required but --no-input specified');
}
// Non-TTY: use default
if (!process.stdin.isTTY) {
return defaultValue;
}
// Interactive prompt
const rl = readline.createInterface({
input: process.stdin,
output: process.stderr,
terminal: true
});
const promptText = defaultValue
? `${message} [Y/n]: `
: `${message} [y/N]: `;
return new Promise((resolve) => {
rl.question(promptText, (answer) => {
rl.close();
const normalized = answer.trim().toLowerCase();
// Empty answer: use default
if (normalized === '') {
resolve(defaultValue);
return;
}
// Valid answers
if (normalized === 'y' || normalized === 'yes') {
resolve(true);
return;
}
if (normalized === 'n' || normalized === 'no') {
resolve(false);
return;
}
// Invalid input: retry
console.error('[!] Please answer y or n');
resolve(InteractivePrompt.confirm(message, options));
});
});
}
/**
* Get text input from user
* @param {string} message - Prompt message
* @param {Object} options - Options
* @param {string} options.default - Default value
* @param {Function} options.validate - Validation function
* @returns {Promise<string>} User input
*/
static async input(message, options = {}) {
const { default: defaultValue = '', validate = null } = options;
// Non-TTY: use default or error
if (!process.stdin.isTTY) {
if (defaultValue) {
return defaultValue;
}
throw new Error('Interactive input required but stdin is not a TTY');
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stderr,
terminal: true
});
const promptText = defaultValue
? `${message} [${defaultValue}]: `
: `${message}: `;
return new Promise((resolve) => {
rl.question(promptText, (answer) => {
rl.close();
const value = answer.trim() || defaultValue;
// Validate input if validator provided
if (validate) {
const error = validate(value);
if (error) {
console.error(`[!] ${error}`);
resolve(InteractivePrompt.input(message, options));
return;
}
}
resolve(value);
});
});
}
}
module.exports = { InteractivePrompt };