@camstack/addon-import-alexa 0.2.178 → 0.2.180

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -834,7 +834,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
834
834
  return EventCategory;
835
835
  }({});
836
836
  //#endregion
837
- //#region ../types/dist/device-type-BOWLtoOt.mjs
837
+ //#region ../types/dist/device-type-D1fH5kaC.mjs
838
838
  /**
839
839
  import { errMsg } from '@camstack/types'
840
840
  * Extract a human-readable message from an unknown error value.
@@ -1023,6 +1023,9 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
1023
1023
  DeviceFeature["CoverPositionable"] = "cover-positionable";
1024
1024
  /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
1025
1025
  DeviceFeature["CoverTilt"] = "cover-tilt";
1026
+ /** Cover has a real stop: `stop` halts the travel in place. Gates the stop
1027
+ * control in exporters; a provider whose `stop` is a no-op never declares it. */
1028
+ DeviceFeature["CoverStoppable"] = "cover-stoppable";
1026
1029
  /** Valve exposes intermediate position control (0..100). Gates the
1027
1030
  * position slider / drag surface UI. */
1028
1031
  DeviceFeature["ValvePositionable"] = "valve-positionable";
@@ -1062,6 +1065,12 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
1062
1065
  DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
1063
1066
  /** Media player exposes stop distinct from pause. */
1064
1067
  DeviceFeature["MediaPlayerStop"] = "media-player-stop";
1068
+ /** Media player exposes volume up / down in steps (an infrared TV has no level). */
1069
+ DeviceFeature["MediaPlayerVolumeStep"] = "media-player-volume-step";
1070
+ /** Media player exposes channel up / down in steps. */
1071
+ DeviceFeature["MediaPlayerChannelStep"] = "media-player-channel-step";
1072
+ /** Media player exposes power on / off. */
1073
+ DeviceFeature["MediaPlayerPower"] = "media-player-power";
1065
1074
  /** Alarm panel requires a PIN code on arm/disarm. */
1066
1075
  DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
