@camstack/addon-provider-enpal 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.mjs CHANGED
@@ -6056,7 +6056,7 @@ var ZodIssueCode = {
6056
6056
  var ZodFirstPartyTypeKind;
6057
6057
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6058
6058
  //#endregion
6059
- //#region ../types/dist/sleep-Czv-4wFH.mjs
6059
+ //#region ../types/dist/sleep-DyIxjZZt.mjs
6060
6060
  /**
6061
6061
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6062
6062
  * window to float samples (D455).
@@ -15261,6 +15261,32 @@ var CAP_VOCABULARY_BY_NAME = {
15261
15261
  ...SYSTEM_CAP_VOCABULARY
15262
15262
  };
15263
15263
  /**
15264
+ * Vocabulary actions by id (D983). An action step names a vocabulary action
15265
+ * (`cover.open`), never a raw cap method; this is the one lookup that turns the
15266
+ * id into its cap, method and effect — and a persisted legacy `cap` + `method`
15267
+ * pair back into the action id when exactly one action matches.
15268
+ */
15269
+ function buildIndex() {
15270
+ const byId = /* @__PURE__ */ new Map();
15271
+ for (const [cap, entry] of Object.entries(CAP_VOCABULARY_BY_NAME)) {
15272
+ if (entry.scope !== "device") continue;
15273
+ for (const action of entry.actions ?? []) byId.set(action.id, {
15274
+ cap,
15275
+ action
15276
+ });
15277
+ }
15278
+ return byId;
15279
+ }
15280
+ var index;
15281
+ function actionIndex() {
15282
+ index ??= buildIndex();
15283
+ return index;
15284
+ }
15285
+ /** The vocabulary action with this id, or `undefined`. */
15286
+ function findVocabAction(actionId) {
15287
+ return actionIndex().get(actionId);
15288
+ }
15289
+ /**
15264
15290
  * `addon-pages-source` — collection cap exposing per-provider raw page
15265
15291
  * declarations. Every addon that contributes a UI page registers a
15266
15292
  * provider here. The hub-side singleton aggregator (`addon-pages` cap,
@@ -44946,21 +44972,23 @@ function convertLegacyWhen(half) {
44946
44972
  * (instants) and state leaves (durations). It replaces the flat trigger list
44947
44973
  * plus the separate condition tree; the firing rule is in that file and in
44948
44974
  * ADR "one tree".
44949
- * - **Actions** — the `wait` and `cap` steps, plus the `{kind:'code'}`
44975
+ * - **Actions** — the `wait` and device `command` steps, plus the `{kind:'code'}`
44950
44976
  * fragment of §3.2.2. See {@link AutomationActionSchema}.
44951
44977
  *
44952
44978
  * A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
44953
44979
  * {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
44954
44980
  */
44955
44981
  /**
44956
- * One action step: `wait`, or a `cap` call.
44982
+ * One action step: `wait`, or a device `command`.
44957
44983
  *
44958
- * A cap step's target is who it acts on (D983: `devices`, a `domain`, or a
44959
- * `{ $var }` resolved from the run's variables); the legacy `deviceId` is still
44960
- * READ, never written. The step runner (`runSteps`) owns the device-scope
44961
- * check, the stop-at-first-failure and the variable substitution, once, for
44962
- * every caller — automations, Actions, and the notification centre, which runs
44963
- * only Actions (planning#473).
44984
+ * A command step names a VOCABULARY action (`cover.open`) and who it acts on
44985
+ * (D983: `devices`, a `domain`, or a `{ $var }` resolved from the run's
44986
+ * variables). It never names a cap method: the step runner turns the action
44987
+ * id into its cap + method through the vocabulary index when the step runs
44988
+ * (`vocabCall`), the one place that mapping happens. The step runner
44989
+ * (`runSteps`) owns the device-scope check, the stop-at-first-failure and the
44990
+ * variable substitution, once, for every caller — automations, Actions, and
44991
+ * the notification centre, which runs only Actions (planning#473).
44964
44992
  */
44965
44993
  /** The longest one `wait` step lasts. It is cut by a restart of the run and does not survive a hub restart. */
44966
44994
  var WAIT_MAX_SECONDS = 3600;
@@ -44969,39 +44997,22 @@ var AutomationWaitStepSchema = object({
44969
44997
  kind: literal("wait"),
44970
44998
  seconds: number$1().min(0).max(WAIT_MAX_SECONDS, WAIT_TOO_LONG)
44971
44999
  });
44972
- /** The legacy `deviceId` of a cap step: still READ, never written (D983). */
44973
- var LegacyStepDeviceSchema = union([
44974
- number$1().int(),
44975
- array(number$1().int()).min(1),
44976
- object({ $var: string().min(1) })
44977
- ]);
44978
45000
  /**
44979
- * The fields of a cap step, before the target rules are applied. Exported for
44980
- * a shape that is "a cap call, narrowed" (a remote-control binding is one
44981
- * device); a step is {@link AutomationCapStepSchema}.
45001
+ * The fields of a command step, before the vocabulary check. A step is
45002
+ * {@link AutomationCommandStepSchema}.
44982
45003
  */
44983
- var AutomationCapStepBaseSchema = object({
44984
- kind: literal("cap"),
44985
- /**
44986
- * The vocabulary action this step performs (`cover.open`). Required when
44987
- * `target` is a `domain` (the domain is "every device offering this
44988
- * action"); filled by {@link normalizeCapStep} for a legacy step whose
44989
- * cap + method match exactly one vocabulary action.
44990
- */
44991
- actionId: string().min(1).optional(),
44992
- /** WHO the step acts on. The only shape that is written. */
44993
- target: ActionTargetSchema.optional(),
45004
+ var AutomationCommandStepBaseSchema = object({
45005
+ kind: literal("command"),
45006
+ /** WHO the step acts on. */
45007
+ target: ActionTargetSchema,
45008
+ /** The vocabulary action this step performs (`cover.open`). */
45009
+ actionId: string().min(1),
44994
45010
  /**
44995
- * @deprecated The pre-D983 target: one device, several, or `{ $var }`.
44996
- * Still READ — {@link normalizeCapStep} maps it to `target` — and never
44997
- * written.
45011
+ * The values of the action's OPEN args (`position`, `presetId`); the args
45012
+ * the vocabulary bakes into the action are added when the step runs. Values
45013
+ * may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
45014
+ * SUBSTITUTE and do not evaluate (§3.2.3).
44998
45015
  */
44999
- deviceId: LegacyStepDeviceSchema.optional(),
45000
- cap: string().min(1),
45001
- method: string().min(1),
45002
- /** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
45003
- * SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
45004
- * the expression leaf. */
45005
45016
  args: record(string(), unknown()).optional(),
45006
45017
  /**
45007
45018
  * The unit the person picked per arg (display only). `args` always hold the
@@ -45010,18 +45021,20 @@ var AutomationCapStepBaseSchema = object({
45010
45021
  */
45011
45022
  argUnits: record(string(), string().min(1)).optional()
45012
45023
  });
45024
+ /** The vocabulary check every command step passes: the action exists. */
45025
+ function refineKnownAction(step, ctx) {
45026
+ if (findVocabAction(step.actionId) === void 0) ctx.addIssue({
45027
+ code: "custom",
45028
+ message: `"${step.actionId}" is not a vocabulary action id`,
45029
+ path: ["actionId"]
45030
+ });
45031
+ }
45013
45032
  /**
45014
- * The steps an internal Action may contain: cap and wait, and nothing that can
45033
+ * The steps an internal Action may contain: command and wait, and nothing that can
45015
45034
  * call back into an Action — so an Action can never reach itself (no cycles).
45016
45035
  * This is the LEAF shape: no condition of its own.
45017
45036
  */
45018
- var LeafActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCapStepBaseSchema.refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
45019
- message: "a cap step needs a target",
45020
- path: ["target"]
45021
- }).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
45022
- message: "a domain target needs the vocabulary action it applies to (actionId)",
45023
- path: ["actionId"]
45024
- })]);
45037
+ var LeafActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCommandStepBaseSchema.superRefine(refineKnownAction)]);
45025
45038
  /**
45026
45039
  * The optional guard of a step (#472): `if` is the ONE `when` tree (D980), read
45027
45040
  * on the state mirror when the step is reached; `else` runs when it is false.
@@ -45042,13 +45055,7 @@ var GuardedWaitStepSchema = AutomationWaitStepSchema.extend(stepGuardShape).refi
45042
45055
  * same two step kinds an automation runs, minus a guard that reads the
45043
45056
  * automation's trigger, which such a list does not have.
45044
45057
  */
