
Build a Habit Tracker CLI That Teaches Files and Args. A Build Next Stack field guide for learners shipping small projects.
What you are building in one focused weekend
This project is a command-line habit tracker that stores streaks in a plain text file on your machine. No database, no web UI, no sync service. You run habit log water after a glass, habit status before bed, and habit init once when you clone the repo. The stack lesson is deliberately narrow: read and write local files safely, parse simple arguments, and exit with codes that scripts can understand.
Pick one language you already use for scripts—Python, Node, or Go all work. The acceptance bar is behavior, not framework fashion. If you cannot explain where the habits file lives and what happens when two terminals write at once, you are not done yet. That honesty is the point.
Timebox the first version to two evenings. Evening one builds file I/O and a minimal log command. Evening two adds status output and argument parsing. Anything beyond that—colors, config files, cloud backup—belongs on a sticky note labeled v2, not in your editor tonight.
Choose habits you can log in under ten seconds. Drink water and read ten pages work; be healthier fails because you cannot tell when it happened. Specific verbs make the CLI feel useful on day three instead of like homework.
Milestone one: a habits file that survives reboots
Create a single file, default path ~/.habits.json or .habits.json in the project root—pick one and document it in the README. Structure it as an object keyed by habit name, each value holding last_done (ISO date string) and streak (integer). On first run, create the file with an empty object if missing; never crash on a fresh machine.
Implement log <name> first. When the user logs a habit:
- Load the file; if corrupt JSON, print a clear error and exit code 2.
- If today’s date equals last_done, print already logged and exit 0—idempotent beats clever.
- If yesterday equals last_done, increment streak; otherwise reset streak to 1.
- Write atomically: save to .habits.json.tmp, then rename over the real file.
Atomic writes teach a habit you will reuse on every side project. Crash mid-write and you still have the old file. Acceptance for milestone one: log three different habits, reboot, run status, numbers match.
Print confirmation after log: water: streak 4 (logged 2026-05-10). Silent success feels broken to beginners; one line builds trust. Send that line to stdout, not stderr, so piping stays predictable if you script later.
Milestone two: argparse without twelve flags
Add a second subcommand, status, that prints a fixed-width table: habit name, streak, last done. No sorting options yet. Add init –file path only if you need a custom location; otherwise one optional global flag –file shared by subcommands is enough.
Use your language’s standard argument parser—argparse, commander, or flag—with exactly three subcommands: init, log, status. Resist nested sub-subcommands. The learning goal is discoverable help text: running habit –help should read like a cheat sheet you would paste inside a terminal notebook.
Exit codes matter for scripting: 0 success, 1 user error (unknown habit after you add validation), 2 system error (unreadable file). Write one three-line shell script that logs stretch and checks $?—if you skip exit codes, you miss half the CLI craft.
Acceptance checks before you call it done
Run this checklist on a clean checkout—not your messy dev folder:
- Fresh init: delete the habits file, run init, file exists with {}.
- Streak math: log on consecutive calendar days; streak increments. Skip a day; streak resets to 1 on next log.
- Double log: same habit twice same day does not inflate streak.
- Corrupt file: insert a stray comma; command errors without truncating data.
- Help: habit log –help shows usage in under ten lines.
Record a terminal GIF or asciinema under thirty seconds showing log → status. Future you uses it as regression proof when adding v2 features.
Traps that turn a CLI lesson into yak shaving
Trap: JSON schema obsession. You do not need JSON Schema or migrations for three fields. A dict on disk is fine until you have ten habits and a month of logs.
Trap: cross-platform path drama. Use the standard library path helper; do not hand-build strings with backslashes. Test once on your OS; note WSL quirks in the README if you use them.
Trap: pretty colors before atomic writes. Green checkmarks do not teach durability. Students ship rainbows and lose data the first time the laptop sleeps mid-save.
Trap: inventing a custom format. CSV or JSON—pick one boring standard. Custom line formats feel fast until you need commas inside habit names.
When you catch yourself researching terminal UI libraries, close the tab and log one real habit instead. The stack lesson is files and args, not animation.
Stretch goals that wait until v2
After the acceptance checks pass, these are fair game—one at a time:
- undo last log for today only, implemented by reverting last_done and decrementing streak carefully.
- habit list showing zero-streak habits you defined in a separate habits.config file.
- –json flag on status for piping into other tools.
Each addition should keep the test script green. If you add unit tests, test streak logic with frozen dates—do not depend on “today” in assertions. Time-freeze patterns appear again in API and batch jobs later; practice here where the blast radius is one JSON file.
Version your habits file in Git only if it contains no sensitive health data. Many learners commit a sample.habits.json for demos and gitignore the real path. That split keeps repos shareable without oversharing personal logs.
Log one real habit tonight
Initialize the repo, implement log and status, and use the tracker for three days on one habit you already care about—water, walk, or bedtime reading. The file on disk and the help output are the portfolio. No screenshot of a fake dashboard required.
When v1 passes the checklist, tag v0.1.0 and write five README lines: install, init, log, status, where the file lives. That README is the second deliverable; treat it as part of the project, not an afterthought.
If streak math confuses you, print debug lines to stderr during development, then remove them before tag. Clarity beats cleverness for your first CLI.