refactor: enhance add-task fuzzy search and fix duplicate banner display

- **Remove hardcoded category system** in add-task that always matched 'Task management'
- **Eliminate arbitrary limits** in fuzzy search results (5→25 high relevance, 3→10 medium relevance, 8→20 detailed tasks)
- **Improve semantic weighting** in Fuse.js search (details=3, description=2, title=1.5) for better relevance
- **Fix duplicate banner issue** by removing console.clear() and redundant displayBanner() calls from UI functions
- **Enhance context generation** to rely on semantic similarity rather than rigid pattern matching
- **Preserve terminal history** to address GitHub issue #553 about eating terminal lines
- **Remove displayBanner() calls** from: displayHelp, displayNextTask, displayTaskById, displayComplexityReport, set-task-status, clear-subtasks, dependency-manager functions

The add-task system now provides truly relevant task context based on semantic similarity rather than arbitrary categories and limits, while maintaining a cleaner terminal experience.

Changes span: add-task.js, ui.js, set-task-status.js, clear-subtasks.js, list-tasks.js, dependency-manager.js

Closes #553
This commit is contained in:
Eyal Toledano
2025-06-07 20:23:55 -04:00
parent 54005d5486
commit af652978a0
9 changed files with 3047 additions and 3095 deletions

View File