45045
- var ActionStepSchema = discriminatedUnion("kind", [GuardedWaitStepSchema, AutomationCapStepBaseSchema.extend(stepGuardShape).refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
45046
- message: "a cap step needs a target",
45047
- path: ["target"]
45048
- }).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
45049
- message: "a domain target needs the vocabulary action it applies to (actionId)",
45050
- path: ["actionId"]
45051
- }).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf)]).superRefine((step, ctx) => {
45058
+ var ActionStepSchema = discriminatedUnion("kind", [GuardedWaitStepSchema, AutomationCommandStepBaseSchema.extend(stepGuardShape).superRefine(refineKnownAction).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf)]).superRefine((step, ctx) => {
45052
45059
  if (step.if !== void 0 && hasTriggeredByLeaves(step.if)) ctx.addIssue({
45053
45060
  code: "custom",
45054
45061
  message: TRIGGERED_BY_NEEDS_AUTOMATION,
@@ -45063,17 +45070,11 @@ var ActionStepSchema = discriminatedUnion("kind", [GuardedWaitStepSchema, Automa
45063
45070
  var continueOnErrorShape = {
45064
45071
  /** A failure of this step is traced `failed-continued` and the next step runs. */
45065
45072
  continueOnError: literal(true).optional() };
45066
- /** An automation's cap step: the shared guarded cap step, plus `continueOnError`. */
45067
- var AutomationGuardedCapStepSchema = AutomationCapStepBaseSchema.extend({
45073
+ /** An automation's command step: the shared guarded command step, plus `continueOnError`. */
45074
+ var AutomationGuardedCommandStepSchema = AutomationCommandStepBaseSchema.extend({
45068
45075
  ...stepGuardShape,
45069
45076
  ...continueOnErrorShape
45070
- }).refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
45071
- message: "a cap step needs a target",
45072
- path: ["target"]
45073
- }).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
45074
- message: "a domain target needs the vocabulary action it applies to (actionId)",
45075
- path: ["actionId"]
45076
- }).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf);
45077
+ }).superRefine(refineKnownAction).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf);
45077
45078
  /** End the run here, on purpose. `reason` may carry `{{vars.x}}` slots. */
45078
45079
  var AutomationStopStepSchema = object({
45079
45080
  kind: literal("stop"),
@@ -45153,13 +45154,7 @@ function nestTooDeep(issue) {
45153
45154
  }
45154
45155
  var plainStepSchemas = [
45155
45156
  AutomationWaitStepSchema.strict(),
45156
- AutomationCapStepBaseSchema.extend(continueOnErrorShape).strict().refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
45157
- message: "a cap step needs a target",
45158
- path: ["target"]
45159
- }).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
45160
- message: "a domain target needs the vocabulary action it applies to (actionId)",
45161
- path: ["actionId"]
45162
- }),
45157
+ AutomationCommandStepBaseSchema.extend(continueOnErrorShape).strict().superRefine(refineKnownAction),
45163
45158
  AutomationStopStepSchema.omit({ if: true }).strict(),
45164
45159
  AutomationSetStepSchema.omit({ if: true }).strict(),
45165
45160
  AutomationRunActionStepSchema.strict()
