Skip to content

Releases: culiplan/home-assistant-culiplan

v0.16.0: setup is now one login

Choose a tag to compare

@Vincent91s Vincent91s released this 28 Oct 13:52

Setup is now one login. Most people linking Home Assistant are new to Culiplan, so the flow is shorter and an empty account no longer looks broken.

Changed

  • Setup is OAuth only. After you log in and approve, the integration is created straight away with Cloud AI. The initial-setup copies of the AI provider steps (ai_provider, ai_byok, ai_local, local_endpoint_remote_warning) are gone; the AI mode is chosen only in Configure → Change AI mode, which reuses the existing advanced_ai options steps. The Mealie import offer still appears only when a Mealie config entry exists in this Home Assistant.
  • Configure shows the AI mode you actually use. The options form read the mode from entry.data only, so after switching to BYOK or Local it still said "cloud". It now reads entry.options first, matching the runtime.
  • No more Premium wording. Culiplan is free. Setup, options, services and Repairs copy no longer mention Premium, tiers or upgrades, in all five languages. A 403 from the backend now raises a neutral, non-fixable Repairs issue ("A Culiplan feature isn't available") that links to Culiplan support instead of an upgrade page, and the service error says the same.
  • The meal-plan calendar always exists, even for an account with nothing planned (it shows "off" instead of being missing).

Added

  • "Plan your first week in Culiplan" Repairs notice. Shown once, only on installs created with 0.16.0 or later, when the account has no meals in the backend's window (last 7 days plus the next 14) and none in the past year. It waits two minutes after setup so a first week seeded on link can arrive first. It links to the web planner and disappears by itself as soon as a meal is planned; submitting it also hides it for good. Existing installs never see it.
  • Redirect tip on the login step and in the README for when the browser does not return to Home Assistant after approval (set an external URL or enable My Home Assistant, then try again).
  • async_create_fix_flow in repairs.py, so HA registers it as a valid repairs platform (it previously logged "Invalid repairs platform").

Fixed

  • An empty Mealie token in the import wizard showed the BYOK "API key required" error; it now has its own message.
  • translations/en.json had drifted from strings.json (options, services and some errors were missing, so HA showed raw keys). It is regenerated from strings.json.

Upgrading

Existing installs keep their AI mode, BYOK provider, Local AI endpoint and options unchanged. No entry migration runs. The new translations for de / es / fr / nl were machine-written and should get a native-speaker check.

v0.15.2 — Pantry expiry no longer lands a day early east of UTC

Choose a tag to compare

@Vincent91s Vincent91s released this 21 Sep 18:54

Fixed

  • Pantry expiry no longer lands a day early for users east of UTC. culiplan.pantry_add, the add_to_pantry LLM tool and the CuliplanAddToPantry Assist intent now send Home Assistant's configured timezone (hass.config.time_zone) with every /api/voice/execute call. The backend counts "expires in N days" from that zone's calendar day instead of the server's UTC instant, so an 08:00 AEST "add milk to the fridge, expires in 3 days" gets the 3-days-from-today date the user meant. Previously it fell back to the timezone synced from the Culiplan mobile app, and to UTC for HA-only accounts — where anything added before the local UTC offset had elapsed that morning was stored one day short. Reported by a user in Australia; the matching backend and app fixes ship separately.

v0.15.1 — Dependency fix: Anthropic SDK 1.x broke imports on fresh installs

Choose a tag to compare

@Vincent91s Vincent91s released this 17 Sep 08:54

Dependency fix only; no functional change from 0.15.0.

Fixed

  • anthropic is now constrained to >=0.49.0,<1. The Anthropic Python SDK 1.x (August 2026) moved its HTTP layer to httpx2, and import anthropic then fails with ValueError: httpx2.Timeout must either include a default… under the pydantic versions Home Assistant pins. On a fresh install that would have broken the BYOK Anthropic dispatcher at load time. Home Assistant core pins the 0.x series too, so the cap does not conflict with the built-in Anthropic integration. Migrating the dispatcher to SDK 1.x is tracked as follow-up work.
  • CI lint is pinned to ruff==0.15.16 / mypy==2.1.0, matching the versions the repo is linted with. ruff 0.16 turned on 413 default rules (up from 59), which made the unpinned Validate workflow fail on every push since 2 September with findings in untouched files. Adopting the new defaults is a separate, deliberate change.

v0.15.0 — "Add milk to the fridge" now works, and so do the other voice commands

