Skip to content

Releases

Kai v2.5.0

Choose a tag to compare

@gertybot gertybot released this 01 Oct 03:00
c1733da

Indoor rides whose FIT file can't be decoded can now be analysed, through their TCX export. Nothing to change when upgrading from v2.4.0, except for people who copy the Garmin skill into their own agent (see below).

Added

  • garmin_activity_files.py reads TCX files (#36). parse, query and analyze accept .tcx (any case) and return the same fields as for FIT (timestamp, heart rate, distance, altitude, speed, power, cadence, position), plus a summary per lap. It uses only the standard library, so there is nothing new to install.
  • Why it matters: the FIT file of some indoor rides uploaded by third-party apps (seen with TrainingPeaks Virtual) can't be decoded by the FIT parser, so those activities couldn't be analysed at all. Their TCX export carries the full per-second data. To use it: garmin_activity_files.py download --activity-id <id> --format tcx, then analyze, parse or query the saved file.
  • analyze gives the same statistics from a TCX as from the FIT of the same activity.

Changed

  • When a FIT file can't be decoded, the error now says to download the same activity with --format tcx and parse that.
  • A TCX file that declares a DTD or XML entities is refused (a real one never does), which closes the usual XML entity-expansion trick. Files over 100 MB are refused before being read.

If you copied the Garmin skill into your own agent rather than tracking main: copy the updated skills/garmin-health-analysis/ folder, which includes the new TCX support and the assets/ folder from v2.4.0.

How it was checked

22 new offline tests (162 in total), including the same activity built as a FIT and as a TCX giving identical analyze output; fifteen deliberate breakages of the parser each fail a test. Against a real Garmin account: all 6 indoor rides that couldn't be analysed before now analyse and match Garmin's own summaries (distance within 2%, average heart rate within 3%, max heart rate exactly), and for 12 device-recorded activities across five types the FIT and TCX analyses of the same activity agree within 1%. A synthetic 100,000-point file (45 MB) parses in about 6.5 seconds. Not checked: TCX files from sources other than Garmin Connect.

Known issues

  • parse_gpx_file fails on GPX files that carry heart rate (14 of 16 activities in a sample), and analyze can't use GPX output (#42). Use TCX for analysis meanwhile.
  • query --time with a timestamp that has no timezone is read in the machine's local time; pass a Z or an offset.
  • The Hermes Agent and Codex setups are still untested against live installs.

Full changelog: v2.4.0...v2.5.0

Kai v2.4.0

Choose a tag to compare

@gertybot gertybot released this 01 Oct 02:42
c049d29

Garmin dashboards now work with no internet connection. Nothing to change when upgrading from v2.3.2, except for people who copy the Garmin skill into their own agent (see below).

image

Changed

  • Garmin dashboards no longer need the internet (#7). garmin_chart.py loaded Chart.js from cdn.jsdelivr.net, so a dashboard was blank offline and depended on a third-party server. Chart.js 4.4.0 is now bundled in skills/garmin-health-analysis/assets/ and embedded in each page, so a dashboard is one self-contained file that draws with no network access and still works after you move, email or archive it. Pages are about 200 KB larger.
  • What's bundled: the official Chart.js build from npm, unmodified (checked against the registry's integrity hash), plus its @kurkle/color dependency, both MIT-licensed. Their licences, a checksum file and a README with the update steps are in the same folder, and the root README's credits mention them.
  • If the bundled file is ever missing, the script warns and falls back to the CDN instead of failing.

If you copied the Garmin skill into your own agent rather than tracking main: copy the whole skills/garmin-health-analysis/ folder, including the new assets/. If only the scripts are copied, dashboards still work but fall back to the CDN with a warning.

How it was checked

11 new offline tests (140 in total), and seven deliberate breakages of the change, including a one-byte edit of the bundled library, each fail a test. Rendered in headless Chromium with every hostname blocked: the previous version's page drew nothing, the new one drew every stat card and chart. Only Chromium was tried, and with a synthetic dashboard rather than real Garmin data (the chart settings and the Chart.js version are unchanged).

Known issues

  • FIT files from indoor rides uploaded by third-party apps (seen with TrainingPeaks Virtual) can't be decoded by the FIT parser, and parse can't read the TCX export that does carry their data (#36).
  • The Hermes Agent and Codex setups are still untested against live installs.

Full changelog: v2.3.2...v2.4.0

Kai v2.3.2

Choose a tag to compare

@gertybot gertybot released this 29 Sep 09:22
58d0b0a

Bug fix release: downloading a Garmin activity as a FIT file now gives you a real FIT file, and SECURITY.md becomes a real vulnerability-reporting policy. Nothing to change when upgrading from v2.3.1 unless you copied this workspace into your own agent (see the CREDENTIALS.md note below).

Fixed

  • garmin_activity_files.py download --format fit saved a ZIP archive under a .fit name (#15). Garmin sends the original file zipped, so parse, query and analyze failed with Invalid .FIT File Header. The file is now unpacked on download, and parse/query/analyze also accept a ZIP-wrapped FIT, so files an earlier version left on disk work without downloading them again.
  • download failed when --output-dir didn't exist. It is now created (mode 700).
  • .FIT in capitals (what devices and Garmin Express write) was rejected as an unsupported file type.
  • A FIT file the parser can't decode now says so plainly instead of Invalid struct format: <0f.

Changed

  • Downloaded activity files are now saved owner-only (600) and are never written through a symlink at the destination. They contain GPS tracks, and the default folder, /tmp, is shared. If you relied on other users being able to read them, adjust the permissions yourself.
  • SECURITY.md renamed to CREDENTIALS.md, and a real SECURITY.md added (#24). Kai's own rules on handling your credentials lived in SECURITY.md, but GitHub treats that filename as a repository's vulnerability-reporting policy (shown on the Security tab, offered to anyone reporting a bug). The agent rules are now CREDENTIALS.md, and SECURITY.md says which versions are supported, what's in scope and how to report a problem privately (with a reminder not to attach real tokens or personal data). AGENTS.md's session routine reads CREDENTIALS.md, and a new test checks that every file the routine names exists.
    If you copied this workspace into your own agent rather than tracking main: copy CREDENTIALS.md in and change SECURITY.md to CREDENTIALS.md in the "Every Session" section of your AGENTS.md. Your old file still has the same rules, so nothing breaks in the meantime. Details in docs/upgrading.md.

How it was checked

27 new offline tests (26 for the FIT fix, 1 for the session routine), including a FIT file built inside the test so the whole path (unzip, parse, analyze) runs end to end; deliberately breaking the fix in nine ways each fails a test. Against a real Garmin account, every activity recorded on a watch in a 14-activity sample downloaded and analyzed correctly.

Known issues

  • FIT files from indoor rides uploaded by third-party apps (seen with TrainingPeaks Virtual) can't be decoded by the FIT parser, and parse can't read the TCX export that does carry their data (#36). In the sample, all 5 such rides failed.
  • The Hermes Agent and Codex setups are still untested against live installs.

Full changelog: v2.3.1...v2.3.2

Kai v2.3.1

Choose a tag to compare

@gertybot gertybot released this 29 Sep 04:04
be0503e

Bug fix release: tyre mileage tracking now uses the athlete's own local date, not UTC. Additive: no commands, paths or credentials change from v2.3.0.

Fixed

  • tyre-mileage.sh compared a tyre's fitted_date against activities' UTC timestamps instead of the athlete's own local date (#22):

    • East of UTC (e.g. Sydney): a ride on the local morning of the fit day could have a UTC timestamp before UTC midnight of that date, so it was never fetched from Strava at all and silently missed.
    • West of UTC (e.g. the Americas): a ride the evening before the fit day could fall after UTC midnight and be wrongly counted.

    The fix compares each activity's own start_date_local (which Strava already provides, per-activity, from its GPS-detected timezone) against fitted_date directly — no machine or ledger timezone configuration needed. The filtering logic moved into a small, independently tested module (tyre_activity_filters.py); the Strava after= fetch keeps a generous margin for efficiency only, with the local-date compare doing the real filtering afterwards.

    Verified against live data: a real early-morning ride in Sydney that the old code fetched zero times is now correctly counted, and a same-fetch-window ride from the day before is still correctly excluded.

Known issues

  • FIT downloads from the Garmin skill are saved as a ZIP, so analyze/parse on them fails (#15).
  • SECURITY.md holds Kai's own agent rules on handling credentials, but GitHub treats that filename as the repo's vulnerability-reporting policy (#24; fix in review).
  • The Hermes Agent and Codex setups are still untested against live installs.

Full changelog: v2.3.0...v2.3.1

Kai v2.3.0

Choose a tag to compare

@gertybot gertybot released this 28 Sep 21:16
1a05b9c

Tyre ledger helpers, and a documentation restructure. Additive: no commands, paths or credentials change from v2.2.1.

Added: tyre ledger helpers (#16)

skills/gear-maintenance/scripts/tyres.py edits the tyre ledger, so data/tyres.json no longer needs hand-editing:

python3 skills/gear-maintenance/scripts/tyres.py list
python3 skills/gear-maintenance/scripts/tyres.py add-wheelset road_wheels --name "Road wheels" --gear-id b1234567
python3 skills/gear-maintenance/scripts/tyres.py add-set road_wheels --model "GP5000 28mm" --replace-at 4000
python3 skills/gear-maintenance/scripts/tyres.py add-set road_wheels --model "GP5000 28mm" --replace   # retires the old set, fits the new one, in one write
python3 skills/gear-maintenance/scripts/tyres.py retire road_wheels-gp5000-28mm-2026-01-01
  • A wheelset wears one active tyre set at a time; add-set --replace retires the old one (recording when) and fits the new one atomically.
  • fitted_bike_odometer_km is informational only: from --odometer, or from Strava only when the wheelset has exactly one bike and the set is fitted today. Strava being unreachable never blocks a change.
  • Notes, custom fields and key order in your existing ledger are preserved. A ledger left in the old skills/strava/data/ location is edited in place, with a notice; a new file is never created alongside it.
  • The mileage report is unchanged (tyre-mileage.sh).

Documentation restructure

The README was 381 lines covering three audiences at once. It's now 123 lines: the pitch, the capability table, how it works, a five-step quick start, and a documentation index. Everything else moved out, and each command now lives in exactly one place instead of being repeated across files:

  • docs/installation.md — requirements, Python packages (venv and uv), connecting each data source, gear maintenance setup.
  • docs/openclaw.md, docs/claude-code-codex.md, docs/hermes.md — one page per agent.
  • docs/features.md — every capability in detail: what you get, how it works, what it needs, known limits, and example prompts.
  • docs/architecture.md — how it works, repository layout, customizing, privacy detail.
  • docs/upgrading.md — v1.x → v2, gear maintenance moved.
  • CONTRIBUTING.md — set-up, tests, branch and PR naming, the personal-data rule, how to add a skill, releases.
  • CHANGELOG.md — one entry per release from here on.
  • .github/ — a pull request template and bug/feature issue templates, each with a reminder to keep personal data out of anything public.

Old links keep working: the README still has a short "Upgrading from v1.x" section at the anchor the v2.0.0 release notes point to.

Tests (83, was 79)

New: every relative link and heading anchor across the tracked docs resolves; the anchor published releases link to still exists; the README stays under 160 lines; every docs/ page is linked from it; 23 new tests for tyres.py (refusal without --replace, atomic replace, id collisions, ledger-location fallback, a round trip through the real mileage report).

Known issues

  • FIT downloads from the Garmin skill are saved as a ZIP, so analyze/parse on them fails (#15).
  • tyre-mileage.sh treats a tyre's fitted date as midnight UTC, so a fit-day ride outside UTC can be missed or miscounted (#22).
  • SECURITY.md holds Kai's own agent rules, but GitHub treats that filename as the repo's vulnerability-reporting policy (#24).
  • The Hermes Agent and Codex setups are still untested against live installs.

Full changelog: v2.2.1...v2.3.0

Kai v2.2.1

Choose a tag to compare

@gertybot gertybot released this 28 Sep 12:06
3d61fd5

Docs and error-message fixes. No behaviour or path changes: nothing to do when upgrading from v2.2.0 except git pull.

Fixed

  • Stale login instructions. Five Garmin scripts (garmin_data.py, garmin_data_extended.py, garmin_query.py, garmin_activity_files.py, garmin_chart.py) told an unauthenticated user to run garmin_auth.py login --email ... --password ..., or just said "Not authenticated". The --password flag was removed in v2.0. They now say to ask the account owner to run garmin_auth.py login in their own terminal. The Garmin README had the same stale line and is fixed too.
  • {baseDir} removed (#3). The Garmin, Strava and TrainingPeaks SKILL.md files used an OpenClaw-specific placeholder that Claude Code, Codex and Hermes don't resolve. They, and the skill READMEs, now use plain paths from the repo root (skills/<name>/scripts/...), so commands can be pasted as they are. Garmin commands use .venv/bin/python3, as AGENTS.md requires.

Added

  • README: a "Branches and pull requests" section with the branch naming (feat/, fix/, docs/, test/, refactor/, chore/), PR and release conventions.
  • Tests (56, was 52): docs checks (no {baseDir}, every documented repo path exists, no doc recommends a password argument) and a check that each Garmin script points to the login command when unauthenticated.

Known issues

  • FIT downloads from the Garmin skill are saved as a ZIP, so analyze/parse on them fails (#15).
  • Tyre ledger helper commands are still hand-edited JSON (#16).
  • The Hermes Agent and Codex setups are still untested against live installs.

Full changelog: v2.2.0...v2.2.1

Kai v2.2.0

Choose a tag to compare

@gertybot gertybot released this 28 Sep 11:12

Chain wax and tyre tracking move into their own skill, gear-maintenance. Nothing breaks if you don't use them; if you do, see Upgrading below.

What's new

  • New skill: skills/gear-maintenance/. It holds chain-wax.py and tyre-mileage.sh (previously in the Strava skill) with their example ledgers, a SKILL.md and a README. They aren't Strava-specific: a bike without Strava tracking works too, and Strava is just one way to read an odometer. The Strava skill is purely Strava again.
  • Claude Code and Codex find the new skill through the .claude/skills/ and .agents/skills/ links.
  • README: a new optional setup step (chain wax and tyres, with a quick start), an updated requirements note, the skills diagram and layout, and an example prompt.
  • tyre-mileage.sh now explains what to do when no tyre ledger exists, instead of failing with a traceback.
  • Tests: 52 offline tests (was 44). New ones cover the ledger-location logic for both scripts, and the repo-hygiene test now also checks the new skill and its links are tracked and that personal ledgers stay ignored.

Upgrading from v2.0 / v2.1

If you use chain wax or tyre tracking, commands moved from skills/strava/scripts/ to skills/gear-maintenance/scripts/, and ledgers from skills/strava/data/ to skills/gear-maintenance/data/:

git pull
mkdir -p skills/gear-maintenance/data
mv skills/strava/data/chain-wax.json skills/strava/data/tyres.json skills/gear-maintenance/data/ 2>/dev/null

A ledger left in the old place still works, with a notice, so nothing stops working in the meantime. If you don't use these features there is nothing to do.

Known issues

  • FIT downloads from the Garmin skill are saved as a ZIP, so analyze/parse on them fails (#15).
  • Tyre ledger helper commands (add and retire sets) are still hand-edited JSON (#16).
  • The Hermes Agent and Codex setups are still untested against live installs.

Full changelog: v2.1.0...v2.2.0

Kai v2.1.0

Choose a tag to compare

@gertybot gertybot released this 28 Sep 10:30
cc5924e

Chain wax tracking is now a script instead of arithmetic done by the agent. Additive release: nothing to change if you don't use it.

What's new

  • skills/strava/scripts/chain-wax.py (Python standard library only) keeps a ledger of chain waxes per bike and reads live bike odometers from Strava.
    • report shows km since the last wax and the next-due odometer for each bike (--json for machine output).
    • log records a wax: today with the live odometer by default, or a past date with --odometer. Options: --product, --kind hot|drip, --interval MIN-MAX, --degreased.
    • add-bike and set-odometer create the ledger and handle bikes without Strava tracking (a partner's bike, say): you give the odometer by hand and the report flags readings older than 30 days.
  • Statuses: OK, DUE SOON (the last 10% before the minimum), DUE (between minimum and maximum), OVERDUE.
  • Due ranges are fixed when a wax is logged. Hot wax defaults to an early first re-wax at 150-250 km (the first coating is thin), then 450-500 km. Drip lube defaults to 200-300 km. Any interval can be set.
  • Indoor kilometres count, since a chain wears on the trainer too.
  • Safe writes: the ledger is written atomically, and a corrupt ledger is reported and never overwritten.
  • AGENTS.md: Kai now runs the script whenever bike mileage or maintenance comes up, so the answer is the same every session.
  • Ledger file: skills/strava/data/chain-wax.json is git-ignored personal data; chain-wax.example.json shows the format.
  • Tests: 24 offline tests for the script and 3 repo-hygiene tests (the right files tracked, personal files ignored). 44 in total.

Getting started

python3 skills/strava/scripts/chain-wax.py add-bike road --name "Road bike" --gear-id <your Strava bike id>
python3 skills/strava/scripts/chain-wax.py log road --odometer <km at your last wax> --date <YYYY-MM-DD> --product "Hot wax"
python3 skills/strava/scripts/chain-wax.py report

Find your Strava bike ids with skills/strava/scripts/gear-mileage.sh. Full details are in the Strava skill README.

Known issues

  • FIT downloads from the Garmin skill are saved as a ZIP, so analyze/parse on them fails (#15).
  • Tyre ledger helper commands are still hand-edited JSON (#16).
  • The Hermes Agent and Codex setups are still untested against live installs.

Full changelog: v2.0.0...v2.1.0

Kai v2.0.0

Choose a tag to compare

@gertybot gertybot released this 28 Sep 09:27

The Garmin skill moves to garminconnect 0.3.x and gets safer login. This is a breaking release for Garmin users: read Upgrading from v1.x. TrainingPeaks, Strava and Kai's instructions are unchanged.

Breaking changes

  • Python 3.12 or newer for the Garmin skill (was 3.10). On older systems use uv venv --python 3.12; the README shows how.
  • garminconnect 0.3.x (was pinned to 0.2.38). A plain pip install garminconnect no longer breaks the skill, and the dependency on the retired garth login is gone.
  • You must log in to Garmin once more. Tokens saved by v1.x are in a different format and can't be reused. garmin_auth.py status recognises them and tells you.
  • garmin_auth.py login --password is removed, and a password in config.json is ignored with a warning. Use the prompt, --password-stdin or GARMIN_PASSWORD.
  • Anything of yours that calls the Garmin scripts must use the project's .venv/bin/python3, not the system Python.

What's new

  • Password prompt (#6): login asks for your password and any MFA code on the terminal (hidden input). The password is never stored; only session tokens are kept (garmin_tokens.json, 600 in a 700 folder).
  • No terminal, no hang: in an agent's shell, login stops with "run this yourself" instead of waiting or asking for the password in chat. AGENTS.md now tells Kai never to ask for it.
  • Port to 0.3.x (#5): verified against a live account. All 34 script cases give the same output structure as on 0.2.38 (32 byte-identical; the other two differ by 1 ms in timestamps and by data that changed during the day).
  • GARMIN_TOKEN_DIR keeps tokens somewhere else.
  • Offline tests for the login code (.venv/bin/python3 -m unittest discover -s tests), and a rewritten install.sh.
  • README: upgrade guide, requirements table, uv instructions and a privacy note on passwords.

Known issues

  • FIT downloads are saved as a ZIP, so analyze/parse on them fails (#15). This also happens on v1.x. GPX downloads work.
  • The Hermes Agent and Codex setups are still untested against live installs.
  • Skill symlinks need Linux or macOS (or WSL).
  • TrainingPeaks (cookie auth) and Garmin (community library) use unofficial interfaces and can break or be rate-limited.

Full changelog: v1.1.0...v2.0.0

Kai v1.1.0

Choose a tag to compare

@gertybot gertybot released this 28 Sep 04:30

Adds Claude Code and Codex support, and makes the docs runtime-neutral.

What's new

  • Claude Code: a one-line CLAUDE.md (@AGENTS.md) loads Kai's operating manual, and .claude/skills/ links to the four skills. Tested: Claude Code picked up the instructions, took on the Kai role and listed all four skills.
  • Codex: AGENTS.md is read natively and .agents/skills/ links to the four skills. Based on Codex's documentation, not yet run against this repo.
  • README: new "Using Kai with Claude Code or Codex" section. The intro, requirements table, diagram and privacy notes no longer assume OpenClaw.

Upgrading from v1.0.0

git pull. There are no changes to the skills or to requirements.txt. Your USER.md, MEMORY.md and memory/ are untouched.

Known limitations

  • The Hermes Agent and Codex setups are untested against live installs.
  • The skill symlinks need Linux or macOS (or WSL). Without symlinks the skills still work through their skills/ paths.
  • Garmin needs Python 3.10+ and the pinned packages from requirements.txt. A plain pip install garminconnect installs 0.3.x, which the Garmin scripts don't support.
  • TrainingPeaks (cookie auth) and Garmin (community library) use unofficial interfaces and can break or be rate-limited.

Full changelog: v1.0.0...v1.1.0