@@ -45170,7 +45165,7 @@ var plainStepSchemas = [
45170
45165
  */
45171
45166
  var AutomationBranchStepSchema = discriminatedUnion("kind", plainStepSchemas, { error: nestTooDeep });
45172
45167
  /** A step that can run beside others: it writes no variable and does not end the run. */
45173
- var AutomationParallelMemberSchema = AutomationBranchStepSchema.refine((step) => step.kind === "cap" || step.kind === "action" || step.kind === "wait", "a parallel holds device commands, actions and waits only: a set or a stop beside other steps has no order");
45168
+ var AutomationParallelMemberSchema = AutomationBranchStepSchema.refine((step) => step.kind === "command" || step.kind === "action" || step.kind === "wait", "a parallel holds device commands, actions and waits only: a set or a stop beside other steps has no order");
45174
45169
  /**
45175
45170
  * Several steps at once (#500): all are started together and all are awaited.
45176
45171
  * A failure fails the run once the others finished, as in a sequence; a member
@@ -45250,7 +45245,7 @@ var AutomationBodyStepSchema = discriminatedUnion("kind", [
45250
45245
  var AutomationRepeatStepSchema = repeatSchemaOf(AutomationBodyStepSchema);
45251
45246
  var AutomationActionSchema = discriminatedUnion("kind", [
45252
45247
  GuardedWaitStepSchema,
45253
- AutomationGuardedCapStepSchema,
45248
+ AutomationGuardedCommandStepSchema,
45254
45249
  AutomationStopStepSchema,
45255
45250
  AutomationSetStepSchema,
45256
45251
  chooseSchemaOf(AutomationBodyStepSchema),
@@ -45321,110 +45316,6 @@ function convertLegacyRecipe(legacy) {
45321
45316
  };
45322
45317
  }
45323
45318
  var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
45324
- var DETECTION_DESCRIPTIONS = {
45325
- deviceId: "the camera that saw it",
45326
- className: "what it saw",
45327
- trackId: "the track",
45328
- score: "how sure the detector was",
45329
- label: "the label it matched (a name, a plate), empty when none"
45330
- };
45331
- var STATIONARY_DESCRIPTIONS = {
45332
- deviceId: "the camera that saw it",
45333
- className: "what it saw",
45334
- zones: "the zones it is in",
45335
- phase: "appeared or departed",
45336
- entryId: "the stopped object"
45337
- };
45338
- var BECOMES_DESCRIPTIONS = {
45339
- deviceId: "the device that changed",
45340
- from: "the value before the change, empty when it is not known",
45341
- to: "the value it changed to"
45342
- };
45343
- /**
45344
- * A fire's payload is offered too, as `data.<path>` (`{{vars.data.channel}}`):
45345
- * those names depend on the device, so they are not in this list.
45346
- */
45347
- var FIRED_DESCRIPTIONS = {
45348
- deviceId: "the device that fired",
45349
- eventType: "the event, as the device names it",
45350
- sourceId: "the part of the device that fired (a button), empty when it names none",
45351
- eventKind: "the kind of press (press, long-press …), empty when the device declares none"
45352
- };
45353
- function describe$1(descriptions) {
45354
- return Object.entries(descriptions).map(([name, description]) => ({
45355
- name,
45356
- description
45357
- }));
45358
- }
45359
- describe$1(DETECTION_DESCRIPTIONS);
45360
- describe$1(STATIONARY_DESCRIPTIONS);
45361
- describe$1(FIRED_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
45362
- ...v,
45363
- hintKey: "firedDeviceId"
45364
- } : v);
45365
- describe$1(BECOMES_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
45366
- ...v,
45367
- hintKey: "becomesDeviceId"
45368
- } : v);
45369
- new Set(Object.values(COCO_TO_MACRO.mapping));
45370
- /**
45371
- * Generic stateless event emitter. Installed on a `DeviceType.EventEmitter`
45372
- * device. Carries the device's EXACT declared event vocabulary verbatim
45373
- * (NO normalization). Two shapes:
45374
- * - structured source (HA `event.*` entity) → `vocabulary` is the fixed
45375
- * declared set (e.g. press_short, press_long, …).
45376
- * - generic/legacy source (HA bus events, e.g. zha_event) → `vocabulary`
45377
- * is [] (open) and `lastEvent.data` carries the complex payload.
45378
- * `vocabulary` (D1044) names each event for a person and types it for a
45379
- * rule; `eventTypes` is its list of native tokens, kept for older readers.
45380
- * A fire names the part of the device it came from in `sourceId` when the
45381
- * vocabulary declares sources (a remote's buttons).
45382
- * `seq` is monotonic so two identical events still produce distinct slice writes.
45383
- */
45384
- var EventFireSchema = object({
45385
- deviceId: number$1(),
45386
- eventType: string(),
45387
- sourceId: string().optional(),
45388
- data: record(string(), unknown()).nullable(),
45389
- timestamp: number$1().meta(readingMeta("none")),
45390
- seq: number$1().meta(readingMeta("count"))
45391
- });
45392
- var EventEmitterStatusSchema = object({
45393
- eventTypes: array(string()),
45394
- /**
45395
- * Required of a provider by the type. A status written before the field
45396
- * existed still parses, as an empty vocabulary: read it through
45397
- * `readEventVocabulary`, which then answers with the plain tokens.
45398
- */
45399
- vocabulary: array(EventVocabularyEntrySchema).default([]),
45400
- lastEvent: EventFireSchema.nullable(),
45401
- eventCountSinceStart: number$1().meta(readingMeta("count"))
45402
- });
45403
- var eventEmitterCapability = {
45404
- name: "event-emitter",
45405
- scope: "device",
45406
- deviceNative: true,
45407
- mode: "singleton",
45408
- deviceTypes: [DeviceType.EventEmitter],
45409
- methods: {},
45410
- events: { onEvent: { data: EventFireSchema } },
45411
- status: {
45412
- schema: EventEmitterStatusSchema,
45413
- kind: "push"
45414
- },
45415
- runtimeState: EventEmitterStatusSchema,
45416
- /**
45417
- * Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
45418
- *
45419
- * See `RuntimeStateDurability`. Enforced by
45420
- * `scripts/check-runtime-state-durability.ts`.
45421
- */
45422
- durability: "session",
45423
- /**
45424
- * History (D850): none — `eventCountSinceStart` resets with the addon; events live in ledgers.
45425
- */
45426
- history: "none"
45427
- };
45428
45319
  /**
45429
45320
  * Schedulers (D983) — an entity that performs actions over the day.
45430
45321
  *
@@ -45823,6 +45714,110 @@ var AutomationStateDocSchema = object({
45823
45714
  lastRunStartedAt: number$1().nullable(),
45824
45715
  traces: array(AutomationRunTraceSchema)
45825
45716
  });
45717
+ var DETECTION_DESCRIPTIONS = {
45718
+ deviceId: "the camera that saw it",
45719
+ className: "what it saw",
45720
+ trackId: "the track",
45721
+ score: "how sure the detector was",
45722
+ label: "the label it matched (a name, a plate), empty when none"
45723
+ };
45724
+ var STATIONARY_DESCRIPTIONS = {
45725
+ deviceId: "the camera that saw it",
45726
+ className: "what it saw",
45727
+ zones: "the zones it is in",
45728
+ phase: "appeared or departed",
45729
+ entryId: "the stopped object"
45730
+ };
45731
+ var BECOMES_DESCRIPTIONS = {
45732
+ deviceId: "the device that changed",
45733
+ from: "the value before the change, empty when it is not known",
45734
+ to: "the value it changed to"
45735
+ };
45736
+ /**
45737
+ * A fire's payload is offered too, as `data.<path>` (`{{vars.data.channel}}`):
45738
+ * those names depend on the device, so they are not in this list.
45739
+ */
45740
+ var FIRED_DESCRIPTIONS = {
45741
+ deviceId: "the device that fired",
45742
+ eventType: "the event, as the device names it",
45743
+ sourceId: "the part of the device that fired (a button), empty when it names none",
45744
+ eventKind: "the kind of press (press, long-press …), empty when the device declares none"
45745
+ };
45746
+ function describe$1(descriptions) {
45747
+ return Object.entries(descriptions).map(([name, description]) => ({
45748
+ name,
45749
+ description
45750
+ }));
45751
+ }
45752
+ describe$1(DETECTION_DESCRIPTIONS);
45753
+ describe$1(STATIONARY_DESCRIPTIONS);
45754
+ describe$1(FIRED_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
45755
+ ...v,
45756
+ hintKey: "firedDeviceId"
45757
+ } : v);
45758
+ describe$1(BECOMES_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
45759
+ ...v,
45760
+ hintKey: "becomesDeviceId"
45761
+ } : v);
45762
+ new Set(Object.values(COCO_TO_MACRO.mapping));
45763
+ /**
45764
+ * Generic stateless event emitter. Installed on a `DeviceType.EventEmitter`
45765
+ * device. Carries the device's EXACT declared event vocabulary verbatim
45766
+ * (NO normalization). Two shapes:
45767
+ * - structured source (HA `event.*` entity) → `vocabulary` is the fixed
45768
+ * declared set (e.g. press_short, press_long, …).
45769
+ * - generic/legacy source (HA bus events, e.g. zha_event) → `vocabulary`
45770
+ * is [] (open) and `lastEvent.data` carries the complex payload.
45771
+ * `vocabulary` (D1044) names each event for a person and types it for a
45772
+ * rule; `eventTypes` is its list of native tokens, kept for older readers.
45773
+ * A fire names the part of the device it came from in `sourceId` when the
45774
+ * vocabulary declares sources (a remote's buttons).
45775
+ * `seq` is monotonic so two identical events still produce distinct slice writes.
45776
+ */
45777
+ var EventFireSchema = object({
45778
+ deviceId: number$1(),
45779
+ eventType: string(),
45780
+ sourceId: string().optional(),
45781
+ data: record(string(), unknown()).nullable(),
45782
+ timestamp: number$1().meta(readingMeta("none")),
45783
+ seq: number$1().meta(readingMeta("count"))
45784
+ });
45785
+ var EventEmitterStatusSchema = object({
45786
+ eventTypes: array(string()),
45787
+ /**
45788
+ * Required of a provider by the type. A status written before the field
45789
+ * existed still parses, as an empty vocabulary: read it through
45790
+ * `readEventVocabulary`, which then answers with the plain tokens.
45791
+ */
45792
+ vocabulary: array(EventVocabularyEntrySchema).default([]),
45793
+ lastEvent: EventFireSchema.nullable(),
45794
+ eventCountSinceStart: number$1().meta(readingMeta("count"))
45795
+ });
45796
+ var eventEmitterCapability = {
45797
+ name: "event-emitter",
45798
+ scope: "device",
45799
+ deviceNative: true,
45800
+ mode: "singleton",
45801
+ deviceTypes: [DeviceType.EventEmitter],
45802
+ methods: {},
45803
+ events: { onEvent: { data: EventFireSchema } },
45804
+ status: {
45805
+ schema: EventEmitterStatusSchema,
45806
+ kind: "push"
45807
+ },
45808
+ runtimeState: EventEmitterStatusSchema,
45809
+ /**
45810
+ * Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
45811
+ *
45812
+ * See `RuntimeStateDurability`. Enforced by
45813
+ * `scripts/check-runtime-state-durability.ts`.
45814
+ */
45815
+ durability: "session",
45816
+ /**
45817
+ * History (D850): none — `eventCountSinceStart` resets with the addon; events live in ledgers.
45818
+ */
45819
+ history: "none"
45820
+ };
45826
45821
  /**
45827
45822
  * Actions — named, runnable things an operator (or an automation) can start,
45828
45823
  * declared as LIST ENTRIES, never as devices.
@@ -46002,6 +45997,177 @@ var UnitWarningSchema = object({
46002
45997
  assumedUnit: string(),
46003
45998
  message: string()
46004
45999
  });
46000
+ /**
46001
+ * `device-export` — collection cap for addons that export camstack
46002
+ * devices to external ecosystems (HomeAssistant via MQTT discovery,
46003
+ * HomeKit/HAP, Alexa Smart Home, …).
46004
+ *
46005
+ * No `ecosystem` enum — the addon id identifies the export target.
46006
+ * Each addon owns its mapping logic in its own settings UI. The cap
46007
+ * exposes only the COMMON surface: link state, supported device
46008
+ * kinds (so the UI can filter the device picker), and per-device
46009
+ * expose/unexpose.
46010
+ *
46011
+ * INSTANCES. One export addon can serve several independent targets (a
46012
+ * HomeKit bridge, a Home Assistant broker). `listInstances` names them.
46013
+ * `instanceId` on a READ is optional: absent = the whole addon (linked if any
46014
+ * enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
46015
+ * default instance's); present = that instance; an id the provider does not
46016
+ * serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
46017
+ * never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
46018
+ * change always names the target it changes. A single-instance provider
46019
+ * accepts an absent id on a read or its own id, and refuses any other.
46020
+ */
46021
+ /** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
46022
+ var InstanceIdSchema$1 = string().min(1);
46023
+ var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
46024
+ /** One independent export target of an addon. */
46025
+ var ExportInstanceSchema = object({
46026
+ instanceId: InstanceIdSchema$1,
46027
+ name: string(),
46028
+ enabled: boolean(),
46029
+ kind: string().optional(),
46030
+ /**
46031
+ * What this instance's platform accepts as a device name, in one operator-facing sentence
46032
+ * (D1049). The derived "Name on <instance>" field shows it; absent = the field says nothing
46033
+ * about the rule.
46034
+ */
46035
+ nameRule: string().optional()
46036
+ });
46037
+ var LinkStateSchema = _enum([
46038
+ "unlinked",
46039
+ "linked",
46040
+ "error"
46041
+ ]);
46042
+ /**
46043
+ * A single label/value row surfaced in the export panel's "Setup"
46044
+ * section. Rendered with a copy-to-clipboard button. `secret: true`
46045
+ * rows are masked by default with a reveal toggle (client ids,
46046
+ * secrets, …).
46047
+ */
46048
+ var ExportSetupFieldSchema = object({
46049
+ label: string(),
46050
+ value: string(),
46051
+ /** Mask the value by default + render a reveal toggle (client id, secrets). */
46052
+ secret: boolean().optional()
46053
+ });
46054
+ /**
46055
+ * Generic, addon-agnostic pairing/account block. Any export addon can
46056
+ * surface a scannable QR, a set of copyable label/value rows, and a
46057
+ * free-form operator note — the `DeviceExportPanel` renders whatever
46058
+ * the provider supplies and skips the section entirely when `setup`
46059
+ * is unset.
46060
+ */
46061
+ var ExportSetupSchema = object({
46062
+ /** A string to render as a scannable QR — HAP `X-HM://…` URI, a pairing URL, etc. Omitted when there's nothing to scan. */
46063
+ qr: string().optional(),
46064
+ /** Label/value rows shown with a copy button (HAP setup code, OAuth URLs, client id, linked-account count, …). */
46065
+ fields: array(ExportSetupFieldSchema).readonly().optional(),
46066
+ /** Free-form operator instructions rendered above the fields. */
46067
+ note: string().optional()
46068
+ });
46069
+ var DeviceExportStatusSchema = object({
46070
+ linkState: LinkStateSchema,
46071
+ exposedDeviceCount: number$1(),
46072
+ error: string().optional(),
46073
+ /**
46074
+ * Optional pairing/account info the panel renders in a generic
46075
+ * "Setup" section. Addon-agnostic — the addon id identifies the
46076
+ * export target, never an `ecosystem` key here.
46077
+ */
46078
+ setup: ExportSetupSchema.optional()
46079
+ });
46080
+ var DeviceKindSchema = string();
46081
+ /**
46082
+ * EXPOSED NAME. What one instance calls a device on its platform, when it should not be the
46083
+ * device's own name. The exporter owns it: it is stored per instance in the exporter's per-device
46084
+ * store (D865), validated by the exporter against its platform's naming rules, and never reaches
46085
+ * the exported id — a rename updates the SAME device on the platform, never a second one.
46086
+ * Blank or `null` = the device's own name. A name the platform would refuse is refused with
46087
+ * `ExportNameRefusedError` (`export-name-refused`), whose message says why.
46088
+ */
46089
+ var ExposedDeviceSchema = object({
46090
+ deviceId: string(),
46091
+ exposedAs: string().optional(),
46092
+ /** The operator's name for this device on this instance; absent = the device's own name. */
46093
+ exposedName: string().optional(),
46094
+ capabilities: array(string()).optional()
46095
+ });
46096
+ var ExposeInputSchema = object({
46097
+ instanceId: InstanceIdSchema$1,
46098
+ deviceId: string(),
46099
+ capabilities: array(string()).optional(),
46100
+ /** Set the exposed name in the same call; absent = leave it as it is, blank/null = clear it. */
46101
+ exposedName: string().nullable().optional()
46102
+ });
46103
+ var SetExposedNameInputSchema = object({
46104
+ instanceId: InstanceIdSchema$1,
46105
+ deviceId: string(),
46106
+ /** Blank or null = the device's own name. */
46107
+ name: string().nullable(),
46108
+ /**
46109
+ * Validate only: refuse exactly as a real call would, write nothing. A caller saving several
46110
+ * names at once checks every one first, so one refusal writes none (D1049).
46111
+ */
46112
+ dryRun: boolean().optional()
46113
+ });
46114
+ var UnexposeInputSchema = object({
46115
+ instanceId: InstanceIdSchema$1,
46116
+ deviceId: string()
46117
+ });
46118
+ /**
46119
+ * What an exporter may expose besides devices (D968). An action and a
46120
+ * notification target are LIST ENTRIES, never devices (D954): they enter an
46121
+ * exporter's own selection, not the device registry.
46122
+ */
46123
+ var ExportEntityKindSchema = _enum([
46124
+ "device",
46125
+ "action",
46126
+ "notifier"
46127
+ ]);
46128
+ var ExportEntityRefSchema = discriminatedUnion("kind", [
46129
+ object({
46130
+ kind: literal("device"),
46131
+ deviceId: string()
46132
+ }),
46133
+ object({
46134
+ kind: literal("action"),
46135
+ actionId: string().min(1)
46136
+ }),
46137
+ object({
46138
+ kind: literal("notifier"),
46139
+ targetId: string().min(1)
46140
+ })
46141
+ ]);
46142
+ var ExposedEntitySchema = object({
46143
+ entity: ExportEntityRefSchema,
46144
+ exposedAs: string().optional()
46145
+ });
46146
+ var ExposeEntityInputSchema = object({
46147
+ instanceId: InstanceIdSchema$1,
46148
+ entity: ExportEntityRefSchema
46149
+ });
46150
+ var UnexposeEntityInputSchema = object({
46151
+ instanceId: InstanceIdSchema$1,
46152
+ entity: ExportEntityRefSchema
46153
+ });
46154
+ method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
46155
+ kind: "mutation",
46156
+ auth: "admin"
46157
+ }), method(UnexposeInputSchema, _void(), {
46158
+ kind: "mutation",
46159
+ auth: "admin"
46160
+ }), method(SetExposedNameInputSchema, _void(), {
46161
+ kind: "mutation",
46162
+ auth: "admin"
46163
+ }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
46164
+ kind: "mutation",
46165
+ auth: "admin"
46166
+ }), optionalMethod(UnexposeEntityInputSchema, _void(), {
46167
+ kind: "mutation",
46168
+ auth: "admin"
46169
+ });
46170
+ record(string(), string());
46005
46171
  _enum([
46006
46172
  "automation",
46007
46173
  "action",
@@ -53783,144 +53949,6 @@ method(object({
53783
53949
  kind: "mutation",
53784
53950
  auth: "admin"
53785
53951
  });
53786
- /**
53787
- * `device-export` — collection cap for addons that export camstack
53788
- * devices to external ecosystems (HomeAssistant via MQTT discovery,
53789
- * HomeKit/HAP, Alexa Smart Home, …).
53790
- *
53791
- * No `ecosystem` enum — the addon id identifies the export target.
53792
- * Each addon owns its mapping logic in its own settings UI. The cap
53793
- * exposes only the COMMON surface: link state, supported device
53794
- * kinds (so the UI can filter the device picker), and per-device
53795
- * expose/unexpose.
53796
- *
53797
- * INSTANCES. One export addon can serve several independent targets (a
53798
- * HomeKit bridge, a Home Assistant broker). `listInstances` names them.
53799
- * `instanceId` on a READ is optional: absent = the whole addon (linked if any
53800
- * enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
53801
- * default instance's); present = that instance; an id the provider does not
53802
- * serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
53803
- * never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
53804
- * change always names the target it changes. A single-instance provider
53805
- * accepts an absent id on a read or its own id, and refuses any other.
53806
- */
53807
- /** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
53808
- var InstanceIdSchema$1 = string().min(1);
53809
- var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
53810
- /** One independent export target of an addon. */
53811
- var ExportInstanceSchema = object({
53812
- instanceId: InstanceIdSchema$1,
53813
- name: string(),
53814
- enabled: boolean(),
53815
- kind: string().optional()
53816
- });
53817
- var LinkStateSchema = _enum([
53818
- "unlinked",
53819
- "linked",
53820
- "error"
53821
- ]);
53822
- /**
53823
- * A single label/value row surfaced in the export panel's "Setup"
53824
- * section. Rendered with a copy-to-clipboard button. `secret: true`
53825
- * rows are masked by default with a reveal toggle (client ids,
53826
- * secrets, …).
53827
- */
53828
- var ExportSetupFieldSchema = object({
53829
- label: string(),
53830
- value: string(),
53831
- /** Mask the value by default + render a reveal toggle (client id, secrets). */
53832
- secret: boolean().optional()
53833
- });
53834
- /**
53835
- * Generic, addon-agnostic pairing/account block. Any export addon can
53836
- * surface a scannable QR, a set of copyable label/value rows, and a
53837
- * free-form operator note — the `DeviceExportPanel` renders whatever
53838
- * the provider supplies and skips the section entirely when `setup`
53839
- * is unset.
53840
- */
53841
- var ExportSetupSchema = object({
53842
- /** A string to render as a scannable QR — HAP `X-HM://…` URI, a pairing URL, etc. Omitted when there's nothing to scan. */
53843
- qr: string().optional(),
53844
- /** Label/value rows shown with a copy button (HAP setup code, OAuth URLs, client id, linked-account count, …). */
53845
- fields: array(ExportSetupFieldSchema).readonly().optional(),
53846
- /** Free-form operator instructions rendered above the fields. */
53847
- note: string().optional()
53848
- });
53849
- var DeviceExportStatusSchema = object({
53850
- linkState: LinkStateSchema,
53851
- exposedDeviceCount: number$1(),
53852
- error: string().optional(),
53853
- /**
53854
- * Optional pairing/account info the panel renders in a generic
53855
- * "Setup" section. Addon-agnostic — the addon id identifies the
53856
- * export target, never an `ecosystem` key here.
53857
- */
53858
- setup: ExportSetupSchema.optional()
53859
- });
53860
- var DeviceKindSchema = string();
53861
- var ExposedDeviceSchema = object({
53862
- deviceId: string(),
53863
- exposedAs: string().optional(),
53864
- capabilities: array(string()).optional()
53865
- });
53866
- var ExposeInputSchema = object({
53867
- instanceId: InstanceIdSchema$1,
53868
- deviceId: string(),
53869
- capabilities: array(string()).optional()
53870
- });
53871
- var UnexposeInputSchema = object({
53872
- instanceId: InstanceIdSchema$1,
53873
- deviceId: string()
53874
- });
53875
- /**
53876
- * What an exporter may expose besides devices (D968). An action and a
53877
- * notification target are LIST ENTRIES, never devices (D954): they enter an
53878
- * exporter's own selection, not the device registry.
53879
- */
53880
- var ExportEntityKindSchema = _enum([
53881
- "device",
53882
- "action",
53883
- "notifier"
53884
- ]);
53885
- var ExportEntityRefSchema = discriminatedUnion("kind", [
53886
- object({
53887
- kind: literal("device"),
53888
- deviceId: string()
53889
- }),
53890
- object({
53891
- kind: literal("action"),
53892
- actionId: string().min(1)
53893
- }),
53894
- object({
53895
- kind: literal("notifier"),
53896
- targetId: string().min(1)
53897
- })
53898
- ]);
53899
- var ExposedEntitySchema = object({
53900
- entity: ExportEntityRefSchema,
53901
- exposedAs: string().optional()
53902
- });
53903
- var ExposeEntityInputSchema = object({
53904
- instanceId: InstanceIdSchema$1,
53905
- entity: ExportEntityRefSchema
53906
- });
53907
- var UnexposeEntityInputSchema = object({
53908
- instanceId: InstanceIdSchema$1,
53909
- entity: ExportEntityRefSchema
53910
- });
53911
- method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
53912
- kind: "mutation",
53913
- auth: "admin"
53914
- }), method(UnexposeInputSchema, _void(), {
53915
- kind: "mutation",
53916
- auth: "admin"
53917
- }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
53918
- kind: "mutation",
53919
- auth: "admin"
53920
- }), optionalMethod(UnexposeEntityInputSchema, _void(), {
53921
- kind: "mutation",
53922
- auth: "admin"
53923
- });
53924
53952
  var DeviceAliasSchema = string().max(64).regex(/^[a-z][a-z0-9_]*$/);