1067
1076
  /** Presence device carries GPS coordinates (lat/lng/accuracy) in
@@ -6035,7 +6044,7 @@ function number(params) {
6035
6044
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
6036
6045
  }
6037
6046
  //#endregion
6038
- //#region ../types/dist/sleep-B-vp7x15.mjs
6047
+ //#region ../types/dist/sleep-BKClVk0K.mjs
6039
6048
  /**
6040
6049
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6041
6050
  * window to float samples (D455).
@@ -6114,6 +6123,22 @@ function buildAlawTable() {
6114
6123
  }
6115
6124
  buildUlawTable();
6116
6125
  buildAlawTable();
6126
+ /**
6127
+ * An addon whose own migrations did not apply refuses to initialise (D987). The error is typed
6128
+ * so a runner hosting several addons can tell "this addon is blocked" from a crash and keep its
6129
+ * co-located siblings running. Recognised by NAME and shape, never `instanceof`: a forked addon
6130
+ * bundles its own copy of this module, so the class the runner sees is not the one that threw.
6131
+ */
6132
+ var MIGRATIONS_BLOCKED_ERROR_NAME = "AddonMigrationsBlockedError";
6133
+ var AddonMigrationsBlockedError = class extends Error {
6134
+ /** The ledger ids that did not apply and block the owner. */
6135
+ migrationIds;
6136
+ constructor(migrationIds) {
6137
+ super(`migration(s) ${migrationIds.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
6138
+ this.name = MIGRATIONS_BLOCKED_ERROR_NAME;
6139
+ this.migrationIds = migrationIds;
6140
+ }
6141
+ };
6117
6142
  /** Is this field's stored value a credential? */
6118
6143
  function isSecretConfigField(field) {
6119
6144
  if (field.type === "password") return true;
@@ -6152,7 +6177,7 @@ function isKeychainStoredField(field) {
6152
6177
  var SECRET_CLEAR = { $secretClear: true };
6153
6178
  /** The `'***'` token D634 redaction writes for non-admins. */
6154
6179
  var LEGACY_DEVICE_REDACTION = "***";
6155
- function isRecord$6(value) {
6180
+ function isRecord$7(value) {
6156
6181
  return typeof value === "object" && value !== null && !Array.isArray(value);
6157
6182
  }
6158
6183
  function makeSecretMarker(ref) {
@@ -6163,12 +6188,12 @@ function makeSecretMarker(ref) {
6163
6188
  }
6164
6189
  /** A stored marker: an object whose `$secret` is a non-empty string. */
6165
6190
  function isSecretMarker(value) {
6166
- if (!isRecord$6(value)) return false;
6191
+ if (!isRecord$7(value)) return false;
6167
6192
  const ref = value["$secret"];
6168
6193
  return typeof ref === "string" && ref.length > 0;
6169
6194
  }
6170
6195
  function isSecretClear(value) {
6171
- return isRecord$6(value) && value["$secretClear"] === true;
6196
+ return isRecord$7(value) && value["$secretClear"] === true;
6172
6197
  }
6173
6198
  /**
6174
6199
  * The UI view of a stored secret field value: a marker becomes "set", inline
@@ -6921,7 +6946,7 @@ var KeychainWriteRefusedError = class extends Error {
6921
6946
  }
6922
6947
  };
6923
6948
  var KEEP = { kind: "keep" };
6924
- function isRecord$5(value) {
6949
+ function isRecord$6(value) {
6925
6950
  return typeof value === "object" && value !== null && !Array.isArray(value);
6926
6951
  }
6927
6952
  function defaultMintId() {
@@ -6934,7 +6959,7 @@ function collectSecretMarkerRefs(value, out = []) {
6934
6959
  return out;
6935
6960
  }
6936
6961
  if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
6937
- else if (isRecord$5(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
6962
+ else if (isRecord$6(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
6938
6963
  return out;
6939
6964
  }
6940
6965
  async function storedValueEquals(secrets, marker, candidate) {
@@ -6989,7 +7014,7 @@ function rowId(row, idKey) {
6989
7014
  return null;
6990
7015
  }
6991
7016
  async function writeArray(ctx, node, incoming, stored, fieldPath) {
6992
- const storedRows = Array.isArray(stored) ? stored.filter(isRecord$5) : [];
7017
+ const storedRows = Array.isArray(stored) ? stored.filter(isRecord$6) : [];
6993
7018
  if (!Array.isArray(incoming)) {
6994
7019
  ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
6995
7020
  return {
@@ -7005,7 +7030,7 @@ async function writeArray(ctx, node, incoming, stored, fieldPath) {
7005
7030
  const kept = /* @__PURE__ */ new Set();
7006
7031
  const rows = [];
7007
7032
  for (const row of incoming) {
7008
- if (!isRecord$5(row)) {
7033
+ if (!isRecord$6(row)) {
7009
7034
  rows.push(row);
7010
7035
  continue;
7011
7036
  }
@@ -7030,7 +7055,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
7030
7055
  case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
7031
7056
  case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
7032
7057
  case "object": {
7033
- if (!isRecord$5(incoming)) {
7058
+ if (!isRecord$6(incoming)) {
7034
7059
  if (incoming === void 0) return KEEP;
7035
7060
  ctx.deletes.push(...collectSecretMarkerRefs(stored));
7036
7061
  return {
@@ -7038,7 +7063,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
7038
7063
  value: incoming
7039
7064
  };
7040
7065
  }
7041
- const storedObj = isRecord$5(stored) ? stored : {};
7066
+ const storedObj = isRecord$6(stored) ? stored : {};
7042
7067
  return {
7043
7068
  kind: "value",
7044
7069
  value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
@@ -7161,7 +7186,7 @@ async function resolveSecretMarkers(value, secrets, options = {}) {
7161
7186
  value: items
7162
7187
  };
7163
7188
  }
7164
- if (isRecord$5(v)) {
7189
+ if (isRecord$6(v)) {
7165
7190
  const out = {};
7166
7191
  for (const [k, child] of Object.entries(v)) {
7167
7192
  const s = substitute(child);
@@ -7193,7 +7218,7 @@ var SecretOverwriteRefusedError = class extends Error {
7193
7218
  this.name = "SecretOverwriteRefusedError";
7194
7219
  }
7195
7220
  };
7196
- function isRecord$4$1(value) {
7221
+ function isRecord$5(value) {
7197
7222
  return typeof value === "object" && value !== null && !Array.isArray(value);
7198
7223
  }
7199
7224
  function resolvedValue(pass, marker) {
@@ -7222,7 +7247,7 @@ function inArray(pass, incoming, stored) {
7222
7247
  return incoming.map((item, index) => {
7223
7248
  const twin = stored[index];
7224
7249
  if (isSecretMarker(twin)) return typeof item === "string" && item === resolvedValue(pass, twin) ? twin : item;
7225
- if (isRecord$4$1(item) && isRecord$4$1(twin)) return inArrayRow(pass, item, twin);
7250
+ if (isRecord$5(item) && isRecord$5(twin)) return inArrayRow(pass, item, twin);
7226
7251
  if (Array.isArray(item) && Array.isArray(twin)) return inArray(pass, item, twin);
7227
7252
  return item;
7228
7253
  });
@@ -7233,7 +7258,7 @@ function inArrayRow(pass, incoming, stored) {
7233
7258
  const twin = stored[key];
7234
7259
  if (isSecretMarker(twin)) {
7235
7260
  if (typeof value === "string" && value === resolvedValue(pass, twin)) out[key] = twin;
7236
- } else if (isRecord$4$1(value) && isRecord$4$1(twin)) out[key] = inArrayRow(pass, value, twin);
7261
+ } else if (isRecord$5(value) && isRecord$5(twin)) out[key] = inArrayRow(pass, value, twin);
7237
7262
  else if (Array.isArray(value) && Array.isArray(twin)) out[key] = inArray(pass, value, twin);
7238
7263
  }
7239
7264
  return out;
@@ -7241,7 +7266,7 @@ function inArrayRow(pass, incoming, stored) {
7241
7266
  async function walk(pass, incoming, stored) {
7242
7267
  if (isSecretMarker(stored)) return atMarker(pass, incoming, stored);
7243
7268
  if (Array.isArray(incoming) && Array.isArray(stored)) return inArray(pass, incoming, stored);
7244
- if (!isRecord$4$1(incoming) || !isRecord$4$1(stored)) return incoming;
7269
+ if (!isRecord$5(incoming) || !isRecord$5(stored)) return incoming;
7245
7270
  const out = { ...incoming };
7246
7271
  for (const [key, value] of Object.entries(incoming)) out[key] = await walk(pass, value, stored[key]);
7247
7272
  return out;
@@ -7272,7 +7297,7 @@ async function restoreUnchangedSecrets(input) {
7272
7297
  function isJsonEqual(a, b) {
7273
7298
  if (Object.is(a, b)) return true;
7274
7299
  if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((v, i) => isJsonEqual(v, b[i]));
7275
- if (isRecord$4$1(a) && isRecord$4$1(b)) {
7300
+ if (isRecord$5(a) && isRecord$5(b)) {
7276
7301
  const keys = Object.keys(a);
7277
7302
  if (keys.length !== Object.keys(b).length) return false;
7278
7303
  return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && isJsonEqual(a[k], b[k]));
@@ -7296,7 +7321,7 @@ function markerKeysOf(blob, owner) {
7296
7321
  }
7297
7322
  function nodeFromStored(value, owner) {
7298
7323
  if (isSecretMarker(value)) return isSecretRefOwnedBy(value.$secret, owner) ? { kind: "leaf" } : null;
7299
- if (!isRecord$4$1(value)) return null;
7324
+ if (!isRecord$5(value)) return null;
7300
7325
  const children = /* @__PURE__ */ new Map();
7301
7326
  for (const [key, child] of Object.entries(value)) {
7302
7327
  const node = nodeFromStored(child, owner);
@@ -7767,7 +7792,7 @@ var BaseAddon = class {
7767
7792
  startMigration: (input) => api.startMigration.mutate(input),
7768
7793
  finishMigration: (input) => api.finishMigration.mutate(input)
7769
7794
  }, migrations, this.ctx.logger);
7770
- if (report.blockOwner.length > 0) throw new Error(`migration(s) ${report.blockOwner.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
7795
+ if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
7771
7796
  return report;
7772
7797
  }
7773
7798
  /**
@@ -7915,6 +7940,44 @@ var BaseAddon = class {
7915
7940
  return null;
7916
7941
  }
7917
7942
  /**
7943
+ * D986 scoped settings: the form shape of the CLUSTER-wide settings one capability this addon
7944
+ * provides owns, or `null` when it has none. `cap` is a capability name the addon's manifest
7945
+ * declares. Stored as `addon-settings` rows `<addonId>.<field>`. The keys of this schema, of
7946
+ * {@link nodeSettingsSchema} and of {@link deviceDefaultsSchema} are disjoint, and a key never
7947
+ * contains `@` (guard `check-scoped-settings-declared`). SHAPE only: values are read by scope.
7948
+ */
7949
+ async clusterSettingsSchema(_cap) {
7950
+ return null;
7951
+ }
7952
+ /**
7953
+ * D986: the form shape of the settings one capability owns PER NODE, or `null`. Stored as
7954
+ * `addon-settings` rows `<addonId>.<field>@<nodeId>` carrying `nodeId` in the row data, so a
7955
+ * forgotten node's rows go in one central delete.
7956
+ */
7957
+ async nodeSettingsSchema(_cap) {
7958
+ return null;
7959
+ }
7960
+ /**
7961
+ * D986: the form shape of the FLEET DEFAULTS of the per-device settings one capability owns,
7962
+ * or `null`. Stored as `addon-settings` rows `<addonId>.<field>`; a device resolves its value
7963
+ * as device row, then this default, then the schema default (`resolveDeviceSettings`).
7964
+ */
7965
+ async deviceDefaultsSchema(_cap) {
7966
+ return null;
7967
+ }
7968
+ /**
7969
+ * The host's read of the scoped declarations: the schema one capability declares for `scope`,
7970
+ * or `null`. Beside {@link getIntegrationFormSchema}, the same shape: the hook is protected, the
7971
+ * host asks this method.
7972
+ */
7973
+ async getScopedSettingsSchema(scope, cap) {
7974
+ switch (scope) {
7975
+ case "cluster": return this.clusterSettingsSchema(cap);
7976
+ case "node": return this.nodeSettingsSchema(cap);
7977
+ case "device-defaults": return this.deviceDefaultsSchema(cap);
7978
+ }
7979
+ }
7980
+ /**
7918
7981
  * What the create wizard and the integration edit page render for one instance: the addon's
7919
7982
  * per-instance schema when it declares one, else its global schema (what they always showed).
7920
7983
  */
@@ -8708,47 +8771,6 @@ function normalizeAddonInitResult(result) {
8708
8771
  if (Array.isArray(result)) return { providers: result };
8709
8772
  return result;
8710
8773
  }
8711
- /**
8712
- * The share-view scope kinds a share-link token can carry. The hub mints
8713
- * and validates them (`ShareViewGrantSchema` builds its
8714
- * enum from this list); a data plane names the kinds it ACCEPTS in `share`.
8715
- */
8716
- var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
8717
- /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
8718
- var PeerBytesTicketSchema = object({
8719
- /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
8720
- url: string().min(1),
8721
- /**
8722
- * The HOST node this URL means something on — the hub or a named agent,
8723
- * never a runner. {@link AddonPeerBytes.open} compares it to its own and
8724
- * refuses `cross-node` by name when they differ, without dialling.
8725
- */
8726
- hostNodeId: string().min(1),
8727
- expiresAtMs: number$1().int().nonnegative(),
8728
- /**
8729
- * What the producer DECLARED the body to be, when it knows — `null` when it
8730
- * does not. Never `0` for unknown (D393): a consumer sizing a bound off this
8731
- * must be able to tell "the producer did not say" from "the body is empty".
8732
- */
8733
- declaredBytes: number$1().int().nonnegative().nullable()
8734
- });
8735
- /** Limits and names of `ctx.records` (D881). One authority; the engine and the facade read it. */
8736
- /** The files a kind may live in. `history` is device-history's (D850), never records. */
8737
- var RECORD_DATABASES = ["settings", "events"];
8738
- /** The classes of a kind: the `@durable` vocabulary, one authority (replaces the annotation). */
8739
- var RECORD_CLASSES = [
8740
- "config",
8741
- "ledger",
8742
- "audit",
8743
- "mirror",
8744
- "ephemeral"
8745
- ];
8746
- var RECORD_DEFAULT_MAX_DATA_BYTES = 64 * 1024;
8747
- var RECORD_MAX_DATA_BYTES = 1024 * 1024;
8748
- var RECORD_BATCH_MAX = 1e3;
8749
- var RECORD_QUERY_MAX = 1e3;
8750
- /** A kind in the `settings` file may not bound itself above this (D447: high volume is `events`). */
8751
- var RECORD_SETTINGS_MAX_ROWS = 5e4;
8752
8774
  var PREFIX = "records/";
8753
8775
  /** The base of every records failure. `detail` is the human part of the message. */
8754
8776
  var RecordsError = class extends Error {
@@ -8781,6 +8803,83 @@ var DeclarationRefusedError = class extends RecordsError {
8781
8803
  this.name = "DeclarationRefusedError";
8782
8804
  }
8783
8805
  };
8806
+ /** Limits and names of `ctx.records` (D881). One authority; the engine and the facade read it. */
8807
+ /** The files a kind may live in. `history` is device-history's (D850), never records. */
8808
+ var RECORD_DATABASES = ["settings", "events"];
8809
+ /** The classes of a kind: the `@durable` vocabulary, one authority (replaces the annotation). */
8810
+ var RECORD_CLASSES = [
8811
+ "config",
8812
+ "ledger",
8813
+ "audit",
8814
+ "mirror",
8815
+ "ephemeral"
8816
+ ];
8817
+ var RECORD_DEFAULT_MAX_DATA_BYTES = 64 * 1024;
8818
+ var RECORD_MAX_DATA_BYTES = 1024 * 1024;
8819
+ var RECORD_BATCH_MAX = 1e3;
8820
+ var RECORD_QUERY_MAX = 1e3;
8821
+ /** A kind in the `settings` file may not bound itself above this (D447: high volume is `events`). */
8822
+ var RECORD_SETTINGS_MAX_ROWS = 5e4;
8823
+ /**
8824
+ * Secret fields of a record, addressed the way a Zod schema declares them
8825
+ * (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
8826
+ * (`auth.accessToken`, `headers[].value`).
8827
+ *
8828
+ * Array elements are matched by a DECLARED id key (`secretArrayKeys`:
8829
+ * `{ headers: 'name' }`), never by index, so reordering a list keeps every
8830
+ * keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
8831
+ * leaf names its element: `headers[X-Api].value`.
8832
+ *
8833
+ * Pure and browser-safe. Nothing here mutates: every transform returns a new value.
8834
+ */
8835
+ /** The array prefixes (declared form, e.g. `headers` or `a.headers`) of a path. */
8836
+ function parseSecretPath(path) {
8837
+ if (path.length === 0) throw new InvalidRecordError("an empty secret path");
8838
+ return path.split(".").map((raw) => {
8839
+ const each = raw.endsWith("[]");
8840
+ const key = each ? raw.slice(0, -2) : raw;
8841
+ if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
8842
+ return {
8843
+ key,
8844
+ each
8845
+ };
8846
+ });
8847
+ }
8848
+ /** The declared array prefixes of `path` that need an id key (`headers`, `a.rows`). */
8849
+ function secretArrayPrefixes(path) {
8850
+ const segments = parseSecretPath(path);
8851
+ const prefixes = [];
8852
+ const walked = [];
8853
+ for (const segment of segments) {
8854
+ walked.push(segment.key);
8855
+ if (segment.each) prefixes.push(walked.join("."));
8856
+ }
8857
+ return prefixes;
8858
+ }
8859
+ /**
8860
+ * The share-view scope kinds a share-link token can carry. The hub mints
8861
+ * and validates them (`ShareViewGrantSchema` builds its
8862
+ * enum from this list); a data plane names the kinds it ACCEPTS in `share`.
8863
+ */
8864
+ var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
8865
+ /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
8866
+ var PeerBytesTicketSchema = object({
8867
+ /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
8868
+ url: string().min(1),
8869
+ /**
8870
+ * The HOST node this URL means something on — the hub or a named agent,
8871
+ * never a runner. {@link AddonPeerBytes.open} compares it to its own and
8872
+ * refuses `cross-node` by name when they differ, without dialling.
8873
+ */
8874
+ hostNodeId: string().min(1),
8875
+ expiresAtMs: number$1().int().nonnegative(),
8876
+ /**
8877
+ * What the producer DECLARED the body to be, when it knows — `null` when it
8878
+ * does not. Never `0` for unknown (D393): a consumer sizing a bound off this
8879
+ * must be able to tell "the producer did not say" from "the body is empty".
8880
+ */
8881
+ declaredBytes: number$1().int().nonnegative().nullable()
8882
+ });
8784
8883
  function defOf(schema) {
8785
8884
  const zodBag = Reflect.get(schema, "_zod");
8786
8885
  if (typeof zodBag !== "object" || zodBag === null) return {};
@@ -8849,42 +8948,6 @@ function collectZodSecretPaths(schema, prefix = "") {
8849
8948
  return [...new Set(paths)];
8850
8949
  }
8851
8950
  /**
8852
- * Secret fields of a record, addressed the way a Zod schema declares them
8853
- * (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
8854
- * (`auth.accessToken`, `headers[].value`).
8855
- *
8856
- * Array elements are matched by a DECLARED id key (`secretArrayKeys`:
8857
- * `{ headers: 'name' }`), never by index, so reordering a list keeps every
8858
- * keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
8859
- * leaf names its element: `headers[X-Api].value`.
8860
- *
8861
- * Pure and browser-safe. Nothing here mutates: every transform returns a new value.
8862
- */
8863
- /** The array prefixes (declared form, e.g. `headers` or `a.headers`) of a path. */
8864
- function parseSecretPath(path) {
8865
- if (path.length === 0) throw new InvalidRecordError("an empty secret path");
8866
- return path.split(".").map((raw) => {
8867
- const each = raw.endsWith("[]");
8868
- const key = each ? raw.slice(0, -2) : raw;
8869
- if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
8870
- return {
8871
- key,
8872
- each
8873
- };
8874
- });
8875
- }
8876
- /** The declared array prefixes of `path` that need an id key (`headers`, `a.rows`). */
8877
- function secretArrayPrefixes(path) {
8878
- const segments = parseSecretPath(path);
8879
- const prefixes = [];
8880
- const walked = [];
8881
- for (const segment of segments) {
8882
- walked.push(segment.key);
8883
- if (segment.each) prefixes.push(walked.join("."));
8884
- }
8885
- return prefixes;
8886
- }
8887
- /**
8888
8951
  * Record kinds (D881): the declaration an addon makes of one kind of fact, the
8889
8952
  * Zod row schema that describes it, and the helper that turns the two into the
8890
8953
  * JSON the engine stores.
@@ -9724,6 +9787,7 @@ var CAP_NAMES = Object.freeze([
9724
9787
  "backup",
9725
9788
  "battery",
9726
9789
  "binary",
9790
+ "board-key-source",
9727
9791
  "brightness",
9728
9792
  "broker",
9729
9793
  "button",
@@ -9787,6 +9851,7 @@ var CAP_NAMES = Object.freeze([
9787
9851
  "humidity-sensor",
9788
9852
  "image",
9789
9853
  "image-settings",
9854
+ "integration-instances",
9790
9855
  "integrations",
9791
9856
  "intercom",
9792
9857
  "ir-transmitter",
@@ -9844,6 +9909,7 @@ var CAP_NAMES = Object.freeze([
9844
9909
  "privacy-mask",
9845
9910
  "ptz",
9846
9911
  "ptz-autotrack",
9912
+ "pump-cycle",
9847
9913
  "reboot",
9848
9914
  "record-entries",
9849
9915
  "recording",
@@ -11136,18 +11202,6 @@ Object.freeze({
11136
11202
  "path": "pumpRunning",
11137
11203
  "valueType": "boolean"
11138
11204
  },
11139
- {
11140
- "path": "pumpIntervalEnabled",
11141
- "valueType": "boolean"
11142
- },
11143
- {
11144
- "path": "pumpOnTime",
11145
- "valueType": "number"
11146
- },
11147
- {
11148
- "path": "pumpOffTime",
11149
- "valueType": "number"
11150
- },
11151
11205
  {
11152
11206
  "path": "lastDrink",
11153
11207
  "valueType": "number"
@@ -11271,6 +11325,20 @@ Object.freeze({
11271
11325
  "valueType": "number"
11272
11326
  }
11273
11327
  ],
11328
+ "pump-cycle": [
11329
+ {
11330
+ "path": "enabled",
11331
+ "valueType": "boolean"
11332
+ },
11333
+ {
11334
+ "path": "onTime",
11335
+ "valueType": "number"
11336
+ },
11337
+ {
11338
+ "path": "offTime",
11339
+ "valueType": "number"
11340
+ }
11341
+ ],
11274
11342
  "recording-archive": [
11275
11343
  {
11276
11344
  "path": "deviceId",
@@ -11720,7 +11788,7 @@ object({
11720
11788
  params: record(string(), union([string(), number$1()])).optional()
11721
11789
  });
11722
11790
  //#endregion
11723
- //#region ../types/dist/composition-kKKOdFIG.mjs
11791
+ //#region ../types/dist/composition-BodkvOV0.mjs
11724
11792
  /**
11725
11793
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11726
11794
  *
@@ -12857,7 +12925,14 @@ var CompositionForwardCommandSchema = object({
12857
12925
  method: string().min(1),
12858
12926
  args: record(string().min(1), CompositionForwardArgSchema).optional()
12859
12927
  });
12860
- var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
12928
+ var CompositionCommandTargetSchema = discriminatedUnion("kind", [
12929
+ CompositionForwardCommandSchema,
12930
+ CompositionCodeSourceSchema,
12931
+ object({
12932
+ kind: literal("extension"),
12933
+ target: literal("remote")
12934
+ })
12935
+ ]);
12861
12936
  /** An item key inside an item-array cap (`consumables.items[<key>]`). */
12862
12937
  var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
12863
12938
  /**
@@ -12934,6 +13009,73 @@ var NodeColorIdSchema = _enum([
12934
13009
  "node-g",
12935
13010
  "node-h"
12936
13011
  ]);
13012
+ //#endregion
13013
+ //#region ../types/dist/index.mjs
13014
+ var SMTP_INBOUND_DEFAULT_PORT = 2525;
13015
+ var SMTP_INBOUND_DEFAULT_DOMAIN = "camstack.local";
13016
+ var SMTP_INBOUND_DEFAULT_MAX_MESSAGE_BYTES = 25 * 1024 * 1024;
13017
+ /** The text excerpt a message carries on the bus. */
13018
+ var SMTP_INBOUND_TEXT_MAX_CHARS = 4096;
13019
+ var PortSchema = number$1().int().min(1).max(65535);
13020
+ /** A recipient local-part: what stands before the `@`. */
13021
+ var SmtpRecipientSchema = string().min(1).max(64).regex(/^[A-Za-z0-9._+-]+$/);
13022
+ object({
13023
+ port: PortSchema.default(SMTP_INBOUND_DEFAULT_PORT),
13024
+ bindHost: string().min(1).default("0.0.0.0"),
13025
+ domain: string().min(1).default(SMTP_INBOUND_DEFAULT_DOMAIN),
13026
+ requireAuth: boolean().default(true),
13027
+ username: string().default(""),
13028
+ password: string().default("").meta({ secret: true }),
13029
+ starttls: boolean().default(false),
13030
+ maxMessageBytes: number$1().int().positive().default(SMTP_INBOUND_DEFAULT_MAX_MESSAGE_BYTES)
13031
+ });
13032
+ object({
13033
+ host: string().min(1).nullable(),
13034
+ hostReason: string().nullable(),
13035
+ port: PortSchema,
13036
+ domain: string().min(1),
13037
+ requireAuth: boolean(),
13038
+ username: string(),
13039
+ password: string(),
13040
+ starttls: boolean()
13041
+ });
13042
+ object({
13043
+ recipients: array(SmtpRecipientSchema).min(1),
13044
+ /**
13045
+ * Who is subscribing. A second `subscribe` with the same id REPLACES that subscriber's
13046
+ * recipients (the way a consumer that restarted, or lost a device, declares its set again);
13047
+ * a recipient held by another subscriber is refused with `smtp-inbound/recipient-claimed`.
13048
+ * Absent, the subscriber is anonymous and unique.
13049
+ */
13050
+ subscriberId: string().min(1).max(64).optional()
13051
+ });
13052
+ var SmtpInboundAttachmentSchema = object({
13053
+ filename: string(),
13054
+ contentType: string(),
13055
+ bytes: number$1().int().nonnegative()
13056
+ });
13057
+ /** The payload of one `broker.message` (the key is the recipient local-part). */
13058
+ var SmtpInboundMessageSchema = object({
13059
+ /** Monotonic per mailbox; a gap tells a consumer it missed an event. */
13060
+ seq: number$1().int().positive(),
13061
+ messageId: string(),
13062
+ receivedAt: number$1().int().nonnegative(),
13063
+ from: string(),
13064
+ to: string(),
13065
+ subject: string(),
13066
+ text: string().max(SMTP_INBOUND_TEXT_MAX_CHARS),
13067
+ attachments: array(SmtpInboundAttachmentSchema)
13068
+ });
13069
+ object({
13070
+ /** The newest seq issued so far (0 before any mail). */
13071
+ lastSeq: number$1().int().nonnegative(),
13072
+ /** The oldest seq still retained; messages below it are gone. */
13073
+ oldestSeq: number$1().int().nonnegative(),
13074
+ messages: array(object({
13075
+ key: string(),
13076
+ message: SmtpInboundMessageSchema
13077
+ }))
13078
+ });
12937
13079
  new Set([
12938
13080
  "track",
12939
13081
  "face",
@@ -16093,8 +16235,18 @@ var LogChannelDescriptorSchema = object({
16093
16235
  * the operator narrows to one camera, sees nothing, and concludes the code
16094
16236
  * path was never taken.
16095
16237
  */
16096
- perDevice: boolean()
16097
- });
16238
+ perDevice: boolean(),
16239
+ /**
16240
+ * What the channel is FOR, so the UI can list the channels an addon offers.
16241
+ *
16242
+ * - `diagnostic` (the default): a per-component diagnostic armed from the
16243
+ * Levels page, emitted at its declared level.
16244
+ * - `debug`: a `debug.<channel>` dump watched live for a short window
16245
+ * (`ctx.debug`). Always per-device, and its lines are denied by the log
16246
+ * ring unless somebody is watching that device.
16247
+ */
16248
+ kind: _enum(["diagnostic", "debug"]).default("diagnostic")
16249
+ }).refine((d) => d.kind !== "debug" || d.name.startsWith("debug.") && d.perDevice, { message: "a debug channel is named debug.<channel> and is per-device" });
16098
16250
  /**
16099
16251
  * An armed window over one channel, as the document hands it to a mirror.
16100
16252
  *
@@ -19312,56 +19464,6 @@ var APPLIANCE_CAP_VOCABULARY = {
19312
19464
  icon: "square",
19313
19465
  args: { on: false }
19314
19466
  },
19315
- {
19316
- id: "petFountain.intervalOn",
19317
- method: "setPumpInterval",
19318
- label: t("Pump cycle on", "Ciclo pompa attivo"),
19319
- phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
19320
- icon: "timer",
19321
- args: { on: true }
19322
- },
19323
- {
19324
- id: "petFountain.intervalOff",
19325
- method: "setPumpInterval",
19326
- label: t("Pump cycle off", "Ciclo pompa spento"),
19327
- phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
19328
- icon: "timer-off",
19329
- args: { on: false }
19330
- },
19331
- {
19332
- id: "petFountain.pumpOnMinutes",
19333
- method: "setPumpOnMinutes",
19334
- label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
19335
- phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
19336
- icon: "timer",
19337
- openArgs: [{
19338
- key: "minutes",
19339
- label: t("Minutes", "Minuti"),
19340
- offer: {
19341
- kind: "number",
19342
- min: 0,
19343
- step: 1,
19344
- unit: "min"
19345
- }
19346
- }]
19347
- },
19348
- {
19349
- id: "petFountain.pumpOffMinutes",
19350
- method: "setPumpOffMinutes",
19351
- label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
19352
- phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
19353
- icon: "timer-off",
19354
- openArgs: [{
19355
- key: "minutes",
19356
- label: t("Minutes", "Minuti"),
19357
- offer: {
19358
- kind: "number",
19359
- min: 0,
19360
- step: 1,
19361
- unit: "min"
19362
- }
19363
- }]
19364
- },
19365
19467
  {
19366
19468
  id: "petFountain.resetDailyConsumption",
19367
19469
  method: "resetDailyConsumption",
@@ -19386,10 +19488,116 @@ var APPLIANCE_CAP_VOCABULARY = {
19386
19488
  icon: "droplets"
19387
19489
  }]
19388
19490
  },
19491
+ "pump-cycle": {
19492
+ scope: "device",
19493
+ noun: t("Pump cycle", "Ciclo della pompa"),
19494
+ actions: [
19495
+ {
19496
+ id: "pumpCycle.on",
19497
+ method: "setEnabled",
19498
+ label: t("Pump cycle on", "Ciclo pompa attivo"),
19499
+ phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
19500
+ icon: "timer",
19501
+ args: { on: true }
19502
+ },
19503
+ {
19504
+ id: "pumpCycle.off",
19505
+ method: "setEnabled",
19506
+ label: t("Pump cycle off", "Ciclo pompa spento"),
19507
+ phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
19508
+ icon: "timer-off",
19509
+ args: { on: false }
19510
+ },
19511
+ {
19512
+ id: "pumpCycle.onMinutes",
19513
+ method: "setOnMinutes",
19514
+ label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
19515
+ phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
19516
+ icon: "timer",
19517
+ openArgs: [{
19518
+ key: "minutes",
19519
+ label: t("Minutes", "Minuti"),
19520
+ offer: {
19521
+ kind: "number",
19522
+ min: 0,
19523
+ step: 1,
19524
+ unit: "min"
19525
+ }
19526
+ }]
19527
+ },
19528
+ {
19529
+ id: "pumpCycle.offMinutes",
19530
+ method: "setOffMinutes",
19531
+ label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
19532
+ phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
19533
+ icon: "timer-off",
19534
+ openArgs: [{
19535
+ key: "minutes",
19536
+ label: t("Minutes", "Minuti"),
19537
+ offer: {
19538
+ kind: "number",
19539
+ min: 0,
19540
+ step: 1,
19541
+ unit: "min"
19542
+ }
19543
+ }]
19544
+ }
19545
+ ],
19546
+ state: [{
19547
+ kind: "boolean",
19548
+ field: "enabled",
19549
+ label: t("Pump cycle", "Ciclo pompa"),
19550
+ whenTrue: t("When {device} starts running its pump on a cycle", "Quando {device} inizia a far lavorare la pompa a ciclo"),
19551
+ whenFalse: t("When {device} runs its pump continuously", "Quando {device} fa lavorare la pompa in continuo"),
19552
+ icon: "timer"
19553
+ }]
19554
+ },
19389
19555
  "media-player": {
19390
19556
  scope: "device",
19391
19557
  noun: t("Media player", "Lettore multimediale"),
19392
19558
  actions: [
19559
+ {
19560
+ id: "media.turnOn",
19561
+ method: "turnOn",
19562
+ label: t("Turn on", "Accendi"),
19563
+ phrase: t("Turn on {device}", "Accendi {device}"),
19564
+ icon: "power"
19565
+ },
19566
+ {
19567
+ id: "media.turnOff",
19568
+ method: "turnOff",
19569
+ label: t("Turn off", "Spegni"),
19570
+ phrase: t("Turn off {device}", "Spegni {device}"),
19571
+ icon: "power"
19572
+ },
19573
+ {
19574
+ id: "media.volumeUp",
19575
+ method: "volumeUp",
19576
+ label: t("Volume up", "Alza il volume"),
19577
+ phrase: t("Raise the volume of {device}", "Alza il volume di {device}"),
19578
+ icon: "volume-2"
19579
+ },
19580
+ {
19581
+ id: "media.volumeDown",
19582
+ method: "volumeDown",
19583
+ label: t("Volume down", "Abbassa il volume"),
19584
+ phrase: t("Lower the volume of {device}", "Abbassa il volume di {device}"),
19585
+ icon: "volume-2"
19586
+ },
19587
+ {
19588
+ id: "media.channelUp",
19589
+ method: "channelUp",
19590
+ label: t("Channel up", "Canale successivo"),
19591
+ phrase: t("Go to the next channel on {device}", "Passa al canale successivo su {device}"),
19592
+ icon: "skip-forward"
19593
+ },
19594
+ {
19595
+ id: "media.channelDown",
19596
+ method: "channelDown",
19597
+ label: t("Channel down", "Canale precedente"),
19598
+ phrase: t("Go to the previous channel on {device}", "Torna al canale precedente su {device}"),
19599
+ icon: "skip-back"
19600
+ },
19393
19601
  {
19394
19602
  id: "media.play",
19395
19603
  method: "play",
@@ -21276,6 +21484,7 @@ var SYSTEM_CAP_VOCABULARY = {
21276
21484
  "audio-analyzer": system(PIPELINE),
21277
21485
  "audio-codec": system(INTERNAL),
21278
21486
  "auth-provider": system(IDENTITY),
21487
+ "board-key-source": system(INTERNAL),
21279
21488
  backup: system("system backups — run and restored from the Backups page"),
21280
21489
  broker: system("message-broker connections — configured, not actuated"),
21281
21490
  "calendar-source": system(INTERNAL),
@@ -21310,6 +21519,7 @@ var SYSTEM_CAP_VOCABULARY = {
21310
21519
  "load-contribution": system(INTERNAL),
21311
21520
  "local-network": system(NETWORK),
21312
21521
  "log-channels": system(INTERNAL),
21522
+ "integration-instances": system(INTERNAL),
21313
21523
  "log-destination": system(INTERNAL),
21314
21524
  "login-method": system(IDENTITY),
21315
21525
  "mesh-network": system(NETWORK),
@@ -43516,7 +43726,7 @@ var TargetKindCapsSchema = object({
43516
43726
  * the union is large and not meant for runtime validation here; the exported
43517
43727
  * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
43518
43728
  */
43519
- var ConfigSchemaPassthrough$3 = unknown();
43729
+ var ConfigSchemaPassthrough$4 = unknown();
43520
43730
  var TargetKindSchema = object({
43521
43731
  kind: string(),
43522
43732
  label: string(),
@@ -43553,7 +43763,7 @@ var TargetKindSchema = object({
43553
43763
  * its raster path, which is the safe default for an unknown image.
43554
43764
  */
43555
43765
  iconMediaType: string().optional(),
43556
- configSchema: ConfigSchemaPassthrough$3,
43766
+ configSchema: ConfigSchemaPassthrough$4,
43557
43767
  /**
43558
43768
  * The config keys that decide WHERE this kind's secrets are sent (server,
43559
43769
  * base URL, instance, chat, recipient). A save that changes any of them
@@ -46225,6 +46435,49 @@ var binaryCapability = {
46225
46435
  volatileStateFields: ["lastChangedAt"]
46226
46436
  };
46227
46437
  /**
46438
+ * Why a board key could not be read. Typed so a caller reports the cause and
46439
+ * never treats "could not read" as "has no key" (D49).
46440
+ */
46441
+ var BoardKeyFailureReasonSchema = _enum([
46442
+ "unknown-source",
46443
+ "not-admin",
46444
+ "no-builder",
46445
+ "no-ingress-url",
46446
+ "unreachable",
46447
+ "unknown-board",
46448
+ "no-encryption-key",
46449
+ "unreadable-config"
46450
+ ]);
46451
+ var BoardKeySourceSchema = object({
46452
+ sourceId: string(),
46453
+ name: string()
46454
+ });
46455
+ var BoardKeyBoardSchema = object({
46456
+ /** The builder's file name, e.g. `kitchen.yaml`: the handle `getBoardKey` takes. */
46457
+ configuration: string(),
46458
+ /** The node name the builder gives the board. */
46459
+ name: string(),
46460
+ /** The address the builder knows the board by, when it has one. */
46461
+ address: string().nullable()
46462
+ });
46463
+ var BoardKeyFailureSchema = object({
46464
+ ok: literal(false),
46465
+ reason: BoardKeyFailureReasonSchema,
46466
+ detail: string().optional()
46467
+ });
46468
+ var ListBoardsResultSchema = discriminatedUnion("ok", [object({
46469
+ ok: literal(true),
46470
+ boards: array(BoardKeyBoardSchema).readonly()
46471
+ }), BoardKeyFailureSchema]);
46472
+ var GetBoardKeyResultSchema = discriminatedUnion("ok", [object({
46473
+ ok: literal(true),
46474
+ key: string()
46475
+ }), BoardKeyFailureSchema]);
46476
+ method(_void(), array(BoardKeySourceSchema).readonly(), { auth: "admin" }), method(object({ sourceId: string().min(1) }), ListBoardsResultSchema, { auth: "admin" }), method(object({
46477
+ sourceId: string().min(1),
46478
+ configuration: string().min(1)
46479
+ }), GetBoardKeyResultSchema, { auth: "admin" });
46480
+ /**
46228
46481
  * Dimmable-light brightness control. Co-exists with `switch` on the
46229
46482
  * same device — the switch toggles on/off, this cap sets the level
46230
46483
  * applied when the light is on. Drivers map their per-vendor dim
@@ -48972,6 +49225,244 @@ var controlCapability = {
48972
49225
  */
48973
49226
  history: "none"
48974
49227
  };
49228
+ /**
49229
+ * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
49230
+ * Apple TV, Spotify, Roku, generic OTT receivers, …) on
49231
+ * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
49232
+ * surface — those use `DeviceType.Camera` + the broker pipeline.
49233
+ *
49234
+ * The cap declares a wide method surface; not every backend supports
49235
+ * every method. Drivers signal sub-capability via the
49236
+ * `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
49237
+ * corresponding method) so the UI gates buttons without a slice
49238
+ * fetch — same pattern as `PanTiltZoom` for cameras.
49239
+ */
49240
+ var MediaPlayerStateSchema = _enum([
49241
+ "off",
49242
+ "on",
49243
+ "idle",
49244
+ "playing",
49245
+ "paused",
49246
+ "buffering",
49247
+ "standby"
49248
+ ]);
49249
+ var MediaPlayerRepeatSchema = _enum([
49250
+ "off",
49251
+ "all",
49252
+ "one"
49253
+ ]);
49254
+ var MediaInfoSchema = object({
49255
+ /** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
49256
+ * `podcast`, …). */
49257
+ type: string(),
49258
+ /** Human-readable title. */
49259
+ title: string(),
49260
+ /** Optional artist / channel / station label. */
49261
+ artist: string().optional(),
49262
+ /** Optional album / season / show name. */
49263
+ album: string().optional(),
49264
+ /** Optional cover-art / thumbnail URL. */
49265
+ imageUrl: string().optional()
49266
+ });
49267
+ /**
49268
+ * Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
49269
+ * the device said so. `assumed`: the driver has no feedback (infrared) and
49270
+ * tracks what it last SENT; it moves only after a send was accepted, and a
49271
+ * human at the real remote makes it wrong. A UI shows an assumed value as a
49272
+ * guess, never as a reading.
49273
+ */
49274
+ var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
49275
+ /** The fields of a media-player status a driver can only guess. */
49276
+ var MediaPlayerAssumedFieldSchema = _enum([
49277
+ "state",
49278
+ "volumeLevel",
49279
+ "isMuted",
49280
+ "source"
49281
+ ]);
49282
+ var MediaPlayerStatusSchema = object({
49283
+ /** Playback lifecycle state. */
49284
+ state: MediaPlayerStateSchema,
49285
+ /**
49286
+ * Whether the state fields are readings or the driver's own bookkeeping.
49287
+ * A slice written before this field existed parses as `reported`.
49288
+ */
49289
+ stateSource: MediaPlayerStateSourceSchema.default("reported"),
49290
+ /**
49291
+ * Which fields are the driver's own guess. `stateSource` is `assumed` when
49292
+ * this is not empty; a field not listed here is a reading even then (power
49293
+ * from a running-state source, mute guessed from the last send).
49294
+ */
49295
+ assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
49296
+ /** Volume as 0..100 inclusive. Null when the device has no volume
49297
+ * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
49298
+ volumeLevel: number$1().min(0).max(100).nullable().meta(readingMeta("percentage")),
49299
+ /** Mute toggle distinct from volume=0. Null when no mute surface.
49300
+ * Pair with `DeviceFeature.MediaPlayerMute`. */
49301
+ isMuted: boolean().nullable(),
49302
+ /** Active source / input. Empty when no source surface. */
49303
+ source: string(),
49304
+ /** Selectable sources. Empty when no source surface.
49305
+ * Pair with `DeviceFeature.MediaPlayerSelectSource`. */
49306
+ availableSources: array(string()),
49307
+ /** Currently-playing media info. Null when nothing is playing. */
49308
+ currentMedia: MediaInfoSchema.nullable(),
49309
+ /** Current playback position in ms. Null when not seekable or
49310
+ * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
49311
+ position: number$1().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
49312
+ /** Total duration of the current media in ms. Null when unknown
49313
+ * (live stream) or nothing is playing. */
49314
+ duration: number$1().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
49315
+ /** Shuffle toggle. Null when no shuffle surface.
49316
+ * Pair with `DeviceFeature.MediaPlayerShuffle`. */
49317
+ shuffle: boolean().nullable(),
49318
+ /** Repeat mode. Null when no repeat surface.
49319
+ * Pair with `DeviceFeature.MediaPlayerRepeat`. */
49320
+ repeat: MediaPlayerRepeatSchema.nullable(),
49321
+ /** Ms epoch when the slice was last updated. */
49322
+ lastChangedAt: number$1()
49323
+ });
49324
+ var stepInput = object({
49325
+ deviceId: number$1().int().nonnegative(),
49326
+ /** How many presses: the caller's count (Alexa `volumeSteps`, `channelCount`). */
49327
+ steps: number$1().int().min(1).max(50).default(1)
49328
+ });
49329
+ var deviceOnly = object({ deviceId: number$1().int().nonnegative() });
49330
+ var mediaPlayerCapability = {
49331
+ name: "media-player",
49332
+ scope: "device",
49333
+ deviceNative: true,
49334
+ mode: "singleton",
49335
+ deviceTypes: [DeviceType.MediaPlayer],
49336
+ methods: {
49337
+ turnOn: method(deviceOnly, _void(), {
49338
+ kind: "mutation",
49339
+ auth: "protected"
49340
+ }),
49341
+ turnOff: method(deviceOnly, _void(), {
49342
+ kind: "mutation",
49343
+ auth: "protected"
49344
+ }),
49345
+ volumeUp: method(stepInput, _void(), {
49346
+ kind: "mutation",
49347
+ auth: "protected"
49348
+ }),
49349
+ volumeDown: method(stepInput, _void(), {
49350
+ kind: "mutation",
49351
+ auth: "protected"
49352
+ }),
49353
+ channelUp: method(stepInput, _void(), {
49354
+ kind: "mutation",
49355
+ auth: "protected"
49356
+ }),
49357
+ channelDown: method(stepInput, _void(), {
49358
+ kind: "mutation",
49359
+ auth: "protected"
49360
+ }),
49361
+ play: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
49362
+ kind: "mutation",
49363
+ auth: "protected"
49364
+ }),
49365
+ pause: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
49366
+ kind: "mutation",
49367
+ auth: "protected"
49368
+ }),
49369
+ stop: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
49370
+ kind: "mutation",
49371
+ auth: "protected"
49372
+ }),
49373
+ next: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
49374
+ kind: "mutation",
49375
+ auth: "protected"
49376
+ }),
49377
+ previous: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
49378
+ kind: "mutation",
49379
+ auth: "protected"
49380
+ }),
49381
+ seek: method(object({
49382
+ deviceId: number$1().int().nonnegative(),
49383
+ position: number$1().int().nonnegative()
49384
+ }), _void(), {
49385
+ kind: "mutation",
49386
+ auth: "protected"
49387
+ }),
49388
+ setVolume: method(object({
49389
+ deviceId: number$1().int().nonnegative(),
49390
+ volumeLevel: number$1().min(0).max(100)
49391
+ }), _void(), {
49392
+ kind: "mutation",
49393
+ auth: "protected"
49394
+ }),
49395
+ setMute: method(object({
49396
+ deviceId: number$1().int().nonnegative(),
49397
+ muted: boolean()
49398
+ }), _void(), {
49399
+ kind: "mutation",
49400
+ auth: "protected"
49401
+ }),
49402
+ setShuffle: method(object({
49403
+ deviceId: number$1().int().nonnegative(),
49404
+ shuffle: boolean()
49405
+ }), _void(), {
49406
+ kind: "mutation",
49407
+ auth: "protected"
49408
+ }),
49409
+ setRepeat: method(object({
49410
+ deviceId: number$1().int().nonnegative(),
49411
+ repeat: MediaPlayerRepeatSchema
49412
+ }), _void(), {
49413
+ kind: "mutation",
49414
+ auth: "protected"
49415
+ }),
49416
+ selectSource: method(object({
49417
+ deviceId: number$1().int().nonnegative(),
49418
+ source: string().min(1)
49419
+ }), _void(), {
49420
+ kind: "mutation",
49421
+ auth: "protected"
49422
+ }),
49423
+ playMedia: method(object({
49424
+ deviceId: number$1().int().nonnegative(),
49425
+ /** Media identifier / URL. */
49426
+ mediaId: string().min(1),
49427
+ /** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
49428
+ * passes it through to the upstream service. */
49429
+ mediaType: string().min(1)
49430
+ }), _void(), {
49431
+ kind: "mutation",
49432
+ auth: "protected"
49433
+ })
49434
+ },
49435
+ status: {
49436
+ schema: MediaPlayerStatusSchema,
49437
+ kind: "push"
49438
+ },
49439
+ /**
49440
+ * Runtime-state slice — mirrored by the kernel. UI players read the
49441
+ * full slice for live now-playing, volume, and progress updates
49442
+ * without polling.
49443
+ */
49444
+ runtimeState: MediaPlayerStatusSchema,
49445
+ /**
49446
+ * Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
49447
+ *
49448
+ * See `RuntimeStateDurability`. Enforced by
49449
+ * `scripts/check-runtime-state-durability.ts`.
49450
+ */
49451
+ durability: "session",
49452
+ /**
49453
+ * History (D850): volume (a level an operator or an automation sets; the
49454
+ * default 10 s floor folds a slider drag into its end value) and mute.
49455
+ * `position` is not recorded: it moves continuously while playing.
49456
+ */
49457
+ history: { fields: {
49458
+ volumeLevel: {
49459
+ kind: "gauge",
49460
+ unit: "%",
49461
+ deadband: 1
49462
+ },
49463
+ isMuted: { kind: "boolean" }
49464
+ } }
49465
+ };
48975
49466
  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.Heater, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Fan, DeviceType.Lock, 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.Heater, 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.Heater, 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.Heater, 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.Heater, DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, 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, DeviceType.Heater;