Choose a tag to compare

@Vincent91s Vincent91s released this 17 Sep 08:40

Adds the first pantry write path for Home Assistant, and fixes two bugs that meant no Culiplan Assist sentence has ever been recognised in production.

Added

  • culiplan.pantry_add service — name (required), quantity, unit, location (pantry / fridge / freezer / counter / spice_rack / other, default pantry), expiration_days. Turns a "new item on the fridge list" automation into a one-line service call.
  • add_to_pantry LLM tool — the seventh Culiplan tool exposed to any HA conversation agent (OpenAI / Anthropic / Google / Ollama), so "add milk to the fridge" works through whichever LLM you already run in HA.
  • CuliplanAddToPantry Assist intent in en / nl / de / fr / es — "add milk to the fridge", "zet melk in de koelkast", "füge Milch zum Kühlschrank hinzu", "ajoute du lait au frigo", "añade leche a la nevera" — with a location-aware spoken confirmation. The location slot is optional and defaults to the pantry.

All three go through the backend's voice-tool executor (POST /api/voice/execute, tool add_to_pantry), which matches an existing catalog item before creating one and writes real stock. The raw POST /api/pantry/items endpoint was deliberately not used: it creates a catalog entry with no stock, so "add milk to the fridge" would have put nothing in the fridge.

