@camstack/addon-waste-collections 0.1.31 → 0.1.32

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.
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { spawn } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
5
- //#region ../types/dist/event-category-CT-hz5Rb.mjs
5
+ //#region ../types/dist/event-category-DmZDPDf_.mjs
6
6
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
7
7
  EventCategory["SystemBoot"] = "system.boot";
8
8
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -629,7 +629,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
629
629
  * (a track was promoted to a stationary-registry entry) or departs (the
630
630
  * object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
631
631
  * the dedicated "Stationary" section — never the live event feed. Payload:
632
- * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
632
+ * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp, observedAt, zones }`.
633
633
  */
634
634
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
635
635
  /**
@@ -767,6 +767,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
767
767
  */
768
768
  EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
769
769
  /**
770
+ * An action run started or ended. Emitted by the `actions` surface.
771
+ * Payload: `ActionRunChanged` (`{ actionId, runId, isRunning, outcome?, lastRunAt, lastError }`).
772
+ */
773
+ EventCategory["ActionsRunChanged"] = "actions.onRunChanged";
774
+ /**
770
775
  * Progress update from a running model conversion job.
771
776
  * Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
772
777
  * Emitted by `addon-model-studio` on the converting node.
@@ -5199,6 +5204,14 @@ function object(shape, params) {
5199
5204
  ...normalizeParams(params)
5200
5205
  });
5201
5206
  }
5207
+ function strictObject(shape, params) {
5208
+ return new ZodObject({
5209
+ type: "object",
5210
+ shape,
5211
+ catchall: never(),
5212
+ ...normalizeParams(params)
5213
+ });
5214
+ }
5202
5215
  function looseObject(shape, params) {
5203
5216
  return new ZodObject({
5204
5217
  type: "object",
@@ -5578,7 +5591,7 @@ var ZodIssueCode = {
5578
5591
  var ZodFirstPartyTypeKind;
5579
5592
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5580
5593
  //#endregion
5581
- //#region ../types/dist/sleep--jXS9hWo.mjs
5594
+ //#region ../types/dist/sleep-6eG9C8Xl.mjs
5582
5595
  /**
5583
5596
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5584
5597
  * window to float samples (D455).
@@ -8819,6 +8832,52 @@ function optionalMethod(input, output, options) {
8819
8832
  providerOptional: true
8820
8833
  };
8821
8834
  }
8835
+ /**
8836
+ * `addon-pages-source` — collection cap exposing per-provider raw page
8837
+ * declarations. Every addon that contributes a UI page registers a
8838
+ * provider here. The hub-side singleton aggregator (`addon-pages` cap,
8839
+ * see `addon-pages.cap.ts`) walks this collection, stamps versioned
8840
+ * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
8841
+ * that admin-ui consumes.
8842
+ *
8843
+ * The split exists because the public listing has a different output
8844
+ * shape than the per-provider raw declarations, and we want both ends
8845
+ * to flow through codegen instead of relying on a hand-written wrapper.
8846
+ */
8847
+ var AddonPageDeclarationSchema = object({
8848
+ id: string(),
8849
+ label: string(),
8850
+ icon: string(),
8851
+ path: string(),
8852
+ /**
8853
+ * Module Federation remote name — must match the `name` field on the
8854
+ * page addon's `federation()` plugin config. Used by admin-ui's
8855
+ * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
8856
+ * Conventionally `addon_<id>_page` (snake_case; MF names cannot
8857
+ * contain hyphens).
8858
+ */
8859
+ remoteName: string(),
8860
+ /**
8861
+ * Bundle filename inside the addon's `dist/` dir served at
8862
+ * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
8863
+ * is always `'remoteEntry.js'`; the value is kept on the metadata so
8864
+ * the static-file route can compute an mtime-based cache-buster URL
8865
+ * without a separate filesystem stat.
8866
+ */
8867
+ bundle: string(),
8868
+ /**
8869
+ * Sidebar section this page docks into. Well-known ids: `'detection'`,
8870
+ * `'cluster'`, `'administration'` — the page renders inside that group.
8871
+ * Any OTHER string creates (or joins) a custom section rendered after
8872
+ * the built-in groups; its label comes from `sectionLabel` (first
8873
+ * declaration wins), falling back to the id. Absent → the legacy
8874
+ * "Addon Pages" group.
8875
+ */
8876
+ section: string().optional(),
8877
+ /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
8878
+ sectionLabel: string().optional()
8879
+ });
8880
+ method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
8822
8881
  var StaticDirOutputSchema$1 = object({ staticDir: string() });
8823
8882
  var VersionOutputSchema$1 = object({ version: string() });
8824
8883
  method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
@@ -8844,6 +8903,27 @@ function customAction(input, output, options) {
8844
8903
  ...options?.caller ? { caller: "required" } : {}
8845
8904
  };
8846
8905
  }
8906
+ /**
8907
+ * The pipeline step-input shape and the run-result bridge, in a module that
8908
+ * imports nothing but zod and types.
8909
+ *
8910
+ * WHY A SEPARATE FILE (addon footprint, D28): both were declared in
8911
+ * `pipeline-executor.cap.ts`, whose import evaluates the whole executor schema
8912
+ * graph. An addon that only needs these two (the benchmark's `runPipelineTest`
8913
+ * action) paid ~130 MB of heap at load. `pipeline-executor.cap.ts` re-exports
8914
+ * them, so every existing import keeps working; addon server code takes them
8915
+ * from `@camstack/types/addon`.
8916
+ */
8917
+ var PipelineStepInputSchema = lazy(() => object({
8918
+ addonId: string(),
8919
+ modelId: string().optional(),
8920
+ enabled: boolean().default(true),
8921
+ children: array(PipelineStepInputSchema).optional(),
8922
+ settings: record(string(), unknown()).optional(),
8923
+ jumpDeviceKey: string().optional()
8924
+ }));
8925
+ /** Typed bridge to the recursive `FrameResult` graph; validation is structural. */
8926
+ var PipelineRunResultBridge = custom();
8847
8927
  var StaticDirOutputSchema = object({ staticDir: string() });
8848
8928
  var VersionOutputSchema = object({ version: string() });
8849
8929
  method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
@@ -8914,6 +8994,8 @@ Object.freeze({ "doorbell": "lastPressedAt" });
8914
8994
  /** Every capability name, so addon code can check a name without the schema graph. */
8915
8995
  var CAP_NAMES = Object.freeze([
8916
8996
  "accessories",
8997
+ "action-source",
8998
+ "actions",
8917
8999
  "addon-pages",
8918
9000
  "addon-pages-source",
8919
9001
  "addon-routes",
@@ -8997,6 +9079,7 @@ var CAP_NAMES = Object.freeze([
8997
9079
  "image-settings",
8998
9080
  "integrations",
8999
9081
  "intercom",
9082
+ "ir-transmitter",
9000
9083
  "keychain",
9001
9084
  "keychain-admin",
9002
9085
  "lawn-mower-control",
@@ -9025,6 +9108,7 @@ var CAP_NAMES = Object.freeze([
9025
9108
  "network-link",
9026
9109
  "network-quality",
9027
9110
  "node-certificates",
9111
+ "node-removal-hook",
9028
9112
  "nodes",
9029
9113
  "notification-output",
9030
9114
  "notification-rules",
@@ -9035,6 +9119,7 @@ var CAP_NAMES = Object.freeze([
9035
9119
  "osd",
9036
9120
  "osd-manager",
9037
9121
  "pet-feeder",
9122
+ "pet-fountain",
9038
9123
  "pipeline-analytics",
9039
9124
  "pipeline-executor",
9040
9125
  "pipeline-orchestrator",
@@ -9055,6 +9140,7 @@ var CAP_NAMES = Object.freeze([
9055
9140
  "recording-onboard",
9056
9141
  "recording-signal",
9057
9142
  "records",
9143
+ "remote",
9058
9144
  "running-state",
9059
9145
  "scene-monitor",
9060
9146
  "script-runner",
@@ -9078,6 +9164,7 @@ var CAP_NAMES = Object.freeze([
9078
9164
  "system",
9079
9165
  "tamper",
9080
9166
  "temperature-sensor",
9167
+ "template",
9081
9168
  "terminal-session",
9082
9169
  "toast",
9083
9170
  "training",
@@ -9102,11 +9189,11 @@ var CAP_NAMES = Object.freeze([
9102
9189
  Object.freeze({
9103
9190
  "air-quality-sensor": [
9104
9191
  {
9105
- "path": "co2Ppm",
9192
+ "path": "co2",
9106
9193
  "valueType": "number"
9107
9194
  },
9108
9195
  {
9109
- "path": "vocPpb",
9196
+ "path": "voc",
9110
9197
  "valueType": "number"
9111
9198
  },
9112
9199
  {
@@ -9156,7 +9243,7 @@ Object.freeze({
9156
9243
  }
9157
9244
  ],
9158
9245
  "ambient-light-sensor": [{
9159
- "path": "lux",
9246
+ "path": "illuminance",
9160
9247
  "valueType": "number"
9161
9248
  }],
9162
9249
  "audio-metrics": [
@@ -9217,7 +9304,7 @@ Object.freeze({
9217
9304
  ],
9218
9305
  "battery": [
9219
9306
  {
9220
- "path": "percentage",
9307
+ "path": "level",
9221
9308
  "valueType": "number"
9222
9309
  },
9223
9310
  {
@@ -9239,7 +9326,7 @@ Object.freeze({
9239
9326
  "valueType": "number"
9240
9327
  },
9241
9328
  {
9242
- "path": "volts",
9329
+ "path": "voltage",
9243
9330
  "valueType": "number"
9244
9331
  },
9245
9332
  {
@@ -9260,7 +9347,7 @@ Object.freeze({
9260
9347
  "valueType": "boolean"
9261
9348
  }],
9262
9349
  "brightness": [{
9263
- "path": "percentage",
9350
+ "path": "level",
9264
9351
  "valueType": "number"
9265
9352
  }],
9266
9353
  "broker": [{
@@ -9461,7 +9548,7 @@ Object.freeze({
9461
9548
  "valueType": "number"
9462
9549
  },
9463
9550
  {
9464
- "path": "mireds",
9551
+ "path": "colorTemperature",
9465
9552
  "valueType": "number"
9466
9553
  }
9467
9554
  ],
@@ -9640,15 +9727,15 @@ Object.freeze({
9640
9727
  "valueType": "string"
9641
9728
  },
9642
9729
  {
9643
- "path": "remainingSeconds",
9730
+ "path": "remaining",
9644
9731
  "valueType": "number"
9645
9732
  },
9646
9733
  {
9647
- "path": "delayedStartSeconds",
9734
+ "path": "delayedStart",
9648
9735
  "valueType": "number"
9649
9736
  },
9650
9737
  {
9651
- "path": "progressPercent",
9738
+ "path": "progress",
9652
9739
  "valueType": "number"
9653
9740
  },
9654
9741
  {
@@ -9762,7 +9849,7 @@ Object.freeze({
9762
9849
  ],
9763
9850
  "fan-control": [
9764
9851
  {
9765
- "path": "percentage",
9852
+ "path": "speed",
9766
9853
  "valueType": "number"
9767
9854
  },
9768
9855
  {
@@ -9862,7 +9949,7 @@ Object.freeze({
9862
9949
  }
9863
9950
  ],
9864
9951
  "humidity-sensor": [{
9865
- "path": "percent",
9952
+ "path": "humidity",
9866
9953
  "valueType": "number"
9867
9954
  }],
9868
9955
  "image": [{
@@ -9972,7 +10059,7 @@ Object.freeze({
9972
10059
  "valueType": "number"
9973
10060
  },
9974
10061
  {
9975
- "path": "progressPercent",
10062
+ "path": "progress",
9976
10063
  "valueType": "number"
9977
10064
  },
9978
10065
  {
@@ -10018,6 +10105,11 @@ Object.freeze({
10018
10105
  "standby"
10019
10106
  ]
10020
10107
  },
10108
+ {
10109
+ "path": "stateSource",
10110
+ "valueType": "enum",
10111
+ "enumValues": ["reported", "assumed"]
10112
+ },
10021
10113
  {
10022
10114
  "path": "volumeLevel",
10023
10115
  "valueType": "number"
@@ -10051,11 +10143,11 @@ Object.freeze({
10051
10143
  "valueType": "string"
10052
10144
  },
10053
10145
  {
10054
- "path": "positionMs",
10146
+ "path": "position",
10055
10147
  "valueType": "number"
10056
10148
  },
10057
10149
  {
10058
- "path": "durationMs",
10150
+ "path": "duration",
10059
10151
  "valueType": "number"
10060
10152
  },
10061
10153
  {
@@ -10157,11 +10249,11 @@ Object.freeze({
10157
10249
  ]
10158
10250
  },
10159
10251
  {
10160
- "path": "signalPercent",
10252
+ "path": "signal",
10161
10253
  "valueType": "number"
10162
10254
  },
10163
10255
  {
10164
- "path": "rssiDbm",
10256
+ "path": "rssi",
10165
10257
  "valueType": "number"
10166
10258
  },
10167
10259
  {
@@ -10216,6 +10308,18 @@ Object.freeze({
10216
10308
  "path": "foodLevel",
10217
10309
  "valueType": "number"
10218
10310
  },
10311
+ {
10312
+ "path": "bowlFood",
10313
+ "valueType": "number"
10314
+ },
10315
+ {
10316
+ "path": "eating",
10317
+ "valueType": "boolean"
10318
+ },
10319
+ {
10320
+ "path": "supportsMarkRefilled",
10321
+ "valueType": "boolean"
10322
+ },
10219
10323
  {
10220
10324
  "path": "food1",
10221
10325
  "valueType": "number"
@@ -10232,6 +10336,11 @@ Object.freeze({
10232
10336
  "path": "batteryPower",
10233
10337
  "valueType": "number"
10234
10338
  },
10339
+ {
10340
+ "path": "powerSource",
10341
+ "valueType": "enum",
10342
+ "enumValues": ["mains", "battery"]
10343
+ },
10235
10344
  {
10236
10345
  "path": "desiccantLeftDays",
10237
10346
  "valueType": "number"
@@ -10276,27 +10385,89 @@ Object.freeze({
10276
10385
  {
10277
10386
  "path": "volume",
10278
10387
  "valueType": "number"
10388
+ },
10389
+ {
10390
+ "path": "lastFeedGrams",
10391
+ "valueType": "number"
10392
+ },
10393
+ {
10394
+ "path": "lastFeedHopper1",
10395
+ "valueType": "number"
10396
+ },
10397
+ {
10398
+ "path": "lastFeedHopper2",
10399
+ "valueType": "number"
10400
+ }
10401
+ ],
10402
+ "pet-fountain": [
10403
+ {
10404
+ "path": "waterLevel",
10405
+ "valueType": "number"
10406
+ },
10407
+ {
10408
+ "path": "waterVolume",
10409
+ "valueType": "number"
10410
+ },
10411
+ {
10412
+ "path": "empty",
10413
+ "valueType": "boolean"
10414
+ },
10415
+ {
10416
+ "path": "pumpRunning",
10417
+ "valueType": "boolean"
10418
+ },
10419
+ {
10420
+ "path": "pumpIntervalEnabled",
10421
+ "valueType": "boolean"
10422
+ },
10423
+ {
10424
+ "path": "pumpOnTime",
10425
+ "valueType": "number"
10426
+ },
10427
+ {
10428
+ "path": "pumpOffTime",
10429
+ "valueType": "number"
10430
+ },
10431
+ {
10432
+ "path": "lastDrink",
10433
+ "valueType": "number"
10434
+ },
10435
+ {
10436
+ "path": "currentHourDrink",
10437
+ "valueType": "number"
10438
+ },
10439
+ {
10440
+ "path": "last24hDrink",
10441
+ "valueType": "number"
10442
+ },
10443
+ {
10444
+ "path": "todayDrink",
10445
+ "valueType": "number"
10446
+ },
10447
+ {
10448
+ "path": "error",
10449
+ "valueType": "string"
10279
10450
  }
10280
10451
  ],
10281
10452
  "power-meter": [
10282
10453
  {
10283
- "path": "watts",
10454
+ "path": "power",
10284
10455
  "valueType": "number"
10285
10456
  },
10286
10457
  {
10287
- "path": "kwhTotal",
10458
+ "path": "energy",
10288
10459
  "valueType": "number"
10289
10460
  },
10290
10461
  {
10291
- "path": "volts",
10462
+ "path": "voltage",
10292
10463
  "valueType": "number"
10293
10464
  },
10294
10465
  {
10295
- "path": "amps",
10466
+ "path": "current",
10296
10467
  "valueType": "number"
10297
10468
  },
10298
10469
  {
10299
- "path": "va",
10470
+ "path": "apparentPower",
10300
10471
  "valueType": "number"
10301
10472
  },
10302
10473
  {
@@ -10324,14 +10495,10 @@ Object.freeze({
10324
10495
  {
10325
10496
  "path": "gps.accuracyMeters",
10326
10497
  "valueType": "number"
10327
- },
10328
- {
10329
- "path": "batteryPercent",
10330
- "valueType": "number"
10331
10498
  }
10332
10499
  ],
10333
10500
  "pressure-sensor": [{
10334
- "path": "hpa",
10501
+ "path": "pressure",
10335
10502
  "valueType": "number"
10336
10503
  }],
10337
10504
  "privacy-mask": [{
@@ -10645,7 +10812,7 @@ Object.freeze({
10645
10812
  "valueType": "boolean"
10646
10813
  }],
10647
10814
  "temperature-sensor": [{
10648
- "path": "celsius",
10815
+ "path": "temperature",
10649
10816
  "valueType": "number"
10650
10817
  }],
10651
10818
  "update": [
@@ -10693,7 +10860,7 @@ Object.freeze({
10693
10860
  "valueType": "string"
10694
10861
  },
10695
10862
  {
10696
- "path": "progressPercent",
10863
+ "path": "progress",
10697
10864
  "valueType": "number"
10698
10865
  },
10699
10866
  {
@@ -10819,7 +10986,7 @@ object({
10819
10986
  params: record(string(), union([string(), number()])).optional()
10820
10987
  });
10821
10988
  //#endregion
10822
- //#region ../types/dist/err-msg-DIorGYeC.mjs
10989
+ //#region ../types/dist/err-msg-DWXWc0Di.mjs
10823
10990
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
10824
10991
  DeviceType["Camera"] = "camera";
10825
10992
  DeviceType["Hub"] = "hub";
@@ -10911,6 +11078,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
10911
11078
  * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
10912
11079
  * integrations sharing the same food/desiccant/hopper surface. */
10913
11080
  DeviceType["PetFeeder"] = "pet-feeder";
11081
+ /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
11082
+ * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
11083
+ * of a container and is BUILT as a composed device (D967). */
11084
+ DeviceType["PetFountain"] = "pet-fountain";
10914
11085
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
10915
11086
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
10916
11087
  * Sources: native Bosch Home Connect, reusable by similar integrations.
@@ -11165,10 +11336,13 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11165
11336
  /** Fallback when the notifier service name doesn't match a known
11166
11337
  * pattern. */
11167
11338
  DeviceRole["GenericNotifier"] = "generic-notifier";
11339
+ /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
11340
+ * cap. A `DeviceType.Generic` device. */
11341
+ DeviceRole["IrTransmitter"] = "ir-transmitter";
11168
11342
  return DeviceRole;
11169
11343
  }({});
11170
11344
  //#endregion
11171
- //#region ../types/dist/composition-Bb8iwHEJ.mjs
11345
+ //#region ../types/dist/composition-kSuOifuG.mjs
11172
11346
  /**
11173
11347
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11174
11348
  *
@@ -12253,12 +12427,28 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
12253
12427
  ])
12254
12428
  })
12255
12429
  ]);
12256
- var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
12430
+ var forwardArgUnion = union([
12431
+ string(),
12432
+ number(),
12433
+ boolean(),
12434
+ strictObject({ ["$input"]: string().min(1) }),
12435
+ record(string().min(1), lazy(() => forwardArgUnion))
12436
+ ]);
12437
+ var CompositionForwardArgSchema = forwardArgUnion;
12438
+ /**
12439
+ * `forward` (D659, amended by D967): one method of a BUILT capability is one
12440
+ * call of `cap.method` on `source`. Without `args` the built method's input
12441
+ * passes through minus `deviceId`; with `args` the template IS the source's
12442
+ * whole input. `deviceId` is always the source's own, never an argument.
12443
+ */
12444
+ var CompositionForwardCommandSchema = object({
12257
12445
  kind: literal("forward"),
12258
12446
  source: CompositionSourceRefSchema,
12259
12447
  cap: string().min(1),
12260
- method: string().min(1)
12261
- }), CompositionCodeSourceSchema]);
12448
+ method: string().min(1),
12449
+ args: record(string().min(1), CompositionForwardArgSchema).optional()
12450
+ });
12451
+ var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
12262
12452
  /** An item key inside an item-array cap (`consumables.items[<key>]`). */
12263
12453
  var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
12264
12454
  /**
@@ -12268,7 +12458,13 @@ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
12268
12458
  */
12269
12459
  var CompositionItemEntrySchema = object({
12270
12460
  label: string().min(1).max(80),
12271
- fields: record(string().min(1), CompositionFieldSourceSchema)
12461
+ fields: record(string().min(1), CompositionFieldSourceSchema),
12462
+ /**
12463
+ * What the cap's `reset` does for THIS item (consumables). Present = the item
12464
+ * is resettable and a `reset` call naming its key forwards here. The item key
12465
+ * selects the command, so it is never an argument: there is no pass-through.
12466
+ */
12467
+ reset: CompositionForwardCommandSchema.optional()
12272
12468
  });
12273
12469
  var CompositionFeatureSchema = discriminatedUnion("kind", [object({
12274
12470
  kind: literal("fields"),
@@ -12280,7 +12476,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
12280
12476
  * owns the WHOLE array field: the composed value holds exactly these items.
12281
12477
  */
12282
12478
  items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
12283
- /** RESERVED for slice 3; refused by the validator when non-empty. */
12479
+ /** Method name → what serves it. Only a BUILT capability takes commands (D967). */
12284
12480
  commands: record(string().min(1), CompositionCommandTargetSchema).optional()
12285
12481
  }), object({
12286
12482
  kind: literal("passthrough"),
@@ -15688,6 +15884,14 @@ var AutomationConditionLeafSchema = object({
15688
15884
  array(union([string(), number()]))
15689
15885
  ])
15690
15886
  });
15887
+ var AutomationConditionStationaryPresentSchema = object({
15888
+ kind: literal("stationary-present"),
15889
+ deviceId: number().int().nonnegative(),
15890
+ /** Class names; a macro class matches every raw class that maps to it. */
15891
+ classes: array(string().min(1)).min(1).optional(),
15892
+ /** The parked object must be in at least one of these zones. */
15893
+ zones: array(string().min(1)).min(1).optional()
15894
+ });
15691
15895
  /**
15692
15896
  * The expression leaf, declared as a plain object rather than an intersection
15693
15897
  * with {@link ExpressionSourceSchema}: a discriminated union has to be able to
@@ -15721,7 +15925,8 @@ var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
15721
15925
  child: AutomationConditionSchema
15722
15926
  }),
15723
15927
  AutomationConditionLeafSchema,
15724
- AutomationConditionExpressionSchema
15928
+ AutomationConditionExpressionSchema,
15929
+ AutomationConditionStationaryPresentSchema
15725
15930
  ]));
15726
15931
  /**
15727
15932
  * What starts a run.
@@ -15762,7 +15967,34 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
15762
15967
  kind: literal("schedule"),
15763
15968
  cron: string().min(1).max(120)
15764
15969
  }),
15765
- object({ kind: literal("manual") })
15970
+ object({ kind: literal("manual") }),
15971
+ object({
15972
+ kind: literal("detection"),
15973
+ /** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
15974
+ * matches every raw class that maps to it; any other name matches
15975
+ * exactly. */
15976
+ classes: array(string().min(1)).min(1),
15977
+ deviceIds: array(number().int().nonnegative()).min(1),
15978
+ /** The track must have visited at least one of these zones. */
15979
+ zones: array(string().min(1)).min(1).optional(),
15980
+ /** Best-so-far confidence the track must reach. */
15981
+ minScore: number().min(0).max(1).optional()
15982
+ }),
15983
+ object({
15984
+ kind: literal("stationary"),
15985
+ deviceIds: array(number().int().nonnegative()).min(1),
15986
+ /** Class names; a macro class matches like the detection trigger. */
15987
+ classes: array(string().min(1)).min(1).optional(),
15988
+ /** The parked object must be in at least one of these zones. */
15989
+ zones: array(string().min(1)).min(1).optional(),
15990
+ /** `appeared`: it became parked. `departed`: the registry retired it.
15991
+ * The vocabulary of `PipelineAnalyticsStationaryChanged`. */
15992
+ phase: _enum(["appeared", "departed"])
15993
+ }),
15994
+ object({
15995
+ kind: literal("interval"),
15996
+ minutes: number().int().min(1).max(1440)
15997
+ })
15766
15998
  ]);
15767
15999
  /**
15768
16000
  * One action step.
@@ -15782,28 +16014,47 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
15782
16014
  * is called. The runner still receives a number and is untouched; the
15783
16015
  * resolution is the recipe's job, not the runner's.
15784
16016
  */
16017
+ var AutomationWaitStepSchema = object({
16018
+ kind: literal("wait"),
16019
+ seconds: number().min(0).max(300)
16020
+ });
16021
+ var AutomationCapStepSchema = object({
16022
+ kind: literal("cap"),
16023
+ /**
16024
+ * One device, several same-family devices (locks / climate family / …),
16025
+ * or `{ $var }` for the device that triggered the run. An array is
16026
+ * fan-out at codegen — each id gets the same cap.method call.
16027
+ */
16028
+ deviceId: union([
16029
+ number().int(),
16030
+ array(number().int()).min(1),
16031
+ object({ $var: string().min(1) })
16032
+ ]),
16033
+ cap: string().min(1),
16034
+ method: string().min(1),
16035
+ /** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
16036
+ * SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
16037
+ * the expression leaf. */
16038
+ args: record(string(), unknown()).optional()
16039
+ });
16040
+ /**
16041
+ * The steps an internal Action may contain: cap and wait, and nothing that can
16042
+ * call back into an Action — so an Action can never reach itself (no cycles).
16043
+ */
16044
+ var ActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCapStepSchema]);
15785
16045
  var AutomationActionSchema = discriminatedUnion("kind", [
16046
+ AutomationWaitStepSchema,
16047
+ AutomationCapStepSchema,
15786
16048
  object({
15787
- kind: literal("wait"),
15788
- seconds: number().min(0).max(300)
15789
- }),
15790
- object({
15791
- kind: literal("cap"),
15792
- /**
15793
- * One device, several same-family devices (locks / climate family / …),
15794
- * or `{ $var }` for the device that triggered the run. An array is
15795
- * fan-out at codegen — each id gets the same cap.method call.
15796
- */
15797
- deviceId: union([
15798
- number().int(),
15799
- array(number().int()).min(1),
15800
- object({ $var: string().min(1) })
15801
- ]),
15802
- cap: string().min(1),
15803
- method: string().min(1),
15804
- /** Values may carry `{{vars.x}}` slots, which SUBSTITUTE and do not
15805
- * evaluate (§3.2.3). Anything beyond substitution is the expression leaf. */
15806
- args: record(string(), unknown()).optional()
16049
+ kind: literal("action"),
16050
+ /** An entry of the `actions` surface (`internal:<uuid>` or `<addonId>:<localId>`). */
16051
+ actionId: string().min(1),
16052
+ /** Values for the action's declared variables; the others take their default. */
16053
+ variables: record(string(), union([
16054
+ string(),
16055
+ number(),
16056
+ boolean()
16057
+ ])).optional()
15807
16058
  }),
15808
16059
  object({
15809
16060
  kind: literal("code"),
@@ -15812,10 +16063,778 @@ var AutomationActionSchema = discriminatedUnion("kind", [
15812
16063
  code: string().min(1).max(2e4)
15813
16064
  })
15814
16065
  ]);
16066
+ /**
16067
+ * How a trigger that arrives while the automation is busy (or cooling down) is
16068
+ * handled. One of the automation's execution modes, never a property of its
16069
+ * actions. Absent means `single`.
16070
+ *
16071
+ * - `single` — drop the trigger while a run is in progress (traced + logged).
16072
+ * - `restart` — abort the run in progress (at its next step boundary), start fresh.
16073
+ * - `queued` — queue up to `max` triggers, drop the rest (traced).
16074
+ * - `parallel` — up to `max` runs at once, drop the rest (traced).
16075
+ * - `cooldown` — `single`, and no new run until `minutes` after the last run
16076
+ * STARTED; persisted across a restart. A manual trigger bypasses only this
16077
+ * wait, never the single/queued limits.
16078
+ */
16079
+ var AutomationRunModeSchema = discriminatedUnion("mode", [
16080
+ object({ mode: literal("single") }),
16081
+ object({ mode: literal("restart") }),
16082
+ object({
16083
+ mode: literal("queued"),
16084
+ max: number().int().min(1).max(10)
16085
+ }),
16086
+ object({
16087
+ mode: literal("parallel"),
16088
+ max: number().int().min(1).max(10)
16089
+ }),
16090
+ object({
16091
+ mode: literal("cooldown"),
16092
+ minutes: number().int().min(1).max(1440)
16093
+ })
16094
+ ]);
15815
16095
  var AutomationRecipeSchema = object({
15816
16096
  triggers: array(AutomationTriggerSchema),
15817
16097
  conditions: AutomationConditionSchema.optional(),
15818
- actions: array(AutomationActionSchema)
16098
+ actions: array(AutomationActionSchema),
16099
+ runMode: AutomationRunModeSchema.optional(),
16100
+ /** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
16101
+ keepTraces: number().int().min(1).max(100).optional()
16102
+ });
16103
+ new Set([
16104
+ {
16105
+ id: "person",
16106
+ name: "Person"
16107
+ },
16108
+ {
16109
+ id: "vehicle",
16110
+ name: "Vehicle"
16111
+ },
16112
+ {
16113
+ id: "animal",
16114
+ name: "Animal"
16115
+ },
16116
+ {
16117
+ id: "package",
16118
+ name: "Package"
16119
+ }
16120
+ ].map((l) => l.id));
16121
+ var COCO_TO_MACRO = {
16122
+ mapping: {
16123
+ person: "person",
16124
+ bicycle: "vehicle",
16125
+ car: "vehicle",
16126
+ motorcycle: "vehicle",
16127
+ airplane: "vehicle",
16128
+ bus: "vehicle",
16129
+ train: "vehicle",
16130
+ truck: "vehicle",
16131
+ boat: "vehicle",
16132
+ bird: "animal",
16133
+ cat: "animal",
16134
+ dog: "animal",
16135
+ horse: "animal",
16136
+ sheep: "animal",
16137
+ cow: "animal",
16138
+ elephant: "animal",
16139
+ bear: "animal",
16140
+ zebra: "animal",
16141
+ giraffe: "animal",
16142
+ suitcase: "package",
16143
+ backpack: "package",
16144
+ handbag: "package"
16145
+ },
16146
+ preserveOriginal: false
16147
+ };
16148
+ new Set(Object.values(COCO_TO_MACRO.mapping));
16149
+ /**
16150
+ * Run traces — what an automation (or, later, an Action run) DID, node by node,
16151
+ * so the editor can paint every block green or red with its reason.
16152
+ *
16153
+ * A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
16154
+ * every condition node (containers and leaves, including the ones a
16155
+ * short-circuit never reached), the run-mode verdict and every action step —
16156
+ * each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
16157
+ * for an Action run). Skipped
16158
+ * evaluations are traced too, and identical consecutive skips collapse into a
16159
+ * repeat count so a trigger that fails every 5 minutes cannot flush real runs.
16160
+ *
16161
+ * {@link AutomationStepTraceSchema} is deliberately free of automation
16162
+ * vocabulary so an Action run records the same step shape.
16163
+ */
16164
+ /** A value a trace may show: JSON primitives and short lists, nothing deeper. */
16165
+ var TraceValueSchema = union([
16166
+ string(),
16167
+ number(),
16168
+ boolean(),
16169
+ _null(),
16170
+ array(union([string(), number()]))
16171
+ ]);
16172
+ var AutomationRunOutcomeSchema = _enum([
16173
+ "ran",
16174
+ "skipped-trigger",
16175
+ "skipped-conditions",
16176
+ "skipped-cooldown",
16177
+ "skipped-overlap",
16178
+ "failed",
16179
+ "aborted"
16180
+ ]);
16181
+ /** `not-evaluated` = a short-circuit never reached the node. */
16182
+ var TraceNodeOutcomeSchema = _enum([
16183
+ "pass",
16184
+ "fail",
16185
+ "not-evaluated"
16186
+ ]);
16187
+ var AutomationTriggerTraceSchema = object({
16188
+ kind: string().min(1),
16189
+ outcome: _enum(["pass", "fail"]),
16190
+ /** Why an event was rejected ("class person not in [dog]") or what fired. */
16191
+ reason: string(),
16192
+ /** deviceId, className, trackId, score … — only what the trigger exposes. */
16193
+ vars: record(string(), TraceValueSchema)
16194
+ });
16195
+ var AutomationConditionTraceNodeSchema = object({
16196
+ /** `conditions` for the root, `conditions.<i>` for its i-th child, … */
16197
+ path: string().min(1),
16198
+ kind: _enum([
16199
+ "all",
16200
+ "any",
16201
+ "not",
16202
+ "condition",
16203
+ "expression",
16204
+ "stationary-present"
16205
+ ]),
16206
+ outcome: TraceNodeOutcomeSchema,
16207
+ reason: string(),
16208
+ deviceId: number().int().optional(),
16209
+ cap: string().optional(),
16210
+ fieldPath: string().optional(),
16211
+ operator: string().optional(),
16212
+ expected: TraceValueSchema.optional(),
16213
+ /** What the mirror held; `null` when it held nothing (unknown). */
16214
+ observed: TraceValueSchema.optional()
16215
+ });
16216
+ var AutomationRunVerdictSchema = object({
16217
+ mode: AutomationRunModeSchema,
16218
+ decision: _enum([
16219
+ "run",
16220
+ "queued",
16221
+ "restart",
16222
+ "drop",
16223
+ "cooldown",
16224
+ "bypass"
16225
+ ]),
16226
+ reason: string()
16227
+ });
16228
+ /** One executed (or not-run) step — shared by automations and Action runs. */
16229
+ var AutomationStepTraceSchema = object({
16230
+ /** `actions.<i>` */
16231
+ path: string().min(1),
16232
+ kind: string().min(1),
16233
+ cap: string().optional(),
16234
+ method: string().optional(),
16235
+ deviceId: number().int().optional(),
16236
+ startedAt: number().optional(),
16237
+ endedAt: number().optional(),
16238
+ outcome: _enum([
16239
+ "ok",
16240
+ "error",
16241
+ "not-run"
16242
+ ]),
16243
+ message: string().optional()
16244
+ });
16245
+ var AutomationRunTraceSchema = object({
16246
+ runId: string().min(1),
16247
+ startedAt: number(),
16248
+ endedAt: number(),
16249
+ trigger: AutomationTriggerTraceSchema,
16250
+ conditions: array(AutomationConditionTraceNodeSchema),
16251
+ verdict: AutomationRunVerdictSchema.optional(),
16252
+ steps: array(AutomationStepTraceSchema),
16253
+ outcome: AutomationRunOutcomeSchema,
16254
+ /** How many identical consecutive skips this trace stands for (>= 1). */
16255
+ repeat: number().int().min(1),
16256
+ /** When the latest of those repeats happened. */
16257
+ lastAt: number()
16258
+ });
16259
+ /**
16260
+ * What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
16261
+ * `action:<actionId>`): the cooldown stamp and the trace ring.
16262
+ */
16263
+ var AutomationStateDocSchema = object({
16264
+ version: literal(1),
16265
+ lastRunStartedAt: number().nullable(),
16266
+ traces: array(AutomationRunTraceSchema)
16267
+ });
16268
+ /**
16269
+ * Actions — named, runnable things an operator (or an automation) can start,
16270
+ * declared as LIST ENTRIES, never as devices.
16271
+ *
16272
+ * An entry is either **internal** (a short list of cap/wait steps kept in the
16273
+ * hub's `actions` record kind) or declared by an addon through the
16274
+ * `action-source` collection (a Home Assistant script, a scene …). The one
16275
+ * surface the UI and automations talk to is the `actions` cap; it keeps no
16276
+ * catalog of its own (D3) — every `list` asks the providers live.
16277
+ *
16278
+ * This file is the data half: the entry schema, the variables an entry may
16279
+ * declare, and the pure resolution of the values a run was given. It holds no
16280
+ * runtime.
16281
+ */
16282
+ /** What a variable's value is. `select` takes one of `options`. */
16283
+ var ActionVariableTypeSchema = _enum([
16284
+ "string",
16285
+ "number",
16286
+ "boolean",
16287
+ "select"
16288
+ ]);
16289
+ var ACTION_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
16290
+ var ActionVariableValueSchema = union([
16291
+ string(),
16292
+ number(),
16293
+ boolean()
16294
+ ]);
16295
+ /**
16296
+ * One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
16297
+ * value) or `{{vars.name}}` (inside a string) in their args.
16298
+ */
16299
+ var ActionVariableSchema = object({
16300
+ name: string().regex(ACTION_VARIABLE_NAME_RE),
16301
+ label: string().min(1).max(120).optional(),
16302
+ type: ActionVariableTypeSchema,
16303
+ required: boolean().optional(),
16304
+ default: ActionVariableValueSchema.optional(),
16305
+ /** The allowed values of a `select`. */
16306
+ options: array(string().min(1)).min(1).optional()
16307
+ }).superRefine((v, ctx) => {
16308
+ if (v.type === "select" && v.options === void 0) ctx.addIssue({
16309
+ code: "custom",
16310
+ message: "a select variable needs options",
16311
+ path: ["options"]
16312
+ });
16313
+ if (v.default !== void 0 && variableValueError(v, v.default) !== null) ctx.addIssue({
16314
+ code: "custom",
16315
+ message: variableValueError(v, v.default) ?? "invalid default",
16316
+ path: ["default"]
16317
+ });
16318
+ });
16319
+ /** Why `value` is not a legal value for the variable, or null when it is. */
16320
+ function variableValueError(variable, value) {
16321
+ switch (variable.type) {
16322
+ case "string": return typeof value === "string" ? null : `variable "${variable.name}" must be a string`;
16323
+ case "number": return typeof value === "number" && Number.isFinite(value) ? null : `variable "${variable.name}" must be a number`;
16324
+ case "boolean": return typeof value === "boolean" ? null : `variable "${variable.name}" must be true or false`;
16325
+ case "select": return typeof value === "string" && (variable.options ?? []).includes(value) ? null : `variable "${variable.name}" must be one of ${(variable.options ?? []).join(", ")}`;
16326
+ }
16327
+ }
16328
+ var ActionSourceKindSchema = _enum(["internal", "addon"]);
16329
+ function isInternalActionId(actionId) {
16330
+ return actionId.startsWith("internal:") && actionId.length > 9;
16331
+ }
16332
+ /** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
16333
+ function addonIdOfActionId(actionId) {
16334
+ if (isInternalActionId(actionId)) return null;
16335
+ const at = actionId.indexOf(":");
16336
+ return at > 0 && at < actionId.length - 1 ? actionId.slice(0, at) : null;
16337
+ }
16338
+ var ActionStatusSchema = object({
16339
+ isRunning: boolean(),
16340
+ /** Ms epoch the latest run STARTED; null when it never ran. */
16341
+ lastRunAt: number().nullable(),
16342
+ /** The latest run's failure; null on success or when it never ran. */
16343
+ lastError: string().nullable()
16344
+ });
16345
+ var ActionEntrySchema = object({
16346
+ /** `internal:<uuid>` or `<addonId>:<localId>`. */
16347
+ id: string().min(3),
16348
+ name: string().min(1).max(120),
16349
+ /** The addon that declared it; `core-blocks` for an internal one. */
16350
+ addonId: string().min(1),
16351
+ source: ActionSourceKindSchema,
16352
+ /** An addon-declared entry is edited where it was declared. */
16353
+ readOnly: boolean(),
16354
+ /** Internal only: cap and wait steps. No action-in-action, so no cycles. */
16355
+ steps: array(ActionStepSchema).min(1).max(50).optional(),
16356
+ variables: array(ActionVariableSchema).max(20).optional(),
16357
+ status: ActionStatusSchema
16358
+ }).superRefine((entry, ctx) => {
16359
+ if (entry.source === "internal") {
16360
+ if (!isInternalActionId(entry.id)) ctx.addIssue({
16361
+ code: "custom",
16362
+ message: "an internal action id is internal:<uuid>",
16363
+ path: ["id"]
16364
+ });
16365
+ return;
16366
+ }
16367
+ if (entry.steps !== void 0) ctx.addIssue({
16368
+ code: "custom",
16369
+ message: "only an internal action carries steps",
16370
+ path: ["steps"]
16371
+ });
16372
+ if (!entry.readOnly) ctx.addIssue({
16373
+ code: "custom",
16374
+ message: "an addon action is read-only here",
16375
+ path: ["readOnly"]
16376
+ });
16377
+ if (addonIdOfActionId(entry.id) === null) ctx.addIssue({
16378
+ code: "custom",
16379
+ message: "an addon action id is <addonId>:<localId>",
16380
+ path: ["id"]
16381
+ });
16382
+ });
16383
+ /** What an operator writes for an internal action. */
16384
+ var ActionInputSchema = object({
16385
+ name: string().min(1).max(120),
16386
+ steps: array(ActionStepSchema).min(1).max(50),
16387
+ variables: array(ActionVariableSchema).max(20).optional()
16388
+ });
16389
+ /** Who started a run. Absent = the operator pressed it. */
16390
+ var ActionRunOriginSchema = object({
16391
+ /** The automation block whose step started it. */
16392
+ blockId: string().min(1).optional() });
16393
+ /** `started` = the caller did not ask to wait. */
16394
+ var ActionRunOutcomeSchema = union([AutomationRunOutcomeSchema, literal("started")]);
16395
+ var ActionRunResultSchema = object({
16396
+ runId: string().min(1),
16397
+ outcome: ActionRunOutcomeSchema,
16398
+ /** Why it did not run, or what failed. */
16399
+ message: string().optional()
16400
+ });
16401
+ _enum([
16402
+ "not-found",
16403
+ "read-only",
16404
+ "invalid-variables",
16405
+ "invalid-steps",
16406
+ "not-linked",
16407
+ "busy"
16408
+ ]);
16409
+ object({
16410
+ actionId: string(),
16411
+ runId: string(),
16412
+ isRunning: boolean(),
16413
+ /** Set when the run ended. */
16414
+ outcome: AutomationRunOutcomeSchema.optional(),
16415
+ lastRunAt: number().nullable(),
16416
+ lastError: string().nullable()
16417
+ });
16418
+ /**
16419
+ * notification-output — canonical, capability-gated notification delivery.
16420
+ *
16421
+ * Apprise-derived model (see
16422
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
16423
+ * callers emit ONE canonical `Notification`; each provider declares a
16424
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
16425
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
16426
+ * message to what the kind supports — callers never special-case a service.
16427
+ *
16428
+ * DESIGN DECISIONS (locked):
16429
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
16430
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
16431
+ * cap. Rationale: the admin UI needs one uniform surface across the
16432
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
16433
+ * alternative would fork the UI per addon and cannot host the
16434
+ * discovery→adopt flow.
16435
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
16436
+ * the generated cap-mount auto-`concatCollection`-fans them across every
16437
+ * registered provider (notifiers addon + HA addon) so one catalog is
16438
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
16439
+ * `addonId` the generated collection router extracts from the call input.
16440
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
16441
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
16442
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
16443
+ * base64 fallback needed.
16444
+ *
16445
+ * TODO (deferred, closed-set change — separate decision): add
16446
+ * `providerKind: 'notify'` so notification providers surface on the unified
16447
+ * admin "Integrations" page.
16448
+ */
16449
+ /**
16450
+ * Zentik-derived typed-media enum — the superset across every kind. Each
16451
+ * adapter picks what it supports and the degrade engine filters the rest.
16452
+ */
16453
+ var AttachmentMediaTypeSchema = _enum([
16454
+ "image",
16455
+ "video",
16456
+ "gif",
16457
+ "audio",
16458
+ "icon"
16459
+ ]);
16460
+ /**
16461
+ * A single attachment. Exactly one of `url` (remote source, most adapters
16462
+ * prefer this) or `bytes` (inline source; required for Pushover-style
16463
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
16464
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
16465
+ */
16466
+ var AttachmentSchema = object({
16467
+ mediaType: AttachmentMediaTypeSchema,
16468
+ url: string().optional(),
16469
+ bytes: _instanceof(Uint8Array).optional(),
16470
+ mime: string().optional(),
16471
+ name: string().optional()
16472
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
16473
+ var NotificationFormatSchema = _enum([
16474
+ "text",
16475
+ "markdown",
16476
+ "html"
16477
+ ]);
16478
+ /**
16479
+ * The CLOSED icon vocabulary an action button may use.
16480
+ *
16481
+ * A closed set, not a free string, and that is the whole point: an arbitrary
16482
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
16483
+ * notices — the same class of gap as a zone vocabulary nothing produced
16484
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
16485
+ * declares `actionIcons: false` and the degrade engine strips the field.
16486
+ *
16487
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
16488
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
16489
+ * differently.
16490
+ *
16491
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
16492
+ *
16493
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
16494
+ * the whole set onto its own renderer's vocabulary through a
16495
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
16496
+ * fallback, so adding a member here fails every adapter's build until someone
16497
+ * decides its glyph, which is the only place that decision can be made
16498
+ * honestly.
16499
+ *
16500
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
16501
+ * `disarm` straight through; iOS feeds that string to
16502
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
16503
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
16504
+ * mapping, and "the field is documented" is not "the value renders".
16505
+ *
16506
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
16507
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
16508
+ * so an addon that emits a token the running hub does not know does not lose an
16509
+ * icon — its whole `send` fails Zod validation and the notification never
16510
+ * arrives. Never emit a new token from an addon before the train carrying it.
16511
+ */
16512
+ var NotificationActionIconSchema = _enum([
16513
+ "acknowledge",
16514
+ "dismiss",
16515
+ "silence",
16516
+ "snooze",
16517
+ "view",
16518
+ "play",
16519
+ "open",
16520
+ "close",
16521
+ "lock",
16522
+ "unlock",
16523
+ "arm",
16524
+ "arm-home",
16525
+ "arm-away",
16526
+ "arm-night",
16527
+ "disarm",
16528
+ "light",
16529
+ "alert",
16530
+ "camera"
16531
+ ]);
16532
+ /** A single tap-through action button. */
16533
+ var NotificationActionSchema = object({
16534
+ id: string(),
16535
+ label: string(),
16536
+ url: string().optional(),
16537
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
16538
+ icon: NotificationActionIconSchema.optional(),
16539
+ /**
16540
+ * Renders in a warning style where the notifier supports it.
16541
+ *
16542
+ * A HINT, never a gate. The callback's authority is its token and nothing
16543
+ * else — see `notification-center/action-token.ts` for what that does and
16544
+ * does not buy.
16545
+ */
16546
+ destructive: boolean().optional(),
16547
+ /**
16548
+ * How the tap should REACH the url.
16549
+ *
16550
+ * `navigate` (absent, and every button authored before this field) opens it:
16551
+ * the phone leaves the notification and shows whatever the callback returns.
16552
+ * That is right for a button whose answer the operator wants to read.
16553
+ *
16554
+ * `background` fires it as a POST and stays put. It exists for the buttons
16555
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
16556
+ * an answer to the notification, and being thrown into a browser tab to
16557
+ * confirm it costs more attention than the notification did. A backend that
16558
+ * cannot do a background call renders it as an ordinary link (the adapters
16559
+ * fall back rather than dropping the button), so this is a preference, never
16560
+ * a requirement.
16561
+ */
16562
+ mode: _enum(["navigate", "background"]).optional()
16563
+ });
16564
+ /** A delivery parameter value: what a notifier declared it takes. */
16565
+ var DeliveryParamValueSchema = union([
16566
+ string(),
16567
+ number(),
16568
+ boolean()
16569
+ ]);
16570
+ /**
16571
+ * One extra delivery parameter a notifier KIND declares (D938). A rule that
16572
+ * addresses a target of that kind may set it; a kind that declares nothing is
16573
+ * never handed one. The admin UI derives its control from this descriptor —
16574
+ * no editor knows a service by name.
16575
+ *
16576
+ * Optional fields are `.optional()`, never `.default()` (a Zod default does not
16577
+ * run on the addon cap path).
16578
+ */
16579
+ var DeliveryParamSchema = object({
16580
+ key: string().min(1),
16581
+ label: string(),
16582
+ /** i18n key of the label, resolved by the declaring addon's namespace. */
16583
+ labelKey: string().optional(),
16584
+ type: _enum([
16585
+ "enum",
16586
+ "number",
16587
+ "boolean",
16588
+ "string"
16589
+ ]),
16590
+ /** `enum` only: the closed set of accepted values. */
16591
+ options: array(object({
16592
+ value: string(),
16593
+ label: string()
16594
+ })).optional(),
16595
+ /** `number` only. */
16596
+ min: number().optional(),
16597
+ max: number().optional(),
16598
+ /** What the notifier does when the rule sets nothing; shown as the placeholder. */
16599
+ default: DeliveryParamValueSchema.optional()
16600
+ });
16601
+ /**
16602
+ * The canonical notification. `body` is the only hard field (Apprise model).
16603
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
16604
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
16605
+ * the adapter maps this ordinal onto its native level. `level?` is an
16606
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
16607
+ * `priority` for that one target.
16608
+ */
16609
+ var NotificationSchema = object({
16610
+ body: string(),
16611
+ title: string().optional(),
16612
+ format: NotificationFormatSchema.default("text"),
16613
+ priority: number().int().min(1).max(5).default(3),
16614
+ level: string().optional(),
16615
+ attachments: array(AttachmentSchema).optional(),
16616
+ clickUrl: string().optional(),
16617
+ actions: array(NotificationActionSchema).optional(),
16618
+ sound: string().optional(),
16619
+ ttl: number().optional(),
16620
+ tag: string().optional(),
16621
+ deviceId: number().optional(),
16622
+ eventId: string().optional(),
16623
+ metadata: record(string(), unknown()).optional(),
16624
+ /**
16625
+ * Values for the parameters the target's kind DECLARES in
16626
+ * {@link TargetKindSchema} `deliveryParams`. Already validated against the
16627
+ * declaration by the sender; a notifier reads only the keys it declared.
16628
+ */
16629
+ delivery: record(string(), DeliveryParamValueSchema).optional()
16630
+ });
16631
+ /** One declared native severity/priority level for a kind. */
16632
+ var TargetKindLevelSchema = object({
16633
+ id: string(),
16634
+ label: string(),
16635
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
16636
+ ordinal: number().int().min(1).max(5).nullable(),
16637
+ flags: object({
16638
+ critical: boolean().optional(),
16639
+ silent: boolean().optional(),
16640
+ noPush: boolean().optional()
16641
+ }).optional(),
16642
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
16643
+ requires: array(string()).optional(),
16644
+ description: string().optional()
16645
+ });
16646
+ /** The full capability block consulted before dispatch. */
16647
+ var TargetKindCapsSchema = object({
16648
+ attachments: object({
16649
+ mediaTypes: array(AttachmentMediaTypeSchema),
16650
+ mode: _enum([
16651
+ "url",
16652
+ "bytes",
16653
+ "both"
16654
+ ]),
16655
+ max: number().int().nonnegative(),
16656
+ maxBytes: number().int().positive().optional()
16657
+ }),
16658
+ /** Max action buttons (0 = none). */
16659
+ actions: number().int().nonnegative(),
16660
+ /**
16661
+ * Whether this kind renders a per-action ICON.
16662
+ *
16663
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
16664
+ * run on the addon cap path — three production failures in one day taught
16665
+ * this repo that once. Absent is read as false by the degrade engine, which
16666
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
16667
+ * assumed and dropped costs the operator's trust in the field.
16668
+ */
16669
+ actionIcons: boolean().optional(),
16670
+ levels: array(TargetKindLevelSchema),
16671
+ format: array(NotificationFormatSchema),
16672
+ clickUrl: boolean(),
16673
+ sound: boolean(),
16674
+ ttl: boolean(),
16675
+ bodyMaxLen: number().int().positive()
16676
+ });
16677
+ /**
16678
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
16679
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
16680
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
16681
+ * the union is large and not meant for runtime validation here; the exported
16682
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
16683
+ */
16684
+ var ConfigSchemaPassthrough$3 = unknown();
16685
+ var TargetKindSchema = object({
16686
+ kind: string(),
16687
+ label: string(),
16688
+ icon: string(),
16689
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
16690
+ addonId: string(),
16691
+ /**
16692
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
16693
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
16694
+ * when the addon bundles no icon for that kind — the client then falls back
16695
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
16696
+ *
16697
+ * Root-relative on purpose: it resolves against whatever origin serves a web
16698
+ * client, and a native client joins it onto its own hub base.
16699
+ *
16700
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
16701
+ * field that survived only because the runtime cap-router forwards provider
16702
+ * output verbatim — so every consumer had to re-declare it by hand to stop
16703
+ * its own Zod parse from stripping it, and the whole arrangement would have
16704
+ * broken silently the moment output validation was tightened anywhere.
16705
+ */
16706
+ iconUrl: string().optional(),
16707
+ /**
16708
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
16709
+ *
16710
+ * The server knows this and therefore says it, because the client cannot
16711
+ * safely guess: a React-Native client renders SVG and raster through two
16712
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
16713
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
16714
+ * placeholder glyph for every vector icon while the web build looked fine.
16715
+ *
16716
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
16717
+ * not been updated — a client that cannot determine the type should prefer
16718
+ * its raster path, which is the safe default for an unknown image.
16719
+ */
16720
+ iconMediaType: string().optional(),
16721
+ configSchema: ConfigSchemaPassthrough$3,
16722
+ /**
16723
+ * The config keys that decide WHERE this kind's secrets are sent (server,
16724
+ * base URL, instance, chat, recipient). A save that changes any of them
16725
+ * while leaving a secret redacted is refused by the provider (D634), so an
16726
+ * editor uses this to ask for the secret on exactly those edits instead of
16727
+ * on every edit.
16728
+ *
16729
+ * Optional: absent from a provider that holds no secret (the Home Assistant
16730
+ * kinds) and from a provider older than this field — an editor that finds
16731
+ * it absent must assume ANY edit may need the secret.
16732
+ */
16733
+ destinationKeys: array(string()).optional(),
16734
+ supportsDiscovery: boolean(),
16735
+ /**
16736
+ * Extra delivery parameters a rule may set for a target of this kind (D938).
16737
+ * Absent = none; the rule editor then offers nothing.
16738
+ */
16739
+ deliveryParams: array(DeliveryParamSchema).optional(),
16740
+ caps: TargetKindCapsSchema
16741
+ });
16742
+ /**
16743
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
16744
+ * (return a presence marker only) when serving `listTargets` — never
16745
+ * round-trip a stored secret to the UI.
16746
+ */
16747
+ var TargetSchema = object({
16748
+ id: string(),
16749
+ name: string(),
16750
+ kind: string(),
16751
+ addonId: string(),
16752
+ enabled: boolean(),
16753
+ config: record(string(), unknown())
16754
+ });
16755
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
16756
+ var DiscoveredTargetSchema = object({
16757
+ kind: string(),
16758
+ suggestedName: string(),
16759
+ config: record(string(), unknown())
16760
+ });
16761
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
16762
+ var RenderedAsSchema = object({
16763
+ level: string(),
16764
+ format: NotificationFormatSchema,
16765
+ attachmentsSent: number().int().nonnegative(),
16766
+ actionsSent: number().int().nonnegative(),
16767
+ truncated: boolean(),
16768
+ dropped: array(string())
16769
+ });
16770
+ var SendResultSchema = object({
16771
+ success: boolean(),
16772
+ error: string().optional(),
16773
+ renderedAs: RenderedAsSchema.optional()
16774
+ });
16775
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
16776
+ var TestResultSchema = SendResultSchema;
16777
+ /**
16778
+ * Pre-save credential probe against a partial (unsaved) target config.
16779
+ * `resolvedUrl` never includes secrets (token stripped from path).
16780
+ */
16781
+ var ProbeTargetConfigResultSchema = object({
16782
+ ok: boolean(),
16783
+ resolvedUrl: string(),
16784
+ httpStatus: number().int().optional(),
16785
+ stateInstance: string().optional(),
16786
+ error: string().optional()
16787
+ });
16788
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
16789
+ kind: string(),
16790
+ config: record(string(), unknown()).optional(),
16791
+ /**
16792
+ * When editing / duplicating, the blank password field never round-trips
16793
+ * the stored token. Pass the source target id so the provider can restore
16794
+ * secrets server-side before dialling (same merge as upsert).
16795
+ */
16796
+ targetId: string().optional()
16797
+ }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
16798
+ kind: string(),
16799
+ config: record(string(), unknown()),
16800
+ /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
16801
+ targetId: string().optional()
16802
+ }), ProbeTargetConfigResultSchema, {
16803
+ kind: "mutation",
16804
+ auth: "admin"
16805
+ }), method(object({
16806
+ targetId: string(),
16807
+ notification: NotificationSchema
16808
+ }), SendResultSchema, {
16809
+ kind: "mutation",
16810
+ auth: "admin"
16811
+ }), method(object({
16812
+ targetId: string(),
16813
+ sample: NotificationSchema.optional()
16814
+ }), TestResultSchema, {
16815
+ kind: "mutation",
16816
+ auth: "admin"
16817
+ }), method(object({
16818
+ target: TargetSchema,
16819
+ /**
16820
+ * Duplicate flow: create a NEW target id while copying secrets from an
16821
+ * existing row. The UI never holds the stored token; blank / redacted
16822
+ * password fields are restored from this source (D634 still applies if
16823
+ * destinations diverge without a re-typed secret).
16824
+ */
16825
+ cloneSecretsFrom: string().optional()
16826
+ }), TargetSchema, {
16827
+ kind: "mutation",
16828
+ auth: "admin"
16829
+ }), method(object({ targetId: string() }), _void(), {
16830
+ kind: "mutation",
16831
+ auth: "admin"
16832
+ }), method(object({
16833
+ targetId: string(),
16834
+ enabled: boolean()
16835
+ }), _void(), {
16836
+ kind: "mutation",
16837
+ auth: "admin"
15819
16838
  });
15820
16839
  /**
15821
16840
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
@@ -15849,52 +16868,6 @@ var AddonPageInfoSchema = object({
15849
16868
  bundleUrl: string()
15850
16869
  });
15851
16870
  method(_void(), array(AddonPageInfoSchema).readonly());
15852
- /**
15853
- * `addon-pages-source` — collection cap exposing per-provider raw page
15854
- * declarations. Every addon that contributes a UI page registers a
15855
- * provider here. The hub-side singleton aggregator (`addon-pages` cap,
15856
- * see `addon-pages.cap.ts`) walks this collection, stamps versioned
15857
- * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
15858
- * that admin-ui consumes.
15859
- *
15860
- * The split exists because the public listing has a different output
15861
- * shape than the per-provider raw declarations, and we want both ends
15862
- * to flow through codegen instead of relying on a hand-written wrapper.
15863
- */
15864
- var AddonPageDeclarationSchema = object({
15865
- id: string(),
15866
- label: string(),
15867
- icon: string(),
15868
- path: string(),
15869
- /**
15870
- * Module Federation remote name — must match the `name` field on the
15871
- * page addon's `federation()` plugin config. Used by admin-ui's
15872
- * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
15873
- * Conventionally `addon_<id>_page` (snake_case; MF names cannot
15874
- * contain hyphens).
15875
- */
15876
- remoteName: string(),
15877
- /**
15878
- * Bundle filename inside the addon's `dist/` dir served at
15879
- * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
15880
- * is always `'remoteEntry.js'`; the value is kept on the metadata so
15881
- * the static-file route can compute an mtime-based cache-buster URL
15882
- * without a separate filesystem stat.
15883
- */
15884
- bundle: string(),
15885
- /**
15886
- * Sidebar section this page docks into. Well-known ids: `'detection'`,
15887
- * `'cluster'`, `'administration'` — the page renders inside that group.
15888
- * Any OTHER string creates (or joins) a custom section rendered after
15889
- * the built-in groups; its label comes from `sectionLabel` (first
15890
- * declaration wins), falling back to the id. Absent → the legacy
15891
- * "Addon Pages" group.
15892
- */
15893
- section: string().optional(),
15894
- /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
15895
- sectionLabel: string().optional()
15896
- });
15897
- method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
15898
16871
  var AddonHttpRouteSchema = object({
15899
16872
  method: _enum([
15900
16873
  "GET",
@@ -16176,9 +17149,21 @@ var AlertSeveritySchema = _enum([
16176
17149
  "error",
16177
17150
  "critical"
16178
17151
  ]);
17152
+ /**
17153
+ * An action that RUNS something instead of going somewhere: the admin UI calls
17154
+ * the addon's custom action (`addons.custom`) with exactly these fields. The
17155
+ * producer owns the meaning; the UI knows nothing about the addon or the kind.
17156
+ */
17157
+ var AlertActionInvokeSchema = object({
17158
+ addonId: string().min(1),
17159
+ action: string().min(1),
17160
+ input: record(string(), unknown()).optional()
17161
+ });
17162
+ /** An action has `href` (a link) OR `invoke` (a button that runs a custom action). */
16179
17163
  var AlertActionSchema = object({
16180
17164
  label: string(),
16181
17165
  href: string().optional(),
17166
+ invoke: AlertActionInvokeSchema.optional(),
16182
17167
  /**
16183
17168
  * How `href` is followed. Absent / `app`: an admin-ui route (client-side).
16184
17169
  * `server`: a route the HUB serves (an addon HTTP route such as an OAuth
@@ -18396,7 +19381,7 @@ var CompositionEventPlanSchema = object({
18396
19381
  stateField: string(),
18397
19382
  fieldKey: string()
18398
19383
  });
18399
- /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
19384
+ /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.temperature`. */
18400
19385
  var CompositionProblemSchema = object({
18401
19386
  code: _enum([
18402
19387
  "reserved-variant",
@@ -18425,7 +19410,11 @@ var CompositionProblemSchema = object({
18425
19410
  "mixed-quantity",
18426
19411
  "helper-mismatch",
18427
19412
  "member-constraint",
18428
- "retention"
19413
+ "retention",
19414
+ "command-method-unknown",
19415
+ "command-source-unknown",
19416
+ "command-input-unknown",
19417
+ "command-input-missing"
18429
19418
  ]),
18430
19419
  path: string(),
18431
19420
  message: string()
@@ -18464,7 +19453,9 @@ var CompositionFieldRoleSchema = _enum([
18464
19453
  var CompositionFieldItemSchema = object({
18465
19454
  arrayPath: string(),
18466
19455
  key: string(),
18467
- label: string()
19456
+ label: string(),
19457
+ /** True when the item carries a `reset` forward: the composer writes `resettable` from this (D967). */
19458
+ resettable: boolean().optional()
18468
19459
  });
18469
19460
  var CompositionFieldPlanSchema = object({
18470
19461
  cap: string(),
@@ -18669,7 +19660,7 @@ Object.freeze({
18669
19660
  * One catalog row per base reading a group helper can build (D944 §3).
18670
19661
  * Data, not a form default written into the block.
18671
19662
  */
18672
- var NOW$2 = {
19663
+ var NOW$3 = {
18673
19664
  fieldPath: "lastFetchedAt",
18674
19665
  expr: "now"
18675
19666
  };
@@ -18678,12 +19669,13 @@ var CHANGED = {
18678
19669
  expr: "now"
18679
19670
  };
18680
19671
  function numeric(input) {
19672
+ const { alsoFrom, ...base } = input;
18681
19673
  return {
18682
- ...input,
19674
+ ...base,
18683
19675
  family: "numeric",
18684
19676
  deviceType: DeviceType.Sensor,
18685
19677
  memberDeviceType: DeviceType.Sensor,
18686
- memberDeviceTypes: [DeviceType.Sensor],
19678
+ memberDeviceTypes: [DeviceType.Sensor, ...alsoFrom?.deviceTypes ?? []],
18687
19679
  activeWhen: void 0,
18688
19680
  commandCaps: [],
18689
19681
  baseUnit: baseUnitOf(input.quantity),
@@ -18691,9 +19683,10 @@ function numeric(input) {
18691
19683
  accept: [{
18692
19684
  cap: input.outputCap,
18693
19685
  fieldPath: input.outputField
18694
- }]
19686
+ }, ...alsoFrom === void 0 ? [] : [alsoFrom.accept]]
18695
19687
  };
18696
19688
  }
19689
+ var CLIMATE_DEVICES = [DeviceType.Thermostat, DeviceType.Climate];
18697
19690
  function binary(input) {
18698
19691
  return {
18699
19692
  ...input,
@@ -18715,73 +19708,87 @@ var GROUP_TEMPLATES = [
18715
19708
  summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18716
19709
  role: DeviceRole.TemperatureSensor,
18717
19710
  outputCap: "temperature-sensor",
18718
- outputField: "celsius",
19711
+ outputField: "temperature",
18719
19712
  quantity: "temperature",
18720
19713
  defaultAggregate: "mean",
18721
19714
  empty: "null",
18722
- companions: [NOW$2]
19715
+ companions: [NOW$3],
19716
+ alsoFrom: {
19717
+ deviceTypes: CLIMATE_DEVICES,
19718
+ accept: {
19719
+ cap: "climate-control",
19720
+ fieldPath: "currentTemp"
19721
+ }
19722
+ }
18723
19723
  }),
18724
19724
  numeric({
18725
19725
  id: "humidity",
18726
19726
  summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18727
19727
  role: DeviceRole.HumiditySensor,
18728
19728
  outputCap: "humidity-sensor",
18729
- outputField: "percent",
19729
+ outputField: "humidity",
18730
19730
  quantity: "humidity",
18731
19731
  defaultAggregate: "mean",
18732
19732
  empty: "null",
18733
- companions: [NOW$2]
19733
+ companions: [NOW$3],
19734
+ alsoFrom: {
19735
+ deviceTypes: CLIMATE_DEVICES,
19736
+ accept: {
19737
+ cap: "climate-control",
19738
+ fieldPath: "currentHumidity"
19739
+ }
19740
+ }
18734
19741
  }),
18735
19742
  numeric({
18736
19743
  id: "pressure",
18737
19744
  summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18738
19745
  role: DeviceRole.PressureSensor,
18739
19746
  outputCap: "pressure-sensor",
18740
- outputField: "hpa",
19747
+ outputField: "pressure",
18741
19748
  quantity: "pressure",
18742
19749
  defaultAggregate: "mean",
18743
19750
  empty: "null",
18744
- companions: [NOW$2]
19751
+ companions: [NOW$3]
18745
19752
  }),
18746
19753
  numeric({
18747
19754
  id: "illuminance",
18748
19755
  summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18749
19756
  role: DeviceRole.AmbientLightSensor,
18750
19757
  outputCap: "ambient-light-sensor",
18751
- outputField: "lux",
19758
+ outputField: "illuminance",
18752
19759
  quantity: "illuminance",
18753
19760
  defaultAggregate: "mean",
18754
19761
  empty: "null",
18755
- companions: [NOW$2]
19762
+ companions: [NOW$3]
18756
19763
  }),
18757
19764
  numeric({
18758
19765
  id: "power",
18759
19766
  summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18760
19767
  role: DeviceRole.PowerSensor,
18761
19768
  outputCap: "power-meter",
18762
- outputField: "watts",
19769
+ outputField: "power",
18763
19770
  quantity: "power",
18764
19771
  defaultAggregate: "sum",
18765
19772
  empty: "omit",
18766
- companions: [NOW$2]
19773
+ companions: [NOW$3]
18767
19774
  }),
18768
19775
  numeric({
18769
19776
  id: "energy",
18770
19777
  summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18771
19778
  role: DeviceRole.EnergySensor,
18772
19779
  outputCap: "power-meter",
18773
- outputField: "kwhTotal",
19780
+ outputField: "energy",
18774
19781
  quantity: "energy",
18775
19782
  defaultAggregate: "sum",
18776
19783
  empty: "omit",
18777
- companions: [NOW$2]
19784
+ companions: [NOW$3]
18778
19785
  }),
18779
19786
  numeric({
18780
19787
  id: "battery",
18781
19788
  summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18782
19789
  role: DeviceRole.BatterySensor,
18783
19790
  outputCap: "battery",
18784
- outputField: "percentage",
19791
+ outputField: "level",
18785
19792
  quantity: "percentage",
18786
19793
  defaultAggregate: "mean",
18787
19794
  empty: "null",
@@ -18809,7 +19816,7 @@ var GROUP_TEMPLATES = [
18809
19816
  quantity: "device",
18810
19817
  defaultAggregate: "mean",
18811
19818
  empty: "null",
18812
- companions: [NOW$2]
19819
+ companions: [NOW$3]
18813
19820
  }),
18814
19821
  binary({
18815
19822
  id: "motion",
@@ -19359,7 +20366,18 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
19359
20366
  kind: "mutation",
19360
20367
  auth: "admin",
19361
20368
  access: "view"
19362
- });
20369
+ }), method(object({
20370
+ blockId: string().min(1),
20371
+ trace: AutomationRunTraceSchema.optional(),
20372
+ runStartedAt: number().optional(),
20373
+ keepTraces: number().int().min(1).max(100).optional()
20374
+ }), _void(), {
20375
+ kind: "mutation",
20376
+ auth: "admin"
20377
+ }), method(object({ blockId: string().min(1) }), AutomationStateDocSchema, { auth: "admin" }), method(object({ blockId: string().min(1) }), object({
20378
+ traces: array(AutomationRunTraceSchema),
20379
+ lastError: string().nullable()
20380
+ }), { auth: "admin" });
19363
20381
  /**
19364
20382
  * `custom-model-registry` — collection cap exposing operator-registered
19365
20383
  * custom detection models. Each provider (today: `addon-model-studio`)
@@ -21144,12 +22162,48 @@ var ExposeInputSchema = object({
21144
22162
  capabilities: array(string()).optional()
21145
22163
  });
21146
22164
  var UnexposeInputSchema = object({ deviceId: string() });
22165
+ /**
22166
+ * What an exporter may expose besides devices (D968). An action and a
22167
+ * notification target are LIST ENTRIES, never devices (D954): they enter an
22168
+ * exporter's own selection, not the device registry.
22169
+ */
22170
+ var ExportEntityKindSchema = _enum([
22171
+ "device",
22172
+ "action",
22173
+ "notifier"
22174
+ ]);
22175
+ var ExportEntityRefSchema = discriminatedUnion("kind", [
22176
+ object({
22177
+ kind: literal("device"),
22178
+ deviceId: string()
22179
+ }),
22180
+ object({
22181
+ kind: literal("action"),
22182
+ actionId: string().min(1)
22183
+ }),
22184
+ object({
22185
+ kind: literal("notifier"),
22186
+ targetId: string().min(1)
22187
+ })
22188
+ ]);
22189
+ var ExposedEntitySchema = object({
22190
+ entity: ExportEntityRefSchema,
22191
+ exposedAs: string().optional()
22192
+ });
22193
+ var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
22194
+ var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
21147
22195
  method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
21148
22196
  kind: "mutation",
21149
22197
  auth: "admin"
21150
22198
  }), method(UnexposeInputSchema, _void(), {
21151
22199
  kind: "mutation",
21152
22200
  auth: "admin"
22201
+ }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
22202
+ kind: "mutation",
22203
+ auth: "admin"
22204
+ }), optionalMethod(UnexposeEntityInputSchema, _void(), {
22205
+ kind: "mutation",
22206
+ auth: "admin"
21153
22207
  });
21154
22208
  /** Lucide category ids, alphabetical. `ICON_META` indexes into this. */
21155
22209
  var ICON_CATEGORY_IDS = [
@@ -42290,6 +43344,14 @@ method(object({
42290
43344
  kind: "mutation",
42291
43345
  auth: "admin"
42292
43346
  });
43347
+ method(object({
43348
+ nodeId: string().min(1),
43349
+ /** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
43350
+ removedAt: number()
43351
+ }), object({ counts: record(string(), number().int().nonnegative()) }), {
43352
+ kind: "mutation",
43353
+ auth: "admin"
43354
+ });
42293
43355
  /**
42294
43356
  * `device-removal-hook` — the reliable way an addon learns a device was DELETED,
42295
43357
  * so it can delete what it keeps for that device (D865).
@@ -42937,6 +43999,17 @@ var LlmProfileSchema = object({
42937
43999
  * field in its editor only when its descriptor lists it in `profileFields`. */
42938
44000
  topK: number().int().positive().optional(),
42939
44001
  maxTokens: number().int().positive().optional(),
44002
+ /** How hard the model "thinks" before it answers. Omitted = the model's own
44003
+ * default. `off` matters for structured output: a thinking model can spend
44004
+ * the whole completion on reasoning and return an empty `content`. Carried
44005
+ * only by the wires that have it — a kind offers the field in its editor
44006
+ * only when its descriptor lists it in `profileFields`. */
44007
+ reasoningEffort: _enum([
44008
+ "off",
44009
+ "low",
44010
+ "medium",
44011
+ "high"
44012
+ ]).optional(),
42940
44013
  /** Prompt context window. Advisory for cloud kinds (they enforce their own). */
42941
44014
  contextLength: number().int().positive().optional(),
42942
44015
  /** Default system prompt. A caller's `system` REPLACES it (never appends —
@@ -42975,6 +44048,7 @@ var LlmProfileFieldSchema = _enum([
42975
44048
  "topP",
42976
44049
  "topK",
42977
44050
  "maxTokens",
44051
+ "reasoningEffort",
42978
44052
  "contextLength",
42979
44053
  "systemPrompt",
42980
44054
  "timeoutMs",
@@ -42998,7 +44072,7 @@ var LlmProviderWidgetSchema = object({
42998
44072
  /** ConfigUISchema tree passed through untyped on the wire (the
42999
44073
  * notification-output `ConfigSchemaPassthrough` precedent at
43000
44074
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
43001
- var ConfigSchemaPassthrough$3 = unknown();
44075
+ var ConfigSchemaPassthrough$2 = unknown();
43002
44076
  var LlmProfileKindDescriptorSchema = object({
43003
44077
  kind: LlmProfileKindSchema,
43004
44078
  label: string(),
@@ -43007,9 +44081,9 @@ var LlmProfileKindDescriptorSchema = object({
43007
44081
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
43008
44082
  addonId: string(),
43009
44083
  /** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
43010
- connectionSchema: ConfigSchemaPassthrough$3,
44084
+ connectionSchema: ConfigSchemaPassthrough$2,
43011
44085
  /** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
43012
- advancedSchema: ConfigSchemaPassthrough$3.optional(),
44086
+ advancedSchema: ConfigSchemaPassthrough$2.optional(),
43013
44087
  /** The kind's defaults; a model from `listModels` may override any of them. */
43014
44088
  features: LlmModelFeaturesSchema,
43015
44089
  /** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
@@ -43535,10 +44609,10 @@ var CpuBreakdownSchema = object({
43535
44609
  cores: number()
43536
44610
  });
43537
44611
  var MemoryInfoSchema = object({
43538
- percent: number(),
44612
+ percent: number().nullable(),
43539
44613
  totalBytes: number(),
43540
- usedBytes: number(),
43541
- availableBytes: number(),
44614
+ usedBytes: number().nullable(),
44615
+ availableBytes: number().nullable(),
43542
44616
  swapUsedBytes: number(),
43543
44617
  swapTotalBytes: number()
43544
44618
  });
@@ -43842,8 +44916,9 @@ var GetLoadSeriesInputSchema = object({
43842
44916
  });
43843
44917
  var SystemMetricsSchema = object({
43844
44918
  cpuPercent: number(),
43845
- memoryPercent: number(),
43846
- memoryUsedMB: number(),
44919
+ /** `null` when the memory sample failed or was not taken yet (D393). */
44920
+ memoryPercent: number().nullable(),
44921
+ memoryUsedMB: number().nullable(),
43847
44922
  memoryTotalMB: number(),
43848
44923
  diskPercent: number().optional(),
43849
44924
  temperature: number().optional(),
@@ -44079,424 +45154,6 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
44079
45154
  sourcePort: number().optional()
44080
45155
  });
44081
45156
  method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
44082
- /**
44083
- * notification-output — canonical, capability-gated notification delivery.
44084
- *
44085
- * Apprise-derived model (see
44086
- * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
44087
- * callers emit ONE canonical `Notification`; each provider declares a
44088
- * per-kind capability descriptor (`TargetKind`), and the pure degrade
44089
- * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
44090
- * message to what the kind supports — callers never special-case a service.
44091
- *
44092
- * DESIGN DECISIONS (locked):
44093
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
44094
- * `setTargetEnabled`), each provider persisting via the `settings-store`
44095
- * cap. Rationale: the admin UI needs one uniform surface across the
44096
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
44097
- * alternative would fork the UI per addon and cannot host the
44098
- * discovery→adopt flow.
44099
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
44100
- * the generated cap-mount auto-`concatCollection`-fans them across every
44101
- * registered provider (notifiers addon + HA addon) so one catalog is
44102
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
44103
- * `addonId` the generated collection router extracts from the call input.
44104
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
44105
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
44106
- * `storage` / `storage-provider` / `recording` caps over the same path. No
44107
- * base64 fallback needed.
44108
- *
44109
- * TODO (deferred, closed-set change — separate decision): add
44110
- * `providerKind: 'notify'` so notification providers surface on the unified
44111
- * admin "Integrations" page.
44112
- */
44113
- /**
44114
- * Zentik-derived typed-media enum — the superset across every kind. Each
44115
- * adapter picks what it supports and the degrade engine filters the rest.
44116
- */
44117
- var AttachmentMediaTypeSchema = _enum([
44118
- "image",
44119
- "video",
44120
- "gif",
44121
- "audio",
44122
- "icon"
44123
- ]);
44124
- /**
44125
- * A single attachment. Exactly one of `url` (remote source, most adapters
44126
- * prefer this) or `bytes` (inline source; required for Pushover-style
44127
- * bytes-only kinds) MUST be present — the degrade engine expresses a
44128
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
44129
- */
44130
- var AttachmentSchema = object({
44131
- mediaType: AttachmentMediaTypeSchema,
44132
- url: string().optional(),
44133
- bytes: _instanceof(Uint8Array).optional(),
44134
- mime: string().optional(),
44135
- name: string().optional()
44136
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
44137
- var NotificationFormatSchema = _enum([
44138
- "text",
44139
- "markdown",
44140
- "html"
44141
- ]);
44142
- /**
44143
- * The CLOSED icon vocabulary an action button may use.
44144
- *
44145
- * A closed set, not a free string, and that is the whole point: an arbitrary
44146
- * icon name is one that ntfy renders, zentik silently drops, and nobody
44147
- * notices — the same class of gap as a zone vocabulary nothing produced
44148
- * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
44149
- * declares `actionIcons: false` and the degrade engine strips the field.
44150
- *
44151
- * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
44152
- * renderer's icon set; "acknowledge" survives an adapter that draws it
44153
- * differently.
44154
- *
44155
- * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
44156
- *
44157
- * These names are for US. **No adapter may forward one verbatim.** Each maps
44158
- * the whole set onto its own renderer's vocabulary through a
44159
- * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
44160
- * fallback, so adding a member here fails every adapter's build until someone
44161
- * decides its glyph, which is the only place that decision can be made
44162
- * honestly.
44163
- *
44164
- * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
44165
- * `disarm` straight through; iOS feeds that string to
44166
- * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
44167
- * and every snooze and alarm button arrived BLANK. A pass-through is not a
44168
- * mapping, and "the field is documented" is not "the value renders".
44169
- *
44170
- * Adding a member is TRAIN-BOUND. The enum lives in the published
44171
- * `@camstack/server` closure and the cap seam validates against the HUB's copy,
44172
- * so an addon that emits a token the running hub does not know does not lose an
44173
- * icon — its whole `send` fails Zod validation and the notification never
44174
- * arrives. Never emit a new token from an addon before the train carrying it.
44175
- */
44176
- var NotificationActionIconSchema = _enum([
44177
- "acknowledge",
44178
- "dismiss",
44179
- "silence",
44180
- "snooze",
44181
- "view",
44182
- "play",
44183
- "open",
44184
- "close",
44185
- "lock",
44186
- "unlock",
44187
- "arm",
44188
- "arm-home",
44189
- "arm-away",
44190
- "arm-night",
44191
- "disarm",
44192
- "light",
44193
- "alert",
44194
- "camera"
44195
- ]);
44196
- /** A single tap-through action button. */
44197
- var NotificationActionSchema = object({
44198
- id: string(),
44199
- label: string(),
44200
- url: string().optional(),
44201
- /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
44202
- icon: NotificationActionIconSchema.optional(),
44203
- /**
44204
- * Renders in a warning style where the notifier supports it.
44205
- *
44206
- * A HINT, never a gate. The callback's authority is its token and nothing
44207
- * else — see `notification-center/action-token.ts` for what that does and
44208
- * does not buy.
44209
- */
44210
- destructive: boolean().optional(),
44211
- /**
44212
- * How the tap should REACH the url.
44213
- *
44214
- * `navigate` (absent, and every button authored before this field) opens it:
44215
- * the phone leaves the notification and shows whatever the callback returns.
44216
- * That is right for a button whose answer the operator wants to read.
44217
- *
44218
- * `background` fires it as a POST and stays put. It exists for the buttons
44219
- * whose whole point is not to interrupt — "silence this for 30 minutes" is
44220
- * an answer to the notification, and being thrown into a browser tab to
44221
- * confirm it costs more attention than the notification did. A backend that
44222
- * cannot do a background call renders it as an ordinary link (the adapters
44223
- * fall back rather than dropping the button), so this is a preference, never
44224
- * a requirement.
44225
- */
44226
- mode: _enum(["navigate", "background"]).optional()
44227
- });
44228
- /**
44229
- * The canonical notification. `body` is the only hard field (Apprise model).
44230
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
44231
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
44232
- * the adapter maps this ordinal onto its native level. `level?` is an
44233
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
44234
- * `priority` for that one target.
44235
- */
44236
- var NotificationSchema = object({
44237
- body: string(),
44238
- title: string().optional(),
44239
- format: NotificationFormatSchema.default("text"),
44240
- priority: number().int().min(1).max(5).default(3),
44241
- level: string().optional(),
44242
- attachments: array(AttachmentSchema).optional(),
44243
- clickUrl: string().optional(),
44244
- actions: array(NotificationActionSchema).optional(),
44245
- sound: string().optional(),
44246
- ttl: number().optional(),
44247
- tag: string().optional(),
44248
- deviceId: number().optional(),
44249
- eventId: string().optional(),
44250
- metadata: record(string(), unknown()).optional()
44251
- });
44252
- /** One declared native severity/priority level for a kind. */
44253
- var TargetKindLevelSchema = object({
44254
- id: string(),
44255
- label: string(),
44256
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
44257
- ordinal: number().int().min(1).max(5).nullable(),
44258
- flags: object({
44259
- critical: boolean().optional(),
44260
- silent: boolean().optional(),
44261
- noPush: boolean().optional()
44262
- }).optional(),
44263
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
44264
- requires: array(string()).optional(),
44265
- description: string().optional()
44266
- });
44267
- /** The full capability block consulted before dispatch. */
44268
- var TargetKindCapsSchema = object({
44269
- attachments: object({
44270
- mediaTypes: array(AttachmentMediaTypeSchema),
44271
- mode: _enum([
44272
- "url",
44273
- "bytes",
44274
- "both"
44275
- ]),
44276
- max: number().int().nonnegative(),
44277
- maxBytes: number().int().positive().optional()
44278
- }),
44279
- /** Max action buttons (0 = none). */
44280
- actions: number().int().nonnegative(),
44281
- /**
44282
- * Whether this kind renders a per-action ICON.
44283
- *
44284
- * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
44285
- * run on the addon cap path — three production failures in one day taught
44286
- * this repo that once. Absent is read as false by the degrade engine, which
44287
- * is the safe direction: an icon that is not rendered costs nothing, an icon
44288
- * assumed and dropped costs the operator's trust in the field.
44289
- */
44290
- actionIcons: boolean().optional(),
44291
- levels: array(TargetKindLevelSchema),
44292
- format: array(NotificationFormatSchema),
44293
- clickUrl: boolean(),
44294
- sound: boolean(),
44295
- ttl: boolean(),
44296
- bodyMaxLen: number().int().positive()
44297
- });
44298
- /**
44299
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
44300
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
44301
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
44302
- * the union is large and not meant for runtime validation here; the exported
44303
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
44304
- */
44305
- var ConfigSchemaPassthrough$2 = unknown();
44306
- var TargetKindSchema = object({
44307
- kind: string(),
44308
- label: string(),
44309
- icon: string(),
44310
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
44311
- addonId: string(),
44312
- /**
44313
- * URL of the kind's bundled BRAND icon, served by the providing addon over
44314
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
44315
- * when the addon bundles no icon for that kind — the client then falls back
44316
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
44317
- *
44318
- * Root-relative on purpose: it resolves against whatever origin serves a web
44319
- * client, and a native client joins it onto its own hub base.
44320
- *
44321
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
44322
- * field that survived only because the runtime cap-router forwards provider
44323
- * output verbatim — so every consumer had to re-declare it by hand to stop
44324
- * its own Zod parse from stripping it, and the whole arrangement would have
44325
- * broken silently the moment output validation was tightened anywhere.
44326
- */
44327
- iconUrl: string().optional(),
44328
- /**
44329
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
44330
- *
44331
- * The server knows this and therefore says it, because the client cannot
44332
- * safely guess: a React-Native client renders SVG and raster through two
44333
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
44334
- * not decode SVG on iOS/Android), so without this it silently fell back to a
44335
- * placeholder glyph for every vector icon while the web build looked fine.
44336
- *
44337
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
44338
- * not been updated — a client that cannot determine the type should prefer
44339
- * its raster path, which is the safe default for an unknown image.
44340
- */
44341
- iconMediaType: string().optional(),
44342
- configSchema: ConfigSchemaPassthrough$2,
44343
- /**
44344
- * The config keys that decide WHERE this kind's secrets are sent (server,
44345
- * base URL, instance, chat, recipient). A save that changes any of them
44346
- * while leaving a secret redacted is refused by the provider (D634), so an
44347
- * editor uses this to ask for the secret on exactly those edits instead of
44348
- * on every edit.
44349
- *
44350
- * Optional: absent from a provider that holds no secret (the Home Assistant
44351
- * kinds) and from a provider older than this field — an editor that finds
44352
- * it absent must assume ANY edit may need the secret.
44353
- */
44354
- destinationKeys: array(string()).optional(),
44355
- supportsDiscovery: boolean(),
44356
- caps: TargetKindCapsSchema
44357
- });
44358
- /**
44359
- * A persisted target. `config` holds secrets; providers REDACT secret fields
44360
- * (return a presence marker only) when serving `listTargets` — never
44361
- * round-trip a stored secret to the UI.
44362
- */
44363
- var TargetSchema = object({
44364
- id: string(),
44365
- name: string(),
44366
- kind: string(),
44367
- addonId: string(),
44368
- enabled: boolean(),
44369
- config: record(string(), unknown())
44370
- });
44371
- /** A discovery-surfaced candidate (config is partial + non-secret). */
44372
- var DiscoveredTargetSchema = object({
44373
- kind: string(),
44374
- suggestedName: string(),
44375
- config: record(string(), unknown())
44376
- });
44377
- /** The degrade engine's report — what was resolved / dropped / degraded. */
44378
- var RenderedAsSchema = object({
44379
- level: string(),
44380
- format: NotificationFormatSchema,
44381
- attachmentsSent: number().int().nonnegative(),
44382
- actionsSent: number().int().nonnegative(),
44383
- truncated: boolean(),
44384
- dropped: array(string())
44385
- });
44386
- var SendResultSchema = object({
44387
- success: boolean(),
44388
- error: string().optional(),
44389
- renderedAs: RenderedAsSchema.optional()
44390
- });
44391
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
44392
- var TestResultSchema = SendResultSchema;
44393
- /**
44394
- * Pre-save credential probe against a partial (unsaved) target config.
44395
- * `resolvedUrl` never includes secrets (token stripped from path).
44396
- */
44397
- var ProbeTargetConfigResultSchema = object({
44398
- ok: boolean(),
44399
- resolvedUrl: string(),
44400
- httpStatus: number().int().optional(),
44401
- stateInstance: string().optional(),
44402
- error: string().optional()
44403
- });
44404
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
44405
- kind: string(),
44406
- config: record(string(), unknown()).optional(),
44407
- /**
44408
- * When editing / duplicating, the blank password field never round-trips
44409
- * the stored token. Pass the source target id so the provider can restore
44410
- * secrets server-side before dialling (same merge as upsert).
44411
- */
44412
- targetId: string().optional()
44413
- }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
44414
- kind: string(),
44415
- config: record(string(), unknown()),
44416
- /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
44417
- targetId: string().optional()
44418
- }), ProbeTargetConfigResultSchema, {
44419
- kind: "mutation",
44420
- auth: "admin"
44421
- }), method(object({
44422
- targetId: string(),
44423
- notification: NotificationSchema
44424
- }), SendResultSchema, {
44425
- kind: "mutation",
44426
- auth: "admin"
44427
- }), method(object({
44428
- targetId: string(),
44429
- sample: NotificationSchema.optional()
44430
- }), TestResultSchema, {
44431
- kind: "mutation",
44432
- auth: "admin"
44433
- }), method(object({
44434
- target: TargetSchema,
44435
- /**
44436
- * Duplicate flow: create a NEW target id while copying secrets from an
44437
- * existing row. The UI never holds the stored token; blank / redacted
44438
- * password fields are restored from this source (D634 still applies if
44439
- * destinations diverge without a re-typed secret).
44440
- */
44441
- cloneSecretsFrom: string().optional()
44442
- }), TargetSchema, {
44443
- kind: "mutation",
44444
- auth: "admin"
44445
- }), method(object({ targetId: string() }), _void(), {
44446
- kind: "mutation",
44447
- auth: "admin"
44448
- }), method(object({
44449
- targetId: string(),
44450
- enabled: boolean()
44451
- }), _void(), {
44452
- kind: "mutation",
44453
- auth: "admin"
44454
- });
44455
- new Set([
44456
- {
44457
- id: "person",
44458
- name: "Person"
44459
- },
44460
- {
44461
- id: "vehicle",
44462
- name: "Vehicle"
44463
- },
44464
- {
44465
- id: "animal",
44466
- name: "Animal"
44467
- },
44468
- {
44469
- id: "package",
44470
- name: "Package"
44471
- }
44472
- ].map((l) => l.id));
44473
- var COCO_TO_MACRO = {
44474
- mapping: {
44475
- person: "person",
44476
- bicycle: "vehicle",
44477
- car: "vehicle",
44478
- motorcycle: "vehicle",
44479
- airplane: "vehicle",
44480
- bus: "vehicle",
44481
- train: "vehicle",
44482
- truck: "vehicle",
44483
- boat: "vehicle",
44484
- bird: "animal",
44485
- cat: "animal",
44486
- dog: "animal",
44487
- horse: "animal",
44488
- sheep: "animal",
44489
- cow: "animal",
44490
- elephant: "animal",
44491
- bear: "animal",
44492
- zebra: "animal",
44493
- giraffe: "animal",
44494
- suitcase: "package",
44495
- backpack: "package",
44496
- handbag: "package"
44497
- },
44498
- preserveOriginal: false
44499
- };
44500
45157
  var AUDIO_MACRO_LABELS = [
44501
45158
  {
44502
45159
  id: "speech",
@@ -50897,14 +51554,6 @@ var EngineProvisioningSchema = object({
50897
51554
  */
50898
51555
  configIssues: array(string()).optional()
50899
51556
  });
50900
- var PipelineStepInputSchema = lazy(() => object({
50901
- addonId: string(),
50902
- modelId: string().optional(),
50903
- enabled: boolean().default(true),
50904
- children: array(PipelineStepInputSchema).optional(),
50905
- settings: record(string(), unknown()).optional(),
50906
- jumpDeviceKey: string().optional()
50907
- }));
50908
51557
  var ModelSubstitutionSchema = object({
50909
51558
  addonId: string(),
50910
51559
  chosen: string(),
@@ -50983,7 +51632,6 @@ var ConfigUISchemaBridge = custom();
50983
51632
  var ConfigUISchemaNullableBridge = custom();
50984
51633
  var InferenceCapabilitiesBridge = custom();
50985
51634
  var ModelAvailabilityListBridge = custom();
50986
- var PipelineRunResultBridge = custom();
50987
51635
  method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
50988
51636
  modelId: string(),
50989
51637
  settings: record(string(), unknown()).readonly()
@@ -54145,6 +54793,12 @@ var ServerUpdateActionResultSchema = object({
54145
54793
  */
54146
54794
  blockers: array(UpdateBlockerSchema).optional()
54147
54795
  });
54796
+ var LaunchOverridesSchema = object({
54797
+ /** The persisted heap ceiling in MB, or `null` when none is set. */
54798
+ heapMb: number().int().nullable() });
54799
+ var SetLaunchOverridesResultSchema = LaunchOverridesSchema.extend({
54800
+ /** Always true: the override is read at launch, not by the running process. */
54801
+ restartRequired: boolean() });
54148
54802
  method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
54149
54803
  kind: "mutation",
54150
54804
  auth: "admin"
@@ -54167,6 +54821,9 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
54167
54821
  }), method(_void(), ServerUpdateActionResultSchema, {
54168
54822
  kind: "mutation",
54169
54823
  auth: "admin"
54824
+ }), method(_void(), LaunchOverridesSchema, { auth: "admin" }), method(object({ heapMb: number().int().min(512).max(65536).nullable() }), SetLaunchOverridesResultSchema, {
54825
+ kind: "mutation",
54826
+ auth: "admin"
54170
54827
  });
54171
54828
  /**
54172
54829
  * `smtp-provider` — pluggable email delivery surface.
@@ -56568,7 +57225,9 @@ var ManifestShapeSchema = object({
56568
57225
  icon: string().optional()
56569
57226
  }).optional(),
56570
57227
  protected: boolean().optional(),
56571
- removable: boolean().optional()
57228
+ removable: boolean().optional(),
57229
+ /** Declared Integrations-page block (mirror of `AddonDeclaration.integrationSection`). */
57230
+ integrationSection: _enum(["system", "providers"]).optional()
56572
57231
  });
56573
57232
  var AddonProcessSchema = object({
56574
57233
  pid: number().optional(),
@@ -56933,9 +57592,9 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56933
57592
  }), LogStreamEntrySchema, { kind: "subscription" });
56934
57593
  object({
56935
57594
  /** Carbon dioxide concentration in ppm. */
56936
- co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
57595
+ co2: number().min(0).optional().meta(readingMeta("concentration-volume")),
56937
57596
  /** Total volatile organic compounds in ppb. */
56938
- vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
57597
+ voc: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56939
57598
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56940
57599
  pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56941
57600
  /** Particulate matter ≤ 10 μm in µg/m³. */
@@ -56948,7 +57607,7 @@ object({
56948
57607
  DeviceType.Sensor;
56949
57608
  object({
56950
57609
  /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56951
- lux: number().min(0).nullable().meta(readingMeta("illuminance", {
57610
+ illuminance: number().min(0).nullable().meta(readingMeta("illuminance", {
56952
57611
  precision: 0,
56953
57612
  label: "Illuminance"
56954
57613
  })),
@@ -57045,6 +57704,84 @@ DeviceType.Camera, method(object({ deviceId: number() }), AudioMetricsSnapshotSc
57045
57704
  * would produce more than `maxPoints` samples. */
57046
57705
  sampleEveryMs: number().int().positive().optional()
57047
57706
  }), AudioMetricsHistorySchema);
57707
+ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
57708
+ actionId: string().min(1),
57709
+ variables: record(string(), ActionVariableValueSchema).optional()
57710
+ }), _void(), {
57711
+ kind: "mutation",
57712
+ auth: "admin"
57713
+ }), method(object({ actionId: string().min(1) }), _void(), {
57714
+ kind: "mutation",
57715
+ auth: "admin"
57716
+ });
57717
+ /**
57718
+ * actions — the ONE surface for runnable actions, for the UI and for
57719
+ * automations. Provided by the core-blocks builtin on the hub.
57720
+ *
57721
+ * An action is a LIST ENTRY, not a device (D883): either internal (cap and wait
57722
+ * steps, kept in the hub's `actions` record kind) or declared by an addon over
57723
+ * `action-source`. `list` asks every `action-source` provider LIVE and overlays
57724
+ * the run status from the hub's `runs` ledger; there is no catalog here to
57725
+ * drift (D3).
57726
+ *
57727
+ * **Permissions follow the camera, as linked devices do.** An action is linked
57728
+ * to cameras by the operator (`setLinks`, D356: nothing links itself). A
57729
+ * non-admin may `get` / `run` an action only through a camera they may see that
57730
+ * links it: they name the camera (`deviceId`, a top-level field so the scope
57731
+ * gate sees it) and the provider refuses an action that camera does not link.
57732
+ * An admin, and an in-process caller (an automation block), may run any action.
57733
+ * Every refusal is typed (`[not-linked] …`) and logged.
57734
+ *
57735
+ * A second `run` of an action that is already running is REFUSED — traced and
57736
+ * logged — never queued.
57737
+ */
57738
+ var ActionIdInput = object({ actionId: string().min(1) });
57739
+ var ActionLinksSchema = object({
57740
+ deviceId: number().int().nonnegative(),
57741
+ actionIds: array(string().min(1)).max(100)
57742
+ });
57743
+ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
57744
+ actionId: string().min(1),
57745
+ deviceId: number().int().nonnegative().optional()
57746
+ }), object({ action: ActionEntrySchema.nullable() }), {
57747
+ role: "viewer",
57748
+ auth: "protected",
57749
+ kind: "query",
57750
+ caller: "required"
57751
+ }), method(object({
57752
+ actionId: string().min(1),
57753
+ variables: record(string(), ActionVariableValueSchema).optional(),
57754
+ wait: boolean().optional(),
57755
+ origin: ActionRunOriginSchema.optional(),
57756
+ deviceId: number().int().nonnegative().optional()
57757
+ }), ActionRunResultSchema, {
57758
+ role: "viewer",
57759
+ auth: "protected",
57760
+ kind: "mutation",
57761
+ caller: "required"
57762
+ }), method(ActionIdInput, _void(), {
57763
+ kind: "mutation",
57764
+ auth: "admin"
57765
+ }), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({ action: ActionEntrySchema }), {
57766
+ kind: "mutation",
57767
+ auth: "admin"
57768
+ }), method(object({
57769
+ actionId: string().min(1),
57770
+ action: ActionInputSchema.partial()
57771
+ }), object({ action: ActionEntrySchema }), {
57772
+ kind: "mutation",
57773
+ auth: "admin"
57774
+ }), method(ActionIdInput, _void(), {
57775
+ kind: "mutation",
57776
+ auth: "admin"
57777
+ }), method(object({ deviceId: number().int().nonnegative() }), array(ActionEntrySchema), {
57778
+ role: "viewer",
57779
+ auth: "protected",
57780
+ kind: "query"
57781
+ }), method(object({ deviceId: number().int().nonnegative() }), ActionLinksSchema, { auth: "admin" }), method(ActionLinksSchema, ActionLinksSchema, {
57782
+ kind: "mutation",
57783
+ auth: "admin"
57784
+ });
57048
57785
  object({
57049
57786
  /** Whether the automation is currently enabled. Disabled automations
57050
57787
  * ignore their trigger block — manual `trigger` still works. */
@@ -57098,7 +57835,7 @@ var BatteryStatusSchema = object({
57098
57835
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
57099
57836
  * declines to band a non-finite reading, which is the correct shape.
57100
57837
  */
57101
- percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
57838
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
57102
57839
  /**
57103
57840
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
57104
57841
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -57108,7 +57845,7 @@ var BatteryStatusSchema = object({
57108
57845
  * `'unknown'` is the absence of an answer: the provider has not read the
57109
57846
  * power fields yet, or the firmware reported neither. Before it existed,
57110
57847
  * two unreadable fields produced `'none'`, and a camera nobody had reached
57111
- * was drawn "on battery alone" — the same D315 defect `percentage` closed
57848
+ * was drawn "on battery alone" — the same D315 defect `level` closed
57112
57849
  * by going nullable. Consumers SKIP it (no charger state published, no
57113
57850
  * glyph, no HomeKit ChargingState) rather than coerce it either way.
57114
57851
  */
@@ -57132,7 +57869,7 @@ var BatteryStatusSchema = object({
57132
57869
  * that only report charge. A second reading on this cap, not a second
57133
57870
  * `numeric-sensor` on the same device.
57134
57871
  */
57135
- volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
57872
+ voltage: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
57136
57873
  /**
57137
57874
  * Ms epoch of the last time the device PROVED it was reachable — a
57138
57875
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -57166,7 +57903,7 @@ var BatteryStatusSchema = object({
57166
57903
  /**
57167
57904
  * True when the source is a BINARY low-battery indicator (HA
57168
57905
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
57169
- * charge level — `percentage` is then a coarse stand-in (100 = normal,
57906
+ * charge level — `level` is then a coarse stand-in (100 = normal,
57170
57907
  * sub-threshold = low). UI MUST render "Normal"/"Low" instead of a
57171
57908
  * misleading exact percentage. Absent/false → genuine 0–100 % reading.
57172
57909
  */
@@ -57192,26 +57929,388 @@ object({
57192
57929
  DeviceType.Sensor;
57193
57930
  object({
57194
57931
  /** Current level as 0..100 inclusive. Firmware-reported. */
57195
- percentage: number().min(0).max(100).meta(readingMeta("percentage")),
57932
+ level: number().min(0).max(100).meta(readingMeta("percentage")),
57196
57933
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
57197
57934
  lastChangedAt: number()
57198
57935
  });
57199
57936
  DeviceType.Light, method(object({
57200
57937
  deviceId: number().int().nonnegative(),
57201
- percentage: number().min(0).max(100)
57938
+ level: number().min(0).max(100)
57202
57939
  }), _void(), {
57203
57940
  kind: "mutation",
57204
57941
  auth: "protected"
57205
57942
  }), object({
57206
57943
  deviceId: number(),
57207
- percentage: number().min(0).max(100),
57944
+ level: number().min(0).max(100),
57208
57945
  lastChangedAt: number()
57209
57946
  });
57947
+ /**
57948
+ * The role vocabulary of a remote (D963): what a button MEANS, independent of
57949
+ * how it is wired. A layout template places buttons by role; a device binds a
57950
+ * role to an action; an exporter asks for a role by name. Roles are shared so
57951
+ * one layout fits every appliance and one voice command fits every binding.
57952
+ *
57953
+ * A role outside this list is `custom:<key>` — the operator's own button (a
57954
+ * SmartIR source, a channel preset). The key is a lowercase slug.
57955
+ */
57956
+ var REMOTE_KNOWN_ROLES = [
57957
+ "power",
57958
+ "power_on",
57959
+ "power_off",
57960
+ "volume_up",
57961
+ "volume_down",
57962
+ "mute",
57963
+ "channel_up",
57964
+ "channel_down",
57965
+ "up",
57966
+ "down",
57967
+ "left",
57968
+ "right",
57969
+ "ok",
57970
+ "back",
57971
+ "home",
57972
+ "menu",
57973
+ "info",
57974
+ "guide",
57975
+ "exit",
57976
+ "input",
57977
+ "play",
57978
+ "pause",
57979
+ "play_pause",
57980
+ "stop",
57981
+ "next",
57982
+ "previous",
57983
+ "fast_forward",
57984
+ "rewind",
57985
+ "digit_0",
57986
+ "digit_1",
57987
+ "digit_2",
57988
+ "digit_3",
57989
+ "digit_4",
57990
+ "digit_5",
57991
+ "digit_6",
57992
+ "digit_7",
57993
+ "digit_8",
57994
+ "digit_9"
57995
+ ];
57996
+ var CUSTOM_KEY_RE = /^[a-z0-9][a-z0-9_]{0,47}$/;
57997
+ var KNOWN_ROLES = new Set(REMOTE_KNOWN_ROLES);
57998
+ function isRemoteKnownRole(value) {
57999
+ return KNOWN_ROLES.has(value);
58000
+ }
58001
+ function isRemoteCustomKey(key) {
58002
+ return CUSTOM_KEY_RE.test(key);
58003
+ }
58004
+ /** The key of a `custom:<key>` role, or null for any other string. */
58005
+ function customKeyOfRole(role) {
58006
+ if (!role.startsWith("custom:")) return null;
58007
+ const key = role.slice(7);
58008
+ return isRemoteCustomKey(key) ? key : null;
58009
+ }
58010
+ function isRemoteRole(value) {
58011
+ return isRemoteKnownRole(value) || customKeyOfRole(value) !== null;
58012
+ }
58013
+ var RemoteRoleSchema = string().refine(isRemoteRole, { message: "a role is one of the known roles or custom:<key> (lowercase letters, digits, _)" });
58014
+ var RemoteBindingSchema = object({ action: AutomationCapStepSchema.extend({ deviceId: number().int().nonnegative() }) });
58015
+ var RemoteCustomButtonSchema = object({
58016
+ /** The slug after `custom:` in the role. */
58017
+ key: string().refine(isRemoteCustomKey, { message: "a lowercase slug" }),
58018
+ /** What the operator calls it, and what a voice command may name. */
58019
+ name: string().min(1).max(60)
58020
+ });
58021
+ var RemotePowerModeSchema = _enum(["toggle", "discrete"]);
58022
+ var RemoteConfigSchema = object({
58023
+ layoutTemplateId: string().min(1).nullable(),
58024
+ /**
58025
+ * The transmitter device new bindings and an import send through. A binding
58026
+ * names its own device, so one command may override it.
58027
+ */
58028
+ defaultTransmitterId: number().int().nonnegative().nullable().default(null),
58029
+ /** Keyed by role. A role with no entry falls back to the native cap. */
58030
+ bindings: record(string(), RemoteBindingSchema),
58031
+ customButtons: array(RemoteCustomButtonSchema).max(100),
58032
+ power: object({ mode: RemotePowerModeSchema })
58033
+ }).superRefine((config, ctx) => {
58034
+ const roles = Object.keys(config.bindings);
58035
+ if (roles.length > 200) ctx.addIssue({
58036
+ code: "custom",
58037
+ message: "too many bindings",
58038
+ path: ["bindings"]
58039
+ });
58040
+ for (const role of roles) if (!isRemoteRole(role)) ctx.addIssue({
58041
+ code: "custom",
58042
+ message: `"${role}" is not a role`,
58043
+ path: ["bindings", role]
58044
+ });
58045
+ const keys = /* @__PURE__ */ new Set();
58046
+ config.customButtons.forEach((button, index) => {
58047
+ if (keys.has(button.key)) ctx.addIssue({
58048
+ code: "custom",
58049
+ message: `custom button "${button.key}" is listed twice`,
58050
+ path: [
58051
+ "customButtons",
58052
+ index,
58053
+ "key"
58054
+ ]
58055
+ });
58056
+ keys.add(button.key);
58057
+ });
58058
+ for (const role of roles) {
58059
+ const key = customKeyOfRole(role);
58060
+ if (key !== null && !keys.has(key)) ctx.addIssue({
58061
+ code: "custom",
58062
+ message: `${role} is bound but has no entry in customButtons`,
58063
+ path: ["bindings", role]
58064
+ });
58065
+ }
58066
+ });
58067
+ object({
58068
+ power: object({
58069
+ state: _enum(["on", "off"]),
58070
+ at: number()
58071
+ }).nullable(),
58072
+ muted: object({
58073
+ value: boolean(),
58074
+ at: number()
58075
+ }).nullable()
58076
+ });
58077
+ var RemoteLayoutButtonSchema = object({
58078
+ id: string().min(1).max(40),
58079
+ /** 0-based grid position. */
58080
+ row: number().int().min(0).max(24),
58081
+ col: number().int().min(0).max(24),
58082
+ rowSpan: number().int().min(1).max(24),
58083
+ colSpan: number().int().min(1).max(24),
58084
+ icon: string().min(1).max(60).optional(),
58085
+ label: string().min(1).max(40).optional(),
58086
+ role: RemoteRoleSchema
58087
+ });
58088
+ object({
58089
+ columns: number().int().min(1).max(24),
58090
+ rows: number().int().min(1).max(24),
58091
+ buttons: array(RemoteLayoutButtonSchema).max(200)
58092
+ }).superRefine((grid, ctx) => {
58093
+ const ids = /* @__PURE__ */ new Set();
58094
+ const taken = /* @__PURE__ */ new Map();
58095
+ grid.buttons.forEach((button, index) => {
58096
+ if (ids.has(button.id)) ctx.addIssue({
58097
+ code: "custom",
58098
+ message: `button id "${button.id}" is used twice`,
58099
+ path: [
58100
+ "buttons",
58101
+ index,
58102
+ "id"
58103
+ ]
58104
+ });
58105
+ ids.add(button.id);
58106
+ if (button.row + button.rowSpan > grid.rows || button.col + button.colSpan > grid.columns) {
58107
+ ctx.addIssue({
58108
+ code: "custom",
58109
+ message: `button "${button.id}" leaves the grid`,
58110
+ path: ["buttons", index]
58111
+ });
58112
+ return;
58113
+ }
58114
+ for (let r = button.row; r < button.row + button.rowSpan; r += 1) for (let c = button.col; c < button.col + button.colSpan; c += 1) {
58115
+ const cell = `${r}:${c}`;
58116
+ const other = taken.get(cell);
58117
+ if (other !== void 0) {
58118
+ ctx.addIssue({
58119
+ code: "custom",
58120
+ message: `buttons "${other}" and "${button.id}" overlap`,
58121
+ path: ["buttons", index]
58122
+ });
58123
+ return;
58124
+ }
58125
+ taken.set(cell, button.id);
58126
+ }
58127
+ });
58128
+ });
58129
+ var RemoteBoundCommandSchema = object({
58130
+ role: RemoteRoleSchema,
58131
+ /** The operator's name for a custom button; absent for a known role. */
58132
+ label: string().optional(),
58133
+ custom: boolean()
58134
+ });
58135
+ var RemoteCommandListSchema = object({
58136
+ layoutTemplateId: string().nullable(),
58137
+ power: object({ mode: RemotePowerModeSchema }),
58138
+ /** Only the roles that are BOUND — the ones a press can reach. */
58139
+ commands: array(RemoteBoundCommandSchema)
58140
+ });
58141
+ var RemoteRefusalReasonSchema = _enum([
58142
+ "no-remote",
58143
+ "unbound-role",
58144
+ "action-failed"
58145
+ ]);
58146
+ var RemoteSendResultSchema = discriminatedUnion("status", [
58147
+ object({
58148
+ status: literal("sent"),
58149
+ /** How many times the action ran. */
58150
+ sends: number().int().min(1)
58151
+ }),
58152
+ object({
58153
+ status: literal("skipped"),
58154
+ reason: literal("already-in-state")
58155
+ }),
58156
+ object({
58157
+ status: literal("refused"),
58158
+ reason: RemoteRefusalReasonSchema,
58159
+ detail: string().optional()
58160
+ })
58161
+ ]);
58162
+ var RemoteMediaCommandSchema = _enum([
58163
+ "play",
58164
+ "pause",
58165
+ "stop",
58166
+ "next",
58167
+ "previous",
58168
+ "setMute",
58169
+ "selectSource"
58170
+ ]);
58171
+ /** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
58172
+ var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
58173
+ var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status: literal("saved") }), object({
58174
+ status: literal("refused"),
58175
+ reason: _enum(["unknown-template", "not-a-media-player"]),
58176
+ detail: string()
58177
+ })]);
58178
+ var deviceInput = object({ deviceId: number().int().nonnegative() });
58179
+ DeviceType.MediaPlayer;
58180
+ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
58181
+ kind: "mutation",
58182
+ auth: "admin"
58183
+ }), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
58184
+ role: RemoteRoleSchema,
58185
+ /** Sends in a row (volume steps). */
58186
+ repeat: number().int().min(1).max(50).default(1)
58187
+ }), RemoteSendResultSchema, {
58188
+ kind: "mutation",
58189
+ auth: "protected"
58190
+ }), method(deviceInput.extend({
58191
+ command: RemoteMediaCommandSchema,
58192
+ muted: boolean().optional(),
58193
+ source: string().optional(),
58194
+ repeat: number().int().min(1).max(50).default(1)
58195
+ }), RemoteMediaCommandResultSchema, {
58196
+ kind: "mutation",
58197
+ auth: "protected"
58198
+ });
58199
+ var TemplateKindSchema = _enum(["remote"]);
58200
+ /**
58201
+ * template — the generic store of reusable, named operator data (D963). A
58202
+ * record is `{ id, kind, name, payload }`; the kinds (today only `remote`, the
58203
+ * button grid of a remote) and the schema each payload must satisfy are
58204
+ * declared by the system in `template-kinds.ts`. Nothing here is specific to a
58205
+ * kind: the engine validates a payload against its kind's schema and, on a
58206
+ * delete, asks the kind's owner whether anything still points at the record.
58207
+ *
58208
+ * Every refusal is a typed result — a UI says "unknown kind" or names the
58209
+ * devices that still use a template, without parsing text.
58210
+ */
58211
+ var TemplateRecordSchema = object({
58212
+ id: string().min(1),
58213
+ kind: TemplateKindSchema,
58214
+ name: string().min(1).max(120),
58215
+ /** Shaped by the kind; `checkTemplatePayload(kind, payload)` reads it. */
58216
+ payload: unknown(),
58217
+ createdAt: number(),
58218
+ updatedAt: number()
58219
+ });
58220
+ var TemplateWriteResultSchema = discriminatedUnion("status", [object({
58221
+ status: literal("saved"),
58222
+ template: TemplateRecordSchema
58223
+ }), object({
58224
+ status: literal("refused"),
58225
+ reason: _enum(["invalid-payload", "not-found"]),
58226
+ detail: string()
58227
+ })]);
58228
+ /** A device still pointing at a template. */
58229
+ var TemplateUserSchema = object({
58230
+ deviceId: number().int().nonnegative(),
58231
+ name: string()
58232
+ });
58233
+ var TemplateDeleteResultSchema = discriminatedUnion("status", [
58234
+ object({ status: literal("deleted") }),
58235
+ object({ status: literal("not-found") }),
58236
+ object({
58237
+ status: literal("in-use"),
58238
+ devices: array(TemplateUserSchema).min(1)
58239
+ })
58240
+ ]);
58241
+ method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSchema), { auth: "protected" }), method(object({ id: string().min(1) }), TemplateRecordSchema.nullable(), { auth: "protected" }), method(object({
58242
+ kind: TemplateKindSchema,
58243
+ name: string().min(1).max(120),
58244
+ payload: unknown()
58245
+ }), TemplateWriteResultSchema, {
58246
+ kind: "mutation",
58247
+ auth: "admin"
58248
+ }), method(object({
58249
+ id: string().min(1),
58250
+ name: string().min(1).max(120).optional(),
58251
+ payload: unknown().optional()
58252
+ }), TemplateWriteResultSchema, {
58253
+ kind: "mutation",
58254
+ auth: "admin"
58255
+ }), method(object({ id: string().min(1) }), TemplateDeleteResultSchema, {
58256
+ kind: "mutation",
58257
+ auth: "admin"
58258
+ });
57210
58259
  DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
57211
58260
  kind: "mutation",
57212
58261
  auth: "protected"
57213
58262
  });
57214
58263
  /**
58264
+ * A pulse train in signed microseconds: mark (+) first, then space (-), strictly
58265
+ * alternating, no zeros, 2..1024 entries.
58266
+ */
58267
+ var IrTimingsSchema = array(number().int()).min(2).max(1024).superRefine((timings, ctx) => {
58268
+ timings.forEach((value, index) => {
58269
+ const expectMark = index % 2 === 0;
58270
+ if (value === 0 || value > 0 !== expectMark) ctx.addIssue({
58271
+ code: "custom",
58272
+ message: `timing ${index} must be a non-zero ${expectMark ? "mark (positive)" : "space (negative)"}`,
58273
+ path: [index]
58274
+ });
58275
+ });
58276
+ });
58277
+ /** Per-command transmit options. Every key is optional; a transmitter applies what it can. */
58278
+ var TransmitSettingsSchema = object({
58279
+ carrierHz: number().int().positive().optional(),
58280
+ dutyCyclePercent: number().int().min(1).max(100).optional(),
58281
+ repeat: number().int().min(0).max(20).optional(),
58282
+ repeatDelayMs: number().int().min(0).optional()
58283
+ });
58284
+ /** The setting keys a transmitter may declare as settable (`getInfo.settable`). */
58285
+ var TransmitSettingKeySchema = _enum([
58286
+ "carrierHz",
58287
+ "dutyCyclePercent",
58288
+ "repeat",
58289
+ "repeatDelayMs"
58290
+ ]);
58291
+ var IrMediumSchema = _enum(["ir", "rf"]);
58292
+ var IrSendResultSchema = object({
58293
+ ok: literal(true),
58294
+ /** Settings the transmitter could not apply; the train was still sent. */
58295
+ ignoredSettings: array(string())
58296
+ });
58297
+ var IrTransmitterInfoSchema = object({
58298
+ media: array(IrMediumSchema),
58299
+ settable: array(TransmitSettingKeySchema),
58300
+ maxTimings: number().int().positive()
58301
+ });
58302
+ DeviceType.Generic, method(object({
58303
+ deviceId: number().int().nonnegative(),
58304
+ timings: IrTimingsSchema,
58305
+ settings: TransmitSettingsSchema.optional()
58306
+ }), IrSendResultSchema, {
58307
+ kind: "mutation",
58308
+ auth: "protected"
58309
+ }), method(object({ deviceId: number().int().nonnegative() }), IrTransmitterInfoSchema, {
58310
+ kind: "query",
58311
+ auth: "protected"
58312
+ });
58313
+ /**
57215
58314
  * `calendar-source` — collection cap exposing per-provider calendars and
57216
58315
  * their events. Every addon that contributes calendars (Home Assistant,
57217
58316
  * a future Google Calendar addon, …) registers a provider here. The hub
@@ -57715,11 +58814,11 @@ var ColorInputSchema = discriminatedUnion("mode", [
57715
58814
  }),
57716
58815
  object({
57717
58816
  mode: literal("mired"),
57718
- mireds: number().int().min(50).max(1e3)
58817
+ colorTemperature: number().int().min(50).max(1e3)
57719
58818
  })
57720
58819
  ]);
57721
58820
  object({
57722
- /** Active color mode — which of `rgb` / `hsv` / `mireds` reflects
58821
+ /** Active color mode — which of `rgb` / `hsv` / `colorTemperature` reflects
57723
58822
  * the bulb's current state. */
57724
58823
  mode: _enum([
57725
58824
  "rgb",
@@ -57731,7 +58830,7 @@ object({
57731
58830
  /** Populated when `mode === 'hsv'`. */
57732
58831
  hsv: HsvTripletSchema.optional(),
57733
58832
  /** Populated when `mode === 'mired'`. */
57734
- mireds: number().int().optional().meta(readingMeta("colour-temperature")),
58833
+ colorTemperature: number().int().optional().meta(readingMeta("colour-temperature")),
57735
58834
  /** Ms epoch of the last operator-driven change. */
57736
58835
  lastChangedAt: number()
57737
58836
  });
@@ -57750,7 +58849,7 @@ DeviceType.Light, method(object({
57750
58849
  ]),
57751
58850
  rgb: RgbTripletSchema.optional(),
57752
58851
  hsv: HsvTripletSchema.optional(),
57753
- mireds: number().int().optional(),
58852
+ colorTemperature: number().int().optional(),
57754
58853
  lastChangedAt: number()
57755
58854
  });
57756
58855
  var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
@@ -58176,11 +59275,11 @@ object({
58176
59275
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
58177
59276
  availablePrograms: array(string()),
58178
59277
  /** Seconds remaining on the active cycle, or null when not running / not known. */
58179
- remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
59278
+ remaining: number().int().nonnegative().nullable().meta(readingMeta("duration")),
58180
59279
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
58181
- delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
59280
+ delayedStart: number().int().nonnegative().nullable().meta(readingMeta("duration")),
58182
59281
  /** Cycle completion 0..100, or null when not applicable. */
58183
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59282
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58184
59283
  /** True when the door is reported open; null when the door state is not known. */
58185
59284
  doorOpen: boolean().nullable(),
58186
59285
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -58225,7 +59324,7 @@ object({
58225
59324
  /** Ms epoch of the last value received from the appliance; 0 = never. */
58226
59325
  lastFetchedAt: number()
58227
59326
  });
58228
- var DeviceIdSchema = number().int().nonnegative();
59327
+ var DeviceIdSchema$1 = number().int().nonnegative();
58229
59328
  /** A value a program option or setting can take. */
58230
59329
  var DishwasherValueSchema = union([
58231
59330
  boolean(),
@@ -58289,77 +59388,77 @@ var DishwasherOptionsSchema = object({
58289
59388
  options: array(DishwasherOptionDescriptorSchema)
58290
59389
  });
58291
59390
  DeviceType.Dishwasher, method(object({
58292
- deviceId: DeviceIdSchema,
59391
+ deviceId: DeviceIdSchema$1,
58293
59392
  program: string().min(1).optional(),
58294
59393
  options: record(string(), DishwasherValueSchema).optional()
58295
59394
  }), _void(), {
58296
59395
  kind: "mutation",
58297
59396
  auth: "protected"
58298
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
59397
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
58299
59398
  kind: "mutation",
58300
59399
  auth: "protected"
58301
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
59400
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
58302
59401
  kind: "mutation",
58303
59402
  auth: "protected"
58304
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
59403
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
58305
59404
  kind: "mutation",
58306
59405
  auth: "protected"
58307
59406
  }), method(object({
58308
- deviceId: DeviceIdSchema,
59407
+ deviceId: DeviceIdSchema$1,
58309
59408
  program: string().min(1)
58310
59409
  }), _void(), {
58311
59410
  kind: "mutation",
58312
59411
  auth: "protected"
58313
59412
  }), method(object({
58314
- deviceId: DeviceIdSchema,
59413
+ deviceId: DeviceIdSchema$1,
58315
59414
  on: boolean()
58316
59415
  }), _void(), {
58317
59416
  kind: "mutation",
58318
59417
  auth: "protected"
58319
59418
  }), method(object({
58320
- deviceId: DeviceIdSchema,
59419
+ deviceId: DeviceIdSchema$1,
58321
59420
  on: boolean()
58322
59421
  }), _void(), {
58323
59422
  kind: "mutation",
58324
59423
  auth: "protected"
58325
59424
  }), method(object({
58326
- deviceId: DeviceIdSchema,
59425
+ deviceId: DeviceIdSchema$1,
58327
59426
  on: boolean()
58328
59427
  }), _void(), {
58329
59428
  kind: "mutation",
58330
59429
  auth: "protected"
58331
59430
  }), method(object({
58332
- deviceId: DeviceIdSchema,
59431
+ deviceId: DeviceIdSchema$1,
58333
59432
  on: boolean()
58334
59433
  }), _void(), {
58335
59434
  kind: "mutation",
58336
59435
  auth: "protected"
58337
59436
  }), method(object({
58338
- deviceId: DeviceIdSchema,
59437
+ deviceId: DeviceIdSchema$1,
58339
59438
  on: boolean()
58340
59439
  }), _void(), {
58341
59440
  kind: "mutation",
58342
59441
  auth: "protected"
58343
59442
  }), method(object({
58344
- deviceId: DeviceIdSchema,
59443
+ deviceId: DeviceIdSchema$1,
58345
59444
  on: boolean()
58346
59445
  }), _void(), {
58347
59446
  kind: "mutation",
58348
59447
  auth: "protected"
58349
59448
  }), method(object({
58350
- deviceId: DeviceIdSchema,
59449
+ deviceId: DeviceIdSchema$1,
58351
59450
  on: boolean()
58352
59451
  }), _void(), {
58353
59452
  kind: "mutation",
58354
59453
  auth: "protected"
58355
59454
  }), method(object({
58356
- deviceId: DeviceIdSchema,
59455
+ deviceId: DeviceIdSchema$1,
58357
59456
  key: string().min(1),
58358
59457
  value: DishwasherValueSchema
58359
59458
  }), _void(), {
58360
59459
  kind: "mutation",
58361
59460
  auth: "protected"
58362
- }), method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" });
59461
+ }), method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" });
58363
59462
  DeviceType.Camera;
58364
59463
  object({
58365
59464
  /** Ms epoch of the last press. null = never observed since this provider started. */
@@ -58798,7 +59897,7 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
58798
59897
  object({
58799
59898
  /** Active speed as 0..100 inclusive. Null when the device has no
58800
59899
  * speed surface (single-speed fan). */
58801
- percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59900
+ speed: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58802
59901
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
58803
59902
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
58804
59903
  percentageStep: number().positive().optional().meta(readingMeta("percentage")),
@@ -58818,7 +59917,7 @@ object({
58818
59917
  });
58819
59918
  DeviceType.Fan, method(object({
58820
59919
  deviceId: number().int().nonnegative(),
58821
- percentage: number().min(0).max(100)
59920
+ speed: number().min(0).max(100)
58822
59921
  }), _void(), {
58823
59922
  kind: "mutation",
58824
59923
  auth: "protected"
@@ -59001,7 +60100,7 @@ DeviceType.Humidifier, method(object({
59001
60100
  });
59002
60101
  object({
59003
60102
  /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
59004
- percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
60103
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
59005
60104
  precision: 0,
59006
60105
  label: "Humidity"
59007
60106
  })),
@@ -59444,7 +60543,7 @@ object({
59444
60543
  batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59445
60544
  /** 0..100 mowing-completion percentage of the current task, or null when no
59446
60545
  * task is active / progress is unavailable. */
59447
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60546
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59448
60547
  /** Current device/event code (dynamic — mostly status, sometimes an error),
59449
60548
  * or null when unknown. */
59450
60549
  currentCode: number().nullable().meta(readingMeta("none")),
@@ -59916,9 +61015,35 @@ var MediaInfoSchema = object({
59916
61015
  /** Optional cover-art / thumbnail URL. */
59917
61016
  imageUrl: string().optional()
59918
61017
  });
61018
+ /**
61019
+ * Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
61020
+ * the device said so. `assumed`: the driver has no feedback (infrared) and
61021
+ * tracks what it last SENT; it moves only after a send was accepted, and a
61022
+ * human at the real remote makes it wrong. A UI shows an assumed value as a
61023
+ * guess, never as a reading.
61024
+ */
61025
+ var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
61026
+ /** The fields of a media-player status a driver can only guess. */
61027
+ var MediaPlayerAssumedFieldSchema = _enum([
61028
+ "state",
61029
+ "volumeLevel",
61030
+ "isMuted",
61031
+ "source"
61032
+ ]);
59919
61033
  object({
59920
61034
  /** Playback lifecycle state. */
59921
61035
  state: MediaPlayerStateSchema,
61036
+ /**
61037
+ * Whether the state fields are readings or the driver's own bookkeeping.
61038
+ * A slice written before this field existed parses as `reported`.
61039
+ */
61040
+ stateSource: MediaPlayerStateSourceSchema.default("reported"),
61041
+ /**
61042
+ * Which fields are the driver's own guess. `stateSource` is `assumed` when
61043
+ * this is not empty; a field not listed here is a reading even then (power
61044
+ * from a running-state source, mute guessed from the last send).
61045
+ */
61046
+ assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
59922
61047
  /** Volume as 0..100 inclusive. Null when the device has no volume
59923
61048
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
59924
61049
  volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
@@ -59934,10 +61059,10 @@ object({
59934
61059
  currentMedia: MediaInfoSchema.nullable(),
59935
61060
  /** Current playback position in ms. Null when not seekable or
59936
61061
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
59937
- positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
61062
+ position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59938
61063
  /** Total duration of the current media in ms. Null when unknown
59939
61064
  * (live stream) or nothing is playing. */
59940
- durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
61065
+ duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59941
61066
  /** Shuffle toggle. Null when no shuffle surface.
59942
61067
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
59943
61068
  shuffle: boolean().nullable(),
@@ -59964,7 +61089,7 @@ DeviceType.MediaPlayer, method(object({ deviceId: number().int().nonnegative() }
59964
61089
  auth: "protected"
59965
61090
  }), method(object({
59966
61091
  deviceId: number().int().nonnegative(),
59967
- positionMs: number().int().nonnegative()
61092
+ position: number().int().nonnegative()
59968
61093
  }), _void(), {
59969
61094
  kind: "mutation",
59970
61095
  auth: "protected"
@@ -60637,9 +61762,9 @@ var NetworkLinkStatusSchema = object({
60637
61762
  * one whose reading has not landed must not be drawn at 0 %. Consumers
60638
61763
  * SKIP a null rather than coerce it.
60639
61764
  */
60640
- signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61765
+ signal: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60641
61766
  /** Raw received signal strength in dBm, when the firmware reports one. */
60642
- rssiDbm: number().optional().meta(readingMeta("signal-level")),
61767
+ rssi: number().optional().meta(readingMeta("signal-level")),
60643
61768
  /** Network name of a wireless link, when the firmware reports it. */
60644
61769
  ssid: string().optional(),
60645
61770
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
@@ -60771,7 +61896,8 @@ var TopologyNodeSchema = object({
60771
61896
  isHub: boolean(),
60772
61897
  isOnline: boolean(),
60773
61898
  cpuPercent: number(),
60774
- memoryPercent: number(),
61899
+ /** `null` when the node's memory read failed or it has not reported one (D393). */
61900
+ memoryPercent: number().nullable(),
60775
61901
  uptime: number(),
60776
61902
  lastSeen: string(),
60777
61903
  localIps: array(string()).readonly(),
@@ -61300,10 +62426,21 @@ var PetFeederDeviceStatusSchema = _enum([
61300
62426
  ]);
61301
62427
  var gramsPortion = number().int().min(4).max(200);
61302
62428
  object({
61303
- /** Food currently in the bowl (grams). Null when the device has not
61304
- * reported a reading yet. On dual-hopper models this is the combined
61305
- * bowl reading; per-hopper levels live in `food1`/`food2`. */
62429
+ /** Food in the TANK (hopper), as the feeder's own sensor reports it
62430
+ * (`state.food`). A coarse level code, not grams — `0` is empty. The
62431
+ * amount in the bowl is `bowlFood`. On dual-hopper models this is the
62432
+ * combined reading; per-hopper levels live in `food1`/`food2`. Null when
62433
+ * the device has not reported yet. */
61306
62434
  foodLevel: number().nullable().meta(readingMeta("none")),
62435
+ /** Food in the BOWL, a mass (stored in kilograms, D944). Reported only by a
62436
+ * feeder with a bowl scale; null when the model has none or its reading is
62437
+ * of an unproven unit. */
62438
+ bowlFood: number().nullable().meta(readingMeta("mass")),
62439
+ /** True while the pet is eating (`state.eating`). */
62440
+ eating: boolean(),
62441
+ /** Whether `markFoodReplenished` does anything on this model. When false
62442
+ * the client must not offer it. */
62443
+ supportsMarkRefilled: boolean(),
61307
62444
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
61308
62445
  * single-hopper models. */
61309
62446
  food1: number().nullable().meta(readingMeta("none")),
@@ -61311,12 +62448,17 @@ object({
61311
62448
  * single-hopper models. */
61312
62449
  food2: number().nullable().meta(readingMeta("none")),
61313
62450
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
61314
- * (`device_class: problem`, on = low). True when the bowl is empty /
61315
- * below the feeder's low threshold. */
62451
+ * (`device_class: problem`, on = low). True when the tank reports empty,
62452
+ * or, on a dual-hopper model, when EITHER hopper is empty. */
61316
62453
  lowFood: boolean(),
61317
- /** Battery charge 0..100 (%). Null on mains-powered models or when the
61318
- * device has no battery reading. */
62454
+ /** Battery charge 0..100 (%). On a feeder running on `mains` this is the
62455
+ * BACKUP battery's charge, not the power source. Null when the device has
62456
+ * no battery reading. */
61319
62457
  batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62458
+ /** What the feeder is powered from right now: `mains` (plugged in, the
62459
+ * battery is only a backup) or `battery`. Null when the device does not
62460
+ * report it. */
62461
+ powerSource: _enum(["mains", "battery"]).nullable(),
61320
62462
  /**
61321
62463
  * Desiccant life remaining, stored in seconds (duration base, D944).
61322
62464
  * Providers convert from the days the feeder reports. Null when the
@@ -61344,6 +62486,14 @@ object({
61344
62486
  feedSound: boolean(),
61345
62487
  /** Speaker / prompt volume level (device-scaled integer). */
61346
62488
  volume: number().meta(readingMeta("none")),
62489
+ /** Portion (grams) of the last manual feed sent from a single-hopper
62490
+ * stepper — the default the next manual feed starts from. Persisted per
62491
+ * device by the provider; null until a manual feed has been sent. */
62492
+ lastFeedGrams: gramsPortion.nullable(),
62493
+ /** Hopper-1 portion of the last manual feed (dual-hopper); null until sent. */
62494
+ lastFeedHopper1: gramsPortion.nullable(),
62495
+ /** Hopper-2 portion of the last manual feed (dual-hopper); null until sent. */
62496
+ lastFeedHopper2: gramsPortion.nullable(),
61347
62497
  /** Ms epoch when the slice was last refreshed from the cloud. */
61348
62498
  lastFetchedAt: number()
61349
62499
  });
@@ -61398,6 +62548,64 @@ DeviceType.PetFeeder, method(object({
61398
62548
  kind: "mutation",
61399
62549
  auth: "protected"
61400
62550
  });
62551
+ object({
62552
+ /** Water in the tank, 0..100 (%). Null while the source has not reported. */
62553
+ waterLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62554
+ /** Water in the tank, in litres. Null while the source has not reported. */
62555
+ waterVolume: number().min(0).nullable().meta(readingMeta("volume")),
62556
+ /** True when the tank is empty. Null while unknown. */
62557
+ empty: boolean().nullable(),
62558
+ /** True while the pump is running. Null while unknown. */
62559
+ pumpRunning: boolean().nullable(),
62560
+ /** True when the pump works on an on/off duty cycle rather than continuously. */
62561
+ pumpIntervalEnabled: boolean().nullable(),
62562
+ /** How long the pump runs per cycle, in seconds. */
62563
+ pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
62564
+ /** How long the pump rests per cycle, in seconds. */
62565
+ pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
62566
+ /** Water drunk in the last drink, in litres. */
62567
+ lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
62568
+ /** Water drunk in the current hour, in litres. */
62569
+ currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
62570
+ /** Water drunk over the last 24 hours (rolling), in litres. */
62571
+ last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
62572
+ /** Water drunk since local midnight, in litres. */
62573
+ todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
62574
+ /** Decoded fault message. Null means healthy; a non-null string is an active fault. */
62575
+ error: string().nullable(),
62576
+ /** Ms epoch when the slice was last refreshed. */
62577
+ lastFetchedAt: number()
62578
+ });
62579
+ var DeviceIdSchema = number().int().nonnegative();
62580
+ var MinutesSchema = number().int().min(0);
62581
+ DeviceType.PetFountain, method(object({
62582
+ deviceId: DeviceIdSchema,
62583
+ on: boolean()
62584
+ }), _void(), {
62585
+ kind: "mutation",
62586
+ auth: "protected"
62587
+ }), method(object({
62588
+ deviceId: DeviceIdSchema,
62589
+ on: boolean()
62590
+ }), _void(), {
62591
+ kind: "mutation",
62592
+ auth: "protected"
62593
+ }), method(object({
62594
+ deviceId: DeviceIdSchema,
62595
+ minutes: MinutesSchema
62596
+ }), _void(), {
62597
+ kind: "mutation",
62598
+ auth: "protected"
62599
+ }), method(object({
62600
+ deviceId: DeviceIdSchema,
62601
+ minutes: MinutesSchema
62602
+ }), _void(), {
62603
+ kind: "mutation",
62604
+ auth: "protected"
62605
+ }), method(object({ deviceId: DeviceIdSchema }), _void(), {
62606
+ kind: "mutation",
62607
+ auth: "protected"
62608
+ });
61401
62609
  var VehicleSchema = object({
61402
62610
  id: string(),
61403
62611
  name: string(),
@@ -61779,16 +62987,16 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
61779
62987
  auth: "admin"
61780
62988
  });
61781
62989
  object({
61782
- /** Instantaneous power draw in watts. */
61783
- watts: number().optional().meta(readingMeta("power", { label: "Power" })),
62990
+ /** Instantaneous power draw (watts). */
62991
+ power: number().optional().meta(readingMeta("power", { label: "Power" })),
61784
62992
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
61785
- kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
61786
- /** Voltage in volts. */
61787
- volts: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
62993
+ energy: number().optional().meta(readingMeta("energy", { label: "Energy" })),
62994
+ /** Voltage (volts). */
62995
+ voltage: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
61788
62996
  /** Current in amperes. */
61789
- amps: number().optional().meta(readingMeta("current", { label: "Current" })),
62997
+ current: number().optional().meta(readingMeta("current", { label: "Current" })),
61790
62998
  /** Apparent power in volt-amperes. */
61791
- va: number().optional().meta(readingMeta("apparent-power")),
62999
+ apparentPower: number().optional().meta(readingMeta("apparent-power")),
61792
63000
  /** Power factor (0..1) if reported. */
61793
63001
  powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
61794
63002
  /** Ms epoch when the slice was last updated. */
@@ -61843,16 +63051,13 @@ object({
61843
63051
  location: string().nullable(),
61844
63052
  /** GPS coordinates when available. Null otherwise. */
61845
63053
  gps: GpsLocationSchema.nullable(),
61846
- /** Optional battery level of the tracking device (0..100). Null
61847
- * when not reported. */
61848
- batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61849
63054
  /** Ms epoch when the slice was last updated. */
61850
63055
  lastChangedAt: number()
61851
63056
  });
61852
63057
  DeviceType.Presence;
61853
63058
  object({
61854
63059
  /** Current pressure in hPa. `null` when the sample was refused (D393). */
61855
- hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
63060
+ pressure: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
61856
63061
  /** Ms epoch when the slice was last updated. */
61857
63062
  lastFetchedAt: number()
61858
63063
  });
@@ -64855,7 +66060,7 @@ object({
64855
66060
  DeviceType.Sensor;
64856
66061
  object({
64857
66062
  /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64858
- celsius: number().nullable().meta(readingMeta("temperature", {
66063
+ temperature: number().nullable().meta(readingMeta("temperature", {
64859
66064
  precision: 1,
64860
66065
  label: "Temperature"
64861
66066
  })),
@@ -65119,7 +66324,7 @@ object({
65119
66324
  /** Speed tokens the hardware accepts — drives the UI selector. */
65120
66325
  availableFanSpeeds: array(string()),
65121
66326
  /** 0..100 cleaning-completion percentage of the current task, or null. */
65122
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
66327
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65123
66328
  /** Current error code (0 / null = no error). */
65124
66329
  errorCode: number().nullable().meta(readingMeta("none")),
65125
66330
  /** Human label for {@link errorCode}, or null when none / undecodable. */
@@ -65988,9 +67193,17 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
65988
67193
  kind: "mutation",
65989
67194
  auth: "admin"
65990
67195
  });
67196
+ discriminatedUnion("kind", [object({
67197
+ kind: literal("raw"),
67198
+ timings: array(number().int()),
67199
+ settings: TransmitSettingsSchema.optional()
67200
+ }), object({
67201
+ kind: literal("pronto"),
67202
+ code: string()
67203
+ })]);
65991
67204
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
65992
67205
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
65993
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, 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.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
67206
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, 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.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.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
65994
67207
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
65995
67208
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
65996
67209
  new Set(CAP_NAMES);
@@ -66043,7 +67256,7 @@ var AccessoryKind = {
66043
67256
  PrivacyMask: DeviceRole.PrivacyMask
66044
67257
  };
66045
67258
  AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
66046
- DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
67259
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, 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;
66047
67260
  new Set(Object.values(DeviceType));
66048
67261
  Object.freeze({
66049
67262
  "accessories.getStatus": {
@@ -66060,6 +67273,104 @@ Object.freeze({
66060
67273
  access: "create",
66061
67274
  role: "admin"
66062
67275
  },
67276
+ "actions.create": {
67277
+ capName: "actions",
67278
+ capScope: "system",
67279
+ addonId: null,
67280
+ access: "create",
67281
+ role: "admin"
67282
+ },
67283
+ "actions.delete": {
67284
+ capName: "actions",
67285
+ capScope: "system",
67286
+ addonId: null,
67287
+ access: "delete",
67288
+ role: "admin"
67289
+ },
67290
+ "actions.get": {
67291
+ capName: "actions",
67292
+ capScope: "system",
67293
+ addonId: null,
67294
+ access: "view",
67295
+ role: "viewer"
67296
+ },
67297
+ "actions.getLinks": {
67298
+ capName: "actions",
67299
+ capScope: "system",
67300
+ addonId: null,
67301
+ access: "view",
67302
+ role: "admin"
67303
+ },
67304
+ "actions.getTraces": {
67305
+ capName: "actions",
67306
+ capScope: "system",
67307
+ addonId: null,
67308
+ access: "view",
67309
+ role: "admin"
67310
+ },
67311
+ "actions.list": {
67312
+ capName: "actions",
67313
+ capScope: "system",
67314
+ addonId: null,
67315
+ access: "view",
67316
+ role: "admin"
67317
+ },
67318
+ "actions.listForCamera": {
67319
+ capName: "actions",
67320
+ capScope: "system",
67321
+ addonId: null,
67322
+ access: "view",
67323
+ role: "viewer"
67324
+ },
67325
+ "actions.run": {
67326
+ capName: "actions",
67327
+ capScope: "system",
67328
+ addonId: null,
67329
+ access: "create",
67330
+ role: "viewer"
67331
+ },
67332
+ "actions.setLinks": {
67333
+ capName: "actions",
67334
+ capScope: "system",
67335
+ addonId: null,
67336
+ access: "create",
67337
+ role: "admin"
67338
+ },
67339
+ "actions.stop": {
67340
+ capName: "actions",
67341
+ capScope: "system",
67342
+ addonId: null,
67343
+ access: "create",
67344
+ role: "admin"
67345
+ },
67346
+ "actions.update": {
67347
+ capName: "actions",
67348
+ capScope: "system",
67349
+ addonId: null,
67350
+ access: "create",
67351
+ role: "admin"
67352
+ },
67353
+ "actionSource.listActions": {
67354
+ capName: "action-source",
67355
+ capScope: "system",
67356
+ addonId: null,
67357
+ access: "view",
67358
+ role: "admin"
67359
+ },
67360
+ "actionSource.runAction": {
67361
+ capName: "action-source",
67362
+ capScope: "system",
67363
+ addonId: null,
67364
+ access: "create",
67365
+ role: "admin"
67366
+ },
67367
+ "actionSource.stopAction": {
67368
+ capName: "action-source",
67369
+ capScope: "system",
67370
+ addonId: null,
67371
+ access: "create",
67372
+ role: "admin"
67373
+ },
66063
67374
  "addonPages.listPages": {
66064
67375
  capName: "addon-pages",
66065
67376
  capScope: "system",
@@ -67341,6 +68652,13 @@ Object.freeze({
67341
68652
  access: "view",
67342
68653
  role: "admin"
67343
68654
  },
68655
+ "coreBlocks.getAutomationRunState": {
68656
+ capName: "core-blocks",
68657
+ capScope: "system",
68658
+ addonId: null,
68659
+ access: "view",
68660
+ role: "admin"
68661
+ },
67344
68662
  "coreBlocks.getBlockSettings": {
67345
68663
  capName: "core-blocks",
67346
68664
  capScope: "system",
@@ -67362,6 +68680,13 @@ Object.freeze({
67362
68680
  access: "view",
67363
68681
  role: "admin"
67364
68682
  },
68683
+ "coreBlocks.getTraces": {
68684
+ capName: "core-blocks",
68685
+ capScope: "system",
68686
+ addonId: null,
68687
+ access: "view",
68688
+ role: "admin"
68689
+ },
67365
68690
  "coreBlocks.getTypeDefs": {
67366
68691
  capName: "core-blocks",
67367
68692
  capScope: "system",
@@ -67397,6 +68722,13 @@ Object.freeze({
67397
68722
  access: "create",
67398
68723
  role: "admin"
67399
68724
  },
68725
+ "coreBlocks.reportAutomationRun": {
68726
+ capName: "core-blocks",
68727
+ capScope: "system",
68728
+ addonId: null,
68729
+ access: "create",
68730
+ role: "admin"
68731
+ },
67400
68732
  "coreBlocks.restart": {
67401
68733
  capName: "core-blocks",
67402
68734
  capScope: "system",
@@ -67859,6 +69191,13 @@ Object.freeze({
67859
69191
  access: "create",
67860
69192
  role: "admin"
67861
69193
  },
69194
+ "deviceExport.exposeEntity": {
69195
+ capName: "device-export",
69196
+ capScope: "system",
69197
+ addonId: null,
69198
+ access: "create",
69199
+ role: "admin"
69200
+ },
67862
69201
  "deviceExport.getDeviceLiveContribution": {
67863
69202
  capName: "device-export",
67864
69203
  capScope: "system",
@@ -67887,6 +69226,13 @@ Object.freeze({
67887
69226
  access: "view",
67888
69227
  role: "admin"
67889
69228
  },
69229
+ "deviceExport.listExposedEntities": {
69230
+ capName: "device-export",
69231
+ capScope: "system",
69232
+ addonId: null,
69233
+ access: "view",
69234
+ role: "admin"
69235
+ },
67890
69236
  "deviceExport.listSupportedDeviceKinds": {
67891
69237
  capName: "device-export",
67892
69238
  capScope: "system",
@@ -67894,6 +69240,13 @@ Object.freeze({
67894
69240
  access: "view",
67895
69241
  role: "admin"
67896
69242
  },
69243
+ "deviceExport.listSupportedEntityKinds": {
69244
+ capName: "device-export",
69245
+ capScope: "system",
69246
+ addonId: null,
69247
+ access: "view",
69248
+ role: "admin"
69249
+ },
67897
69250
  "deviceExport.unexposeDevice": {
67898
69251
  capName: "device-export",
67899
69252
  capScope: "system",
@@ -67901,6 +69254,13 @@ Object.freeze({
67901
69254
  access: "create",
67902
69255
  role: "admin"
67903
69256
  },
69257
+ "deviceExport.unexposeEntity": {
69258
+ capName: "device-export",
69259
+ capScope: "system",
69260
+ addonId: null,
69261
+ access: "create",
69262
+ role: "admin"
69263
+ },
67904
69264
  "deviceExtension.listCandidates": {
67905
69265
  capName: "device-extension",
67906
69266
  capScope: "device",
@@ -69476,6 +70836,20 @@ Object.freeze({
69476
70836
  access: "create",
69477
70837
  role: "viewer"
69478
70838
  },
70839
+ "irTransmitter.getInfo": {
70840
+ capName: "ir-transmitter",
70841
+ capScope: "device",
70842
+ addonId: null,
70843
+ access: "view",
70844
+ role: "viewer"
70845
+ },
70846
+ "irTransmitter.send": {
70847
+ capName: "ir-transmitter",
70848
+ capScope: "device",
70849
+ addonId: null,
70850
+ access: "create",
70851
+ role: "viewer"
70852
+ },
69479
70853
  "keychain.delete": {
69480
70854
  capName: "keychain",
69481
70855
  capScope: "system",
@@ -70708,6 +72082,13 @@ Object.freeze({
70708
72082
  access: "create",
70709
72083
  role: "admin"
70710
72084
  },
72085
+ "nodeRemovalHook.onNodeRemoved": {
72086
+ capName: "node-removal-hook",
72087
+ capScope: "system",
72088
+ addonId: null,
72089
+ access: "create",
72090
+ role: "admin"
72091
+ },
70711
72092
  "nodes.clusterAddonStatus": {
70712
72093
  capName: "nodes",
70713
72094
  capScope: "system",
@@ -71345,6 +72726,48 @@ Object.freeze({
71345
72726
  access: "create",
71346
72727
  role: "viewer"
71347
72728
  },
72729
+ "petFountain.getStatus": {
72730
+ capName: "pet-fountain",
72731
+ capScope: "device",
72732
+ addonId: null,
72733
+ access: "view",
72734
+ role: "viewer"
72735
+ },
72736
+ "petFountain.resetDailyConsumption": {
72737
+ capName: "pet-fountain",
72738
+ capScope: "device",
72739
+ addonId: null,
72740
+ access: "delete",
72741
+ role: "viewer"
72742
+ },
72743
+ "petFountain.setPump": {
72744
+ capName: "pet-fountain",
72745
+ capScope: "device",
72746
+ addonId: null,
72747
+ access: "create",
72748
+ role: "viewer"
72749
+ },
72750
+ "petFountain.setPumpInterval": {
72751
+ capName: "pet-fountain",
72752
+ capScope: "device",
72753
+ addonId: null,
72754
+ access: "create",
72755
+ role: "viewer"
72756
+ },
72757
+ "petFountain.setPumpOffMinutes": {
72758
+ capName: "pet-fountain",
72759
+ capScope: "device",
72760
+ addonId: null,
72761
+ access: "create",
72762
+ role: "viewer"
72763
+ },
72764
+ "petFountain.setPumpOnMinutes": {
72765
+ capName: "pet-fountain",
72766
+ capScope: "device",
72767
+ addonId: null,
72768
+ access: "create",
72769
+ role: "viewer"
72770
+ },
71348
72771
  "pipelineAnalytics.addRetrainFrameFromRecording": {
71349
72772
  capName: "pipeline-analytics",
71350
72773
  capScope: "device",
@@ -73599,6 +75022,41 @@ Object.freeze({
73599
75022
  access: "create",
73600
75023
  role: "admin"
73601
75024
  },
75025
+ "remote.getConfig": {
75026
+ capName: "remote",
75027
+ capScope: "system",
75028
+ addonId: null,
75029
+ access: "view",
75030
+ role: "admin"
75031
+ },
75032
+ "remote.listCommands": {
75033
+ capName: "remote",
75034
+ capScope: "system",
75035
+ addonId: null,
75036
+ access: "view",
75037
+ role: "admin"
75038
+ },
75039
+ "remote.sendCommand": {
75040
+ capName: "remote",
75041
+ capScope: "system",
75042
+ addonId: null,
75043
+ access: "create",
75044
+ role: "admin"
75045
+ },
75046
+ "remote.sendMediaCommand": {
75047
+ capName: "remote",
75048
+ capScope: "system",
75049
+ addonId: null,
75050
+ access: "create",
75051
+ role: "admin"
75052
+ },
75053
+ "remote.setConfig": {
75054
+ capName: "remote",
75055
+ capScope: "system",
75056
+ addonId: null,
75057
+ access: "create",
75058
+ role: "admin"
75059
+ },
73602
75060
  "runningState.getStatus": {
73603
75061
  capName: "running-state",
73604
75062
  capScope: "device",
@@ -73711,6 +75169,13 @@ Object.freeze({
73711
75169
  access: "create",
73712
75170
  role: "admin"
73713
75171
  },
75172
+ "serverManagement.getLaunchOverrides": {
75173
+ capName: "server-management",
75174
+ capScope: "system",
75175
+ addonId: null,
75176
+ access: "view",
75177
+ role: "admin"
75178
+ },
73714
75179
  "serverManagement.getServerPackageStatus": {
73715
75180
  capName: "server-management",
73716
75181
  capScope: "system",
@@ -73732,6 +75197,13 @@ Object.freeze({
73732
75197
  access: "create",
73733
75198
  role: "admin"
73734
75199
  },
75200
+ "serverManagement.setLaunchOverrides": {
75201
+ capName: "server-management",
75202
+ capScope: "system",
75203
+ addonId: null,
75204
+ access: "create",
75205
+ role: "admin"
75206
+ },
73735
75207
  "settingsStore.aggregate": {
73736
75208
  capName: "settings-store",
73737
75209
  capScope: "system",
@@ -74789,6 +76261,41 @@ Object.freeze({
74789
76261
  access: "view",
74790
76262
  role: "viewer"
74791
76263
  },
76264
+ "template.create": {
76265
+ capName: "template",
76266
+ capScope: "system",
76267
+ addonId: null,
76268
+ access: "create",
76269
+ role: "admin"
76270
+ },
76271
+ "template.delete": {
76272
+ capName: "template",
76273
+ capScope: "system",
76274
+ addonId: null,
76275
+ access: "delete",
76276
+ role: "admin"
76277
+ },
76278
+ "template.get": {
76279
+ capName: "template",
76280
+ capScope: "system",
76281
+ addonId: null,
76282
+ access: "view",
76283
+ role: "admin"
76284
+ },
76285
+ "template.list": {
76286
+ capName: "template",
76287
+ capScope: "system",
76288
+ addonId: null,
76289
+ access: "view",
76290
+ role: "admin"
76291
+ },
76292
+ "template.update": {
76293
+ capName: "template",
76294
+ capScope: "system",
76295
+ addonId: null,
76296
+ access: "create",
76297
+ role: "admin"
76298
+ },
74792
76299
  "terminalSession.adoptLegacyMonitor": {
74793
76300
  capName: "terminal-session",
74794
76301
  capScope: "system",
@@ -75970,7 +77477,7 @@ var ACTUATOR_CAP_VOCABULARY = {
75970
77477
  label: t("Full brightness", "Luminosità massima"),
75971
77478
  phrase: t("Set {device} to full brightness", "Porta {device} alla massima luminosità"),
75972
77479
  icon: "sun",
75973
- args: { percentage: 100 }
77480
+ args: { level: 100 }
75974
77481
  },
75975
77482
  {
75976
77483
  id: "brightness.half",
@@ -75978,7 +77485,7 @@ var ACTUATOR_CAP_VOCABULARY = {
75978
77485
  label: t("Half brightness", "Luminosità a metà"),
75979
77486
  phrase: t("Dim {device} to half", "Abbassa {device} a metà luminosità"),
75980
77487
  icon: "sun-dim",
75981
- args: { percentage: 50 }
77488
+ args: { level: 50 }
75982
77489
  },
75983
77490
  {
75984
77491
  id: "brightness.off",
@@ -75986,16 +77493,16 @@ var ACTUATOR_CAP_VOCABULARY = {
75986
77493
  label: t("Brightness off", "Luminosità a zero"),
75987
77494
  phrase: t("Dim {device} to zero", "Abbassa {device} a zero"),
75988
77495
  icon: "lightbulb-off",
75989
- args: { percentage: 0 }
77496
+ args: { level: 0 }
75990
77497
  },
75991
77498
  {
75992
77499
  id: "brightness.set",
75993
77500
  method: "setBrightness",
75994
77501
  label: t("Set brightness", "Imposta la luminosità"),
75995
- phrase: t("Set the brightness of {device} to {percentage}", "Imposta la luminosità di {device} a {percentage}"),
77502
+ phrase: t("Set the brightness of {device} to {level}", "Imposta la luminosità di {device} a {level}"),
75996
77503
  icon: "sun-medium",
75997
77504
  openArgs: [{
75998
- key: "percentage",
77505
+ key: "level",
75999
77506
  label: PERCENT,
76000
77507
  offer: {
76001
77508
  kind: "number",
@@ -76009,13 +77516,13 @@ var ACTUATOR_CAP_VOCABULARY = {
76009
77516
  ],
76010
77517
  state: [{
76011
77518
  kind: "numeric",
76012
- field: "percentage",
77519
+ field: "level",
76013
77520
  quantity: t("the brightness", "la luminosità"),
76014
77521
  label: t("Brightness", "Luminosità"),
76015
77522
  unit: "%",
76016
77523
  icon: "sun"
76017
77524
  }],
76018
- events: { onBrightnessChanged: { notOperatorFacing: "the `percentage` state trigger reads the same change" } }
77525
+ events: { onBrightnessChanged: { notOperatorFacing: "the `level` state trigger reads the same change" } }
76019
77526
  },
76020
77527
  color: {
76021
77528
  scope: "device",
@@ -76029,7 +77536,7 @@ var ACTUATOR_CAP_VOCABULARY = {
76029
77536
  icon: "sun",
76030
77537
  args: { color: {
76031
77538
  mode: "mired",
76032
- mireds: 370
77539
+ colorTemperature: 370
76033
77540
  } }
76034
77541
  },
76035
77542
  {
@@ -76040,7 +77547,7 @@ var ACTUATOR_CAP_VOCABULARY = {
76040
77547
  icon: "snowflake",
76041
77548
  args: { color: {
76042
77549
  mode: "mired",
76043
- mireds: 200
77550
+ colorTemperature: 200
76044
77551
  } }
76045
77552
  },
76046
77553
  {
@@ -76654,7 +78161,7 @@ var ACTUATOR_CAP_VOCABULARY = {
76654
78161
  label: t("Fan off", "Ventilatore spento"),
76655
78162
  phrase: t("Stop {device}", "Ferma {device}"),
76656
78163
  icon: "power-off",
76657
- args: { percentage: 0 }
78164
+ args: { speed: 0 }
76658
78165
  },
76659
78166
  {
76660
78167
  id: "fan.full",
@@ -76662,16 +78169,16 @@ var ACTUATOR_CAP_VOCABULARY = {
76662
78169
  label: t("Fan full", "Ventilatore al massimo"),
76663
78170
  phrase: t("Run {device} at full speed", "Porta {device} alla massima velocità"),
76664
78171
  icon: "fan",
76665
- args: { percentage: 100 }
78172
+ args: { speed: 100 }
76666
78173
  },
76667
78174
  {
76668
78175
  id: "fan.speed",
76669
78176
  method: "setPercentage",
76670
78177
  label: t("Set speed", "Imposta la velocità"),
76671
- phrase: t("Set the speed of {device} to {percentage}", "Imposta la velocità di {device} al {percentage}"),
78178
+ phrase: t("Set the speed of {device} to {speed}", "Imposta la velocità di {device} al {speed}"),
76672
78179
  icon: "fan",
76673
78180
  openArgs: [{
76674
- key: "percentage",
78181
+ key: "speed",
76675
78182
  label: t("Speed", "Velocità"),
76676
78183
  offer: {
76677
78184
  kind: "number",
@@ -76733,7 +78240,7 @@ var ACTUATOR_CAP_VOCABULARY = {
76733
78240
  ],
76734
78241
  state: [{
76735
78242
  kind: "numeric",
76736
- field: "percentage",
78243
+ field: "speed",
76737
78244
  quantity: t("the speed", "la velocità"),
76738
78245
  label: t("Speed", "Velocità"),
76739
78246
  unit: "%",
@@ -77099,6 +78606,11 @@ var ACTUATOR_CAP_VOCABULARY = {
77099
78606
  icon: "circle-dot"
77100
78607
  }]
77101
78608
  },
78609
+ "ir-transmitter": {
78610
+ scope: "device",
78611
+ noun: t("IR / RF transmitter", "Trasmettitore IR / RF"),
78612
+ notOperatorFacing: { send: "a raw pulse train is not something an operator composes in an automation; the remote that owns the command table sends it" }
78613
+ },
77102
78614
  control: {
77103
78615
  scope: "device",
77104
78616
  noun: t("Control", "Controllo"),
@@ -77531,7 +79043,7 @@ var APPLIANCE_CAP_VOCABULARY = {
77531
79043
  },
77532
79044
  {
77533
79045
  kind: "numeric",
77534
- field: "progressPercent",
79046
+ field: "progress",
77535
79047
  quantity: t("the cycle progress", "l’avanzamento del lavaggio"),
77536
79048
  label: t("Progress", "Avanzamento"),
77537
79049
  unit: "%",
@@ -77752,6 +79264,100 @@ var APPLIANCE_CAP_VOCABULARY = {
77752
79264
  }
77753
79265
  ]
77754
79266
  },
79267
+ "pet-fountain": {
79268
+ scope: "device",
79269
+ noun: t("Pet fountain", "Fontana per animali"),
79270
+ actions: [
79271
+ {
79272
+ id: "petFountain.pumpOn",
79273
+ method: "setPump",
79274
+ label: t("Pump on", "Pompa accesa"),
79275
+ phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
79276
+ icon: "play",
79277
+ args: { on: true }
79278
+ },
79279
+ {
79280
+ id: "petFountain.pumpOff",
79281
+ method: "setPump",
79282
+ label: t("Pump off", "Pompa spenta"),
79283
+ phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
79284
+ icon: "square",
79285
+ args: { on: false }
79286
+ },
79287
+ {
79288
+ id: "petFountain.intervalOn",
79289
+ method: "setPumpInterval",
79290
+ label: t("Pump cycle on", "Ciclo pompa attivo"),
79291
+ phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
79292
+ icon: "timer",
79293
+ args: { on: true }
79294
+ },
79295
+ {
79296
+ id: "petFountain.intervalOff",
79297
+ method: "setPumpInterval",
79298
+ label: t("Pump cycle off", "Ciclo pompa spento"),
79299
+ phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
79300
+ icon: "timer-off",
79301
+ args: { on: false }
79302
+ },
79303
+ {
79304
+ id: "petFountain.pumpOnMinutes",
79305
+ method: "setPumpOnMinutes",
79306
+ label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
79307
+ phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
79308
+ icon: "timer",
79309
+ openArgs: [{
79310
+ key: "minutes",
79311
+ label: t("Minutes", "Minuti"),
79312
+ offer: {
79313
+ kind: "number",
79314
+ min: 0,
79315
+ step: 1,
79316
+ unit: "min"
79317
+ }
79318
+ }]
79319
+ },
79320
+ {
79321
+ id: "petFountain.pumpOffMinutes",
79322
+ method: "setPumpOffMinutes",
79323
+ label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
79324
+ phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
79325
+ icon: "timer-off",
79326
+ openArgs: [{
79327
+ key: "minutes",
79328
+ label: t("Minutes", "Minuti"),
79329
+ offer: {
79330
+ kind: "number",
79331
+ min: 0,
79332
+ step: 1,
79333
+ unit: "min"
79334
+ }
79335
+ }]
79336
+ },
79337
+ {
79338
+ id: "petFountain.resetDailyConsumption",
79339
+ method: "resetDailyConsumption",
79340
+ label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
79341
+ phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
79342
+ icon: "rotate-ccw"
79343
+ }
79344
+ ],
79345
+ state: [{
79346
+ kind: "boolean",
79347
+ field: "empty",
79348
+ label: t("Tank empty", "Serbatoio vuoto"),
79349
+ whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
79350
+ whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
79351
+ icon: "droplet"
79352
+ }, {
79353
+ kind: "numeric",
79354
+ field: "waterLevel",
79355
+ quantity: t("the water level", "il livello dell’acqua"),
79356
+ label: t("Water level", "Livello dell’acqua"),
79357
+ unit: "%",
79358
+ icon: "droplets"
79359
+ }]
79360
+ },
77755
79361
  "media-player": {
77756
79362
  scope: "device",
77757
79363
  noun: t("Media player", "Lettore multimediale"),
@@ -77889,10 +79495,10 @@ var APPLIANCE_CAP_VOCABULARY = {
77889
79495
  id: "media.seek",
77890
79496
  method: "seek",
77891
79497
  label: t("Seek", "Vai al punto"),
77892
- phrase: t("Move {device} to {positionMs}", "Porta {device} al punto {positionMs}"),
79498
+ phrase: t("Move {device} to {position}", "Porta {device} al punto {position}"),
77893
79499
  icon: "fast-forward",
77894
79500
  openArgs: [{
77895
- key: "positionMs",
79501
+ key: "position",
77896
79502
  label: t("Position", "Posizione"),
77897
79503
  offer: {
77898
79504
  kind: "number",
@@ -78941,7 +80547,7 @@ var SENSOR_CAP_VOCABULARY = {
78941
80547
  state: [
78942
80548
  {
78943
80549
  kind: "numeric",
78944
- field: "co2Ppm",
80550
+ field: "co2",
78945
80551
  quantity: t("the CO₂", "la CO₂"),
78946
80552
  label: t("CO₂", "CO₂"),
78947
80553
  unit: "ppm",
@@ -78949,7 +80555,7 @@ var SENSOR_CAP_VOCABULARY = {
78949
80555
  },
78950
80556
  {
78951
80557
  kind: "numeric",
78952
- field: "vocPpb",
80558
+ field: "voc",
78953
80559
  quantity: t("the VOC level", "il livello di COV"),
78954
80560
  label: t("VOC", "COV"),
78955
80561
  unit: "ppb",
@@ -78985,7 +80591,7 @@ var SENSOR_CAP_VOCABULARY = {
78985
80591
  noun: t("Light level", "Luminosità ambiente"),
78986
80592
  state: [{
78987
80593
  kind: "numeric",
78988
- field: "lux",
80594
+ field: "illuminance",
78989
80595
  quantity: t("the light level", "la luminosità"),
78990
80596
  label: t("Illuminance", "Illuminamento"),
78991
80597
  unit: "lx",
@@ -79188,7 +80794,7 @@ var SENSOR_CAP_VOCABULARY = {
79188
80794
  noun: t("Humidity", "Umidità"),
79189
80795
  state: [{
79190
80796
  kind: "numeric",
79191
- field: "percent",
80797
+ field: "humidity",
79192
80798
  quantity: t("the humidity", "l’umidità"),
79193
80799
  label: t("Humidity", "Umidità"),
79194
80800
  unit: "%",
@@ -79236,7 +80842,7 @@ var SENSOR_CAP_VOCABULARY = {
79236
80842
  state: [
79237
80843
  {
79238
80844
  kind: "numeric",
79239
- field: "watts",
80845
+ field: "power",
79240
80846
  quantity: t("the power draw", "il consumo"),
79241
80847
  label: t("Power", "Potenza"),
79242
80848
  unit: "W",
@@ -79244,7 +80850,7 @@ var SENSOR_CAP_VOCABULARY = {
79244
80850
  },
79245
80851
  {
79246
80852
  kind: "numeric",
79247
- field: "kwhTotal",
80853
+ field: "energy",
79248
80854
  quantity: t("the energy used", "l’energia consumata"),
79249
80855
  label: t("Energy", "Energia"),
79250
80856
  unit: "kWh",
@@ -79252,7 +80858,7 @@ var SENSOR_CAP_VOCABULARY = {
79252
80858
  },
79253
80859
  {
79254
80860
  kind: "numeric",
79255
- field: "volts",
80861
+ field: "voltage",
79256
80862
  quantity: t("the voltage", "la tensione"),
79257
80863
  label: t("Voltage", "Tensione"),
79258
80864
  unit: "V",
@@ -79260,7 +80866,7 @@ var SENSOR_CAP_VOCABULARY = {
79260
80866
  },
79261
80867
  {
79262
80868
  kind: "numeric",
79263
- field: "amps",
80869
+ field: "current",
79264
80870
  quantity: t("the current", "la corrente"),
79265
80871
  label: t("Current", "Corrente"),
79266
80872
  unit: "A",
@@ -79277,13 +80883,6 @@ var SENSOR_CAP_VOCABULARY = {
79277
80883
  label: t("Where", "Posizione"),
79278
80884
  phrase: t("When {device} is at {value}", "Quando {device} è in {value}"),
79279
80885
  icon: "map-pin"
79280
- }, {
79281
- kind: "numeric",
79282
- field: "batteryPercent",
79283
- quantity: t("the phone battery", "la batteria del telefono"),
79284
- label: t("Battery", "Batteria"),
79285
- unit: "%",
79286
- icon: "battery"
79287
80886
  }]
79288
80887
  },
79289
80888
  "pressure-sensor": {
@@ -79291,7 +80890,7 @@ var SENSOR_CAP_VOCABULARY = {
79291
80890
  noun: t("Pressure", "Pressione"),
79292
80891
  state: [{
79293
80892
  kind: "numeric",
79294
- field: "hpa",
80893
+ field: "pressure",
79295
80894
  quantity: t("the pressure", "la pressione"),
79296
80895
  label: t("Pressure", "Pressione"),
79297
80896
  unit: "hPa",
@@ -79327,7 +80926,7 @@ var SENSOR_CAP_VOCABULARY = {
79327
80926
  noun: t("Temperature", "Temperatura"),
79328
80927
  state: [{
79329
80928
  kind: "numeric",
79330
- field: "celsius",
80929
+ field: "temperature",
79331
80930
  quantity: t("the temperature", "la temperatura"),
79332
80931
  label: t("Temperature", "Temperatura"),
79333
80932
  unit: "°C",
@@ -79398,7 +80997,7 @@ var SENSOR_CAP_VOCABULARY = {
79398
80997
  state: [
79399
80998
  {
79400
80999
  kind: "numeric",
79401
- field: "percentage",
81000
+ field: "level",
79402
81001
  quantity: t("the battery level", "il livello della batteria"),
79403
81002
  label: t("Battery", "Batteria"),
79404
81003
  unit: "%",
@@ -79440,7 +81039,7 @@ var SENSOR_CAP_VOCABULARY = {
79440
81039
  icon: "battery-charging"
79441
81040
  }
79442
81041
  ],
79443
- events: { onStatusChanged: { notOperatorFacing: "the percentage / sleeping / charging state triggers each read one field of this change" } },
81042
+ events: { onStatusChanged: { notOperatorFacing: "the level / sleeping / charging state triggers each read one field of this change" } },
79444
81043
  extraEvents: [{
79445
81044
  category: EventCategory.DeviceAwake,
79446
81045
  label: t("Woke up", "Risvegliato"),
@@ -79458,7 +81057,7 @@ var SENSOR_CAP_VOCABULARY = {
79458
81057
  noun: t("Network link", "Collegamento di rete"),
79459
81058
  state: [{
79460
81059
  kind: "numeric",
79461
- field: "signalPercent",
81060
+ field: "signal",
79462
81061
  quantity: t("the signal strength", "l’intensità del segnale"),
79463
81062
  label: t("Signal", "Segnale"),
79464
81063
  unit: "%",
@@ -79551,6 +81150,8 @@ var ML = "model and training machinery — no device action";
79551
81150
  var INTERNAL = "internal plumbing between addons — never operator-facing";
79552
81151
  /** Every system-scoped cap, with why it is not a device's vocabulary. */
79553
81152
  var SYSTEM_CAP_VOCABULARY = {
81153
+ "action-source": system(INTERNAL),
81154
+ actions: system("declared actions — run from the Actions page and from the cameras that link them"),
79554
81155
  "addon-pages": system(UI_PLUMBING),
79555
81156
  "addon-pages-source": system(UI_PLUMBING),
79556
81157
  "addon-routes": system(UI_PLUMBING),
@@ -79581,6 +81182,7 @@ var SYSTEM_CAP_VOCABULARY = {
79581
81182
  "device-manager": system("the device registry itself — rename, move and remove are administration"),
79582
81183
  "device-provider": system(INTERNAL),
79583
81184
  "device-removal-hook": system(INTERNAL),
81185
+ "node-removal-hook": system(INTERNAL),
79584
81186
  "device-state": system("the runtime-state mirror every trigger already reads through"),
79585
81187
  "display-palette": system("the cluster display palette — one colour table for every client, not a device action"),
79586
81188
  "embedding-encoder": system(ML),
@@ -79620,6 +81222,7 @@ var SYSTEM_CAP_VOCABULARY = {
79620
81222
  "recording-archive": system(STORAGE),
79621
81223
  "recording-export": system("recording exports — requested from the timeline, not an automation step"),
79622
81224
  records: system(INTERNAL),
81225
+ remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
79623
81226
  "server-management": system(CLUSTER),
79624
81227
  "settings-store": system(INTERNAL),
79625
81228
  "smtp-provider": system(INTERNAL),
@@ -79632,6 +81235,7 @@ var SYSTEM_CAP_VOCABULARY = {
79632
81235
  "storage-provider": system(STORAGE),
79633
81236
  "stream-broker": system(PIPELINE),
79634
81237
  system: system(CLUSTER),
81238
+ template: system("operator-authored templates — edited in their own editor, never actuated"),
79635
81239
  "terminal-session": system(CLUSTER),
79636
81240
  toast: system(UI_PLUMBING),
79637
81241
  training: system(ML),
@@ -79691,7 +81295,7 @@ function projectAction(cap, action) {
79691
81295
  };
79692
81296
  }
79693
81297
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
79694
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, 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.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
81298
+ DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, 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.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;
79695
81299
  DeviceFeature.BatteryOperated;
79696
81300
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
79697
81301
  var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";
@@ -79747,6 +81351,31 @@ Object.freeze({
79747
81351
  form: "single",
79748
81352
  optional: false
79749
81353
  }],
81354
+ "actions.get": [{
81355
+ name: "deviceId",
81356
+ form: "single",
81357
+ optional: true
81358
+ }],
81359
+ "actions.getLinks": [{
81360
+ name: "deviceId",
81361
+ form: "single",
81362
+ optional: false
81363
+ }],
81364
+ "actions.listForCamera": [{
81365
+ name: "deviceId",
81366
+ form: "single",
81367
+ optional: false
81368
+ }],
81369
+ "actions.run": [{
81370
+ name: "deviceId",
81371
+ form: "single",
81372
+ optional: true
81373
+ }],
81374
+ "actions.setLinks": [{
81375
+ name: "deviceId",
81376
+ form: "single",
81377
+ optional: false
81378
+ }],
79750
81379
  "addonSettings.getDeviceSettings": [{
79751
81380
  name: "deviceId",
79752
81381
  form: "single",
@@ -80798,6 +82427,16 @@ Object.freeze({
80798
82427
  form: "single",
80799
82428
  optional: false
80800
82429
  }],
82430
+ "irTransmitter.getInfo": [{
82431
+ name: "deviceId",
82432
+ form: "single",
82433
+ optional: false
82434
+ }],
82435
+ "irTransmitter.send": [{
82436
+ name: "deviceId",
82437
+ form: "single",
82438
+ optional: false
82439
+ }],
80801
82440
  "keychain.deleteDeviceScope": [{
80802
82441
  name: "deviceId",
80803
82442
  form: "single",
@@ -81207,6 +82846,36 @@ Object.freeze({
81207
82846
  form: "single",
81208
82847
  optional: false
81209
82848
  }],
82849
+ "petFountain.getStatus": [{
82850
+ name: "deviceId",
82851
+ form: "single",
82852
+ optional: false
82853
+ }],
82854
+ "petFountain.resetDailyConsumption": [{
82855
+ name: "deviceId",
82856
+ form: "single",
82857
+ optional: false
82858
+ }],
82859
+ "petFountain.setPump": [{
82860
+ name: "deviceId",
82861
+ form: "single",
82862
+ optional: false
82863
+ }],
82864
+ "petFountain.setPumpInterval": [{
82865
+ name: "deviceId",
82866
+ form: "single",
82867
+ optional: false
82868
+ }],
82869
+ "petFountain.setPumpOffMinutes": [{
82870
+ name: "deviceId",
82871
+ form: "single",
82872
+ optional: false
82873
+ }],
82874
+ "petFountain.setPumpOnMinutes": [{
82875
+ name: "deviceId",
82876
+ form: "single",
82877
+ optional: false
82878
+ }],
81210
82879
  "pipelineAnalytics.addRetrainFrameFromRecording": [{
81211
82880
  name: "deviceId",
81212
82881
  form: "single",
@@ -82023,6 +83692,31 @@ Object.freeze({
82023
83692
  form: "single",
82024
83693
  optional: true
82025
83694
  }],
83695
+ "remote.getConfig": [{
83696
+ name: "deviceId",
83697
+ form: "single",
83698
+ optional: false
83699
+ }],
83700
+ "remote.listCommands": [{
83701
+ name: "deviceId",
83702
+ form: "single",
83703
+ optional: false
83704
+ }],
83705
+ "remote.sendCommand": [{
83706
+ name: "deviceId",
83707
+ form: "single",
83708
+ optional: false
83709
+ }],
83710
+ "remote.sendMediaCommand": [{
83711
+ name: "deviceId",
83712
+ form: "single",
83713
+ optional: false
83714
+ }],
83715
+ "remote.setConfig": [{
83716
+ name: "deviceId",
83717
+ form: "single",
83718
+ optional: false
83719
+ }],
82026
83720
  "runningState.getStatus": [{
82027
83721
  name: "deviceId",
82028
83722
  form: "single",
@@ -83828,8 +85522,17 @@ DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
83828
85522
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
83829
85523
  DEFAULT_NATIVE_LEASE_SETTINGS.sceneBudgetMb;
83830
85524
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
85525
+ String.raw`(?<=^|["'(\s>,\[{])`;
83831
85526
  var MB = 1024 * 1024;
83832
85527
  1024 * MB, 3072 * MB;
85528
+ object({
85529
+ manufacturer: string().optional(),
85530
+ supportedModels: array(string()).optional(),
85531
+ supportedController: string().optional(),
85532
+ commandsEncoding: string(),
85533
+ delay: number().nonnegative().optional(),
85534
+ commands: record(string(), unknown())
85535
+ });
83833
85536
  //#endregion
83834
85537
  //#region src/custom-actions.ts
83835
85538
  var SourceIdSchema = string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug");