CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-listr2

Terminal task list library for creating beautiful, interactive CLI interfaces with task management, rendering options, and error handling.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

event-management.mddocs/

Event Management

Observable-based event system for monitoring task state changes, progress updates, lifecycle events, and custom event handling throughout task execution.

Capabilities

ListrEventManager

Global event manager for Listr-level events and cross-task communication.

/**
 * Global event manager for Listr-level events
 * Extends the base EventManager with Listr-specific functionality
 */
class ListrEventManager extends EventManager {
  /**
   * Listen to Listr-level events
   * @param event - Event name to listen for
   * @param listener - Event handler function
   */
  on(event: string, listener: (...args: any[]) => void): this;

  /**
   * Listen to event once
   * @param event - Event name to listen for
   * @param listener - Event handler function
   */
  once(event: string, listener: (...args: any[]) => void): this;

  /**
   * Emit an event
   * @param event - Event name to emit
   * @param args - Arguments to pass to listeners
   */
  emit(event: string, ...args: any[]): boolean;

  /**
   * Remove event listener
   * @param event - Event name
   * @param listener - Specific listener to remove
   */
  off(event: string, listener: (...args: any[]) => void): this;

  /**
   * Remove all listeners for an event
   * @param event - Event name
   */
  removeAllListeners(event?: string): this;
}

ListrTaskEventManager

Task-specific event manager for individual task lifecycle and state events.

/**
 * Task-specific event manager for individual task events
 * Handles task lifecycle, state changes, and output events
 */
class ListrTaskEventManager extends EventManager {
  /**
   * Listen to task-specific events
   * @param event - Task event type
   * @param listener - Event handler function
   */
  on(event: ListrTaskEventType, listener: (data: any) => void): this;

  /**
   * Listen to task event once
   * @param event - Task event type
   * @param listener - Event handler function
   */
  once(event: ListrTaskEventType, listener: (data: any) => void): this;

  /**
   * Emit a task event
   * @param event - Task event type
   * @param data - Event data
   */
  emit(event: ListrTaskEventType, data: any): boolean;
}

Base EventManager

Generic event management functionality providing the foundation for all event handling.

/**
 * Generic base event manager
 * Provides core event handling functionality
 */
class EventManager {
  /**
   * Add event listener
   * @param event - Event name
   * @param listener - Event handler function
   */
  on(event: string, listener: (...args: any[]) => void): this;

  /**
   * Add one-time event listener
   * @param event - Event name
   * @param listener - Event handler function
   */
  once(event: string, listener: (...args: any[]) => void): this;

  /**
   * Emit an event to all listeners
   * @param event - Event name
   * @param args - Arguments to pass to listeners
   */
  emit(event: string, ...args: any[]): boolean;

  /**
   * Remove specific event listener
   * @param event - Event name
   * @param listener - Listener function to remove
   */
  off(event: string, listener: (...args: any[]) => void): this;

  /**
   * Remove all listeners for an event or all events
   * @param event - Optional specific event name
   */
  removeAllListeners(event?: string): this;

  /**
   * Get array of listeners for an event
   * @param event - Event name
   */
  listeners(event: string): Function[];

  /**
   * Get count of listeners for an event
   * @param event - Event name
   */
  listenerCount(event: string): number;
}

Task Event Types

Enumeration of all possible task-related events that can be monitored.

/**
 * Task event types for monitoring task lifecycle
 */
enum ListrTaskEventType {
  /** Task title changed */
  TITLE = 'TITLE',
  /** Task state changed */
  STATE = 'STATE',
  /** Task enabled status changed */
  ENABLED = 'ENABLED',
  /** Subtask added or modified */
  SUBTASK = 'SUBTASK',
  /** Prompt started or updated */
  PROMPT = 'PROMPT',
  /** Task output updated */
  OUTPUT = 'OUTPUT',
  /** Task message updated */
  MESSAGE = 'MESSAGE',
  /** Task closed or completed */
  CLOSED = 'CLOSED'
}

Event Data Interfaces

Type definitions for event data structures passed to event handlers.

/**
 * Base event data structure
 */
interface ListrEvent {
  /** Timestamp when event occurred */
  timestamp: Date;
  /** Task ID that generated the event */
  taskId: string;
  /** Event type */
  type: ListrTaskEventType;
}

/**
 * Task state change event data
 */
interface ListrTaskStateEvent extends ListrEvent {
  type: ListrTaskEventType.STATE;
  /** Previous task state */
  previousState: ListrTaskState;
  /** New task state */
  newState: ListrTaskState;
  /** Additional state metadata */
  metadata?: Record<string, any>;
}

/**
 * Task title change event data
 */
