Build a Python CLI Tool with Claude Code

Use Claude Code to build a Python task manager CLI from scratch.

Introduction

30 Second Summary

Every developer has side projects they never finish because the setup and boilerplate take too long. What if you could describe what you want in plain English and have an AI coding partner build it for you?

In this project, you will use Claude Code to build a personal task manager CLI tool in Python. You'll go from an empty folder to a working application without writing a single line of code yourself.

What You'll Build

A Python command-line task manager that lets you add, list, complete, and delete tasks, with data persisted to a local JSON file.

By the end of this project, you'll have:

  • A configured Claude Code environment with a custom CLAUDE.md file that guides its behavior.
  • A working Python CLI application built entirely through natural language prompts.
  • A Git repository with clean, descriptive commits showing the full development history.
  • Secret Mission: Create a custom Claude Code skill that generates a daily summary of your tasks and suggests priorities.

Are there any prerequisites?

You need basic familiarity with using a terminal (navigating directories, running commands). No prior Python experience is required.

Claude Code requires a paid subscription. You need a Claude Pro ($20/month), Max ($100/month or $200/month), Team, or Enterprise plan. The free Claude.ai plan does not include Claude Code access.

Install Claude Code and Set Up Your Project

Before building your task manager, you need your AI coding partner ready to go. Claude Code is a CLI tool that reads your codebase, edits files, runs commands, and handles Git operations from your terminal.

In this step, you'll install Claude Code, confirm your other tools are available, and create a project directory with a configuration file that tells Claude Code exactly how to work on your project.

In this step, get ready to:
  • Install and authenticate Claude Code on your machine.
  • Confirm Git and Python 3 are available.
  • Create your project directory with Git and a CLAUDE.md configuration file.
Install and authenticate Claude Code

Claude Code runs entirely in your terminal. You install it with a single command, verify it works, then sign in through your browser.

Paid subscription required

Claude Code requires a Claude Pro ($20/month), Max ($100/month or $200/month), Team, or Enterprise subscription. The free Claude.ai plan does not include Claude Code access.

  • Press Cmd+Space to open Spotlight Search.
  • Type Terminal and press Enter to open it.
  • Install Claude Code by running this command:
curl -fsSL https://claude.ai/install.sh | bash

What does this command do?

This downloads the Claude Code installer script from Anthropic's servers and runs it immediately. The -fsSL flags tell curl to follow redirects, fail silently on errors, and show no progress bar.

The installer places the claude binary on your system and auto-updates in the background to keep you on the latest version.

  • Verify the installation succeeded by running:
claude --version

✔️ I see a version number

Claude Code is installed and ready. Move on to the authentication step below.

ⓧ Command not found

  • Close your terminal window completely.
  • Reopen Terminal using Spotlight Search (Cmd+Space, type Terminal).
  • Run claude --version again.

If it still fails, run the install command above a second time and watch for any error messages in the output.

  • Start Claude Code to authenticate by running:
claude

What happens next?

Claude Code opens your default browser for authentication. Sign in with the Anthropic account that holds your Pro, Max, Team, or Enterprise subscription.

Once you approve access in the browser, return to your terminal. Claude Code confirms the connection and opens an interactive session.

  • After authentication succeeds, exit the Claude Code session by typing exit and pressing Enter (or pressing Ctrl+D).

Browser didn't open?

Some terminal configurations block the automatic browser launch. If nothing happened, look in your terminal for a URL that starts with https:// and copy-paste it into your browser manually.

help me authenticate Claude Code

Confirm Git and Python 3 are available

Your task manager project needs Git for version control and Python 3 to run the application code. Both are commonly pre-installed on macOS, but let's confirm.

  • Check that Git is installed by running:
git --version

✔️ I see a version number

Git is ready. Move on to the Python check below.

ⓧ Command not found

  • Install Apple's command-line developer tools (which include Git) by running:
xcode-select --install

A system dialog appears asking you to confirm the installation. Click Install and wait for it to complete. This may take a few minutes.

  • Once the installation finishes, run git --version again to confirm.
  • Check that Python 3 is installed by running:
python3 --version

✔️ I see a version number

Python 3 is ready. Move on to the next substep.

ⓧ Command not found

Install Python 3 using Homebrew by running:

brew install python3

If you don't have Homebrew installed, download Python 3 directly from python.org instead. Run the installer and follow the prompts.

  • After installation, run python3 --version again to confirm.
Create your project directory and configure CLAUDE.md

Now you'll create a clean project folder, initialize a Git repository inside it, and add a CLAUDE.md file. This file tells Claude Code your project's coding standards every time it starts a session.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder, move into it, and initialize a Git repository by running these commands:
mkdir task-manager
cd task-manager
git init

What does git init do?

This creates a hidden .git folder inside task-manager that tracks all your file changes. Later, Claude Code uses this Git repository to commit your work with descriptive messages.