48976
49467
  var NUMBER = {
48977
49468
  kind: "fixed",
@@ -51258,6 +51749,45 @@ var deviceProviderCapability = {
51258
51749
  }
51259
51750
  };
51260
51751
  /**
51752
+ * What the passive listener of `local-network` saw on the wire, before any
51753
+ * provider classified it. Device-manager turns an observation into the hits
51754
+ * of the providers whose declared discovery modes it matches.
51755
+ */
51756
+ var DiscoveryObservationSchema = discriminatedUnion("kind", [object({
51757
+ kind: literal("mdns"),
51758
+ /** Normalised DNS-SD service type, e.g. `_esphomelib._tcp`. */
51759
+ serviceType: string(),
51760
+ instance: string(),
51761
+ host: string(),
51762
+ /** The A record address when the announcement carried one. */
51763
+ address: string().nullable(),
51764
+ port: number$1().int(),
51765
+ txt: record(string(), string())
51766
+ }), object({
51767
+ kind: literal("ssdp"),
51768
+ /** Sender address of the datagram. */
51769
+ host: string(),
51770
+ port: number$1().int(),
51771
+ /** `NT` of a NOTIFY, `ST` of a search response. */
51772
+ target: string(),
51773
+ headers: record(string(), string())
51774
+ })]);
51775
+ object({
51776
+ addonId: string(),
51777
+ candidate: DiscoveryCandidateSchema,
51778
+ firstSeenAt: number$1(),
51779
+ lastSeenAt: number$1(),
51780
+ dismissed: boolean()
51781
+ });
51782
+ var DiscoveryProposalSchema = object({
51783
+ id: string(),
51784
+ addonId: string(),
51785
+ candidate: DiscoveryCandidateSchema,
51786
+ firstSeenAt: number$1(),
51787
+ lastSeenAt: number$1(),
51788
+ dismissed: boolean()
51789
+ });
51790
+ /**
51261
51791
  * Device Manager capability — hub-side singleton that unifies device persistence,
51262
51792
  * live registry access, and all management operations into a single tRPC surface.
51263
51793
  *
@@ -51986,6 +52516,19 @@ method(object({
51986
52516
  }), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
51987
52517
  kind: "mutation",
51988
52518
  auth: "admin"
52519
+ }), method(object({}), object({ proposals: array(DiscoveryProposalSchema).readonly() }), { auth: "admin" }), method(object({ id: string() }), object({ dismissed: boolean() }), {
52520
+ kind: "mutation",
52521
+ auth: "admin"
52522
+ }), method(object({ observations: array(DiscoveryObservationSchema).max(512) }), object({ proposed: number$1().int() }), {
52523
+ kind: "mutation",
52524
+ auth: "admin"
52525
+ }), method(object({}), object({
52526
+ providers: number$1().int(),
52527
+ proposed: number$1().int()
52528
+ }), {
52529
+ kind: "mutation",
52530
+ auth: "admin",
52531
+ timeoutMs: 12e4
51989
52532
  }), method(object({ addonId: string() }), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
51990
52533
  deviceId: number$1(),
51991
52534
  key: string(),
@@ -52842,7 +53385,7 @@ var DishwasherStatusSchema = object({
52842
53385
  /** Ms epoch of the last value received from the appliance; 0 = never. */
