Deeplint is still in the MVP development phase and not yet available for use.

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");

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!");

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);
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

  1. Always provide explicit headers when possible to ensure consistent table structure:

const table = ui.createTableFromObjects(data, {
  headers: ["Name", "Age", "Location"],
});
  1. 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);
  1. 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