You should see a message like Initialized empty Git repository confirming the setup.

  • Create and open the CLAUDE.md file by running:
nano CLAUDE.md

What is nano?

Nano is a simple text editor built into macOS Terminal. It opens directly in your terminal window so you can type or paste content into files without leaving the command line.

  • Copy the following content and paste it into nano (use Cmd+V to paste):
# Task Manager Project

## Project Standards
- Python 3 with type hints on all functions
- Docstrings on all public functions (Google style)
- No external dependencies (stdlib only)
- Data stored in JSON format (tasks.json)
- CLI built with argparse
- Use ANSI escape codes for colored terminal output (no external color libraries)

## Commands
- Run the app: `python3 task_manager.py <command> [args]`
- Run tests: `python3 -m pytest tests/ -v` (if tests exist)

## Style
- Keep functions short and focused
- Use meaningful variable names
- Handle errors gracefully with user-friendly messages

What does CLAUDE.md do?

Claude Code reads this file at the start of every conversation. It acts as persistent instructions so Claude Code follows your project's conventions automatically.

Each rule here prevents common mistakes. For example, "No external dependencies" stops Claude Code from importing third-party packages you'd need to install separately.

  • Save the file by pressing Ctrl+O, then press Enter to confirm the filename.
  • Exit nano by pressing Ctrl+X.
  • Confirm the file was created by running:
ls

You should see CLAUDE.md listed in the output.

Don't see CLAUDE.md?

Make sure you pressed Ctrl+O (the letter O, not zero) to save before exiting nano. If the file is missing, run nano CLAUDE.md again and repeat the paste-and-save steps.

Also check that your terminal is inside the task-manager folder by running pwd. You should see a path ending in /task-manager.

help me create CLAUDE.md

✔️ Awesome, I've got everything!

Great. Double check you've saved your file and that ls shows CLAUDE.md in your task-manager directory.

ⓧ I'd like to double check the full code

Here's the complete CLAUDE.md file. Compare it with yours by running cat CLAUDE.md in your terminal.

# Task Manager Project

## Project Standards
- Python 3 with type hints on all functions
- Docstrings on all public functions (Google style)
- No external dependencies (stdlib only)
- Data stored in JSON format (tasks.json)
- CLI built with argparse
- Use ANSI escape codes for colored terminal output (no external color libraries)

## Commands
- Run the app: `python3 task_manager.py <command> [args]`
- Run tests: `python3 -m pytest tests/ -v` (if tests exist)

## Style
- Keep functions short and focused
- Use meaningful variable names
- Handle errors gracefully with user-friendly messages

Your environment is set up and Claude Code knows your project standards. Next up, you'll use plan mode to design the task manager architecture before writing any code.

Explore and Plan with Claude Code

Your project directory is ready with a CLAUDE.md file that tells Claude Code exactly how you want your code written.

Before writing a single line of code, you'll use Claude Code's plan mode to think through the design of your task manager. This "explore first, then plan, then code" workflow prevents Claude from jumping straight into coding and solving the wrong problem.

In this step, get ready to:
  • Start Claude Code in plan mode and explore your project.
  • Generate a detailed implementation plan for the task manager CLI.
  • Refine the plan with specific feedback before writing any code.
Start Claude Code in plan mode

Plan mode is a special permission mode in Claude Code. It lets Claude read your files and answer questions without making any changes to your project.

  • Launch Claude Code in plan mode by running this command:
claude --permission-mode plan

What does this command do?

The --permission-mode plan flag starts Claude Code in a read-only mode. Claude can explore your files and propose a plan, but it cannot edit files or run commands until you switch out of plan mode.

This is different from running claude on its own, which starts in the default mode where Claude can request permission to make changes.

Claude Code starts up and automatically reads your CLAUDE.md file. You should see it acknowledge your project standards.

Claude Code not starting?

Make sure your terminal's current directory is your project folder (the one containing CLAUDE.md). Claude Code reads the CLAUDE.md from whatever directory you launch it in.

If you see an authentication error, run /login inside the Claude Code session to re-authenticate through your browser.

Help me troubleshoot why Claude Code is not starting in plan mode.

  • Ask Claude to explore your project and create an implementation plan by typing this prompt into the Claude Code session:
Look at what we have in this project so far. Then create a detailed implementation plan for a task manager CLI that supports these commands: add, list, complete, and delete. Don't write any code yet - just give me the plan.

Why ask Claude not to write code?

Even in plan mode, being explicit about "don't write code yet" reinforces your intent. You want Claude to focus on the architecture and design decisions first.

This gives you a chance to review the approach, catch potential issues, and shape the direction before any implementation begins.

Claude responds with a structured plan. It typically includes a file structure, function breakdown, data model, and the flow for each command.

  • Read through the plan Claude generates. Look for the overall structure, how it plans to store data, and what functions it proposes.