52843
53386
  lastFetchedAt: number$1()
52844
53387
  });
52845
- var DeviceIdSchema$1 = number$1().int().nonnegative();
53388
+ var DeviceIdSchema$2 = number$1().int().nonnegative();
52846
53389
  /** A value a program option or setting can take. */
52847
53390
  var DishwasherValueSchema = union([
52848
53391
  boolean(),
@@ -52917,77 +53460,77 @@ var dishwasherCapability = {
52917
53460
  * option feature keys; each is checked against the program's constraints.
52918
53461
  */
52919
53462
  start: method(object({
52920
- deviceId: DeviceIdSchema$1,
53463
+ deviceId: DeviceIdSchema$2,
52921
53464
  program: string().min(1).optional(),
52922
53465
  options: record(string(), DishwasherValueSchema).optional()
52923
53466
  }), _void(), {
52924
53467
  kind: "mutation",
52925
53468
  auth: "protected"
52926
53469
  }),
52927
- pause: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
53470
+ pause: method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
52928
53471
  kind: "mutation",
52929
53472
  auth: "protected"
52930
53473
  }),
52931
- resume: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
53474
+ resume: method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
52932
53475
  kind: "mutation",
52933
53476
  auth: "protected"
52934
53477
  }),
52935
- stop: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
53478
+ stop: method(object({ deviceId: DeviceIdSchema$2 }), _void(), {
52936
53479
  kind: "mutation",
52937
53480
  auth: "protected"
52938
53481
  }),
