Metadata-Version: 2.5
Name: healthlog
Version: 0.1.0
Summary: Read Google Health data, and log explicit nutrient data to it.
Project-URL: Homepage, https://github.com/owahltinez/healthlog
Project-URL: Repository, https://github.com/owahltinez/healthlog
Author: Healthlog Contributors
License-Expression: MIT
License-File: LICENSE
Keywords: calories,cli,fitbit,google-health,health,macros,nutrition,sleep,weight
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: System :: Monitoring
Classifier: Topic :: Utilities
Requires-Python: >=3.12
Requires-Dist: click-agentcli>=0.1.0
Requires-Dist: click>=8.1
Requires-Dist: google-auth-oauthlib>=1.2.0
Requires-Dist: google-auth>=2.28.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: mealtime-nutrients>=0.3.0
Description-Content-Type: text/markdown

# Healthlog

Read Google Health data, and write explicit nutrient data to it.

```console
healthlog auth login
healthlog food log "Bean salad" --grams 350 --kcal 420 --protein 25 --fat 12 --carbs 48
healthlog food log --input meal.json
cat meal.json | healthlog food log --input -
healthlog food history
healthlog food history yesterday
healthlog food history 2026-08-17 2026-08-23
healthlog food duplicate POINT_ID --protein 0
healthlog food delete POINT_ID

healthlog weight history 2026-08-01 2026-08-27
healthlog sleep history yesterday
healthlog types
```

Every data type is a noun and every noun reads the same way, so a caller that
can read one can read all of them. `healthlog types` lists them. Food is the
only type this version writes, and the only one with more than `history`.

## Food

JSON input is a flat item carrying `name`, `meal_type`, optional `time`,
optional `grams`, and nutrient fields. Omit `time` to use the device's current
local time.
Every new entry needs `kcal`, `protein`, `fat`, and `carbs`; explicit zero is a
valid value. Use `--nutrient NAME=GRAMS` for another nutrient; names come from
`mealtime-nutrients`, the list the mealtime tools share, which holds exactly
one per nutrient. Dietary fibre is `fiber`, and carbohydrate has its own field,
so it is `carbs` and never `carbohydrates`. Explicit flags override the input.
Piped tool output keeps its `{"ok":true,"data":...}` envelope, and a field this
version has not heard of is dropped, so the other tools stay free to add one.
A bare JSON object is read as hand-written instead: an unrecognised key there
is an error, rather than a nutrient quietly left out of the entry.
`grams` is written to Google as a gram serving and survives reads. When
it is absent, the shared format treats the nutrients as a 100 g fallback.

An item's nutrients describe the weight that item states, so `--grams` may not
contradict it: piping a 100 g product and asking for 250 g is refused, because
it would relabel the nutrients rather than convert them. Ask the source for the
weight you mean, as with `pantry lookup --grams 250`, or state every nutrient
here yourself. Restating the four core macros is not an escape, and is not
accepted as one: an item carrying fibre or sugar would keep those at the old
weight. An item stating no weight has nothing to contradict, so `--grams`
records what was eaten; an Eatout meal is the usual case.

`food duplicate` always keeps the source and accepts the same overrides as
`food log`. To correct an entry, duplicate it with the correction, inspect the
result, then delete the source explicitly. JSON overrides may use `null` to
remove a value.

Output carries `kcal`, `protein`, `fat` and `carbs` always, plus only the
nutrients the entry states; an absent key and a `null` mean the same, while an
explicit zero survives. Missing legacy Google core macros render as zero in
Healthlog output. Unstated nutrients are omitted from writes. `--dry-run
--json` shows the record without authenticating or writing.

`food history` totals the core macros always. Every other nutrient is totalled
only when an entry states it, over the entries that state it, so a total may
cover part of the range.

## Reading a range

Every `history` reads today by default. Pass one date for that day or two dates
for an inclusive range; dates may be ISO dates, `today`, or `yesterday`. Dates
become UTC bounds using the device's local timezone. Offset-aware ISO datetimes
are exact bounds; the end datetime is exclusive. Points are then compared only
in UTC.

Google Health spells its server-side time filter differently for nearly every
data type and rejects a wrong spelling outright, so the range is applied here
instead. Points arrive newest first, so a read stops at the first page holding
nothing new enough rather than walking a whole history.

Types other than food report each point as the API stated it, under `data`,
with only the time lifted out — samples state a `sampleTime`, intervals and
sessions an `interval`, and daily summaries a civil `date`. `--limit` caps a
dense type at 500 points by default; a capped read always says `truncated`, and
`--limit 0` reads every point in the range.

## Authentication

`healthlog auth login` asks for the read scope of every type in
`healthlog types`, plus the nutrition write scope. Google refuses to refresh a
token for a scope it never granted, so a token from an earlier version keeps
working for what it does cover: `healthlog auth status` reports the scopes it
lacks, and a read it cannot do fails with a 403 naming the re-login.

OAuth tokens remain in `~/.config/healthlog/tokens.json` with mode `0600`.
