home-services/CLAUDE.md
Nik Afiq 5238298b55
Some checks failed
CI / test (push) Successful in 6s
CI / build-ai-gateway (push) Failing after 28s
CI / build-ha-gateway (push) Failing after 24s
CI / build-discord-bot (push) Failing after 29s
Add TTS model components and inference server
- Implemented core model components in `modules.py` including various convolutional layers and normalization techniques.
- Added transformation functions in `transforms.py` for piecewise rational quadratic transformations.
- Created utility functions in `utils.py` for checkpoint management, logging, and hyperparameter handling.
- Introduced monotonic alignment functionality with Cython optimization in `monotonic_align`.
- Developed a minimal inference server in `server.py` to handle synthesis requests.
- Updated requirements to include necessary dependencies for Cython and scipy.
2026-07-24 22:38:36 +09:00

7.8 KiB

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:

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, light control/discovery, switch control/discovery, and climate (HVAC) control/discovery; also relays SwitchBot Cloud remote commands (RemoteApp) when SWITCHBOT_TOKEN/SWITCHBOT_SECRET are set. Event streaming is 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, /ac, /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:

cmd/<entrypoint>/        # 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/.

tmp/ is reference-only, never a dependency

tmp/ is gitignored — nothing under it is pushed to git, and it should be treated as temporary scratch space for reference material (e.g. tmp/reference/switchbot-control-reference/, a standalone CLI copied in for local discovery/testing against an external API). Rules:

  • It's fine to read code under tmp/ for patterns, to run its scripts/tools locally (a discovery script, a CLI, etc.), or to use it as a manual testing aid.
  • Never make any of the three services (ha-gateway, ai-gateway, discord-bot) import, go.work use, or otherwise depend on anything under tmp/ at build or runtime. Since tmp/ isn't committed, that dependency would silently break for every other clone of the repo (including CI).
  • If a reference tool under tmp/ lives in its own Go module nested inside this repo's go.work workspace, invoke it with GOWORK=off rather than adding it to the root go.work — e.g. GOWORK=off ./scripts/some-tool ... from that tool's own directory.
  • If something under tmp/ turns out to be genuinely needed at runtime, port the actual logic into the relevant service's internal/ tree (following the hexagonal layout above) instead of reaching into tmp/ from committed code.

Common Commands

Regenerate protobuf code after changing anything under proto/ (requires buf):

buf generate

Run tests / vet (from repo root, or cd into a service and drop the prefix):

go test ./ha-gateway/... ./ai-gateway/... ./discord-bot/...
go vet ./ha-gateway/... ./ai-gateway/... ./discord-bot/...

Run a single test:

cd ha-gateway && go test ./internal/app/... -run TestEntityAppGetState

Build binaries:

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):

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):

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):

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.
  • ha-gateway reads SWITCHBOT_TOKEN/SWITCHBOT_SECRET (optional) to enable SwitchBot Cloud remote commands via RemoteApp; leave empty to disable that path.
  • 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.

nik-gpu deployment target

tts-gateway (planned in TTS_GATEWAY_PLAN.md) is designed to run on nik-gpu, a remote Nvidia GPU host reachable via ssh nik-gpu, managed through the Claude Code skills nik-gpu-status (read-only check), nik-gpu-sync (rsync this repo to ~/repo/home-service/ there), and nik-gpu-docker-build (build/smoke-test via a nik-gpu Docker context).

Never automatically run installation or other host-system-altering commands on nik-gpuapt/apt-get, pip install outside a container, Docker daemon config changes, driver/toolkit updates, or anything requiring sudo. Always print the exact command and ask the user to run it themselves (their own terminal, or ! <command> in a Claude Code session). This applies to the bare nik-gpu host specifically; installing packages inside a Dockerfile build (e.g. apt-get install open-jtalk as a build step) is a normal container build action, not a host mutation, and is fine to run.

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.