├── habits.db ├── requirements.txt ├── __pycache__ ├── db.cpython-311.pyc ├── stats.cpython-311.pyc └── habits.cpython-311.pyc ├── README.md ├── main.py ├── db.py ├── templates └── index.html ├── habits.py ├── stats.py ├── LICENSE └── static └── styles.css /habits.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tubakhxn/habit-tracker-cli/HEAD/habits.db -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tubakhxn/habit-tracker-cli/HEAD/requirements.txt -------------------------------------------------------------------------------- /__pycache__/db.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tubakhxn/habit-tracker-cli/HEAD/__pycache__/db.cpython-311.pyc -------------------------------------------------------------------------------- /__pycache__/stats.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tubakhxn/habit-tracker-cli/HEAD/__pycache__/stats.cpython-311.pyc -------------------------------------------------------------------------------- /__pycache__/habits.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tubakhxn/habit-tracker-cli/HEAD/__pycache__/habits.cpython-311.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### File: README.md 2 | # Habit Tracker CLI App 3 | 4 | Track your daily habits from the command line! 🧘🏽‍♂️ 5 | 6 | ## Features 7 | - Add new habits 8 | - Mark habits as done for today 9 | - List all your habits 10 | - View streaks and completion stats 11 | 12 | ## Installation 13 | ```bash 14 | git clone https://github.com/tubakhxn/habit-tracker-cli 15 | cd habit-tracker-cli 16 | pip install click 17 | ``` 18 | 19 | ## Usage 20 | ```bash 21 | python main.py add "Drink Water" 22 | python main.py done "Drink Water" 23 | python main.py list 24 | python main.py stats 25 | ``` 26 | 27 | ## Tech Stack 28 | - Python 3 29 | - Click for CLI 30 | - SQLite for storage 31 | 32 | ## License 33 | MIT -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, redirect, url_for 2 | from habits import add_habit, complete_habit, list_habits 3 | from stats import get_all_stats 4 | from db import init_db 5 | 6 | app = Flask(__name__) 7 | 8 | @app.route('/') 9 | def index(): 10 | habits = list_habits() 11 | stats = get_all_stats() 12 | return render_template('index.html', habits=habits, stats=stats) 13 | 14 | @app.route('/add', methods=['POST']) 15 | def add(): 16 | name = request.form.get('habit') 17 | if name: 18 | add_habit(name) 19 | return redirect(url_for('index')) 20 | 21 | @app.route('/done/') 22 | def done(habit_name): 23 | complete_habit(habit_name) 24 | return redirect(url_for('index')) 25 | 26 | if __name__ == '__main__': 27 | init_db() 28 | app.run(debug=True) 29 | -------------------------------------------------------------------------------- /db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from pathlib import Path 3 | 4 | DB_PATH = Path("habits.db") 5 | 6 | def get_connection(): 7 | conn = sqlite3.connect(DB_PATH) 8 | conn.row_factory = sqlite3.Row 9 | return conn 10 | 11 | def init_db(): 12 | with get_connection() as conn: 13 | conn.execute(''' 14 | CREATE TABLE IF NOT EXISTS habits ( 15 | id INTEGER PRIMARY KEY AUTOINCREMENT, 16 | name TEXT NOT NULL, 17 | last_done TEXT 18 | ) 19 | ''') 20 | conn.execute(''' 21 | CREATE TABLE IF NOT EXISTS completions ( 22 | id INTEGER PRIMARY KEY AUTOINCREMENT, 23 | habit_id INTEGER, 24 | date TEXT, 25 | FOREIGN KEY(habit_id) REFERENCES habits(id) 26 | ) 27 | ''') 28 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Habit Tracker 6 | 7 | 8 | 9 |

Habit Tracker

10 |
11 | 12 | 13 |
14 |

My Habits

