@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.
@@ -6,7 +6,7 @@ let node_fs = require("node:fs");
6
6
  let node_path = require("node:path");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_url = require("node:url");
9
- //#region ../types/dist/event-category-CT-hz5Rb.mjs
9
+ //#region ../types/dist/event-category-DmZDPDf_.mjs
10
10
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
11
11
  EventCategory["SystemBoot"] = "system.boot";
12
12
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -633,7 +633,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
633
633
  * (a track was promoted to a stationary-registry entry) or departs (the
634
634
  * object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
635
635
  * the dedicated "Stationary" section — never the live event feed. Payload:
636
- * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
636
+ * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp, observedAt, zones }`.
637
637
  */
638
638
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
639
639
  /**
@@ -771,6 +771,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
771
771
  */
772
772
  EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
773
773
  /**
774
+ * An action run started or ended. Emitted by the `actions` surface.
775
+ * Payload: `ActionRunChanged` (`{ actionId, runId, isRunning, outcome?, lastRunAt, lastError }`).
776
+ */
777
+ EventCategory["ActionsRunChanged"] = "actions.onRunChanged";
778
+ /**
774
779
  * Progress update from a running model conversion job.
775
780
  * Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
776
781
  * Emitted by `addon-model-studio` on the converting node.
@@ -5203,6 +5208,14 @@ function object(shape, params) {
5203
5208
  ...normalizeParams(params)
5204
5209
  });
5205
5210
  }
5211
+ function strictObject(shape, params) {
5212
+ return new ZodObject({
5213
+ type: "object",
5214
+ shape,
5215
+ catchall: never(),
5216
+ ...normalizeParams(params)
5217
+ });
5218
+ }
5206
5219
  function looseObject(shape, params) {
5207
5220
  return new ZodObject({
5208
5221
  type: "object",
@@ -5582,7 +5595,7 @@ var ZodIssueCode = {
5582
5595
  var ZodFirstPartyTypeKind;
5583
5596
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5584
5597
  //#endregion
5585
- //#region ../types/dist/sleep--jXS9hWo.mjs
5598
+ //#region ../types/dist/sleep-6eG9C8Xl.mjs
5586
5599
  /**
5587
5600
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5588
5601
  * window to float samples (D455).
@@ -8823,6 +8836,52 @@ function optionalMethod(input, output, options) {
8823
8836
  providerOptional: true
8824
8837
  };
8825
8838
  }
8839
+ /**
8840
+ * `addon-pages-source` — collection cap exposing per-provider raw page
8841
+ * declarations. Every addon that contributes a UI page registers a
8842
+ * provider here. The hub-side singleton aggregator (`addon-pages` cap,
8843
+ * see `addon-pages.cap.ts`) walks this collection, stamps versioned
8844
+ * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
8845
+ * that admin-ui consumes.
8846
+ *
8847
+ * The split exists because the public listing has a different output
8848
+ * shape than the per-provider raw declarations, and we want both ends
8849
+ * to flow through codegen instead of relying on a hand-written wrapper.
8850
+ */
8851
+ var AddonPageDeclarationSchema = object({
8852
+ id: string(),
8853
+ label: string(),
8854
+ icon: string(),
8855
+ path: string(),
8856
+ /**
8857
+ * Module Federation remote name — must match the `name` field on the
8858
+ * page addon's `federation()` plugin config. Used by admin-ui's
8859
+ * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
8860
+ * Conventionally `addon_<id>_page` (snake_case; MF names cannot
8861
+ * contain hyphens).
8862
+ */
8863
+ remoteName: string(),
8864
+ /**
8865
+ * Bundle filename inside the addon's `dist/` dir served at
8866
+ * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
8867
+ * is always `'remoteEntry.js'`; the value is kept on the metadata so
8868
+ * the static-file route can compute an mtime-based cache-buster URL
8869
+ * without a separate filesystem stat.
8870
+ */
8871
+ bundle: string(),
8872
+ /**
8873
+ * Sidebar section this page docks into. Well-known ids: `'detection'`,
8874
+ * `'cluster'`, `'administration'` — the page renders inside that group.
8875
+ * Any OTHER string creates (or joins) a custom section rendered after
8876
+ * the built-in groups; its label comes from `sectionLabel` (first
8877
+ * declaration wins), falling back to the id. Absent → the legacy
8878
+ * "Addon Pages" group.
8879
+ */
8880
+ section: string().optional(),
8881
+ /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
8882
+ sectionLabel: string().optional()
8883
+ });
8884
+ method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
8826
8885
  var StaticDirOutputSchema$1 = object({ staticDir: string() });
8827
8886
  var VersionOutputSchema$1 = object({ version: string() });
8828
8887
  method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
@@ -8848,6 +8907,27 @@ function customAction(input, output, options) {
8848
8907
  ...options?.caller ? { caller: "required" } : {}
8849
8908
  };
8850
8909
  }
8910
+ /**
8911
+ * The pipeline step-input shape and the run-result bridge, in a module that
8912
+ * imports nothing but zod and types.
8913
+ *
8914
+ * WHY A SEPARATE FILE (addon footprint, D28): both were declared in
8915
+ * `pipeline-executor.cap.ts`, whose import evaluates the whole executor schema
8916
+ * graph. An addon that only needs these two (the benchmark's `runPipelineTest`
8917
+ * action) paid ~130 MB of heap at load. `pipeline-executor.cap.ts` re-exports
8918
+ * them, so every existing import keeps working; addon server code takes them
8919
+ * from `@camstack/types/addon`.
8920
+ */
8921
+ var PipelineStepInputSchema = lazy(() => object({
8922
+ addonId: string(),
8923
+ modelId: string().optional(),
8924
+ enabled: boolean().default(true),
8925
+ children: array(PipelineStepInputSchema).optional(),
8926
+ settings: record(string(), unknown()).optional(),
8927
+ jumpDeviceKey: string().optional()
8928
+ }));
8929
+ /** Typed bridge to the recursive `FrameResult` graph; validation is structural. */
8930
+ var PipelineRunResultBridge = custom();
8851
8931
  var StaticDirOutputSchema = object({ staticDir: string() });
8852
8932
  var VersionOutputSchema = object({ version: string() });
8853
8933
  method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
@@ -8918,6 +8998,8 @@ Object.freeze({ "doorbell": "lastPressedAt" });
8918
8998
  /** Every capability name, so addon code can check a name without the schema graph. */
8919
8999
  var CAP_NAMES = Object.freeze([
8920
9000
  "accessories",
9001
+ "action-source",
9002
+ "actions",
8921
9003
  "addon-pages",
8922
9004
  "addon-pages-source",
8923
9005
  "addon-routes",
@@ -9001,6 +9083,7 @@ var CAP_NAMES = Object.freeze([
9001
9083
  "image-settings",
9002
9084
  "integrations",
9003
9085
  "intercom",
9086
+ "ir-transmitter",
9004
9087
  "keychain",
9005
9088
  "keychain-admin",
9006
9089
  "lawn-mower-control",
@@ -9029,6 +9112,7 @@ var CAP_NAMES = Object.freeze([
9029
9112
  "network-link",
9030
9113
  "network-quality",
9031
9114
  "node-certificates",
9115
+ "node-removal-hook",
9032
9116
  "nodes",
9033
9117
  "notification-output",
9034
9118
  "notification-rules",
@@ -9039,6 +9123,7 @@ var CAP_NAMES = Object.freeze([
9039
9123
  "osd",
9040
9124
  "osd-manager",
9041
9125
  "pet-feeder",
9126
+ "pet-fountain",
9042
9127
  "pipeline-analytics",
9043
9128
  "pipeline-executor",
9044
9129
  "pipeline-orchestrator",
@@ -9059,6 +9144,7 @@ var CAP_NAMES = Object.freeze([
9059
9144
  "recording-onboard",
9060
9145
  "recording-signal",
9061
9146
  "records",
9147
+ "remote",
9062
9148
  "running-state",
9063
9149
  "scene-monitor",
9064
9150
  "script-runner",
@@ -9082,6 +9168,7 @@ var CAP_NAMES = Object.freeze([
9082
9168
  "system",
9083
9169
  "tamper",
9084
9170
  "temperature-sensor",
9171
+ "template",
9085
9172
  "terminal-session",
9086
9173
  "toast",
9087
9174
  "training",
@@ -9106,11 +9193,11 @@ var CAP_NAMES = Object.freeze([
9106
9193
  Object.freeze({
9107
9194
  "air-quality-sensor": [
9108
9195
  {
9109
- "path": "co2Ppm",
9196
+ "path": "co2",
9110
9197
  "valueType": "number"
9111
9198
  },
9112
9199
  {
9113
- "path": "vocPpb",
9200
+ "path": "voc",
9114
9201
  "valueType": "number"
9115
9202
  },
9116
9203
  {
@@ -9160,7 +9247,7 @@ Object.freeze({
9160
9247
  }
9161
9248
  ],
9162
9249
  "ambient-light-sensor": [{
9163
- "path": "lux",
9250
+ "path": "illuminance",
9164
9251
  "valueType": "number"
9165
9252
  }],
9166
9253
  "audio-metrics": [
@@ -9221,7 +9308,7 @@ Object.freeze({
9221
9308
  ],
9222
9309
  "battery": [
9223
9310
  {
9224
- "path": "percentage",
9311
+ "path": "level",
9225
9312
  "valueType": "number"
9226
9313
  },
9227
9314
  {
@@ -9243,7 +9330,7 @@ Object.freeze({
9243
9330
  "valueType": "number"
9244
9331
  },
9245
9332
  {
9246
- "path": "volts",
9333
+ "path": "voltage",
9247
9334
  "valueType": "number"
9248
9335
  },
9249
9336
  {
@@ -9264,7 +9351,7 @@ Object.freeze({
9264
9351
  "valueType": "boolean"
9265
9352
  }],
9266
9353
  "brightness": [{
9267
- "path": "percentage",
9354
+ "path": "level",
9268
9355
  "valueType": "number"
9269
9356
  }],
9270
9357
  "broker": [{
@@ -9465,7 +9552,7 @@ Object.freeze({
9465
9552
  "valueType": "number"
9466
9553
  },
9467
9554
  {
9468
- "path": "mireds",
9555
+ "path": "colorTemperature",
9469
9556
  "valueType": "number"
9470
9557
  }
9471
9558
  ],
@@ -9644,15 +9731,15 @@ Object.freeze({
9644
9731
  "valueType": "string"
9645
9732
  },
9646
9733
  {
9647
- "path": "remainingSeconds",
9734
+ "path": "remaining",
9648
9735
  "valueType": "number"
9649
9736
  },
9650
9737
  {
9651
- "path": "delayedStartSeconds",
9738
+ "path": "delayedStart",
9652
9739
  "valueType": "number"
9653
9740
  },
9654
9741
  {
9655
- "path": "progressPercent",
9742
+ "path": "progress",
9656
9743
  "valueType": "number"
9657
9744
  },
9658
9745
  {
@@ -9766,7 +9853,7 @@ Object.freeze({
9766
9853
  ],
9767
9854
  "fan-control": [
9768
9855
  {
9769
- "path": "percentage",
9856
+ "path": "speed",
9770
9857
  "valueType": "number"
9771
9858
  },
9772
9859
  {
@@ -9866,7 +9953,7 @@ Object.freeze({
9866
9953
  }
9867
9954
  ],
9868
9955
  "humidity-sensor": [{
9869
- "path": "percent",
9956
+ "path": "humidity",
9870
9957
  "valueType": "number"
9871
9958
  }],
9872
9959
  "image": [{
@@ -9976,7 +10063,7 @@ Object.freeze({
9976
10063
  "valueType": "number"
9977
10064
  },
9978
10065
  {
9979
- "path": "progressPercent",
10066
+ "path": "progress",
9980
10067
  "valueType": "number"
9981
10068
  },
9982
10069
  {
@@ -10022,6 +10109,11 @@ Object.freeze({
10022
10109
  "standby"
10023
10110
  ]
10024
10111
  },
10112
+ {
10113
+ "path": "stateSource",
10114
+ "valueType": "enum",
10115
+ "enumValues": ["reported", "assumed"]
10116
+ },
10025
10117
  {
10026
10118
  "path": "volumeLevel",
10027
10119
  "valueType": "number"
@@ -10055,11 +10147,11 @@ Object.freeze({
10055
10147
  "valueType": "string"
10056
10148
  },
10057
10149
  {
10058
- "path": "positionMs",
10150
+ "path": "position",
10059
10151
  "valueType": "number"
10060
10152
  },
10061
10153
  {
10062
- "path": "durationMs",
10154
+ "path": "duration",
10063
10155
  "valueType": "number"
10064
10156
  },
10065
10157
  {
@@ -10161,11 +10253,11 @@ Object.freeze({
10161
10253
  ]
10162
10254
  },
10163
10255
  {
10164
- "path": "signalPercent",
10256
+ "path": "signal",
10165
10257
  "valueType": "number"
10166
10258
  },
10167
10259
  {
10168
- "path": "rssiDbm",
10260
+ "path": "rssi",
10169
10261
  "valueType": "number"
10170
10262
  },
10171
10263
  {
@@ -10220,6 +10312,18 @@ Object.freeze({
10220
10312
  "path": "foodLevel",
10221
10313
  "valueType": "number"
10222
10314
  },
10315
+ {
10316
+ "path": "bowlFood",
10317
+ "valueType": "number"
10318
+ },
10319
+ {
10320
+ "path": "eating",
10321
+ "valueType": "boolean"
10322
+ },
10323
+ {
10324
+ "path": "supportsMarkRefilled",
10325
+ "valueType": "boolean"
10326
+ },
10223
10327
  {
10224
10328
  "path": "food1",
10225
10329
  "valueType": "number"
@@ -10236,6 +10340,11 @@ Object.freeze({
10236
10340
  "path": "batteryPower",
10237
10341
  "valueType": "number"
10238
10342
  },
10343
+ {
10344
+ "path": "powerSource",
10345
+ "valueType": "enum",
10346
+ "enumValues": ["mains", "battery"]
10347
+ },
10239
10348
  {
10240
10349
  "path": "desiccantLeftDays",
10241
10350
  "valueType": "number"
@@ -10280,27 +10389,89 @@ Object.freeze({
10280
10389
  {
10281
10390
  "path": "volume",
10282
10391
  "valueType": "number"
10392
+ },
10393
+ {
10394
+ "path": "lastFeedGrams",
10395
+ "valueType": "number"
10396
+ },
10397
+ {
10398
+ "path": "lastFeedHopper1",
10399
+ "valueType": "number"
10400
+ },
10401
+ {
10402
+ "path": "lastFeedHopper2",
10403
+ "valueType": "number"
10404
+ }
10405
+ ],
10406
+ "pet-fountain": [
10407
+ {
10408
+ "path": "waterLevel",
10409
+ "valueType": "number"
10410
+ },
10411
+ {
10412
+ "path": "waterVolume",
10413
+ "valueType": "number"
10414
+ },
10415
+ {
10416
+ "path": "empty",
10417
+ "valueType": "boolean"
10418
+ },
10419
+ {
10420
+ "path": "pumpRunning",
10421
+ "valueType": "boolean"
10422
+ },
10423
+ {
10424
+ "path": "pumpIntervalEnabled",
10425
+ "valueType": "boolean"
10426
+ },
10427
+ {
10428
+ "path": "pumpOnTime",
10429
+ "valueType": "number"
10430
+ },
10431
+ {
10432
+ "path": "pumpOffTime",
10433
+ "valueType": "number"
10434
+ },
10435
+ {
10436
+ "path": "lastDrink",
10437
+ "valueType": "number"
10438
+ },
10439
+ {
10440
+ "path": "currentHourDrink",
10441
+ "valueType": "number"
10442
+ },
10443
+ {
10444
+ "path": "last24hDrink",
10445
+ "valueType": "number"
10446
+ },
10447
+ {
10448
+ "path": "todayDrink",
10449
+ "valueType": "number"
10450
+ },
10451
+ {
10452
+ "path": "error",
10453
+ "valueType": "string"
10283
10454
  }
10284
10455
  ],
10285
10456
  "power-meter": [
10286
10457
  {
10287
- "path": "watts",
10458
+ "path": "power",
10288
10459
  "valueType": "number"
10289
10460
  },
10290
10461
  {
10291
- "path": "kwhTotal",
10462
+ "path": "energy",
10292
10463
  "valueType": "number"
10293
10464
  },
10294
10465
  {
10295
- "path": "volts",
10466
+ "path": "voltage",
10296
10467
  "valueType": "number"
10297
10468
  },
10298
10469
  {
10299
- "path": "amps",
10470
+ "path": "current",
10300
10471
  "valueType": "number"
10301
10472
  },
10302
10473
  {
10303
- "path": "va",
10474
+ "path": "apparentPower",
10304
10475
  "valueType": "number"
10305
10476
  },
10306
10477
  {
@@ -10328,14 +10499,10 @@ Object.freeze({
10328
10499
  {
10329
10500
  "path": "gps.accuracyMeters",
10330
10501
  "valueType": "number"
10331
- },
10332
- {
10333
- "path": "batteryPercent",
10334
- "valueType": "number"
10335
10502
  }
10336
10503
  ],
10337
10504
  "pressure-sensor": [{
10338
- "path": "hpa",
10505
+ "path": "pressure",
10339
10506
  "valueType": "number"
10340
10507
  }],
10341
10508
  "privacy-mask": [{
@@ -10649,7 +10816,7 @@ Object.freeze({
10649
10816
  "valueType": "boolean"
10650
10817
  }],
10651
10818
  "temperature-sensor": [{
10652
- "path": "celsius",
10819
+ "path": "temperature",
10653
10820
  "valueType": "number"
10654
10821
  }],
10655
10822
  "update": [
@@ -10697,7 +10864,7 @@ Object.freeze({
10697
10864
  "valueType": "string"
10698
10865
  },
10699
10866
  {
10700
- "path": "progressPercent",
10867
+ "path": "progress",
10701
10868
  "valueType": "number"
10702
10869
  },
10703
10870
  {
@@ -10823,7 +10990,7 @@ object({
10823
10990
  params: record(string(), union([string(), number()])).optional()
10824
10991
  });
10825
10992
  //#endregion
10826
- //#region ../types/dist/err-msg-DIorGYeC.mjs
10993
+ //#region ../types/dist/err-msg-DWXWc0Di.mjs
10827
10994
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
10828
10995
  DeviceType["Camera"] = "camera";
10829
10996
  DeviceType["Hub"] = "hub";
@@ -10915,6 +11082,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
10915
11082
  * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
10916
11083
  * integrations sharing the same food/desiccant/hopper surface. */
10917
11084
  DeviceType["PetFeeder"] = "pet-feeder";
11085
+ /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
11086
+ * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
11087
+ * of a container and is BUILT as a composed device (D967). */
11088
+ DeviceType["PetFountain"] = "pet-fountain";
10918
11089
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
10919
11090
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
10920
11091
  * Sources: native Bosch Home Connect, reusable by similar integrations.
@@ -11169,10 +11340,13 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11169
11340
  /** Fallback when the notifier service name doesn't match a known
11170
11341
  * pattern. */
11171
11342
  DeviceRole["GenericNotifier"] = "generic-notifier";
11343
+ /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
11344
+ * cap. A `DeviceType.Generic` device. */
11345
+ DeviceRole["IrTransmitter"] = "ir-transmitter";
11172
11346
  return DeviceRole;
11173
11347
  }({});
11174
11348
  //#endregion
11175
- //#region ../types/dist/composition-Bb8iwHEJ.mjs
11349
+ //#region ../types/dist/composition-kSuOifuG.mjs
11176
11350
  /**
11177
11351
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11178
11352
  *
@@ -12257,12 +12431,28 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
12257
12431
  ])
12258
12432
  })
12259
12433
  ]);
12260
- var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
12434
+ var forwardArgUnion = union([
12435
+ string(),
12436
+ number(),
12437
+ boolean(),
12438
+ strictObject({ ["$input"]: string().min(1) }),
12439
+ record(string().min(1), lazy(() => forwardArgUnion))
12440
+ ]);
12441
+ var CompositionForwardArgSchema = forwardArgUnion;
12442
+ /**
12443
+ * `forward` (D659, amended by D967): one method of a BUILT capability is one
12444
+ * call of `cap.method` on `source`. Without `args` the built method's input
12445
+ * passes through minus `deviceId`; with `args` the template IS the source's
12446
+ * whole input. `deviceId` is always the source's own, never an argument.
12447
+ */
12448
+ var CompositionForwardCommandSchema = object({
12261
12449
  kind: literal("forward"),
12262
12450
  source: CompositionSourceRefSchema,
12263
12451
  cap: string().min(1),
12264
- method: string().min(1)
12265
- }), CompositionCodeSourceSchema]);
12452
+ method: string().min(1),
12453
+ args: record(string().min(1), CompositionForwardArgSchema).optional()
12454
+ });
12455
+ var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
12266
12456
  /** An item key inside an item-array cap (`consumables.items[<key>]`). */
12267
12457
  var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
12268
12458
  /**
@@ -12272,7 +12462,13 @@ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
12272
12462
  */
12273
12463
  var CompositionItemEntrySchema = object({
12274
12464
  label: string().min(1).max(80),
12275
- fields: record(string().min(1), CompositionFieldSourceSchema)
12465
+ fields: record(string().min(1), CompositionFieldSourceSchema),
12466
+ /**
12467
+ * What the cap's `reset` does for THIS item (consumables). Present = the item
12468
+ * is resettable and a `reset` call naming its key forwards here. The item key
12469
+ * selects the command, so it is never an argument: there is no pass-through.
12470
+ */
12471
+ reset: CompositionForwardCommandSchema.optional()
12276
12472
  });
12277
12473
  var CompositionFeatureSchema = discriminatedUnion("kind", [object({
12278
12474
  kind: literal("fields"),
@@ -12284,7 +12480,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
12284
12480
  * owns the WHOLE array field: the composed value holds exactly these items.
12285
12481
  */
12286
12482
  items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
12287
- /** RESERVED for slice 3; refused by the validator when non-empty. */
12483
+ /** Method name → what serves it. Only a BUILT capability takes commands (D967). */
12288
12484
  commands: record(string().min(1), CompositionCommandTargetSchema).optional()
12289
12485
  }), object({
12290
12486
  kind: literal("passthrough"),
@@ -15692,6 +15888,14 @@ var AutomationConditionLeafSchema = object({
15692
15888
  array(union([string(), number()]))
15693
15889
  ])
15694
15890
  });
15891
+ var AutomationConditionStationaryPresentSchema = object({
15892
+ kind: literal("stationary-present"),
15893
+ deviceId: number().int().nonnegative(),
15894
+ /** Class names; a macro class matches every raw class that maps to it. */
15895
+ classes: array(string().min(1)).min(1).optional(),
15896
+ /** The parked object must be in at least one of these zones. */
15897
+ zones: array(string().min(1)).min(1).optional()
15898
+ });
15695
15899
  /**
15696
15900
  * The expression leaf, declared as a plain object rather than an intersection
15697
15901
  * with {@link ExpressionSourceSchema}: a discriminated union has to be able to
@@ -15725,7 +15929,8 @@ var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
15725
15929
  child: AutomationConditionSchema
15726
15930
  }),
15727
15931
  AutomationConditionLeafSchema,
15728
- AutomationConditionExpressionSchema
15932
+ AutomationConditionExpressionSchema,
15933
+ AutomationConditionStationaryPresentSchema
15729
15934
  ]));
15730
15935
  /**
15731
15936
  * What starts a run.
@@ -15766,7 +15971,34 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
15766
15971
  kind: literal("schedule"),
15767
15972
  cron: string().min(1).max(120)
15768
15973
  }),
15769
- object({ kind: literal("manual") })
15974
+ object({ kind: literal("manual") }),
15975
+ object({
15976
+ kind: literal("detection"),
15977
+ /** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
15978
+ * matches every raw class that maps to it; any other name matches
15979
+ * exactly. */
15980
+ classes: array(string().min(1)).min(1),
15981
+ deviceIds: array(number().int().nonnegative()).min(1),
15982
+ /** The track must have visited at least one of these zones. */
15983
+ zones: array(string().min(1)).min(1).optional(),
15984
+ /** Best-so-far confidence the track must reach. */
15985
+ minScore: number().min(0).max(1).optional()
15986
+ }),
15987
+ object({
15988
+ kind: literal("stationary"),
15989
+ deviceIds: array(number().int().nonnegative()).min(1),
15990
+ /** Class names; a macro class matches like the detection trigger. */
15991
+ classes: array(string().min(1)).min(1).optional(),
15992
+ /** The parked object must be in at least one of these zones. */
15993
+ zones: array(string().min(1)).min(1).optional(),
15994
+ /** `appeared`: it became parked. `departed`: the registry retired it.
15995
+ * The vocabulary of `PipelineAnalyticsStationaryChanged`. */
15996
+ phase: _enum(["appeared", "departed"])
15997
+ }),
15998
+ object({
15999
+ kind: literal("interval"),
16000
+ minutes: number().int().min(1).max(1440)
16001
+ })
15770
16002
  ]);
15771
16003
  /**
15772
16004
  * One action step.
@@ -15786,28 +16018,47 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
15786
16018
  * is called. The runner still receives a number and is untouched; the
15787
16019
  * resolution is the recipe's job, not the runner's.
15788
16020
  */
16021
+ var AutomationWaitStepSchema = object({
16022
+ kind: literal("wait"),
16023
+ seconds: number().min(0).max(300)
16024
+ });
16025
+ var AutomationCapStepSchema = object({
16026
+ kind: literal("cap"),
16027
+ /**
16028
+ * One device, several same-family devices (locks / climate family / …),
16029
+ * or `{ $var }` for the device that triggered the run. An array is
16030
+ * fan-out at codegen — each id gets the same cap.method call.
16031
+ */
16032
+ deviceId: union([
16033
+ number().int(),
16034
+ array(number().int()).min(1),
16035
+ object({ $var: string().min(1) })
16036
+ ]),
16037
+ cap: string().min(1),
16038
+ method: string().min(1),
16039
+ /** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
16040
+ * SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
16041
+ * the expression leaf. */
16042
+ args: record(string(), unknown()).optional()
16043
+ });
16044
+ /**
16045
+ * The steps an internal Action may contain: cap and wait, and nothing that can
16046
+ * call back into an Action — so an Action can never reach itself (no cycles).
16047
+ */
16048
+ var ActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCapStepSchema]);
15789
16049
  var AutomationActionSchema = discriminatedUnion("kind", [
16050
+ AutomationWaitStepSchema,
16051
+ AutomationCapStepSchema,
15790
16052
  object({
15791
- kind: literal("wait"),
15792
- seconds: number().min(0).max(300)
15793
- }),
15794
- object({
15795
- kind: literal("cap"),
15796
- /**
15797
- * One device, several same-family devices (locks / climate family / …),
15798
- * or `{ $var }` for the device that triggered the run. An array is
15799
- * fan-out at codegen — each id gets the same cap.method call.
15800
- */
15801
- deviceId: union([
15802
- number().int(),
15803
- array(number().int()).min(1),
15804
- object({ $var: string().min(1) })
15805
- ]),
15806
- cap: string().min(1),
15807
- method: string().min(1),
15808
- /** Values may carry `{{vars.x}}` slots, which SUBSTITUTE and do not
15809
- * evaluate (§3.2.3). Anything beyond substitution is the expression leaf. */
15810
- args: record(string(), unknown()).optional()
16053
+ kind: literal("action"),
16054
+ /** An entry of the `actions` surface (`internal:<uuid>` or `<addonId>:<localId>`). */
16055
+ actionId: string().min(1),
16056
+ /** Values for the action's declared variables; the others take their default. */
16057
+ variables: record(string(), union([
16058
+ string(),
16059
+ number(),
16060
+ boolean()
16061
+ ])).optional()
15811
16062
  }),
15812
16063
  object({
15813
16064
  kind: literal("code"),
@@ -15816,10 +16067,778 @@ var AutomationActionSchema = discriminatedUnion("kind", [
15816
16067
  code: string().min(1).max(2e4)
15817
16068
  })
15818
16069
  ]);
16070
+ /**
16071
+ * How a trigger that arrives while the automation is busy (or cooling down) is
16072
+ * handled. One of the automation's execution modes, never a property of its
16073
+ * actions. Absent means `single`.
16074
+ *
16075
+ * - `single` — drop the trigger while a run is in progress (traced + logged).
16076
+ * - `restart` — abort the run in progress (at its next step boundary), start fresh.
16077
+ * - `queued` — queue up to `max` triggers, drop the rest (traced).
16078
+ * - `parallel` — up to `max` runs at once, drop the rest (traced).
16079
+ * - `cooldown` — `single`, and no new run until `minutes` after the last run
16080
+ * STARTED; persisted across a restart. A manual trigger bypasses only this
16081
+ * wait, never the single/queued limits.
16082
+ */
16083
+ var AutomationRunModeSchema = discriminatedUnion("mode", [
16084
+ object({ mode: literal("single") }),
16085
+ object({ mode: literal("restart") }),
16086
+ object({
16087
+ mode: literal("queued"),
16088
+ max: number().int().min(1).max(10)
16089
+ }),
16090
+ object({
16091
+ mode: literal("parallel"),
16092
+ max: number().int().min(1).max(10)
16093
+ }),
16094
+ object({
16095
+ mode: literal("cooldown"),
16096
+ minutes: number().int().min(1).max(1440)
16097
+ })
16098
+ ]);
15819
16099
  var AutomationRecipeSchema = object({
15820
16100
  triggers: array(AutomationTriggerSchema),
15821
16101
  conditions: AutomationConditionSchema.optional(),
15822
- actions: array(AutomationActionSchema)
16102
+ actions: array(AutomationActionSchema),
16103
+ runMode: AutomationRunModeSchema.optional(),
16104
+ /** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
16105
+ keepTraces: number().int().min(1).max(100).optional()
16106
+ });
16107
+ new Set([
16108
+ {
16109
+ id: "person",
16110
+ name: "Person"
16111
+ },
16112
+ {
16113
+ id: "vehicle",
16114
+ name: "Vehicle"
16115
+ },
16116
+ {
16117
+ id: "animal",
16118
+ name: "Animal"
16119
+ },
16120
+ {
16121
+ id: "package",
16122
+ name: "Package"
16123
+ }
16124
+ ].map((l) => l.id));
16125
+ var COCO_TO_MACRO = {
16126
+ mapping: {
16127
+ person: "person",
16128
+ bicycle: "vehicle",
16129
+ car: "vehicle",
16130
+ motorcycle: "vehicle",
16131
+ airplane: "vehicle",
16132
+ bus: "vehicle",
16133
+ train: "vehicle",
16134
+ truck: "vehicle",
16135
+ boat: "vehicle",
16136
+ bird: "animal",
16137
+ cat: "animal",
16138
+ dog: "animal",
16139
+ horse: "animal",
16140
+ sheep: "animal",
16141
+ cow: "animal",
16142
+ elephant: "animal",
16143
+ bear: "animal",
16144
+ zebra: "animal",
16145
+ giraffe: "animal",
16146
+ suitcase: "package",
16147
+ backpack: "package",
16148
+ handbag: "package"
16149
+ },
16150
+ preserveOriginal: false
16151
+ };
16152
+ new Set(Object.values(COCO_TO_MACRO.mapping));
16153
+ /**
16154
+ * Run traces — what an automation (or, later, an Action run) DID, node by node,
16155
+ * so the editor can paint every block green or red with its reason.
16156
+ *
16157
+ * A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
16158
+ * every condition node (containers and leaves, including the ones a
16159
+ * short-circuit never reached), the run-mode verdict and every action step —
16160
+ * each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
16161
+ * for an Action run). Skipped
16162
+ * evaluations are traced too, and identical consecutive skips collapse into a
16163
+ * repeat count so a trigger that fails every 5 minutes cannot flush real runs.
16164
+ *
16165
+ * {@link AutomationStepTraceSchema} is deliberately free of automation
16166
+ * vocabulary so an Action run records the same step shape.
16167
+ */
16168
+ /** A value a trace may show: JSON primitives and short lists, nothing deeper. */
16169
+ var TraceValueSchema = union([
16170
+ string(),
16171
+ number(),
16172
+ boolean(),
16173
+ _null(),
16174
+ array(union([string(), number()]))
16175
+ ]);
16176
+ var AutomationRunOutcomeSchema = _enum([
16177
+ "ran",
16178
+ "skipped-trigger",
16179
+ "skipped-conditions",
16180
+ "skipped-cooldown",
16181
+ "skipped-overlap",
16182
+ "failed",
16183
+ "aborted"
16184
+ ]);
16185
+ /** `not-evaluated` = a short-circuit never reached the node. */
16186
+ var TraceNodeOutcomeSchema = _enum([
16187
+ "pass",
16188
+ "fail",
16189
+ "not-evaluated"
16190
+ ]);
16191
+ var AutomationTriggerTraceSchema = object({
16192
+ kind: string().min(1),
16193
+ outcome: _enum(["pass", "fail"]),
16194
+ /** Why an event was rejected ("class person not in [dog]") or what fired. */
16195
+ reason: string(),
16196
+ /** deviceId, className, trackId, score … — only what the trigger exposes. */
16197
+ vars: record(string(), TraceValueSchema)
16198
+ });
16199
+ var AutomationConditionTraceNodeSchema = object({
16200
+ /** `conditions` for the root, `conditions.<i>` for its i-th child, … */
16201
+ path: string().min(1),
16202
+ kind: _enum([
16203
+ "all",
16204
+ "any",
16205
+ "not",
16206
+ "condition",
16207
+ "expression",
16208
+ "stationary-present"
16209
+ ]),
16210
+ outcome: TraceNodeOutcomeSchema,
16211
+ reason: string(),
16212
+ deviceId: number().int().optional(),
16213
+ cap: string().optional(),
16214
+ fieldPath: string().optional(),
16215
+ operator: string().optional(),
16216
+ expected: TraceValueSchema.optional(),
16217
+ /** What the mirror held; `null` when it held nothing (unknown). */
16218
+ observed: TraceValueSchema.optional()
16219
+ });
16220
+ var AutomationRunVerdictSchema = object({
16221
+ mode: AutomationRunModeSchema,
16222
+ decision: _enum([
16223
+ "run",
16224
+ "queued",
16225
+ "restart",
16226
+ "drop",
16227
+ "cooldown",
16228
+ "bypass"
16229
+ ]),
16230
+ reason: string()
16231
+ });
16232
+ /** One executed (or not-run) step — shared by automations and Action runs. */
16233
+ var AutomationStepTraceSchema = object({
16234
+ /** `actions.<i>` */
16235
+ path: string().min(1),
16236
+ kind: string().min(1),
16237
+ cap: string().optional(),
16238
+ method: string().optional(),
16239
+ deviceId: number().int().optional(),
16240
+ startedAt: number().optional(),
16241
+ endedAt: number().optional(),
16242
+ outcome: _enum([
16243
+ "ok",
16244
+ "error",
16245
+ "not-run"
16246
+ ]),
16247
+ message: string().optional()
16248
+ });
16249
+ var AutomationRunTraceSchema = object({
16250
+ runId: string().min(1),
16251
+ startedAt: number(),
16252
+ endedAt: number(),
16253
+ trigger: AutomationTriggerTraceSchema,
16254
+ conditions: array(AutomationConditionTraceNodeSchema),
16255
+ verdict: AutomationRunVerdictSchema.optional(),
16256
+ steps: array(AutomationStepTraceSchema),
16257
+ outcome: AutomationRunOutcomeSchema,
16258
+ /** How many identical consecutive skips this trace stands for (>= 1). */
16259
+ repeat: number().int().min(1),
16260
+ /** When the latest of those repeats happened. */
16261
+ lastAt: number()
16262
+ });
16263
+ /**
16264
+ * What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
16265
+ * `action:<actionId>`): the cooldown stamp and the trace ring.
16266
+ */
16267
+ var AutomationStateDocSchema = object({
16268
+ version: literal(1),
16269
+ lastRunStartedAt: number().nullable(),
16270
+ traces: array(AutomationRunTraceSchema)
16271
+ });
16272
+ /**
16273
+ * Actions — named, runnable things an operator (or an automation) can start,
16274
+ * declared as LIST ENTRIES, never as devices.
16275
+ *
16276
+ * An entry is either **internal** (a short list of cap/wait steps kept in the
16277
+ * hub's `actions` record kind) or declared by an addon through the
16278
+ * `action-source` collection (a Home Assistant script, a scene …). The one
16279
+ * surface the UI and automations talk to is the `actions` cap; it keeps no
16280
+ * catalog of its own (D3) — every `list` asks the providers live.
16281
+ *
16282
+ * This file is the data half: the entry schema, the variables an entry may
16283
+ * declare, and the pure resolution of the values a run was given. It holds no
16284
+ * runtime.
16285
+ */
16286
+ /** What a variable's value is. `select` takes one of `options`. */
16287
+ var ActionVariableTypeSchema = _enum([
16288
+ "string",
16289
+ "number",
16290
+ "boolean",
16291
+ "select"
16292
+ ]);
16293
+ var ACTION_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
16294
+ var ActionVariableValueSchema = union([
16295
+ string(),
16296
+ number(),
16297
+ boolean()
16298
+ ]);
16299
+ /**
16300
+ * One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
16301
+ * value) or `{{vars.name}}` (inside a string) in their args.
16302
+ */
16303
+ var ActionVariableSchema = object({
16304
+ name: string().regex(ACTION_VARIABLE_NAME_RE),
16305
+ label: string().min(1).max(120).optional(),
16306
+ type: ActionVariableTypeSchema,
16307
+ required: boolean().optional(),
16308
+ default: ActionVariableValueSchema.optional(),
16309
+ /** The allowed values of a `select`. */
16310
+ options: array(string().min(1)).min(1).optional()
16311
+ }).superRefine((v, ctx) => {
16312
+ if (v.type === "select" && v.options === void 0) ctx.addIssue({
16313
+ code: "custom",
16314
+ message: "a select variable needs options",
16315
+ path: ["options"]
16316
+ });
16317
+ if (v.default !== void 0 && variableValueError(v, v.default) !== null) ctx.addIssue({
16318
+ code: "custom",
16319
+ message: variableValueError(v, v.default) ?? "invalid default",
16320
+ path: ["default"]
16321
+ });
16322
+ });
16323
+ /** Why `value` is not a legal value for the variable, or null when it is. */
16324
+ function variableValueError(variable, value) {
16325
+ switch (variable.type) {
16326
+ case "string": return typeof value === "string" ? null : `variable "${variable.name}" must be a string`;
16327
+ case "number": return typeof value === "number" && Number.isFinite(value) ? null : `variable "${variable.name}" must be a number`;
16328
+ case "boolean": return typeof value === "boolean" ? null : `variable "${variable.name}" must be true or false`;
16329
+ case "select": return typeof value === "string" && (variable.options ?? []).includes(value) ? null : `variable "${variable.name}" must be one of ${(variable.options ?? []).join(", ")}`;
16330
+ }
16331
+ }
16332
+ var ActionSourceKindSchema = _enum(["internal", "addon"]);
16333
+ function isInternalActionId(actionId) {
16334
+ return actionId.startsWith("internal:") && actionId.length > 9;
16335
+ }
16336
+ /** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
16337
+ function addonIdOfActionId(actionId) {
16338
+ if (isInternalActionId(actionId)) return null;
16339
+ const at = actionId.indexOf(":");
16340
+ return at > 0 && at < actionId.length - 1 ? actionId.slice(0, at) : null;
16341
+ }
16342
+ var ActionStatusSchema = object({
16343
+ isRunning: boolean(),
16344
+ /** Ms epoch the latest run STARTED; null when it never ran. */
16345
+ lastRunAt: number().nullable(),
16346
+ /** The latest run's failure; null on success or when it never ran. */
16347
+ lastError: string().nullable()
16348
+ });
16349
+ var ActionEntrySchema = object({
16350
+ /** `internal:<uuid>` or `<addonId>:<localId>`. */
16351
+ id: string().min(3),
16352
+ name: string().min(1).max(120),
16353
+ /** The addon that declared it; `core-blocks` for an internal one. */
16354
+ addonId: string().min(1),
16355
+ source: ActionSourceKindSchema,
16356
+ /** An addon-declared entry is edited where it was declared. */
16357
+ readOnly: boolean(),
16358
+ /** Internal only: cap and wait steps. No action-in-action, so no cycles. */
16359
+ steps: array(ActionStepSchema).min(1).max(50).optional(),
16360
+ variables: array(ActionVariableSchema).max(20).optional(),
16361
+ status: ActionStatusSchema
16362
+ }).superRefine((entry, ctx) => {
16363
+ if (entry.source === "internal") {
16364
+ if (!isInternalActionId(entry.id)) ctx.addIssue({
16365
+ code: "custom",
16366
+ message: "an internal action id is internal:<uuid>",
16367
+ path: ["id"]
16368
+ });
16369
+ return;
16370
+ }
16371
+ if (entry.steps !== void 0) ctx.addIssue({
16372
+ code: "custom",
16373
+ message: "only an internal action carries steps",
16374
+ path: ["steps"]
16375
+ });
16376
+ if (!entry.readOnly) ctx.addIssue({
16377
+ code: "custom",
16378
+ message: "an addon action is read-only here",
16379
+ path: ["readOnly"]
16380
+ });
16381
+ if (addonIdOfActionId(entry.id) === null) ctx.addIssue({
16382
+ code: "custom",
16383
+ message: "an addon action id is <addonId>:<localId>",
16384
+ path: ["id"]
16385
+ });
16386
+ });
16387
+ /** What an operator writes for an internal action. */
16388
+ var ActionInputSchema = object({
16389
+ name: string().min(1).max(120),
16390
+ steps: array(ActionStepSchema).min(1).max(50),
16391
+ variables: array(ActionVariableSchema).max(20).optional()
16392
+ });
16393
+ /** Who started a run. Absent = the operator pressed it. */
16394
+ var ActionRunOriginSchema = object({
16395
+ /** The automation block whose step started it. */
16396
+ blockId: string().min(1).optional() });
16397
+ /** `started` = the caller did not ask to wait. */
16398
+ var ActionRunOutcomeSchema = union([AutomationRunOutcomeSchema, literal("started")]);
16399
+ var ActionRunResultSchema = object({
16400
+ runId: string().min(1),
16401
+ outcome: ActionRunOutcomeSchema,
16402
+ /** Why it did not run, or what failed. */
16403
+ message: string().optional()
16404
+ });
16405
+ _enum([
16406
+ "not-found",
16407
+ "read-only",
16408
+ "invalid-variables",
16409
+ "invalid-steps",
16410
+ "not-linked",
16411
+ "busy"
16412
+ ]);
16413
+ object({
16414
+ actionId: string(),
16415
+ runId: string(),
16416
+ isRunning: boolean(),
16417
+ /** Set when the run ended. */
16418
+ outcome: AutomationRunOutcomeSchema.optional(),
16419
+ lastRunAt: number().nullable(),
16420
+ lastError: string().nullable()
16421
+ });
16422
+ /**
16423
+ * notification-output — canonical, capability-gated notification delivery.
16424
+ *
16425
+ * Apprise-derived model (see
16426
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
16427
+ * callers emit ONE canonical `Notification`; each provider declares a
16428
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
16429
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
16430
+ * message to what the kind supports — callers never special-case a service.
16431
+ *
16432
+ * DESIGN DECISIONS (locked):
16433
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
16434
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
16435
+ * cap. Rationale: the admin UI needs one uniform surface across the
16436
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
16437
+ * alternative would fork the UI per addon and cannot host the
16438
+ * discovery→adopt flow.
16439
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
16440
+ * the generated cap-mount auto-`concatCollection`-fans them across every
16441
+ * registered provider (notifiers addon + HA addon) so one catalog is
16442
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
16443
+ * `addonId` the generated collection router extracts from the call input.
16444
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
16445
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
16446
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
16447
+ * base64 fallback needed.
16448
+ *
16449
+ * TODO (deferred, closed-set change — separate decision): add
16450
+ * `providerKind: 'notify'` so notification providers surface on the unified
16451
+ * admin "Integrations" page.
16452
+ */
16453
+ /**
16454
+ * Zentik-derived typed-media enum — the superset across every kind. Each
16455
+ * adapter picks what it supports and the degrade engine filters the rest.
16456
+ */
16457
+ var AttachmentMediaTypeSchema = _enum([
16458
+ "image",
16459
+ "video",
16460
+ "gif",
16461
+ "audio",
16462
+ "icon"
16463
+ ]);
16464
+ /**
16465
+ * A single attachment. Exactly one of `url` (remote source, most adapters
16466
+ * prefer this) or `bytes` (inline source; required for Pushover-style
16467
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
16468
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
16469
+ */
16470
+ var AttachmentSchema = object({
16471
+ mediaType: AttachmentMediaTypeSchema,
16472
+ url: string().optional(),
16473
+ bytes: _instanceof(Uint8Array).optional(),
16474
+ mime: string().optional(),
16475
+ name: string().optional()
16476
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
16477
+ var NotificationFormatSchema = _enum([
16478
+ "text",
16479
+ "markdown",
16480
+ "html"
16481
+ ]);
16482
+ /**
16483
+ * The CLOSED icon vocabulary an action button may use.
16484
+ *
16485
+ * A closed set, not a free string, and that is the whole point: an arbitrary
16486
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
16487
+ * notices — the same class of gap as a zone vocabulary nothing produced
16488
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
16489
+ * declares `actionIcons: false` and the degrade engine strips the field.
16490
+ *
16491
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
16492
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
16493
+ * differently.
16494
+ *
16495
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
16496
+ *
16497
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
16498
+ * the whole set onto its own renderer's vocabulary through a
16499
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
16500
+ * fallback, so adding a member here fails every adapter's build until someone
16501
+ * decides its glyph, which is the only place that decision can be made
16502
+ * honestly.
16503
+ *
16504
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
16505
+ * `disarm` straight through; iOS feeds that string to
16506
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
16507
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
16508
+ * mapping, and "the field is documented" is not "the value renders".
16509
+ *
16510
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
16511
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
16512
+ * so an addon that emits a token the running hub does not know does not lose an
16513
+ * icon — its whole `send` fails Zod validation and the notification never
16514
+ * arrives. Never emit a new token from an addon before the train carrying it.
16515
+ */
16516
+ var NotificationActionIconSchema = _enum([
16517
+ "acknowledge",
16518
+ "dismiss",
16519
+ "silence",
16520
+ "snooze",
16521
+ "view",
16522
+ "play",
16523
+ "open",
16524
+ "close",
16525
+ "lock",
16526
+ "unlock",
16527
+ "arm",
16528
+ "arm-home",
16529
+ "arm-away",
16530
+ "arm-night",
16531
+ "disarm",
16532
+ "light",
16533
+ "alert",
16534
+ "camera"
16535
+ ]);
16536
+ /** A single tap-through action button. */
16537
+ var NotificationActionSchema = object({
16538
+ id: string(),
16539
+ label: string(),
16540
+ url: string().optional(),
16541
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
16542
+ icon: NotificationActionIconSchema.optional(),
16543
+ /**
16544
+ * Renders in a warning style where the notifier supports it.
16545
+ *
16546
+ * A HINT, never a gate. The callback's authority is its token and nothing
16547
+ * else — see `notification-center/action-token.ts` for what that does and
16548
+ * does not buy.
16549
+ */
16550
+ destructive: boolean().optional(),
16551
+ /**
16552
+ * How the tap should REACH the url.
16553
+ *
16554
+ * `navigate` (absent, and every button authored before this field) opens it:
16555
+ * the phone leaves the notification and shows whatever the callback returns.
16556
+ * That is right for a button whose answer the operator wants to read.
16557
+ *
16558
+ * `background` fires it as a POST and stays put. It exists for the buttons
16559
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
16560
+ * an answer to the notification, and being thrown into a browser tab to
16561
+ * confirm it costs more attention than the notification did. A backend that
16562
+ * cannot do a background call renders it as an ordinary link (the adapters
16563
+ * fall back rather than dropping the button), so this is a preference, never
16564
+ * a requirement.
16565
+ */
16566
+ mode: _enum(["navigate", "background"]).optional()
16567
+ });
16568
+ /** A delivery parameter value: what a notifier declared it takes. */
16569
+ var DeliveryParamValueSchema = union([
16570
+ string(),
16571
+ number(),
16572
+ boolean()
16573
+ ]);
16574
+ /**
16575
+ * One extra delivery parameter a notifier KIND declares (D938). A rule that
16576
+ * addresses a target of that kind may set it; a kind that declares nothing is
16577
+ * never handed one. The admin UI derives its control from this descriptor —
16578
+ * no editor knows a service by name.
16579
+ *
16580
+ * Optional fields are `.optional()`, never `.default()` (a Zod default does not
16581
+ * run on the addon cap path).
16582
+ */
16583
+ var DeliveryParamSchema = object({
16584
+ key: string().min(1),
16585
+ label: string(),
16586
+ /** i18n key of the label, resolved by the declaring addon's namespace. */
16587
+ labelKey: string().optional(),
16588
+ type: _enum([
16589
+ "enum",
16590
+ "number",
16591
+ "boolean",
16592
+ "string"
16593
+ ]),
16594
+ /** `enum` only: the closed set of accepted values. */
16595
+ options: array(object({
16596
+ value: string(),
16597
+ label: string()
16598
+ })).optional(),
16599
+ /** `number` only. */
16600
+ min: number().optional(),
16601
+ max: number().optional(),
16602
+ /** What the notifier does when the rule sets nothing; shown as the placeholder. */
16603
+ default: DeliveryParamValueSchema.optional()
16604
+ });
16605
+ /**
16606
+ * The canonical notification. `body` is the only hard field (Apprise model).
16607
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
16608
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
16609
+ * the adapter maps this ordinal onto its native level. `level?` is an
16610
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
16611
+ * `priority` for that one target.
16612
+ */
16613
+ var NotificationSchema = object({
16614
+ body: string(),
16615
+ title: string().optional(),
16616
+ format: NotificationFormatSchema.default("text"),
16617
+ priority: number().int().min(1).max(5).default(3),
16618
+ level: string().optional(),
16619
+ attachments: array(AttachmentSchema).optional(),
16620
+ clickUrl: string().optional(),
16621
+ actions: array(NotificationActionSchema).optional(),
16622
+ sound: string().optional(),
16623
+ ttl: number().optional(),
16624
+ tag: string().optional(),
16625
+ deviceId: number().optional(),
16626
+ eventId: string().optional(),
16627
+ metadata: record(string(), unknown()).optional(),
16628
+ /**
16629
+ * Values for the parameters the target's kind DECLARES in
16630
+ * {@link TargetKindSchema} `deliveryParams`. Already validated against the
16631
+ * declaration by the sender; a notifier reads only the keys it declared.
16632
+ */
16633
+ delivery: record(string(), DeliveryParamValueSchema).optional()
16634
+ });
16635
+ /** One declared native severity/priority level for a kind. */
16636
+ var TargetKindLevelSchema = object({
16637
+ id: string(),
16638
+ label: string(),
16639
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
16640
+ ordinal: number().int().min(1).max(5).nullable(),
16641
+ flags: object({
16642
+ critical: boolean().optional(),
16643
+ silent: boolean().optional(),
16644
+ noPush: boolean().optional()
16645
+ }).optional(),
16646
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
16647
+ requires: array(string()).optional(),
16648
+ description: string().optional()
16649
+ });
16650
+ /** The full capability block consulted before dispatch. */
16651
+ var TargetKindCapsSchema = object({
16652
+ attachments: object({
16653
+ mediaTypes: array(AttachmentMediaTypeSchema),
16654
+ mode: _enum([
16655
+ "url",
16656
+ "bytes",
16657
+ "both"
16658
+ ]),
16659
+ max: number().int().nonnegative(),
16660
+ maxBytes: number().int().positive().optional()
16661
+ }),
16662
+ /** Max action buttons (0 = none). */
16663
+ actions: number().int().nonnegative(),
16664
+ /**
16665
+ * Whether this kind renders a per-action ICON.
16666
+ *
16667
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
16668
+ * run on the addon cap path — three production failures in one day taught
16669
+ * this repo that once. Absent is read as false by the degrade engine, which
16670
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
16671
+ * assumed and dropped costs the operator's trust in the field.
16672
+ */
16673
+ actionIcons: boolean().optional(),
16674
+ levels: array(TargetKindLevelSchema),
16675
+ format: array(NotificationFormatSchema),
16676
+ clickUrl: boolean(),
16677
+ sound: boolean(),
16678
+ ttl: boolean(),
16679
+ bodyMaxLen: number().int().positive()
16680
+ });
16681
+ /**
16682
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
16683
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
16684
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
16685
+ * the union is large and not meant for runtime validation here; the exported
16686
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
16687
+ */
16688
+ var ConfigSchemaPassthrough$3 = unknown();
16689
+ var TargetKindSchema = object({
16690
+ kind: string(),
16691
+ label: string(),
16692
+ icon: string(),
16693
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
16694
+ addonId: string(),
16695
+ /**
16696
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
16697
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
16698
+ * when the addon bundles no icon for that kind — the client then falls back
16699
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
16700
+ *
16701
+ * Root-relative on purpose: it resolves against whatever origin serves a web
16702
+ * client, and a native client joins it onto its own hub base.
16703
+ *
16704
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
16705
+ * field that survived only because the runtime cap-router forwards provider
16706
+ * output verbatim — so every consumer had to re-declare it by hand to stop
16707
+ * its own Zod parse from stripping it, and the whole arrangement would have
16708
+ * broken silently the moment output validation was tightened anywhere.
16709
+ */
16710
+ iconUrl: string().optional(),
16711
+ /**
16712
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
16713
+ *
16714
+ * The server knows this and therefore says it, because the client cannot
16715
+ * safely guess: a React-Native client renders SVG and raster through two
16716
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
16717
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
16718
+ * placeholder glyph for every vector icon while the web build looked fine.
16719
+ *
16720
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
16721
+ * not been updated — a client that cannot determine the type should prefer
16722
+ * its raster path, which is the safe default for an unknown image.
16723
+ */
16724
+ iconMediaType: string().optional(),
16725
+ configSchema: ConfigSchemaPassthrough$3,
16726
+ /**
16727
+ * The config keys that decide WHERE this kind's secrets are sent (server,
16728
+ * base URL, instance, chat, recipient). A save that changes any of them
16729
+ * while leaving a secret redacted is refused by the provider (D634), so an
16730
+ * editor uses this to ask for the secret on exactly those edits instead of
16731
+ * on every edit.
16732
+ *
16733
+ * Optional: absent from a provider that holds no secret (the Home Assistant
16734
+ * kinds) and from a provider older than this field — an editor that finds
16735
+ * it absent must assume ANY edit may need the secret.
16736
+ */
16737
+ destinationKeys: array(string()).optional(),
16738
+ supportsDiscovery: boolean(),
16739
+ /**
16740
+ * Extra delivery parameters a rule may set for a target of this kind (D938).
16741
+ * Absent = none; the rule editor then offers nothing.
16742
+ */
16743
+ deliveryParams: array(DeliveryParamSchema).optional(),
16744
+ caps: TargetKindCapsSchema
16745
+ });
16746
+ /**
16747
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
16748
+ * (return a presence marker only) when serving `listTargets` — never
16749
+ * round-trip a stored secret to the UI.
16750
+ */
16751
+ var TargetSchema = object({
16752
+ id: string(),
16753
+ name: string(),
16754
+ kind: string(),
16755
+ addonId: string(),
16756
+ enabled: boolean(),
16757
+ config: record(string(), unknown())
16758
+ });
16759
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
16760
+ var DiscoveredTargetSchema = object({
16761
+ kind: string(),
16762
+ suggestedName: string(),
16763
+ config: record(string(), unknown())
16764
+ });
16765
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
16766
+ var RenderedAsSchema = object({
16767
+ level: string(),
16768
+ format: NotificationFormatSchema,
16769
+ attachmentsSent: number().int().nonnegative(),
16770
+ actionsSent: number().int().nonnegative(),
16771
+ truncated: boolean(),
16772
+ dropped: array(string())
16773
+ });
16774
+ var SendResultSchema = object({
16775
+ success: boolean(),
16776
+ error: string().optional(),
16777
+ renderedAs: RenderedAsSchema.optional()
16778
+ });
16779
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
16780
+ var TestResultSchema = SendResultSchema;
16781
+ /**
16782
+ * Pre-save credential probe against a partial (unsaved) target config.
16783
+ * `resolvedUrl` never includes secrets (token stripped from path).
16784
+ */
16785
+ var ProbeTargetConfigResultSchema = object({
16786
+ ok: boolean(),
16787
+ resolvedUrl: string(),
16788
+ httpStatus: number().int().optional(),
16789
+ stateInstance: string().optional(),
16790
+ error: string().optional()
16791
+ });
16792
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
16793
+ kind: string(),
16794
+ config: record(string(), unknown()).optional(),
16795
+ /**
16796
+ * When editing / duplicating, the blank password field never round-trips
16797
+ * the stored token. Pass the source target id so the provider can restore
16798
+ * secrets server-side before dialling (same merge as upsert).
16799
+ */
16800
+ targetId: string().optional()
16801
+ }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
16802
+ kind: string(),
16803
+ config: record(string(), unknown()),
16804
+ /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
16805
+ targetId: string().optional()
16806
+ }), ProbeTargetConfigResultSchema, {
16807
+ kind: "mutation",
16808
+ auth: "admin"
16809
+ }), method(object({
16810
+ targetId: string(),
16811
+ notification: NotificationSchema
16812
+ }), SendResultSchema, {
16813
+ kind: "mutation",
16814
+ auth: "admin"
16815
+ }), method(object({
16816
+ targetId: string(),
16817
+ sample: NotificationSchema.optional()
16818
+ }), TestResultSchema, {
16819
+ kind: "mutation",
16820
+ auth: "admin"
16821
+ }), method(object({
16822
+ target: TargetSchema,
16823
+ /**
16824
+ * Duplicate flow: create a NEW target id while copying secrets from an
16825
+ * existing row. The UI never holds the stored token; blank / redacted
16826
+ * password fields are restored from this source (D634 still applies if
16827
+ * destinations diverge without a re-typed secret).
16828
+ */
16829
+ cloneSecretsFrom: string().optional()
16830
+ }), TargetSchema, {
16831
+ kind: "mutation",
16832
+ auth: "admin"
16833
+ }), method(object({ targetId: string() }), _void(), {
16834
+ kind: "mutation",
16835
+ auth: "admin"
16836
+ }), method(object({
16837
+ targetId: string(),
16838
+ enabled: boolean()
16839
+ }), _void(), {
16840
+ kind: "mutation",
16841
+ auth: "admin"
15823
16842
  });
15824
16843
  /**
15825
16844
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
@@ -15853,52 +16872,6 @@ var AddonPageInfoSchema = object({
15853
16872
  bundleUrl: string()
15854
16873
  });
15855
16874
  method(_void(), array(AddonPageInfoSchema).readonly());
15856
- /**
15857
- * `addon-pages-source` — collection cap exposing per-provider raw page
15858
- * declarations. Every addon that contributes a UI page registers a
15859
- * provider here. The hub-side singleton aggregator (`addon-pages` cap,
15860
- * see `addon-pages.cap.ts`) walks this collection, stamps versioned
15861
- * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
15862
- * that admin-ui consumes.
15863
- *
15864
- * The split exists because the public listing has a different output
15865
- * shape than the per-provider raw declarations, and we want both ends
15866
- * to flow through codegen instead of relying on a hand-written wrapper.
15867
- */
15868
- var AddonPageDeclarationSchema = object({
15869
- id: string(),
15870
- label: string(),
15871
- icon: string(),
15872
- path: string(),
15873
- /**
15874
- * Module Federation remote name — must match the `name` field on the
15875
- * page addon's `federation()` plugin config. Used by admin-ui's
15876
- * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
15877
- * Conventionally `addon_<id>_page` (snake_case; MF names cannot
15878
- * contain hyphens).
15879
- */
15880
- remoteName: string(),
15881
- /**
15882
- * Bundle filename inside the addon's `dist/` dir served at
15883
- * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
15884
- * is always `'remoteEntry.js'`; the value is kept on the metadata so
15885
- * the static-file route can compute an mtime-based cache-buster URL
15886
- * without a separate filesystem stat.
15887
- */
15888
- bundle: string(),
15889
- /**
15890
- * Sidebar section this page docks into. Well-known ids: `'detection'`,
15891
- * `'cluster'`, `'administration'` — the page renders inside that group.
15892
- * Any OTHER string creates (or joins) a custom section rendered after
15893
- * the built-in groups; its label comes from `sectionLabel` (first
15894
- * declaration wins), falling back to the id. Absent → the legacy
15895
- * "Addon Pages" group.
15896
- */
15897
- section: string().optional(),
15898
- /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
15899
- sectionLabel: string().optional()
15900
- });
15901
- method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
15902
16875
  var AddonHttpRouteSchema = object({
15903
16876
  method: _enum([
15904
16877
  "GET",
@@ -16180,9 +17153,21 @@ var AlertSeveritySchema = _enum([
16180
17153
  "error",
16181
17154
  "critical"
16182
17155
  ]);
17156
+ /**
17157
+ * An action that RUNS something instead of going somewhere: the admin UI calls
17158
+ * the addon's custom action (`addons.custom`) with exactly these fields. The
17159
+ * producer owns the meaning; the UI knows nothing about the addon or the kind.
17160
+ */
17161
+ var AlertActionInvokeSchema = object({
17162
+ addonId: string().min(1),
17163
+ action: string().min(1),
17164
+ input: record(string(), unknown()).optional()
17165
+ });
17166
+ /** An action has `href` (a link) OR `invoke` (a button that runs a custom action). */
16183
17167
  var AlertActionSchema = object({
16184
17168
  label: string(),
16185
17169
  href: string().optional(),
17170
+ invoke: AlertActionInvokeSchema.optional(),
16186
17171
  /**
16187
17172
  * How `href` is followed. Absent / `app`: an admin-ui route (client-side).
16188
17173
  * `server`: a route the HUB serves (an addon HTTP route such as an OAuth
@@ -18400,7 +19385,7 @@ var CompositionEventPlanSchema = object({
18400
19385
  stateField: string(),
18401
19386
  fieldKey: string()
18402
19387
  });
18403
- /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
19388
+ /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.temperature`. */
18404
19389
  var CompositionProblemSchema = object({
18405
19390
  code: _enum([
18406
19391
  "reserved-variant",
@@ -18429,7 +19414,11 @@ var CompositionProblemSchema = object({
18429
19414
  "mixed-quantity",
18430
19415
  "helper-mismatch",
18431
19416
  "member-constraint",
18432
- "retention"
19417
+ "retention",
19418
+ "command-method-unknown",
19419
+ "command-source-unknown",
19420
+ "command-input-unknown",
19421
+ "command-input-missing"
18433
19422
  ]),
18434
19423
  path: string(),
18435
19424
  message: string()
@@ -18468,7 +19457,9 @@ var CompositionFieldRoleSchema = _enum([
18468
19457
  var CompositionFieldItemSchema = object({
18469
19458
  arrayPath: string(),
18470
19459
  key: string(),
18471
- label: string()
19460
+ label: string(),
19461
+ /** True when the item carries a `reset` forward: the composer writes `resettable` from this (D967). */
19462
+ resettable: boolean().optional()
18472
19463
  });
18473
19464
  var CompositionFieldPlanSchema = object({
18474
19465
  cap: string(),
@@ -18673,7 +19664,7 @@ Object.freeze({
18673
19664
  * One catalog row per base reading a group helper can build (D944 §3).
18674
19665
  * Data, not a form default written into the block.
18675
19666
  */
18676
- var NOW$2 = {
19667
+ var NOW$3 = {
18677
19668
  fieldPath: "lastFetchedAt",
18678
19669
  expr: "now"
18679
19670
  };
@@ -18682,12 +19673,13 @@ var CHANGED = {
18682
19673
  expr: "now"
18683
19674
  };
18684
19675
  function numeric(input) {
19676
+ const { alsoFrom, ...base } = input;
18685
19677
  return {
18686
- ...input,
19678
+ ...base,
18687
19679
  family: "numeric",
18688
19680
  deviceType: DeviceType.Sensor,
18689
19681
  memberDeviceType: DeviceType.Sensor,
18690
- memberDeviceTypes: [DeviceType.Sensor],
19682
+ memberDeviceTypes: [DeviceType.Sensor, ...alsoFrom?.deviceTypes ?? []],
18691
19683
  activeWhen: void 0,
18692
19684
  commandCaps: [],
18693
19685
  baseUnit: baseUnitOf(input.quantity),
@@ -18695,9 +19687,10 @@ function numeric(input) {
18695
19687
  accept: [{
18696
19688
  cap: input.outputCap,
18697
19689
  fieldPath: input.outputField
18698
- }]
19690
+ }, ...alsoFrom === void 0 ? [] : [alsoFrom.accept]]
18699
19691
  };
18700
19692
  }
19693
+ var CLIMATE_DEVICES = [DeviceType.Thermostat, DeviceType.Climate];
18701
19694
  function binary(input) {
18702
19695
  return {
18703
19696
  ...input,
@@ -18719,73 +19712,87 @@ var GROUP_TEMPLATES = [
18719
19712
  summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18720
19713
  role: DeviceRole.TemperatureSensor,
18721
19714
  outputCap: "temperature-sensor",
18722
- outputField: "celsius",
19715
+ outputField: "temperature",
18723
19716
  quantity: "temperature",
18724
19717
  defaultAggregate: "mean",
18725
19718
  empty: "null",
18726
- companions: [NOW$2]
19719
+ companions: [NOW$3],
19720
+ alsoFrom: {
19721
+ deviceTypes: CLIMATE_DEVICES,
19722
+ accept: {
19723
+ cap: "climate-control",
19724
+ fieldPath: "currentTemp"
19725
+ }
19726
+ }
18727
19727
  }),
18728
19728
  numeric({
18729
19729
  id: "humidity",
18730
19730
  summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18731
19731
  role: DeviceRole.HumiditySensor,
18732
19732
  outputCap: "humidity-sensor",
18733
- outputField: "percent",
19733
+ outputField: "humidity",
18734
19734
  quantity: "humidity",
18735
19735
  defaultAggregate: "mean",
18736
19736
  empty: "null",
18737
- companions: [NOW$2]
19737
+ companions: [NOW$3],
19738
+ alsoFrom: {
19739
+ deviceTypes: CLIMATE_DEVICES,
19740
+ accept: {
19741
+ cap: "climate-control",
19742
+ fieldPath: "currentHumidity"
19743
+ }
19744
+ }
18738
19745
  }),
18739
19746
  numeric({
18740
19747
  id: "pressure",
18741
19748
  summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18742
19749
  role: DeviceRole.PressureSensor,
18743
19750
  outputCap: "pressure-sensor",
18744
- outputField: "hpa",
19751
+ outputField: "pressure",
18745
19752
  quantity: "pressure",
18746
19753
  defaultAggregate: "mean",
18747
19754
  empty: "null",
18748
- companions: [NOW$2]
19755
+ companions: [NOW$3]
18749
19756
  }),
18750
19757
  numeric({
18751
19758
  id: "illuminance",
18752
19759
  summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18753
19760
  role: DeviceRole.AmbientLightSensor,
18754
19761
  outputCap: "ambient-light-sensor",
18755
- outputField: "lux",
19762
+ outputField: "illuminance",
18756
19763
  quantity: "illuminance",
18757
19764
  defaultAggregate: "mean",
18758
19765
  empty: "null",
18759
- companions: [NOW$2]
19766
+ companions: [NOW$3]
18760
19767
  }),
18761
19768
  numeric({
18762
19769
  id: "power",
18763
19770
  summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18764
19771
  role: DeviceRole.PowerSensor,
18765
19772
  outputCap: "power-meter",
18766
- outputField: "watts",
19773
+ outputField: "power",
18767
19774
  quantity: "power",
18768
19775
  defaultAggregate: "sum",
18769
19776
  empty: "omit",
18770
- companions: [NOW$2]
19777
+ companions: [NOW$3]
18771
19778
  }),
18772
19779
  numeric({
18773
19780
  id: "energy",
18774
19781
  summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18775
19782
  role: DeviceRole.EnergySensor,
18776
19783
  outputCap: "power-meter",
18777
- outputField: "kwhTotal",
19784
+ outputField: "energy",
18778
19785
  quantity: "energy",
18779
19786
  defaultAggregate: "sum",
18780
19787
  empty: "omit",
18781
- companions: [NOW$2]
19788
+ companions: [NOW$3]
18782
19789
  }),
18783
19790
  numeric({
18784
19791
  id: "battery",
18785
19792
  summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18786
19793
  role: DeviceRole.BatterySensor,
18787
19794
  outputCap: "battery",
18788
- outputField: "percentage",
19795
+ outputField: "level",
18789
19796
  quantity: "percentage",
18790
19797
  defaultAggregate: "mean",
18791
19798
  empty: "null",
@@ -18813,7 +19820,7 @@ var GROUP_TEMPLATES = [
18813
19820
  quantity: "device",
18814
19821
  defaultAggregate: "mean",
18815
19822
  empty: "null",
18816
- companions: [NOW$2]
19823
+ companions: [NOW$3]
18817
19824
  }),
18818
19825
  binary({
18819
19826
  id: "motion",
@@ -19363,7 +20370,18 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
19363
20370
  kind: "mutation",
19364
20371
  auth: "admin",
19365
20372
  access: "view"
19366
- });
20373
+ }), method(object({
20374
+ blockId: string().min(1),
20375
+ trace: AutomationRunTraceSchema.optional(),
20376
+ runStartedAt: number().optional(),
20377
+ keepTraces: number().int().min(1).max(100).optional()
20378
+ }), _void(), {
20379
+ kind: "mutation",
20380
+ auth: "admin"
20381
+ }), method(object({ blockId: string().min(1) }), AutomationStateDocSchema, { auth: "admin" }), method(object({ blockId: string().min(1) }), object({
20382
+ traces: array(AutomationRunTraceSchema),
20383
+ lastError: string().nullable()
20384
+ }), { auth: "admin" });
19367
20385
  /**
19368
20386
  * `custom-model-registry` — collection cap exposing operator-registered
19369
20387
  * custom detection models. Each provider (today: `addon-model-studio`)
@@ -21148,12 +22166,48 @@ var ExposeInputSchema = object({
21148
22166
  capabilities: array(string()).optional()
21149
22167
  });
21150
22168
  var UnexposeInputSchema = object({ deviceId: string() });
22169
+ /**
22170
+ * What an exporter may expose besides devices (D968). An action and a
22171
+ * notification target are LIST ENTRIES, never devices (D954): they enter an
22172
+ * exporter's own selection, not the device registry.
22173
+ */
22174
+ var ExportEntityKindSchema = _enum([
22175
+ "device",
22176
+ "action",
22177
+ "notifier"
22178
+ ]);
22179
+ var ExportEntityRefSchema = discriminatedUnion("kind", [
22180
+ object({
22181
+ kind: literal("device"),
22182
+ deviceId: string()
22183
+ }),
22184
+ object({
22185
+ kind: literal("action"),
22186
+ actionId: string().min(1)
22187
+ }),
22188
+ object({
22189
+ kind: literal("notifier"),
22190
+ targetId: string().min(1)
22191
+ })
22192
+ ]);
22193
+ var ExposedEntitySchema = object({
22194
+ entity: ExportEntityRefSchema,
22195
+ exposedAs: string().optional()
22196
+ });
22197
+ var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
22198
+ var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
21151
22199
  method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
21152
22200
  kind: "mutation",
21153
22201
  auth: "admin"
21154
22202
  }), method(UnexposeInputSchema, _void(), {
21155
22203
  kind: "mutation",
21156
22204
  auth: "admin"
22205
+ }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
22206
+ kind: "mutation",
22207
+ auth: "admin"
22208
+ }), optionalMethod(UnexposeEntityInputSchema, _void(), {
22209
+ kind: "mutation",
22210
+ auth: "admin"
21157
22211
  });
21158
22212
  /** Lucide category ids, alphabetical. `ICON_META` indexes into this. */
21159
22213
  var ICON_CATEGORY_IDS = [
@@ -42294,6 +43348,14 @@ method(object({
42294
43348
  kind: "mutation",
42295
43349
  auth: "admin"
42296
43350
  });
43351
+ method(object({
43352
+ nodeId: string().min(1),
43353
+ /** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
43354
+ removedAt: number()
43355
+ }), object({ counts: record(string(), number().int().nonnegative()) }), {
43356
+ kind: "mutation",
43357
+ auth: "admin"
43358
+ });
42297
43359
  /**
42298
43360
  * `device-removal-hook` — the reliable way an addon learns a device was DELETED,
42299
43361
  * so it can delete what it keeps for that device (D865).
@@ -42941,6 +44003,17 @@ var LlmProfileSchema = object({
42941
44003
  * field in its editor only when its descriptor lists it in `profileFields`. */
42942
44004
  topK: number().int().positive().optional(),
42943
44005
  maxTokens: number().int().positive().optional(),
44006
+ /** How hard the model "thinks" before it answers. Omitted = the model's own
44007
+ * default. `off` matters for structured output: a thinking model can spend
44008
+ * the whole completion on reasoning and return an empty `content`. Carried
44009
+ * only by the wires that have it — a kind offers the field in its editor
44010
+ * only when its descriptor lists it in `profileFields`. */
44011
+ reasoningEffort: _enum([
44012
+ "off",
44013
+ "low",
44014
+ "medium",
44015
+ "high"
44016
+ ]).optional(),
42944
44017
  /** Prompt context window. Advisory for cloud kinds (they enforce their own). */
42945
44018
  contextLength: number().int().positive().optional(),
42946
44019
  /** Default system prompt. A caller's `system` REPLACES it (never appends —
@@ -42979,6 +44052,7 @@ var LlmProfileFieldSchema = _enum([
42979
44052
  "topP",
42980
44053
  "topK",
42981
44054
  "maxTokens",
44055
+ "reasoningEffort",
42982
44056
  "contextLength",
42983
44057
  "systemPrompt",
42984
44058
  "timeoutMs",
@@ -43002,7 +44076,7 @@ var LlmProviderWidgetSchema = object({
43002
44076
  /** ConfigUISchema tree passed through untyped on the wire (the
43003
44077
  * notification-output `ConfigSchemaPassthrough` precedent at
43004
44078
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
43005
- var ConfigSchemaPassthrough$3 = unknown();
44079
+ var ConfigSchemaPassthrough$2 = unknown();
43006
44080
  var LlmProfileKindDescriptorSchema = object({
43007
44081
  kind: LlmProfileKindSchema,
43008
44082
  label: string(),
@@ -43011,9 +44085,9 @@ var LlmProfileKindDescriptorSchema = object({
43011
44085
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
43012
44086
  addonId: string(),
43013
44087
  /** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
43014
- connectionSchema: ConfigSchemaPassthrough$3,
44088
+ connectionSchema: ConfigSchemaPassthrough$2,
43015
44089
  /** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
43016
- advancedSchema: ConfigSchemaPassthrough$3.optional(),
44090
+ advancedSchema: ConfigSchemaPassthrough$2.optional(),
43017
44091
  /** The kind's defaults; a model from `listModels` may override any of them. */
43018
44092
  features: LlmModelFeaturesSchema,
43019
44093
  /** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
@@ -43539,10 +44613,10 @@ var CpuBreakdownSchema = object({
43539
44613
  cores: number()
43540
44614
  });
43541
44615
  var MemoryInfoSchema = object({
43542
- percent: number(),
44616
+ percent: number().nullable(),
43543
44617
  totalBytes: number(),
43544
- usedBytes: number(),
43545
- availableBytes: number(),
44618
+ usedBytes: number().nullable(),
44619
+ availableBytes: number().nullable(),
43546
44620
  swapUsedBytes: number(),
43547
44621
  swapTotalBytes: number()
43548
44622
  });
@@ -43846,8 +44920,9 @@ var GetLoadSeriesInputSchema = object({
43846
44920
  });
43847
44921
  var SystemMetricsSchema = object({
43848
44922
  cpuPercent: number(),
43849
- memoryPercent: number(),
43850
- memoryUsedMB: number(),
44923
+ /** `null` when the memory sample failed or was not taken yet (D393). */
44924
+ memoryPercent: number().nullable(),
44925
+ memoryUsedMB: number().nullable(),
43851
44926
  memoryTotalMB: number(),
43852
44927
  diskPercent: number().optional(),
43853
44928
  temperature: number().optional(),
@@ -44083,424 +45158,6 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
44083
45158
  sourcePort: number().optional()
44084
45159
  });
44085
45160
  method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
44086
- /**
44087
- * notification-output — canonical, capability-gated notification delivery.
44088
- *
44089
- * Apprise-derived model (see
44090
- * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
44091
- * callers emit ONE canonical `Notification`; each provider declares a
44092
- * per-kind capability descriptor (`TargetKind`), and the pure degrade
44093
- * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
44094
- * message to what the kind supports — callers never special-case a service.
44095
- *
44096
- * DESIGN DECISIONS (locked):
44097
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
44098
- * `setTargetEnabled`), each provider persisting via the `settings-store`
44099
- * cap. Rationale: the admin UI needs one uniform surface across the
44100
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
44101
- * alternative would fork the UI per addon and cannot host the
44102
- * discovery→adopt flow.
44103
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
44104
- * the generated cap-mount auto-`concatCollection`-fans them across every
44105
- * registered provider (notifiers addon + HA addon) so one catalog is
44106
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
44107
- * `addonId` the generated collection router extracts from the call input.
44108
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
44109
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
44110
- * `storage` / `storage-provider` / `recording` caps over the same path. No
44111
- * base64 fallback needed.
44112
- *
44113
- * TODO (deferred, closed-set change — separate decision): add
44114
- * `providerKind: 'notify'` so notification providers surface on the unified
44115
- * admin "Integrations" page.
44116
- */
44117
- /**
44118
- * Zentik-derived typed-media enum — the superset across every kind. Each
44119
- * adapter picks what it supports and the degrade engine filters the rest.
44120
- */
44121
- var AttachmentMediaTypeSchema = _enum([
44122
- "image",
44123
- "video",
44124
- "gif",
44125
- "audio",
44126
- "icon"
44127
- ]);
44128
- /**
44129
- * A single attachment. Exactly one of `url` (remote source, most adapters
44130
- * prefer this) or `bytes` (inline source; required for Pushover-style
44131
- * bytes-only kinds) MUST be present — the degrade engine expresses a
44132
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
44133
- */
44134
- var AttachmentSchema = object({
44135
- mediaType: AttachmentMediaTypeSchema,
44136
- url: string().optional(),
44137
- bytes: _instanceof(Uint8Array).optional(),
44138
- mime: string().optional(),
44139
- name: string().optional()
44140
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
44141
- var NotificationFormatSchema = _enum([
44142
- "text",
44143
- "markdown",
44144
- "html"
44145
- ]);
44146
- /**
44147
- * The CLOSED icon vocabulary an action button may use.
44148
- *
44149
- * A closed set, not a free string, and that is the whole point: an arbitrary
44150
- * icon name is one that ntfy renders, zentik silently drops, and nobody
44151
- * notices — the same class of gap as a zone vocabulary nothing produced
44152
- * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
44153
- * declares `actionIcons: false` and the degrade engine strips the field.
44154
- *
44155
- * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
44156
- * renderer's icon set; "acknowledge" survives an adapter that draws it
44157
- * differently.
44158
- *
44159
- * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
44160
- *
44161
- * These names are for US. **No adapter may forward one verbatim.** Each maps
44162
- * the whole set onto its own renderer's vocabulary through a
44163
- * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
44164
- * fallback, so adding a member here fails every adapter's build until someone
44165
- * decides its glyph, which is the only place that decision can be made
44166
- * honestly.
44167
- *
44168
- * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
44169
- * `disarm` straight through; iOS feeds that string to
44170
- * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
44171
- * and every snooze and alarm button arrived BLANK. A pass-through is not a
44172
- * mapping, and "the field is documented" is not "the value renders".
44173
- *
44174
- * Adding a member is TRAIN-BOUND. The enum lives in the published
44175
- * `@camstack/server` closure and the cap seam validates against the HUB's copy,
44176
- * so an addon that emits a token the running hub does not know does not lose an
44177
- * icon — its whole `send` fails Zod validation and the notification never
44178
- * arrives. Never emit a new token from an addon before the train carrying it.
44179
- */
44180
- var NotificationActionIconSchema = _enum([
44181
- "acknowledge",
44182
- "dismiss",
44183
- "silence",
44184
- "snooze",
44185
- "view",
44186
- "play",
44187
- "open",
44188
- "close",
44189
- "lock",
44190
- "unlock",
44191
- "arm",
44192
- "arm-home",
44193
- "arm-away",
44194
- "arm-night",
44195
- "disarm",
44196
- "light",
44197
- "alert",
44198
- "camera"
44199
- ]);
44200
- /** A single tap-through action button. */
44201
- var NotificationActionSchema = object({
44202
- id: string(),
44203
- label: string(),
44204
- url: string().optional(),
44205
- /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
44206
- icon: NotificationActionIconSchema.optional(),
44207
- /**
44208
- * Renders in a warning style where the notifier supports it.
44209
- *
44210
- * A HINT, never a gate. The callback's authority is its token and nothing
44211
- * else — see `notification-center/action-token.ts` for what that does and
44212
- * does not buy.
44213
- */
44214
- destructive: boolean().optional(),
44215
- /**
44216
- * How the tap should REACH the url.
44217
- *
44218
- * `navigate` (absent, and every button authored before this field) opens it:
44219
- * the phone leaves the notification and shows whatever the callback returns.
44220
- * That is right for a button whose answer the operator wants to read.
44221
- *
44222
- * `background` fires it as a POST and stays put. It exists for the buttons
44223
- * whose whole point is not to interrupt — "silence this for 30 minutes" is
44224
- * an answer to the notification, and being thrown into a browser tab to
44225
- * confirm it costs more attention than the notification did. A backend that
44226
- * cannot do a background call renders it as an ordinary link (the adapters
44227
- * fall back rather than dropping the button), so this is a preference, never
44228
- * a requirement.
44229
- */
44230
- mode: _enum(["navigate", "background"]).optional()
44231
- });
44232
- /**
44233
- * The canonical notification. `body` is the only hard field (Apprise model).
44234
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
44235
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
44236
- * the adapter maps this ordinal onto its native level. `level?` is an
44237
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
44238
- * `priority` for that one target.
44239
- */
44240
- var NotificationSchema = object({
44241
- body: string(),
44242
- title: string().optional(),
44243
- format: NotificationFormatSchema.default("text"),
44244
- priority: number().int().min(1).max(5).default(3),
44245
- level: string().optional(),
44246
- attachments: array(AttachmentSchema).optional(),
44247
- clickUrl: string().optional(),
44248
- actions: array(NotificationActionSchema).optional(),
44249
- sound: string().optional(),
44250
- ttl: number().optional(),
44251
- tag: string().optional(),
44252
- deviceId: number().optional(),
44253
- eventId: string().optional(),
44254
- metadata: record(string(), unknown()).optional()
44255
- });
44256
- /** One declared native severity/priority level for a kind. */
44257
- var TargetKindLevelSchema = object({
44258
- id: string(),
44259
- label: string(),
44260
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
44261
- ordinal: number().int().min(1).max(5).nullable(),
44262
- flags: object({
44263
- critical: boolean().optional(),
44264
- silent: boolean().optional(),
44265
- noPush: boolean().optional()
44266
- }).optional(),
44267
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
44268
- requires: array(string()).optional(),
44269
- description: string().optional()
44270
- });
44271
- /** The full capability block consulted before dispatch. */
44272
- var TargetKindCapsSchema = object({
44273
- attachments: object({
44274
- mediaTypes: array(AttachmentMediaTypeSchema),
44275
- mode: _enum([
44276
- "url",
44277
- "bytes",
44278
- "both"
44279
- ]),
44280
- max: number().int().nonnegative(),
44281
- maxBytes: number().int().positive().optional()
44282
- }),
44283
- /** Max action buttons (0 = none). */
44284
- actions: number().int().nonnegative(),
44285
- /**
44286
- * Whether this kind renders a per-action ICON.
44287
- *
44288
- * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
44289
- * run on the addon cap path — three production failures in one day taught
44290
- * this repo that once. Absent is read as false by the degrade engine, which
44291
- * is the safe direction: an icon that is not rendered costs nothing, an icon
44292
- * assumed and dropped costs the operator's trust in the field.
44293
- */
44294
- actionIcons: boolean().optional(),
44295
- levels: array(TargetKindLevelSchema),
44296
- format: array(NotificationFormatSchema),
44297
- clickUrl: boolean(),
44298
- sound: boolean(),
44299
- ttl: boolean(),
44300
- bodyMaxLen: number().int().positive()
44301
- });
44302
- /**
44303
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
44304
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
44305
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
44306
- * the union is large and not meant for runtime validation here; the exported
44307
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
44308
- */
44309
- var ConfigSchemaPassthrough$2 = unknown();
44310
- var TargetKindSchema = object({
44311
- kind: string(),
44312
- label: string(),
44313
- icon: string(),
44314
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
44315
- addonId: string(),
44316
- /**
44317
- * URL of the kind's bundled BRAND icon, served by the providing addon over
44318
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
44319
- * when the addon bundles no icon for that kind — the client then falls back
44320
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
44321
- *
44322
- * Root-relative on purpose: it resolves against whatever origin serves a web
44323
- * client, and a native client joins it onto its own hub base.
44324
- *
44325
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
44326
- * field that survived only because the runtime cap-router forwards provider
44327
- * output verbatim — so every consumer had to re-declare it by hand to stop
44328
- * its own Zod parse from stripping it, and the whole arrangement would have
44329
- * broken silently the moment output validation was tightened anywhere.
44330
- */
44331
- iconUrl: string().optional(),
44332
- /**
44333
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
44334
- *
44335
- * The server knows this and therefore says it, because the client cannot
44336
- * safely guess: a React-Native client renders SVG and raster through two
44337
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
44338
- * not decode SVG on iOS/Android), so without this it silently fell back to a
44339
- * placeholder glyph for every vector icon while the web build looked fine.
44340
- *
44341
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
44342
- * not been updated — a client that cannot determine the type should prefer
44343
- * its raster path, which is the safe default for an unknown image.
44344
- */
44345
- iconMediaType: string().optional(),
44346
- configSchema: ConfigSchemaPassthrough$2,
44347
- /**
44348
- * The config keys that decide WHERE this kind's secrets are sent (server,
44349
- * base URL, instance, chat, recipient). A save that changes any of them
44350
- * while leaving a secret redacted is refused by the provider (D634), so an
44351
- * editor uses this to ask for the secret on exactly those edits instead of
44352
- * on every edit.
44353
- *
44354
- * Optional: absent from a provider that holds no secret (the Home Assistant
44355
- * kinds) and from a provider older than this field — an editor that finds
44356
- * it absent must assume ANY edit may need the secret.
44357
- */
44358
- destinationKeys: array(string()).optional(),
44359
- supportsDiscovery: boolean(),
44360
- caps: TargetKindCapsSchema
44361
- });
44362
- /**
44363
- * A persisted target. `config` holds secrets; providers REDACT secret fields
44364
- * (return a presence marker only) when serving `listTargets` — never
44365
- * round-trip a stored secret to the UI.
44366
- */
44367
- var TargetSchema = object({
44368
- id: string(),
44369
- name: string(),
44370
- kind: string(),
44371
- addonId: string(),
44372
- enabled: boolean(),
44373
- config: record(string(), unknown())
44374
- });
44375
- /** A discovery-surfaced candidate (config is partial + non-secret). */
44376
- var DiscoveredTargetSchema = object({
44377
- kind: string(),
44378
- suggestedName: string(),
44379
- config: record(string(), unknown())
44380
- });
44381
- /** The degrade engine's report — what was resolved / dropped / degraded. */
44382
- var RenderedAsSchema = object({
44383
- level: string(),
44384
- format: NotificationFormatSchema,
44385
- attachmentsSent: number().int().nonnegative(),
44386
- actionsSent: number().int().nonnegative(),
44387
- truncated: boolean(),
44388
- dropped: array(string())
44389
- });
44390
- var SendResultSchema = object({
44391
- success: boolean(),
44392
- error: string().optional(),
44393
- renderedAs: RenderedAsSchema.optional()
44394
- });
44395
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
44396
- var TestResultSchema = SendResultSchema;
44397
- /**
44398
- * Pre-save credential probe against a partial (unsaved) target config.
44399
- * `resolvedUrl` never includes secrets (token stripped from path).
44400
- */
44401
- var ProbeTargetConfigResultSchema = object({
44402
- ok: boolean(),
44403
- resolvedUrl: string(),
44404
- httpStatus: number().int().optional(),
44405
- stateInstance: string().optional(),
44406
- error: string().optional()
44407
- });
44408
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
44409
- kind: string(),
44410
- config: record(string(), unknown()).optional(),
44411
- /**
44412
- * When editing / duplicating, the blank password field never round-trips
44413
- * the stored token. Pass the source target id so the provider can restore
44414
- * secrets server-side before dialling (same merge as upsert).
44415
- */
44416
- targetId: string().optional()
44417
- }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
44418
- kind: string(),
44419
- config: record(string(), unknown()),
44420
- /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
44421
- targetId: string().optional()
44422
- }), ProbeTargetConfigResultSchema, {
44423
- kind: "mutation",
44424
- auth: "admin"
44425
- }), method(object({
44426
- targetId: string(),
44427
- notification: NotificationSchema
44428
- }), SendResultSchema, {
44429
- kind: "mutation",
44430
- auth: "admin"
44431
- }), method(object({
44432
- targetId: string(),
44433
- sample: NotificationSchema.optional()
44434
- }), TestResultSchema, {
44435
- kind: "mutation",
44436
- auth: "admin"
44437
- }), method(object({
44438
- target: TargetSchema,
44439
- /**
44440
- * Duplicate flow: create a NEW target id while copying secrets from an
44441
- * existing row. The UI never holds the stored token; blank / redacted
44442
- * password fields are restored from this source (D634 still applies if
44443
- * destinations diverge without a re-typed secret).
44444
- */
44445
- cloneSecretsFrom: string().optional()
44446
- }), TargetSchema, {
44447
- kind: "mutation",
44448
- auth: "admin"
44449
- }), method(object({ targetId: string() }), _void(), {
44450
- kind: "mutation",
44451
- auth: "admin"
44452
- }), method(object({
44453
- targetId: string(),
44454
- enabled: boolean()
44455
- }), _void(), {
44456
- kind: "mutation",
44457
- auth: "admin"
44458
- });
44459
- new Set([
44460
- {
44461
- id: "person",
44462
- name: "Person"
44463
- },
44464
- {
44465
- id: "vehicle",
44466
- name: "Vehicle"
44467
- },
44468
- {
44469
- id: "animal",
44470
- name: "Animal"
44471
- },
44472
- {
44473
- id: "package",
44474
- name: "Package"
44475
- }
44476
- ].map((l) => l.id));
44477
- var COCO_TO_MACRO = {
44478
- mapping: {
44479
- person: "person",
44480
- bicycle: "vehicle",
44481
- car: "vehicle",
44482
- motorcycle: "vehicle",
44483
- airplane: "vehicle",
44484
- bus: "vehicle",
44485
- train: "vehicle",
44486
- truck: "vehicle",
44487
- boat: "vehicle",
44488
- bird: "animal",
44489
- cat: "animal",
44490
- dog: "animal",
44491
- horse: "animal",
44492
- sheep: "animal",
44493
- cow: "animal",
44494
- elephant: "animal",
44495
- bear: "animal",
44496
- zebra: "animal",
44497
- giraffe: "animal",
44498
- suitcase: "package",
44499
- backpack: "package",
44500
- handbag: "package"
44501
- },
44502
- preserveOriginal: false
44503
- };
44504
45161
  var AUDIO_MACRO_LABELS = [
44505
45162
  {
44506
45163
  id: "speech",
@@ -50901,14 +51558,6 @@ var EngineProvisioningSchema = object({
50901
51558
  */
50902
51559
  configIssues: array(string()).optional()
50903
51560
  });
50904
- var PipelineStepInputSchema = lazy(() => object({
50905
- addonId: string(),
50906
- modelId: string().optional(),
50907
- enabled: boolean().default(true),
50908
- children: array(PipelineStepInputSchema).optional(),
50909
- settings: record(string(), unknown()).optional(),
50910
- jumpDeviceKey: string().optional()
50911
- }));
50912
51561
  var ModelSubstitutionSchema = object({
50913
51562
  addonId: string(),
50914
51563
  chosen: string(),
@@ -50987,7 +51636,6 @@ var ConfigUISchemaBridge = custom();
50987
51636
  var ConfigUISchemaNullableBridge = custom();
50988
51637
  var InferenceCapabilitiesBridge = custom();
50989
51638
  var ModelAvailabilityListBridge = custom();
50990
- var PipelineRunResultBridge = custom();
50991
51639
  method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
50992
51640
  modelId: string(),
50993
51641
  settings: record(string(), unknown()).readonly()
@@ -54149,6 +54797,12 @@ var ServerUpdateActionResultSchema = object({
54149
54797
  */
54150
54798
  blockers: array(UpdateBlockerSchema).optional()
54151
54799
  });
54800
+ var LaunchOverridesSchema = object({
54801
+ /** The persisted heap ceiling in MB, or `null` when none is set. */
54802
+ heapMb: number().int().nullable() });
54803
+ var SetLaunchOverridesResultSchema = LaunchOverridesSchema.extend({
54804
+ /** Always true: the override is read at launch, not by the running process. */
54805
+ restartRequired: boolean() });
54152
54806
  method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
54153
54807
  kind: "mutation",
54154
54808
  auth: "admin"
@@ -54171,6 +54825,9 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
54171
54825
  }), method(_void(), ServerUpdateActionResultSchema, {
54172
54826
  kind: "mutation",
54173
54827
  auth: "admin"
54828
+ }), method(_void(), LaunchOverridesSchema, { auth: "admin" }), method(object({ heapMb: number().int().min(512).max(65536).nullable() }), SetLaunchOverridesResultSchema, {
54829
+ kind: "mutation",
54830
+ auth: "admin"
54174
54831
  });
54175
54832
  /**
54176
54833
  * `smtp-provider` — pluggable email delivery surface.
@@ -56572,7 +57229,9 @@ var ManifestShapeSchema = object({
56572
57229
  icon: string().optional()
56573
57230
  }).optional(),
56574
57231
  protected: boolean().optional(),
56575
- removable: boolean().optional()
57232
+ removable: boolean().optional(),
57233
+ /** Declared Integrations-page block (mirror of `AddonDeclaration.integrationSection`). */
57234
+ integrationSection: _enum(["system", "providers"]).optional()
56576
57235
  });
56577
57236
  var AddonProcessSchema = object({
56578
57237
  pid: number().optional(),
@@ -56937,9 +57596,9 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56937
57596
  }), LogStreamEntrySchema, { kind: "subscription" });
56938
57597
  object({
56939
57598
  /** Carbon dioxide concentration in ppm. */
56940
- co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
57599
+ co2: number().min(0).optional().meta(readingMeta("concentration-volume")),
56941
57600
  /** Total volatile organic compounds in ppb. */
56942
- vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
57601
+ voc: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56943
57602
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56944
57603
  pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56945
57604
  /** Particulate matter ≤ 10 μm in µg/m³. */
@@ -56952,7 +57611,7 @@ object({
56952
57611
  DeviceType.Sensor;
56953
57612
  object({
56954
57613
  /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56955
- lux: number().min(0).nullable().meta(readingMeta("illuminance", {
57614
+ illuminance: number().min(0).nullable().meta(readingMeta("illuminance", {
56956
57615
  precision: 0,
56957
57616
  label: "Illuminance"
56958
57617
  })),
@@ -57049,6 +57708,84 @@ DeviceType.Camera, method(object({ deviceId: number() }), AudioMetricsSnapshotSc
57049
57708
  * would produce more than `maxPoints` samples. */
57050
57709
  sampleEveryMs: number().int().positive().optional()
57051
57710
  }), AudioMetricsHistorySchema);
57711
+ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
57712
+ actionId: string().min(1),
57713
+ variables: record(string(), ActionVariableValueSchema).optional()
57714
+ }), _void(), {
57715
+ kind: "mutation",
57716
+ auth: "admin"
57717
+ }), method(object({ actionId: string().min(1) }), _void(), {
57718
+ kind: "mutation",
57719
+ auth: "admin"
57720
+ });
57721
+ /**
57722
+ * actions — the ONE surface for runnable actions, for the UI and for
57723
+ * automations. Provided by the core-blocks builtin on the hub.
57724
+ *
57725
+ * An action is a LIST ENTRY, not a device (D883): either internal (cap and wait
57726
+ * steps, kept in the hub's `actions` record kind) or declared by an addon over
57727
+ * `action-source`. `list` asks every `action-source` provider LIVE and overlays
57728
+ * the run status from the hub's `runs` ledger; there is no catalog here to
57729
+ * drift (D3).
57730
+ *
57731
+ * **Permissions follow the camera, as linked devices do.** An action is linked
57732
+ * to cameras by the operator (`setLinks`, D356: nothing links itself). A
57733
+ * non-admin may `get` / `run` an action only through a camera they may see that
57734
+ * links it: they name the camera (`deviceId`, a top-level field so the scope
57735
+ * gate sees it) and the provider refuses an action that camera does not link.
57736
+ * An admin, and an in-process caller (an automation block), may run any action.
57737
+ * Every refusal is typed (`[not-linked] …`) and logged.
57738
+ *
57739
+ * A second `run` of an action that is already running is REFUSED — traced and
57740
+ * logged — never queued.
57741
+ */
57742
+ var ActionIdInput = object({ actionId: string().min(1) });
57743
+ var ActionLinksSchema = object({
57744
+ deviceId: number().int().nonnegative(),
57745
+ actionIds: array(string().min(1)).max(100)
57746
+ });
57747
+ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
57748
+ actionId: string().min(1),
57749
+ deviceId: number().int().nonnegative().optional()
57750
+ }), object({ action: ActionEntrySchema.nullable() }), {
57751
+ role: "viewer",
57752
+ auth: "protected",
57753
+ kind: "query",
57754
+ caller: "required"
57755
+ }), method(object({
57756
+ actionId: string().min(1),
57757
+ variables: record(string(), ActionVariableValueSchema).optional(),
57758
+ wait: boolean().optional(),
57759
+ origin: ActionRunOriginSchema.optional(),
57760
+ deviceId: number().int().nonnegative().optional()
57761
+ }), ActionRunResultSchema, {
57762
+ role: "viewer",
57763
+ auth: "protected",
57764
+ kind: "mutation",
57765
+ caller: "required"
57766
+ }), method(ActionIdInput, _void(), {
57767
+ kind: "mutation",
57768
+ auth: "admin"
57769
+ }), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({ action: ActionEntrySchema }), {
57770
+ kind: "mutation",
57771
+ auth: "admin"
57772
+ }), method(object({
57773
+ actionId: string().min(1),
57774
+ action: ActionInputSchema.partial()
57775
+ }), object({ action: ActionEntrySchema }), {
57776
+ kind: "mutation",
57777
+ auth: "admin"
57778
+ }), method(ActionIdInput, _void(), {
57779
+ kind: "mutation",
57780
+ auth: "admin"
57781
+ }), method(object({ deviceId: number().int().nonnegative() }), array(ActionEntrySchema), {
57782
+ role: "viewer",
57783
+ auth: "protected",
57784
+ kind: "query"
57785
+ }), method(object({ deviceId: number().int().nonnegative() }), ActionLinksSchema, { auth: "admin" }), method(ActionLinksSchema, ActionLinksSchema, {
57786
+ kind: "mutation",
57787
+ auth: "admin"
57788
+ });
57052
57789
  object({
57053
57790
  /** Whether the automation is currently enabled. Disabled automations
57054
57791
  * ignore their trigger block — manual `trigger` still works. */
@@ -57102,7 +57839,7 @@ var BatteryStatusSchema = object({
57102
57839
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
57103
57840
  * declines to band a non-finite reading, which is the correct shape.
57104
57841
  */
57105
- percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
57842
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
57106
57843
  /**
57107
57844
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
57108
57845
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -57112,7 +57849,7 @@ var BatteryStatusSchema = object({
57112
57849
  * `'unknown'` is the absence of an answer: the provider has not read the
57113
57850
  * power fields yet, or the firmware reported neither. Before it existed,
57114
57851
  * two unreadable fields produced `'none'`, and a camera nobody had reached
57115
- * was drawn "on battery alone" — the same D315 defect `percentage` closed
57852
+ * was drawn "on battery alone" — the same D315 defect `level` closed
57116
57853
  * by going nullable. Consumers SKIP it (no charger state published, no
57117
57854
  * glyph, no HomeKit ChargingState) rather than coerce it either way.
57118
57855
  */
@@ -57136,7 +57873,7 @@ var BatteryStatusSchema = object({
57136
57873
  * that only report charge. A second reading on this cap, not a second
57137
57874
  * `numeric-sensor` on the same device.
57138
57875
  */
57139
- volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
57876
+ voltage: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
57140
57877
  /**
57141
57878
  * Ms epoch of the last time the device PROVED it was reachable — a
57142
57879
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -57170,7 +57907,7 @@ var BatteryStatusSchema = object({
57170
57907
  /**
57171
57908
  * True when the source is a BINARY low-battery indicator (HA
57172
57909
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
57173
- * charge level — `percentage` is then a coarse stand-in (100 = normal,
57910
+ * charge level — `level` is then a coarse stand-in (100 = normal,
57174
57911
  * sub-threshold = low). UI MUST render "Normal"/"Low" instead of a
57175
57912
  * misleading exact percentage. Absent/false → genuine 0–100 % reading.
57176
57913
  */
@@ -57196,26 +57933,388 @@ object({
57196
57933
  DeviceType.Sensor;
57197
57934
  object({
57198
57935
  /** Current level as 0..100 inclusive. Firmware-reported. */
57199
- percentage: number().min(0).max(100).meta(readingMeta("percentage")),
57936
+ level: number().min(0).max(100).meta(readingMeta("percentage")),
57200
57937
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
57201
57938
  lastChangedAt: number()
57202
57939
  });
57203
57940
  DeviceType.Light, method(object({
57204
57941
  deviceId: number().int().nonnegative(),
57205
- percentage: number().min(0).max(100)
57942
+ level: number().min(0).max(100)
57206
57943
  }), _void(), {
57207
57944
  kind: "mutation",
57208
57945
  auth: "protected"
57209
57946
  }), object({
57210
57947
  deviceId: number(),
57211
- percentage: number().min(0).max(100),
57948
+ level: number().min(0).max(100),
57212
57949
  lastChangedAt: number()
57213
57950
  });
57951
+ /**
57952
+ * The role vocabulary of a remote (D963): what a button MEANS, independent of
57953
+ * how it is wired. A layout template places buttons by role; a device binds a
57954
+ * role to an action; an exporter asks for a role by name. Roles are shared so
57955
+ * one layout fits every appliance and one voice command fits every binding.
57956
+ *
57957
+ * A role outside this list is `custom:<key>` — the operator's own button (a
57958
+ * SmartIR source, a channel preset). The key is a lowercase slug.
57959
+ */
57960
+ var REMOTE_KNOWN_ROLES = [
57961
+ "power",
57962
+ "power_on",
57963
+ "power_off",
57964
+ "volume_up",
57965
+ "volume_down",
57966
+ "mute",
57967
+ "channel_up",
57968
+ "channel_down",
57969
+ "up",
57970
+ "down",
57971
+ "left",
57972
+ "right",
57973
+ "ok",
57974
+ "back",
57975
+ "home",
57976
+ "menu",
57977
+ "info",
57978
+ "guide",
57979
+ "exit",
57980
+ "input",
57981
+ "play",
57982
+ "pause",
57983
+ "play_pause",
57984
+ "stop",
57985
+ "next",
57986
+ "previous",
57987
+ "fast_forward",
57988
+ "rewind",
57989
+ "digit_0",
57990
+ "digit_1",
57991
+ "digit_2",
57992
+ "digit_3",
57993
+ "digit_4",
57994
+ "digit_5",
57995
+ "digit_6",
57996
+ "digit_7",
57997
+ "digit_8",
57998
+ "digit_9"
57999
+ ];
58000
+ var CUSTOM_KEY_RE = /^[a-z0-9][a-z0-9_]{0,47}$/;
58001
+ var KNOWN_ROLES = new Set(REMOTE_KNOWN_ROLES);
58002
+ function isRemoteKnownRole(value) {
58003
+ return KNOWN_ROLES.has(value);
58004
+ }
58005
+ function isRemoteCustomKey(key) {
58006
+ return CUSTOM_KEY_RE.test(key);
58007
+ }
58008
+ /** The key of a `custom:<key>` role, or null for any other string. */
58009
+ function customKeyOfRole(role) {
58010
+ if (!role.startsWith("custom:")) return null;
58011
+ const key = role.slice(7);
58012
+ return isRemoteCustomKey(key) ? key : null;
58013
+ }
58014
+ function isRemoteRole(value) {
58015
+ return isRemoteKnownRole(value) || customKeyOfRole(value) !== null;
58016
+ }
58017
+ var RemoteRoleSchema = string().refine(isRemoteRole, { message: "a role is one of the known roles or custom:<key> (lowercase letters, digits, _)" });
58018
+ var RemoteBindingSchema = object({ action: AutomationCapStepSchema.extend({ deviceId: number().int().nonnegative() }) });
58019
+ var RemoteCustomButtonSchema = object({
58020
+ /** The slug after `custom:` in the role. */
58021
+ key: string().refine(isRemoteCustomKey, { message: "a lowercase slug" }),
58022
+ /** What the operator calls it, and what a voice command may name. */
58023
+ name: string().min(1).max(60)
58024
+ });
58025
+ var RemotePowerModeSchema = _enum(["toggle", "discrete"]);
58026
+ var RemoteConfigSchema = object({
58027
+ layoutTemplateId: string().min(1).nullable(),
58028
+ /**
58029
+ * The transmitter device new bindings and an import send through. A binding
58030
+ * names its own device, so one command may override it.
58031
+ */
58032
+ defaultTransmitterId: number().int().nonnegative().nullable().default(null),
58033
+ /** Keyed by role. A role with no entry falls back to the native cap. */
58034
+ bindings: record(string(), RemoteBindingSchema),
58035
+ customButtons: array(RemoteCustomButtonSchema).max(100),
58036
+ power: object({ mode: RemotePowerModeSchema })
58037
+ }).superRefine((config, ctx) => {
58038
+ const roles = Object.keys(config.bindings);
58039
+ if (roles.length > 200) ctx.addIssue({
58040
+ code: "custom",
58041
+ message: "too many bindings",
58042
+ path: ["bindings"]
58043
+ });
58044
+ for (const role of roles) if (!isRemoteRole(role)) ctx.addIssue({
58045
+ code: "custom",
58046
+ message: `"${role}" is not a role`,
58047
+ path: ["bindings", role]
58048
+ });
58049
+ const keys = /* @__PURE__ */ new Set();
58050
+ config.customButtons.forEach((button, index) => {
58051
+ if (keys.has(button.key)) ctx.addIssue({
58052
+ code: "custom",
58053
+ message: `custom button "${button.key}" is listed twice`,
58054
+ path: [
58055
+ "customButtons",
58056
+ index,
58057
+ "key"
58058
+ ]
58059
+ });
58060
+ keys.add(button.key);
58061
+ });
58062
+ for (const role of roles) {
58063
+ const key = customKeyOfRole(role);
58064
+ if (key !== null && !keys.has(key)) ctx.addIssue({
58065
+ code: "custom",
58066
+ message: `${role} is bound but has no entry in customButtons`,
58067
+ path: ["bindings", role]
58068
+ });
58069
+ }
58070
+ });
58071
+ object({
58072
+ power: object({
58073
+ state: _enum(["on", "off"]),
58074
+ at: number()
58075
+ }).nullable(),
58076
+ muted: object({
58077
+ value: boolean(),
58078
+ at: number()
58079
+ }).nullable()
58080
+ });
58081
+ var RemoteLayoutButtonSchema = object({
58082
+ id: string().min(1).max(40),
58083
+ /** 0-based grid position. */
58084
+ row: number().int().min(0).max(24),
58085
+ col: number().int().min(0).max(24),
58086
+ rowSpan: number().int().min(1).max(24),
58087
+ colSpan: number().int().min(1).max(24),
58088
+ icon: string().min(1).max(60).optional(),
58089
+ label: string().min(1).max(40).optional(),
58090
+ role: RemoteRoleSchema
58091
+ });
58092
+ object({
58093
+ columns: number().int().min(1).max(24),
58094
+ rows: number().int().min(1).max(24),
58095
+ buttons: array(RemoteLayoutButtonSchema).max(200)
58096
+ }).superRefine((grid, ctx) => {
58097
+ const ids = /* @__PURE__ */ new Set();
58098
+ const taken = /* @__PURE__ */ new Map();
58099
+ grid.buttons.forEach((button, index) => {
58100
+ if (ids.has(button.id)) ctx.addIssue({
58101
+ code: "custom",
58102
+ message: `button id "${button.id}" is used twice`,
58103
+ path: [
58104
+ "buttons",
58105
+ index,
58106
+ "id"
58107
+ ]
58108
+ });
58109
+ ids.add(button.id);
58110
+ if (button.row + button.rowSpan > grid.rows || button.col + button.colSpan > grid.columns) {
58111
+ ctx.addIssue({
58112
+ code: "custom",
58113
+ message: `button "${button.id}" leaves the grid`,
58114
+ path: ["buttons", index]
58115
+ });
58116
+ return;
58117
+ }
58118
+ for (let r = button.row; r < button.row + button.rowSpan; r += 1) for (let c = button.col; c < button.col + button.colSpan; c += 1) {
58119
+ const cell = `${r}:${c}`;
58120
+ const other = taken.get(cell);
58121
+ if (other !== void 0) {
58122
+ ctx.addIssue({
58123
+ code: "custom",
58124
+ message: `buttons "${other}" and "${button.id}" overlap`,
58125
+ path: ["buttons", index]
58126
+ });
58127
+ return;
58128
+ }
58129
+ taken.set(cell, button.id);
58130
+ }
58131
+ });
58132
+ });
58133
+ var RemoteBoundCommandSchema = object({
58134
+ role: RemoteRoleSchema,
58135
+ /** The operator's name for a custom button; absent for a known role. */
58136
+ label: string().optional(),
58137
+ custom: boolean()
58138
+ });
58139
+ var RemoteCommandListSchema = object({
58140
+ layoutTemplateId: string().nullable(),
58141
+ power: object({ mode: RemotePowerModeSchema }),
58142
+ /** Only the roles that are BOUND — the ones a press can reach. */
58143
+ commands: array(RemoteBoundCommandSchema)
58144
+ });
58145
+ var RemoteRefusalReasonSchema = _enum([
58146
+ "no-remote",
58147
+ "unbound-role",
58148
+ "action-failed"
58149
+ ]);
58150
+ var RemoteSendResultSchema = discriminatedUnion("status", [
58151
+ object({
58152
+ status: literal("sent"),
58153
+ /** How many times the action ran. */
58154
+ sends: number().int().min(1)
58155
+ }),
58156
+ object({
58157
+ status: literal("skipped"),
58158
+ reason: literal("already-in-state")
58159
+ }),
58160
+ object({
58161
+ status: literal("refused"),
58162
+ reason: RemoteRefusalReasonSchema,
58163
+ detail: string().optional()
58164
+ })
58165
+ ]);
58166
+ var RemoteMediaCommandSchema = _enum([
58167
+ "play",
58168
+ "pause",
58169
+ "stop",
58170
+ "next",
58171
+ "previous",
58172
+ "setMute",
58173
+ "selectSource"
58174
+ ]);
58175
+ /** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
58176
+ var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
58177
+ var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status: literal("saved") }), object({
58178
+ status: literal("refused"),
58179
+ reason: _enum(["unknown-template", "not-a-media-player"]),
58180
+ detail: string()
58181
+ })]);
58182
+ var deviceInput = object({ deviceId: number().int().nonnegative() });
58183
+ DeviceType.MediaPlayer;
58184
+ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
58185
+ kind: "mutation",
58186
+ auth: "admin"
58187
+ }), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
58188
+ role: RemoteRoleSchema,
58189
+ /** Sends in a row (volume steps). */
58190
+ repeat: number().int().min(1).max(50).default(1)
58191
+ }), RemoteSendResultSchema, {
58192
+ kind: "mutation",
58193
+ auth: "protected"
58194
+ }), method(deviceInput.extend({
58195
+ command: RemoteMediaCommandSchema,
58196
+ muted: boolean().optional(),
58197
+ source: string().optional(),
58198
+ repeat: number().int().min(1).max(50).default(1)
58199
+ }), RemoteMediaCommandResultSchema, {
58200
+ kind: "mutation",
58201
+ auth: "protected"
58202
+ });
58203
+ var TemplateKindSchema = _enum(["remote"]);
58204
+ /**
58205
+ * template — the generic store of reusable, named operator data (D963). A
58206
+ * record is `{ id, kind, name, payload }`; the kinds (today only `remote`, the
58207
+ * button grid of a remote) and the schema each payload must satisfy are
58208
+ * declared by the system in `template-kinds.ts`. Nothing here is specific to a
58209
+ * kind: the engine validates a payload against its kind's schema and, on a
58210
+ * delete, asks the kind's owner whether anything still points at the record.
58211
+ *
58212
+ * Every refusal is a typed result — a UI says "unknown kind" or names the
58213
+ * devices that still use a template, without parsing text.
58214
+ */
58215
+ var TemplateRecordSchema = object({
58216
+ id: string().min(1),
58217
+ kind: TemplateKindSchema,
58218
+ name: string().min(1).max(120),
58219
+ /** Shaped by the kind; `checkTemplatePayload(kind, payload)` reads it. */
58220
+ payload: unknown(),
58221
+ createdAt: number(),
58222
+ updatedAt: number()
58223
+ });
58224
+ var TemplateWriteResultSchema = discriminatedUnion("status", [object({
58225
+ status: literal("saved"),
58226
+ template: TemplateRecordSchema
58227
+ }), object({
58228
+ status: literal("refused"),
58229
+ reason: _enum(["invalid-payload", "not-found"]),
58230
+ detail: string()
58231
+ })]);
58232
+ /** A device still pointing at a template. */
58233
+ var TemplateUserSchema = object({
58234
+ deviceId: number().int().nonnegative(),
58235
+ name: string()
58236
+ });
58237
+ var TemplateDeleteResultSchema = discriminatedUnion("status", [
58238
+ object({ status: literal("deleted") }),
58239
+ object({ status: literal("not-found") }),
58240
+ object({
58241
+ status: literal("in-use"),
58242
+ devices: array(TemplateUserSchema).min(1)
58243
+ })
58244
+ ]);
58245
+ method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSchema), { auth: "protected" }), method(object({ id: string().min(1) }), TemplateRecordSchema.nullable(), { auth: "protected" }), method(object({
58246
+ kind: TemplateKindSchema,
58247
+ name: string().min(1).max(120),
58248
+ payload: unknown()
58249
+ }), TemplateWriteResultSchema, {
58250
+ kind: "mutation",
58251
+ auth: "admin"
58252
+ }), method(object({
58253
+ id: string().min(1),
58254
+ name: string().min(1).max(120).optional(),
58255
+ payload: unknown().optional()
58256
+ }), TemplateWriteResultSchema, {
58257
+ kind: "mutation",
58258
+ auth: "admin"
58259
+ }), method(object({ id: string().min(1) }), TemplateDeleteResultSchema, {
58260
+ kind: "mutation",
58261
+ auth: "admin"
58262
+ });
57214
58263
  DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
57215
58264
  kind: "mutation",
57216
58265
  auth: "protected"
57217
58266
  });
57218
58267
  /**
58268
+ * A pulse train in signed microseconds: mark (+) first, then space (-), strictly
58269
+ * alternating, no zeros, 2..1024 entries.
58270
+ */
58271
+ var IrTimingsSchema = array(number().int()).min(2).max(1024).superRefine((timings, ctx) => {
58272
+ timings.forEach((value, index) => {
58273
+ const expectMark = index % 2 === 0;
58274
+ if (value === 0 || value > 0 !== expectMark) ctx.addIssue({
58275
+ code: "custom",
58276
+ message: `timing ${index} must be a non-zero ${expectMark ? "mark (positive)" : "space (negative)"}`,
58277
+ path: [index]
58278
+ });
58279
+ });
58280
+ });
58281
+ /** Per-command transmit options. Every key is optional; a transmitter applies what it can. */
58282
+ var TransmitSettingsSchema = object({
58283
+ carrierHz: number().int().positive().optional(),
58284
+ dutyCyclePercent: number().int().min(1).max(100).optional(),
58285
+ repeat: number().int().min(0).max(20).optional(),
58286
+ repeatDelayMs: number().int().min(0).optional()
58287
+ });
58288
+ /** The setting keys a transmitter may declare as settable (`getInfo.settable`). */
58289
+ var TransmitSettingKeySchema = _enum([
58290
+ "carrierHz",
58291
+ "dutyCyclePercent",
58292
+ "repeat",
58293
+ "repeatDelayMs"
58294
+ ]);
58295
+ var IrMediumSchema = _enum(["ir", "rf"]);
58296
+ var IrSendResultSchema = object({
58297
+ ok: literal(true),
58298
+ /** Settings the transmitter could not apply; the train was still sent. */
58299
+ ignoredSettings: array(string())
58300
+ });
58301
+ var IrTransmitterInfoSchema = object({
58302
+ media: array(IrMediumSchema),
58303
+ settable: array(TransmitSettingKeySchema),
58304
+ maxTimings: number().int().positive()
58305
+ });
58306
+ DeviceType.Generic, method(object({
58307
+ deviceId: number().int().nonnegative(),
58308
+ timings: IrTimingsSchema,
58309
+ settings: TransmitSettingsSchema.optional()
58310
+ }), IrSendResultSchema, {
58311
+ kind: "mutation",
58312
+ auth: "protected"
58313
+ }), method(object({ deviceId: number().int().nonnegative() }), IrTransmitterInfoSchema, {
58314
+ kind: "query",
58315
+ auth: "protected"
58316
+ });
58317
+ /**
57219
58318
  * `calendar-source` — collection cap exposing per-provider calendars and
57220
58319
  * their events. Every addon that contributes calendars (Home Assistant,
57221
58320
  * a future Google Calendar addon, …) registers a provider here. The hub
@@ -57719,11 +58818,11 @@ var ColorInputSchema = discriminatedUnion("mode", [
57719
58818
  }),
57720
58819
  object({
57721
58820
  mode: literal("mired"),
57722
- mireds: number().int().min(50).max(1e3)
58821
+ colorTemperature: number().int().min(50).max(1e3)
57723
58822
  })
57724
58823
  ]);
57725
58824
  object({
57726
- /** Active color mode — which of `rgb` / `hsv` / `mireds` reflects
58825
+ /** Active color mode — which of `rgb` / `hsv` / `colorTemperature` reflects
57727
58826
  * the bulb's current state. */
57728
58827
  mode: _enum([
57729
58828
  "rgb",
@@ -57735,7 +58834,7 @@ object({
57735
58834
  /** Populated when `mode === 'hsv'`. */
57736
58835
  hsv: HsvTripletSchema.optional(),
57737
58836
  /** Populated when `mode === 'mired'`. */
57738
- mireds: number().int().optional().meta(readingMeta("colour-temperature")),
58837
+ colorTemperature: number().int().optional().meta(readingMeta("colour-temperature")),
57739
58838
  /** Ms epoch of the last operator-driven change. */
57740
58839
  lastChangedAt: number()
57741
58840
  });
@@ -57754,7 +58853,7 @@ DeviceType.Light, method(object({
57754
58853
  ]),
57755
58854
  rgb: RgbTripletSchema.optional(),
57756
58855
  hsv: HsvTripletSchema.optional(),
57757
- mireds: number().int().optional(),
58856
+ colorTemperature: number().int().optional(),
57758
58857
  lastChangedAt: number()
57759
58858
  });
57760
58859
  var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
@@ -58180,11 +59279,11 @@ object({
58180
59279
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
58181
59280
  availablePrograms: array(string()),
58182
59281
  /** Seconds remaining on the active cycle, or null when not running / not known. */
58183
- remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
59282
+ remaining: number().int().nonnegative().nullable().meta(readingMeta("duration")),
58184
59283
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
58185
- delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
59284
+ delayedStart: number().int().nonnegative().nullable().meta(readingMeta("duration")),
58186
59285
  /** Cycle completion 0..100, or null when not applicable. */
58187
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59286
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58188
59287
  /** True when the door is reported open; null when the door state is not known. */
58189
59288
  doorOpen: boolean().nullable(),
58190
59289
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -58229,7 +59328,7 @@ object({
58229
59328
  /** Ms epoch of the last value received from the appliance; 0 = never. */
58230
59329
  lastFetchedAt: number()
58231
59330
  });
58232
- var DeviceIdSchema = number().int().nonnegative();
59331
+ var DeviceIdSchema$1 = number().int().nonnegative();
58233
59332
  /** A value a program option or setting can take. */
58234
59333
  var DishwasherValueSchema = union([
58235
59334
  boolean(),
@@ -58293,77 +59392,77 @@ var DishwasherOptionsSchema = object({
58293
59392
  options: array(DishwasherOptionDescriptorSchema)
58294
59393
  });
58295
59394
  DeviceType.Dishwasher, method(object({
58296
- deviceId: DeviceIdSchema,
59395
+ deviceId: DeviceIdSchema$1,
58297
59396
  program: string().min(1).optional(),
58298
59397
  options: record(string(), DishwasherValueSchema).optional()
58299
59398
  }), _void(), {
58300
59399
  kind: "mutation",
58301
59400
  auth: "protected"
58302
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
59401
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
58303
59402
  kind: "mutation",
58304
59403
  auth: "protected"
58305
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
59404
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
58306
59405
  kind: "mutation",
58307
59406
  auth: "protected"
58308
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
59407
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
58309
59408
  kind: "mutation",
58310
59409
  auth: "protected"
58311
59410
  }), method(object({
58312
- deviceId: DeviceIdSchema,
59411
+ deviceId: DeviceIdSchema$1,
58313
59412
  program: string().min(1)
58314
59413
  }), _void(), {
58315
59414
  kind: "mutation",
58316
59415
  auth: "protected"
58317
59416
  }), method(object({
58318
- deviceId: DeviceIdSchema,
59417
+ deviceId: DeviceIdSchema$1,
58319
59418
  on: boolean()
58320
59419
  }), _void(), {
58321
59420
  kind: "mutation",
58322
59421
  auth: "protected"
58323
59422
  }), method(object({
58324
- deviceId: DeviceIdSchema,
59423
+ deviceId: DeviceIdSchema$1,
58325
59424
  on: boolean()
58326
59425
  }), _void(), {
58327
59426
  kind: "mutation",
58328
59427
  auth: "protected"
58329
59428
  }), method(object({
58330
- deviceId: DeviceIdSchema,
59429
+ deviceId: DeviceIdSchema$1,
58331
59430
  on: boolean()
58332
59431
  }), _void(), {
58333
59432
  kind: "mutation",
58334
59433
  auth: "protected"
58335
59434
  }), method(object({
58336
- deviceId: DeviceIdSchema,
59435
+ deviceId: DeviceIdSchema$1,
58337
59436
  on: boolean()
58338
59437
  }), _void(), {
58339
59438
  kind: "mutation",
58340
59439
  auth: "protected"
58341
59440
  }), method(object({
58342
- deviceId: DeviceIdSchema,
59441
+ deviceId: DeviceIdSchema$1,
58343
59442
  on: boolean()
58344
59443
  }), _void(), {
58345
59444
  kind: "mutation",
58346
59445
  auth: "protected"
58347
59446
  }), method(object({
58348
- deviceId: DeviceIdSchema,
59447
+ deviceId: DeviceIdSchema$1,
58349
59448
  on: boolean()
58350
59449
  }), _void(), {
58351
59450
  kind: "mutation",
58352
59451
  auth: "protected"
58353
59452
  }), method(object({
58354
- deviceId: DeviceIdSchema,
59453
+ deviceId: DeviceIdSchema$1,
58355
59454
  on: boolean()
58356
59455
  }), _void(), {
58357
59456
  kind: "mutation",
58358
59457
  auth: "protected"
58359
59458
  }), method(object({
58360
- deviceId: DeviceIdSchema,
59459
+ deviceId: DeviceIdSchema$1,
58361
59460
  key: string().min(1),
58362
59461
  value: DishwasherValueSchema
58363
59462
  }), _void(), {
58364
59463
  kind: "mutation",
58365
59464
  auth: "protected"
58366
- }), method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" });
59465
+ }), method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" });
58367
59466
  DeviceType.Camera;
58368
59467
  object({
58369
59468
  /** Ms epoch of the last press. null = never observed since this provider started. */
@@ -58802,7 +59901,7 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
58802
59901
  object({
58803
59902
  /** Active speed as 0..100 inclusive. Null when the device has no
58804
59903
  * speed surface (single-speed fan). */
58805
- percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59904
+ speed: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58806
59905
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
58807
59906
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
58808
59907
  percentageStep: number().positive().optional().meta(readingMeta("percentage")),
@@ -58822,7 +59921,7 @@ object({
58822
59921
  });
58823
59922
  DeviceType.Fan, method(object({
58824
59923
  deviceId: number().int().nonnegative(),
58825
- percentage: number().min(0).max(100)
59924
+ speed: number().min(0).max(100)
58826
59925
  }), _void(), {
58827
59926
  kind: "mutation",
58828
59927
  auth: "protected"
@@ -59005,7 +60104,7 @@ DeviceType.Humidifier, method(object({
59005
60104
  });
59006
60105
  object({
59007
60106
  /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
59008
- percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
60107
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
59009
60108
  precision: 0,
59010
60109
  label: "Humidity"
59011
60110
  })),
@@ -59448,7 +60547,7 @@ object({
59448
60547
  batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59449
60548
  /** 0..100 mowing-completion percentage of the current task, or null when no
59450
60549
  * task is active / progress is unavailable. */
59451
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60550
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59452
60551
  /** Current device/event code (dynamic — mostly status, sometimes an error),
59453
60552
  * or null when unknown. */
59454
60553
  currentCode: number().nullable().meta(readingMeta("none")),
@@ -59920,9 +61019,35 @@ var MediaInfoSchema = object({
59920
61019
  /** Optional cover-art / thumbnail URL. */
59921
61020
  imageUrl: string().optional()
59922
61021
  });
61022
+ /**
61023
+ * Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
61024
+ * the device said so. `assumed`: the driver has no feedback (infrared) and
61025
+ * tracks what it last SENT; it moves only after a send was accepted, and a
61026
+ * human at the real remote makes it wrong. A UI shows an assumed value as a
61027
+ * guess, never as a reading.
61028
+ */
61029
+ var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
61030
+ /** The fields of a media-player status a driver can only guess. */
61031
+ var MediaPlayerAssumedFieldSchema = _enum([
61032
+ "state",
61033
+ "volumeLevel",
61034
+ "isMuted",
61035
+ "source"
61036
+ ]);
59923
61037
  object({
59924
61038
  /** Playback lifecycle state. */
59925
61039
  state: MediaPlayerStateSchema,
61040
+ /**
61041
+ * Whether the state fields are readings or the driver's own bookkeeping.
61042
+ * A slice written before this field existed parses as `reported`.
61043
+ */
61044
+ stateSource: MediaPlayerStateSourceSchema.default("reported"),
61045
+ /**
61046
+ * Which fields are the driver's own guess. `stateSource` is `assumed` when
61047
+ * this is not empty; a field not listed here is a reading even then (power
61048
+ * from a running-state source, mute guessed from the last send).
61049
+ */
61050
+ assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
59926
61051
  /** Volume as 0..100 inclusive. Null when the device has no volume
59927
61052
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
59928
61053
  volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
@@ -59938,10 +61063,10 @@ object({
59938
61063
  currentMedia: MediaInfoSchema.nullable(),
59939
61064
  /** Current playback position in ms. Null when not seekable or
59940
61065
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
59941
- positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
61066
+ position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59942
61067
  /** Total duration of the current media in ms. Null when unknown
59943
61068
  * (live stream) or nothing is playing. */
59944
- durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
61069
+ duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59945
61070
  /** Shuffle toggle. Null when no shuffle surface.
59946
61071
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
59947
61072
  shuffle: boolean().nullable(),
@@ -59968,7 +61093,7 @@ DeviceType.MediaPlayer, method(object({ deviceId: number().int().nonnegative() }
59968
61093
  auth: "protected"
59969
61094
  }), method(object({
59970
61095
  deviceId: number().int().nonnegative(),
59971
- positionMs: number().int().nonnegative()
61096
+ position: number().int().nonnegative()
59972
61097
  }), _void(), {
59973
61098
  kind: "mutation",
59974
61099
  auth: "protected"
@@ -60641,9 +61766,9 @@ var NetworkLinkStatusSchema = object({
60641
61766
  * one whose reading has not landed must not be drawn at 0 %. Consumers
60642
61767
  * SKIP a null rather than coerce it.
60643
61768
  */
60644
- signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61769
+ signal: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60645
61770
  /** Raw received signal strength in dBm, when the firmware reports one. */
60646
- rssiDbm: number().optional().meta(readingMeta("signal-level")),
61771
+ rssi: number().optional().meta(readingMeta("signal-level")),
60647
61772
  /** Network name of a wireless link, when the firmware reports it. */
60648
61773
  ssid: string().optional(),
60649
61774
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
@@ -60775,7 +61900,8 @@ var TopologyNodeSchema = object({
60775
61900
  isHub: boolean(),
60776
61901
  isOnline: boolean(),
60777
61902
  cpuPercent: number(),
60778
- memoryPercent: number(),
61903
+ /** `null` when the node's memory read failed or it has not reported one (D393). */
61904
+ memoryPercent: number().nullable(),
60779
61905
  uptime: number(),
60780
61906
  lastSeen: string(),
60781
61907
  localIps: array(string()).readonly(),
@@ -61304,10 +62430,21 @@ var PetFeederDeviceStatusSchema = _enum([
61304
62430
  ]);
61305
62431
  var gramsPortion = number().int().min(4).max(200);
61306
62432
  object({
61307
- /** Food currently in the bowl (grams). Null when the device has not
61308
- * reported a reading yet. On dual-hopper models this is the combined
61309
- * bowl reading; per-hopper levels live in `food1`/`food2`. */
62433
+ /** Food in the TANK (hopper), as the feeder's own sensor reports it
62434
+ * (`state.food`). A coarse level code, not grams — `0` is empty. The
62435
+ * amount in the bowl is `bowlFood`. On dual-hopper models this is the
62436
+ * combined reading; per-hopper levels live in `food1`/`food2`. Null when
62437
+ * the device has not reported yet. */
61310
62438
  foodLevel: number().nullable().meta(readingMeta("none")),
62439
+ /** Food in the BOWL, a mass (stored in kilograms, D944). Reported only by a
62440
+ * feeder with a bowl scale; null when the model has none or its reading is
62441
+ * of an unproven unit. */
62442
+ bowlFood: number().nullable().meta(readingMeta("mass")),
62443
+ /** True while the pet is eating (`state.eating`). */
62444
+ eating: boolean(),
62445
+ /** Whether `markFoodReplenished` does anything on this model. When false
62446
+ * the client must not offer it. */
62447
+ supportsMarkRefilled: boolean(),
61311
62448
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
61312
62449
  * single-hopper models. */
61313
62450
  food1: number().nullable().meta(readingMeta("none")),
@@ -61315,12 +62452,17 @@ object({
61315
62452
  * single-hopper models. */
61316
62453
  food2: number().nullable().meta(readingMeta("none")),
61317
62454
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
61318
- * (`device_class: problem`, on = low). True when the bowl is empty /
61319
- * below the feeder's low threshold. */
62455
+ * (`device_class: problem`, on = low). True when the tank reports empty,
62456
+ * or, on a dual-hopper model, when EITHER hopper is empty. */
61320
62457
  lowFood: boolean(),
61321
- /** Battery charge 0..100 (%). Null on mains-powered models or when the
61322
- * device has no battery reading. */
62458
+ /** Battery charge 0..100 (%). On a feeder running on `mains` this is the
62459
+ * BACKUP battery's charge, not the power source. Null when the device has
62460
+ * no battery reading. */
61323
62461
  batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62462
+ /** What the feeder is powered from right now: `mains` (plugged in, the
62463
+ * battery is only a backup) or `battery`. Null when the device does not
62464
+ * report it. */
62465
+ powerSource: _enum(["mains", "battery"]).nullable(),
61324
62466
  /**
61325
62467
  * Desiccant life remaining, stored in seconds (duration base, D944).
61326
62468
  * Providers convert from the days the feeder reports. Null when the
@@ -61348,6 +62490,14 @@ object({
61348
62490
  feedSound: boolean(),
61349
62491
  /** Speaker / prompt volume level (device-scaled integer). */
61350
62492
  volume: number().meta(readingMeta("none")),
62493
+ /** Portion (grams) of the last manual feed sent from a single-hopper
62494
+ * stepper — the default the next manual feed starts from. Persisted per
62495
+ * device by the provider; null until a manual feed has been sent. */
62496
+ lastFeedGrams: gramsPortion.nullable(),
62497
+ /** Hopper-1 portion of the last manual feed (dual-hopper); null until sent. */
62498
+ lastFeedHopper1: gramsPortion.nullable(),
62499
+ /** Hopper-2 portion of the last manual feed (dual-hopper); null until sent. */
62500
+ lastFeedHopper2: gramsPortion.nullable(),
61351
62501
  /** Ms epoch when the slice was last refreshed from the cloud. */
61352
62502
  lastFetchedAt: number()
61353
62503
  });
@@ -61402,6 +62552,64 @@ DeviceType.PetFeeder, method(object({
61402
62552
  kind: "mutation",
61403
62553
  auth: "protected"
61404
62554
  });
62555
+ object({
62556
+ /** Water in the tank, 0..100 (%). Null while the source has not reported. */
62557
+ waterLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62558
+ /** Water in the tank, in litres. Null while the source has not reported. */
62559
+ waterVolume: number().min(0).nullable().meta(readingMeta("volume")),
62560
+ /** True when the tank is empty. Null while unknown. */
62561
+ empty: boolean().nullable(),
62562
+ /** True while the pump is running. Null while unknown. */
62563
+ pumpRunning: boolean().nullable(),
62564
+ /** True when the pump works on an on/off duty cycle rather than continuously. */
62565
+ pumpIntervalEnabled: boolean().nullable(),
62566
+ /** How long the pump runs per cycle, in seconds. */
62567
+ pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
62568
+ /** How long the pump rests per cycle, in seconds. */
62569
+ pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
62570
+ /** Water drunk in the last drink, in litres. */
62571
+ lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
62572
+ /** Water drunk in the current hour, in litres. */
62573
+ currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
62574
+ /** Water drunk over the last 24 hours (rolling), in litres. */
62575
+ last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
62576
+ /** Water drunk since local midnight, in litres. */
62577
+ todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
62578
+ /** Decoded fault message. Null means healthy; a non-null string is an active fault. */
62579
+ error: string().nullable(),
62580
+ /** Ms epoch when the slice was last refreshed. */
62581
+ lastFetchedAt: number()
62582
+ });
62583
+ var DeviceIdSchema = number().int().nonnegative();
62584
+ var MinutesSchema = number().int().min(0);
62585
+ DeviceType.PetFountain, method(object({
62586
+ deviceId: DeviceIdSchema,
62587
+ on: boolean()
62588
+ }), _void(), {
62589
+ kind: "mutation",
62590
+ auth: "protected"
62591
+ }), method(object({
62592
+ deviceId: DeviceIdSchema,
62593
+ on: boolean()
62594
+ }), _void(), {
62595
+ kind: "mutation",
62596
+ auth: "protected"
62597
+ }), method(object({
62598
+ deviceId: DeviceIdSchema,
62599
+ minutes: MinutesSchema
62600
+ }), _void(), {
62601
+ kind: "mutation",
62602
+ auth: "protected"
62603
+ }), method(object({
62604
+ deviceId: DeviceIdSchema,
62605
+ minutes: MinutesSchema
62606
+ }), _void(), {
62607
+ kind: "mutation",
62608
+ auth: "protected"
62609
+ }), method(object({ deviceId: DeviceIdSchema }), _void(), {
62610
+ kind: "mutation",
62611
+ auth: "protected"
62612
+ });
61405
62613
  var VehicleSchema = object({
61406
62614
  id: string(),
61407
62615
  name: string(),
@@ -61783,16 +62991,16 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
61783
62991
  auth: "admin"
61784
62992
  });
61785
62993
  object({
61786
- /** Instantaneous power draw in watts. */
61787
- watts: number().optional().meta(readingMeta("power", { label: "Power" })),
62994
+ /** Instantaneous power draw (watts). */
62995
+ power: number().optional().meta(readingMeta("power", { label: "Power" })),
61788
62996
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
61789
- kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
61790
- /** Voltage in volts. */
61791
- volts: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
62997
+ energy: number().optional().meta(readingMeta("energy", { label: "Energy" })),
62998
+ /** Voltage (volts). */
62999
+ voltage: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
61792
63000
  /** Current in amperes. */
61793
- amps: number().optional().meta(readingMeta("current", { label: "Current" })),
63001
+ current: number().optional().meta(readingMeta("current", { label: "Current" })),
61794
63002
  /** Apparent power in volt-amperes. */
61795
- va: number().optional().meta(readingMeta("apparent-power")),
63003
+ apparentPower: number().optional().meta(readingMeta("apparent-power")),
61796
63004
  /** Power factor (0..1) if reported. */
61797
63005
  powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
61798
63006
  /** Ms epoch when the slice was last updated. */
@@ -61847,16 +63055,13 @@ object({
61847
63055
  location: string().nullable(),
61848
63056
  /** GPS coordinates when available. Null otherwise. */
61849
63057
  gps: GpsLocationSchema.nullable(),
61850
- /** Optional battery level of the tracking device (0..100). Null
61851
- * when not reported. */
61852
- batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61853
63058
  /** Ms epoch when the slice was last updated. */
61854
63059
  lastChangedAt: number()
61855
63060
  });
61856
63061
  DeviceType.Presence;
61857
63062
  object({
61858
63063
  /** Current pressure in hPa. `null` when the sample was refused (D393). */
61859
- hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
63064
+ pressure: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
61860
63065
  /** Ms epoch when the slice was last updated. */
61861
63066
  lastFetchedAt: number()
61862
63067
  });
@@ -64859,7 +66064,7 @@ object({
64859
66064
  DeviceType.Sensor;
64860
66065
  object({
64861
66066
  /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64862
- celsius: number().nullable().meta(readingMeta("temperature", {
66067
+ temperature: number().nullable().meta(readingMeta("temperature", {
64863
66068
  precision: 1,
64864
66069
  label: "Temperature"
64865
66070
  })),
@@ -65123,7 +66328,7 @@ object({
65123
66328
  /** Speed tokens the hardware accepts — drives the UI selector. */
65124
66329
  availableFanSpeeds: array(string()),
65125
66330
  /** 0..100 cleaning-completion percentage of the current task, or null. */
65126
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
66331
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65127
66332
  /** Current error code (0 / null = no error). */
65128
66333
  errorCode: number().nullable().meta(readingMeta("none")),
65129
66334
  /** Human label for {@link errorCode}, or null when none / undecodable. */
@@ -65992,9 +67197,17 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
65992
67197
  kind: "mutation",
65993
67198
  auth: "admin"
65994
67199
  });
67200
+ discriminatedUnion("kind", [object({
67201
+ kind: literal("raw"),
67202
+ timings: array(number().int()),
67203
+ settings: TransmitSettingsSchema.optional()
67204
+ }), object({
67205
+ kind: literal("pronto"),
67206
+ code: string()
67207
+ })]);
65995
67208
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
65996
67209
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
65997
- 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;
67210
+ 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;
65998
67211
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
65999
67212
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
66000
67213
  new Set(CAP_NAMES);
@@ -66047,7 +67260,7 @@ var AccessoryKind = {
66047
67260
  PrivacyMask: DeviceRole.PrivacyMask
66048
67261
  };
66049
67262
  AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
66050
- 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;
67263
+ 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;
66051
67264
  new Set(Object.values(DeviceType));
66052
67265
  Object.freeze({
66053
67266
  "accessories.getStatus": {
@@ -66064,6 +67277,104 @@ Object.freeze({
66064
67277
  access: "create",
66065
67278
  role: "admin"
66066
67279
  },
67280
+ "actions.create": {
67281
+ capName: "actions",
67282
+ capScope: "system",
67283
+ addonId: null,
67284
+ access: "create",
67285
+ role: "admin"
67286
+ },
67287
+ "actions.delete": {
67288
+ capName: "actions",
67289
+ capScope: "system",
67290
+ addonId: null,
67291
+ access: "delete",
67292
+ role: "admin"
67293
+ },
67294
+ "actions.get": {
67295
+ capName: "actions",
67296
+ capScope: "system",
67297
+ addonId: null,
67298
+ access: "view",
67299
+ role: "viewer"
67300
+ },
67301
+ "actions.getLinks": {
67302
+ capName: "actions",
67303
+ capScope: "system",
67304
+ addonId: null,
67305
+ access: "view",
67306
+ role: "admin"
67307
+ },
67308
+ "actions.getTraces": {
67309
+ capName: "actions",
67310
+ capScope: "system",
67311
+ addonId: null,
67312
+ access: "view",
67313
+ role: "admin"
67314
+ },
67315
+ "actions.list": {
67316
+ capName: "actions",
67317
+ capScope: "system",
67318
+ addonId: null,
67319
+ access: "view",
67320
+ role: "admin"
67321
+ },
67322
+ "actions.listForCamera": {
67323
+ capName: "actions",
67324
+ capScope: "system",
67325
+ addonId: null,
67326
+ access: "view",
67327
+ role: "viewer"
67328
+ },
67329
+ "actions.run": {
67330
+ capName: "actions",
67331
+ capScope: "system",
67332
+ addonId: null,
67333
+ access: "create",
67334
+ role: "viewer"
67335
+ },
67336
+ "actions.setLinks": {
67337
+ capName: "actions",
67338
+ capScope: "system",
67339
+ addonId: null,
67340
+ access: "create",
67341
+ role: "admin"
67342
+ },
67343
+ "actions.stop": {
67344
+ capName: "actions",
67345
+ capScope: "system",
67346
+ addonId: null,
67347
+ access: "create",
67348
+ role: "admin"
67349
+ },
67350
+ "actions.update": {
67351
+ capName: "actions",
67352
+ capScope: "system",
67353
+ addonId: null,
67354
+ access: "create",
67355
+ role: "admin"
67356
+ },
67357
+ "actionSource.listActions": {
67358
+ capName: "action-source",
67359
+ capScope: "system",
67360
+ addonId: null,
67361
+ access: "view",
67362
+ role: "admin"
67363
+ },
67364
+ "actionSource.runAction": {
67365
+ capName: "action-source",
67366
+ capScope: "system",
67367
+ addonId: null,
67368
+ access: "create",
67369
+ role: "admin"
67370
+ },
67371
+ "actionSource.stopAction": {
67372
+ capName: "action-source",
67373
+ capScope: "system",
67374
+ addonId: null,
67375
+ access: "create",
67376
+ role: "admin"
67377
+ },
66067
67378
  "addonPages.listPages": {
66068
67379
  capName: "addon-pages",
66069
67380
  capScope: "system",
@@ -67345,6 +68656,13 @@ Object.freeze({
67345
68656
  access: "view",
67346
68657
  role: "admin"
67347
68658
  },
68659
+ "coreBlocks.getAutomationRunState": {
68660
+ capName: "core-blocks",
68661
+ capScope: "system",
68662
+ addonId: null,
68663
+ access: "view",
68664
+ role: "admin"
68665
+ },
67348
68666
  "coreBlocks.getBlockSettings": {
67349
68667
  capName: "core-blocks",
67350
68668
  capScope: "system",
@@ -67366,6 +68684,13 @@ Object.freeze({
67366
68684
  access: "view",
67367
68685
  role: "admin"
67368
68686
  },
68687
+ "coreBlocks.getTraces": {
68688
+ capName: "core-blocks",
68689
+ capScope: "system",
68690
+ addonId: null,
68691
+ access: "view",
68692
+ role: "admin"
68693
+ },
67369
68694
  "coreBlocks.getTypeDefs": {
67370
68695
  capName: "core-blocks",
67371
68696
  capScope: "system",
@@ -67401,6 +68726,13 @@ Object.freeze({
67401
68726
  access: "create",
67402
68727
  role: "admin"
67403
68728
  },
68729
+ "coreBlocks.reportAutomationRun": {
68730
+ capName: "core-blocks",
68731
+ capScope: "system",
68732
+ addonId: null,
68733
+ access: "create",
68734
+ role: "admin"
68735
+ },
67404
68736
  "coreBlocks.restart": {
67405
68737
  capName: "core-blocks",
67406
68738
  capScope: "system",
@@ -67863,6 +69195,13 @@ Object.freeze({
67863
69195
  access: "create",
67864
69196
  role: "admin"
67865
69197
  },
69198
+ "deviceExport.exposeEntity": {
69199
+ capName: "device-export",
69200
+ capScope: "system",
69201
+ addonId: null,
69202
+ access: "create",
69203
+ role: "admin"
69204
+ },
67866
69205
  "deviceExport.getDeviceLiveContribution": {
67867
69206
  capName: "device-export",
67868
69207
  capScope: "system",
@@ -67891,6 +69230,13 @@ Object.freeze({
67891
69230
  access: "view",
67892
69231
  role: "admin"
67893
69232
  },
69233
+ "deviceExport.listExposedEntities": {
69234
+ capName: "device-export",
69235
+ capScope: "system",
69236
+ addonId: null,
69237
+ access: "view",
69238
+ role: "admin"
69239
+ },
67894
69240
  "deviceExport.listSupportedDeviceKinds": {
67895
69241
  capName: "device-export",
67896
69242
  capScope: "system",
@@ -67898,6 +69244,13 @@ Object.freeze({
67898
69244
  access: "view",
67899
69245
  role: "admin"
67900
69246
  },
69247
+ "deviceExport.listSupportedEntityKinds": {
69248
+ capName: "device-export",
69249
+ capScope: "system",
69250
+ addonId: null,
69251
+ access: "view",
69252
+ role: "admin"
69253
+ },
67901
69254
  "deviceExport.unexposeDevice": {
67902
69255
  capName: "device-export",
67903
69256
  capScope: "system",
@@ -67905,6 +69258,13 @@ Object.freeze({
67905
69258
  access: "create",
67906
69259
  role: "admin"
67907
69260
  },
69261
+ "deviceExport.unexposeEntity": {
69262
+ capName: "device-export",
69263
+ capScope: "system",
69264
+ addonId: null,
69265
+ access: "create",
69266
+ role: "admin"
69267
+ },
67908
69268
  "deviceExtension.listCandidates": {
67909
69269
  capName: "device-extension",
67910
69270
  capScope: "device",
@@ -69480,6 +70840,20 @@ Object.freeze({
69480
70840
  access: "create",
69481
70841
  role: "viewer"
69482
70842
  },
70843
+ "irTransmitter.getInfo": {
70844
+ capName: "ir-transmitter",
70845
+ capScope: "device",
70846
+ addonId: null,
70847
+ access: "view",
70848
+ role: "viewer"
70849
+ },
70850
+ "irTransmitter.send": {
70851
+ capName: "ir-transmitter",
70852
+ capScope: "device",
70853
+ addonId: null,
70854
+ access: "create",
70855
+ role: "viewer"
70856
+ },
69483
70857
  "keychain.delete": {
69484
70858
  capName: "keychain",
69485
70859
  capScope: "system",
@@ -70712,6 +72086,13 @@ Object.freeze({
70712
72086
  access: "create",
70713
72087
  role: "admin"
70714
72088
  },
72089
+ "nodeRemovalHook.onNodeRemoved": {
72090
+ capName: "node-removal-hook",
72091
+ capScope: "system",
72092
+ addonId: null,
72093
+ access: "create",
72094
+ role: "admin"
72095
+ },
70715
72096
  "nodes.clusterAddonStatus": {
70716
72097
  capName: "nodes",
70717
72098
  capScope: "system",
@@ -71349,6 +72730,48 @@ Object.freeze({
71349
72730
  access: "create",
71350
72731
  role: "viewer"
71351
72732
  },
72733
+ "petFountain.getStatus": {
72734
+ capName: "pet-fountain",
72735
+ capScope: "device",
72736
+ addonId: null,
72737
+ access: "view",
72738
+ role: "viewer"
72739
+ },
72740
+ "petFountain.resetDailyConsumption": {
72741
+ capName: "pet-fountain",
72742
+ capScope: "device",
72743
+ addonId: null,
72744
+ access: "delete",
72745
+ role: "viewer"
72746
+ },
72747
+ "petFountain.setPump": {
72748
+ capName: "pet-fountain",
72749
+ capScope: "device",
72750
+ addonId: null,
72751
+ access: "create",
72752
+ role: "viewer"
72753
+ },
72754
+ "petFountain.setPumpInterval": {
72755
+ capName: "pet-fountain",
72756
+ capScope: "device",
72757
+ addonId: null,
72758
+ access: "create",
72759
+ role: "viewer"
72760
+ },
72761
+ "petFountain.setPumpOffMinutes": {
72762
+ capName: "pet-fountain",
72763
+ capScope: "device",
72764
+ addonId: null,
72765
+ access: "create",
72766
+ role: "viewer"
72767
+ },
72768
+ "petFountain.setPumpOnMinutes": {
72769
+ capName: "pet-fountain",
72770
+ capScope: "device",
72771
+ addonId: null,
72772
+ access: "create",
72773
+ role: "viewer"
72774
+ },
71352
72775
  "pipelineAnalytics.addRetrainFrameFromRecording": {
71353
72776
  capName: "pipeline-analytics",
71354
72777
  capScope: "device",
@@ -73603,6 +75026,41 @@ Object.freeze({
73603
75026
  access: "create",
73604
75027
  role: "admin"
73605
75028
  },
75029
+ "remote.getConfig": {
75030
+ capName: "remote",
75031
+ capScope: "system",
75032
+ addonId: null,
75033
+ access: "view",
75034
+ role: "admin"
75035
+ },
75036
+ "remote.listCommands": {
75037
+ capName: "remote",
75038
+ capScope: "system",
75039
+ addonId: null,
75040
+ access: "view",
75041
+ role: "admin"
75042
+ },
75043
+ "remote.sendCommand": {
75044
+ capName: "remote",
75045
+ capScope: "system",
75046
+ addonId: null,
75047
+ access: "create",
75048
+ role: "admin"
75049
+ },
75050
+ "remote.sendMediaCommand": {
75051
+ capName: "remote",
75052
+ capScope: "system",
75053
+ addonId: null,
75054
+ access: "create",
75055
+ role: "admin"
75056
+ },
75057
+ "remote.setConfig": {
75058
+ capName: "remote",
75059
+ capScope: "system",
75060
+ addonId: null,
75061
+ access: "create",
75062
+ role: "admin"
75063
+ },
73606
75064
  "runningState.getStatus": {
73607
75065
  capName: "running-state",
73608
75066
  capScope: "device",
@@ -73715,6 +75173,13 @@ Object.freeze({
73715
75173
  access: "create",
73716
75174
  role: "admin"
73717
75175
  },
75176
+ "serverManagement.getLaunchOverrides": {
75177
+ capName: "server-management",
75178
+ capScope: "system",
75179
+ addonId: null,
75180
+ access: "view",
75181
+ role: "admin"
75182
+ },
73718
75183
  "serverManagement.getServerPackageStatus": {
73719
75184
  capName: "server-management",
73720
75185
  capScope: "system",
@@ -73736,6 +75201,13 @@ Object.freeze({
73736
75201
  access: "create",
73737
75202
  role: "admin"
73738
75203
  },
75204
+ "serverManagement.setLaunchOverrides": {
75205
+ capName: "server-management",
75206
+ capScope: "system",
75207
+ addonId: null,
75208
+ access: "create",
75209
+ role: "admin"
75210
+ },
73739
75211
  "settingsStore.aggregate": {
73740
75212
  capName: "settings-store",
73741
75213
  capScope: "system",
@@ -74793,6 +76265,41 @@ Object.freeze({
74793
76265
  access: "view",
74794
76266
  role: "viewer"
74795
76267
  },
76268
+ "template.create": {
76269
+ capName: "template",
76270
+ capScope: "system",
76271
+ addonId: null,
76272
+ access: "create",
76273
+ role: "admin"
76274
+ },
76275
+ "template.delete": {
76276
+ capName: "template",
76277
+ capScope: "system",
76278
+ addonId: null,
76279
+ access: "delete",
76280
+ role: "admin"
76281
+ },
76282
+ "template.get": {
76283
+ capName: "template",
76284
+ capScope: "system",
76285
+ addonId: null,
76286
+ access: "view",
76287
+ role: "admin"
76288
+ },
76289
+ "template.list": {
76290
+ capName: "template",
76291
+ capScope: "system",
76292
+ addonId: null,
76293
+ access: "view",
76294
+ role: "admin"
76295
+ },
76296
+ "template.update": {
76297
+ capName: "template",
76298
+ capScope: "system",
76299
+ addonId: null,
76300
+ access: "create",
76301
+ role: "admin"
76302
+ },
74796
76303
  "terminalSession.adoptLegacyMonitor": {
74797
76304
  capName: "terminal-session",
74798
76305
  capScope: "system",
@@ -75974,7 +77481,7 @@ var ACTUATOR_CAP_VOCABULARY = {
75974
77481
  label: t("Full brightness", "Luminosità massima"),
75975
77482
  phrase: t("Set {device} to full brightness", "Porta {device} alla massima luminosità"),
75976
77483
  icon: "sun",
75977
- args: { percentage: 100 }
77484
+ args: { level: 100 }
75978
77485
  },
75979
77486
  {
75980
77487
  id: "brightness.half",
@@ -75982,7 +77489,7 @@ var ACTUATOR_CAP_VOCABULARY = {
75982
77489
  label: t("Half brightness", "Luminosità a metà"),
75983
77490
  phrase: t("Dim {device} to half", "Abbassa {device} a metà luminosità"),
75984
77491
  icon: "sun-dim",
75985
- args: { percentage: 50 }
77492
+ args: { level: 50 }
75986
77493
  },
75987
77494
  {
75988
77495
  id: "brightness.off",
@@ -75990,16 +77497,16 @@ var ACTUATOR_CAP_VOCABULARY = {
75990
77497
  label: t("Brightness off", "Luminosità a zero"),
75991
77498
  phrase: t("Dim {device} to zero", "Abbassa {device} a zero"),
75992
77499
  icon: "lightbulb-off",
75993
- args: { percentage: 0 }
77500
+ args: { level: 0 }
75994
77501
  },
75995
77502
  {
75996
77503
  id: "brightness.set",
75997
77504
  method: "setBrightness",
75998
77505
  label: t("Set brightness", "Imposta la luminosità"),
75999
- phrase: t("Set the brightness of {device} to {percentage}", "Imposta la luminosità di {device} a {percentage}"),
77506
+ phrase: t("Set the brightness of {device} to {level}", "Imposta la luminosità di {device} a {level}"),
76000
77507
  icon: "sun-medium",
76001
77508
  openArgs: [{
76002
- key: "percentage",
77509
+ key: "level",
76003
77510
  label: PERCENT,
76004
77511
  offer: {
76005
77512
  kind: "number",
@@ -76013,13 +77520,13 @@ var ACTUATOR_CAP_VOCABULARY = {
76013
77520
  ],
76014
77521
  state: [{
76015
77522
  kind: "numeric",
76016
- field: "percentage",
77523
+ field: "level",
76017
77524
  quantity: t("the brightness", "la luminosità"),
76018
77525
  label: t("Brightness", "Luminosità"),
76019
77526
  unit: "%",
76020
77527
  icon: "sun"
76021
77528
  }],
76022
- events: { onBrightnessChanged: { notOperatorFacing: "the `percentage` state trigger reads the same change" } }
77529
+ events: { onBrightnessChanged: { notOperatorFacing: "the `level` state trigger reads the same change" } }
76023
77530
  },
76024
77531
  color: {
76025
77532
  scope: "device",
@@ -76033,7 +77540,7 @@ var ACTUATOR_CAP_VOCABULARY = {
76033
77540
  icon: "sun",
76034
77541
  args: { color: {
76035
77542
  mode: "mired",
76036
- mireds: 370
77543
+ colorTemperature: 370
76037
77544
  } }
76038
77545
  },
76039
77546
  {
@@ -76044,7 +77551,7 @@ var ACTUATOR_CAP_VOCABULARY = {
76044
77551
  icon: "snowflake",
76045
77552
  args: { color: {
76046
77553
  mode: "mired",
76047
- mireds: 200
77554
+ colorTemperature: 200
76048
77555
  } }
76049
77556
  },
76050
77557
  {
@@ -76658,7 +78165,7 @@ var ACTUATOR_CAP_VOCABULARY = {
76658
78165
  label: t("Fan off", "Ventilatore spento"),
76659
78166
  phrase: t("Stop {device}", "Ferma {device}"),
76660
78167
  icon: "power-off",
76661
- args: { percentage: 0 }
78168
+ args: { speed: 0 }
76662
78169
  },
76663
78170
  {
76664
78171
  id: "fan.full",
@@ -76666,16 +78173,16 @@ var ACTUATOR_CAP_VOCABULARY = {
76666
78173
  label: t("Fan full", "Ventilatore al massimo"),
76667
78174
  phrase: t("Run {device} at full speed", "Porta {device} alla massima velocità"),
76668
78175
  icon: "fan",
76669
- args: { percentage: 100 }
78176
+ args: { speed: 100 }
76670
78177
  },
76671
78178
  {
76672
78179
  id: "fan.speed",
76673
78180
  method: "setPercentage",
76674
78181
  label: t("Set speed", "Imposta la velocità"),
76675
- phrase: t("Set the speed of {device} to {percentage}", "Imposta la velocità di {device} al {percentage}"),
78182
+ phrase: t("Set the speed of {device} to {speed}", "Imposta la velocità di {device} al {speed}"),
76676
78183
  icon: "fan",
76677
78184
  openArgs: [{
76678
- key: "percentage",
78185
+ key: "speed",
76679
78186
  label: t("Speed", "Velocità"),
76680
78187
  offer: {
76681
78188
  kind: "number",
@@ -76737,7 +78244,7 @@ var ACTUATOR_CAP_VOCABULARY = {
76737
78244
  ],
76738
78245
  state: [{
76739
78246
  kind: "numeric",
76740
- field: "percentage",
78247
+ field: "speed",
76741
78248
  quantity: t("the speed", "la velocità"),
76742
78249
  label: t("Speed", "Velocità"),
76743
78250
  unit: "%",
@@ -77103,6 +78610,11 @@ var ACTUATOR_CAP_VOCABULARY = {
77103
78610
  icon: "circle-dot"
77104
78611
  }]
77105
78612
  },
78613
+ "ir-transmitter": {
78614
+ scope: "device",
78615
+ noun: t("IR / RF transmitter", "Trasmettitore IR / RF"),
78616
+ notOperatorFacing: { send: "a raw pulse train is not something an operator composes in an automation; the remote that owns the command table sends it" }
78617
+ },
77106
78618
  control: {
77107
78619
  scope: "device",
77108
78620
  noun: t("Control", "Controllo"),
@@ -77535,7 +79047,7 @@ var APPLIANCE_CAP_VOCABULARY = {
77535
79047
  },
77536
79048
  {
77537
79049
  kind: "numeric",
77538
- field: "progressPercent",
79050
+ field: "progress",
77539
79051
  quantity: t("the cycle progress", "l’avanzamento del lavaggio"),
77540
79052
  label: t("Progress", "Avanzamento"),
77541
79053
  unit: "%",
@@ -77756,6 +79268,100 @@ var APPLIANCE_CAP_VOCABULARY = {
77756
79268
  }
77757
79269
  ]
77758
79270
  },
79271
+ "pet-fountain": {
79272
+ scope: "device",
79273
+ noun: t("Pet fountain", "Fontana per animali"),
79274
+ actions: [
79275
+ {
79276
+ id: "petFountain.pumpOn",
79277
+ method: "setPump",
79278
+ label: t("Pump on", "Pompa accesa"),
79279
+ phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
79280
+ icon: "play",
79281
+ args: { on: true }
79282
+ },
79283
+ {
79284
+ id: "petFountain.pumpOff",
79285
+ method: "setPump",
79286
+ label: t("Pump off", "Pompa spenta"),
79287
+ phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
79288
+ icon: "square",
79289
+ args: { on: false }
79290
+ },
79291
+ {
79292
+ id: "petFountain.intervalOn",
79293
+ method: "setPumpInterval",
79294
+ label: t("Pump cycle on", "Ciclo pompa attivo"),
79295
+ phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
79296
+ icon: "timer",
79297
+ args: { on: true }
79298
+ },
79299
+ {
79300
+ id: "petFountain.intervalOff",
79301
+ method: "setPumpInterval",
79302
+ label: t("Pump cycle off", "Ciclo pompa spento"),
79303
+ phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
79304
+ icon: "timer-off",
79305
+ args: { on: false }
79306
+ },
79307
+ {
79308
+ id: "petFountain.pumpOnMinutes",
79309
+ method: "setPumpOnMinutes",
79310
+ label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
79311
+ phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
79312
+ icon: "timer",
79313
+ openArgs: [{
79314
+ key: "minutes",
79315
+ label: t("Minutes", "Minuti"),
79316
+ offer: {
79317
+ kind: "number",
79318
+ min: 0,
79319
+ step: 1,
79320
+ unit: "min"
79321
+ }
79322
+ }]
79323
+ },
79324
+ {
79325
+ id: "petFountain.pumpOffMinutes",
79326
+ method: "setPumpOffMinutes",
79327
+ label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
79328
+ phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
79329
+ icon: "timer-off",
79330
+ openArgs: [{
79331
+ key: "minutes",
79332
+ label: t("Minutes", "Minuti"),
79333
+ offer: {
79334
+ kind: "number",
79335
+ min: 0,
79336
+ step: 1,
79337
+ unit: "min"
79338
+ }
79339
+ }]
79340
+ },
79341
+ {
79342
+ id: "petFountain.resetDailyConsumption",
79343
+ method: "resetDailyConsumption",
79344
+ label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
79345
+ phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
79346
+ icon: "rotate-ccw"
79347
+ }
79348
+ ],
79349
+ state: [{
79350
+ kind: "boolean",
79351
+ field: "empty",
79352
+ label: t("Tank empty", "Serbatoio vuoto"),
79353
+ whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
79354
+ whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
79355
+ icon: "droplet"
79356
+ }, {
79357
+ kind: "numeric",
79358
+ field: "waterLevel",
79359
+ quantity: t("the water level", "il livello dell’acqua"),
79360
+ label: t("Water level", "Livello dell’acqua"),
79361
+ unit: "%",
79362
+ icon: "droplets"
79363
+ }]
79364
+ },
77759
79365
  "media-player": {
77760
79366
  scope: "device",
77761
79367
  noun: t("Media player", "Lettore multimediale"),
@@ -77893,10 +79499,10 @@ var APPLIANCE_CAP_VOCABULARY = {
77893
79499
  id: "media.seek",
77894
79500
  method: "seek",
77895
79501
  label: t("Seek", "Vai al punto"),
77896
- phrase: t("Move {device} to {positionMs}", "Porta {device} al punto {positionMs}"),
79502
+ phrase: t("Move {device} to {position}", "Porta {device} al punto {position}"),
77897
79503
  icon: "fast-forward",
77898
79504
  openArgs: [{
77899
- key: "positionMs",
79505
+ key: "position",
77900
79506
  label: t("Position", "Posizione"),
77901
79507
  offer: {
77902
79508
  kind: "number",
@@ -78945,7 +80551,7 @@ var SENSOR_CAP_VOCABULARY = {
78945
80551
  state: [
78946
80552
  {
78947
80553
  kind: "numeric",
78948
- field: "co2Ppm",
80554
+ field: "co2",
78949
80555
  quantity: t("the CO₂", "la CO₂"),
78950
80556
  label: t("CO₂", "CO₂"),
78951
80557
  unit: "ppm",
@@ -78953,7 +80559,7 @@ var SENSOR_CAP_VOCABULARY = {
78953
80559
  },
78954
80560
  {
78955
80561
  kind: "numeric",
78956
- field: "vocPpb",
80562
+ field: "voc",
78957
80563
  quantity: t("the VOC level", "il livello di COV"),
78958
80564
  label: t("VOC", "COV"),
78959
80565
  unit: "ppb",
@@ -78989,7 +80595,7 @@ var SENSOR_CAP_VOCABULARY = {
78989
80595
  noun: t("Light level", "Luminosità ambiente"),
78990
80596
  state: [{
78991
80597
  kind: "numeric",
78992
- field: "lux",
80598
+ field: "illuminance",
78993
80599
  quantity: t("the light level", "la luminosità"),
78994
80600
  label: t("Illuminance", "Illuminamento"),
78995
80601
  unit: "lx",
@@ -79192,7 +80798,7 @@ var SENSOR_CAP_VOCABULARY = {
79192
80798
  noun: t("Humidity", "Umidità"),
79193
80799
  state: [{
79194
80800
  kind: "numeric",
79195
- field: "percent",
80801
+ field: "humidity",
79196
80802
  quantity: t("the humidity", "l’umidità"),
79197
80803
  label: t("Humidity", "Umidità"),
79198
80804
  unit: "%",
@@ -79240,7 +80846,7 @@ var SENSOR_CAP_VOCABULARY = {
79240
80846
  state: [
79241
80847
  {
79242
80848
  kind: "numeric",
79243
- field: "watts",
80849
+ field: "power",
79244
80850
  quantity: t("the power draw", "il consumo"),
79245
80851
  label: t("Power", "Potenza"),
79246
80852
  unit: "W",
@@ -79248,7 +80854,7 @@ var SENSOR_CAP_VOCABULARY = {
79248
80854
  },
79249
80855
  {
79250
80856
  kind: "numeric",
79251
- field: "kwhTotal",
80857
+ field: "energy",
79252
80858
  quantity: t("the energy used", "l’energia consumata"),
79253
80859
  label: t("Energy", "Energia"),
79254
80860
  unit: "kWh",
@@ -79256,7 +80862,7 @@ var SENSOR_CAP_VOCABULARY = {
79256
80862
  },
79257
80863
  {
79258
80864
  kind: "numeric",
79259
- field: "volts",
80865
+ field: "voltage",
79260
80866
  quantity: t("the voltage", "la tensione"),
79261
80867
  label: t("Voltage", "Tensione"),
79262
80868
  unit: "V",
@@ -79264,7 +80870,7 @@ var SENSOR_CAP_VOCABULARY = {
79264
80870
  },
79265
80871
  {
79266
80872
  kind: "numeric",
79267
- field: "amps",
80873
+ field: "current",
79268
80874
  quantity: t("the current", "la corrente"),
79269
80875
  label: t("Current", "Corrente"),
79270
80876
  unit: "A",
@@ -79281,13 +80887,6 @@ var SENSOR_CAP_VOCABULARY = {
79281
80887
  label: t("Where", "Posizione"),
79282
80888
  phrase: t("When {device} is at {value}", "Quando {device} è in {value}"),
79283
80889
  icon: "map-pin"
79284
- }, {
79285
- kind: "numeric",
79286
- field: "batteryPercent",
79287
- quantity: t("the phone battery", "la batteria del telefono"),
79288
- label: t("Battery", "Batteria"),
79289
- unit: "%",
79290
- icon: "battery"
79291
80890
  }]
79292
80891
  },
79293
80892
  "pressure-sensor": {
@@ -79295,7 +80894,7 @@ var SENSOR_CAP_VOCABULARY = {
79295
80894
  noun: t("Pressure", "Pressione"),
79296
80895
  state: [{
79297
80896
  kind: "numeric",
79298
- field: "hpa",
80897
+ field: "pressure",
79299
80898
  quantity: t("the pressure", "la pressione"),
79300
80899
  label: t("Pressure", "Pressione"),
79301
80900
  unit: "hPa",
@@ -79331,7 +80930,7 @@ var SENSOR_CAP_VOCABULARY = {
79331
80930
  noun: t("Temperature", "Temperatura"),
79332
80931
  state: [{
79333
80932
  kind: "numeric",
79334
- field: "celsius",
80933
+ field: "temperature",
79335
80934
  quantity: t("the temperature", "la temperatura"),
79336
80935
  label: t("Temperature", "Temperatura"),
79337
80936
  unit: "°C",
@@ -79402,7 +81001,7 @@ var SENSOR_CAP_VOCABULARY = {
79402
81001
  state: [
79403
81002
  {
79404
81003
  kind: "numeric",
79405
- field: "percentage",
81004
+ field: "level",
79406
81005
  quantity: t("the battery level", "il livello della batteria"),
79407
81006
  label: t("Battery", "Batteria"),
79408
81007
  unit: "%",
@@ -79444,7 +81043,7 @@ var SENSOR_CAP_VOCABULARY = {
79444
81043
  icon: "battery-charging"
79445
81044
  }
79446
81045
  ],
79447
- events: { onStatusChanged: { notOperatorFacing: "the percentage / sleeping / charging state triggers each read one field of this change" } },
81046
+ events: { onStatusChanged: { notOperatorFacing: "the level / sleeping / charging state triggers each read one field of this change" } },
79448
81047
  extraEvents: [{
79449
81048
  category: EventCategory.DeviceAwake,
79450
81049
  label: t("Woke up", "Risvegliato"),
@@ -79462,7 +81061,7 @@ var SENSOR_CAP_VOCABULARY = {
79462
81061
  noun: t("Network link", "Collegamento di rete"),
79463
81062
  state: [{
79464
81063
  kind: "numeric",
79465
- field: "signalPercent",
81064
+ field: "signal",
79466
81065
  quantity: t("the signal strength", "l’intensità del segnale"),
79467
81066
  label: t("Signal", "Segnale"),
79468
81067
  unit: "%",
@@ -79555,6 +81154,8 @@ var ML = "model and training machinery — no device action";
79555
81154
  var INTERNAL = "internal plumbing between addons — never operator-facing";
79556
81155
  /** Every system-scoped cap, with why it is not a device's vocabulary. */
79557
81156
  var SYSTEM_CAP_VOCABULARY = {
81157
+ "action-source": system(INTERNAL),
81158
+ actions: system("declared actions — run from the Actions page and from the cameras that link them"),
79558
81159
  "addon-pages": system(UI_PLUMBING),
79559
81160
  "addon-pages-source": system(UI_PLUMBING),
79560
81161
  "addon-routes": system(UI_PLUMBING),
@@ -79585,6 +81186,7 @@ var SYSTEM_CAP_VOCABULARY = {
79585
81186
  "device-manager": system("the device registry itself — rename, move and remove are administration"),
79586
81187
  "device-provider": system(INTERNAL),
79587
81188
  "device-removal-hook": system(INTERNAL),
81189
+ "node-removal-hook": system(INTERNAL),
79588
81190
  "device-state": system("the runtime-state mirror every trigger already reads through"),
79589
81191
  "display-palette": system("the cluster display palette — one colour table for every client, not a device action"),
79590
81192
  "embedding-encoder": system(ML),
@@ -79624,6 +81226,7 @@ var SYSTEM_CAP_VOCABULARY = {
79624
81226
  "recording-archive": system(STORAGE),
79625
81227
  "recording-export": system("recording exports — requested from the timeline, not an automation step"),
79626
81228
  records: system(INTERNAL),
81229
+ remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
79627
81230
  "server-management": system(CLUSTER),
79628
81231
  "settings-store": system(INTERNAL),
79629
81232
  "smtp-provider": system(INTERNAL),
@@ -79636,6 +81239,7 @@ var SYSTEM_CAP_VOCABULARY = {
79636
81239
  "storage-provider": system(STORAGE),
79637
81240
  "stream-broker": system(PIPELINE),
79638
81241
  system: system(CLUSTER),
81242
+ template: system("operator-authored templates — edited in their own editor, never actuated"),
79639
81243
  "terminal-session": system(CLUSTER),
79640
81244
  toast: system(UI_PLUMBING),
79641
81245
  training: system(ML),
@@ -79695,7 +81299,7 @@ function projectAction(cap, action) {
79695
81299
  };
79696
81300
  }
79697
81301
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
79698
- 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;
81302
+ 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;
79699
81303
  DeviceFeature.BatteryOperated;
79700
81304
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
79701
81305
  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.";
@@ -79751,6 +81355,31 @@ Object.freeze({
79751
81355
  form: "single",
79752
81356
  optional: false
79753
81357
  }],
81358
+ "actions.get": [{
81359
+ name: "deviceId",
81360
+ form: "single",
81361
+ optional: true
81362
+ }],
81363
+ "actions.getLinks": [{
81364
+ name: "deviceId",
81365
+ form: "single",
81366
+ optional: false
81367
+ }],
81368
+ "actions.listForCamera": [{
81369
+ name: "deviceId",
81370
+ form: "single",
81371
+ optional: false
81372
+ }],
81373
+ "actions.run": [{
81374
+ name: "deviceId",
81375
+ form: "single",
81376
+ optional: true
81377
+ }],
81378
+ "actions.setLinks": [{
81379
+ name: "deviceId",
81380
+ form: "single",
81381
+ optional: false
81382
+ }],
79754
81383
  "addonSettings.getDeviceSettings": [{
79755
81384
  name: "deviceId",
79756
81385
  form: "single",
@@ -80802,6 +82431,16 @@ Object.freeze({
80802
82431
  form: "single",
80803
82432
  optional: false
80804
82433
  }],
82434
+ "irTransmitter.getInfo": [{
82435
+ name: "deviceId",
82436
+ form: "single",
82437
+ optional: false
82438
+ }],
82439
+ "irTransmitter.send": [{
82440
+ name: "deviceId",
82441
+ form: "single",
82442
+ optional: false
82443
+ }],
80805
82444
  "keychain.deleteDeviceScope": [{
80806
82445
  name: "deviceId",
80807
82446
  form: "single",
@@ -81211,6 +82850,36 @@ Object.freeze({
81211
82850
  form: "single",
81212
82851
  optional: false
81213
82852
  }],
82853
+ "petFountain.getStatus": [{
82854
+ name: "deviceId",
82855
+ form: "single",
82856
+ optional: false
82857
+ }],
82858
+ "petFountain.resetDailyConsumption": [{
82859
+ name: "deviceId",
82860
+ form: "single",
82861
+ optional: false
82862
+ }],
82863
+ "petFountain.setPump": [{
82864
+ name: "deviceId",
82865
+ form: "single",
82866
+ optional: false
82867
+ }],
82868
+ "petFountain.setPumpInterval": [{
82869
+ name: "deviceId",
82870
+ form: "single",
82871
+ optional: false
82872
+ }],
82873
+ "petFountain.setPumpOffMinutes": [{
82874
+ name: "deviceId",
82875
+ form: "single",
82876
+ optional: false
82877
+ }],
82878
+ "petFountain.setPumpOnMinutes": [{
82879
+ name: "deviceId",
82880
+ form: "single",
82881
+ optional: false
82882
+ }],
81214
82883
  "pipelineAnalytics.addRetrainFrameFromRecording": [{
81215
82884
  name: "deviceId",
81216
82885
  form: "single",
@@ -82027,6 +83696,31 @@ Object.freeze({
82027
83696
  form: "single",
82028
83697
  optional: true
82029
83698
  }],
83699
+ "remote.getConfig": [{
83700
+ name: "deviceId",
83701
+ form: "single",
83702
+ optional: false
83703
+ }],
83704
+ "remote.listCommands": [{
83705
+ name: "deviceId",
83706
+ form: "single",
83707
+ optional: false
83708
+ }],
83709
+ "remote.sendCommand": [{
83710
+ name: "deviceId",
83711
+ form: "single",
83712
+ optional: false
83713
+ }],
83714
+ "remote.sendMediaCommand": [{
83715
+ name: "deviceId",
83716
+ form: "single",
83717
+ optional: false
83718
+ }],
83719
+ "remote.setConfig": [{
83720
+ name: "deviceId",
83721
+ form: "single",
83722
+ optional: false
83723
+ }],
82030
83724
  "runningState.getStatus": [{
82031
83725
  name: "deviceId",
82032
83726
  form: "single",
@@ -83832,8 +85526,17 @@ DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
83832
85526
  DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
83833
85527
  DEFAULT_NATIVE_LEASE_SETTINGS.sceneBudgetMb;
83834
85528
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
85529
+ String.raw`(?<=^|["'(\s>,\[{])`;
83835
85530
  var MB = 1024 * 1024;
83836
85531
  1024 * MB, 3072 * MB;
85532
+ object({
85533
+ manufacturer: string().optional(),
85534
+ supportedModels: array(string()).optional(),
85535
+ supportedController: string().optional(),
85536
+ commandsEncoding: string(),
85537
+ delay: number().nonnegative().optional(),
85538
+ commands: record(string(), unknown())
85539
+ });
83837
85540
  //#endregion
83838
85541
  //#region src/custom-actions.ts
83839
85542
  var SourceIdSchema = string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug");