focus-productivity-cli
Version:
An ADHD-friendly productivity CLI tool built to run inside Warp terminal
208 lines (171 loc) ⢠7.01 kB
JavaScript
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;