weather-cli-16bit
Version:
A simple, intelligent weather CLI with smart location detection - no quotes needed
296 lines (254 loc) • 9.88 kB
JavaScript
import chalk from 'chalk';
import boxen from 'boxen';
// Weather emoji mapping
const weatherEmojis = {
Clear: '☀️',
Clouds: '☁️',
Rain: '🌧️',
Drizzle: '🌦️',
Thunderstorm: '⛈️',
Snow: '❄️',
Mist: '🌫️',
Fog: '🌫️',
Haze: '🌫️'
};
// Format temperature with color coding
function formatTemp(temp, displayUnit, options = {}) {
const unit = displayUnit === 'fahrenheit' ? '°F' : '°C';
const rounded = Math.round(temp);
const tempString = `${rounded}${unit}`;
if (options.colorCode) {
if (options.type === 'max') return chalk.red(tempString);
if (options.type === 'min') return chalk.blue(tempString);
if (options.type === 'current') return chalk.yellow(tempString);
}
return tempString;
}
// Format time
function formatTime(timestamp) {
return new Date(timestamp * 1000).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true
});
}
// Convert wind speed based on units
function formatWindSpeed(speed, displayUnit) {
if (displayUnit === 'fahrenheit') {
// Convert m/s to m/h
const mh = speed * 2.237;
return `${mh.toFixed(1)} m/h`;
}
return `${speed} m/s`;
}
// Create a data row with consistent alignment
function createDataRow(label, value, options = {}) {
const { labelWidth = 20, valueWidth = 30, icon = '' } = options;
const formattedLabel = icon ? `${icon} ${label}` : label;
return `${formattedLabel.padEnd(labelWidth)} ${value}`;
}
// Display weather banner with ASCII art
function displayWeatherBanner() {
console.log(chalk.cyan.bold(`
╔════════════════════════════════════════════╗
║ 🌤️ WEATHER CLI 🌤️ ║
║ Your Terminal Weather Friend ║
╚════════════════════════════════════════════╝
`));
}
// Get air quality description with color
function getAirQualityDescription(aqi) {
const descriptions = {
1: { text: 'Good', color: chalk.green },
2: { text: 'Fair', color: chalk.greenBright },
3: { text: 'Moderate', color: chalk.yellow },
4: { text: 'Poor', color: chalk.red },
5: { text: 'Very Poor', color: chalk.magenta }
};
const desc = descriptions[aqi] || { text: 'Unknown', color: chalk.gray };
return desc.color(desc.text);
}
// Display alerts
function displayAlerts(data) {
if (!data.pollution?.list?.[0]?.main?.aqi) return;
const aqi = data.pollution.list[0].main.aqi;
const description = getAirQualityDescription(aqi);
console.log(chalk.bold('\n⚠️ Air Quality Alert:'));
console.log(boxen(
`Air Quality Index: ${aqi}\nStatus: ${description}`,
{
padding: 0,
margin: 0,
borderStyle: 'round',
borderColor: aqi >= 4 ? 'red' : aqi >= 3 ? 'yellow' : 'green'
}
));
}
// Display sun times
function displaySunTimes(data) {
const { sunrise, sunset } = data.current.sys;
console.log(chalk.yellow(`\n🌅 Sunrise: ${formatTime(sunrise)}`));
console.log(chalk.magenta(`🌇 Sunset: ${formatTime(sunset)}`));
}
// Display ASCII art
function displayASCIIArt(weatherMain) {
const art = {
Clear: ' \\ / \n .-. \n ― ( ) ― \n `-´ \n / \\ ',
Clouds: ' .--. \n .-( ). \n (___.__)__) ',
Rain: ' .--. \n .-( ). \n (___.__)__) \n /|/|/ ',
Snow: ' .--. \n .-( ). \n (___.__)__) \n * * * * ',
Thunderstorm: ' .--. \n .-( ). \n (___.__)__) \n ⚡⚡⚡ '
};
return art[weatherMain] || art.Clouds;
}
// Get terminal width
function getTerminalWidth() {
return process.stdout.columns || 80;
}
// Display current weather in compact horizontal layout
function displayCurrentWeather(data, displayUnit) {
// Check if data has current property or is the weather data directly
const weather = data.current || data;
// Add safety checks for weather data
if (!weather || !weather.weather || !weather.weather[0]) {
console.error(chalk.red('❌ Invalid weather data structure'));
return;
}
const emoji = weatherEmojis[weather.weather[0].main] || '🌤️';
const terminalWidth = getTerminalWidth();
// Add timestamp
const timestamp = new Date().toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true
});
// Compact location header with timestamp
const locationHeader = `${emoji} ${chalk.cyan.bold(weather.name)}, ${chalk.yellow.bold(weather.sys.country)} ${chalk.gray(`• ${timestamp}`)}`;
// Compact separator - shorter
const separator = chalk.gray('─'.repeat(40));
// More compact sections with tighter spacing
const tempSection = [
createDataRow('Temp:', `${formatTemp(weather.main.temp, displayUnit, { colorCode: true, type: 'current' })} (feels ${formatTemp(weather.main.feels_like, displayUnit)})`, { labelWidth: 8 }),
createDataRow('Range:', `${formatTemp(weather.main.temp_min, displayUnit, { colorCode: true, type: 'min' })} - ${formatTemp(weather.main.temp_max, displayUnit, { colorCode: true, type: 'max' })}`, { labelWidth: 8 })
].join('\n');
const conditionsSection = [
createDataRow('Weather:', weather.weather[0].description, { labelWidth: 8 }),
createDataRow('Humidity:', `${weather.main.humidity}% • ${weather.main.pressure}hPa • ${(weather.visibility / 1000).toFixed(1)}km`, { labelWidth: 8 })
].join('\n');
const windSection = [
createDataRow('Wind:', `${formatWindSpeed(weather.wind.speed, displayUnit)} ${weather.wind.deg}°${weather.wind.gust ? ` (gust ${formatWindSpeed(weather.wind.gust, displayUnit)})` : ''}`, { labelWidth: 8 })
].join('\n');
const sunSection = [
createDataRow('Sun:', `${formatTime(weather.sys.sunrise)} - ${formatTime(weather.sys.sunset)}`, { labelWidth: 8 })
].join('\n');
// Air quality section - more compact
const aqi = data.pollution?.list?.[0]?.main?.aqi;
const airQualitySection = aqi ? [
createDataRow('Air:', `${getAirQualityDescription(aqi)} (AQI: ${aqi})`, { labelWidth: 8 })
].join('\n') : '';
// Always use single column for more compact display
const content = [
locationHeader,
separator,
tempSection,
conditionsSection,
windSection,
sunSection,
airQualitySection
].filter(Boolean).join('\n');
// More compact box with smaller padding
console.log(boxen(
content,
{
padding: { top: 0, bottom: 0, left: 1, right: 1 },
margin: 0,
borderStyle: 'round',
borderColor: 'cyan',
width: Math.min(terminalWidth - 2, 80)
}
));
}
// Display compact 5-day forecast
function display5DayForecast(data, displayUnit) {
const dailyData = {};
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const today = new Date().getDay();
data.forecast.list.forEach(item => {
const date = new Date(item.dt * 1000);
const dateKey = date.toDateString();
const dayIndex = date.getDay();
if (!dailyData[dateKey]) {
dailyData[dateKey] = {
temps: [],
descriptions: [],
dayName: dayNames[dayIndex],
isToday: dayIndex === today,
date: date
};
}
dailyData[dateKey].temps.push(item.main.temp);
dailyData[dateKey].descriptions.push({
main: item.weather[0].main,
description: item.weather[0].description
});
});
const forecastLines = Object.entries(dailyData).slice(0, 5).map(([_, info]) => {
const minTemp = Math.min(...info.temps);
const maxTemp = Math.max(...info.temps);
// Get most common weather
const weatherCount = {};
info.descriptions.forEach(desc => {
weatherCount[desc.main] = (weatherCount[desc.main] || 0) + 1;
});
const mostCommonWeather = Object.entries(weatherCount)
.sort(([,a], [,b]) => b - a)[0][0];
const emoji = weatherEmojis[mostCommonWeather] || '🌤️';
const dayLabel = info.isToday ? chalk.yellow(`${info.dayName}`) : info.dayName;
const temps = `${formatTemp(minTemp, displayUnit, { colorCode: true, type: 'min' })}/${formatTemp(maxTemp, displayUnit, { colorCode: true, type: 'max' })}`;
return `${emoji} ${dayLabel.padEnd(4)} ${temps}`;
});
console.log(boxen(
forecastLines.join('\n'),
{
padding: { top: 0, bottom: 0, left: 1, right: 1 },
margin: 0,
borderStyle: 'round',
borderColor: 'green'
}
));
}
// Display compact 24-hour forecast
function display24HourForecast(data, displayUnit) {
const next24Hours = data.forecast.list.slice(0, 8); // 3-hour intervals
const forecastLines = next24Hours.map(item => {
const time = new Date(item.dt * 1000).toLocaleTimeString('en-US', {
hour: 'numeric',
hour12: true
});
const emoji = weatherEmojis[item.weather[0].main] || '🌤️';
const temp = formatTemp(item.main.temp, displayUnit);
return `${time.padEnd(6)} ${emoji} ${temp.padEnd(6)} ${chalk.gray(item.weather[0].description)}`;
});
console.log(boxen(
forecastLines.join('\n'),
{
padding: { top: 0, bottom: 0, left: 1, right: 1 },
margin: 0,
borderStyle: 'round',
borderColor: 'blue'
}
));
}
export {
formatTemp,
formatTime,
getAirQualityDescription,
displayAlerts,
displaySunTimes,
displayASCIIArt,
displayCurrentWeather,
display5DayForecast,
display24HourForecast,
displayWeatherBanner,
createDataRow
};