@@ -1,8 +1,8 @@
{ {
"models": { "models": {
"main": { "main": {
"provider": "openrouter", "provider": "anthropic",
"modelId": "qwen/qwen3-235b-a22b:free", "modelId": "claude-sonnet-4-20250514",
"maxTokens": 50000, "maxTokens": 50000,
"temperature": 0.2 "temperature": 0.2
}, },

View File

@@ -1,35 +0,0 @@
# Task ID: 97
# Title: Create Taskmaster Jingle Implementation
# Status: pending
# Dependencies: 95, 57, 3, 2
# Priority: medium
# Description: Develop a musical jingle system for Taskmaster that plays sound effects during key CLI interactions to enhance user experience.
# Details:
This task involves implementing a sound system that plays audio cues during Taskmaster CLI operations. Key implementation steps include:
1. Audio System Integration:
- Research and select appropriate audio library compatible with Node.js CLI applications
- Implement cross-platform audio playback (Windows, macOS, Linux)
- Create sound configuration options in .taskmasterconfig
2. Jingle Design:
- Define sound triggers for key events (task creation, completion, errors, etc.)
- Create or source appropriate sound files (WAV/MP3 format)
- Implement volume control and mute option in settings
3. CLI Integration:
- Add sound playback to core CLI commands (init, create, update, delete)
- Implement optional sound effects toggle via command line flags
- Ensure audio playback doesn't interfere with CLI performance
4. Documentation:
- Update user guide with sound configuration instructions
- Add troubleshooting section for audio playback issues
# Test Strategy:
1. Verify audio plays correctly during each supported CLI operation
2. Test sound configuration options across different platforms
3. Confirm volume control and mute functionality works as expected
4. Validate that audio playback doesn't affect CLI performance
5. Test edge cases (no audio hardware, invalid sound files, etc.)
6. Ensure sound effects can be disabled via configuration and CLI flags

View File

@@ -5871,22 +5871,6 @@
"parentTaskId": 96 "parentTaskId": 96
} }
] ]
},
{
"id": 97,
"title": "Create Taskmaster Jingle Implementation",
"description": "Develop a musical jingle system for Taskmaster that plays sound effects during key CLI interactions to enhance user experience.",
"details": "This task involves implementing a sound system that plays audio cues during Taskmaster CLI operations. Key implementation steps include:\n\n1. Audio System Integration:\n - Research and select appropriate audio library compatible with Node.js CLI applications\n - Implement cross-platform audio playback (Windows, macOS, Linux)\n - Create sound configuration options in .taskmasterconfig\n\n2. Jingle Design:\n - Define sound triggers for key events (task creation, completion, errors, etc.)\n - Create or source appropriate sound files (WAV/MP3 format)\n - Implement volume control and mute option in settings\n\n3. CLI Integration:\n - Add sound playback to core CLI commands (init, create, update, delete)\n - Implement optional sound effects toggle via command line flags\n - Ensure audio playback doesn't interfere with CLI performance\n\n4. Documentation:\n - Update user guide with sound configuration instructions\n - Add troubleshooting section for audio playback issues",
"testStrategy": "1. Verify audio plays correctly during each supported CLI operation\n2. Test sound configuration options across different platforms\n3. Confirm volume control and mute functionality works as expected\n4. Validate that audio playback doesn't affect CLI performance\n5. Test edge cases (no audio hardware, invalid sound files, etc.)\n6. Ensure sound effects can be disabled via configuration and CLI flags",
"status": "pending",
"dependencies": [
95,
57,
3,
2
],
"priority": "medium",
"subtasks": []
} }
] ]
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
import path from 'path'; import path from "path";
import chalk from 'chalk'; import chalk from "chalk";
import boxen from 'boxen'; import boxen from "boxen";
import Table from 'cli-table3'; import Table from "cli-table3";
import { log, readJSON, writeJSON, truncate, isSilentMode } from '../utils.js'; import { log, readJSON, writeJSON, truncate, isSilentMode } from "../utils.js";
import { displayBanner } from '../ui.js'; import { displayBanner } from "../ui.js";
import generateTaskFiles from './generate-task-files.js'; import generateTaskFiles from "./generate-task-files.js";
/** /**
* Clear subtasks from specified tasks * Clear subtasks from specified tasks
@@ -13,140 +13,138 @@ import generateTaskFiles from './generate-task-files.js';
* @param {string} taskIds - Task IDs to clear subtasks from * @param {string} taskIds - Task IDs to clear subtasks from
*/ */
function clearSubtasks(tasksPath, taskIds) { function clearSubtasks(tasksPath, taskIds) {
displayBanner(); log("info", `Reading tasks from ${tasksPath}...`);
const data = readJSON(tasksPath);
if (!data || !data.tasks) {
log("error", "No valid tasks found.");
process.exit(1);
}
log('info', `Reading tasks from ${tasksPath}...`); if (!isSilentMode()) {
const data = readJSON(tasksPath); console.log(
if (!data || !data.tasks) { boxen(chalk.white.bold("Clearing Subtasks"), {
log('error', 'No valid tasks found.'); padding: 1,
process.exit(1); borderColor: "blue",
} borderStyle: "round",
margin: { top: 1, bottom: 1 },
})
);
}
if (!isSilentMode()) { // Handle multiple task IDs (comma-separated)
console.log( const taskIdArray = taskIds.split(",").map((id) => id.trim());
boxen(chalk.white.bold('Clearing Subtasks'), { let clearedCount = 0;
padding: 1,
borderColor: 'blue',
borderStyle: 'round',
margin: { top: 1, bottom: 1 }
})
);
}
// Handle multiple task IDs (comma-separated) // Create a summary table for the cleared subtasks
const taskIdArray = taskIds.split(',').map((id) => id.trim()); const summaryTable = new Table({
let clearedCount = 0; head: [
chalk.cyan.bold("Task ID"),
chalk.cyan.bold("Task Title"),
chalk.cyan.bold("Subtasks Cleared"),
],
colWidths: [10, 50, 20],
style: { head: [], border: [] },
});
// Create a summary table for the cleared subtasks taskIdArray.forEach((taskId) => {
const summaryTable = new Table({ const id = parseInt(taskId, 10);
head: [ if (isNaN(id)) {
chalk.cyan.bold('Task ID'), log("error", `Invalid task ID: ${taskId}`);
chalk.cyan.bold('Task Title'), return;
chalk.cyan.bold('Subtasks Cleared') }
],
colWidths: [10, 50, 20],
style: { head: [], border: [] }
});
taskIdArray.forEach((taskId) => { const task = data.tasks.find((t) => t.id === id);
const id = parseInt(taskId, 10); if (!task) {
if (isNaN(id)) { log("error", `Task ${id} not found`);
log('error', `Invalid task ID: ${taskId}`); return;
return; }
}
const task = data.tasks.find((t) => t.id === id); if (!task.subtasks || task.subtasks.length === 0) {
if (!task) { log("info", `Task ${id} has no subtasks to clear`);
log('error', `Task ${id} not found`); summaryTable.push([
return; id.toString(),
} truncate(task.title, 47),
chalk.yellow("No subtasks"),
]);
return;
}
if (!task.subtasks || task.subtasks.length === 0) { const subtaskCount = task.subtasks.length;
log('info', `Task ${id} has no subtasks to clear`); task.subtasks = [];
summaryTable.push([ clearedCount++;
id.toString(), log("info", `Cleared ${subtaskCount} subtasks from task ${id}`);
truncate(task.title, 47),
chalk.yellow('No subtasks')
]);
return;
}
const subtaskCount = task.subtasks.length; summaryTable.push([
task.subtasks = []; id.toString(),
clearedCount++; truncate(task.title, 47),
log('info', `Cleared ${subtaskCount} subtasks from task ${id}`); chalk.green(`${subtaskCount} subtasks cleared`),
]);
});
summaryTable.push([ if (clearedCount > 0) {
id.toString(), writeJSON(tasksPath, data);
truncate(task.title, 47),
chalk.green(`${subtaskCount} subtasks cleared`)
]);
});
if (clearedCount > 0) { // Show summary table
writeJSON(tasksPath, data); if (!isSilentMode()) {
console.log(
boxen(chalk.white.bold("Subtask Clearing Summary:"), {
padding: { left: 2, right: 2, top: 0, bottom: 0 },
margin: { top: 1, bottom: 0 },
borderColor: "blue",
borderStyle: "round",
})
);
console.log(summaryTable.toString());
}
// Show summary table // Regenerate task files to reflect changes
if (!isSilentMode()) { log("info", "Regenerating task files...");
console.log( generateTaskFiles(tasksPath, path.dirname(tasksPath));
boxen(chalk.white.bold('Subtask Clearing Summary:'), {
padding: { left: 2, right: 2, top: 0, bottom: 0 },
margin: { top: 1, bottom: 0 },
borderColor: 'blue',
borderStyle: 'round'
})
);
console.log(summaryTable.toString());
}
// Regenerate task files to reflect changes // Success message
log('info', 'Regenerating task files...'); if (!isSilentMode()) {
generateTaskFiles(tasksPath, path.dirname(tasksPath)); console.log(
boxen(
chalk.green(
`Successfully cleared subtasks from ${chalk.bold(clearedCount)} task(s)`
),
{
padding: 1,
borderColor: "green",
borderStyle: "round",
margin: { top: 1 },
}
)
);
// Success message // Next steps suggestion
if (!isSilentMode()) { console.log(
console.log( boxen(
boxen( chalk.white.bold("Next Steps:") +
chalk.green( "\n\n" +
`Successfully cleared subtasks from ${chalk.bold(clearedCount)} task(s)` `${chalk.cyan("1.")} Run ${chalk.yellow("task-master expand --id=<id>")} to generate new subtasks\n` +
), `${chalk.cyan("2.")} Run ${chalk.yellow("task-master list --with-subtasks")} to verify changes`,
{ {
padding: 1, padding: 1,
borderColor: 'green', borderColor: "cyan",
borderStyle: 'round', borderStyle: "round",
margin: { top: 1 } margin: { top: 1 },
} }
) )
); );
}
// Next steps suggestion } else {
console.log( if (!isSilentMode()) {
boxen( console.log(
chalk.white.bold('Next Steps:') + boxen(chalk.yellow("No subtasks were cleared"), {
'\n\n' + padding: 1,
`${chalk.cyan('1.')} Run ${chalk.yellow('task-master expand --id=<id>')} to generate new subtasks\n` + borderColor: "yellow",
`${chalk.cyan('2.')} Run ${chalk.yellow('task-master list --with-subtasks')} to verify changes`, borderStyle: "round",
{ margin: { top: 1 },
padding: 1, })
borderColor: 'cyan', );
borderStyle: 'round', }
margin: { top: 1 } }
}
)
);
}
} else {
if (!isSilentMode()) {
console.log(
boxen(chalk.yellow('No subtasks were cleared'), {
padding: 1,
borderColor: 'yellow',
borderStyle: 'round',
margin: { top: 1 }
})
);
}
}
} }
export default clearSubtasks; export default clearSubtasks;

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,17 @@
import path from 'path'; import path from "path";
import chalk from 'chalk'; import chalk from "chalk";
import boxen from 'boxen'; import boxen from "boxen";
import { log, readJSON, writeJSON, findTaskById } from '../utils.js'; import { log, readJSON, writeJSON, findTaskById } from "../utils.js";
import { displayBanner } from '../ui.js'; import { displayBanner } from "../ui.js";
import { validateTaskDependencies } from '../dependency-manager.js'; import { validateTaskDependencies } from "../dependency-manager.js";
import { getDebugFlag } from '../config-manager.js'; import { getDebugFlag } from "../config-manager.js";
import updateSingleTaskStatus from './update-single-task-status.js'; import updateSingleTaskStatus from "./update-single-task-status.js";
import generateTaskFiles from './generate-task-files.js'; import generateTaskFiles from "./generate-task-files.js";
import { import {
isValidTaskStatus, isValidTaskStatus,
TASK_STATUS_OPTIONS TASK_STATUS_OPTIONS,
} from '../../../src/constants/task-status.js'; } from "../../../src/constants/task-status.js";
/** /**
* Set the status of a task * Set the status of a task
@@ -22,102 +22,100 @@ import {
* @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode * @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode
*/ */
async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) { async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
try { try {
if (!isValidTaskStatus(newStatus)) { if (!isValidTaskStatus(newStatus)) {
throw new Error( throw new Error(
`Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}` `Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(", ")}`
); );
} }
// Determine if we're in MCP mode by checking for mcpLog // Determine if we're in MCP mode by checking for mcpLog
const isMcpMode = !!options?.mcpLog; const isMcpMode = !!options?.mcpLog;
// Only display UI elements if not in MCP mode // Only display UI elements if not in MCP mode
if (!isMcpMode) { if (!isMcpMode) {
displayBanner(); console.log(
boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), {
padding: 1,
borderColor: "blue",
borderStyle: "round",
})
);
}
console.log( log("info", `Reading tasks from ${tasksPath}...`);
boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), { const data = readJSON(tasksPath);
padding: 1, if (!data || !data.tasks) {
borderColor: 'blue', throw new Error(`No valid tasks found in ${tasksPath}`);
borderStyle: 'round' }
})
);
}
log('info', `Reading tasks from ${tasksPath}...`); // Handle multiple task IDs (comma-separated)
const data = readJSON(tasksPath); const taskIds = taskIdInput.split(",").map((id) => id.trim());
if (!data || !data.tasks) { const updatedTasks = [];
throw new Error(`No valid tasks found in ${tasksPath}`);
}
// Handle multiple task IDs (comma-separated) // Update each task
const taskIds = taskIdInput.split(',').map((id) => id.trim()); for (const id of taskIds) {
const updatedTasks = []; await updateSingleTaskStatus(tasksPath, id, newStatus, data, !isMcpMode);
updatedTasks.push(id);
}
// Update each task // Write the updated tasks to the file
for (const id of taskIds) { writeJSON(tasksPath, data);
await updateSingleTaskStatus(tasksPath, id, newStatus, data, !isMcpMode);
updatedTasks.push(id);
}
// Write the updated tasks to the file // Validate dependencies after status update
writeJSON(tasksPath, data); log("info", "Validating dependencies after status update...");
validateTaskDependencies(data.tasks);
// Validate dependencies after status update // Generate individual task files
log('info', 'Validating dependencies after status update...'); log("info", "Regenerating task files...");
validateTaskDependencies(data.tasks); await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
mcpLog: options.mcpLog,
});
// Generate individual task files // Display success message - only in CLI mode
log('info', 'Regenerating task files...'); if (!isMcpMode) {
await generateTaskFiles(tasksPath, path.dirname(tasksPath), { for (const id of updatedTasks) {
mcpLog: options.mcpLog const task = findTaskById(data.tasks, id);
}); const taskName = task ? task.title : id;
// Display success message - only in CLI mode console.log(
if (!isMcpMode) { boxen(
for (const id of updatedTasks) { chalk.white.bold(`Successfully updated task ${id} status:`) +
const task = findTaskById(data.tasks, id); "\n" +
const taskName = task ? task.title : id; `From: ${chalk.yellow(task ? task.status : "unknown")}\n` +
`To: ${chalk.green(newStatus)}`,
{ padding: 1, borderColor: "green", borderStyle: "round" }
)
);
}
}
console.log( // Return success value for programmatic use
boxen( return {
chalk.white.bold(`Successfully updated task ${id} status:`) + success: true,
'\n' + updatedTasks: updatedTasks.map((id) => ({
`From: ${chalk.yellow(task ? task.status : 'unknown')}\n` + id,
`To: ${chalk.green(newStatus)}`, status: newStatus,
{ padding: 1, borderColor: 'green', borderStyle: 'round' } })),
) };
); } catch (error) {
} log("error", `Error setting task status: ${error.message}`);
}
// Return success value for programmatic use // Only show error UI in CLI mode
return { if (!options?.mcpLog) {
success: true, console.error(chalk.red(`Error: ${error.message}`));
updatedTasks: updatedTasks.map((id) => ({
id,
status: newStatus
}))
};
} catch (error) {
log('error', `Error setting task status: ${error.message}`);
// Only show error UI in CLI mode // Pass session to getDebugFlag
if (!options?.mcpLog) { if (getDebugFlag(options?.session)) {
console.error(chalk.red(`Error: ${error.message}`)); // Use getter
console.error(error);
}
// Pass session to getDebugFlag process.exit(1);
if (getDebugFlag(options?.session)) { } else {
// Use getter // In MCP mode, throw the error for the caller to handle
console.error(error); throw error;
} }
}
process.exit(1);
} else {
// In MCP mode, throw the error for the caller to handle
throw error;
}
}
} }
export default setTaskStatus; export default setTaskStatus;