53925
53953
  var DeviceTemplateSchema = string().min(1).max(4096);
53926
53954
  /** One value a caller hands the renderer for `thisDevice`. */
@@ -58775,6 +58803,128 @@ var StateLookSchema = object({
58775
58803
  background: _enum(HERO_BACKGROUNDS),
58776
58804
  animation: HeroAnimationSchema
58777
58805
  });
58806
+ /** A string in both languages, as the cap vocabulary writes it (D920): no `t()` in the model. */
58807
+ var HeroTextSchema = object({
58808
+ en: string(),
58809
+ it: string()
58810
+ });
58811
+ /**
58812
+ * The ONE measurement a simple sensor shows (card wave, phase 1).
58813
+ *
58814
+ * A sensor that measures a single quantity (a temperature, a humidity, a power,
58815
+ * a contact, a text state) is drawn as that reading, once, big, inside a scene
58816
+ * whose motion says what the number means: heat rising when hot, flakes when
58817
+ * cold, a wire flowing at the speed of the power, a ring while a window is
58818
+ * open. Everything the card draws is decided here; a client only paints it.
58819
+ */
58820
+ var HERO_MEASUREMENT_QUANTITIES = [
58821
+ "temperature",
58822
+ "humidity",
58823
+ "power",
58824
+ "contact",
58825
+ "text"
58826
+ ];
58827
+ /** How a temperature reads to a person; drives the heat or the cold scene. */
58828
+ var HERO_MEASUREMENT_BANDS = [
58829
+ "hot",
58830
+ "mild",
58831
+ "cold"
58832
+ ];
58833
+ /** `updated` = the value was last read then ("2 min ago"); `changed` = it has held since then ("for 12 min"). */
58834
+ var HERO_MEASUREMENT_SINCE_KINDS = ["updated", "changed"];
58835
+ var HeroMeasurementReadingSchema = object({
58836
+ value: number$1(),
58837
+ quantity: ReadingQuantitySchema
58838
+ });
58839
+ var HeroMeasurementSchema = object({
58840
+ quantity: _enum(HERO_MEASUREMENT_QUANTITIES),
58841
+ reading: HeroMeasurementReadingSchema.nullable(),
58842
+ icon: IconKeySchema.nullable(),
58843
+ value: HeroTextSchema,
58844
+ unit: string().nullable(),
58845
+ band: _enum(HERO_MEASUREMENT_BANDS).nullable(),
58846
+ level: number$1().min(0).max(1).nullable(),
58847
+ word: HeroTextSchema,
58848
+ tone: _enum(HERO_TONES),
58849
+ since: number$1().nullable(),
58850
+ sinceKind: _enum(HERO_MEASUREMENT_SINCE_KINDS),
58851
+ historyKey: string().nullable()
58852
+ });
58853
+ var same$2 = (text) => t(text, text);
58854
+ var clamp01$1 = (n) => Math.min(1, Math.max(0, n));
58855
+ var trimmed = (n, digits) => String(Number(n.toFixed(digits)));
58856
+ function temperatureBand(celsius) {
58857
+ if (celsius < 12) return "cold";
58858
+ if (celsius > 28) return "hot";
58859
+ return "mild";
58860
+ }
58861
+ var BAND_WORD = {
58862
+ hot: t("Hot", "Caldo"),
58863
+ mild: t("Normal", "Normale"),
58864
+ cold: t("Cold", "Freddo")
58865
+ };
58866
+ var BAND_TONE = {
58867
+ hot: "warning",
58868
+ mild: "muted",
58869
+ cold: "info"
58870
+ };
58871
+ function temperatureMeasurement(celsius, since) {
58872
+ const band = temperatureBand(celsius);
58873
+ return {
58874
+ quantity: "temperature",
58875
+ reading: {
58876
+ value: celsius,
58877
+ quantity: "temperature"
58878
+ },
58879
+ icon: "thermometer",
58880
+ value: same$2(trimmed(celsius, 1)),
58881
+ unit: "°C",
58882
+ band,
58883
+ level: null,
58884
+ word: BAND_WORD[band],
58885
+ tone: BAND_TONE[band],
58886
+ since,
58887
+ sinceKind: "updated",
58888
+ historyKey: "cap:temperature-sensor.temperature"
58889
+ };
58890
+ }
58891
+ function humidityMeasurement(percent, since) {
58892
+ const dry = percent < 30;
58893
+ const humid = percent > 70;
58894
+ return {
58895
+ quantity: "humidity",
58896
+ reading: {
58897
+ value: percent,
58898
+ quantity: "humidity"
58899
+ },
58900
+ icon: "droplets",
58901
+ value: same$2(String(Math.round(percent))),
58902
+ unit: "%",
58903
+ band: null,
58904
+ level: clamp01$1(percent / 100),
58905
+ word: dry ? t("Dry", "Secco") : humid ? t("Humid", "Umido") : t("Normal", "Normale"),
58906
+ tone: dry || humid ? "warning" : "info",
58907
+ since,
58908
+ sinceKind: "updated",
58909
+ historyKey: "cap:humidity-sensor.humidity"
58910
+ };
58911
+ }
58912
+ function textMeasurement(text, since) {
58913
+ return {
58914
+ quantity: "text",
58915
+ reading: null,
58916
+ icon: null,
58917
+ value: same$2(text),
58918
+ unit: null,
58919
+ band: null,
58920
+ level: null,
58921
+ word: t("Changed", "Cambiato"),
58922
+ tone: "muted",
58923
+ since,
58924
+ sinceKind: "updated",
58925
+ historyKey: null
58926
+ };
58927
+ }
58778
58928
  /** The scene as the model carries it. `null` = the device did not say (never a guess). */
