mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 20:20:09 +00:00
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
112 lines
3.0 KiB
JavaScript
112 lines
3.0 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Simple Progress Indicator (no external dependencies)
|
|
*
|
|
* Features:
|
|
* - ASCII-only spinner frames (cross-platform compatible)
|
|
* - TTY detection (no spinners in pipes/logs)
|
|
* - Elapsed time display
|
|
* - CI environment detection
|
|
*/
|
|
|
|
class ProgressIndicator {
|
|
/**
|
|
* Create a progress indicator
|
|
* @param {string} message - Message to display
|
|
* @param {Object} options - Options
|
|
* @param {string[]} options.frames - Spinner frames (default: ASCII)
|
|
* @param {number} options.interval - Frame interval in ms (default: 80)
|
|
*/
|
|
constructor(message, options = {}) {
|
|
this.message = message;
|
|
// ASCII-only frames for cross-platform compatibility
|
|
this.frames = options.frames || ['|', '/', '-', '\\'];
|
|
this.frameIndex = 0;
|
|
this.interval = null;
|
|
this.startTime = Date.now();
|
|
|
|
// TTY detection: only animate if stderr is TTY and not in CI
|
|
this.isTTY = process.stderr.isTTY === true && !process.env.CI && !process.env.NO_COLOR;
|
|
}
|
|
|
|
/**
|
|
* Start the spinner
|
|
*/
|
|
start() {
|
|
if (!this.isTTY) {
|
|
// Non-TTY: just print message once
|
|
process.stderr.write(`[i] ${this.message}...\n`);
|
|
return;
|
|
}
|
|
|
|
// TTY: animate spinner
|
|
this.interval = setInterval(() => {
|
|
const frame = this.frames[this.frameIndex];
|
|
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
|
process.stderr.write(`\r[${frame}] ${this.message}... (${elapsed}s)`);
|
|
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
}, 80); // 12.5fps for smooth animation
|
|
}
|
|
|
|
/**
|
|
* Stop spinner with success message
|
|
* @param {string} message - Optional success message (defaults to original message)
|
|
*/
|
|
succeed(message) {
|
|
this.stop();
|
|
const finalMessage = message || this.message;
|
|
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
|
|
|
if (this.isTTY) {
|
|
// Clear spinner line and show success
|
|
process.stderr.write(`\r[OK] ${finalMessage} (${elapsed}s)\n`);
|
|
} else {
|
|
// Non-TTY: just show completion
|
|
process.stderr.write(`[OK] ${finalMessage}\n`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop spinner with failure message
|
|
* @param {string} message - Optional failure message (defaults to original message)
|
|
*/
|
|
fail(message) {
|
|
this.stop();
|
|
const finalMessage = message || this.message;
|
|
|
|
if (this.isTTY) {
|
|
// Clear spinner line and show failure
|
|
process.stderr.write(`\r[X] ${finalMessage}\n`);
|
|
} else {
|
|
// Non-TTY: just show failure
|
|
process.stderr.write(`[X] ${finalMessage}\n`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update spinner message (while running)
|
|
* @param {string} newMessage - New message to display
|
|
*/
|
|
update(newMessage) {
|
|
this.message = newMessage;
|
|
}
|
|
|
|
/**
|
|
* Stop the spinner without showing success/failure
|
|
*/
|
|
stop() {
|
|
if (this.interval) {
|
|
clearInterval(this.interval);
|
|
this.interval = null;
|
|
|
|
if (this.isTTY) {
|
|
// Clear the spinner line
|
|
process.stderr.write('\r\x1b[K');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = { ProgressIndicator };
|