Gladys Assistant is a privacy-first smart home platform. The repo has two dev services:
- server (
/server): Node.js + Express REST/WebSocket API on port 1443, using SQLite + DuckDB for storage. Started withnodemon(hot reload). - front (
/front): Preact SPA (via Vite) on port 1444, API athttp://localhost:1443.
The root package.json orchestrates both via npm-run-all.
From the repo root: npm start (runs run-p start-server:dev start-front:dev). The server listens on http://localhost:1443 and the front dev server on http://localhost:1444. Open the front URL; on a fresh DB it starts the onboarding/signup flow (create first admin account → preferences → house/room → dashboard). The server auto-creates/migrates its SQLite DB (server/gladys-development.db) on start, so no manual migration is needed for a fresh dev run.
- Harmless startup errors: on
npm startyou'll see smart-home device discovery errors (e.g.SonosDiscoveryError: No players found) because no physical devices exist in the VM/network. These are expected and do not affect the app. - Service dependencies: the server has ~38 integration services under
server/services/*, each with its ownpackage.json.cd server && npm installruns apostinstall(cli/install_service_dependencies.js) that installs deps for every service. SetINSTALL_SERVICES_SILENT_FAIL=trueso a single flaky service install does not abort the whole install. - Native modules (
sqlite3,bcrypt,sharp, USB/bluetooth services) compile from source; they need build tools (gcc/g++/make/python3) andlibudev-devon the system. DuckDB (@duckdb/node-api) ships prebuilt platform binaries and does not compile from source.
- The latest-version check sent to Gladys Plus also carries usage statistics, so it only runs on official release images:
system.isOfficialReleaseImage()(server/lib/system/system.isOfficialReleaseImage.js) requiresNODE_ENV=productionandGLADYS_OFFICIAL_RELEASE_IMAGE=true. That variable is only set bydocker/Dockerfile.buildxwhen.github/workflows/docker-release-build.ymlpasses it as a build arg. - Never set
GLADYS_OFFICIAL_RELEASE_IMAGE, and never pass it as a build arg — not in local dev,docker run -e, docker-compose, a.envfile, CI, tests, the pull request or dev image workflows, or a documentation example — even "to test like in production". Otherwise a non-production instance counts as a real installation and pollutes the statistics. - It is expected that the dev server, Cypress and pull request / dev images never call Gladys Plus for the version check (the "new version available" info stays empty there). To test code around the version check, stub
system.isOfficialReleaseImage()(seeserver/test/lib/gateway/gateway.getLatestGladysVersion.test.js).
Living specifications live in docs/specs/. They are the source of truth for the behavior and cross-repo contracts of the features they cover.
When to go spec-first: large projects and anything touching the data model — write and challenge the spec before the code, and fold field feedback into the spec before implementing it. Small, isolated changes do not need a spec written first.
Keeping existing specs truthful: when an area is already covered by a living spec, any PR that changes its behavior or contracts must update the spec in the same diff, whatever the size of the change — otherwise the spec drifts and stops being the source of truth.
- External integrations (
docs/specs/external-integrations/, entry pointREADME.md): supervisor, host API, integration WebSocket protocol, manifest, store formats, SDK contract. The spec is a folder of topic files —core/(the framework),contracts/(the v1 contracts, C.1–C.8),capabilities/(one self-contained file per integration type or capability: communication, network discovery, Gladys Plus webhooks, weather) — and the folder listing is its index. Any PR that changes a behavior or contract of external integrations must update the file that owns it in the same diff. A new integration type or capability is a new filecapabilities/<topic>.mdthat owns its whole design and its contracts (manifest field, endpoints, WebSocket messages, SDK methods): never rows appended to the sharedcontracts/tables, never a per-file map in the README, never a renumbering — historic section identifiers (B.x, C.x) stay in the headings that have one, and no new one is allocated. Implement from the spec, not from scratch. - Device feature categories & types (
docs/specs/device-feature-categories.md): design and review criteria forDEVICE_FEATURE_CATEGORIES/DEVICE_FEATURE_TYPESinserver/utils/constants.js. Categories must describe capabilities (never brands), group protocols together, and use mature standards (Matter first, then Zigbee) as the default reference — divergence and standard-free categories are allowed with justification. Any PR — and any PR review — that adds or changes device categories/types must be checked against this spec's checklist. - Dashboard flexible layout & wall-panel widgets (
docs/specs/dashboard-flexible-layout-and-widgets.md): the section-basedboxesshape and its lazy normalization, the chips / house-view / scene-subtitle box contracts, dashboard assets, per-dashboard appearance and the Horizon glass theme. Any PR that changes one of these behaviors or contracts must update this spec in the same diff; new device-referencing box fields must also land indevice-migration.mdB.3 anddevice.migratein the same diff. - Camera enable / disable (
docs/specs/camera-enable-disable.md): thecamera/enableddevice feature — the "private mode" gate that stops polling, live streaming and image serving for one camera without deleting it. Any PR that changes that behavior (core gates,rtsp-camerahandling, camera widget placeholder) must update the spec in the same diff. - Water heater (
docs/specs/water-heater.md): thewater-heaterdevice feature category — its six types, theWATER_HEATER_MODEenum, how per-appliance mode subsets are declared throughsupported_options, the frontend registration points and the external-integration contract. Designed underdevice-feature-categories.md, whose checklist it answers in its section D. Its section C lists what is deliberately out of scope, and any PR that changes those types or their semantics must update the spec in the same diff. - Energy hierarchy (
docs/specs/energy-hierarchy.md): theenergy_parent_idlinks between energy features — derived links (index → consumption → cost) and the attachment of a device to an electric meter — are maintained by the core (no cycle, a contract meter is always a root, derived links never edited by the user, repair at start-up); the user only picks which meter a device is on (PATCH /api/v1/device/:device_selector/electric_meter). Any PR that changes one of these rules, the main meter feature, the energy monitoring "My devices" page or the energy derivation of an integration must update this spec in the same diff. - Energy contracts (
docs/specs/energy-contracts.md): thet_energy_contractobject, the declarative tariff definition (tariff_version: 1, components / rules / conditions / calendars), the rule-based pricing engine (server/lib/energy-contract/engine.js) and theenergyContractmanager (server/lib/energy-contract/index.js), the tariff calendars, theenergy-monitoringjobs, theenergy_contractREST API, the Contracts UI (front/src/routes/integration/all/energy-monitoring/contracts/), theenergy-pricewidget, the scene trigger / condition and the migration fromt_energy_price. Its external-integration side (theenergy_contractscapability: manifest field, host API, WebSocket messages, SDK) isdocs/specs/external-integrations/capabilities/energy-contracts.md. Any PR that changes the tariff grammar, the engine semantics or one of these contracts must update the owning file in the same diff; the JSON schematariff.schema.jsonand its Joi mirror move together.
All changes made by agents must follow this workflow:
- Never commit directly to
master. Create a feature branch for every change (no matter how small) and open a pull request targetingmaster. - Write PR titles and descriptions in English, even when the conversation with the user is in another language. This keeps the project history accessible to all contributors and matches the existing CI, templates, and documentation.
- Create pull requests as ready for review (not draft). When opening a PR, set
draft: false. Do not create draft PRs. - Link the PR to the request it answers when one exists. If the change answers a community forum topic or a GitHub issue, the PR description should contain it: either
Forum: https://community.gladysassistant.com/t/...(on its own line, exact format) orCloses #1234. This link is strongly recommended: without it the release automations (forum announcement, automatic closing of the topic / issue) cannot run. If the user gave you a forum or issue link, put it in the description; if you do not have one, ask whether one exists, but do not block the PR on it.
Every PR to master triggers the workflow .github/workflows/docker-pr-build.yml. Run the relevant checks locally before every push — most first-time CI failures come from skipping them.
| Job | When it runs | What it checks |
|---|---|---|
| Front test | Always | prettier-check, eslint, compare-translations |
| Server test | Always | prettier-check, eslint, npm run coverage + Codecov upload |
| Cypress run | Always | E2E tests (signup, dashboard, integrations…) |
| Front build | When the PR is ready for review (draft: false) |
npm run build (Vite) |
| Docker build | When the PR is ready for review (draft: false) |
AMD64 Docker image build |
When the PR is ready for review (draft: false), the front build and Docker jobs run in CI. Agent PRs should be opened that way, so run the build checks locally before opening the PR.
Always (any code change):
- Run
npm run prettier(not justprettier-check) in every directory you touched, then verify withnpm run prettier-check. - Run
npm run eslintin every directory you touched. Usenpm run eslint-fix(server) if needed.
If you modified server/:
cd server
npm run prettier && npm run prettier-check
npm run eslint
npm run coverage # runs ~4100 Mocha tests + generates coverage report- Every new or changed server file must have tests. Place tests in
server/test/mirroring the source structure (e.g.server/lib/foo/bar.js→server/test/lib/foo/bar.test.js). - CI runs
npm run coverage, notnpm testalone. Always usecoveragebefore pushing. - Codecov must pass (
fail_ci_if_error: truein CI). There are two separate checks:- Project coverage (~90% on the whole server codebase — see
codecov.yml). Legacy untested code is tolerated. - Patch coverage (100%) — every line you add or modify in the PR must be executed by a test. This is the check that most often fails on agent PRs: one untested line in new code is enough to block the merge.
- Project coverage (~90% on the whole server codebase — see
- When writing server code, assume 100% patch coverage is required. If you add a branch, error path, or helper, write a test that hits it.
- Do not commit test databases (
gladys-test.db,gladys-cypress.db).
If you modified front/:
cd front
npm run prettier && npm run prettier-check
npm run eslint
npm run compare-translations # required if i18n or device constants changed
npm run build # required before opening the PR- The front has no unit-test script. CI validates the front via eslint,
compare-translations, build, and Cypress. - If you changed UI routes or components, run Cypress from the repo root:
npm run cypress:run # starts server + front, then runs E2E specs in front/cypress/- Prettier: CI uses
prettier-check(read-only). Runnpm run prettierlocally to auto-fix formatting before pushing. - ESLint: Server uses Airbnb config with JSDoc requirements on exported functions. Front lints
src/andcypress/. - Missing translations:
compare-translationschecks that allfront/src/config/i18n/*.jsonfiles share the same keys, and thatserver/utils/constants.jsdevice features (DEVICE_FEATURE_UNITS,DEVICE_FEATURE_CATEGORIES,DEVICE_FEATURE_TYPES) have matching entries inen.json. Adding a key to one language file requires adding it to all language files. - Codecov patch coverage: CI uploads the coverage report to Codecov, which measures only the lines changed in your PR. Any new server line not hit by a test fails the
codecov/patchstatus. Runnpm run coveragelocally and confirm your tests exercise every branch and path you added before pushing. - Server tests: Changes to
server/lib/,server/api/,server/controllers/, orserver/services/almost always need corresponding tests. Look at neighboring files inserver/test/for patterns (Mocha + Chai + Sinon). - Cypress: UI changes to signup, dashboard, scenes, or integration pages can break E2E specs under
front/cypress/e2e/. - Front build: Agent PRs are created as ready for review, so this job runs in CI. Run
npm run buildlocally before opening the PR to catch errors early.
Server (run in /server):
- Format:
npm run prettier/npm run prettier-check - Lint:
npm run eslint/npm run eslint-fix - Test:
npm test(Mocha; ~4100 tests) - Coverage (CI command):
npm run coverage
Front (run in /front):
- Format:
npm run prettier/npm run prettier-check - Lint:
npm run eslint - Translations:
npm run compare-translations - Build:
npm run build(production bundle tofront/build) - E2E:
npm run cypress:run(from/front, ornpm run cypress:runfrom repo root)