interface ListrTaskTitleEvent extends ListrEvent {
  type: ListrTaskEventType.TITLE;
  /** Previous title */
  previousTitle?: string;
  /** New title */
  newTitle: string;
}

/**
 * Task output event data
 */
interface ListrTaskOutputEvent extends ListrEvent {
  type: ListrTaskEventType.OUTPUT;
  /** Output content */
  output: string;
  /** Whether this is persistent output */
  persistent?: boolean;
}

/**
 * Task message event data
 */
interface ListrTaskMessageEvent extends ListrEvent {
  type: ListrTaskEventType.MESSAGE;
  /** Message content */
  message: string;
  /** Message level or type */
  level?: 'info' | 'warn' | 'error';
}

Usage Examples:

Basic Event Listening

import { Listr, ListrTaskEventType } from "listr2";

const tasks = new Listr([
  {
    title: "Task with events",
    task: async (ctx, task) => {
      task.output = "Starting...";
      await new Promise(resolve => setTimeout(resolve, 1000));
      task.output = "Processing...";
      await new Promise(resolve => setTimeout(resolve, 1000));
      task.output = "Completed!";
    }
  }
]);

// Listen to task state changes
tasks.events.on('TASK_STATE_CHANGED', (event) => {
  console.log(`Task ${event.taskId} changed from ${event.previousState} to ${event.newState}`);
});

// Listen to task output updates
tasks.events.on('TASK_OUTPUT_UPDATED', (event) => {
  console.log(`Task ${event.taskId} output: ${event.output}`);
});

await tasks.run();

Advanced Event Monitoring

import { Listr, ListrTaskEventType, ListrTaskState } from "listr2";

interface MonitoringContext {
  startTime: Date;
  completedTasks: number;
  failedTasks: number;
}

const tasks = new Listr<MonitoringContext>([
  {
    title: "Task 1",
    task: () => new Promise(resolve => setTimeout(resolve, 1000))
  },
  {
    title: "Task 2", 
    task: () => new Promise((resolve, reject) => {
      setTimeout(() => Math.random() > 0.5 ? resolve(undefined) : reject(new Error("Random failure")), 1000);
    })
  },
  {
    title: "Task 3",
    task: () => new Promise(resolve => setTimeout(resolve, 1000))
  }
]);

// Track task completion statistics
tasks.events.on('TASK_STATE_CHANGED', (event) => {
  if (event.newState === ListrTaskState.COMPLETED) {
    console.log(`✅ Task completed: ${event.taskId}`);
  } else if (event.newState === ListrTaskState.FAILED) {
    console.log(`❌ Task failed: ${event.taskId}`);
  } else if (event.newState === ListrTaskState.STARTED) {
    console.log(`🔄 Task started: ${event.taskId}`);
  }
});

// Monitor overall progress
let totalTasks = 0;
let completedTasks = 0;

tasks.events.on('TASK_CREATED', () => totalTasks++);
tasks.events.on('TASK_STATE_CHANGED', (event) => {
  if (event.newState === ListrTaskState.COMPLETED || event.newState === ListrTaskState.SKIPPED) {
    completedTasks++;
    console.log(`Progress: ${completedTasks}/${totalTasks} tasks completed`);
  }
});

const result = await tasks.run({ 
  startTime: new Date(), 
  completedTasks: 0, 
  failedTasks: 0 
});

Custom Event Emitters

import { Listr } from "listr2";

const tasks = new Listr([
  {
    title: "Task with custom events",
    task: async (ctx, task) => {
      // Emit custom events through the task's event manager
      task.events.emit('CUSTOM_PROGRESS', { progress: 0, message: "Starting..." });
      
      for (let i = 1; i <= 5; i++) {
        await new Promise(resolve => setTimeout(resolve, 500));
        task.events.emit('CUSTOM_PROGRESS', { 
          progress: i * 20, 
          message: `Step ${i}/5 completed` 
        });
        task.output = `Progress: ${i * 20}%`;
      }
      
      task.events.emit('CUSTOM_COMPLETE', { 
        duration: Date.now() - startTime,
        result: "success"
      });
    }
  }
]);

// Listen to custom events
tasks.events.on('CUSTOM_PROGRESS', (data) => {
  console.log(`Custom progress: ${data.progress}% - ${data.message}`);
});

tasks.events.on('CUSTOM_COMPLETE', (data) => {
  console.log(`Custom completion: ${data.result} (took ${data.duration}ms)`);
});

const startTime = Date.now();
await tasks.run();

Event-Based Task Coordination

import { Listr } from "listr2";

interface SharedContext {
  phase1Complete: boolean;
  phase2Data?: any;
  finalResults?: any[];
}

