UI Module
The UI module provides a set of components and utilities for creating a consistent and user-friendly command-line interface. This document provides an overview of the UI module and how to use it.
Overview
The UI module is designed to provide a consistent visual language across the CLI, making it easier to create a polished and professional user experience. It includes:
Theme System: A centralized theme system that defines colors, typography, symbols, and spacing.
Enhanced Logger: An enhanced logger that uses the theme system to create visually appealing log messages.
Interactive Elements: Spinners, progress bars, and interactive prompts for better user interaction.
Command Output Enhancements: Banners, tables, and JSON output for better command output.
Getting Started
To use the UI module, import it in your code:
import * as ui from "../ui";
Theme System
The theme system provides a consistent visual language across the CLI. It includes:
Colors
The theme system defines a set of colors for different purposes:
// Primary colors
ui.colors.primary; // Blue - Primary brand color
ui.colors.secondary; // Purple - Secondary brand color
// Status colors
ui.colors.success; // Green - Success messages
ui.colors.warning; // Yellow - Warning messages
ui.colors.error; // Red - Error messages
ui.colors.info; // Blue - Info messages
// Neutral colors
ui.colors.muted; // Gray - Muted text
ui.colors.subtle; // Light gray - Subtle text
// Text colors
ui.colors.heading; // White bold - Headings
ui.colors.text; // White - Normal text
// Special colors
ui.colors.highlight; // Yellow - Highlighted text
ui.colors.link; // Blue underlined - Links
Symbols
The theme system defines a set of symbols for different message types:
ui.symbols.info; // ℹ - Info messages
ui.symbols.success; // ✓ - Success messages
ui.symbols.warning; // ⚠ - Warning messages
ui.symbols.error; // ✖ - Error messages
ui.symbols.debug; // 🔍 - Debug messages
ui.symbols.verbose; // 🔎 - Verbose messages
ui.symbols.bullet; // • - Bullet points
ui.symbols.arrow; // → - Arrows
ui.symbols.pointer; // ❯ - Pointers
Typography
The theme system defines a set of typography styles for consistent text formatting:
ui.typography.heading1("Heading 1"); // Bold white text
ui.typography.heading2("Heading 2"); // Bold white text
ui.typography.heading3("Heading 3"); // Bold white text
ui.typography.text("Normal text"); // White text
ui.typography.muted("Muted text"); // Gray text
ui.typography.code("Code"); // Code with background
Spacing
The theme system defines a set of spacing constants for consistent layout:
ui.spacing.xs; // 2 spaces - Extra small spacing
ui.spacing.sm; // 4 spaces - Small spacing
ui.spacing.md; // 8 spaces - Medium spacing
ui.spacing.lg; // 12 spaces - Large spacing
ui.spacing.xl; // 16 spaces - Extra large spacing
Utility Functions
The theme system provides utility functions for common tasks:
// Create indentation
ui.indent(2); // 8 spaces (2 * spacing.sm)
// Create a horizontal line
ui.line(); // 80 characters of "─"
ui.line(40); // 40 characters of "─"
ui.line(40, "="); // 40 characters of "="
// Apply theme to a message
ui.applyTheme("info", "Hello, world!"); // Blue "ℹ Hello, world!"
Enhanced Logger
The enhanced logger provides a more visually appealing and structured logging experience. It includes:
Basic Logging
ui.logger.debug("Debug message"); // Gray "🔍 Debug message"
ui.logger.verbose("Verbose message"); // Gray "🔎 Verbose message"
ui.logger.info("Info message"); // Blue "ℹ Info message"
ui.logger.success("Success message"); // Green "✓ Success message"
ui.logger.warn("Warning message"); // Yellow "⚠ Warning message"
ui.logger.error("Error message"); // Red "✖ Error message"
Error Logging with Context
try {
// Some code that might throw an error
} catch (error) {
ui.logger.errorWithContext("Failed to process file", error, {
file: "example.txt",
line: 42,
});
// Red "✖ Failed to process file (file: example.txt, line: 42) Error: Something went wrong"
}
Grouped Logs
ui.logger.group("Configuration", () => {
ui.logger.info("Loading configuration...");
ui.logger.info("Configuration loaded successfully.");
});
// ▼ Configuration
// ℹ Loading configuration...
// ℹ Configuration loaded successfully.
Collapsed Groups
ui.logger.group(
"Details",
() => {
ui.logger.info("Some details...");
},
{ collapsed: true },
);
// ▶ Details
Indented Groups
ui.logger.group("Parent", () => {
ui.logger.info("Parent info");
ui.logger.group(
"Child",
() => {
ui.logger.info("Child info");
},
{ indentLevel: 1 },
);
});
// ▼ Parent
// ℹ Parent info
// ▼ Child
// ℹ Child info
JSON Output
// Set output format to JSON
ui.logger.setOutputFormat("json");
// Log messages
ui.logger.info("Info message");
// {"level":"info","message":"Info message","timestamp":"2025-04-30T10:00:00.000Z"}
// Reset output format to text
ui.logger.setOutputFormat("text");
Log Levels
// Set log level
ui.logger.setLogLevel(ui.logger.LogLevel.DEBUG); // Show all logs
ui.logger.setLogLevel(ui.logger.LogLevel.INFO); // Show info and above
ui.logger.setLogLevel(ui.logger.LogLevel.ERROR); // Show only errors
ui.logger.setLogLevel("debug"); // String version
// Get current log level
const level = ui.logger.getLogLevel(); // Returns LogLevel enum value
const levelName = ui.logger.getLogLevelName(); // Returns string (e.g., "INFO")
Best Practices
Use the Theme System
Always use the theme system to ensure a consistent visual language across the CLI. This makes it easier to maintain and update the UI in the future.
// Good
console.log(ui.colors.primary("Primary text"));
// Bad
console.log("\x1b[34mPrimary text\x1b[0m");
Use the Enhanced Logger
Use the enhanced logger for all logging to ensure a consistent logging experience. The enhanced logger provides better formatting, support for structured output, and log levels.
// Good
ui.logger.info("Info message");
// Bad
console.log("Info message");
Group Related Logs
Use log groups to organize related logs. This makes it easier to understand the structure of the output.
// Good
ui.logger.group("Configuration", () => {
ui.logger.info("Loading configuration...");
ui.logger.info("Configuration loaded successfully.");
});
// Bad
ui.logger.info("Configuration:");
ui.logger.info("Loading configuration...");
ui.logger.info("Configuration loaded successfully.");
Use Appropriate Log Levels
Use the appropriate log level for each message. This allows users to control the verbosity of the output.
// Debug information (only shown with --debug)
ui.logger.debug("Detailed debug information");
// Verbose information (only shown with --verbose)
ui.logger.verbose("More details than normal");
// Normal information
ui.logger.info("Normal information");
// Success messages
ui.logger.success("Operation completed successfully");
// Warning messages
ui.logger.warn("Something might be wrong");
// Error messages
ui.logger.error("Something went wrong");
Spinner Component
The spinner component provides a loading indicator for long-running operations. It uses the ora library for the actual spinner implementation.
Basic Usage
import * as ui from "../ui";
// Create and start a spinner
const spinner = new ui.Spinner({ text: "Loading..." }).start();
// Do some work
// ...
// Update the spinner text
spinner.text("Still loading...");
// Mark the spinner as succeeded
spinner.succeed("Loaded successfully!");
Spinner Options
const spinner = new ui.Spinner({
text: "Loading...", // Text to display next to the spinner
type: "dots", // Spinner type (default, dots, line, star, arrow, bouncingBar, bouncingBall)
color: "primary", // Color of the spinner (primary, secondary, success, warning, error, info, muted)
enabled: true, // Whether to show the spinner
});
Spinner Methods
// Start the spinner
spinner.start();
// Stop the spinner
spinner.stop();
// Update the spinner text
spinner.text("New text");
// Mark the spinner as succeeded
spinner.succeed("Success message");
// Mark the spinner as failed
spinner.fail("Error message");
// Mark the spinner as warned
spinner.warn("Warning message");
// Mark the spinner as information
spinner.info("Info message");
// Clear the spinner
spinner.clear();
// Reset the spinner
spinner.reset();
Static Methods
// Create and start a spinner
const spinner = ui.Spinner.start({ text: "Loading..." });
// Create a spinner for a promise
const result = await ui.Spinner.promise(
{ text: "Loading data..." },
fetchData(),
"Data loaded successfully!",
"Failed to load data",
);
Helper Functions
// Create and start a spinner
const spinner = ui.spinner({ text: "Loading..." });
// Create a spinner for a promise
const result = await ui.spinnerPromise(
{ text: "Loading data..." },
fetchData(),
"Data loaded successfully!",
"Failed to load data",
);
Progress Bar Component
The progress bar component provides a visual indicator for tracking progress in long-running operations. It uses the cli-progress library for the actual progress bar implementation.
Basic Usage
import * as ui from "../ui";
// Create and start a progress bar
const progressBar = new ui.ProgressBar({ total: 100 }).start();
// Update the progress bar
progressBar.update(25, { status: "Processing..." });
// Increment the progress bar
progressBar.increment(5, { status: "Still processing..." });
// Stop the progress bar
progressBar.stop();
Progress Bar Options
const progressBar = new ui.ProgressBar({
total: 100, // Total number of steps
start: 0, // Initial value
format: "{bar} {percentage}%", // Format string
barWidth: 40, // Width of the progress bar
barCompleteChar: "█", // Character for completed part
barIncompleteChar: "░", // Character for incomplete part
clearOnComplete: true, // Clear on completion
stopOnComplete: true, // Stop on completion
hideCursor: true, // Hide cursor
enabled: true, // Whether to show the progress bar
color: "primary", // Color of the progress bar
});
Progress Bar Methods
// Start the progress bar
progressBar.start(0, { status: "Starting..." });
// Update the progress bar
progressBar.update(50, { status: "Halfway done..." });
// Increment the progress bar
progressBar.increment(10, { status: "Making progress..." });
// Stop the progress bar
progressBar.stop();
// Update the total value
progressBar.setTotal(200);
Processing Arrays
// Process an array of items with a progress bar
const items = ["item1", "item2", "item3", "item4", "item5"];
const results = await ui.ProgressBar.forEachAsync(
{ total: items.length, color: "primary" },
items,
async (item, index) => {
// Process the item
await processItem(item);
return `Processed ${item}`;
},
(item, index) => `Processing ${item}...`,
);
// Or use the helper function
const results = await ui.progressForEach(
{ total: items.length, color: "primary" },
items,
async (item, index) => {
// Process the item
await processItem(item);
return `Processed ${item}`;
},
(item, index) => `Processing ${item}...`,
);
Multiple Progress Bars
// Create a multi-bar container
const multiBar = ui.ProgressBar.createMultiBar();
// Create progress bars in the container
const bar1 = multiBar.create("bar1", { total: 100, color: "primary" });
const bar2 = multiBar.create("bar2", { total: 200, color: "secondary" });
const bar3 = multiBar.create("bar3", { total: 300, color: "success" });
// Update the progress bars
bar1.update(25, { status: "Processing bar 1..." });
bar2.update(50, { status: "Processing bar 2..." });
bar3.update(75, { status: "Processing bar 3..." });
// Stop all progress bars
multiBar.stop();
Interactive Prompts Component
The interactive prompts component provides a set of utilities for creating interactive prompts in the CLI. It uses the @clack/prompts library for the actual prompt implementation.
Basic Usage
import * as ui from "../ui";
// Initialize the prompt system
ui.init("DeepLint CLI");
// Show a text input prompt
const name = await ui.text({
message: "What is your name?",
placeholder: "Enter your name",
validate: (value) => {
if (!value) return "Name is required";
},
});
// Check if the user cancelled the prompt
if (ui.isCancel(name)) {
ui.cancel("Operation cancelled");
process.exit(1);
}
// Show a confirmation prompt
const confirm = await ui.confirm({
message: "Are you sure?",
initialValue: true,
});
// Show a select prompt
const option = await ui.select({
message: "Select an option",
options: [
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
],
});
// Show a multi-select prompt
const options = await ui.multiselect({
message: "Select options",
options: [
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
],
required: true,
});
// Show a note
ui.note("This is a note", "Note Title");
// Show a themed note
ui.info("This is an info note");
ui.success("This is a success note");
ui.warning("This is a warning note");
ui.error("This is an error note");
// Run a task with a spinner
const result = await ui.task(
"Running task...",
async () => {
// Do some work
return "Task result";
},
"Task completed successfully!",
"Task failed",
);
// End the prompt session
ui.done("All done!");
Helper Functions
The interactive prompts component provides several helper functions for common prompt types:
Text Input
const name = await ui.text({
message: "What is your name?",
placeholder: "Enter your name",
initialValue: "John Doe",
validate: (value) => {
if (!value) return "Name is required";
},
});
Password Input
const password = await ui.password({
message: "Enter your password",
validate: (value) => {
if (!value) return "Password is required";
if (value.length < 8) return "Password must be at least 8 characters";
},
});
Confirmation
const confirm = await ui.confirm({
message: "Are you sure?",
initialValue: true,
active: "Yes",
inactive: "No",
});
Select
const option = await ui.select({
message: "Select an option",
options: [
{ value: "option1", label: "Option 1", hint: "This is option 1" },
{ value: "option2", label: "Option 2", hint: "This is option 2" },
{ value: "option3", label: "Option 3", hint: "This is option 3" },
],
initialValue: "option1",
});
Multi-select
const options = await ui.multiselect({
message: "Select options",
options: [
{ value: "option1", label: "Option 1", hint: "This is option 1" },
{ value: "option2", label: "Option 2", hint: "This is option 2" },
{ value: "option3", label: "Option 3", hint: "This is option 3" },
],
required: true,
max: 2,
});
Notes
// Regular note
ui.note("This is a note", "Note Title");
// Themed notes
ui.info("This is an info note");
ui.success("This is a success note");
ui.warning("This is a warning note");
ui.error("This is an error note");
Task with Spinner
const result = await ui.task(
"Running task...",
async () => {
// Do some work
return "Task result";
},
"Task completed successfully!",
"Task failed",
);
Cancellation
// Check if the user cancelled the prompt
if (ui.isCancel(result)) {
ui.cancel("Operation cancelled");
process.exit(1);
}
Session Management
// Initialize the prompt session
ui.init("DeepLint CLI");
// End the prompt session
ui.done("All done!");
Banner Component
The banner component provides a way to display headers and messages in the CLI. It uses the boxen library for creating boxes around text.
Basic Usage
import * as ui from "../ui";
// Create a simple banner
const simpleBanner = ui.banner("Hello, world!", {
title: "Welcome",
borderColor: "primary",
});
console.log(simpleBanner);
Banner Options
const banner = ui.banner("Hello, world!", {
title: "Welcome", // Title of the banner
subtitle: "This is a subtitle", // Subtitle of the banner
borderStyle: "round", // Border style (single, double, round, bold, classic)
borderColor: "primary", // Border color (primary, secondary, success, warning, error, info, muted)
align: "center", // Text alignment (left, center, right)
padding: 1, // Padding (number or object)
margin: 1, // Margin (number or object)
width: 60, // Width of the banner
dimBorder: false, // Whether to dim the border
});
App Banner
// Create an app banner
const appBanner = ui.appBanner({
name: "DeepLint",
version: "1.0.0",
description: "A CLI tool for linting code with AI",
borderColor: "primary",
borderStyle: "bold",
});
console.log(appBanner);
Command Banner
// Create a command banner
const commandBanner = ui.commandBanner({
command: "init",
description: "Initialize DeepLint in the current project",
usage: "deeplint init [options]",
examples: ["deeplint init", "deeplint init --force"],
borderColor: "primary",
borderStyle: "round",
});
console.log(commandBanner);
Themed Banners
// Create a success banner
const successBanner = ui.successBanner("Operation completed successfully!", "Success");
// Create a warning banner
const warningBanner = ui.warningBanner("This operation might take a while.", "Warning");
// Create an error banner
const errorBanner = ui.errorBanner(
"An error occurred while processing your request.",
"Error",
);
// Create an info banner
const infoBanner = ui.infoBanner("This is an informational message.", "Info");
Custom Padding and Margin
// Create a banner with custom padding and margin
const customBanner = ui.banner("Hello, world!", {
padding: {
top: 1,
right: 2,
bottom: 1,
left: 2,
},
margin: {
top: 1,
right: 0,
bottom: 1,
left: 0,
},
});
Table Component
The table component provides a way to display structured data in the CLI. It uses a custom implementation that's designed to be robust and handle edge cases gracefully.
Basic Usage
import * as ui from "../ui";
// Create a simple table
const table = ui.createTable({
headers: ["Name", "Age", "Location"],
});
// Add rows to the table
table.push(["John", "25", "New York"]);
table.push(["Jane", "30", "San Francisco"]);
table.push(["Bob", "35", "Chicago"]);
// Display the table
console.log(table.toString());
Table Options
const table = ui.createTable({
headers: ["Name", "Age", "Location"], // Table headers
style: "default", // Table style (default, compact, markdown, borderless)
colors: {
header: "primary", // Header color (primary, secondary, success, warning, error, info, muted)
border: "muted", // Border color (primary, secondary, success, warning, error, info, muted)
},
});
Table Styles
// Default style
const defaultTable = ui.createTable({
style: "default",
headers: ["Name", "Age", "Location"],
});
// Compact style
const compactTable = ui.createTable({
style: "compact",
headers: ["Name", "Age", "Location"],
});
// Markdown style
const markdownTable = ui.createTable({
style: "markdown",
headers: ["Name", "Age", "Location"],
});
// Borderless style
const borderlessTable = ui.createTable({
style: "borderless",
headers: ["Name", "Age", "Location"],
});
Creating Tables from Data
// Create a table from an array of objects
const users = [
{ name: "John", age: 25, location: "New York" },
{ name: "Jane", age: 30, location: "San Francisco" },
{ name: "Bob", age: 35, location: "Chicago" },
];
const objectTable = ui.createTableFromObjects(users, {
style: "compact",
colors: { header: "primary" },
});
console.log(objectTable.toString());
// Create a table from an array of arrays
const data = [
["Name", "Age", "Location"],
["John", "25", "New York"],
["Jane", "30", "San Francisco"],
["Bob", "35", "Chicago"],
];
const arrayTable = ui.createTableFromArrays(data, {
style: "compact",
colors: { header: "primary" },
});
console.log(arrayTable.toString());
// Create a key-value table
const config = {
apiKey: "abc123",
endpoint: "https://api.example.com",
timeout: 5000,
retries: 3,
};
const keyValueTable = ui.createKeyValueTable(config, {
style: "compact",
colors: { header: "primary" },
});
console.log(keyValueTable.toString());
Formatting Tables as Strings
// Format a table as a string
const table = ui.createTable({
headers: ["Name", "Age", "Location"],
});
table.push(["John", "25", "New York"]);
table.push(["Jane", "30", "San Francisco"]);
table.push(["Bob", "35", "Chicago"]);
const tableString = ui.formatTable(table);
console.log(tableString);
// Format an array of objects as a table string
const users = [
{ name: "John", age: 25, location: "New York" },
{ name: "Jane", age: 30, location: "San Francisco" },
{ name: "Bob", age: 35, location: "Chicago" },
];
const objectTableString = ui.formatObjectsAsTable(users, {
style: "compact",
colors: { header: "primary" },
});
console.log(objectTableString);
// Format an array of arrays as a table string
const data = [
["Name", "Age", "Location"],
["John", "25", "New York"],
["Jane", "30", "San Francisco"],
["Bob", "35", "Chicago"],
];
const arrayTableString = ui.formatArraysAsTable(data, {
style: "compact",
colors: { header: "primary" },
});
console.log(arrayTableString);
// Format a key-value object as a table string
const config = {
apiKey: "abc123",
endpoint: "https://api.example.com",
timeout: 5000,
retries: 3,
};
const keyValueTableString = ui.formatKeyValueAsTable(config, {
style: "compact",
colors: { header: "primary" },
});
console.log(keyValueTableString);
Error Handling
The table component is designed to handle edge cases gracefully:
Empty or null data
Missing or malformed headers
ANSI color codes in cell content
Varying row lengths
Null or undefined cell values
// The table will handle these edge cases gracefully
const robustTable = ui.createTableFromObjects(
[
{ name: "John", age: 25 },
{ name: "Jane", age: null },
null,
{ name: undefined, age: 35 },
],
{
headers: ["Name", "Age"], // Explicit headers are recommended
},
);
console.log(robustTable.toString());
Best Practices
Always provide explicit headers when possible to ensure consistent table structure:
const table = ui.createTableFromObjects(data, {
headers: ["Name", "Age", "Location"],
});
Handle potential null/undefined values in your data before passing to the table:
const safeData = data.filter((item) => item != null);
const table = ui.createTableFromObjects(safeData);
Use try/catch blocks when displaying tables with potentially problematic data:
try {
const table = ui.createTableFromObjects(data);
console.log(table.toString());
} catch (error) {
logger.error("Error displaying table:", error);
// Fallback to simple display
for (const item of data) {
if (item) console.log(`${item.name}: ${item.value}`);
}
}
Future Enhancements
The UI module will be enhanced with additional components in the future:
JSON Output: For programmatic consumption
Last updated