UNPKG

taskwerk

Version:

A task management CLI for developers and AI agents working together

307 lines (260 loc) 9.39 kB
import { Command } from 'commander'; import { TaskwerkAPI } from '../api/taskwerk-api.js'; import { Logger } from '../logging/logger.js'; import fs from 'fs/promises'; export function exportCommand() { const exp = new Command('export'); exp .description('Export tasks to a file') .option('-f, --format <format>', 'Export format (json, markdown, csv)', 'markdown') .option('-o, --output <file>', 'Output file path (auto-generated if not specified)') .option('-s, --status <status>', 'Filter by status') .option('-a, --assignee <name>', 'Filter by assignee') .option('-t, --tasks <ids...>', 'Export specific tasks by ID') .option('--all', 'Include completed and cancelled tasks') .option('--with-subtasks', 'Include subtasks of exported tasks') .option('--with-metadata', 'Include YAML frontmatter metadata (markdown only)') .option('--stdout', 'Output to stdout instead of file') .addHelpText( 'after', ` Examples: Basic exports: $ twrk export # Export active tasks to dated file $ twrk export -o mytasks.md # Export to specific filename $ twrk export --stdout # Output to terminal (pipe-friendly) $ twrk export --all # Include completed/cancelled tasks Export specific tasks: $ twrk export -t 1 2 3 # Export tasks 1, 2, and 3 $ twrk export -t TASK-001 TASK-002 # Use full IDs $ twrk export -t 1 --with-subtasks # Include all subtasks Filter exports: $ twrk export -s todo # Only todo tasks $ twrk export -s in-progress -a @john # John's active tasks $ twrk export -a @ai-agent # Tasks for AI to work on Different formats: $ twrk export -f json # JSON for programmatic use $ twrk export -f csv # CSV for spreadsheets $ twrk export -f markdown --with-metadata # Markdown with YAML frontmatter AI/LLM workflows: $ twrk export -t 1 2 3 --stdout | pbcopy # Copy to clipboard (macOS) $ twrk export -a @ai-agent -o ai-tasks.md # Export AI's assigned tasks $ twrk export -s todo --stdout | llm # Pipe directly to LLM CLI Advanced examples: $ twrk export -t $(twrk listtask -s blocked --format json | jq -r '.[].id') $ twrk export --all -f json | jq '.[] | select(.priority=="high")' $ twrk export -s todo --stdout | grep -A5 "Priority: high" Note: - Default format is Markdown, perfect for LLMs - Files are saved as 'tasks-export-YYYY-MM-DD.{ext}' - Use --stdout to pipe to other commands` ) .action(async options => { const logger = new Logger('export'); try { const api = new TaskwerkAPI(); // Build query options const queryOptions = {}; if (options.status) { queryOptions.status = options.status; } if (options.assignee) { queryOptions.assignee = options.assignee; } // Get tasks let tasks; if (options.tasks && options.tasks.length > 0) { // Export specific tasks by ID tasks = []; for (const taskId of options.tasks) { try { const task = api.getTask(taskId); tasks.push(task); // Include subtasks if requested if (options.withSubtasks) { const subtasks = api.getSubtasks(task.id); tasks.push(...subtasks); } } catch (error) { logger.warn(`Failed to get task ${taskId}: ${error.message}`); console.warn(`⚠️ Skipping ${taskId}: ${error.message}`); } } } else { // Export filtered tasks tasks = api.listTasks(queryOptions); // Filter out completed and cancelled tasks unless --all is specified if (!options.all) { tasks = tasks.filter( task => task.status !== 'completed' && task.status !== 'cancelled' ); } } if (tasks.length === 0) { console.log('No tasks found matching the criteria.'); return; } // Format output based on format option let output; switch (options.format) { case 'markdown': output = await formatAsMarkdown(tasks, api, options.withMetadata); break; case 'json': output = formatAsJson(tasks); break; case 'csv': output = formatAsCsv(tasks); break; default: throw new Error(`Unsupported format: ${options.format}`); } // Write output if (options.stdout) { // Output to stdout (old behavior) console.log(output); } else { // Save to file (new default behavior) let filename = options.output; if (!filename) { // Generate filename based on date and format const date = new Date().toISOString().split('T')[0]; const extension = options.format === 'json' ? 'json' : options.format === 'csv' ? 'csv' : 'md'; filename = `tasks-export-${date}.${extension}`; } await fs.writeFile(filename, output, 'utf8'); console.log(`✅ Exported ${tasks.length} tasks to ${filename}`); } } catch (error) { logger.error('Export failed', error); console.error('❌ Export failed:', error.message); process.exit(1); } }); return exp; } async function formatAsMarkdown(tasks, api, includeMetadata = false) { const date = new Date().toISOString().split('T')[0]; let output = ''; if (includeMetadata) { output += '---\n'; output += `title: Tasks Export\n`; output += `date: ${date}\n`; output += `taskCount: ${tasks.length}\n`; output += '---\n\n'; } output += `# Tasks Export - ${date}\n\n`; // Add LLM-friendly instructions output += `> The following tasks are exported from the Taskwerk task management system. Each task includes its ID, name, status, priority, and other relevant details.\n\n`; for (const task of tasks) { output += `## ${task.id}: ${task.name}\n`; output += `- Status: ${task.status}\n`; output += `- Priority: ${task.priority}\n`; if (task.assignee) { output += `- Assignee: ${task.assignee}\n`; } if (task.estimate) { output += `- Estimate: ${task.estimate} hours\n`; } if (task.due_date) { output += `- Due: ${task.due_date}\n`; } // Get and display tags const tags = api.getTaskTags(task.id); if (tags.length > 0) { output += `- Tags: ${tags.join(', ')}\n`; } if (task.category) { output += `- Category: ${task.category}\n`; } output += `- Created: ${new Date(task.created_at).toLocaleString()}\n`; if (task.updated_at) { output += `- Updated: ${new Date(task.updated_at).toLocaleString()}\n`; } if (task.description) { output += `\n${task.description}\n`; } if (task.content) { output += `\n### Details\n${task.content}\n`; } // Get and display notes const notes = api.getTaskNotes(task.id); if (notes.length > 0) { output += `\n### Notes\n`; for (const note of notes) { const noteDate = new Date(note.created_at).toLocaleString(); output += `- [${noteDate}] @${note.user}: ${note.note}\n`; if (note.content) { const indentedContent = note.content .split('\n') .map(line => ` ${line}`) .join('\n'); output += `${indentedContent}\n`; } } } // Get dependencies const dependencies = api.getTaskDependencies(task.id); if (dependencies.length > 0) { output += `\n### Dependencies\n`; for (const dep of dependencies) { output += `- ${dep.id}: ${dep.name}\n`; } } output += '\n---\n\n'; } return output.trim() + '\n'; } function formatAsJson(tasks) { return JSON.stringify(tasks, null, 2) + '\n'; } function formatAsCsv(tasks) { const headers = [ 'ID', 'Name', 'Description', 'Status', 'Priority', 'Assignee', 'Category', 'Estimate', 'Due Date', 'Created At', 'Updated At', 'Created By', 'Updated By', ]; let output = headers.join(',') + '\n'; for (const task of tasks) { const row = [ task.id, escapeCSV(task.name), escapeCSV(task.description || ''), task.status, task.priority, task.assignee || '', task.category || '', task.estimate || '', task.due_date || '', task.created_at, task.updated_at || '', task.created_by || '', task.updated_by || '', ]; output += row.join(',') + '\n'; } return output; } function escapeCSV(value) { if (typeof value !== 'string') { return value; } // If value contains comma, newline, or double quote, wrap in quotes if (value.includes(',') || value.includes('\n') || value.includes('"')) { // Escape double quotes by doubling them value = value.replace(/"/g, '""'); return `"${value}"`; } return value; }