15 | {% for habit in habits %} 16 |
17 | {{ habit.name }} - Last done: {{ habit.last_done or 'Never' }} 18 | ✔️ Mark Done 19 |
Streak: {{ stats.get(habit.name, 0) }} days
20 |
21 | {% endfor %} 22 | 23 | 24 | -------------------------------------------------------------------------------- /habits.py: -------------------------------------------------------------------------------- 1 | ### File: habits.py 2 | from db import get_connection 3 | from datetime import datetime 4 | 5 | 6 | def add_habit(name): 7 | with get_connection() as conn: 8 | conn.execute("INSERT OR IGNORE INTO habits (name, last_done) VALUES (?, ?)", (name, None)) 9 | 10 | 11 | def complete_habit(name): 12 | today = datetime.today().strftime('%Y-%m-%d') 13 | with get_connection() as conn: 14 | habit = conn.execute("SELECT id FROM habits WHERE name = ?", (name,)).fetchone() 15 | if habit: 16 | conn.execute("INSERT INTO completions (habit_id, date) VALUES (?, ?)", (habit['id'], today)) 17 | conn.execute("UPDATE habits SET last_done = ? WHERE id = ?", (today, habit['id'])) 18 | 19 | 20 | def list_habits(): 21 | with get_connection() as conn: 22 | habits = conn.execute("SELECT name, last_done FROM habits").fetchall() 23 | return [dict(row) for row in habits] -------------------------------------------------------------------------------- /stats.py: -------------------------------------------------------------------------------- 1 | from db import get_connection 2 | from datetime import datetime, timedelta 3 | 4 | def get_all_stats(): 5 | with get_connection() as conn: 6 | habits = conn.execute("SELECT id, name FROM habits").fetchall() 7 | stats = {} 8 | for habit in habits: 9 | completions = conn.execute( 10 | "SELECT date FROM completions WHERE habit_id = ? ORDER BY date DESC", 11 | (habit['id'],) 12 | ).fetchall() 13 | dates = [datetime.strptime(row['date'], '%Y-%m-%d') for row in completions] 14 | streak = calculate_streak(dates) 15 | stats[habit['name']] = streak 16 | return stats 17 | 18 | def calculate_streak(dates): 19 | if not dates: 20 | return 0 21 | 22 | streak = 1 23 | for i in range(1, len(dates)): 24 | delta = (dates[i-1] - dates[i]).days 25 | if delta == 1: 26 | streak += 1 27 | else: 28 | break 29 | return streak 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Tuba Khan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /static/styles.css: -------------------------------------------------------------------------------- 1 | /* Dark Theme Styles */ 2 | body { 3 | background-color: #121212; 4 | color: #e0e0e0; 5 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 6 | margin: 2rem; 7 | } 8 | 9 | h1, h2 { 10 | color: #ffffff; 11 | } 12 | 13 | input[type="text"] { 14 | padding: 8px; 15 | border: none; 16 | border-radius: 4px; 17 | background-color: #1e1e1e; 18 | color: #ffffff; 19 | margin-right: 10px; 20 | } 21 | 22 | button, input[type="submit"] { 23 | background-color: #bb86fc; 24 | color: #121212; 25 | border: none; 26 | padding: 8px 12px; 27 | border-radius: 4px; 28 | cursor: pointer; 29 | transition: background-color 0.2s; 30 | } 31 | 32 | button:hover, input[type="submit"]:hover { 33 | background-color: #985eff; 34 | } 35 | 36 | a { 37 | color: #bb86fc; 38 | text-decoration: none; 39 | font-weight: bold; 40 | } 41 | 42 | a:hover { 43 | text-decoration: underline; 44 | } 45 | 46 | .streak { 47 | color: #03dac6; 48 | font-weight: bold; 49 | } 50 | 51 | .habit-entry { 52 | margin-bottom: 12px; 53 | padding: 10px; 54 | background-color: #1e1e1e; 55 | border-radius: 8px; 56 | box-shadow: 0 0 10px rgba(187, 134, 252, 0.1); 57 | } 58 | --------------------------------------------------------------------------------