Build a CLI "Vibe Check" Tool in One Afternoon 🎯
A weekend project that's actually useful on Monday
It's Saturday afternoon. You've got a few hours, a cup of coffee, and the urge to build something. Not another todo app. Something with personality. Something you'll actually use.
Let's build vibe-check — a CLI tool that analyzes any text and tells you its emotional tone, formality level, and gives you a rewritten version if the vibe is off. Perfect for reviewing emails before you send something you'll regret, or checking if your documentation sounds too aggressive.
Time needed: 2-3 hours
Difficulty: Beginner-friendly
What you'll learn: CLI design, API integration, Python packaging
The Vision
$ echo "Per my last email, I've attached the document." | vibe-check
📊 Vibe Analysis
━━━━━━━━━━━━━━━━━━━━━━━━━━
Tone: 😤 Passive-aggressive (87%)
Formality: 🎩 Corporate stiff
Energy: ⚡ Low-key hostile
💡 Suggested rewrite:
"I've attached the document — let me know if you need anything else!"
Yes. We're building this. Let's go.
Step 1: Project Setup (10 minutes)
Create your project structure:
mkdir vibe-check && cd vibe-check
python -m venv venv
source venv/bin/activate # or `venv\Scripts\activate` on Windows
pip install openai click rich
We're using:
- click — Makes CLI tools painless
- rich — Beautiful terminal output
- openai — For the AI analysis (works with any OpenAI-compatible API)
Step 2: The Core Analyzer (30 minutes)
Create vibe_check/analyzer.py:
import json
from openai import OpenAI
client = OpenAI() # Uses OPENAI_API_KEY env var
SYSTEM_PROMPT = """You are a communication tone analyzer. Given text, analyze:
1. tone: The emotional undertone (friendly, neutral, aggressive, passive-aggressive, anxious, confident, etc.)
2. tone_score: Confidence 0-100
3. tone_emoji: Single emoji representing the tone
4. formality: One of: casual, neutral, formal, corporate-stiff
5. formality_emoji: Single emoji for formality
6. energy: The vibe energy (enthusiastic, calm, low-key-hostile, desperate, chill, etc.)
7. energy_emoji: Single emoji for energy
8. suggestions: List of 1-3 brief improvement suggestions (empty if text is fine)
9. rewrite: A better version IF the tone could be improved, otherwise null
Return valid JSON only. No markdown, no explanation."""
def analyze_vibe(text: str) -> dict:
"""Analyze the vibe of given text."""
response = client.chat.completions.create(
model="gpt-4o-mini", # Cheap and fast
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Analyze this text:\n\n{text}"}
],
temperature=0.3,
max_tokens=500
)
result = response.choices[0].message.content
return json.loads(result)
The magic is in the prompt engineering. We're asking for structured JSON output with specific fields — this gives us predictable data to display beautifully.
Step 3: The Beautiful CLI (45 minutes)
Create vibe_check/cli.py:
import sys
import click
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from .analyzer import analyze_vibe
console = Console()
@click.command()
@click.argument('text', required=False)
@click.option('--file', '-f', type=click.File('r'), help='Read from file')
@click.option('--raw', is_flag=True, help='Output raw JSON')
def main(text, file, raw):
"""Analyze the vibe of any text. 🎯
Pass text as argument, pipe it in, or use --file.
Examples:
vibe-check "Looking forward to our meeting!"
echo "Per my last email..." | vibe-check
vibe-check -f email-draft.txt
"""
# Get input from various sources
if file:
content = file.read()
elif text:
content = text
elif not sys.stdin.isatty():
content = sys.stdin.read()
else:
console.print("[red]No input provided![/red] Pass text, use --file, or pipe input.")
raise SystemExit(1)
content = content.strip()
if not content:
console.print("[red]Empty input![/red]")
raise SystemExit(1)
# Analyze
with console.status("[bold blue]Checking vibes...[/bold blue]"):
result = analyze_vibe(content)
if raw:
import json
print(json.dumps(result, indent=2))
return
# Display results beautifully
display_results(result)
def display_results(result: dict):
"""Render analysis results with style."""
# Main metrics table
table = Table(show_header=False, box=None, padding=(0, 2))
table.add_column("Label", style="dim")
table.add_column("Value")
table.add_row(
"Tone:",
f"{result['tone_emoji']} {result['tone'].title()} ({result['tone_score']}%)"
)
table.add_row(
"Formality:",
f"{result['formality_emoji']} {result['formality'].replace('-', ' ').title()}"
)
table.add_row(
"Energy:",
f"{result['energy_emoji']} {result['energy'].replace('-', ' ').title()}"
)
console.print(Panel(table, title="📊 Vibe Analysis", border_style="blue"))
# Suggestions
if result.get('suggestions'):
suggestions = "\n".join(f"• {s}" for s in result['suggestions'])
console.print(Panel(suggestions, title="💡 Suggestions", border_style="yellow"))
# Rewrite
if result.get('rewrite'):
console.print(Panel(
result['rewrite'],
title="✨ Suggested Rewrite",
border_style="green"
))
else:
console.print("\n[green]✓ Vibes are good! No changes needed.[/green]")
if __name__ == "__main__":
main()
Step 4: Package It (15 minutes)
Create pyproject.toml:
[project]
name = "vibe-check"
version = "0.1.0"
description = "Check the vibe of any text 🎯"
requires-python = ">=3.9"
dependencies = [
"openai>=1.0.0",
"click>=8.0.0",
"rich>=13.0.0",
]
[project.scripts]
vibe-check = "vibe_check.cli:main"
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
Create vibe_check/__init__.py (empty file), then install:
pip install -e .
Step 5: Take It For a Spin
# The classic passive-aggressive email
$ vibe-check "As I mentioned before, the deadline was yesterday."
# Check your Slack message before sending
$ echo "Can we talk?" | vibe-check
# Review that PR comment
$ vibe-check "This code is... interesting. Did you test it?"
# Batch check your documentation
$ vibe-check -f README.md
Bonus: Add a "Fix It" Mode (30 minutes)
Want vibe-check to automatically copy the improved version to your clipboard? Add this to your CLI:
@click.option('--fix', is_flag=True, help='Copy improved version to clipboard')
# In main(), after analysis:
if fix and result.get('rewrite'):
import subprocess
subprocess.run(['pbcopy'], input=result['rewrite'].encode(), check=True) # macOS
# For Linux: subprocess.run(['xclip', '-selection', 'clipboard'], ...)
console.print("[green]✓ Improved version copied to clipboard![/green]")
Ideas to Extend This Weekend
Once the basics work, consider:
- Team calibration mode — Analyze your team's Slack channel and see communication patterns
- Git hook integration — Auto-check commit messages and PR descriptions
- Browser extension — Check emails before sending (that's a whole other weekend)
- Local mode — Swap OpenAI for Ollama and run completely offline
Wrapping Up
In about 2-3 hours, you've built a genuinely useful tool. The combination of Click + Rich makes CLI development delightful, and wrapping an LLM gives you capabilities that would've taken weeks to build with traditional NLP.
The best part? You'll actually use this. Next time you're about to send a message that sounds snippier than intended, pipe it through vibe-check first.
Happy building! 🛠️
What weekend project are you working on? Drop a comment below — I love seeing what people build in their spare time.
Top comments (0)