Calorie and macro tracking from the command line, because logging a meal should be one command and not an app with an onboarding flow.
$ cal log "chicken rice" 600 45
Logged: chicken rice, 600 cal, 45g protein
$ cal today
Today (2026-08-30)
┏━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┓
┃ Meal ┃ Calories ┃ Protein ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━┩
│ chicken rice │ 600 │ 45g │
│ Total │ 600 │ 45g │
└──────────────┴──────────┴─────────┘
Remaining: 2300 cal / 2900 cal | 145g protein / 190g| Command | What it does |
|---|---|
log <name> <calories> <protein> |
Log a meal for today |
today |
Today's meals, totals, and what's left against your goals (or how far over) |
delete <name> |
Remove a meal from today by name (case-insensitive) |
history |
Every logged day, chronologically |
weight <lbs> |
Log today's weight |
progress |
Weight history with net change over the tracked period |
goal <calories> <protein> |
Set daily targets (defaults: 2900 cal, 190g protein) |
git clone https://github.com/apollo-2006/cal-cli.git
cd cal-cli
pip install -r requirements.txt
python main.py todayTo get the cal shorthand used above, alias it:
echo "alias cal='python3 $(pwd)/main.py'" >> ~/.bashrcThe log path is anchored to the script's own directory rather than the working directory, so the alias reads the same log from anywhere.
Everything lives in one human-readable log.json next to the script: no database, no
account, no sync:
{
"goals": { "calories": 2900, "protein": 190 },
"2026-05-19": {
"weight": 178.0,
"meals": [
{ "name": "chicken rice", "calories": 600, "protein": 45 }
]
}
}Days written by earlier versions stored a bare list of meals instead of an object.
get_day_data normalizes that on read, so old logs keep working without a migration.
Writes go through a temp file and os.replace, so an interrupted save cannot leave the
log truncated.
MIT. See LICENSE.
Abir Deol · abirdeol.tech