View File

@@ -40,7 +40,7 @@ const warmGradient = gradient(['#fb8b24', '#e36414', '#9a031e']);
function displayBanner() { function displayBanner() {
if (isSilentMode()) return; if (isSilentMode()) return;
console.clear(); // console.clear(); // Removing this to avoid clearing the terminal per command
const bannerText = figlet.textSync('Task Master', { const bannerText = figlet.textSync('Task Master', {
font: 'Standard', font: 'Standard',
horizontalLayout: 'default', horizontalLayout: 'default',
@@ -78,6 +78,8 @@ function displayBanner() {
* @returns {Object} Spinner object * @returns {Object} Spinner object
*/ */
function startLoadingIndicator(message) { function startLoadingIndicator(message) {
if (isSilentMode()) return null;
const spinner = ora({ const spinner = ora({
text: message, text: message,
color: 'cyan' color: 'cyan'
@@ -87,15 +89,75 @@ function startLoadingIndicator(message) {
} }
/** /**
* Stop a loading indicator * Stop a loading indicator (basic stop, no success/fail indicator)
* @param {Object} spinner - Spinner object to stop * @param {Object} spinner - Spinner object to stop
*/ */
function stopLoadingIndicator(spinner) { function stopLoadingIndicator(spinner) {
if (spinner && spinner.stop) { if (spinner && typeof spinner.stop === 'function') {
spinner.stop(); spinner.stop();
} }
} }
/**
* Complete a loading indicator with success (shows checkmark)
* @param {Object} spinner - Spinner object to complete
* @param {string} message - Optional success message (defaults to current text)
*/
function succeedLoadingIndicator(spinner, message = null) {
if (spinner && typeof spinner.succeed === 'function') {
if (message) {
spinner.succeed(message);
} else {
spinner.succeed();
}
}
}
/**
* Complete a loading indicator with failure (shows X)
* @param {Object} spinner - Spinner object to fail
* @param {string} message - Optional failure message (defaults to current text)
*/
function failLoadingIndicator(spinner, message = null) {
if (spinner && typeof spinner.fail === 'function') {
if (message) {
spinner.fail(message);
} else {
spinner.fail();
}
}
}
/**
* Complete a loading indicator with warning (shows warning symbol)
* @param {Object} spinner - Spinner object to warn
* @param {string} message - Optional warning message (defaults to current text)
*/
function warnLoadingIndicator(spinner, message = null) {
if (spinner && typeof spinner.warn === 'function') {
if (message) {
spinner.warn(message);
} else {
spinner.warn();
}
}
}
/**
* Complete a loading indicator with info (shows info symbol)
* @param {Object} spinner - Spinner object to complete with info
* @param {string} message - Optional info message (defaults to current text)
*/
function infoLoadingIndicator(spinner, message = null) {
if (spinner && typeof spinner.info === 'function') {
if (message) {
spinner.info(message);
} else {
spinner.info();
}
}
}
/** /**
* Create a colored progress bar * Create a colored progress bar
* @param {number} percent - The completion percentage * @param {number} percent - The completion percentage
@@ -232,14 +294,14 @@ function getStatusWithColor(status, forTable = false) {
} }
const statusConfig = { const statusConfig = {
done: { color: chalk.green, icon: '', tableIcon: '✓' }, done: { color: chalk.green, icon: '', tableIcon: '✓' },
completed: { color: chalk.green, icon: '', tableIcon: '✓' }, completed: { color: chalk.green, icon: '', tableIcon: '✓' },
pending: { color: chalk.yellow, icon: '⏱️', tableIcon: '⏱' }, pending: { color: chalk.yellow, icon: '', tableIcon: '⏱' },
'in-progress': { color: chalk.hex('#FFA500'), icon: '🔄', tableIcon: '►' }, 'in-progress': { color: chalk.hex('#FFA500'), icon: '🔄', tableIcon: '►' },
deferred: { color: chalk.gray, icon: '⏱️', tableIcon: '⏱' }, deferred: { color: chalk.gray, icon: 'x', tableIcon: '⏱' },
blocked: { color: chalk.red, icon: '', tableIcon: '✗' }, blocked: { color: chalk.red, icon: '!', tableIcon: '✗' },
review: { color: chalk.magenta, icon: '👀', tableIcon: '👁' }, review: { color: chalk.magenta, icon: '?', tableIcon: '?' },
cancelled: { color: chalk.gray, icon: '❌', tableIcon: '' } cancelled: { color: chalk.gray, icon: '❌', tableIcon: 'x' }
}; };
const config = statusConfig[status.toLowerCase()] || { const config = statusConfig[status.toLowerCase()] || {
@@ -383,8 +445,6 @@ function formatDependenciesWithStatus(
* Display a comprehensive help guide * Display a comprehensive help guide
*/ */
function displayHelp() { function displayHelp() {
displayBanner();
// Get terminal width - moved to top of function to make it available throughout // Get terminal width - moved to top of function to make it available throughout
const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect
@@ -772,8 +832,6 @@ function truncateString(str, maxLength) {
* @param {string} tasksPath - Path to the tasks.json file * @param {string} tasksPath - Path to the tasks.json file
*/ */
async function displayNextTask(tasksPath, complexityReportPath = null) { async function displayNextTask(tasksPath, complexityReportPath = null) {
displayBanner();
// Read the tasks file // Read the tasks file
const data = readJSON(tasksPath); const data = readJSON(tasksPath);
if (!data || !data.tasks) { if (!data || !data.tasks) {
@@ -1044,8 +1102,6 @@ async function displayTaskById(
complexityReportPath = null, complexityReportPath = null,
statusFilter = null statusFilter = null
) { ) {
displayBanner();
// Read the tasks file // Read the tasks file
const data = readJSON(tasksPath); const data = readJSON(tasksPath);
if (!data || !data.tasks) { if (!data || !data.tasks) {
@@ -1500,8 +1556,6 @@ async function displayTaskById(
* @param {string} reportPath - Path to the complexity report file * @param {string} reportPath - Path to the complexity report file
*/ */
async function displayComplexityReport(reportPath) { async function displayComplexityReport(reportPath) {
displayBanner();
// Check if the report exists // Check if the report exists
if (!fs.existsSync(reportPath)) { if (!fs.existsSync(reportPath)) {
console.log( console.log(
@@ -2472,5 +2526,8 @@ export {
displayModelConfiguration, displayModelConfiguration,
displayAvailableModels, displayAvailableModels,
displayAiUsageSummary, displayAiUsageSummary,
displayMultipleTasksSummary succeedLoadingIndicator,
failLoadingIndicator,
warnLoadingIndicator,
infoLoadingIndicator
}; };