Capstone Project: Build a CLI Bookmark Manager
Forget todo apps. You're going to build something you'll actually use: a command-line bookmark manager with fuzzy search, tags, and Markdown export.
Why Not a Todo App?
Every tutorial ends with a todo app. They're boring, you'll never use them, and they don't stretch your skills. Instead, you're building bm -- a CLI bookmark manager that:
- Saves URLs with titles, tags, and notes
- Searches bookmarks with fuzzy matching
- Exports your collection to Markdown
- Stores everything in a local JSON file
- Actually replaces that mess of browser bookmarks you never organize
This project exercises everything from Ch1-4: tool-calling, CLAUDE.md, context management, and Git workflows.
Prerequisites
- [x] Ch1: Claude Code installed, understand tool-calling and permissions
- [x] Ch2: Can write CLAUDE.md and understand the memory system
- [x] Ch3: Know how to monitor context and compact when needed
- [x] Ch4: Can run Git workflows through Claude
Project Spec
Commands
bm add https://example.com --title "Example" --tags web,reference
bm add https://github.com/anthropics/claude-code --tags tools,ai --note "The CLI itself"
bm list
bm list --tag ai
bm search "claude" # fuzzy search across title, URL, notes, tags
bm open 3 # open bookmark #3 in default browser
bm delete 5
bm export # exports all bookmarks as Markdown
bm export --tag ai # exports filtered bookmarks
bm stats # show total count, top tags, recently addedTechnical Requirements
- Language: Python 3.10+
- Storage: JSON file (
~/.bm/bookmarks.json) - CLI parsing: argparse or click
- Fuzzy search: simple substring matching is fine, bonus for fuzzywuzzy/thefuzz
- Testing: pytest
- Code standards: whatever you define in CLAUDE.md
Export Format
# Bookmarks
## ai
- [Claude Code CLI](https://github.com/anthropics/claude-code) - The CLI itself
- [Anthropic Docs](https://docs.anthropic.com) - Official API documentation
## web
- [Example](https://example.com)
## reference
- [MDN Web Docs](https://developer.mozilla.org)
---
*Exported on 2026-04-10 | 42 bookmarks | 8 tags*Step-by-Step Guide
Step 1: Project Setup (Ch1 + Ch2)
mkdir -p ~/claude-demos/capstone-bm && cd ~/claude-demos/capstone-bm
git initCreate your CLAUDE.md:
cat > CLAUDE.md << 'EOF'
# bm -- CLI Bookmark Manager
## Key Commands
- Run: python -m bm <command>
- Test: pytest
- Test single: pytest tests/test_service.py -v
## Tech Stack
- Python 3.10+
- Testing: pytest
- CLI: argparse
- Storage: JSON file at ~/.bm/bookmarks.json
## Code Standards
- Type hints on all functions
- Docstrings in Google style
- File names: snake_case
- Constants: UPPER_SNAKE_CASE
- No bare except clauses
- Return types on all public functions
## Project Structure
- src/bm/ -- main source
- tests/ -- test files, mirror src/ structure
- Data: ~/.bm/bookmarks.json
## Commit Convention
- Conventional Commits (feat:, fix:, test:, refactor:)
- One logical change per commit
- English commit messages
## Voice
- Be direct. Skip the preamble.
- No filler. When done, say "Done."
EOFStart Claude:
claudeSet up the project structure:
- src/bm/ directory with __init__.py and __main__.py
- tests/ directory
- pyproject.toml with pytest config and a console_scripts entry point for "bm"
- A .gitignore for Python projects
Then commit to main.Step 2: Data Model & Storage (Ch1 + Ch4)
Create feature/core branch.
Implement src/bm/models.py:
- Bookmark dataclass: id (auto-increment), url, title, tags (list[str]), note (optional), created_at (ISO datetime)
Implement src/bm/storage.py:
- BookmarkStore class that loads/saves from ~/.bm/bookmarks.json
- Methods: add, get_by_id, delete, list_all, search, filter_by_tag
- search should do case-insensitive substring matching across url, title, note, and tags
- Handle the case where ~/.bm/ doesn't exist yet
Commit.Step 3: Business Logic (Ch1)
Implement src/bm/service.py:
- BookmarkService wrapping BookmarkStore
- add_bookmark(url, title=None, tags=None, note=None) -- auto-fetch title from URL if not provided (use urllib)
- delete_bookmark(id)
- search_bookmarks(query)
- get_stats() -- total count, tag frequency, last 5 added
- export_markdown(tag_filter=None) -- generate the markdown export format
Commit.Step 4: CLI Interface (Ch1)
Implement src/bm/cli.py with argparse:
- bm add <url> [--title] [--tags comma,separated] [--note "text"]
- bm list [--tag filter]
- bm search <query>
- bm open <id> (use webbrowser.open)
- bm delete <id>
- bm export [--tag filter]
- bm stats
Wire it up in __main__.py so "python -m bm" works.
Commit.Step 5: Tests (Ch1 + Ch3)
Write comprehensive tests:
- test_models.py: Bookmark creation, serialization
- test_storage.py: CRUD operations, file handling, empty state
- test_service.py: search, stats, export format
- test_cli.py: CLI argument parsing (mock the service layer)
Use a tmp_path fixture for storage tests so we don't touch real ~/.bm/
Run all tests. Fix anything that fails.
Commit.Check
/contexthere. If you're above 50%, compact:/compact keep the project structure, all file paths, and test results
Step 6: Merge & Polish (Ch4)
Switch to main, merge feature/core.
Show the full git log.
Then run the tool manually:
python -m bm add "https://github.com/anthropics/claude-code" --title "Claude Code" --tags tools,ai --note "CLI for Claude"
python -m bm add "https://docs.python.org" --title "Python Docs" --tags python,reference
python -m bm list
python -m bm search "claude"
python -m bm stats
python -m bm exportStep 7: Verify
Exit Claude and test manually:
python -m bm list
python -m bm search "python"
python -m bm export --tag referenceAcceptance Criteria
| Item | Requirement | How to Check |
|---|---|---|
| CLAUDE.md | Complete, project-specific | Read it |
| Structure | src/bm/ + tests/ | ls -R |
| All commands work | add, list, search, delete, export, stats | Run each one |
| Data persists | Bookmarks survive process exit | Add, exit, list |
| Search works | Finds matches across all fields | bm search "python" |
| Export works | Generates valid Markdown grouped by tag | bm export |
| Tests pass | All green | pytest -v |
| Git history | 4+ meaningful, atomic commits | git log --oneline |
| Feature branch | Used and merged | git log --graph --oneline |
| Code quality | Type hints, docstrings, no bare excepts | Read the source |
Bonus Challenges
Once the basics work, try these in new feature branches:
- Import from browser: Parse a Chrome/Firefox bookmark export (HTML) and import into bm
- Duplicate detection: Warn when adding a URL that already exists
- Interactive mode:
bm interactiveopens a TUI with arrow key navigation - Link checking:
bm checkverifies all bookmarked URLs still return 200 - Colored output: Use ANSI colors for tags, status indicators, and search highlights
Each one is a good exercise in feature branching and atomic commits.
Alternative Project: Git Stats Analyzer
If bookmarks aren't your thing, build a git stats analyzer instead:
gitstats ~/my-project
Analyzing 1,247 commits across 18 months...
Top Contributors:
alice 587 commits (47%)
bob 412 commits (33%)
charlie 248 commits (20%)
Most Productive Hours:
10:00-11:00 ████████████████ 23%
14:00-15:00 ████████████ 18%
16:00-17:00 ██████████ 15%
Language Distribution:
TypeScript ████████████████████ 62%
Python ████████ 24%
Shell ████ 14%
Commit Patterns:
Mon ████████████ 18%
Tue ████████████████ 22%
Wed ████████████████ 21%
Thu ██████████████ 19%
Fri ████████████ 18%
Sat ██ 2%
Sun ██ 0%
Hot Files (most changed):
src/api/routes.ts 142 changes
src/utils/helpers.ts 98 changes
tests/api.test.ts 87 changesSame project structure, same CLAUDE.md approach, same Git workflow. Different output.
What You've Built
This isn't a tutorial exercise. It's a real tool. People actually use bookmark managers like this. If you went through the full capstone:
- You used CLAUDE.md to configure project standards and Claude followed them
- You managed context across a multi-step implementation
- You used feature branches with atomic commits
- You wrote tests and had Claude fix failures
- You built something you might actually keep using
That's the Beginner Stage. Everything from here builds on these foundations.
Ready for more? Stage 2: Intermediate unlocks Hooks, MCP integrations, sub-agents, and the tools that turn Claude Code from an assistant into an autonomous development system.