claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
671 lines • 25.1 kB
JavaScript
/**
* V3 CLI Task Command
* Task management for Claude Flow
*/
import { output } from '../output.js';
import { select, confirm, input, multiSelect } from '../prompt.js';
import { callMCPTool, MCPClientError } from '../mcp-client.js';
// Task types
const TASK_TYPES = [
{ value: 'implementation', label: 'Implementation', hint: 'Feature implementation' },
{ value: 'bug-fix', label: 'Bug Fix', hint: 'Fix a bug or issue' },
{ value: 'refactoring', label: 'Refactoring', hint: 'Code refactoring' },
{ value: 'testing', label: 'Testing', hint: 'Write or update tests' },
{ value: 'documentation', label: 'Documentation', hint: 'Documentation updates' },
{ value: 'research', label: 'Research', hint: 'Research and analysis' },
{ value: 'review', label: 'Review', hint: 'Code review' },
{ value: 'optimization', label: 'Optimization', hint: 'Performance optimization' },
{ value: 'security', label: 'Security', hint: 'Security audit or fix' },
{ value: 'custom', label: 'Custom', hint: 'Custom task type' }
];
// Task priorities
const TASK_PRIORITIES = [
{ value: 'critical', label: 'Critical', hint: 'Highest priority' },
{ value: 'high', label: 'High', hint: 'Important task' },
{ value: 'normal', label: 'Normal', hint: 'Standard priority' },
{ value: 'low', label: 'Low', hint: 'Lower priority' }
];
// Format task status with color
function formatStatus(status) {
switch (status) {
case 'completed':
return output.success(status);
case 'running':
case 'in_progress':
return output.info(status);
case 'pending':
case 'queued':
return output.warning(status);
case 'failed':
case 'cancelled':
return output.error(status);
default:
return status;
}
}
// Format priority with color
function formatPriority(priority) {
switch (priority) {
case 'critical':
return output.error(priority);
case 'high':
return output.warning(priority);
case 'normal':
return priority;
case 'low':
return output.dim(priority);
default:
return priority;
}
}
// Create subcommand
const createCommand = {
name: 'create',
aliases: ['new', 'add'],
description: 'Create a new task',
options: [
{
name: 'type',
short: 't',
description: 'Task type',
type: 'string',
choices: TASK_TYPES.map(t => t.value)
},
{
name: 'description',
short: 'd',
description: 'Task description',
type: 'string'
},
{
name: 'priority',
short: 'p',
description: 'Task priority',
type: 'string',
choices: TASK_PRIORITIES.map(p => p.value),
default: 'normal'
},
{
name: 'assign',
short: 'a',
description: 'Assign to agent(s)',
type: 'string'
},
{
name: 'tags',
description: 'Comma-separated tags',
type: 'string'
},
{
name: 'parent',
description: 'Parent task ID',
type: 'string'
},
{
name: 'dependencies',
description: 'Comma-separated task IDs that must complete first',
type: 'string'
},
{
name: 'timeout',
description: 'Task timeout in seconds',
type: 'number',
default: 300
}
],
action: async (ctx) => {
let taskType = ctx.flags.type;
let description = ctx.flags.description;
let priority = ctx.flags.priority;
// Interactive mode
if (!taskType && ctx.interactive) {
taskType = await select({
message: 'Select task type:',
options: TASK_TYPES
});
}
if (!description && ctx.interactive) {
description = await input({
message: 'Task description:',
validate: (v) => v.length > 0 || 'Description is required'
});
}
if (!taskType || !description) {
output.printError('Task type and description are required');
output.printInfo('Use --type and --description flags, or run in interactive mode');
return { success: false, exitCode: 1 };
}
if (!priority && ctx.interactive) {
priority = await select({
message: 'Select priority:',
options: TASK_PRIORITIES,
default: 'normal'
});
}
// Parse tags and dependencies
const tags = ctx.flags.tags ? ctx.flags.tags.split(',').map(t => t.trim()) : [];
const dependencies = ctx.flags.dependencies
? ctx.flags.dependencies.split(',').map(d => d.trim())
: [];
output.writeln();
output.printInfo(`Creating ${taskType} task...`);
try {
const result = await callMCPTool('task_create', {
type: taskType,
description,
priority: priority || 'normal',
assignedTo: ctx.flags.assign ? [ctx.flags.assign] : undefined,
parentId: ctx.flags.parent,
dependencies,
tags,
timeout: ctx.flags.timeout,
metadata: {
source: 'cli',
createdBy: 'user'
}
});
output.writeln();
output.printSuccess(`Task created: ${result.taskId}`);
output.writeln();
output.printTable({
columns: [
{ key: 'property', header: 'Property', width: 15 },
{ key: 'value', header: 'Value', width: 40 }
],
data: [
{ property: 'ID', value: result.taskId },
{ property: 'Type', value: result.type },
{ property: 'Description', value: result.description },
{ property: 'Priority', value: formatPriority(result.priority) },
{ property: 'Status', value: formatStatus(result.status) },
{ property: 'Assigned To', value: result.assignedTo?.join(', ') || 'Unassigned' },
{ property: 'Tags', value: result.tags.join(', ') || 'None' },
{ property: 'Created', value: new Date(result.createdAt).toLocaleString() }
]
});
if (ctx.flags.format === 'json') {
output.printJson(result);
}
return { success: true, data: result };
}
catch (error) {
if (error instanceof MCPClientError) {
output.printError(`Failed to create task: ${error.message}`);
}
else {
output.printError(`Unexpected error: ${String(error)}`);
}
return { success: false, exitCode: 1 };
}
}
};
// List subcommand
const listCommand = {
name: 'list',
aliases: ['ls'],
description: 'List tasks',
options: [
{
name: 'status',
short: 's',
description: 'Filter by status',
type: 'string',
choices: ['pending', 'running', 'completed', 'failed', 'cancelled', 'all']
},
{
name: 'type',
short: 't',
description: 'Filter by task type',
type: 'string'
},
{
name: 'priority',
short: 'p',
description: 'Filter by priority',
type: 'string'
},
{
name: 'agent',
short: 'a',
description: 'Filter by assigned agent',
type: 'string'
},
{
name: 'limit',
short: 'l',
description: 'Maximum number of tasks to show',
type: 'number',
default: 20
},
{
name: 'all',
description: 'Show all tasks including completed',
type: 'boolean',
default: false
}
],
action: async (ctx) => {
const status = ctx.flags.all ? 'all' : ctx.flags.status || 'pending,running';
const limit = ctx.flags.limit;
try {
const result = await callMCPTool('task_list', {
status,
type: ctx.flags.type,
priority: ctx.flags.priority,
agentId: ctx.flags.agent,
limit,
offset: 0
});
if (ctx.flags.format === 'json') {
output.printJson(result);
return { success: true, data: result };
}
output.writeln();
output.writeln(output.bold('Tasks'));
output.writeln();
if (result.tasks.length === 0) {
output.printInfo('No tasks found matching criteria');
return { success: true, data: result };
}
output.printTable({
columns: [
{ key: 'id', header: 'ID', width: 15 },
{ key: 'type', header: 'Type', width: 15 },
{ key: 'description', header: 'Description', width: 30 },
{ key: 'priority', header: 'Priority', width: 10 },
{ key: 'status', header: 'Status', width: 12 },
{ key: 'progress', header: 'Progress', width: 10 }
],
data: result.tasks.map(t => ({
id: t.id,
type: t.type,
description: t.description.length > 27
? t.description.slice(0, 27) + '...'
: t.description,
priority: formatPriority(t.priority),
status: formatStatus(t.status),
progress: `${t.progress}%`
}))
});
output.writeln();
output.printInfo(`Showing ${result.tasks.length} of ${result.total} tasks`);
return { success: true, data: result };
}
catch (error) {
if (error instanceof MCPClientError) {
output.printError(`Failed to list tasks: ${error.message}`);
}
else {
output.printError(`Unexpected error: ${String(error)}`);
}
return { success: false, exitCode: 1 };
}
}
};
// Status subcommand (get task details)
const statusCommand = {
name: 'status',
aliases: ['info', 'get'],
description: 'Get task status and details',
options: [
{
name: 'id',
description: 'Task ID',
type: 'string'
},
{
name: 'logs',
description: 'Include execution logs',
type: 'boolean',
default: false
}
],
action: async (ctx) => {
let taskId = ctx.args[0] || ctx.flags.id;
if (!taskId && ctx.interactive) {
taskId = await input({
message: 'Enter task ID:',
validate: (v) => v.length > 0 || 'Task ID is required'
});
}
if (!taskId) {
output.printError('Task ID is required');
return { success: false, exitCode: 1 };
}
try {
const result = await callMCPTool('task_status', {
taskId,
includeLogs: ctx.flags.logs,
includeMetrics: true
});
if (ctx.flags.format === 'json') {
output.printJson(result);
return { success: true, data: result };
}
output.writeln();
output.printBox([
`Type: ${result.type}`,
`Status: ${formatStatus(result.status)}`,
`Priority: ${formatPriority(result.priority)}`,
`Progress: ${result.progress}%`,
'',
`Description: ${result.description}`
].join('\n'), `Task: ${result.id}`);
// Assignment info
output.writeln();
output.writeln(output.bold('Assignment'));
output.printTable({
columns: [
{ key: 'property', header: 'Property', width: 15 },
{ key: 'value', header: 'Value', width: 40 }
],
data: [
{ property: 'Assigned To', value: result.assignedTo?.join(', ') || 'Unassigned' },
{ property: 'Parent Task', value: result.parentId || 'None' },
{ property: 'Dependencies', value: result.dependencies.join(', ') || 'None' },
{ property: 'Dependents', value: result.dependents.join(', ') || 'None' },
{ property: 'Tags', value: result.tags.join(', ') || 'None' }
]
});
// Timeline
output.writeln();
output.writeln(output.bold('Timeline'));
output.printTable({
columns: [
{ key: 'event', header: 'Event', width: 15 },
{ key: 'time', header: 'Time', width: 30 }
],
data: [
{ event: 'Created', time: new Date(result.createdAt).toLocaleString() },
{ event: 'Started', time: result.startedAt ? new Date(result.startedAt).toLocaleString() : '-' },
{ event: 'Completed', time: result.completedAt ? new Date(result.completedAt).toLocaleString() : '-' }
]
});
// Metrics
if (result.metrics) {
output.writeln();
output.writeln(output.bold('Metrics'));
output.printTable({
columns: [
{ key: 'metric', header: 'Metric', width: 20 },
{ key: 'value', header: 'Value', width: 20, align: 'right' }
],
data: [
{ metric: 'Execution Time', value: `${(result.metrics.executionTime / 1000).toFixed(2)}s` },
{ metric: 'Retries', value: result.metrics.retries },
{ metric: 'Tokens Used', value: result.metrics.tokensUsed.toLocaleString() }
]
});
}
// Error if failed
if (result.status === 'failed' && result.error) {
output.writeln();
output.printError(`Error: ${result.error}`);
}
// Logs if requested
if (ctx.flags.logs && result.logs && result.logs.length > 0) {
output.writeln();
output.writeln(output.bold('Execution Logs'));
for (const log of result.logs.slice(-20)) {
const time = new Date(log.timestamp).toLocaleTimeString();
const level = log.level === 'error' ? output.error(`[${log.level}]`) :
log.level === 'warn' ? output.warning(`[${log.level}]`) :
output.dim(`[${log.level}]`);
output.writeln(` ${output.dim(time)} ${level} ${log.message}`);
}
}
return { success: true, data: result };
}
catch (error) {
if (error instanceof MCPClientError) {
output.printError(`Failed to get task status: ${error.message}`);
}
else {
output.printError(`Unexpected error: ${String(error)}`);
}
return { success: false, exitCode: 1 };
}
}
};
// Cancel subcommand
const cancelCommand = {
name: 'cancel',
aliases: ['abort', 'stop'],
description: 'Cancel a running task',
options: [
{
name: 'force',
short: 'f',
description: 'Force cancel without confirmation',
type: 'boolean',
default: false
},
{
name: 'reason',
short: 'r',
description: 'Cancellation reason',
type: 'string'
}
],
action: async (ctx) => {
const taskId = ctx.args[0];
const force = ctx.flags.force;
const reason = ctx.flags.reason;
if (!taskId) {
output.printError('Task ID is required');
return { success: false, exitCode: 1 };
}
if (!force && ctx.interactive) {
const confirmed = await confirm({
message: `Are you sure you want to cancel task ${taskId}?`,
default: false
});
if (!confirmed) {
output.printInfo('Operation cancelled');
return { success: true };
}
}
try {
const result = await callMCPTool('task_cancel', {
taskId,
reason: reason || 'Cancelled by user via CLI'
});
output.writeln();
output.printSuccess(`Task ${taskId} cancelled`);
output.printInfo(`Previous status: ${result.previousStatus}`);
if (ctx.flags.format === 'json') {
output.printJson(result);
}
return { success: true, data: result };
}
catch (error) {
if (error instanceof MCPClientError) {
output.printError(`Failed to cancel task: ${error.message}`);
}
else {
output.printError(`Unexpected error: ${String(error)}`);
}
return { success: false, exitCode: 1 };
}
}
};
// Assign subcommand
const assignCommand = {
name: 'assign',
description: 'Assign a task to agent(s)',
options: [
{
name: 'agent',
short: 'a',
description: 'Agent ID(s) to assign (comma-separated)',
type: 'string'
},
{
name: 'unassign',
description: 'Remove current assignment',
type: 'boolean',
default: false
}
],
action: async (ctx) => {
const taskId = ctx.args[0];
const agentIds = ctx.flags.agent;
const unassign = ctx.flags.unassign;
if (!taskId) {
output.printError('Task ID is required');
return { success: false, exitCode: 1 };
}
if (!agentIds && !unassign) {
// Interactive agent selection
if (ctx.interactive) {
try {
const agents = await callMCPTool('agent_list', { status: 'active,idle' });
if (agents.agents.length === 0) {
output.printWarning('No available agents');
return { success: false, exitCode: 1 };
}
const selectedAgents = await multiSelect({
message: 'Select agent(s) to assign:',
options: agents.agents.map(a => ({
value: a.id,
label: a.id,
hint: `${a.type} - ${a.status}`
})),
required: true
});
if (selectedAgents.length === 0) {
output.printInfo('No agents selected');
return { success: true };
}
// Continue with assignment
const result = await callMCPTool('task_assign', {
taskId,
agentIds: selectedAgents
});
output.writeln();
output.printSuccess(`Task ${taskId} assigned to ${result.assignedTo.join(', ')}`);
return { success: true, data: result };
}
catch (error) {
if (error instanceof Error && error.message === 'User cancelled') {
output.printInfo('Operation cancelled');
return { success: true };
}
throw error;
}
}
output.printError('Agent ID is required. Use --agent flag or run in interactive mode');
return { success: false, exitCode: 1 };
}
try {
const result = await callMCPTool('task_assign', {
taskId,
agentIds: unassign ? [] : agentIds.split(',').map(id => id.trim()),
unassign
});
output.writeln();
if (unassign) {
output.printSuccess(`Task ${taskId} unassigned`);
}
else {
output.printSuccess(`Task ${taskId} assigned to ${result.assignedTo.join(', ')}`);
}
if (ctx.flags.format === 'json') {
output.printJson(result);
}
return { success: true, data: result };
}
catch (error) {
if (error instanceof MCPClientError) {
output.printError(`Failed to assign task: ${error.message}`);
}
else {
output.printError(`Unexpected error: ${String(error)}`);
}
return { success: false, exitCode: 1 };
}
}
};
// Retry subcommand
const retryCommand = {
name: 'retry',
aliases: ['rerun'],
description: 'Retry a failed task',
options: [
{
name: 'reset-state',
description: 'Reset task state completely',
type: 'boolean',
default: false
}
],
action: async (ctx) => {
const taskId = ctx.args[0];
const resetState = ctx.flags['reset-state'];
if (!taskId) {
output.printError('Task ID is required');
return { success: false, exitCode: 1 };
}
try {
const result = await callMCPTool('task_retry', {
taskId,
resetState
});
output.writeln();
output.printSuccess(`Task ${taskId} retried`);
output.printInfo(`New task ID: ${result.newTaskId}`);
output.printInfo(`Status: ${formatStatus(result.status)}`);
if (ctx.flags.format === 'json') {
output.printJson(result);
}
return { success: true, data: result };
}
catch (error) {
if (error instanceof MCPClientError) {
output.printError(`Failed to retry task: ${error.message}`);
}
else {
output.printError(`Unexpected error: ${String(error)}`);
}
return { success: false, exitCode: 1 };
}
}
};
// Main task command
export const taskCommand = {
name: 'task',
description: 'Task management commands',
subcommands: [createCommand, listCommand, statusCommand, cancelCommand, assignCommand, retryCommand],
options: [],
examples: [
{ command: 'claude-flow task create -t implementation -d "Add user auth"', description: 'Create a task' },
{ command: 'claude-flow task list', description: 'List pending/running tasks' },
{ command: 'claude-flow task list --all', description: 'List all tasks' },
{ command: 'claude-flow task status task-123', description: 'Get task details' },
{ command: 'claude-flow task cancel task-123', description: 'Cancel a task' },
{ command: 'claude-flow task assign task-123 --agent coder-1', description: 'Assign task to agent' },
{ command: 'claude-flow task retry task-123', description: 'Retry a failed task' }
],
action: async (ctx) => {
// Show help if no subcommand
output.writeln();
output.writeln(output.bold('Task Management Commands'));
output.writeln();
output.writeln('Usage: claude-flow task <subcommand> [options]');
output.writeln();
output.writeln('Subcommands:');
output.printList([
`${output.highlight('create')} - Create a new task`,
`${output.highlight('list')} - List tasks`,
`${output.highlight('status')} - Get task details`,
`${output.highlight('cancel')} - Cancel a running task`,
`${output.highlight('assign')} - Assign task to agent(s)`,
`${output.highlight('retry')} - Retry a failed task`
]);
output.writeln();
output.writeln('Run "claude-flow task <subcommand> --help" for subcommand help');
return { success: true };
}
};
export default taskCommand;
//# sourceMappingURL=task.js.map