What is plan mode?

Anthropic's recommended workflow for Claude Code has four phases: explore, plan, code, and verify. Plan mode handles the first two phases. Claude reads files and answers questions without making changes.

Separating planning from execution means you catch design issues early, before they turn into code you have to rewrite.

Refine the plan with specific feedback

Claude's first plan is a starting point. Your CLAUDE.md already specifies argparse and JSON storage, but Claude may have proposed alternatives. Time to steer the plan toward exactly what you want.

  • Type this follow-up prompt to refine the plan:
Refine the plan with these specifics:
- Use Python's argparse module for the CLI interface with subcommands (add, list, complete, delete)
- Store tasks in a JSON file called tasks.json
- Each task should have: id, title, priority (high/medium/low), completed status, and created_at timestamp
- Include colored terminal output using ANSI escape codes

Update the plan with these requirements. Still no code.

Why refine the plan?

Giving Claude specific requirements at the planning stage means the implementation will match your vision on the first try. The more detail you provide now, the less back-and-forth you'll need later when Claude actually writes the code.

Think of it like giving an architect specific room dimensions before they start drawing blueprints.

Claude updates its plan to incorporate your feedback. The revised plan should now clearly show argparse with subcommands, a tasks.json storage file, and the data model with all the fields you specified.

  • Review the updated plan. Confirm it includes argparse subcommands for each of the four operations (add, list, complete, delete).
  • Confirm the data model includes an id, title, priority, completed, and created_at field.
  • Confirm that the plan uses ANSI escape codes for color output, with no external dependencies.
  • Once you're satisfied with the plan, press Shift+Tab to cycle out of plan mode. This switches Claude Code to a mode where it can make edits in the next step.

Switch modes mid-session

Pressing Shift+Tab cycles through permission modes (default, acceptEdits, plan) without restarting Claude Code. You'll use this to leave plan mode when you're ready to let Claude write code.

Plan doesn't match your expectations?

If Claude's plan still uses something other than argparse or proposes external packages, remind it to check the CLAUDE.md file. You can say: "Check CLAUDE.md again. We're using argparse and no external dependencies."

Help me get Claude Code to follow my project standards in the plan.

You now have a solid plan approved and ready to go. Next up, you'll switch out of plan mode and let Claude Code bring the plan to life by writing the actual task manager code.

Build the Task Manager

You have a solid plan. Now it's time to let Claude Code turn that plan into working code.

In this step, you'll give Claude Code a focused implementation prompt, test every command the task manager supports, and make your first Git commit. Claude Code handles the file creation, debugging, and version control for you.

In this step, get ready to:
  • Give Claude Code a focused prompt to build the task manager.
  • Test the add, list, complete, and delete commands.
  • Commit the working implementation with Claude Code's Git integration.
Prompt Claude Code to implement the task manager

Focused prompts give Claude Code a clear scope of work. Instead of asking it to "build everything," you'll break the request into a logical order that matches your plan.

  • In your Claude Code session, type the following prompt and press Enter:
Implement the task manager CLI based on our plan. Start with the data model and storage functions, then add the CLI commands.

What happens next?

Claude Code reads your CLAUDE.md to understand your project standards, then creates task_manager.py step by step. You'll see it writing functions, adding type hints and docstrings (because your CLAUDE.md says to), and choosing argparse for the CLI interface.

Claude Code may also run the script to verify it works. If it encounters any errors, it fixes them automatically before moving on.

  • Watch as Claude Code creates the file and implements each function. This takes about 30-60 seconds.
  • When Claude Code finishes and returns to the prompt, confirm that it created task_manager.py by looking at its output. You should see messages about creating the file and implementing the functions.

Why focused prompts work better

Telling Claude Code to "start with the data model and storage functions, then add the CLI commands" gives it a build order. It implements the foundation first (loading and saving tasks) before adding the commands that depend on that foundation.

This mirrors how a developer thinks about code structure. The more clearly you communicate your intent, the better Claude Code's output.

Claude Code didn't create the file?

If Claude Code asks for clarification instead of writing code, it may still be in plan mode. Type exit to leave plan mode, then re-enter your prompt.

If Claude Code asks for permission to create files or run commands, type y and press Enter to approve.

Help me get Claude Code to write my task manager code

Test the task manager commands

Before committing anything, you need to verify the tool actually works. You'll ask Claude Code to run each command so you can see the output.

  • Ask Claude Code to test the tool by typing this prompt:
Test the task manager: add a task called "Buy groceries", add a high-priority task called "Fix login bug", list all tasks, mark task 1 as complete, then list again to confirm. Finally delete task 1.
  • Watch as Claude Code runs each command. You should see colored output in your terminal as it adds, lists, completes, and deletes tasks.

