Terminal task list library for creating beautiful, interactive CLI interfaces with task management, rendering options, and error handling.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Observable-based event system for monitoring task state changes, progress updates, lifecycle events, and custom event handling throughout task execution.
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;
}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;
}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;
}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'
}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:
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();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
});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();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 });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);
}/**
* 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;
}