Phase I: Todo In-Memory Python Console App.
Building a Command-Line Todo App with Python, Claude Code & Spec-Kit Plus | Step-by-Step Guide
Learn how to create a complete command-line todo application using Python 3.13+, Claude Code AI assistance, and Spec-Kit Plus specification-driven development. Perfect for beginners
Basic Level Functionality
Objective: Build a command-line todo application that stores tasks in memory using Claude
Code and Spec-Kit Plus.
Requirements
- Implement all 5 Basic Level features (Add, Delete, Update, View, Mark Complete)
- Use spec-driven development with Claude Code and Spec-Kit Plus
- Follow clean code principles and proper Python project structure
Technology Stack - UV
- Python 3.13+
- Claude Code
- GitHub Spec-Kit
Windows Users: WSL 2 Setup
Windows users must use WSL 2 (Windows Subsystem for Linux) for development:
# Install WSL 2
wsl --install
# Set WSL 2 as default
wsl --set-default-version 2
# Install Ubuntu
wsl --install -d Ubuntu-22.04PythonExpand
Step 1: Navigate to Desktop
cd %USERPROFILE%\DesktopPythonStep 2: Create Main Project Folder
mkdir MyTodoAppPythonStep 3: Navigate into Project Folder
cd MyTodoAppPythonStep 4: Create All Required Folders in One Command
mkdir src src\models src\managers src\ui specs_historyPythonStep 5: Verify Your Structure
dirPythonStep 6: Create the Task Model File
Create src\models\task.py with this content:
from datetime import datetime
from typing import Optional
class Task:
def __init__(self, task_id: int, title: str, description: str = ""):
self.id = task_id
self.title = title
self.description = description
self.completed = False
self.created_at = datetime.now()
self.updated_at = datetime.now()
def mark_complete(self) -> None:
self.completed = True
self.updated_at = datetime.now()
def mark_incomplete(self) -> None:
self.completed = False
self.updated_at = datetime.now()
def update(self, title: Optional[str] = None, description: Optional[str] = None) -> None:
if title is not None:
self.title = title
if description is not None:
self.description = description
self.updated_at = datetime.now()
def __str__(self) -> str:
status = "✓" if self.completed else "○"
return f"[{status}] {self.id}: {self.title}"PythonStep 7: Create the Todo Manager File
Create src\managers\todo_manager.py with this content:
from typing import List, Optional
from models.task import Task
class TodoManager:
def __init__(self):
self.tasks: List[Task] = []
self.next_id = 1
def add_task(self, title: str, description: str = "") -> Task:
task = Task(self.next_id, title, description)
self.tasks.append(task)
self.next_id += 1
return task
def get_task(self, task_id: int) -> Optional[Task]:
for task in self.tasks:
if task.id == task_id:
return task
return None
def update_task(self, task_id: int, title: Optional[str] = None,
description: Optional[str] = None) -> bool:
task = self.get_task(task_id)
if task:
task.update(title, description)
return True
return False
def delete_task(self, task_id: int) -> bool:
task = self.get_task(task_id)
if task:
self.tasks.remove(task)
return True
return False
def mark_complete(self, task_id: int) -> bool:
task = self.get_task(task_id)
if task:
task.mark_complete()
return True
return False
def mark_incomplete(self, task_id: int) -> bool:
task = self.get_task(task_id)
if task:
task.mark_incomplete()
return True
return False
def list_tasks(self) -> List[Task]:
return self.tasks[:]
def get_completed_tasks(self) -> List[Task]:
return [task for task in self.tasks if task.completed]
def get_pending_tasks(self) -> List[Task]:
return [task for task in self.tasks if not task.completed]PythonStep 8: Create the Console UI File
import sys
from typing import Optional
from managers.todo_manager import TodoManager
from models.task import Task
class ConsoleUI:
def __init__(self, todo_manager: TodoManager):
self.todo_manager = todo_manager
def display_menu(self) -> None:
print("\n===== MY TODO APP =====")
print("1. Add New Task")
print("2. Show All Tasks")
print("3. Edit Task")
print("4. Delete Task")
print("5. Mark Task Done")
print("6. Mark Task Not Done")
print("7. Exit Program")
print("========================")
def get_user_choice(self) -> str:
return input("Enter your choice (1-7): ").strip()
def get_task_details(self) -> tuple[str, str]:
title = input("What do you need to do? : ").strip()
description = input("Any extra details? (optional): ").strip()
return title, description
def get_task_id(self) -> Optional[int]:
try:
return int(input("Enter task number: ").strip())
except ValueError:
print("That's not a valid number!")
return None
def add_task(self) -> None:
print("\n--- Adding New Task ---")
title, description = self.get_task_details()
if not title:
print("You must enter what you need to do!")
return
task = self.todo_manager.add_task(title, description)
print(f"Added! Task #{task.id}: {task.title}")
def list_tasks(self) -> None:
print("\n--- Your Tasks ---")
tasks = self.todo_manager.list_tasks()
if not tasks:
print("No tasks yet! Add one to get started.")
return
pending_tasks = self.todo_manager.get_pending_tasks()
if pending_tasks:
print("\n📋 Things to do:")
for task in pending_tasks:
print(f" {task}")
completed_tasks = self.todo_manager.get_completed_tasks()
if completed_tasks:
print("\n✅ Completed tasks:")
for task in completed_tasks:
print(f" {task}")
def update_task(self) -> None:
print("\n--- Editing Task ---")
task_id = self.get_task_id()
if task_id is None:
return
task = self.todo_manager.get_task(task_id)
if not task:
print(f"No task found with number {task_id}")
return
print(f"Current task: {task.title}")
print(f"Current details: {task.description}")
new_title = input("New task name (press Enter to keep current): ").strip()
new_description = input("New details (press Enter to keep current): ").strip()
if not new_title:
new_title = None
if not new_description:
new_description = None
if self.todo_manager.update_task(task_id, new_title, new_description):
print("Task updated successfully!")
else:
print("Could not update task.")
def delete_task(self) -> None:
print("\n--- Deleting Task ---")
task_id = self.get_task_id()
if task_id is None:
return
task = self.todo_manager.get_task(task_id)
if not task:
print(f"No task found with number {task_id}")
return
confirm = input(f"Really delete '{task.title}'? (y/N): ").strip().lower()
if confirm == 'y' or confirm == 'yes':
if self.todo_manager.delete_task(task_id):
print("Task deleted!")
else:
print("Could not delete task.")
else:
print("Deletion cancelled.")
def mark_complete(self) -> None:
print("\n--- Marking Task Done ---")
task_id = self.get_task_id()
if task_id is None:
return
if self.todo_manager.mark_complete(task_id):
print("Task marked as done! ✅")
else:
print(f"No task found with number {task_id}")
def mark_incomplete(self) -> None:
print("\n--- Marking Task Not Done ---")
task_id = self.get_task_id()
if task_id is None:
return
if self.todo_manager.mark_incomplete(task_id):
print("Task marked as not done! 📋")
else:
print(f"No task found with number {task_id}")
def run(self) -> None:
print("Welcome to My Todo App!")
print("Let's get organized! 🚀")
while True:
self.display_menu()
choice = self.get_user_choice()
if choice == '1':
self.add_task()
elif choice == '2':
self.list_tasks()
elif choice == '3':
self.update_task()
elif choice == '4':
self.delete_task()
elif choice == '5':
self.mark_complete()
elif choice == '6':
self.mark_incomplete()
elif choice == '7':
print("Thanks for using My Todo App! See you next time! 👋")
sys.exit(0)
else:
print("Please pick a number between 1 and 7!")PythonStep 9: Create the Main Application File
Create src\main.py with this content:
from managers.todo_manager import TodoManager
from ui.console_ui import ConsoleUI
def main():
todo_manager = TodoManager()
console_ui = ConsoleUI(todo_manager)
console_ui.run()
if __name__ == "__main__":
main()PythonStep 10:Run Your Application
python src\main.pyPython