What should I see?

Claude Code runs each command using python3 task_manager.py and shows you the output. You'll see green text for added tasks, a formatted table when listing, a green checkmark when completing, and yellow text when deleting.

If any command fails, tell Claude Code what went wrong. For example, type "the list command shows an error" and Claude Code will read the traceback and fix the issue.

Seeing errors in the test output?

If Claude Code's test run shows a Python traceback, that's fine. Claude Code reads the error and fixes it automatically in most cases. Wait for it to retry.

If it doesn't fix the issue on its own, describe the problem in plain English. For example: "The add command crashes with a KeyError" or "The list command doesn't show any tasks after adding them."

Help me fix errors in my task manager

Commit the working implementation

Your task manager works. Now you'll use Claude Code's Git integration to save this progress with a descriptive commit message.

  • Ask Claude Code to commit by typing this prompt:
Commit the working task manager implementation with a descriptive commit message.
  • Claude Code will ask for permission to run git add and git commit. Type y and press Enter to approve each one.
  • Confirm the commit succeeded by checking Claude Code's output. You should see a message showing the commit hash and the commit message Claude Code wrote.

What does Claude Code do when it commits?

Claude Code analyzes your changes, stages the relevant files with git add, then writes a descriptive commit message based on what was implemented. It follows Conventional Commits style, producing messages like "feat: implement task manager CLI with add, list, complete, and delete commands."

This saves you from writing commit messages manually. Claude Code understands the context of your changes because it made them.

Commit failed or was rejected?

If you see a message about "nothing to commit," Claude Code may have already committed during the implementation step. Type git log --oneline -3 in your Claude Code session to check your recent commits.

If you accidentally denied the permission prompt, just ask Claude Code again: "please commit the task manager changes."

Help me commit my task manager code

✔️ Awesome, I've got everything!

Great. Double check that task_manager.py exists in your project folder and that you have at least one Git commit.

ⓧ I'd like to double check the full code

Here's what your task_manager.py should look like. Claude Code may have generated slightly different variable names or formatting, but the structure and functionality should match:

#!/usr/bin/env python3
"""A simple CLI task manager with JSON persistence."""

import argparse
import json
import os
import sys
from datetime import datetime
from typing import Optional

TASKS_FILE = "tasks.json"

# ANSI color codes
GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
CYAN = "\033[96m"
BOLD = "\033[1m"
RESET = "\033[0m"

PRIORITY_COLORS = {
    "high": RED,
    "medium": YELLOW,
    "low": GREEN,
}


def load_tasks() -> list[dict]:
    """Load tasks from the JSON file."""
    if not os.path.exists(TASKS_FILE):
        return []
    with open(TASKS_FILE, "r") as f:
        return json.load(f)


def save_tasks(tasks: list[dict]) -> None:
    """Save tasks to the JSON file."""
    with open(TASKS_FILE, "w") as f:
        json.dump(tasks, f, indent=2)


def get_next_id(tasks: list[dict]) -> int:
    """Get the next available task ID."""
    if not tasks:
        return 1
    return max(task["id"] for task in tasks) + 1


def add_task(title: str, priority: str = "medium") -> None:
    """Add a new task with the given title and priority."""
    tasks = load_tasks()
    task = {
        "id": get_next_id(tasks),
        "title": title,
        "priority": priority,
        "completed": False,
        "created_at": datetime.now().isoformat(),
    }
    tasks.append(task)
    save_tasks(tasks)
    print(f"{GREEN}Added task #{task['id']}: {title} [{priority}]{RESET}")


def list_tasks(show_all: bool = False) -> None:
    """List all tasks, optionally including completed ones."""
    tasks = load_tasks()
    if not tasks:
        print(f"{CYAN}No tasks yet. Add one with: python3 task_manager.py add \"Your task\"{RESET}")
        return

    visible_tasks = tasks if show_all else [t for t in tasks if not t["completed"]]
    if not visible_tasks:
        print(f"{GREEN}All tasks completed! 🎉{RESET}")
        return

    print(f"\n{BOLD}{'ID':<5}{'Status':<10}{'Priority':<10}{'Task'}{RESET}")
    print("-" * 50)
    for task in visible_tasks:
        status = f"{GREEN}✓{RESET}" if task["completed"] else "○"
        color = PRIORITY_COLORS.get(task["priority"], "")
        priority_display = f"{color}{task['priority']}{RESET}"
        title = task["title"]
        if task["completed"]:
            title = f"\033[9m{title}\033[0m"
        print(f"{task['id']:<5}{status:<10}{priority_display:<20}{title}")
    print()


def complete_task(task_id: int) -> None:
    """Mark a task as completed."""
    tasks = load_tasks()
    for task in tasks:
        if task["id"] == task_id:
            task["completed"] = True
            save_tasks(tasks)
            print(f"{GREEN}Completed task #{task_id}: {task['title']} ✓{RESET}")
            return
    print(f"{RED}Task #{task_id} not found.{RESET}")


