UNPKG

focus-productivity-cli

Version:

An ADHD-friendly productivity CLI tool built to run inside Warp terminal

208 lines (171 loc) • 7.01 kB
const chalk = require('chalk'); const notifier = require('node-notifier'); const path = require('path'); class FocusTimer { constructor() { this.activeSession = null; this.timerInterval = null; } async start(minutes) { if (this.activeSession) { throw new Error('A focus session is already active. Stop the current session first.'); } const startTime = Date.now(); const endTime = startTime + (minutes * 60 * 1000); this.activeSession = { startTime, endTime, duration: minutes, completed: false }; // Display initial status console.log(chalk.bgBlue.white.bold(`\n šŸŽÆ FOCUS MODE ACTIVATED šŸŽÆ `)); console.log(chalk.blue(`ā±ļø Duration: ${minutes} minutes`)); console.log(chalk.gray(`šŸ“… Started: ${new Date(startTime).toLocaleTimeString()}`)); console.log(chalk.yellow(`\nšŸ’” Tips for staying focused:`)); console.log(chalk.gray(` • Turn off notifications on other apps`)); console.log(chalk.gray(` • Keep water nearby`)); console.log(chalk.gray(` • Take deep breaths if you feel distracted\n`)); // Start the countdown timer this.startCountdown(); return new Promise((resolve) => { this.onComplete = resolve; }); } startCountdown() { const updateInterval = 1000; // Update every second this.timerInterval = setInterval(() => { const now = Date.now(); const remaining = this.activeSession.endTime - now; if (remaining <= 0) { this.completeSession(); return; } // Update display every 30 seconds or when less than 1 minute remains const remainingSeconds = Math.floor(remaining / 1000); if (remainingSeconds % 30 === 0 || remainingSeconds < 60) { this.displayProgress(remaining); } }, updateInterval); } displayProgress(remaining) { const minutes = Math.floor(remaining / 60000); const seconds = Math.floor((remaining % 60000) / 1000); // Create progress bar const totalDuration = this.activeSession.duration * 60; const elapsed = totalDuration - Math.floor(remaining / 1000); const progress = Math.floor((elapsed / totalDuration) * 20); // 20 char progress bar const progressBar = 'ā–ˆ'.repeat(progress) + 'ā–‘'.repeat(20 - progress); // Clear line and display new progress process.stdout.write('\r'); process.stdout.write(chalk.cyan(`ā±ļø ${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')} `)); process.stdout.write(chalk.blue(`[${progressBar}] `)); process.stdout.write(chalk.yellow(`${Math.floor((elapsed / totalDuration) * 100)}%`)); // Show encouraging messages if (minutes === 0 && seconds <= 10) { process.stdout.write(chalk.red.bold(' šŸ”„ FINAL COUNTDOWN!')); } else if (minutes <= 5) { process.stdout.write(chalk.yellow(' šŸ’Ŗ You\'re almost there!')); } else if (progress >= 10) { process.stdout.write(chalk.green(' 🌟 Great focus!')); } } async completeSession() { if (this.timerInterval) { clearInterval(this.timerInterval); this.timerInterval = null; } const actualDuration = (Date.now() - this.activeSession.startTime) / 1000 / 60; // in minutes this.activeSession.completed = true; this.activeSession.actualDuration = actualDuration; // Clear the progress line process.stdout.write('\r' + ' '.repeat(80) + '\r'); // Show completion message console.log(chalk.bgGreen.black.bold(`\n šŸŽ‰ FOCUS SESSION COMPLETE! šŸŽ‰ `)); console.log(chalk.green(`āœ… You stayed focused for ${Math.floor(actualDuration)} minutes!`)); console.log(chalk.cyan(`🧠 Great job exercising your focus muscle!`)); // Show motivational message const completionMessages = [ "🌟 Every focused minute builds your concentration superpower!", "šŸ’Ž You just turned time into productivity gems!", "šŸš€ Your brain is getting stronger with each session!", "šŸ† Champions are made of moments like these!", "⚔ You're building the habit that changes everything!" ]; const randomMessage = completionMessages[Math.floor(Math.random() * completionMessages.length)]; console.log(chalk.magenta(`\n${randomMessage}\n`)); // Send system notification this.sendNotification(); const completedSession = { ...this.activeSession }; this.activeSession = null; if (this.onComplete) { this.onComplete(completedSession); } return completedSession; } async stop() { if (!this.activeSession) { return null; } if (this.timerInterval) { clearInterval(this.timerInterval); this.timerInterval = null; } const actualDuration = (Date.now() - this.activeSession.startTime) / 1000 / 60; // in minutes const stoppedSession = { ...this.activeSession, actualDuration, completed: false, stopped: true }; // Clear the progress line process.stdout.write('\r' + ' '.repeat(80) + '\r'); this.activeSession = null; return stoppedSession; } sendNotification() { try { notifier.notify({ title: 'šŸŽÆ FocusCLI', message: 'šŸŽ‰ Focus session complete! Great job staying focused!', icon: path.join(__dirname, '../assets/icon.png'), // Optional: add an icon later sound: 'Ping', // Only works on macOS/Windows wait: false }); } catch (error) { // Fail silently if notifications aren't available console.log(chalk.gray('(System notifications not available)')); } } getStatus() { if (!this.activeSession) { return { active: false, message: 'No active focus session' }; } const now = Date.now(); const remaining = this.activeSession.endTime - now; const minutes = Math.floor(remaining / 60000); const seconds = Math.floor((remaining % 60000) / 1000); return { active: true, remaining, minutes, seconds, duration: this.activeSession.duration, startTime: this.activeSession.startTime }; } // Pomodoro-specific helpers static getPomodoroSuggestion() { const suggestions = [ { duration: 25, type: 'Classic Pomodoro', description: 'The traditional 25-minute focus block' }, { duration: 15, type: 'Quick Sprint', description: 'Perfect for small tasks or when starting' }, { duration: 45, type: 'Deep Work', description: 'For complex tasks requiring deep concentration' }, { duration: 10, type: 'Micro Focus', description: 'Great for ADHD minds or when feeling overwhelmed' } ]; return suggestions[Math.floor(Math.random() * suggestions.length)]; } } module.exports = FocusTimer;