import { Command } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import { getApiKey, getProjectId, getApiUrl } from '../utils/config.js'; import axios from 'axios'; import { execSync } from 'child_process'; import fs from 'fs/promises'; import path from 'path'; export function createFocusCommand() { const focus = new Command('focus'); focus .alias('task') .description('Get the next active roadmap task and copy its prompt to clipboard') .option('--no-copy', 'Do not copy to clipboard') .action(async (options) => { const spinner = ora('Fetching next objective...').start(); // Get config let apiKey: string; let projectId: string | undefined; try { apiKey = getApiKey(); } catch (e) { spinner.fail(chalk.red('Not authenticated. Run "rigstate login" first.')); return; } projectId = getProjectId(); if (!projectId) { const { loadManifest } = await import('../utils/manifest.js'); const manifest = await loadManifest(); if (manifest?.project_id) projectId = manifest.project_id; } if (!projectId) { spinner.fail(chalk.red('No project context. Run "rigstate link" first.')); return; } const apiUrl = getApiUrl(); try { // Fetch roadmap const response = await axios.get(`${apiUrl}/api/v1/roadmap`, { params: { project_id: projectId }, headers: { Authorization: `Bearer ${apiKey}` } }); if (!response.data.success) { throw new Error(response.data.error || 'Failed to fetch roadmap'); } const roadmap = response.data.data.roadmap || []; // Priority: IN_PROGRESS > ACTIVE > LOCKED const statusPriority: Record = { 'IN_PROGRESS': 0, 'ACTIVE': 1, 'LOCKED': 2 }; const activeTasks = roadmap .filter((t: any) => ['IN_PROGRESS', 'ACTIVE', 'LOCKED'].includes(t.status)) .sort((a: any, b: any) => { const pA = statusPriority[a.status] ?? 99; const pB = statusPriority[b.status] ?? 99; if (pA !== pB) return pA - pB; return (a.step_number || 0) - (b.step_number || 0); }); if (activeTasks.length === 0) { spinner.succeed('All caught up! No active tasks found.'); return; } const nextTask = activeTasks[0]; spinner.stop(); // Display console.log(''); console.log(chalk.bold.blue(`📌 Task #${nextTask.step_number || '?'}: ${nextTask.title}`)); const statusColor = nextTask.status === 'IN_PROGRESS' ? chalk.yellow : nextTask.status === 'ACTIVE' ? chalk.green : chalk.dim; console.log(chalk.dim('Status: ') + statusColor(nextTask.status)); console.log(chalk.dim('─'.repeat(60))); if (nextTask.prompt_content) { console.log(chalk.white(nextTask.prompt_content)); console.log(chalk.dim('─'.repeat(60))); // Auto-copy for Mac if (options.copy !== false) { try { if (process.platform === 'darwin') { execSync('pbcopy', { input: nextTask.prompt_content }); console.log(chalk.green('✅ Prompt copied to clipboard! Ready to paste (Cmd+V).')); } else if (process.platform === 'linux') { try { execSync('xclip -selection clipboard', { input: nextTask.prompt_content }); console.log(chalk.green('✅ Prompt copied to clipboard!')); } catch (e) { console.log(chalk.yellow('â„šī¸ Copy prompt manually (xclip not available)')); } } else { console.log(chalk.yellow('â„šī¸ Copy prompt manually (Auto-copy not supported on this OS)')); } } catch (e) { // ignore copy error } } } else { console.log(chalk.yellow('No prompt instructions available.')); if (nextTask.architectural_brief) { console.log(chalk.bold('Brief:')); console.log(nextTask.architectural_brief); } } console.log(''); } catch (e: any) { spinner.fail(chalk.red(`Failed to fetch task: ${e.message}`)); } }); return focus; }