52939
53482
  /** Select (not start) a program; refused when the appliance does not offer it. */
52940
53483
  setProgram: method(object({
52941
- deviceId: DeviceIdSchema$1,
53484
+ deviceId: DeviceIdSchema$2,
52942
53485
  program: string().min(1)
52943
53486
  }), _void(), {
52944
53487
  kind: "mutation",
52945
53488
  auth: "protected"
52946
53489
  }),
52947
53490
  setPower: method(object({
52948
- deviceId: DeviceIdSchema$1,
53491
+ deviceId: DeviceIdSchema$2,
52949
53492
  on: boolean()
52950
53493
  }), _void(), {
52951
53494
  kind: "mutation",
52952
53495
  auth: "protected"
52953
53496
  }),
52954
53497
  setHalfLoad: method(object({
52955
- deviceId: DeviceIdSchema$1,
53498
+ deviceId: DeviceIdSchema$2,
52956
53499
  on: boolean()
52957
53500
  }), _void(), {
52958
53501
  kind: "mutation",
52959
53502
  auth: "protected"
52960
53503
  }),
52961
53504
  setHygienePlus: method(object({
52962
- deviceId: DeviceIdSchema$1,
53505
+ deviceId: DeviceIdSchema$2,
52963
53506
  on: boolean()
52964
53507
  }), _void(), {
52965
53508
  kind: "mutation",
52966
53509
  auth: "protected"
52967
53510
  }),
52968
53511
  setExtraDry: method(object({
52969
- deviceId: DeviceIdSchema$1,
53512
+ deviceId: DeviceIdSchema$2,
52970
53513
  on: boolean()
52971
53514
  }), _void(), {
52972
53515
  kind: "mutation",
52973
53516
  auth: "protected"
52974
53517
  }),
52975
53518
  setIntensiveZone: method(object({
52976
- deviceId: DeviceIdSchema$1,
53519
+ deviceId: DeviceIdSchema$2,
52977
53520
  on: boolean()
52978
53521
  }), _void(), {
52979
53522
  kind: "mutation",
52980
53523
  auth: "protected"
52981
53524
  }),
52982
53525
  setVarioSpeedPlus: method(object({
52983
- deviceId: DeviceIdSchema$1,
53526
+ deviceId: DeviceIdSchema$2,
52984
53527
  on: boolean()
52985
53528
  }), _void(), {
52986
53529
  kind: "mutation",
52987
53530
  auth: "protected"
52988
53531
  }),
52989
53532
  setChildLock: method(object({
52990
- deviceId: DeviceIdSchema$1,
53533
+ deviceId: DeviceIdSchema$2,
52991
53534
  on: boolean()
52992
53535
  }), _void(), {
52993
53536
  kind: "mutation",
@@ -52999,7 +53542,7 @@ var dishwasherCapability = {
52999
53542
  * its declared range / members.
53000
53543
  */
53001
53544
  setOption: method(object({
53002
- deviceId: DeviceIdSchema$1,
53545
+ deviceId: DeviceIdSchema$2,
53003
53546
  key: string().min(1),
53004
53547
  value: DishwasherValueSchema
53005
53548
  }), _void(), {
@@ -53007,7 +53550,7 @@ var dishwasherCapability = {
53007
53550
  auth: "protected"
53008
53551
  }),
53009
53552
  /** What this appliance can do, from its own description — the UI is derived from it. */
53010
- getOptions: method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" })
53553
+ getOptions: method(object({ deviceId: DeviceIdSchema$2 }), DishwasherOptionsSchema, { auth: "protected" })
53011
53554
  },
53012
53555
  status: {
53013
53556
  schema: DishwasherStatusSchema,
@@ -55330,6 +55873,89 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
55330
55873
  auth: "admin"
55331
55874
  });
55332
55875
  /**
55876
+ * `integration-instances` - the connection of an addon that holds ONE or SEVERAL
55877
+ * (an account, a relay, a destination) as typed records (D985, D986). The addon's
55878
+ * own function page reads and writes them (Logging for Loki); the Integrations
55879
+ * page lists only the integrations that bring devices (D996).
55880
+ *
55881
+ * A collection of providers: each addon that holds connections registers one and a
55882
+ * caller reaches a provider with the pinned `addonId`.
55883
+ *
55884
+ * Identity: the instance id is immutable and opaque to the caller. A record the
55885
+ * Loki migration made keeps the `integrations` registry id it was created under
55886
+ * (`int_NNNN`); a single instance made by its function page takes the addon id.
55887
+ * `name` is the editable label.
55888
+ *
55889
+ * Secrets: every field the instance kind marks `.meta({ secret: true })` reaches
55890
+ * the form as a `SecretFieldView`, never text (D789). A patch value omitted, `''`
55891
+ * or the redaction token KEEPS the stored secret; `{ $secretClear: true }`
55892
+ * clears it.
55893
+ */
55894
+ var ConfigSchemaPassthrough$3 = custom();
55895
+ var IntegrationInstanceIdSchema = string().min(1);
55896
+ var IntegrationInstanceDescriptionSchema = object({
55897
+ /** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
55898
+ instanceMode: _enum(["single", "multiple"]),
55899
+ /** What the instance is, for the UI (`destination`, `relay`, `account`). */
55900
+ kind: string(),
55901
+ /** True when `testInstance` really probes the remote. */
55902
+ canTest: boolean()
55903
+ });
55904
+ var IntegrationInstanceStatusSchema = object({
55905
+ state: _enum([
55906
+ "ok",
55907
+ "off",
55908
+ "error",
55909
+ "unknown"
55910
+ ]),
55911
+ detail: string().optional()
55912
+ });
55913
+ var IntegrationInstanceSchema = object({
55914
+ id: IntegrationInstanceIdSchema,
55915
+ name: string(),
55916
+ /** The addon's own rule (an URL is present, a client id is present...). */
55917
+ configured: boolean(),
55918
+ /**
55919
+ * The instance's own on/off switch (D996): always present, derived from the record body
55920
+ * (`enabled !== false`, so a row written before the field existed is on).
55921
+ */
55922
+ enabled: boolean(),
55923
+ /** Values for the instance form; a secret is a `SecretFieldView`, never text. */
55924
+ formValues: record(string(), unknown()),
55925
+ status: IntegrationInstanceStatusSchema.optional()
55926
+ });
55927
+ var IntegrationInstancePatchSchema = object({
55928
+ id: IntegrationInstanceIdSchema,
55929
+ name: string().min(1).optional(),
55930
+ /** Omitted / '' / redaction token keeps a secret; `{ $secretClear: true }` clears it. */
55931
+ values: record(string(), unknown())
55932
+ });
55933
+ method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }), method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }), method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }), method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }), method(object({
55934
+ id: IntegrationInstanceIdSchema,
55935
+ name: string().min(1)
55936
+ }), object({ id: string() }), {
55937
+ kind: "mutation",
55938
+ auth: "admin"
55939
+ }), method(IntegrationInstancePatchSchema, object({ ok: literal(true) }), {
55940
+ kind: "mutation",
55941
+ auth: "admin"
55942
+ }), method(object({
55943
+ id: IntegrationInstanceIdSchema,
55944
+ enabled: boolean()
55945
+ }), object({ ok: literal(true) }), {
55946
+ kind: "mutation",
55947
+ auth: "admin"
55948
+ }), method(object({ id: IntegrationInstanceIdSchema }), object({ ok: literal(true) }), {
55949
+ kind: "mutation",
55950
+ auth: "admin"
55951
+ }), optionalMethod(object({
55952
+ id: IntegrationInstanceIdSchema.nullable(),
55953
+ values: record(string(), unknown())
55954
+ }), TestConnectionResultSchema$1, {
55955
+ kind: "mutation",
55956
+ auth: "admin"
55957
+ });
55958
+ /**
55333
55959
  * Two-way audio / talk-back cap. Clients open a WebRTC audio-only
55334
55960
  * session (server-initiated offer pattern, identical to the main
55335
55961
  * video WebRTC flow) and the browser captures the mic; the server
@@ -57530,214 +58156,6 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
57530
58156
  ]);
57531
58157
  method(_void(), array(LoginMethodContributionSchema).readonly());
57532
58158
  /**
57533
- * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
57534
- * Apple TV, Spotify, Roku, generic OTT receivers, …) on
57535
- * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
57536
- * surface — those use `DeviceType.Camera` + the broker pipeline.
57537
- *
57538
- * The cap declares a wide method surface; not every backend supports
57539
- * every method. Drivers signal sub-capability via the
57540
- * `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
57541
- * corresponding method) so the UI gates buttons without a slice
57542
- * fetch — same pattern as `PanTiltZoom` for cameras.
57543
- */
57544
- var MediaPlayerStateSchema = _enum([
57545
- "off",
57546
- "on",
57547
- "idle",
57548
- "playing",
57549
- "paused",
57550
- "buffering",
57551
- "standby"
57552
- ]);
57553
- var MediaPlayerRepeatSchema = _enum([
57554
- "off",
57555
- "all",
57556
- "one"
57557
- ]);
57558
- var MediaInfoSchema = object({
57559
- /** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
57560
- * `podcast`, …). */
57561
- type: string(),
57562
- /** Human-readable title. */
57563
- title: string(),
57564
- /** Optional artist / channel / station label. */
57565
- artist: string().optional(),
57566
- /** Optional album / season / show name. */
57567
- album: string().optional(),
57568
- /** Optional cover-art / thumbnail URL. */
57569
- imageUrl: string().optional()
57570
- });
57571
- /**
57572
- * Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
57573
- * the device said so. `assumed`: the driver has no feedback (infrared) and
57574
- * tracks what it last SENT; it moves only after a send was accepted, and a
57575
- * human at the real remote makes it wrong. A UI shows an assumed value as a
57576
- * guess, never as a reading.
57577
- */
57578
- var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
57579
- /** The fields of a media-player status a driver can only guess. */
57580
- var MediaPlayerAssumedFieldSchema = _enum([
57581
- "state",
57582
- "volumeLevel",
57583
- "isMuted",
57584
- "source"
57585
- ]);
57586
- var MediaPlayerStatusSchema = object({
57587
- /** Playback lifecycle state. */
57588
- state: MediaPlayerStateSchema,
57589
- /**
57590
- * Whether the state fields are readings or the driver's own bookkeeping.
57591
- * A slice written before this field existed parses as `reported`.
57592
- */
57593
- stateSource: MediaPlayerStateSourceSchema.default("reported"),
57594
- /**
57595
- * Which fields are the driver's own guess. `stateSource` is `assumed` when
57596
- * this is not empty; a field not listed here is a reading even then (power
57597
- * from a running-state source, mute guessed from the last send).
57598
- */
57599
- assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
57600
- /** Volume as 0..100 inclusive. Null when the device has no volume
57601
- * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
57602
- volumeLevel: number$1().min(0).max(100).nullable().meta(readingMeta("percentage")),
57603
- /** Mute toggle distinct from volume=0. Null when no mute surface.
57604
- * Pair with `DeviceFeature.MediaPlayerMute`. */
57605
- isMuted: boolean().nullable(),
57606
- /** Active source / input. Empty when no source surface. */
57607
- source: string(),
57608
- /** Selectable sources. Empty when no source surface.
57609
- * Pair with `DeviceFeature.MediaPlayerSelectSource`. */
57610
- availableSources: array(string()),
57611
- /** Currently-playing media info. Null when nothing is playing. */
57612
- currentMedia: MediaInfoSchema.nullable(),
57613
- /** Current playback position in ms. Null when not seekable or
57614
- * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
57615
- position: number$1().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
57616
- /** Total duration of the current media in ms. Null when unknown
57617
- * (live stream) or nothing is playing. */
57618
- duration: number$1().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
57619
- /** Shuffle toggle. Null when no shuffle surface.
57620
- * Pair with `DeviceFeature.MediaPlayerShuffle`. */
57621
- shuffle: boolean().nullable(),
57622
- /** Repeat mode. Null when no repeat surface.
57623
- * Pair with `DeviceFeature.MediaPlayerRepeat`. */
57624
- repeat: MediaPlayerRepeatSchema.nullable(),
57625
- /** Ms epoch when the slice was last updated. */
57626
- lastChangedAt: number$1()
57627
- });
57628
- var mediaPlayerCapability = {
57629
- name: "media-player",
57630
- scope: "device",
57631
- deviceNative: true,
57632
- mode: "singleton",
57633
- deviceTypes: [DeviceType.MediaPlayer],
57634
- methods: {
57635
- play: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
57636
- kind: "mutation",
57637
- auth: "protected"
57638
- }),
57639
- pause: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
57640
- kind: "mutation",
57641
- auth: "protected"
57642
- }),
57643
- stop: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
57644
- kind: "mutation",
57645
- auth: "protected"
57646
- }),
57647
- next: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
57648
- kind: "mutation",
57649
- auth: "protected"
57650
- }),
57651
- previous: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
57652
- kind: "mutation",
57653
- auth: "protected"
57654
- }),
57655
- seek: method(object({
57656
- deviceId: number$1().int().nonnegative(),
57657
- position: number$1().int().nonnegative()
57658
- }), _void(), {
57659
- kind: "mutation",
57660
- auth: "protected"
57661
- }),
57662
- setVolume: method(object({
57663
- deviceId: number$1().int().nonnegative(),
57664
- volumeLevel: number$1().min(0).max(100)
57665
- }), _void(), {
57666
- kind: "mutation",
57667
- auth: "protected"
57668
- }),
57669
- setMute: method(object({
57670
- deviceId: number$1().int().nonnegative(),
57671
- muted: boolean()
57672
- }), _void(), {
57673
- kind: "mutation",
57674
- auth: "protected"
57675
- }),
57676
- setShuffle: method(object({
57677
- deviceId: number$1().int().nonnegative(),
57678
- shuffle: boolean()
57679
- }), _void(), {
57680
- kind: "mutation",
57681
- auth: "protected"
57682
- }),
57683
- setRepeat: method(object({
57684
- deviceId: number$1().int().nonnegative(),
57685
- repeat: MediaPlayerRepeatSchema
57686
- }), _void(), {
57687
- kind: "mutation",
57688
- auth: "protected"
57689
- }),
57690
- selectSource: method(object({
57691
- deviceId: number$1().int().nonnegative(),
57692
- source: string().min(1)
57693
- }), _void(), {
57694
- kind: "mutation",
57695
- auth: "protected"
57696
- }),
57697
- playMedia: method(object({
57698
- deviceId: number$1().int().nonnegative(),
57699
- /** Media identifier / URL. */
57700
- mediaId: string().min(1),
57701
- /** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
57702
- * passes it through to the upstream service. */
57703
- mediaType: string().min(1)
57704
- }), _void(), {
57705
- kind: "mutation",
57706
- auth: "protected"
57707
- })
57708
- },
57709
- status: {
57710
- schema: MediaPlayerStatusSchema,
57711
- kind: "push"
57712
- },
57713
- /**
57714
- * Runtime-state slice — mirrored by the kernel. UI players read the
57715
- * full slice for live now-playing, volume, and progress updates
57716
- * without polling.
57717
- */
57718
- runtimeState: MediaPlayerStatusSchema,
57719
- /**
57720
- * Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
57721
- *
57722
- * See `RuntimeStateDurability`. Enforced by
57723
- * `scripts/check-runtime-state-durability.ts`.
57724
- */
57725
- durability: "session",
57726
- /**
57727
- * History (D850): volume (a level an operator or an automation sets; the
57728
- * default 10 s floor folds a slider drag into its end value) and mute.
57729
- * `position` is not recorded: it moves continuously while playing.
57730
- */
57731
- history: { fields: {
57732
- volumeLevel: {
57733
- kind: "gauge",
57734
- unit: "%",
57735
- deadband: 1
57736
- },
57737
- isMuted: { kind: "boolean" }
57738
- } }
57739
- };
57740
- /**
57741
58159
  * mesh-network — collection cap for mesh-VPN providers.
57742
58160
  *
57743
58161
  * Different shape from `network-access` (public tunnels):
@@ -66097,7 +66515,14 @@ var NcMediaPolicySchema = object({
66097
66515
  */
