# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Repo Overview `home-services` is a Go workspace of three internal services for home control, connected by gRPC and sharing committed protobuf-generated code: ```text Discord users | v discord-bot -----> ha-gateway -----> Home Assistant REST API | v ai-gateway ------> Ollama | v ha-gateway ``` - **ha-gateway** (port `50051`) — gRPC boundary for Home Assistant. Talks to HA's REST API; implements entity state and light control/discovery and switch discovery; switch control and event streaming are stubbed. - **ai-gateway** (port `50052`) — gRPC service that turns free-form text into home actions. Calls Ollama for intent extraction, resolves intents against a cached light list from `ha-gateway`, and calls `ha-gateway` to execute approved actions. - **discord-bot** — registers `/light`, `/switch`, `/ai` slash commands and calls `ha-gateway`/`ai-gateway` via gRPC clients. Each service is a separate Go module (own `go.mod`) joined by `go.work` at the root, plus a `gen` module for shared generated code. Module paths are `gitea.nik4nao.com/nik/home-services/{ha-gateway,ai-gateway,discord-bot,gen}`. ## Architecture (hexagonal, per service) Every service follows the same internal layout, with dependencies pointing inward: ```text cmd// # process entrypoint and wiring (loads .env, builds adapters, starts gRPC) internal/adapters/primary/ # inbound edges: gRPC servers, Discord handlers internal/adapters/secondary/ # outbound edges: HA REST client, Ollama client, ha-gateway/ai-gateway gRPC clients internal/app/ # use-case orchestration internal/core/domain/ # domain types internal/core/ports/ # driving (inbound) and driven (outbound) interfaces internal/config/ # environment loading internal/logger/ # slog setup internal/telemetry/ # OpenTelemetry setup ``` `internal/core` has no dependency on adapters — ports are interfaces that adapters implement (driven) or call into (driving). When adding a capability, the usual path is: define/extend a port in `core/ports`, implement orchestration in `app`, then wire an adapter in `adapters/primary` or `adapters/secondary`. Protobuf contracts live in `proto/` (buf module, `ai/v1` and `ha/v1` packages). Generated Go code is committed under `gen/` and consumed by all three services through the Go workspace — do not hand-edit files in `gen/`. ## Common Commands Regenerate protobuf code after changing anything under `proto/` (requires `buf`): ```bash buf generate ``` Run tests / vet (from repo root, or `cd` into a service and drop the prefix): ```bash go test ./ha-gateway/... ./ai-gateway/... ./discord-bot/... go vet ./ha-gateway/... ./ai-gateway/... ./discord-bot/... ``` Run a single test: ```bash cd ha-gateway && go test ./internal/app/... -run TestEntityAppGetState ``` Build binaries: ```bash go build ./ha-gateway/... ./ai-gateway/... ./discord-bot/... ``` Run a service locally (each loads `.env` from its own working directory via `godotenv`, so `cd` into the service dir first): ```bash cd ha-gateway && cp .env.example .env && go run ./cmd/gateway cd ai-gateway && cp .env.example .env && go run ./cmd/gateway cd discord-bot && cp .env.example .env && go run ./cmd/bot ``` For local plaintext dev, point gateways at each other with `TLS_DIR` empty, e.g. `HA_GATEWAY_ADDR=localhost:50051`, `AI_GATEWAY_ADDR=localhost:50052`. Build container images (from repo root, since Dockerfiles reference the whole workspace): ```bash docker build -f ha-gateway/Dockerfile -t ha-gateway:dev . docker build -f ai-gateway/Dockerfile -t ai-gateway:dev . docker build -f discord-bot/Dockerfile -t discord-bot:dev . ``` gRPC smoke checks (with the relevant service running): ```bash grpcurl -plaintext -d '{"domain":"light"}' localhost:50051 ha.v1.EntityService/ListStates grpcurl -plaintext -d '{"entity_id":"light.living_room","brightness_pct":80}' localhost:50051 ha.v1.LightService/TurnOn grpcurl -plaintext -d '{"text":"turn on the desk lamp","source":"local"}' localhost:50052 ai.v1.AIService/Query grpcurl -plaintext -d '{}' localhost:50052 ai.v1.AIService/ListModels ``` Note: `ha-gateway` always registers gRPC reflection; `ai-gateway` only registers reflection when `LOG_LEVEL=debug`. ## Testing Conventions Tests use the standard library `testing` package only (no testify). Mocks are hand-written structs implementing the relevant `core/ports/driven` interface with function fields (see `ha-gateway/internal/app/entity_test.go` for the pattern). ## Configuration Notes - Every service reads `TLS_DIR` to enable optional mTLS; when set, the directory must contain `tls.crt`, `tls.key`, and `ca.crt`. - `OTEL_ENDPOINT` enables OTLP gRPC traces/metrics; leave empty for local no-op telemetry. - `LOG_FORMAT=json` is the production default; `text` is easier to read locally. - None of the services implement app-layer authorization — they rely on being kept on a trusted internal network or on mTLS. Keep this in mind before adding any endpoint that wasn't previously reachable. ## CI `.gitea/workflows/ci.yaml` runs `go vet` and `go test` for all four modules (`gen`, `ai-gateway`, `ha-gateway`, `discord-bot`), then builds and pushes Docker images for the three services on pushes to `main`.