wwlog
Your WeightWatchers food log data, permanently yours.
wwlog builds a local archive of your WW food log and gives you more insight into it
than the WW app provides. The archive grows every time you run it — and keeps working
long after any particular subscription or app version.
It includes a full-featured TUI for browsing daily logs, comparing nutrition, and
viewing insights, plus a pipeline mode for scripting and file export.

Features
- Local archive — a permanent, plain-JSON store of your food log that survives
subscription changes, API windows, and app discontinuations
- Offline mode — browse, report, and export from your local archive with no
network calls required
- Insights tab — a calendar heatmap of daily points budget, range summary,
points by meal, macro distribution, top foods by points, and a zero-point food log
- Nutrition tab — nutrient bars vs. recommended daily values, per-day averages,
and trend charts for calories, protein, carbs, and fat across the selected range
- Log tab — day-by-day food log with a points bar, meal breakdown, and per-entry kcal;
filter by date and sort entries by points or calories
- Pipeline mode —
--json, --report, and --export flags for scripting and file output
- No DevTools required — one
--login step stores credentials in your system keychain
Installation
Homebrew (macOS):
brew install ali5ter/tap/wwlog
Binary (macOS and Linux, no Go required):
# macOS Apple Silicon
curl -sL https://github.com/ali5ter/wwlog/releases/latest/download/wwlog_darwin_arm64.tar.gz | tar -xz
sudo mv wwlog /usr/local/bin/
# macOS Intel
curl -sL https://github.com/ali5ter/wwlog/releases/latest/download/wwlog_darwin_amd64.tar.gz | tar -xz
sudo mv wwlog /usr/local/bin/
# Linux arm64
curl -sL https://github.com/ali5ter/wwlog/releases/latest/download/wwlog_linux_arm64.tar.gz | tar -xz
sudo mv wwlog /usr/local/bin/
# Linux amd64
curl -sL https://github.com/ali5ter/wwlog/releases/latest/download/wwlog_linux_amd64.tar.gz | tar -xz
sudo mv wwlog /usr/local/bin/
Go:
go install github.com/ali5ter/wwlog@latest
Quick start
1. Authenticate — credentials are stored securely in your system keychain:
wwlog --login
This stores your WW email, password, and session token in your OS keychain (macOS
Keychain, etc.) under the wwlog service. The password is kept — not just the session
token — because WW's API has no refresh-token flow: it's what lets wwlog silently
re-authenticate once your session expires instead of prompting you to log in again on
every run. Run wwlog --logout at any time to clear all three values.
2. Archive your history — pull the full available history into your local store:
wwlog --archive
Run this once to capture everything the WW API can return (~90 days). Run it again
any time to update. Your archive is plain JSON files — readable, portable, and yours.
3. Browse or export:
# Open the TUI (defaults to the last 7 days)
wwlog
# Browse a specific date range
wwlog --start 2026-04-20 --end 2026-04-26
# Print insights report to stdout
wwlog --start 2026-04-20 --end 2026-04-26 --report
# Output log as JSON
wwlog --start 2026-04-20 --end 2026-04-26 --json
# Export to a file (json | csv | markdown | report)
wwlog --start 2026-04-20 --end 2026-04-26 --export markdown
wwlog --start 2026-04-20 --end 2026-04-26 --export json --output ~/Downloads/
Archive & offline
Building your archive
# Pull the full API window into your local store (~90 days)
wwlog --archive
# Check what your archive covers
wwlog --status
--status output:
Store ~/Library/Application Support/wwlog/store
Coverage 2026-02-26 → 2026-05-31 (95 days)
--archive output:
Archiving 90 days (2026-03-03 → 2026-05-31)
fetched 90 / 90...
Archive complete.
New 85 days
Updated 5 days
Stored 95 days total (2026-02-26 → 2026-05-31)
Run --archive regularly to keep your store current. It is safe to run repeatedly —
existing entries are updated, not duplicated.
Browsing your archive offline
Once you have a local archive, wwlog works with no network connection:
# No API calls — serves entirely from your local archive
wwlog --offline --start 2026-01-01 --end 2026-03-31 --report
wwlog --offline --start 2026-01-01 --end 2026-03-31 --json
The TUI also falls back to your archive automatically when the WW API is unavailable,
surfacing a notice in the status bar rather than erroring out.
Cloud sync
Set store_dir in config.toml to a Dropbox, iCloud Drive, or any synced folder to
share your archive across machines:
store_dir = "~/Dropbox/wwlog-store"
The API retention window
The WW API enforces a hard ~90-day backwards retention limit — dates older than 89 days
return HTTP 400 regardless of account age. Your local archive is the solution: once a day
is stored locally, it's available indefinitely regardless of the API window.
If you request a range that starts before the window, wwlog serves older dates from your
archive automatically and fetches the in-window dates from the API:
note: 45 day(s) loaded from local store
If the API is unavailable for any dates in the window, wwlog falls back to your archive
for those dates and continues:
note: API unavailable for 3 day(s) — showing archived data where available
JSON pipeline examples
The --json flag outputs a JSON array of day logs. Each element contains the date, meals
(morning / midday / evening / anytime arrays of food entries), and a points summary. Pipe it
to jq for quick ad-hoc analysis.
Daily points summary:
wwlog --start 2026-04-20 --end 2026-04-26 --json \
| jq '.[] | {date: .Date, used: .Points.DailyUsed, target: .Points.DailyTarget}'
All food names and points across the range (flat list):
wwlog --start 2026-04-20 --end 2026-04-26 --json \
| jq '[.[].Meals | to_entries[].value[]] | map({name: .name, pts: .pointsPrecise})'
Days where you went over budget:
wwlog --start 2026-04-20 --end 2026-04-26 --json \
| jq '.[] | select(.Points.DailyUsed > .Points.DailyTarget)
| {date: .Date, over: (.Points.DailyUsed - .Points.DailyTarget)}'
Top foods by points for the week:
wwlog --start 2026-04-20 --end 2026-04-26 --json \
| jq '[.[].Meals | to_entries[].value[]]
| group_by(.name)
| map({name: .[0].name, total_pts: (map(.pointsPrecise) | add)})
| sort_by(-.total_pts) | .[0:10]'
Breakfast items sorted by calories:
wwlog --start 2026-04-20 --end 2026-04-26 --json \
| jq '[.[].Meals.morning[]
| {name: .name,
kcal: (.defaultPortion.nutrition.calories * (.portionSize / .defaultPortion.size))}]
| sort_by(-.kcal)'
The calorie calculation above mirrors what the app does internally — scale
defaultPortion.nutrition.calories by portionSize / defaultPortion.size. Entries where WW
omits calories (some protein/meat foods) will show 0; use --report for accurate totals.
Average macro distribution across the range (% of calories from protein, carbs, and fat):
wwlog --start 2026-04-20 --end 2026-04-26 --json \
| jq '
[.[].Meals | to_entries[].value[]]
| map(
(if .defaultPortion.size > 0 then .portionSize / .defaultPortion.size else 1 end) as $scale
| { p: (.defaultPortion.nutrition.protein * $scale),
c: (.defaultPortion.nutrition.carbs * $scale),
f: (.defaultPortion.nutrition.fat * $scale) }
)
| (map(.p) | add) * 4 as $pkcal
| (map(.c) | add) * 4 as $ckcal
| (map(.f) | add) * 9 as $fkcal
| ($pkcal + $ckcal + $fkcal) as $total
| { protein: "\($pkcal / $total * 100 | round)%",
carbs: "\($ckcal / $total * 100 | round)%",
fat: "\($fkcal / $total * 100 | round)%" }
'
Macros are scaled to the tracked portion size, then converted to kcal using Atwater factors
(protein and carbs ×4, fat ×9) to match what the Insights tab shows.
All pipeline examples work with --offline — no WW API required:
wwlog --offline --start 2026-04-20 --end 2026-04-26 --json | jq '...'
Key bindings
| Key |
Action |
← / → |
Move focus between the date list and detail pane (Log, Nutrition) |
↑ / ↓ or k / j |
Navigate dates — or scroll the detail pane once it has focus |
g / home, G / end |
Jump to the top/bottom of the focused date list or detail pane |
b / pgup, f / pgdn |
Page up/down in the focused date list or detail pane |
u, d |
Half-page up/down in the focused date list or detail pane |
⇧↑ / ⇧↓ |
Scroll the detail pane without moving focus off the date list |
/ |
Filter dates (Log and Nutrition tabs) |
s |
Cycle sort order (logged → by points → by kcal) |
r |
Change date range |
e |
Export (opens format picker) |
tab / ⇧tab |
Switch tabs |
? |
Toggle the keyboard-shortcut panel |
q or ctrl+c |
Quit |
Options
| Flag |
Default |
Description |
-s, --start |
7 days ago |
Start date (YYYY-MM-DD) |
-e, --end |
today |
End date (YYYY-MM-DD) |
--archive |
— |
Pull the full API window into the local store |
--status |
— |
Show local archive coverage and exit |
--offline |
— |
Serve from local archive only (no API calls) |
--json |
— |
Output log as JSON to stdout (no TUI) |
-r, --report |
— |
Output insights report as text to stdout (no TUI) |
--export |
— |
Export to file: json, csv, markdown, or report |
-o, --output |
reports/ |
Output file or directory for --export |
--no-tty |
— |
Force pipeline mode even in a terminal |
--login |
— |
Authenticate and store credentials |
--logout |
— |
Clear stored credentials |
-l, --tld |
com |
WW top-level domain (com, co.uk, etc.) |
Configuration
Optional config at ~/.config/wwlog/config.toml:
tld = "com" # WW top-level domain
weight_unit = "lb" # override weight unit: "lb" or "kg" (default: from API)
store_dir = "" # local archive path (default: alongside this config file)
[targets]
# All keys are optional. When present, the Nutrition tab bars and --report use
# these values instead of the generic reference daily values.
calories = [1700, 1900] # [min, max] kcal/day band
protein_g = [130, 150] # [min, max] g/day
carbs_g = [158, 228] # [min, max] g/day
fat_g = [31, 54] # [min, max] g/day
fiber_g_min = 26 # floor g/day (more is fine)
sodium_mg_max = 2300 # ceiling mg/day
added_sugar_g_max = 35 # ceiling g/day
saturated_fat_g_max = 23 # ceiling g/day
points_daily = 23 # override WW daily points target
water_fl_oz_target = 64 # floor fl oz/day (default: 64 — WW's 8-glass suggestion)
Range bands ([min, max]) mark the 100% bar level at the upper bound; the bar turns
purple only when you exceed that ceiling. Floor targets (_min) clamp the bar at 100%
once the floor is met — being over is fine. water_fl_oz_target sets the daily water
floor shown in --report and the Log tab water bar (default 64 fl oz / 8 glasses).
When no [targets] section is present, the Nutrition tab falls back to generic 2000 kcal
/ RDV references and --report shows the same generic reference values without hit/miss
indicators.
Support
If wwlog is useful to you, consider
sponsoring it on GitHub.
Credits
wwlog was inspired by wwtracked by
Joshua Wright.