66098
66516
  profile: CamProfileSchema.optional(),
66099
66517
  /** See {@link NcLinkedCameraModeSchema}. */
66100
- linkedCameras: NcLinkedCameraModeSchema.optional()
66518
+ linkedCameras: NcLinkedCameraModeSchema.optional(),
66519
+ /**
66520
+ * Sensor rules only: may this rule wake a SLEEPING battery camera to take
66521
+ * the linked still it attaches (D998)? Absent means yes, an explicit `false`
66522
+ * opts out. Deliberately not a Zod default: a rule that never chose keeps
66523
+ * following the hub's. One bounded wake per event; nothing is attached later.
66524
+ */
66525
+ wakeSleepingCamera: boolean().optional()
66101
66526
  });
66102
66527
  /**
66103
66528
  * Cooldown GRANULARITY over the subject's class — how much a fired
@@ -66489,6 +66914,21 @@ var NcTemplateVarKindSchema = _enum([
66489
66914
  "sound",
66490
66915
  "system"
66491
66916
  ]);
66917
+ /**
66918
+ * The default title/body a rule of one SITUATION is delivered with when the
66919
+ * operator wrote no template — served so an editor can pre-fill it (and the
66920
+ * operator can rebuild it from placeholders) instead of starting from a blank
66921
+ * box that, once saved, silently replaces it. `id` names the situation inside
66922
+ * a rule `kind`: `appliance-cycle` is a `sensor` rule whose device is an
66923
+ * appliance finishing a cycle. Texts are raw templates (`{{var}}` intact), in
66924
+ * the hub's current language and with its overrides applied.
66925
+ */
66926
+ var NcTemplateDefaultSchema = object({
66927
+ id: _enum(["appliance-cycle"]),
66928
+ kind: NcRuleKindSchema,
66929
+ title: string(),
66930
+ body: string()
66931
+ });
66492
66932
  /** Which producer renders a template: an ordinary rule, a timelapse, a digest. */
