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