import { type Logger } from "pino"; import { type Config } from "../config.js"; import type { NotificationService } from "../types/notification.js"; import type { WeatherService } from "../types/weather.js"; import { AutomationManager } from "./automation-manager.js"; import { AggregateDeviceSource } from "./device-sources/aggregate.js"; import { type StateToggleConfig } from "./device-sources/state-source.js"; import { DeviceVisibility } from "./device-visibility.js"; import { EventBus } from "./events/event-bus.js"; import { HttpClient } from "./http/http-client.js"; import { MqttService } from "./mqtt/mqtt-service.js"; import { RoomManager } from "./room-manager.js"; import type { HomekitService } from "./services/homekit-service.js"; import type { NanoleafService } from "./services/nanoleaf-service.js"; import type { PrometheusMetricsService } from "./services/prometheus-metrics-service"; import { ServiceRegistry } from "./services/service-registry.js"; import type { ShellyService, ShellyServiceFactory } from "./services/shelly-service.js"; import { StateManager, type StateManagerOptions } from "./state/state-manager.js"; import { type DeviceNiceNames, DeviceRegistry } from "./zigbee/device-registry.js"; /** * Construct the stdout-only logger used exclusively by the event stream's * delivery path (design.md D32; tasks 5.0b, 5.0c). * * A pino child inherits its parent's destination set, so * `logger.child(...)` would still write to the `LogBuffer` — which is exactly * the cycle D32 exists to cut, since the delivery path itself logs (fan-out * failures, per-connection overflow, the fell-behind signal). This is * therefore always a second, independently constructed pino instance, built * unconditionally — even when the caller supplies their own `options.logger` * — because the engine cannot know whether a caller-supplied logger writes to * the buffer either. */ export declare function createStreamOnlyLogger(logLevel: Config["logLevel"]): Logger; /** * Factory function type for optional services. * * Receives the engine's shared `HttpClient` and a scoped `Logger` so the * service can use them without creating its own. * * @example * ```ts * notifications: (http, logger) => * new NtfyNotificationService({ topic: "alerts", http, logger }), * ``` */ export type ServiceFactory = (http: HttpClient, logger: Logger) => T; /** * Dependency context passed to a `HomekitServiceFactory`. * * Narrowed to `http`, `logger`, and the aggregate device accessor (task * 6.16b) — `HomekitService` reads every device family through * `AggregateDeviceSource` now, so it no longer needs `MqttService`, the * Zigbee `DeviceRegistry`, or `ShellyService` directly. **BREAKING** for any * deployment supplying its own HomeKit factory built against the previous * five-field context. */ export interface HomekitServiceContext { http: HttpClient; logger: Logger; devices: AggregateDeviceSource; /** * The persisted, per-device hidden flag (design.md D7, D10). HomeKit * exposure filters through it directly — see specs/homekit * "Visibility-Filtered Accessory Exposure". */ deviceVisibility: DeviceVisibility; } /** * Factory function type specifically for `HomekitService`. * * Receives a single {@link HomekitServiceContext} object rather than positional * arguments. * * @example * ```ts * homekit: ({ logger, devices, deviceVisibility }) => * new HomekitService(logger, devices, { * pinCode: "031-45-154", * }, deviceVisibility), * ``` */ export type HomekitServiceFactory = (ctx: HomekitServiceContext) => HomekitService; /** * Options for creating an automation engine. * * All fields are optional — sensible defaults are derived from * environment variables (see `.env.example`). */ export interface EngineOptions { /** * Path to the directory containing automation files. * Each `.ts` / `.js` file should default-export a class extending `Automation`. */ automationsDir: string; /** * Whether to scan subdirectories recursively for automation files. * When true, all `.ts` / `.js` files in subdirectories are also loaded. * Useful for organizing automations into folders (e.g. `lights/`, `sensors/`). * * @default false */ recursive?: boolean; /** * Override the environment-derived config. * If omitted, config is loaded from environment variables. */ config?: Partial; /** * Provide your own pino logger instance. * If omitted, a default logger is created based on the config log level. */ logger?: Logger; /** * State manager options. * * Controls whether state is persisted to disk on shutdown and restored * on startup. State is always available in-memory regardless of this setting. * * @example * ```ts * const engine = createEngine({ * automationsDir: "...", * state: { * persist: true, * filePath: "./data/state.json", * }, * }); * ``` */ state?: StateManagerOptions; /** * Runtime options for the Zigbee2MQTT device registry. * Only relevant when `DEVICE_REGISTRY_ENABLED=true`. * * @example * ```ts * const engine = createEngine({ * automationsDir: "...", * deviceRegistry: { * names: { * devices: { * "kitchen_motion_0x1a2b": "Kitchen Motion Sensor", * }, * transform: (name) => name.replace(/_/g, " "), * }, * }, * }); * ``` */ deviceRegistry?: { /** Human-readable name mappings. Used by `registry.getNiceName()`. */ names?: DeviceNiceNames; /** * Whether to persist the device list and state to disk on shutdown * and restore them on startup. * * @default false */ persist?: boolean; /** * Path to the device registry JSON persistence file. * * @default "./device-registry.json" */ filePath?: string; }; /** * Boolean `StateManager` keys to present as devices through the unified * device source layer, controllable from the web UI and the HomeKit * bridge alike. * * This is engine-level configuration rather than a `HomekitService` * option (design.md D19): a source consumed by more than one sink cannot * live inside one of them, or state toggles would disappear from the web * UI whenever HomeKit is disabled. Passing `stateToggles` under * `services.homekit`'s options is rejected — see * `HomekitServiceOptions.stateToggles`. * * @default [] * * @example * ```ts * const engine = createEngine({ * automationsDir: "...", * stateToggles: [ * { stateKey: "night_mode", name: "Night Mode" }, * { stateKey: "away_mode", name: "Away Mode" }, * ], * }); * ``` */ stateToggles?: StateToggleConfig[]; /** * Optional services to register with the engine. * * Well-known service keys (`notifications`, `weather`, `shelly`, `nanoleaf`) * accept either a service instance or a `ServiceFactory` function. Additional * services can be registered under any custom key. * * All registered services are available to automations via * `this.services.get(key)`, `this.services.getOrThrow(key)`, * `this.services.use(key, fn)`, or `this.require(key)` (when the key * is declared in `requiredServices`). * * @example * ```ts * import { createEngine, ShellyService, NanoleafService } from "ts-home-automation"; * * const engine = createEngine({ * automationsDir: "...", * services: { * shelly: ({ http, mqtt, logger }) => { * const shelly = new ShellyService(http, mqtt, logger); * shelly.register("living_room_plug", "192.168.1.50"); * return shelly; * }, * nanoleaf: (http, logger) => * new NanoleafService(http, logger), * notifications: (http, logger) => * new NtfyNotificationService({ topic: "alerts", http, logger }), * }, * }); * ``` */ services?: { notifications?: NotificationService | ServiceFactory; weather?: WeatherService | ServiceFactory; shelly?: ShellyService | ShellyServiceFactory; nanoleaf?: NanoleafService | ServiceFactory; metrics?: PrometheusMetricsService | ServiceFactory; homekit?: HomekitService | HomekitServiceFactory; /** Any additional custom services registered under arbitrary keys. */ [key: string]: unknown; }; } /** * A running automation engine. Returned by `createEngine()`. */ export interface Engine { /** Start the engine: connect to MQTT and register all automations. */ start(): Promise; /** Gracefully stop the engine: unregister automations, stop crons, disconnect MQTT. */ stop(): Promise; /** The resolved configuration. */ readonly config: Config; /** The logger instance. */ readonly logger: Logger; /** * A second, independently-constructed pino instance that writes to stdout * only, bypassing the log buffer entirely (design.md D32). Used solely by * the realtime event stream's delivery path, so that path's own logging * can never feed back into the log category it delivers (task 5.0c). */ readonly streamLogger: Logger; /** The realtime event stream's shared publish/subscribe hub. */ readonly events: EventBus; /** The MQTT service (for advanced usage). */ readonly mqtt: MqttService; /** The HTTP client (for advanced usage). */ readonly http: HttpClient; /** The shared state manager. */ readonly state: StateManager; /** The notification service (if configured). */ readonly notifications: NotificationService | null; /** The shared service registry. Use this to access any registered optional service. */ readonly services: ServiceRegistry; /** The automation manager (for advanced usage, e.g. manual registration). */ readonly manager: AutomationManager; /** * The Zigbee2MQTT device registry. * `null` when `DEVICE_REGISTRY_ENABLED` is `false` (the default). */ readonly deviceRegistry: DeviceRegistry | null; /** * The aggregate device accessor spanning every unified device source * (Zigbee, Shelly, Nanoleaf, state toggles). Always present — an * unconfigured or disabled source is reported unavailable rather than * making this `null` (design.md D2; task 6.13a). */ readonly devices: AggregateDeviceSource; /** * User-defined rooms grouping devices across every unified device source * (design.md D14). Always present, like `devices`. */ readonly rooms: RoomManager; /** * The persisted, per-device hidden flag (design.md D7). Always present, * like `devices` and `rooms`. */ readonly deviceVisibility: DeviceVisibility; } /** * Create and configure an automation engine. * * This is the main entry point for using the framework as a package. * * @example * ```ts * import { createEngine } from "ts-home-automation"; * * const engine = await createEngine({ * automationsDir: new URL("./automations", import.meta.url).pathname, * }); * * await engine.start(); * ``` */ export declare function createEngine(options: EngineOptions): Engine; //# sourceMappingURL=engine.d.ts.map