Skip to content

Instantly share code, notes, and snippets.

@Vaibhavs10
Created September 19, 2025 17:25
Show Gist options
  • Select an option

  • Save Vaibhavs10/4467cd28e53d0c0f0bbdc85d9ae59b50 to your computer and use it in GitHub Desktop.

Select an option

Save Vaibhavs10/4467cd28e53d0c0f0bbdc85d9ae59b50 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
Simple journaling CLI tool.
Features:
- Add a journal entry for today (or a specific date).
- View entry for a given date (default: today).
- List all dates with entries.
Entries are stored in a JSON file at ``~/.journal.json``.
"""
import argparse
import json
import os
import sys
from datetime import datetime
from pathlib import Path
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.prompt import Prompt
from rich.theme import Theme
from rich.markdown import Markdown
JOURNAL_PATH = Path.home() / ".journal.json"
# Define a zen theme
zen_theme = Theme({
"info": "dim cyan",
"warning": "magenta",
"danger": "bold red",
"success": "bold green",
"header": "bold bright_blue",
})
console = Console(theme=zen_theme)
def load_journal():
if JOURNAL_PATH.exists():
with open(JOURNAL_PATH, "r", encoding="utf-8") as f:
try:
return json.load(f)
except json.JSONDecodeError:
return {}
return {}
def save_journal(data):
with open(JOURNAL_PATH, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def add_entry(args):
journal = load_journal()
date_str = args.date or datetime.now().strftime("%Y-%m-%d")
entry_text = args.text
if not entry_text:
# Use a daily template with three reflective questions
console.print("[bold cyan]Please answer the following prompts (press Enter after each line).[/]")
did = Prompt.ask("[green]What did you do today?[/]")
better = Prompt.ask("[green]What could be better?[/]")
wrong = Prompt.ask("[green]What went wrong?[/]")
entry_text = f"## What did I do today?\n{did}\n\n## What could be better?\n{better}\n\n## What went wrong?\n{wrong}"
timestamp = datetime.now().isoformat(timespec="seconds")
journal[date_str] = {"timestamp": timestamp, "text": entry_text}
save_journal(journal)
console.print(f"[bright_green]Entry saved for {date_str}.[/]")
def view_entry(args):
journal = load_journal()
date_str = args.date or datetime.now().strftime("%Y-%m-%d")
entry = journal.get(date_str)
if entry:
md = Markdown(entry["text"], style="info")
console.print(f"[header]{date_str}[/header]")
console.print(md)
console.print(f"[dim]Recorded at: {entry['timestamp']}[/]")
else:
console.print(f"[danger]No entry found for {date_str}.[/]")
def list_entries(_):
journal = load_journal()
if not journal:
console.print("[yellow]No journal entries found.[/]")
return
table = Table(title="Journal entries")
table.add_column("Date", style="cyan", no_wrap=True)
table.add_column("Timestamp", style="magenta")
for d in sorted(journal.keys()):
ts = journal[d]["timestamp"]
table.add_row(d, ts)
console.print(table)
def print_banner():
banner = r"""
____ _ _ _ _
| _ \ __ _(_) |_ _ | | ___ _ _ _ __ _ __ | |
| | | |/ _` | | | | | | |/ _ \| | | | '__| '_ \ | |
| |_| | (_| | | | |_| | | (_) | |_| | | | | | ||_|
|____/ \__,_|_|_|\__, |_|\___/ \__,_|_| |_| |_|(_)
|___/
"""
console.print(banner, style="header")
def main():
print_banner()
parser = argparse.ArgumentParser(description="Simple journaling CLI")
subparsers = parser.add_subparsers(dest="command", required=True)
# add command
parser_add = subparsers.add_parser("add", help="Add a journal entry")
parser_add.add_argument("-t", "--text", type=str, help="Entry text (if omitted, read from stdin)")
parser_add.add_argument("-d", "--date", type=str, help="Date for the entry (YYYY-MM-DD), default today")
parser_add.set_defaults(func=add_entry)
# view command
parser_view = subparsers.add_parser("view", help="View an entry for a date")
parser_view.add_argument("-d", "--date", type=str, help="Date to view (YYYY-MM-DD), default today")
parser_view.set_defaults(func=view_entry)
# list command
parser_list = subparsers.add_parser("list", help="List all entry dates")
parser_list.set_defaults(func=list_entries)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment