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