def delete_task(task_id: int) -> None:
    """Delete a task by ID."""
    tasks = load_tasks()
    original_length = len(tasks)
    tasks = [t for t in tasks if t["id"] != task_id]
    if len(tasks) == original_length:
        print(f"{RED}Task #{task_id} not found.{RESET}")
        return
    save_tasks(tasks)
    print(f"{YELLOW}Deleted task #{task_id}.{RESET}")


def main() -> None:
    """Main entry point for the CLI."""
    parser = argparse.ArgumentParser(
        description="A simple CLI task manager.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # Add command
    add_parser = subparsers.add_parser("add", help="Add a new task")
    add_parser.add_argument("title", help="Task title")
    add_parser.add_argument(
        "-p", "--priority",
        choices=["high", "medium", "low"],
        default="medium",
        help="Task priority (default: medium)",
    )

    # List command
    list_parser = subparsers.add_parser("list", help="List tasks")
    list_parser.add_argument(
        "-a", "--all",
        action="store_true",
        help="Show all tasks including completed",
    )

    # Complete command
    complete_parser = subparsers.add_parser("complete", help="Mark a task as complete")
    complete_parser.add_argument("id", type=int, help="Task ID to complete")

    # Delete command
    delete_parser = subparsers.add_parser("delete", help="Delete a task")
    delete_parser.add_argument("id", type=int, help="Task ID to delete")

    args = parser.parse_args()

    if args.command is None:
        parser.print_help()
        sys.exit(1)

    if args.command == "add":
        add_task(args.title, args.priority)
    elif args.command == "list":
        list_tasks(args.all)
    elif args.command == "complete":
        complete_task(args.id)
    elif args.command == "delete":
        delete_task(args.id)


if __name__ == "__main__":
    main()

Your task manager is built, tested, and committed. Next up, you'll iterate on it by adding color-coded priorities and a README, showing how Claude Code handles incremental improvements to existing code.

Iterate and Improve

Your task manager works. You can add, list, complete, and delete tasks. Now it's time to practice the most powerful part of working with Claude Code: iterating on existing code.

In this step, you'll ask Claude Code to enhance your tool with better visual output and proper documentation. You'll also make another Git commit, building a clean development history.

In this step, get ready to:
  • Add color output and priority levels to your task list.
  • Generate a README.md with usage documentation.
  • Commit all improvements with a descriptive message.
Ask Claude Code to add color and priority features

Your CLAUDE.md already tells Claude to use ANSI escape codes for color and your plan included priorities. Now you'll explicitly ask Claude Code to make sure these features are polished and working correctly.

  • In your Claude Code session, type the following prompt:
Add color output to the task list using ANSI escape codes (no external dependencies) and make sure tasks have a priority field with high, medium, and low options. High priority should show in red, medium in yellow, and low in green. Test it by adding a task with each priority level.

What does this prompt do?

This prompt asks Claude Code to verify and enhance the color and priority features in your task manager. Claude reads your existing code, checks whether these features are already present, and either adds them or confirms they work correctly.

The key here is the last sentence: asking Claude to test the features. Claude Code will actually run your script and show you the output so you can verify everything looks right.

Claude Code reads your code, implements or verifies the features, and runs test commands. You should see colored output in your terminal with tasks displayed at different priority levels.

  • Watch Claude Code run commands like python3 task_manager.py add "Test task" -p high and python3 task_manager.py list to verify the output.
  • Confirm that high-priority tasks appear in red, medium in yellow, and low in green.

Let Claude verify its own work

One of the most effective patterns with Claude Code is asking it to test after making changes. Claude can run your code, spot issues, and fix them in the same session. You don't need to switch back and forth between your editor and terminal.

Colors not showing up?

Some terminals don't support ANSI escape codes by default. The built-in macOS Terminal app supports them out of the box. If you're using a different terminal and see raw escape characters instead of colors, try switching to Terminal.app.

Help me debug why ANSI colors aren't rendering in my terminal output

Generate a README and commit your changes

Good projects have good documentation. Instead of writing a README yourself, you'll ask Claude Code to generate one based on your actual code. Then you'll have it commit everything in one clean Git commit.

  • Ask Claude Code to write documentation by typing this prompt:
Write a brief README.md documenting how to use the task manager. Include examples for each command (add, list, complete, delete), explain the priority levels, and mention where data is stored.

Why ask Claude to write the README?

Claude Code reads your actual source code before writing the README. This means the documentation stays accurate to your implementation. It references the real command syntax, real flags, and real behavior rather than generic boilerplate.

Claude Code creates a README.md file with usage examples for all four commands, priority level descriptions, and data storage details.

  • Verify that Claude created the README.md file by checking its output. You should see a markdown file with sections for adding tasks, listing tasks, completing tasks, and deleting tasks.
  • Now ask Claude Code to commit all your changes by typing this prompt:
Commit all the changes with a descriptive commit message covering the color output, priority features, and README addition.

What happens when Claude commits?

Claude Code stages all modified and new files with git add, writes a descriptive commit message summarizing what changed, and runs git commit. You'll see the exact message it chose in the output.

  • Confirm that Claude Code made the commit by checking its output. You should see a commit hash and a message describing the color, priority, and README changes.

Claude asking for permission to run git?

Claude Code may ask for permission before running git commit. Allow it. Claude is cautious about commands that modify your repository state and will confirm before proceeding.

Help me understand Claude Code's permission system for git commands

✔️ Awesome, I've got everything!

Great. You now have a fully featured task manager with colored priority output, complete documentation, and two clean Git commits in your history.

ⓧ I'd like to double check the full code

Here is what your README.md should look like. Your exact wording may differ slightly since Claude Code generates it, but the structure and commands should match:

# Task Manager CLI

A simple command-line task manager built with Python.

## Usage

### Add a task
```bash
python3 task_manager.py add "Buy groceries"
python3 task_manager.py add "Fix bug in auth module" -p high
python3 task_manager.py add "Clean desk" -p low
```

### List tasks
```bash
python3 task_manager.py list        # Show incomplete tasks
python3 task_manager.py list --all  # Show all tasks
```

### Complete a task
```bash
python3 task_manager.py complete 1
```

### Delete a task
```bash
python3 task_manager.py delete 1
```

## Priority Levels
- **high** (red) - Urgent tasks
- **medium** (yellow) - Normal tasks (default)
- **low** (green) - Can wait

## Data Storage
Tasks are stored in `tasks.json` in the project directory.

And here is what your task_manager.py should look like:

#!/usr/bin/env python3
"""A simple CLI task manager with JSON persistence."""

import argparse
import json
import os
import sys
from datetime import datetime
from typing import Optional

TASKS_FILE = "tasks.json"

# ANSI color codes
GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
CYAN = "\033[96m"
BOLD = "\033[1m"
RESET = "\033[0m"

PRIORITY_COLORS = {
    "high": RED,
    "medium": YELLOW,
    "low": GREEN,
}


def load_tasks() -> list[dict]:
    """Load tasks from the JSON file."""
    if not os.path.exists(TASKS_FILE):
        return []
    with open(TASKS_FILE, "r") as f:
        return json.load(f)


def save_tasks(tasks: list[dict]) -> None:
    """Save tasks to the JSON file."""
    with open(TASKS_FILE, "w") as f:
        json.dump(tasks, f, indent=2)


def get_next_id(tasks: list[dict]) -> int:
    """Get the next available task ID."""
    if not tasks:
        return 1
    return max(task["id"] for task in tasks) + 1


def add_task(title: str, priority: str = "medium") -> None:
    """Add a new task with the given title and priority."""
    tasks = load_tasks()
    task = {
        "id": get_next_id(tasks),
        "title": title,
        "priority": priority,
        "completed": False,
        "created_at": datetime.now().isoformat(),
    }
    tasks.append(task)
    save_tasks(tasks)
    print(f"{GREEN}Added task #{task['id']}: {title} [{priority}]{RESET}")


def list_tasks(show_all: bool = False) -> None:
    """List all tasks, optionally including completed ones."""
    tasks = load_tasks()
    if not tasks:
        print(f"{CYAN}No tasks yet. Add one with: python3 task_manager.py add \"Your task\"{RESET}")
        return

    visible_tasks = tasks if show_all else [t for t in tasks if not t["completed"]]
    if not visible_tasks:
        print(f"{GREEN}All tasks completed! \U0001f389{RESET}")
        return

    print(f"\n{BOLD}{'ID':<5}{'Status':<10}{'Priority':<10}{'Task'}{RESET}")
    print("-" * 50)
    for task in visible_tasks:
        status = f"{GREEN}\u2713{RESET}" if task["completed"] else "\u25cb"
        color = PRIORITY_COLORS.get(task["priority"], "")
        priority_display = f"{color}{task['priority']}{RESET}"
        title = task["title"]
        if task["completed"]:
            title = f"\033[9m{title}\033[0m"
        print(f"{task['id']:<5}{status:<10}{priority_display:<20}{title}")
    print()


def complete_task(task_id: int) -> None:
    """Mark a task as completed."""
    tasks = load_tasks()
    for task in tasks:
        if task["id"] == task_id:
            task["completed"] = True
            save_tasks(tasks)
            print(f"{GREEN}Completed task #{task_id}: {task['title']} \u2713{RESET}")
            return
    print(f"{RED}Task #{task_id} not found.{RESET}")


def delete_task(task_id: int) -> None:
    """Delete a task by ID."""
    tasks = load_tasks()
    original_length = len(tasks)
    tasks = [t for t in tasks if t["id"] != task_id]
    if len(tasks) == original_length:
        print(f"{RED}Task #{task_id} not found.{RESET}")
        return
    save_tasks(tasks)
    print(f"{YELLOW}Deleted task #{task_id}.{RESET}")


def main() -> None:
    """Main entry point for the CLI."""
    parser = argparse.ArgumentParser(
        description="A simple CLI task manager.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # Add command
    add_parser = subparsers.add_parser("add", help="Add a new task")
    add_parser.add_argument("title", help="Task title")
    add_parser.add_argument(
        "-p", "--priority",
        choices=["high", "medium", "low"],
        default="medium",
        help="Task priority (default: medium)",
    )

    # List command
    list_parser = subparsers.add_parser("list", help="List tasks")
    list_parser.add_argument(
        "-a", "--all",
        action="store_true",
        help="Show all tasks including completed",
    )

    # Complete command
    complete_parser = subparsers.add_parser("complete", help="Mark a task as complete")
    complete_parser.add_argument("id", type=int, help="Task ID to complete")

    # Delete command
    delete_parser = subparsers.add_parser("delete", help="Delete a task")
    delete_parser.add_argument("id", type=int, help="Task ID to delete")

    args = parser.parse_args()

    if args.command is None:
        parser.print_help()
        sys.exit(1)

    if args.command == "add":
        add_task(args.title, args.priority)
    elif args.command == "list":
        list_tasks(args.all)
    elif args.command == "complete":
        complete_task(args.id)
    elif args.command == "delete":
        delete_task(args.id)


if __name__ == "__main__":
    main()

Your development history now tells a clear story. Next up, you'll extend Claude Code itself by creating a custom skill.

Create a Custom Skill

Your task manager is built, tested, documented, and committed. Now it's time to extend Claude Code itself.

Skills are reusable instructions that teach Claude Code new workflows. Once you create a skill, you can invoke it in any project with a single command. In this step, you'll build a skill that gives you a quick project status overview whenever you need one.

In this step, get ready to:
  • Create a custom skill directory and write a SKILL.md file with frontmatter and dynamic context injection.
  • Test the skill by invoking it in your Claude Code session.
Create the project summary skill

Every skill needs a SKILL.md file with two parts: YAML frontmatter between --- markers that tells Claude when to use the skill, and markdown content with the instructions Claude follows when the skill runs.

Personal skills live at ~/.claude/skills/<skill-name>/SKILL.md and are available across all your projects.

  • Create the skill directory by running this command in your terminal:
mkdir -p ~/.claude/skills/project-summary

What does this command do?

The -p flag creates all parent directories in the path if they don't exist yet. This creates both ~/.claude/skills/ and the project-summary/ folder inside it in one go.

  • Confirm the directory exists by running:
ls ~/.claude/skills/project-summary

You should see an empty directory with no output (no errors means it exists).

  • Now create the SKILL.md file by running this command to open it in your default terminal editor:
nano ~/.claude/skills/project-summary/SKILL.md
  • Paste the following content into the file:
---
description: Summarizes the current project status including file structure, recent changes, and code health. Use when the user asks for a project overview or status update.
---

Provide a concise project summary covering:
1. Project purpose and tech stack
2. File structure overview
3. Recent git activity (last 3 commits)
4. Any TODO items or areas needing attention

!`find . -name "*.py" -o -name "*.md" -o -name "*.json" | grep -v __pycache__ | sort`
!`git log --oneline -3 2>/dev/null || echo "No git history yet"`
  • Save the file by pressing Ctrl+O then Enter, and exit with Ctrl+X.

What does this file do?

The frontmatter (between the --- markers) contains a description field that tells Claude when this skill is relevant. If you ask something matching that description, Claude can automatically invoke the skill.

The lines starting with !` are dynamic context injection. They run the command inside the backticks and replace that line with the command's output before Claude sees the skill content. This gives Claude real-time information about your project files and git history.

  • Verify the file was saved correctly by running:
cat ~/.claude/skills/project-summary/SKILL.md

You should see the full SKILL.md contents printed in your terminal, starting with the --- frontmatter markers.

File looks empty or missing?

Make sure you saved the file in nano before exiting. If you accidentally exited without saving, run the nano command again and re-paste the content.

Check that the path is exactly ~/.claude/skills/project-summary/SKILL.md. A typo in the directory name means Claude Code won't find the skill.

Help me troubleshoot my SKILL.md file creation

Test the skill in your Claude Code session

Skills can be invoked directly with a slash command or automatically when you ask something matching the description. Let's test both approaches.

  • Return to your Claude Code session (if you exited, start it again by running claude in your project directory).
  • Invoke the skill directly by typing:
/project-summary

Claude reads your skill instructions, runs the dynamic context commands to gather your file list and git history, then generates a summary of your project. You should see output covering the project's purpose, file structure, and recent commits.

What should I see?

Claude will produce a project summary that mentions your Python task manager, lists files like task_manager.py and CLAUDE.md, and shows your recent git commits. The exact wording varies each time since Claude generates it fresh.

  • Now test the natural invocation by typing a question that matches the skill's description:
Give me a project status overview

Claude should recognize that this matches your skill's description and produce a similar summary. This confirms that the description field in the frontmatter is working correctly.

Skill not being recognized?

If the slash command doesn't work, check that your file is at exactly ~/.claude/skills/project-summary/SKILL.md (case-sensitive). The filename must be SKILL.md in all caps.

If the natural language trigger doesn't invoke the skill, try being more explicit with your phrasing. The description matching isn't always exact. Using the slash command directly is the most reliable approach.

Help me debug why my Claude Code skill isn't working

✔️ Awesome, I've got everything!

Great. Double check you can see a project summary when you invoke /project-summary and you're all set.

ⓧ I'd like to double check the full code

Here is the full content of your SKILL.md file for comparison:

---
description: Summarizes the current project status including file structure, recent changes, and code health. Use when the user asks for a project overview or status update.
---

Provide a concise project summary covering:
1. Project purpose and tech stack
2. File structure overview
3. Recent git activity (last 3 commits)
4. Any TODO items or areas needing attention

!`find . -name "*.py" -o -name "*.md" -o -name "*.json" | grep -v __pycache__ | sort`
!`git log --oneline -3 2>/dev/null || echo "No git history yet"`

You've just extended Claude Code with a custom skill that works across any project on your machine. Next up, you'll take this further in the Secret Mission by building a more advanced skill that reads your task data and suggests daily priorities.

Secret mission

Build a Daily Task Summary Skill

You've built one custom skill, but what about something more practical? Create an advanced daily-tasks skill that reads your tasks.json, groups incomplete tasks by priority, suggests what to focus on today, and delivers a motivational message to kick off your morning.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally with no cloud resources, so there are no ongoing infrastructure costs. Your Claude subscription is managed separately through your Anthropic account.

Resources you used:

  • Project folder on your Desktop (task-manager with task_manager.py, CLAUDE.md, README.md, tasks.json, and the Git repository).
  • Claude Code skills directories (~/.claude/skills/project-summary/ and ~/.claude/skills/daily-tasks/).
  • Claude Code CLI (installed via native installer or Homebrew).

Keep everything running

No action needed. Choose this if you plan to keep building on your task manager or using your custom skills.

  • Your project folder stays on your Desktop ready for future development.
  • Your Claude Code skills remain active and available in any project.
  • Claude Code stays installed and will auto-update in the background.

Pause - I'll come back to this later

Exit your Claude Code session to free up terminal resources. Your files and skills stay in place for when you return.

  • If Claude Code is still running, press Ctrl+D or type exit to end the session.
  • Your project folder, skills, and Git history will all be waiting when you come back.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow these steps to clean up everything created during this project.

  • Exit Claude Code if it is still running by pressing Ctrl+D or typing exit.
  • Delete your project folder by running:
cd ~/Desktop
rm -rf task-manager
  • Delete the custom skills directories by running:
rm -rf ~/.claude/skills/project-summary
rm -rf ~/.claude/skills/daily-tasks
  • To uninstall Claude Code itself, use the command matching your original installation method:

If you installed with the native installer:

rm -f ~/.local/bin/claude
rm -rf ~/.local/share/claude

If you installed with Homebrew:

brew uninstall --cask claude-code
  • Optionally, remove Claude Code configuration files (this deletes all settings, session history, and allowed-tools preferences):
rm -rf ~/.claude
rm ~/.claude.json

Removing configuration deletes your skills

The ~/.claude/ directory contains your skills, settings, and session history. If you already deleted the individual skill folders in the step above, this command removes any remaining Claude Code configuration.

Nice Work!

Nice Work!

You just built a complete application without writing a single line of code yourself. From an empty directory to a fully functional Python CLI tool, every line was generated by Claude Code based on your prompts.

Here's what you accomplished:

  • Configured Claude Code with a custom CLAUDE.md file and used the plan-first workflow to design your application before writing any code.
  • Built a working Python CLI task manager entirely through natural language prompts, with add, list, complete, and delete commands plus color-coded priority levels.
  • Created custom skills with dynamic context injection to extend Claude Code's capabilities and used its Git integration to commit changes with descriptive messages.
  • Secret Mission: Built an advanced daily task summary skill that reads your task data, groups incomplete tasks by priority, and suggests what to focus on today.

Ready to quiz yourself?