Fixed

  • Assist sentences are now actually installed. Home Assistant only reads custom sentences from <config>/custom_sentences/<lang>/; the integration shipped its intents/*.yaml but never put them there, so every Culiplan sentence fell through to "Sorry, I couldn't understand that". Setup now copies each language file to custom_sentences/<lang>/culiplan.yaml (byte-compared, never rewritten when unchanged) and calls conversation.reload when something was written. The files are left in place when the integration is removed, in case you edited them.
  • Assist intents no longer 400. The generic intent handler posted to /api/voice/ha-assist, an endpoint that only accepts suggest_meal / fill_shopping_list and requires Premium, so "what's for dinner", "what's in my pantry" and "add … to the shopping list" all failed even when recognised. They now go through /api/voice/execute with the correct slot-to-parameter mapping; the non-existent get_expiring_pantry tool is remapped to get_expiring_items, and the registry's tool names are guarded by a test.

Tests

  • 756 passing, 2 skipped (was 683). Coverage 95.37%. New tests cover the API method, service, LLM tool, hassil recognition of 12 real sentences across the five languages, custom-sentences install/skip/overwrite/reload, and a snapshot of the backend voice-tool names.

v0.14.3 — The panel iframe actually fills the panel now

Choose a tag to compare

@Vincent91s Vincent91s released this 02 Sep 20:09

If you installed v0.14.2 and saw no change, this is the release you want.

v0.14.2 fixed the panel's outer element but not the iframe inside it, so the panel still showed its nav bar, about one card, and then empty background. From the outside nothing looked different — because the iframe was still exactly the same height as before.

What was still wrong

The panel's content sits inside a wrapper element that had no styling rule of its own. v0.14.2 made the outer element fill the window and told the iframe to "grow to fill the space" — but that instruction only works when the element directly above it is set up to distribute space, and this wrapper wasn't. The instruction was silently ignored.

An iframe with no height ends up at the browser's built-in default, which happens to be 150px — the same number as the original bug. That's why updating appeared to change nothing.

The wrapper is now styled correctly, so the sizing runs unbroken from the window down to the iframe.

Upgrading

Update through HACS or the Culiplan update entity and restart Home Assistant. If the panel still looks short afterwards, hard-refresh the browser once (Ctrl/Cmd + Shift + R) to clear the cached panel script.

Everything fixed in v0.14.2 — duplicate Lovelace resources and their automatic cleanup, the 404'ing dashboard cards, and the startup blocking-I/O warnings — is unchanged and still applies.

On the miss

The v0.14.2 tests checked the outer element and the iframe individually, and both were correct in isolation. The bug was in the element between them, which nothing looked at. The tests now check the whole chain from window to iframe, and each new test was confirmed to fail against the v0.14.2 code before being committed.

v0.14.2 — The sidebar panel fills the screen again

Choose a tag to compare

@Vincent91s Vincent91s released this 02 Sep 19:59

If the Culiplan panel showed its nav bar, about one card, and then what looked like a black overlay for the rest of the screen — this release fixes it. Four bugs, all of which affected every installation.

The panel collapsed to a sliver

The panel's stylesheet asked for height: 100%. A percentage height only works when the parent has a definite height of its own, and Home Assistant doesn't give custom panels one — so it silently became "as tall as the content", roughly 150px. What looked like an overlay covering the app was just the page background showing through beneath a very short iframe.

The panel is now sized against the viewport instead of its parent, so it fills the window. Nothing to do on your side beyond updating; if the old layout lingers, hard-refresh once.

Dashboards were firing hundreds of failed requests

Culiplan registers three Lovelace card resources on setup, and that registration was supposed to skip cards it had already added. The check never worked — it called two Home Assistant APIs the wrong way, and the resulting errors were swallowed — so Culiplan appended three more copies on every restart. One reported instance had built up 1398 resources: 466 duplicates of each card, every one of them requested on every dashboard load.

This release registers each card once and, crucially, refuses to register anything at all if it can't first confirm what's already there. It also cleans up after itself: dead Culiplan entries and duplicate rows are removed automatically on the next start. Resources you added yourself are never touched.

The card files didn't exist

All three card URLs returned 404. They pointed into /hacsfiles/culiplan/..., which assumes HACS copies the whole repository into your www folder — it doesn't. For an integration, HACS copies the custom_components folder and nothing else, so those files never arrived.

The card bundles now ship inside the integration itself and are served directly by it, which works the same whether you installed through HACS, copied the files manually, or used the built-in updater.

Startup warnings are gone

Home Assistant logged Detected blocking call to read_text ... inside the event loop by custom integration 'culiplan' on every boot. Culiplan re-read its own manifest from the event loop in two places. It's now read once when the integration loads.


Upgrading: update through HACS or the Culiplan update entity and restart Home Assistant. The duplicate-resource cleanup runs by itself on that restart — if you had accumulated thousands of entries, expect the first start after updating to tidy them.

Reported with unusually precise diagnostics (measured DOM sizes, exact call sites, and confirmation it reproduced with themes disabled), which is why all four were fixable in one pass. Thank you.

v0.14.1 — Stuck on "Error setting up entry"? This update fixes it

Choose a tag to compare

@Vincent91s Vincent91s released this 12 Jul 13:59

If your Culiplan integration has shown "Error setting up entry" since late June: update to this version and restart Home Assistant. You will then get a Reauthenticate prompt — sign in once and everything comes back. No need to delete and re-add the integration.

Why this happened

The Culiplan backend migrated to new infrastructure on 2026-06-25 and server-side OAuth refresh tokens did not survive the move, so every linked Home Assistant lost its session. Two integration bugs then made the failure silent:

Fixed

  • A dead refresh token now triggers Home Assistant's reauthentication repair instead of a generic setup error retried forever. Token refresh failures are now mapped properly: 4xx → ConfigEntryAuthFailed (reauth prompt), 5xx / network errors → ConfigEntryNotReady (transient retry). Applies at setup and at runtime.
  • The reauthentication flow now actually repairs the entry. Previously a reauth dead-ended on already_configured and left the invalid token in place; it now updates the existing entry (with a same-account check) and reloads it.

Also included (previously unreleased 0.14.0)

  • Blueprint: mirror the Culiplan shopping list into any Home Assistant to-do list (Microsoft To Do, Todoist, …).
  • Sidebar panel recovers from SSO bridge failures with a diagnostic trace (auth_rejected vs backend_unavailable).

672 tests passing; ruff + mypy --strict clean.

🤖 Generated with Claude Code

v0.13.2 — Updater no longer blocks the event loop

Choose a tag to compare

@culiplan culiplan released this 08 Jun 12:11

Bugfix

Clears the Detected blocking call to open / scandir warnings the self-updater raised inside the Home Assistant event loop.

Fixed

  • The self-updater no longer does blocking file I/O on the event loop. async_perform_update opened the temp zip file and let tempfile.TemporaryDirectory() run its scandir cleanup directly in the loop. The download now streams into memory (async), and all filesystem work — writing the zip, extracting, the backup/copytree swap, and temp-dir cleanup — runs in a single async_add_executor_job. No change to what gets installed: the zip-slip guard, backup/rollback, and empty/missing-component checks are unchanged.

Tests

  • 18 updater tests + mypy --strict + ruff clean.

Full changelog: https://github.com/culiplan/home-assistant-culiplan/blob/main/CHANGELOG.md

v0.13.1 — No more forced reauth

Choose a tag to compare

@culiplan culiplan released this 08 Jun 11:59

Reliability fix

Stops long-lived entries from being forced into reauthentication once the access token ages past its TTL.

Fixed

  • The integration no longer drops into "reauthentication required" after running for a while. CuliplanApiClient captured the access token once at setup and reused it for every REST call. The coordinator only refreshed it on a Socket.IO (re)connect, so an event-driven refresh or a user service call made hours later sent a stale token, got a 401, and ConfigEntryAuthFailed forced a full reauth. The client now resolves the bearer through a shared async token provider on every request — async_ensure_token_valid() refreshes it transparently when it is near expiry. A single OAuth2Session is now shared between the REST client and the Socket.IO coordinator, so the two consumers cannot race the backend's single-use refresh-token rotation. Pairs with the backend rotation grace window shipped the same day.

Tests

  • 667 passed, 2 skipped; mypy --strict clean.

Full changelog: https://github.com/culiplan/home-assistant-culiplan/blob/main/CHANGELOG.md

v0.13.0 — Test infrastructure overhaul + multi-account unique IDs

Choose a tag to compare

@culiplan culiplan released this 07 Jun 12:39

v0.13.0 — Test infrastructure overhaul + multi-account unique IDs

Quality release. Fixes a real multi-account bug, drives the test suite from
52% with 98 skips to 95.11% with zero skips, and tightens the Platinum-tier
mypy / ruff / pytest gates so CI now enforces the same floor end-to-end.
Targeted at Home Assistant Core inclusion review.

Fixed

  • Adding a second Culiplan account no longer breaks half of the entities.
    The four sensor entities (meals_planned_this_week, shopping_items,
    expiring_pantry, planned_kwh_today), both binary sensors
    (pantry_has_expiring, dinner_party_active) and the update entity all
    used a literal culiplan_<suffix> as their unique_id. Home Assistant
    rejects a duplicate (platform, unique_id) tuple, so the second account
    silently dropped each entity that collided with the first. Switched every
    entity that was not already keyed on a per-resource ID (calendar and todo
    were fine) to f"{entry.entry_id}_<suffix>". The config entry VERSION
    is bumped from 1 to 2 and a new async_migrate_entry rewrites every
    existing entity-registry record on first load after upgrade, so users do
    not lose entity history or break automations that target the old
    entity_id. The migration is idempotent and bounded to the entry being
    migrated.
  • Cooking timer entity IDs no longer have a double underscore when the
    session ID ends on one.
    _timer_entity_id("session_id_abc", "pasta")
    previously produced timer.culiplan_session_session__pasta (note the
    __). Both the truncated session ID and the label are now slugified
    the same way before being joined.

Changed

  • Coverage gate raised from 50% to 95% in CI. tests/conftest.py no
    longer ships a _BROKEN_TEST_IDS skip list; the corresponding
    pytest_collection_modifyitems hook is gone. The 98 tests that lived on
    that list have all been either repaired (drift fixes, lazy-import patch
    paths, typed-error contract migrations) or rewritten against the current
    API surface.
  • mypy --strict is now clean on every source file. The
    disable_error_code list gained arg-type / call-overload /
    attr-defined with a comment explaining the openai / anthropic /
    google-genai stub-drift trade-off.
  • ruff format and ruff check are both clean with a documented
    per-file ignore for test-only patterns.

Tests

  • 664 tests passing on HA 2026.6 / 655 on HA 2024.10 + 2025.1
    (google-genai SDK deliberately skipped on those lanes due to
    httpx<0.28 pin conflict).
  • Coverage 52.10% → 95.11% on the HA 2026.6 lane.
  • New test files: test_application_credentials.py, test_update_entity.py,
    test_updater.py, test_api_client.py, test_init_migrate.py,
    test_init_misc.py, test_config_flow_options.py, test_cooking_extras.py,
    test_services_extras.py, test_platform_setup.py, test_misc_coverage.py,
    test_integration_setup.py (skipped without hass_frontend).

CI

  • Pinned josepy<2 on HA 2024.10/2025.1 lanes (newer HA on those versions
    still imports ComparableX509 from josepy v1).
  • Pinned hassil<2 on HA 2024.10 (UnmatchedRangeEntity dropped in
    hassil v2).
  • Installed google-genai only on the HA 2026.6 lane (older lanes' httpx
    pin makes every google-genai 1.x release unresolvable).