66493
66933
  var NcTemplateFamilySchema = _enum([
66494
66934
  "rule",
@@ -67344,7 +67784,11 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
67344
67784
  }), method(object({}), object({
67345
67785
  catalog: array(NcConditionDescriptorSchema),
67346
67786
  taxonomy: NcTaxonomySchema.optional()
67347
- })), method(object({}), object({ vars: array(NcTemplateVarDescriptorSchema) })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
67787
+ })), method(object({}), object({
67788
+ vars: array(NcTemplateVarDescriptorSchema),
67789
+ /** Per-situation default texts; absent on a hub that predates them. */
67790
+ defaults: array(NcTemplateDefaultSchema)
67791
+ })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
67348
67792
  kind: "mutation",
67349
67793
  access: "view"
67350
67794
  }), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), NcRuleLastRunsSchema, { auth: "admin" }), method(object({ artifactId: string().min(1) }), object({ url: string().nullable() }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
@@ -68240,7 +68684,7 @@ var petFeederCapability = {
68240
68684
  /**
68241
68685
  * Pet water-fountain cap. Models the telemetry and the control surface of a
68242
68686
  * circulating drinking fountain: how much water is in the tank, how much the
68243
- * pet drank, whether the pump runs and on what duty cycle.
68687
+ * pet drank, whether the pump runs.
68244
68688
  *
68245
68689
  * It has no native vendor provider of its own: a fountain reaches CamStack as
68246
68690
  * a container of loose entities (a pump switch, two number boxes, a "filter
@@ -68256,8 +68700,10 @@ var petFeederCapability = {
68256
68700
  * never here. Every reading may be `null`: a source that is offline or does
68257
68701
  * not report is unknown, never zero.
68258
68702
  *
68259
- * The filter is not a field of this cap: it is an item of the `consumables`
68260
- * cap on the same device (kind `wear`), reset by the same forward mechanism.
68703
+ * The pump's on/off duty cycle is model-specific and lives in the optional
68704
+ * `pump-cycle` cap. The filter, the cleaning and the water change are not
68705
+ * fields of this cap: they are items of the `consumables` cap on the same
68706
+ * device (kind `wear`), reset by the same forward mechanism.
68261
68707
  */
68262
68708
  var PetFountainStatusSchema = object({
68263
68709
  /** Water in the tank, 0..100 (%). Null while the source has not reported. */
@@ -68268,27 +68714,20 @@ var PetFountainStatusSchema = object({
68268
68714
  empty: boolean().nullable(),
68269
68715
  /** True while the pump is running. Null while unknown. */
68270
68716
  pumpRunning: boolean().nullable(),
68271
- /** True when the pump works on an on/off duty cycle rather than continuously. */
68272
- pumpIntervalEnabled: boolean().nullable(),
68273
- /** How long the pump runs per cycle, in seconds. */
68274
- pumpOnTime: number$1().min(0).nullable().meta(readingMeta("duration")),
68275
- /** How long the pump rests per cycle, in seconds. */
68276
- pumpOffTime: number$1().min(0).nullable().meta(readingMeta("duration")),
68277
68717
  /** Water drunk in the last drink, in litres. */
68278
68718
  lastDrink: number$1().min(0).nullable().meta(readingMeta("volume")),
68279
68719
  /** Water drunk in the current hour, in litres. */
68280
68720
  currentHourDrink: number$1().min(0).nullable().meta(readingMeta("volume")),
68281
68721
  /** Water drunk over the last 24 hours (rolling), in litres. */
68282
68722
  last24hDrink: number$1().min(0).nullable().meta(readingMeta("volume")),
68283
- /** Water drunk since local midnight, in litres. */
68723
+ /** Water drunk since local midnight, in litres. Null when the fountain has no such sensor. */
68284
68724
  todayDrink: number$1().min(0).nullable().meta(readingMeta("volume")),
68285
68725
  /** Decoded fault message. Null means healthy; a non-null string is an active fault. */
68286
68726
  error: string().nullable(),
68287
68727
  /** Ms epoch when the slice was last refreshed. */
68288
68728
  lastFetchedAt: number$1()
68289
68729
  });
68290
- var DeviceIdSchema = number$1().int().nonnegative();
68291
- var MinutesSchema = number$1().int().min(0);
68730
+ var DeviceIdSchema$1 = number$1().int().nonnegative();
68292
68731
  var petFountainCapability = {
68293
68732
  name: "pet-fountain",
68294
68733
  scope: "device",
@@ -68298,38 +68737,14 @@ var petFountainCapability = {
68298
68737
  methods: {
68299
68738
  /** Start or stop the pump. */
68300
68739
  setPump: method(object({
68301
- deviceId: DeviceIdSchema,
68302
- on: boolean()
68303
- }), _void(), {
68304
- kind: "mutation",
68305
- auth: "protected"
68306
- }),
68307
- /** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
68308
- setPumpInterval: method(object({
68309
- deviceId: DeviceIdSchema,
68740
+ deviceId: DeviceIdSchema$1,
68310
68741
  on: boolean()
68311
68742
  }), _void(), {
68312
68743
  kind: "mutation",
68313
68744
  auth: "protected"
68314
68745
  }),
68315
- /** Set how many minutes the pump runs per cycle. */
68316
- setPumpOnMinutes: method(object({
68317
- deviceId: DeviceIdSchema,
68318
- minutes: MinutesSchema
68319
- }), _void(), {
68320
- kind: "mutation",
68321
- auth: "protected"
68322
- }),
68323
- /** Set how many minutes the pump rests per cycle. */
68324
- setPumpOffMinutes: method(object({
68325
- deviceId: DeviceIdSchema,
68326
- minutes: MinutesSchema
68327
- }), _void(), {
68328
- kind: "mutation",
68329
- auth: "protected"
68330
- }),
68331
68746
  /** Clear the rolling daily consumption counters on the device. */
68332
- resetDailyConsumption: method(object({ deviceId: DeviceIdSchema }), _void(), {
68747
+ resetDailyConsumption: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
68333
68748
  kind: "mutation",
68334
68749
  auth: "protected"
68335
68750
  })
@@ -70662,6 +71077,79 @@ var ptzAutotrackCapability = {
70662
71077
  */
70663
71078
  history: "none"
70664
71079
  };
71080
+ /**
71081
+ * The on/off duty cycle of a pet fountain's pump. Some models run the pump
71082
+ * continuously and have nothing to configure; others can alternate a run time
71083
+ * and a rest time. That is model-specific, so it is its own optional cap on the
71084
+ * fountain device: a fountain without cycle entities simply never composes it
71085
+ * (planning#366), and the `pet-fountain` cap stays the model-independent core.
71086
+ *
71087
+ * Like `pet-fountain` it has no native vendor provider: an operator BUILDS the
71088
+ * cycle from loose entities (a cycle switch and two number boxes) and every
71089
+ * method is a forward to one source entity (D659, D967). Durations are stored
71090
+ * in SECONDS (D944); a number box that cannot be read back leaves them `null`
71091
+ * — unknown, never zero (D393).
71092
+ */
71093
+ var PumpCycleStatusSchema = object({
71094
+ /** True when the pump works on the on/off cycle, false when it runs continuously. Null while unknown. */
71095
+ enabled: boolean().nullable(),
71096
+ /** How long the pump runs per cycle, in seconds. Null while unknown. */
71097
+ onTime: number$1().min(0).nullable().meta(readingMeta("duration")),
71098
+ /** How long the pump rests per cycle, in seconds. Null while unknown. */
71099
+ offTime: number$1().min(0).nullable().meta(readingMeta("duration")),
71100
+ /** Ms epoch when the slice was last refreshed. */
71101
+ lastFetchedAt: number$1()
71102
+ });
71103
+ var DeviceIdSchema = number$1().int().nonnegative();
71104
+ var MinutesSchema = number$1().int().min(0);
71105
+ var pumpCycleCapability = {
71106
+ name: "pump-cycle",
71107
+ scope: "device",
71108
+ deviceNative: true,
71109
+ mode: "singleton",
71110
+ deviceTypes: [DeviceType.PetFountain],
71111
+ methods: {
71112
+ /** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
71113
+ setEnabled: method(object({
71114
+ deviceId: DeviceIdSchema,
71115
+ on: boolean()
71116
+ }), _void(), {
71117
+ kind: "mutation",
71118
+ auth: "protected"
71119
+ }),
71120
+ /** Set how many minutes the pump runs per cycle. */
71121
+ setOnMinutes: method(object({
71122
+ deviceId: DeviceIdSchema,
71123
+ minutes: MinutesSchema
71124
+ }), _void(), {
71125
+ kind: "mutation",
71126
+ auth: "protected"
71127
+ }),
71128
+ /** Set how many minutes the pump rests per cycle. */
71129
+ setOffMinutes: method(object({
71130
+ deviceId: DeviceIdSchema,
71131
+ minutes: MinutesSchema
71132
+ }), _void(), {
71133
+ kind: "mutation",
71134
+ auth: "protected"
71135
+ })
71136
+ },
71137
+ status: {
71138
+ schema: PumpCycleStatusSchema,
71139
+ kind: "push"
71140
+ },
71141
+ /** Runtime-state slice — mirrored by the kernel; the hero card reads it whole. */
71142
+ runtimeState: PumpCycleStatusSchema,
71143
+ /**
71144
+ * Runtime-state durability: **session** — live appliance state re-published on connect.
71145
+ *
71146
+ * See `RuntimeStateDurability`. Enforced by
71147
+ * `scripts/check-runtime-state-durability.ts`.
71148
+ */
71149
+ durability: "session",
71150
+ /** History (D850): a duty-cycle setting is not a series worth recording. */
71151
+ history: "none"
71152
+ };
70665
71153
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, method(object({ deviceId: number$1().int().nonnegative() }), object({ success: literal(true) }), {
70666
71154
  kind: "mutation",
70667
71155
  auth: "admin"
@@ -73452,7 +73940,13 @@ var RemoteMediaCommandSchema = _enum([
73452
73940
  "next",
73453
73941
  "previous",
73454
73942
  "setMute",
73455
- "selectSource"
73943
+ "selectSource",
73944
+ "turnOn",
73945
+ "turnOff",
73946
+ "volumeUp",
73947
+ "volumeDown",
73948
+ "channelUp",
73949
+ "channelDown"
73456
73950
  ]);
73457
73951
  /** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
73458
73952
  var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
@@ -73473,7 +73967,7 @@ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.e
73473
73967
  }), RemoteSendResultSchema, {
73474
73968
  kind: "mutation",
73475
73969
  auth: "protected"
73476
- }), method(deviceInput.extend({
73970
+ }), method(deviceInput, MediaPlayerStatusSchema.nullable(), { auth: "protected" }), method(deviceInput.extend({
73477
73971
  command: RemoteMediaCommandSchema,
73478
73972
  muted: boolean().optional(),
73479
73973
  source: string().optional(),
@@ -74654,7 +75148,16 @@ var smokeCapability = {
74654
75148
  * future fields without breaking the existing call sites.
74655
75149
  */
74656
75150
  var EmailAddressSchema = email();
75151
+ /**
75152
+ * Which relay of a multi-relay provider answers (D996). Absent = the provider's first / default
75153
+ * relay, so a caller written before relays existed keeps working unchanged. A provider with one
75154
+ * fixed relay ignores it. This is the integration instance id (`int_NNNN`), NOT the addon id: the
75155
+ * collection pin `addonId` still picks the provider, `instanceId` picks the relay inside it.
75156
+ */
75157
+ var InstanceIdSchema = string().min(1).optional();
75158
+ var RelayPinSchema = object({ instanceId: InstanceIdSchema });
74657
75159
  var SendEmailInputSchema = object({
75160
+ instanceId: InstanceIdSchema,
74658
75161
  to: union([EmailAddressSchema, array(EmailAddressSchema).min(1)]),
74659
75162
  cc: array(EmailAddressSchema).optional(),
74660
75163
  bcc: array(EmailAddressSchema).optional(),
@@ -74690,11 +75193,11 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
74690
75193
  kind: "mutation",
74691
75194
  auth: "admin",
74692
75195
  access: "create"
74693
- }), method(_void(), SmtpStatusSchema, {
75196
+ }), method(RelayPinSchema, SmtpStatusSchema, {
74694
75197
  kind: "mutation",
74695
75198
  auth: "admin",
74696
75199
  access: "view"
74697
- }), method(_void(), SmtpStatusSchema, { auth: "admin" });
75200
+ }), method(RelayPinSchema, SmtpStatusSchema, { auth: "admin" });
74698
75201
  /**
74699
75202
  * A single device snapshot returned as base64 JPEG/PNG.
74700
75203
  *
@@ -74726,10 +75229,12 @@ DeviceType.Camera, method(object({
74726
75229
  * even when the cache is well within the device's
74727
75230
  * `snapshotMaxAgeS` window.
74728
75231
  *
74729
- * **`force` is an OPERATOR signal, not a freshness preference.** On a
75232
+ * **`force` is a demand with a named principal, not a freshness
75233
+ * preference** — an operator button, the scheduled refresh (D847), or a
75234
+ * notification rule that left the camera's wake allowed (D998). On a
74730
75235
  * battery camera it is the one thing that walks past the wrapper's
74731
- * sleep gate and wakes the camera, so a background caller — a poller,
74732
- * an event handler, a thumbnail — must NEVER set it. Every such caller
75236
+ * sleep gate and wakes the camera, so a poller, a thumbnail or a
75237
+ * prestream must NEVER set it. Every such caller
74733
75238
  * gets the cached frame, which on a sleeping battery camera is the
74734
75239
  * correct answer: stale but honest beats woken.
74735
75240
  */
@@ -75708,7 +76213,11 @@ listSignals: method(_void(), array(StreamSignalSchema).readonly()) }
75708
76213
  * `getSettingsUISchema()` surface.
75709
76214
  */
75710
76215
  var SwitchStatusSchema = object({
75711
- on: boolean().meta({ label: "On" }),
76216
+ /**
76217
+ * `null` = the device has never reported its state (a measurement that
76218
+ * has not happened is unknown, never `false`).
76219
+ */
76220
+ on: boolean().nullable().meta({ label: "On" }),
75712
76221
  /** Ms epoch of the last state change. Useful for UI "X minutes ago". */
75713
76222
  lastChangedAt: number$1()
75714
76223
  });
@@ -75733,7 +76242,7 @@ var switchCapability = {
75733
76242
  }) },
75734
76243
  events: { onStateChanged: { data: object({
75735
76244
  deviceId: number$1(),
75736
- on: boolean(),
76245
+ on: boolean().nullable(),
75737
76246
  lastChangedAt: number$1()
75738
76247
  }) } },
75739
76248
  status: {
@@ -76299,6 +76808,40 @@ var LoggingSettingsStateSchema = object({
76299
76808
  opsLogRetentionDays: number$1().int().min(0),
76300
76809
  persisted: boolean()
76301
76810
  });
76811
+ /**
76812
+ * Debug WATCH — a short, per-device window over `debug.<channel>` lines.
76813
+ *
76814
+ * `start` opens (or re-arms) a 10 minute window; `rearm` extends a LIVE one
76815
+ * and never revives a lapsed one; `stop` closes it; `list` only reads. No
76816
+ * chain of re-arms outlives 60 minutes from the first start. The hub holds the
76817
+ * watches in memory and pushes the interest to every node, so a watch lapses
76818
+ * by itself and survives nothing — a restart is a stop.
76819
+ */
76820
+ var DebugWatchInputSchema = object({
76821
+ action: _enum([
76822
+ "list",
76823
+ "start",
76824
+ "rearm",
76825
+ "stop"
76826
+ ]),
76827
+ /** Full channel names, `debug.<channel>`, each declared with `kind: 'debug'`. */
76828
+ channels: array(string().min(1)).readonly().default([]),
76829
+ /** Numeric device ids. Required (non-empty) for `start`. */
76830
+ deviceIds: array(number$1().int()).readonly().default([])
76831
+ });
76832
+ var DebugWatchStateSchema = object({
76833
+ channel: string(),
76834
+ deviceIds: array(number$1().int()).readonly(),
76835
+ startedAtMs: number$1(),
76836
+ untilMs: number$1(),
76837
+ capAtMs: number$1()
76838
+ });
76839
+ var DebugWatchResultSchema = object({
76840
+ nowMs: number$1(),
76841
+ watches: array(DebugWatchStateSchema).readonly(),
76842
+ /** Channels a `rearm` found already lapsed: nothing was revived. */
76843
+ lapsed: array(string()).readonly()
76844
+ });
76302
76845
  method(_void(), SystemInfoSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
76303
76846
  kind: "mutation",
76304
76847
  auth: "admin"
@@ -76314,6 +76857,9 @@ method(_void(), SystemInfoSchema), method(_void(), HealthStatusSchema), method(_
76314
76857
  }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(_void(), array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }), method(_void(), array(ReportedFailureContributionSchema).readonly(), { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
76315
76858
  kind: "mutation",
76316
76859
  auth: "admin"
76860
+ }), method(DebugWatchInputSchema, DebugWatchResultSchema, {
76861
+ kind: "mutation",
76862
+ auth: "admin"
76317
76863
  });
76318
76864
  /**
76319
76865
  * Tamper / case-open detection sensor. Drives Home Assistant
@@ -79253,6 +79799,7 @@ function buildDescriptor(capName, kind) {
79253
79799
  * contributes one entry per bound cap present in this map.
79254
79800
  */
79255
79801
  var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
79802
+ DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
79256
79803
  var btn = (idSuffix, row, col, init) => ({
79257
79804
  idSuffix,
79258
79805
  row,
@@ -79379,7 +79926,7 @@ discriminatedUnion("kind", [object({
79379
79926
  kind: literal("pronto"),
79380
79927
  code: string()
79381
79928
  })]);
79382
- DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
79929
+ DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
79383
79930
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
79384
79931
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
79385
79932
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
@@ -79739,6 +80286,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
79739
80286
  pressureSensor: pressureSensorCapability,
79740
80287
  privacyMask: privacyMaskCapability,
79741
80288
  ptzAutotrack: ptzAutotrackCapability,
80289
+ pumpCycle: pumpCycleCapability,
79742
80290
  recordingOnboard: recordingOnboardCapability,
79743
80291
  recordingSignal: recordingSignalCapability,
79744
80292
  runningState: runningStateCapability,
@@ -81923,6 +82471,27 @@ Object.freeze({
81923
82471
  access: "view",
81924
82472
  role: "viewer"
81925
82473
  },
82474
+ "boardKeySource.getBoardKey": {
82475
+ capName: "board-key-source",
82476
+ capScope: "system",
82477
+ addonId: null,
82478
+ access: "view",
82479
+ role: "admin"
82480
+ },
82481
+ "boardKeySource.listBoards": {
82482
+ capName: "board-key-source",
82483
+ capScope: "system",
82484
+ addonId: null,
82485
+ access: "view",
82486
+ role: "admin"
82487
+ },
82488
+ "boardKeySource.listSources": {
82489
+ capName: "board-key-source",
82490
+ capScope: "system",
82491
+ addonId: null,
82492
+ access: "view",
82493
+ role: "admin"
82494
+ },
81926
82495
  "brightness.getStatus": {
81927
82496
  capName: "brightness",
81928
82497
  capScope: "device",
@@ -83260,6 +83829,13 @@ Object.freeze({
83260
83829
  access: "view",
83261
83830
  role: "admin"
83262
83831
  },
83832
+ "deviceManager.dismissDiscoveryProposal": {
83833
+ capName: "device-manager",
83834
+ capScope: "system",
83835
+ addonId: null,
83836
+ access: "create",
83837
+ role: "admin"
83838
+ },
83263
83839
  "deviceManager.enable": {
83264
83840
  capName: "device-manager",
83265
83841
  capScope: "system",
@@ -83442,6 +84018,13 @@ Object.freeze({
83442
84018
  access: "view",
83443
84019
  role: "viewer"
83444
84020
  },
84021
+ "deviceManager.listDiscoveryProposals": {
84022
+ capName: "device-manager",
84023
+ capScope: "system",
84024
+ addonId: null,
84025
+ access: "view",
84026
+ role: "admin"
84027
+ },
83445
84028
  "deviceManager.listLocationDescriptions": {
83446
84029
  capName: "device-manager",
83447
84030
  capScope: "system",
@@ -83596,6 +84179,13 @@ Object.freeze({
83596
84179
  access: "create",
83597
84180
  role: "admin"
83598
84181
  },
84182
+ "deviceManager.reportDiscoveryObservations": {
84183
+ capName: "device-manager",
84184
+ capScope: "system",
84185
+ addonId: null,
84186
+ access: "create",
84187
+ role: "admin"
84188
+ },
83599
84189
  "deviceManager.runDeviceAction": {
83600
84190
  capName: "device-manager",
83601
84191
  capScope: "system",
@@ -83603,6 +84193,13 @@ Object.freeze({
83603
84193
  access: "create",
83604
84194
  role: "admin"
83605
84195
  },
84196
+ "deviceManager.runDiscoverySweep": {
84197
+ capName: "device-manager",
84198
+ capScope: "system",
84199
+ addonId: null,
84200
+ access: "create",
84201
+ role: "admin"
84202
+ },
83606
84203
  "deviceManager.setChildLayout": {
83607
84204
  capName: "device-manager",
83608
84205
  capScope: "system",
@@ -84534,6 +85131,69 @@ Object.freeze({
84534
85131
  access: "create",
84535
85132
  role: "admin"
84536
85133
  },
85134
+ "integrationInstances.createInstance": {
85135
+ capName: "integration-instances",
85136
+ capScope: "system",
85137
+ addonId: null,
85138
+ access: "create",
85139
+ role: "admin"
85140
+ },
85141
+ "integrationInstances.deleteInstance": {
85142
+ capName: "integration-instances",
85143
+ capScope: "system",
85144
+ addonId: null,
85145
+ access: "delete",
85146
+ role: "admin"
85147
+ },
85148
+ "integrationInstances.describe": {
85149
+ capName: "integration-instances",
85150
+ capScope: "system",
85151
+ addonId: null,
85152
+ access: "view",
85153
+ role: "admin"
85154
+ },
85155
+ "integrationInstances.getInstance": {
85156
+ capName: "integration-instances",
85157
+ capScope: "system",
85158
+ addonId: null,
85159
+ access: "view",
85160
+ role: "admin"
85161
+ },
85162
+ "integrationInstances.instanceFormSchema": {
85163
+ capName: "integration-instances",
85164
+ capScope: "system",
85165
+ addonId: null,
85166
+ access: "view",
85167
+ role: "admin"
85168
+ },
85169
+ "integrationInstances.listInstances": {
85170
+ capName: "integration-instances",
85171
+ capScope: "system",
85172
+ addonId: null,
85173
+ access: "view",
85174
+ role: "admin"
85175
+ },
85176
+ "integrationInstances.setInstanceEnabled": {
85177
+ capName: "integration-instances",
85178
+ capScope: "system",
85179
+ addonId: null,
85180
+ access: "create",
85181
+ role: "admin"
85182
+ },
85183
+ "integrationInstances.testInstance": {
85184
+ capName: "integration-instances",
85185
+ capScope: "system",
85186
+ addonId: null,
85187
+ access: "create",
85188
+ role: "admin"
85189
+ },
85190
+ "integrationInstances.updateInstance": {
85191
+ capName: "integration-instances",
85192
+ capScope: "system",
85193
+ addonId: null,
85194
+ access: "create",
85195
+ role: "admin"
85196
+ },
84537
85197
  "integrations.create": {
84538
85198
  capName: "integrations",
84539
85199
  capScope: "system",
@@ -85318,6 +85978,20 @@ Object.freeze({
85318
85978
  access: "view",
85319
85979
  role: "admin"
85320
85980
  },
85981
+ "mediaPlayer.channelDown": {
85982
+ capName: "media-player",
85983
+ capScope: "device",
85984
+ addonId: null,
85985
+ access: "create",
85986
+ role: "viewer"
85987
+ },
85988
+ "mediaPlayer.channelUp": {
85989
+ capName: "media-player",
85990
+ capScope: "device",
85991
+ addonId: null,
85992
+ access: "create",
85993
+ role: "viewer"
85994
+ },
85321
85995
  "mediaPlayer.getStatus": {
85322
85996
  capName: "media-player",
85323
85997
  capScope: "device",
@@ -85409,6 +86083,34 @@ Object.freeze({
85409
86083
  access: "create",
85410
86084
  role: "viewer"
85411
86085
  },
86086
+ "mediaPlayer.turnOff": {
86087
+ capName: "media-player",
86088
+ capScope: "device",
86089
+ addonId: null,
86090
+ access: "create",
86091
+ role: "viewer"
86092
+ },
86093
+ "mediaPlayer.turnOn": {
86094
+ capName: "media-player",
86095
+ capScope: "device",
86096
+ addonId: null,
86097
+ access: "create",
86098
+ role: "viewer"
86099
+ },
86100
+ "mediaPlayer.volumeDown": {
86101
+ capName: "media-player",
86102
+ capScope: "device",
86103
+ addonId: null,
86104
+ access: "create",
86105
+ role: "viewer"
86106
+ },
86107
+ "mediaPlayer.volumeUp": {
86108
+ capName: "media-player",
86109
+ capScope: "device",
86110
+ addonId: null,
86111
+ access: "create",
86112
+ role: "viewer"
86113
+ },
85412
86114
  "meshNetwork.getStatus": {
85413
86115
  capName: "mesh-network",
85414
86116
  capScope: "system",
@@ -86655,27 +87357,6 @@ Object.freeze({
86655
87357
  access: "create",
86656
87358
  role: "viewer"
86657
87359
  },
86658
- "petFountain.setPumpInterval": {
86659
- capName: "pet-fountain",
86660
- capScope: "device",
86661
- addonId: null,
86662
- access: "create",
86663
- role: "viewer"
86664
- },
86665
- "petFountain.setPumpOffMinutes": {
86666
- capName: "pet-fountain",
86667
- capScope: "device",
86668
- addonId: null,
86669
- access: "create",
86670
- role: "viewer"
86671
- },
86672
- "petFountain.setPumpOnMinutes": {
86673
- capName: "pet-fountain",
86674
- capScope: "device",
86675
- addonId: null,
86676
- access: "create",
86677
- role: "viewer"
86678
- },
86679
87360
  "pipelineAnalytics.addRetrainFrameFromRecording": {
86680
87361
  capName: "pipeline-analytics",
86681
87362
  capScope: "device",
@@ -88454,6 +89135,34 @@ Object.freeze({
88454
89135
  access: "create",
88455
89136
  role: "admin"
88456
89137
  },
89138
+ "pumpCycle.getStatus": {
89139
+ capName: "pump-cycle",
89140
+ capScope: "device",
89141
+ addonId: null,
89142
+ access: "view",
89143
+ role: "viewer"
89144
+ },
89145
+ "pumpCycle.setEnabled": {
89146
+ capName: "pump-cycle",
89147
+ capScope: "device",
89148
+ addonId: null,
89149
+ access: "create",
89150
+ role: "viewer"
89151
+ },
89152
+ "pumpCycle.setOffMinutes": {
89153
+ capName: "pump-cycle",
89154
+ capScope: "device",
89155
+ addonId: null,
89156
+ access: "create",
89157
+ role: "viewer"
89158
+ },
89159
+ "pumpCycle.setOnMinutes": {
89160
+ capName: "pump-cycle",
89161
+ capScope: "device",
89162
+ addonId: null,
89163
+ access: "create",
89164
+ role: "viewer"
89165
+ },
88457
89166
  "reboot.reboot": {
88458
89167
  capName: "reboot",
88459
89168
  capScope: "device",
@@ -88958,6 +89667,13 @@ Object.freeze({
88958
89667
  access: "create",
88959
89668
  role: "admin"
88960
89669
  },
89670
+ "remote.getAssumedStatus": {
89671
+ capName: "remote",
89672
+ capScope: "system",
89673
+ addonId: null,
89674
+ access: "view",
89675
+ role: "admin"
89676
+ },
88961
89677
  "remote.getConfig": {
88962
89678
  capName: "remote",
88963
89679
  capScope: "system",
@@ -90190,6 +90906,13 @@ Object.freeze({
90190
90906
  access: "create",
90191
90907
  role: "viewer"
90192
90908
  },
90909
+ "system.debugWatch": {
90910
+ capName: "system",
90911
+ capScope: "system",
90912
+ addonId: null,
90913
+ access: "create",
90914
+ role: "admin"
90915
+ },
90193
90916
  "system.detectSiteLocation": {
90194
90917
  capName: "system",
90195
90918
  capScope: "system",
@@ -92617,6 +93340,16 @@ Object.freeze({
92617
93340
  form: "single",
92618
93341
  optional: true
92619
93342
  }],
93343
+ "mediaPlayer.channelDown": [{
93344
+ name: "deviceId",
93345
+ form: "single",
93346
+ optional: false
93347
+ }],
93348
+ "mediaPlayer.channelUp": [{
93349
+ name: "deviceId",
93350
+ form: "single",
93351
+ optional: false
93352
+ }],
92620
93353
  "mediaPlayer.getStatus": [{
92621
93354
  name: "deviceId",
92622
93355
  form: "single",
@@ -92682,6 +93415,26 @@ Object.freeze({
92682
93415
  form: "single",
92683
93416
  optional: false
92684
93417
  }],
93418
+ "mediaPlayer.turnOff": [{
93419
+ name: "deviceId",
93420
+ form: "single",
93421
+ optional: false
93422
+ }],
93423
+ "mediaPlayer.turnOn": [{
93424
+ name: "deviceId",
93425
+ form: "single",
93426
+ optional: false
93427
+ }],
93428
+ "mediaPlayer.volumeDown": [{
93429
+ name: "deviceId",
93430
+ form: "single",
93431
+ optional: false
93432
+ }],
93433
+ "mediaPlayer.volumeUp": [{
93434
+ name: "deviceId",
93435
+ form: "single",
93436
+ optional: false
93437
+ }],
92685
93438
  "meter.getStatus": [{
92686
93439
  name: "deviceId",
92687
93440
  form: "single",
@@ -92991,21 +93744,6 @@ Object.freeze({
92991
93744
  form: "single",
92992
93745
  optional: false
92993
93746
  }],
92994
- "petFountain.setPumpInterval": [{
92995
- name: "deviceId",
92996
- form: "single",
92997
- optional: false
92998
- }],
92999
- "petFountain.setPumpOffMinutes": [{
93000
- name: "deviceId",
93001
- form: "single",
93002
- optional: false
93003
- }],
93004
- "petFountain.setPumpOnMinutes": [{
93005
- name: "deviceId",
93006
- form: "single",
93007
- optional: false
93008
- }],
93009
93747
  "pipelineAnalytics.addRetrainFrameFromRecording": [{
93010
93748
  name: "deviceId",
93011
93749
  form: "single",
@@ -93641,6 +94379,26 @@ Object.freeze({
93641
94379
  form: "single",
93642
94380
  optional: false
93643
94381
  }],
94382
+ "pumpCycle.getStatus": [{
94383
+ name: "deviceId",
94384
+ form: "single",
94385
+ optional: false
94386
+ }],
94387
+ "pumpCycle.setEnabled": [{
94388
+ name: "deviceId",
94389
+ form: "single",
94390
+ optional: false
94391
+ }],
94392
+ "pumpCycle.setOffMinutes": [{
94393
+ name: "deviceId",
94394
+ form: "single",
94395
+ optional: false
94396
+ }],
94397
+ "pumpCycle.setOnMinutes": [{
94398
+ name: "deviceId",
94399
+ form: "single",
94400
+ optional: false
94401
+ }],
93644
94402
  "reboot.reboot": [{
93645
94403
  name: "deviceId",
93646
94404
  form: "single",
@@ -93842,6 +94600,11 @@ Object.freeze({
93842
94600
  form: "single",
93843
94601
  optional: true
93844
94602
  }],
94603
+ "remote.getAssumedStatus": [{
94604
+ name: "deviceId",
94605
+ form: "single",
94606
+ optional: false
94607
+ }],
93845
94608
  "remote.getConfig": [{
93846
94609
  name: "deviceId",
93847
94610
  form: "single",
@@ -94108,6 +94871,11 @@ Object.freeze({
94108
94871
  form: "single",
94109
94872
  optional: false
94110
94873
  }],
94874
+ "system.debugWatch": [{
94875
+ name: "deviceIds",
94876
+ form: "array",
94877
+ optional: true
94878
+ }],
94111
94879
  "tamper.getStatus": [{
94112
94880
  name: "deviceId",
94113
94881
  form: "single",
@@ -95299,6 +96067,7 @@ var STATE_SUMMARY_VARS = [
95299
96067
  [...RULE_TEXT],
95300
96068
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
95301
96069
  ([...RULE_TEXT], [...COMBINED_ALARM]),
96070
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
95302
96071
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
95303
96072
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
95304
96073
  ([...RULE_TEXT], [...CONSUMABLE]),
@@ -140569,7 +141338,7 @@ function buildAlexaBrokerProvider(deps) {
140569
141338
  label: "Alexa",
140570
141339
  signIn: {
140571
141340
  basePath: "/addon/import-alexa/login",
140572
- methods: ["device-code"]
141341
+ methods: ["device-code", "proxy"]
140573
141342
  }
140574
141343
  }]
140575
141344
  }],
@@ -141816,6 +142585,12 @@ var AlexaEchoDevice = class extends BaseDevice {
141816
142585
  this.rereadSoon();
141817
142586
  },
141818
142587
  stop: async () => unsupported("stop"),
142588
+ turnOn: async () => unsupported("power"),
142589
+ turnOff: async () => unsupported("power"),
142590
+ volumeUp: async () => unsupported("volume steps"),
142591
+ volumeDown: async () => unsupported("volume steps"),
142592
+ channelUp: async () => unsupported("channel steps"),
142593
+ channelDown: async () => unsupported("channel steps"),
141819
142594
  seek: async () => unsupported("seek"),
141820
142595
  setMute: async () => unsupported("mute"),
141821
142596
  setShuffle: async () => unsupported("shuffle"),