58779
58929
  var HeroSceneSchema = object({
58780
58930
  id: _enum([
@@ -58826,11 +58976,6 @@ var HeroSceneSchema = object({
58826
58976
  });
58827
58977
  DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
58828
58978
  DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.SwitchHeater, DeviceRole.SwitchFan, DeviceRole.ClimateAc, DeviceRole.ClimateThermostat, DeviceRole.ClimateFloor, DeviceRole.ClimateRadiator, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
58829
- /** A string in both languages, as the cap vocabulary writes it (D920): no `t()` in the model. */
58830
- var HeroTextSchema = object({
58831
- en: string(),
58832
- it: string()
58833
- });
58834
58979
  var HeroRowIdSchema = _enum([
58835
58980
  "progress",
58836
58981
  "run-for",
@@ -58936,6 +59081,25 @@ var HeroControlSchema = discriminatedUnion("kind", [
58936
59081
  optionLabels: record(string(), HeroTextSchema).optional(),
58937
59082
  call: HeroCallSchema,
58938
59083
  valueArg: string()
59084
+ }),
59085
+ object({
59086
+ kind: literal("number"),
59087
+ id: string(),
59088
+ label: HeroTextSchema,
59089
+ value: number$1().nullable(),
59090
+ /** `null` = any number; the device rounds what it does not accept. */
59091
+ step: number$1().nullable(),
59092
+ unit: string(),
59093
+ call: HeroCallSchema,
59094
+ valueArg: string()
59095
+ }),
59096
+ object({
59097
+ kind: literal("text"),
59098
+ id: string(),
59099
+ label: HeroTextSchema,
59100
+ value: string().nullable(),
59101
+ call: HeroCallSchema,
59102
+ valueArg: string()
58939
59103
  })
58940
59104
  ]);
58941
59105
  /** Declared by its interface: a `z.infer` of one more icon union pushes the cap's inferred type past what the compiler can print. */
@@ -59137,6 +59301,11 @@ var HeroModelSchema = object({
59137
59301
  timedRun: HeroTimedRunSlotSchema.nullable(),
59138
59302
  powerSupply: HeroPowerSupplySlotSchema.nullable(),
59139
59303
  rows: array(HeroRowSchema),
59304
+ /**
59305
+ * The ONE measurement of a simple sensor, drawn once inside the scene in place of
59306
+ * the caption, tiles and value chips. Absent = the card draws as before.
59307
+ */
59308
+ measurement: HeroMeasurementSchema.optional(),
59140
59309
  /** Ids of parts that actuate the device, hidden from a session that may not act on it. */
59141
59310
  gated: array(string()),
59142
59311
  provenance: array(HeroProvenanceSchema)
@@ -59208,7 +59377,13 @@ var HeroRowModelSchema = object({
59208
59377
  * the call. Absent or empty = `primary` alone.
59209
59378
  */
59210
59379
  extras: array(HeroControlSchema).max(2).optional(),
59211
- trailing: HeroFactSchema.nullable()
59380
+ trailing: HeroFactSchema.nullable(),
59381
+ /**
59382
+ * The simple sensor's one reading (the hero's `measurement`): the row draws its value
59383
+ * at the right, the unit small, the quantity's icon in the mini-scene. Absent = an
59384
+ * older server or a device that is not a simple sensor: draw `trailing`.
59385
+ */
59386
+ measurement: HeroMeasurementSchema.optional()
59212
59387
  });
59213
59388
  new Set([
59214
59389
  "temperature",
@@ -77735,10 +77910,15 @@ getStatus: method(object({ deviceId: number$1() }), RecordingSignalStatusSchema)
77735
77910
  */
77736
77911
  history: "none"
77737
77912
  };
77738
- var RemoteBindingSchema = object({ action: AutomationCapStepBaseSchema.omit({
77739
- target: true,
77740
- actionId: true
77741
- }).extend({ deviceId: number$1().int().nonnegative() }) });
77913
+ var RemoteBindingSchema = object({ action: object({
77914
+ kind: literal("cap"),
77915
+ deviceId: number$1().int().nonnegative(),
77916
+ cap: string().min(1),
77917
+ method: string().min(1),
77918
+ args: record(string(), unknown()).optional(),
77919
+ /** The unit the person picked per arg (display only); `args` always hold the cap's own unit. */
77920
+ argUnits: record(string(), string().min(1)).optional()
77921
+ }) });
77742
77922
  var RemoteCustomButtonSchema = object({
77743
77923
  /** The slug after `custom:` in the role. */
77744
77924
  key: string().refine(isRemoteCustomKey, { message: "a lowercase slug" }),
@@ -78728,7 +78908,7 @@ var sceneMonitorCapability = {
78728
78908
  * instead of failing silently weeks later.
78729
78909
  *
78730
78910
  * Reachable from three surfaces on this one mutation: the scene card, a
78731
- * notification button (an `onTrigger` sequence with a `kind:'cap'` step —
78911
+ * notification button (an `onTrigger` sequence with a `kind:'command'` step —
78732
78912
  * no new Notification-Center code at all), and tRPC for scripts.
78733
78913
  */
78734
78914
  resetScene: method(object({
@@ -85721,6 +85901,13 @@ function contribution(cap, schema, build) {
85721
85901
  }
85722
85902
  };
85723
85903
  }
85904
+ /** The fragment, carrying the cap's single measurement when it has one. */
85905
+ function measured(fragment, measurement) {
85906
+ return measurement === void 0 ? fragment : {
85907
+ ...fragment,
85908
+ measurement
85909
+ };
85910
+ }
85724
85911
  function action(id, label, icon, cap, method, args = {}, emphasis = "normal", confirm = null) {
85725
85912
  return {
85726
85913
  id,
@@ -86995,10 +87182,26 @@ var controlContribution = contribution("control", ControlStatusSchema, (slice, c
86995
87182
  };
86996
87183
  });
86997
87184
  var CONTROL_LABEL = t("Value", "Valore");
86998
- /** A number with known bounds is a slider, a choice a picker; text and dates stay read-only. */
87185
+ /**
87186
+ * A number with known bounds is a slider, one without a number field, a choice a picker,
87187
+ * free text a text field; a date stays read-only.
87188
+ */
86999
87189
  function controlRows(slice) {
87000
87190
  if (slice.kind === "numeric" && typeof slice.value === "number") {
87001
- if (slice.min === void 0 || slice.max === void 0) return [];
87191
+ if (slice.min === void 0 || slice.max === void 0) return [row$1("control", "sliders-horizontal", null, [{
87192
+ kind: "number",
87193
+ id: "control.value",
87194
+ label: CONTROL_LABEL,
87195
+ value: slice.value,
87196
+ step: slice.step ?? null,
87197
+ unit: "",
87198
+ call: {
87199
+ cap: "control",
87200
+ method: "setValue",
87201
+ args: { control: { kind: "numeric" } }
87202
+ },
87203
+ valueArg: "control.value"
87204
+ }])];
87002
87205
  return [row$1("control", "sliders-horizontal", null, [{
87003
87206
  kind: "slider",
87004
87207
  id: "control.value",
@@ -87029,6 +87232,18 @@ function controlRows(slice) {
87029
87232
  },
87030
87233
  valueArg: "control.value"
87031
87234
  }])];
87235
+ if (slice.kind === "text") return [row$1("control", "sliders-horizontal", null, [{
87236
+ kind: "text",
87237
+ id: "control.value",
87238
+ label: CONTROL_LABEL,
87239
+ value: typeof slice.value === "string" ? slice.value : null,
87240
+ call: {
87241
+ cap: "control",
87242
+ method: "setValue",
87243
+ args: { control: { kind: "text" } }
87244
+ },
87245
+ valueArg: "control.value"
87246
+ }])];
87032
87247
  return [];
87033
87248
  }
87034
87249
  var SENSORS_FAMILY_CONTRIBUTIONS = {
@@ -87384,15 +87599,15 @@ function sensorFragment(value, id, icon, en, it, text) {
87384
87599
  tiles: { right: [tile$1(en, it, text)] }
87385
87600
  };
87386
87601
  }
87387
- var temperatureContribution = contribution("temperature-sensor", TemperatureSensorStatusSchema, (slice) => sensorFragment(slice?.temperature ?? null, "temperature", "thermometer", "Temperature", "Temperatura", formatDegrees(slice?.temperature ?? 0)));
87388
- var humidityContribution = contribution("humidity-sensor", HumiditySensorStatusSchema, (slice) => sensorFragment(slice?.humidity ?? null, "humidity", "droplets", "Humidity", "Umidità", `${Math.round(slice?.humidity ?? 0)}%`));
87602
+ var temperatureContribution = contribution("temperature-sensor", TemperatureSensorStatusSchema, (slice) => measured(sensorFragment(slice?.temperature ?? null, "temperature", "thermometer", "Temperature", "Temperatura", formatDegrees(slice?.temperature ?? 0)), slice?.temperature == null ? void 0 : temperatureMeasurement(slice.temperature, slice.lastFetchedAt)));
87603
+ var humidityContribution = contribution("humidity-sensor", HumiditySensorStatusSchema, (slice) => measured(sensorFragment(slice?.humidity ?? null, "humidity", "droplets", "Humidity", "Umidità", `${Math.round(slice?.humidity ?? 0)}%`), slice?.humidity == null ? void 0 : humidityMeasurement(slice.humidity, slice.lastFetchedAt)));
87389
87604
  var pressureContribution = contribution("pressure-sensor", PressureSensorStatusSchema, (slice) => sensorFragment(slice?.pressure ?? null, "pressure", "gauge", "Pressure", "Pressione", `${Math.round(slice?.pressure ?? 0)} hPa`));
87390
87605
  var illuminanceContribution = contribution("ambient-light-sensor", AmbientLightSensorStatusSchema, (slice) => sensorFragment(slice?.illuminance ?? null, "illuminance", "sun", "Light", "Luce", `${Math.round(slice?.illuminance ?? 0)} lx`));
87391
87606
  var numericContribution = contribution("numeric-sensor", NumericSensorStatusSchema, (slice) => sensorFragment(slice?.value ?? null, "reading", "gauge", "Value", "Valore", String(Number((slice?.value ?? 0).toFixed(2)))));
87392
87607
  var enumContribution = contribution("enum-sensor", EnumSensorStatusSchema, (slice) => {
87393
87608
  if (slice === void 0 || slice.value === "") return { reading: reading("sensor", "unknown") };
87394
87609
  if (slice.timestamp === true) return { reading: reading("sensor", "on") };
87395
- return sensorFragment(slice.value, "reading", "gauge", "State", "Stato", slice.value);
87610
+ return measured(sensorFragment(slice.value, "reading", "gauge", "State", "Stato", slice.value), textMeasurement(slice.value, slice.lastFetchedAt));
87396
87611
  });
87397
87612
  /** Index bands: the headline of an air-quality sensor is a word, not a number. */
87398
87613
  function airQualityWord(aqi) {
@@ -90288,6 +90503,13 @@ Object.freeze({
90288
90503
  access: "view",
90289
90504
  role: "admin"
90290
90505
  },
90506
+ "deviceExport.setExposedName": {
90507
+ capName: "device-export",
90508
+ capScope: "system",
90509
+ addonId: null,
90510
+ access: "create",
90511
+ role: "admin"
90512
+ },
90291
90513
  "deviceExport.unexposeDevice": {
90292
90514
  capName: "device-export",
90293
90515
  capScope: "system",
@@ -98752,48 +98974,6 @@ Object.freeze({
98752
98974
  role: "admin"
98753
98975
  }
98754
98976
  });
98755
- /**
98756
- * "Do" actions an operator can pick for a device — presentation-free.
98757
- *
98758
- * The wire shape stays `{ cap, method, args }`. Since D920 this module no
98759
- * longer HOLDS a catalogue: {@link DEVICE_DO_ACTIONS} is a projection of the
98760
- * cap vocabulary (`cap-vocabulary/`), the one cap-keyed map of every action
98761
- * and trigger — so a picker built on this module and one built on
98762
- * `actionsForDevice` (the vocabulary resolver) can never disagree about what
98763
- * a device can do. Ids, baked args and the matching of saved steps are the
98764
- * vocabulary's; labels here are its English labels.
98765
- *
98766
- * Shared by the admin NC Sequences / rule Do editor and Automations; the
98767
- * viewer can reuse the same catalogue once its step editor catches up.
98768
- */
98769
- function openArgKind(offer) {
98770
- if (offer.kind === "number") return "number";
98771
- if (offer.kind === "dynamic" && offer.valueKind === "number") return "number";
98772
- if (offer.kind === "enum" && offer.values.every((v) => typeof v.value === "number")) return "number";
98773
- return "string";
98774
- }
98775
- function projectOpenArg(arg) {
98776
- return {
98777
- key: arg.key,
98778
- label: arg.label.en,
98779
- kind: openArgKind(arg.offer),
98780
- offer: arg.offer,
98781
- ...arg.optional === true ? { optional: true } : {}
98782
- };
98783
- }
98784
- function projectAction(cap, action) {
98785
- const open = action.openArgs ?? [];
98786
- return {
98787
- id: action.id,
98788
- label: action.label.en,
98789
- cap,
98790
- method: action.method,
98791
- args: action.args ?? {},
98792
- ...open.length > 0 ? { openArgs: open.map(projectOpenArg) } : {}
98793
- };
98794
- }
98795
- Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
98796
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
98797
98977
  DeviceFeature.BatteryOperated;
98798
98978
  function bound(cap, methods = [], authority = "reported") {
98799
98979
  return {
@@ -102239,6 +102419,15 @@ function systemEventKindsOfFamily(family) {
102239
102419
  return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((g) => g.family === family).flatMap((g) => g.kinds);
102240
102420
  }
102241
102421
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
102422
+ new Set([
102423
+ "alarm.trigger",
102424
+ "alarm.disarm",
102425
+ "alarm.arm.home",
102426
+ "alarm.arm.away",
102427
+ "alarm.arm.night",
102428
+ "alarm.arm.vacation",
102429
+ "alarm.arm.custom_bypass"
102430
+ ]);
102242
102431
  /**
102243
102432
  * WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
102244
102433
  * persists (D640 "a sensor rule fires on the raise, the clear, or both").
@@ -103924,7 +104113,7 @@ function findSpec(id) {
103924
104113
  function capsOfSpec(spec) {
103925
104114
  switch (spec.kind) {
103926
104115
  case "power-meter": return ["power-meter"];
103927
- case "battery": return ["battery", "power-meter"];
104116
+ case "battery": return ["numeric-sensor", "power-meter"];
103928
104117
  case "meter": return ["meter"];
103929
104118
  case "numeric": return ["numeric-sensor"];
103930
104119
  case "text": return ["enum-sensor"];
@@ -104027,11 +104216,9 @@ function computeSlices(spec, snapshot, options, now) {
104027
104216
  const v = numberOf(voltage);
104028
104217
  const a = numberOf(current);
104029
104218
  return {
104030
- battery: {
104031
- level: nullableOf(soc),
104032
- charging: "unknown",
104033
- sleeping: false,
104034
- lastUpdated: now
104219
+ numeric: {
104220
+ value: nullableOf(soc),
104221
+ lastFetchedAt: now
104035
104222
  },
104036
104223
  ...w !== void 0 || v !== void 0 || a !== void 0 ? { powerMeter: {
104037
104224
  ...w !== void 0 ? { power: w * sign || 0 } : {},
@@ -104177,12 +104364,6 @@ var METER_CAP = meterCapability.name;
104177
104364
  var NUMERIC_CAP = numericSensorCapability.name;
104178
104365
  var ENUM_CAP = enumSensorCapability.name;
104179
104366
  var POWER_COLD = { lastFetchedAt: 0 };
104180
- var BATTERY_COLD = {
104181
- level: null,
104182
- charging: "unknown",
104183
- sleeping: false,
104184
- lastUpdated: 0
104185
- };
104186
104367
  var VALUE_COLD = {
104187
104368
  value: null,
104188
104369
  lastFetchedAt: 0
@@ -104230,6 +104411,7 @@ var EnpalSensorDevice = class extends BaseDevice {
104230
104411
  id: `${this.integrationId}:${spec.id}`,
104231
104412
  system: ENPAL_SYSTEM,
104232
104413
  ...spec.kind === "meter" || spec.kind === "numeric" ? { quantity: spec.quantity } : {},
104414
+ ...spec.kind === "battery" ? { quantity: spec.level.quantity } : {},
104233
104415
  ...spec.kind === "text" && spec.timestamp === true ? { dateTimeShape: "date" } : {}
104234
104416
  });
104235
104417
  }
@@ -104279,7 +104461,6 @@ var EnpalSensorDevice = class extends BaseDevice {
104279
104461
  energy: this.energyGuard.accept(kwh)
104280
104462
  });
104281
104463
  }
104282
- if (slices.battery !== void 0) this.runtimeState.setCapState(BATTERY_CAP, slices.battery);
104283
104464
  if (slices.meter !== void 0) {
104284
104465
  const raw = slices.meter.value;
104285
104466
  this.runtimeState.setCapState(METER_CAP, {
@@ -104296,7 +104477,8 @@ var EnpalSensorDevice = class extends BaseDevice {
104296
104477
  this.registerPowerMeter();
104297
104478
  return;
104298
104479
  case "battery":
104299
- this.registerBattery();
104480
+ this.retractBatteryCap();
104481
+ this.registerNumeric();
104300
104482
  this.registerPowerMeter();
104301
104483
  return;
104302
104484
  case "meter":
@@ -104308,8 +104490,7 @@ var EnpalSensorDevice = class extends BaseDevice {
104308
104490
  this.runtimeState.setCapState(ENUM_CAP, TEXT_COLD);
104309
104491
  return;
104310
104492
  case "numeric":
104311
- this.ctx.registerNativeCap(numericSensorCapability, { getStatus: async () => this.runtimeState.getCapState(NUMERIC_CAP) ?? VALUE_COLD });
104312
- this.runtimeState.setCapState(NUMERIC_CAP, VALUE_COLD);
104493
+ this.registerNumeric();
104313
104494
  return;
104314
104495
  }
104315
104496
  }
@@ -104317,15 +104498,18 @@ var EnpalSensorDevice = class extends BaseDevice {
104317
104498
  this.ctx.registerNativeCap(powerMeterCapability, { getStatus: async () => this.runtimeState.getCapState(POWER_CAP) ?? POWER_COLD });
104318
104499
  this.runtimeState.setCapState(POWER_CAP, POWER_COLD);
104319
104500
  }
104320
- registerBattery() {
104321
- this.ctx.registerNativeCap(batteryCapability, {
104322
- getStatus: async () => this.runtimeState.getCapState(BATTERY_CAP) ?? BATTERY_COLD,
104323
- wakeForStream: async () => ({
104324
- awoke: false,
104325
- durationMs: 0
104326
- })
104327
- });
104328
- this.runtimeState.setCapState(BATTERY_CAP, BATTERY_COLD);
104501
+ registerNumeric() {
104502
+ this.ctx.registerNativeCap(numericSensorCapability, { getStatus: async () => this.runtimeState.getCapState(NUMERIC_CAP) ?? VALUE_COLD });
104503
+ this.runtimeState.setCapState(NUMERIC_CAP, VALUE_COLD);
104504
+ }
104505
+ /**
104506
+ * The storage battery once registered the `battery` cap (#522). This process never
104507
+ * registers it, so the call retracts the binding a previous incarnation left on the hub
104508
+ * (`unregisterNativeCap`'s stale-retract path): a restored device stops answering
104509
+ * `battery` and no `battery` slice is written again.
104510
+ */
104511
+ retractBatteryCap() {
104512
+ this.ctx.unregisterNativeCap?.(BATTERY_CAP);
104329
104513
  }
104330
104514
  };
104331
104515
  //#endregion