UNPKG

taskwerk

Version:

A task management CLI for developers and AI agents working together

227 lines (190 loc) 7.48 kB
import { Command } from 'commander'; import { ContextManager } from '../../chat/context-manager.js'; import { TaskwerkDatabase } from '../../db/database.js'; import chalk from 'chalk'; // Simple date formatting without external dependencies export function listContextsCommand() { const list = new Command('list'); list .description('List all chat conversations') .option('-g, --global', 'Show only global conversations') .option('-p, --project', 'Show only project conversations') .option('-t, --type <type>', 'Filter by type (ask/agent)') .option('--all', 'Include inactive conversations') .action(async options => { try { // Try both project and global databases const databases = []; // Try project database try { const projectDb = new TaskwerkDatabase(); await projectDb.connect(); databases.push({ db: projectDb, scope: 'project' }); } catch (e) { // No project database } // Always check global database const globalDb = new TaskwerkDatabase({ isGlobal: true }); await globalDb.connect(); databases.push({ db: globalDb, scope: 'global' }); console.log(chalk.bold('🗨️ Your Chat Conversations\n')); let allContexts = []; let currentProjectId = null; // Collect contexts from all databases for (const { db, scope } of databases) { const contextManager = new ContextManager(db.getDB()); // Get project ID if in project if (scope === 'project') { const result = await contextManager.detectProject(); if (result.isProject) { currentProjectId = result.projectId; } } // Get contexts based on filters let contexts; if (options.global && scope === 'project') { continue; } if (options.project && scope === 'global') { continue; } if (options.global) { contexts = await contextManager.listContexts('global'); } else if (options.project) { contexts = await contextManager.listContexts('project'); } else { contexts = await contextManager.listContexts(); } // Add scope info to each context contexts.forEach(ctx => { ctx._dbScope = scope; ctx._isCurrentProject = scope === 'project' && ctx.project_id === currentProjectId; }); allContexts = allContexts.concat(contexts); } // Filter by type if specified if (options.type) { allContexts = allContexts.filter(ctx => ctx.type === options.type); } // Filter out inactive unless --all if (!options.all) { const oneWeekAgo = new Date(); oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); allContexts = allContexts.filter( ctx => new Date(ctx.last_active) > oneWeekAgo || ctx.turn_count > 5 ); } if (allContexts.length === 0) { console.log(chalk.gray('No conversations found.\n')); console.log('💡 Start a conversation with:'); console.log(' twrk ask "your question"'); console.log(' twrk agent "your task"'); return; } // Group by scope const grouped = { currentProject: [], otherProject: [], global: [], }; allContexts.forEach(ctx => { if (ctx._isCurrentProject) { grouped.currentProject.push(ctx); } else if (ctx.scope === 'project') { grouped.otherProject.push(ctx); } else { grouped.global.push(ctx); } }); // Display current project contexts if (grouped.currentProject.length > 0) { console.log(chalk.blue.bold(`📁 Current Project (${currentProjectId})`)); displayContextList(grouped.currentProject); console.log(); } // Display global contexts if (grouped.global.length > 0) { console.log(chalk.green.bold('🌍 Global Conversations')); displayContextList(grouped.global); console.log(); } // Display other project contexts if (grouped.otherProject.length > 0) { console.log(chalk.gray.bold('📁 Other Projects')); displayContextList(grouped.otherProject); console.log(); } // Show helpful tips console.log(chalk.gray('─'.repeat(60))); console.log('💡 Tips:'); console.log(' • Use "twrk context show <id>" to view conversation history'); console.log(' • Use "twrk context switch <name>" to switch conversations'); console.log(' • Add --all to see all conversations including old ones'); // Close databases for (const { db } of databases) { db.close(); } } catch (error) { console.error('❌ Failed to list conversations:', error.message); process.exit(1); } }); return list; } function displayContextList(contexts) { // Sort by last active contexts.sort((a, b) => new Date(b.last_active) - new Date(a.last_active)); contexts.forEach(ctx => { const isActive = isRecentlyActive(ctx); const typeIcon = ctx.type === 'agent' ? '🤖' : '💬'; const statusIcon = isActive ? '🟢' : '⚪'; // Format the display const idDisplay = chalk.gray(`[${ctx.id}]`); const nameDisplay = ctx.name === 'general' ? chalk.bold(ctx.name) : chalk.cyan(ctx.name); const projectDisplay = ctx.project_id !== 'GLOBAL' ? chalk.gray(` (${ctx.project_id})`) : ''; console.log(` ${statusIcon} ${typeIcon} ${nameDisplay}${projectDisplay} ${idDisplay}`); // Show metadata const lastActive = getRelativeTime(new Date(ctx.last_active)); const turns = ctx.turn_count || 0; const turnsText = turns === 1 ? '1 message' : `${turns} messages`; console.log(chalk.gray(` Last active: ${lastActive}${turnsText}`)); // Show first prompt preview if available if (ctx.first_prompt) { const preview = ctx.first_prompt.length > 50 ? ctx.first_prompt.substring(0, 50) + '...' : ctx.first_prompt; console.log(chalk.gray(` Started with: "${preview}"`)); } console.log(); }); } function isRecentlyActive(context) { const hourAgo = new Date(); hourAgo.setHours(hourAgo.getHours() - 1); return new Date(context.last_active) > hourAgo; } function getRelativeTime(date) { const now = new Date(); const diffMs = now - date; const diffSecs = Math.floor(diffMs / 1000); const diffMins = Math.floor(diffSecs / 60); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); if (diffSecs < 60) { return 'just now'; } if (diffMins < 60) { return `${diffMins} minute${diffMins === 1 ? '' : 's'} ago`; } if (diffHours < 24) { return `${diffHours} hour${diffHours === 1 ? '' : 's'} ago`; } if (diffDays < 30) { return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`; } const diffMonths = Math.floor(diffDays / 30); if (diffMonths < 12) { return `${diffMonths} month${diffMonths === 1 ? '' : 's'} ago`; } const diffYears = Math.floor(diffMonths / 12); return `${diffYears} year${diffYears === 1 ? '' : 's'} ago`; }