const tasks = new Listr<SharedContext>([
  {
    title: "Phase 1: Preparation",
    task: async (ctx, task) => {
      task.output = "Preparing data...";
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      ctx.phase1Complete = true;
      
      // Emit event to signal phase 1 completion
      task.events.emit('PHASE_1_COMPLETE', { 
        timestamp: new Date(),
        data: "preparation-data"
      });
      
      task.output = "Phase 1 completed";
    }
  },
  {
    title: "Phase 2: Processing", 
    task: async (ctx, task) => {
      // Wait for phase 1 to complete
      if (!ctx.phase1Complete) {
        await new Promise(resolve => {
          task.events.once('PHASE_1_COMPLETE', resolve);
        });
      }
      
      task.output = "Processing data...";
      await new Promise(resolve => setTimeout(resolve, 1500));
      
      ctx.phase2Data = { processed: true, items: 10 };
      
      task.events.emit('PHASE_2_COMPLETE', ctx.phase2Data);
      task.output = "Phase 2 completed";
    }
  },
  {
    title: "Phase 3: Finalization",
    task: async (ctx, task) => {
      // Wait for phase 2 data
      if (!ctx.phase2Data) {
        await new Promise(resolve => {
          task.events.once('PHASE_2_COMPLETE', (data) => {
            ctx.phase2Data = data;
            resolve(undefined);
          });
        });
      }
      
      task.output = "Finalizing results...";
      await new Promise(resolve => setTimeout(resolve, 800));
      
      ctx.finalResults = Array.from({ length: ctx.phase2Data.items }, (_, i) => `result-${i}`);
      task.output = `Finalized ${ctx.finalResults.length} results`;
    }
  }
]);

await tasks.run({ phase1Complete: false });

Error Event Handling

import { Listr, ListrTaskState } from "listr2";

const tasks = new Listr([
  {
    title: "Potentially failing task",
    retry: 3,
    task: async (ctx, task) => {
      if (Math.random() < 0.7) {
        throw new Error("Simulated failure");
      }
      return "Success";
    }
  }
]);

// Handle error events
tasks.events.on('TASK_ERROR', (event) => {
  console.log(`Task error: ${event.error.message}`);
  console.log(`Will retry: ${event.willRetry}`);
  console.log(`Retry count: ${event.retryCount}`);
});

// Handle retry events
tasks.events.on('TASK_RETRY', (event) => {
  console.log(`Retrying task: ${event.taskId} (attempt ${event.attempt})`);
});

// Handle state changes for detailed error tracking
tasks.events.on('TASK_STATE_CHANGED', (event) => {
  if (event.newState === ListrTaskState.FAILED) {
    console.log(`Task failed permanently: ${event.taskId}`);
  } else if (event.newState === ListrTaskState.RETRY) {
    console.log(`Task entering retry state: ${event.taskId}`);
  }
});

try {
  await tasks.run();
} catch (error) {
  console.log("Task list failed:", error.message);
}

Types

/**
 * Event listener function type
 */
type EventListener = (...args: any[]) => void;

/**
 * Event map interface for type-safe event handling
 */
interface ListrEventMap {
  /** Task state changed */
  [ListrTaskEventType.STATE]: ListrTaskStateEvent;
  /** Task title changed */
  [ListrTaskEventType.TITLE]: ListrTaskTitleEvent;
  /** Task output updated */
  [ListrTaskEventType.OUTPUT]: ListrTaskOutputEvent;
  /** Task message updated */
  [ListrTaskEventType.MESSAGE]: ListrTaskMessageEvent;
  /** Task enabled status changed */
  [ListrTaskEventType.ENABLED]: { taskId: string; enabled: boolean };
  /** Subtask event */
  [ListrTaskEventType.SUBTASK]: { taskId: string; subtask: any };
  /** Prompt event */
  [ListrTaskEventType.PROMPT]: { taskId: string; prompt: any };
  /** Task closed */
  [ListrTaskEventType.CLOSED]: { taskId: string; timestamp: Date };
}

/**
 * Task event map interface for task-specific events
 */
interface ListrTaskEventMap {
  /** Task was created */
  TASK_CREATED: { taskId: string; title: string };
  /** Task state changed */
  TASK_STATE_CHANGED: ListrTaskStateEvent;
  /** Task encountered an error */
  TASK_ERROR: { taskId: string; error: Error; willRetry: boolean; retryCount: number };
  /** Task is retrying */
  TASK_RETRY: { taskId: string; attempt: number; maxAttempts: number };
  /** Task output was updated */
  TASK_OUTPUT_UPDATED: ListrTaskOutputEvent;
}

docs

error-handling.md

event-management.md

index.md

renderers.md

task-configuration.md

task-management.md

tile.json