@camstack/addon-provider-homeassistant 1.2.141 → 1.2.142

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.
@@ -5359,7 +5359,7 @@ var ZodIssueCode = {
5359
5359
  var ZodFirstPartyTypeKind;
5360
5360
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5361
5361
  //#endregion
5362
- //#region ../types/dist/sleep-CiR4z7r9.mjs
5362
+ //#region ../types/dist/sleep-i3eUVc-d.mjs
5363
5363
  /**
5364
5364
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5365
5365
  * window to float samples (D455).
@@ -6989,6 +6989,18 @@ function systemMethod(input, output, options) {
6989
6989
  systemOnly: true
6990
6990
  };
6991
6991
  }
6992
+ /**
6993
+ * A method a SOURCE of a collection cap may legitimately not serve — OPTIONAL
6994
+ * on `InferProvider`. The `providerOptional: true` literal is what
6995
+ * `InferProvider` keys on; see {@link CapabilityMethodSchema.providerOptional}
6996
+ * for when this is the honest answer and when it is a soft stub.
6997
+ */
6998
+ function optionalMethod(input, output, options) {
6999
+ return {
7000
+ ...method(input, output, options),
7001
+ providerOptional: true
7002
+ };
7003
+ }
6992
7004
  /** Shorthand to define an event schema */
6993
7005
  function event(data) {
6994
7006
  return { data };
@@ -7419,6 +7431,8 @@ var EVENT_OWNER_TYPES = [
7419
7431
  * nothing failing until a caller asked.
7420
7432
  */
7421
7433
  var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
7434
+ /** The same list as a Zod enum, for the cap input that carries it. */
7435
+ var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
7422
7436
  new Set(EVENT_OWNER_TYPES);
7423
7437
  /**
7424
7438
  * Build an `IAddonRouteProvider` from a list of routes. Implements
@@ -7971,10 +7985,20 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
7971
7985
  var CameraSwitchUnavailableReasonSchema = _enum([
7972
7986
  "no-provider",
7973
7987
  "source-unreachable",
7974
- "not-configured",
7975
7988
  "device-disabled"
7976
7989
  ]);
7977
7990
  /**
7991
+ * Something true about a switch that is nonetheless OFFERED.
7992
+ *
7993
+ * The distinction from {@link CameraSwitchUnavailableReasonSchema} is the
7994
+ * whole point: a reason replaces the control, a warning travels WITH it. The
7995
+ * privacy mask taught us the difference — greying the one button that starts
7996
+ * the feature teaches the operator the feature is broken, while a live toggle
7997
+ * that flips nothing teaches them the switch is broken. Neither is true; the
7998
+ * honest surface is a working toggle that says what is missing.
7999
+ */
8000
+ var CameraSwitchWarningSchema = _enum(["no-privacy-zones"]);
8001
+ /**
7978
8002
  * One switch, resolved for one camera.
7979
8003
  *
7980
8004
  * `label` and `costWhenOff` travel ON THE WIRE rather than being looked up
@@ -7993,6 +8017,11 @@ var CameraSwitchSchema = object({
7993
8017
  /** False = do not render a control. `unavailableReason` says why. */
7994
8018
  available: boolean(),
7995
8019
  unavailableReason: CameraSwitchUnavailableReasonSchema.optional(),
8020
+ /**
8021
+ * Present on an AVAILABLE switch only. Rendered beside the live control;
8022
+ * never a substitute for one.
8023
+ */
8024
+ warning: CameraSwitchWarningSchema.optional(),
7996
8025
  /** Current state. Meaningless when `available` is false — read it as `true`. */
7997
8026
  enabled: boolean(),
7998
8027
  authority: CameraSwitchAuthoritySchema
@@ -13848,6 +13877,244 @@ var deviceAdoptionCapability = {
13848
13877
  }
13849
13878
  };
13850
13879
  /**
13880
+ * device-admin-link — "this device has a management page of its own, and here
13881
+ * is its address".
13882
+ *
13883
+ * ## Why this is not a `deviceConfig` cap
13884
+ *
13885
+ * There is nothing to edit. A `deviceConfig` cap (D14) exists so the framework
13886
+ * can DERIVE a settings form from `getOptions` + `getStatus` and route a flat
13887
+ * patch back through a setter; it costs a `builderId` reducer in
13888
+ * `device-config-contribution.ts` and a `*-config-schema.ts` beside it, and it
13889
+ * renders a form section. This cap answers ONE question with ONE read and
13890
+ * renders a button. Nothing about it is a form, so it carries no `deviceConfig`
13891
+ * block, no `settings`, no `runtimeState` and no reducer — exactly like
13892
+ * `reboot`, the other pure-RPC device-native cap.
13893
+ *
13894
+ * ## Absent, and the difference between "no page" and "we cannot say"
13895
+ *
13896
+ * The two are answered at DIFFERENT layers, on purpose:
13897
+ *
13898
+ * - **"We cannot say"** → the provider never registers the cap for that
13899
+ * device. A VeSync humidifier, a Petkit feeder, a Dreame vacuum and a Dreo
13900
+ * fan are reached only through a vendor cloud; there is no address to hand
13901
+ * out and no page to open. A Tuya plug, a Wyze camera and a Gree air
13902
+ * conditioner DO have a LAN IP, and still have no HTTP management page
13903
+ * behind it. None of them register, so `deviceManager.getBindings` never
13904
+ * lists the cap and no surface asks.
13905
+ * - **"This device has no page, and I know that"** → the provider registers
13906
+ * and `getAdminLink` returns `null`. This is the answer for a device whose
13907
+ * sibling DOES have a page: a Reolink battery camera reached over UDP by
13908
+ * `uid` with a blank `host`, an Ecowitt gateway configured in `listener`
13909
+ * transport, a Home Assistant broker authenticated by supervisor token
13910
+ * (which carries no `baseUrl` at all).
13911
+ *
13912
+ * Both draw NOTHING. A button that opens a browser error is worse than no
13913
+ * button, and D62 is the same rule from the other side: an off switch is
13914
+ * reported off, never made to look broken. There is no third state where the
13915
+ * UI renders a disabled button "because the device might have a page".
13916
+ *
13917
+ * ## The URL never carries credentials
13918
+ *
13919
+ * Not in userinfo, not in a query string. Every provider builds through
13920
+ * `buildDeviceAdminUrl` (`device-admin-link-url.ts`), which takes host, port,
13921
+ * scheme and path as separate arguments — there is no parameter a secret could
13922
+ * arrive in — and re-checks its own output for the `scheme://user:pass@` shape
13923
+ * that `scripts/check-no-credential-urls-in-fixtures.ts` bans from recorded
13924
+ * output. `scripts/check-admin-link-builder-is-the-only-url-source.ts` is what
13925
+ * keeps providers from hand-rolling one anyway.
13926
+ *
13927
+ * This matters here more than anywhere else in the repo, because every provider
13928
+ * that knows a device's host knows its PASSWORD too: `{ host, port, username,
13929
+ * password }` sit in one object on Hikvision, Amcrest, Reolink and ONVIF alike,
13930
+ * and `http://admin:hunter2@192.168.50.139/` is a URL a browser accepts. The
13931
+ * camera's own page will ask for its own login. That is correct, and pre-
13932
+ * filling it is the operator's business, not ours.
13933
+ *
13934
+ * ## It is a LAN fact
13935
+ *
13936
+ * The URL addresses the device where the NODE can see it. It is not proxied,
13937
+ * not made reachable from outside, and not sent anywhere. A surface renders it
13938
+ * as a link the operator's own browser follows, on the operator's own network,
13939
+ * or renders nothing.
13940
+ */
13941
+ /**
13942
+ * Whose page is it. The distinction is for the OPERATOR, who needs to know
13943
+ * before clicking whether he is about to land on a camera's own web server or
13944
+ * inside Home Assistant.
13945
+ */
13946
+ var AdminLinkTargetEnum = _enum(["device", "integration"]);
13947
+ var DeviceAdminLinkSchema = object({
13948
+ /**
13949
+ * Absolute `http(s)://` URL. Built by `buildDeviceAdminUrl` and therefore
13950
+ * free of userinfo and of any credential-shaped query key.
13951
+ */
13952
+ url: string(),
13953
+ /**
13954
+ * What the surface calls it — "Web UI", "Home Assistant", "UniFi controller".
13955
+ * The PROVIDER names it, because only the provider knows what the page is;
13956
+ * a UI that invented the label from the addon id would call the Home
13957
+ * Assistant device page "Provider Homeassistant".
13958
+ */
13959
+ label: string(),
13960
+ target: AdminLinkTargetEnum,
13961
+ /**
13962
+ * Host the URL points at, without scheme, port or path — for the tooltip, so
13963
+ * an operator can see WHERE the button goes before he follows it. Redundant
13964
+ * with `url` by construction; carried separately so no surface has to parse
13965
+ * a URL to show it.
13966
+ */
13967
+ host: string()
13968
+ });
13969
+ var deviceAdminLinkCapability = {
13970
+ name: "device-admin-link",
13971
+ scope: "device",
13972
+ deviceNative: true,
13973
+ mode: "singleton",
13974
+ methods: {
13975
+ /**
13976
+ * The device's management page, or `null` when this device has none.
13977
+ *
13978
+ * `auth: 'admin'` deliberately. This is administration, not actuation —
13979
+ * the same bucket as `reboot` and `camera-credentials`, and explicitly NOT
13980
+ * the actuation set `scripts/check-actuation-not-admin.ts` protects (D403).
13981
+ * The URL is also a statement about the LAN, which a household member with
13982
+ * a `view` grant on a light has no reason to be handed.
13983
+ *
13984
+ * The surfaces gate on the QUERY, never on a role they guessed: a caller
13985
+ * without the right loses the query and draws nothing, which is the same
13986
+ * thing a device with no page draws. There is no path on which a button
13987
+ * appears and then fails — the D403 failure mode, from the other end.
13988
+ */
13989
+ getAdminLink: method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" }) }
13990
+ };
13991
+ /**
13992
+ * Query keys that may never appear on an admin link. A credential smuggled as
13993
+ * `?password=` is the same leak as userinfo, in a form the userinfo check
13994
+ * cannot see: it survives copy-paste, referrer headers and proxy logs
13995
+ * identically.
13996
+ */
13997
+ var CREDENTIAL_QUERY_KEYS = [
13998
+ "password",
13999
+ "passwd",
14000
+ "pwd",
14001
+ "pass",
14002
+ "user",
14003
+ "username",
14004
+ "usr",
14005
+ "login",
14006
+ "token",
14007
+ "access_token",
14008
+ "auth",
14009
+ "authorization",
14010
+ "apikey",
14011
+ "api_key",
14012
+ "secret",
14013
+ "credential",
14014
+ "credentials",
14015
+ "session",
14016
+ "sessionid",
14017
+ "key"
14018
+ ];
14019
+ /**
14020
+ * The same userinfo shape `scripts/check-no-credential-urls-in-fixtures.ts`
14021
+ * scans recorded fixtures for, applied to what we are about to EMIT. Kept
14022
+ * structurally identical on purpose: a URL this function returns is a URL that
14023
+ * guard would pass.
14024
+ */
14025
+ var CREDENTIAL_URL = /[a-z][a-z0-9+.-]*:\/\/[^/\s:@'"]+:[^/\s:@'"]+@/i;
14026
+ /** A host that is safe to place in an authority component verbatim. */
14027
+ var PLAIN_HOST = /^[a-zA-Z0-9._-]+$/;
14028
+ /** A bracketed IPv6 literal, the only other authority form we emit. */
14029
+ var IPV6_HOST = /^\[[0-9a-fA-F:.]+\]$/;
14030
+ function defaultPortFor(scheme) {
14031
+ return scheme === "https" ? 443 : 80;
14032
+ }
14033
+ /**
14034
+ * Build the admin-page URL, or refuse by name.
14035
+ *
14036
+ * The refusal is never thrown: a provider answering "no page for this device"
14037
+ * is an ordinary answer (`getAdminLink` → `null`), and a throw here would turn
14038
+ * a missing host on one device into a failed query for the whole surface.
14039
+ */
14040
+ function buildDeviceAdminUrl(parts) {
14041
+ const host = parts.host.trim();
14042
+ if (host.length === 0) return {
14043
+ ok: false,
14044
+ reason: "empty-host"
14045
+ };
14046
+ if (host.includes("@")) return {
14047
+ ok: false,
14048
+ reason: "host-carries-userinfo"
14049
+ };
14050
+ if (!PLAIN_HOST.test(host) && !IPV6_HOST.test(host)) return {
14051
+ ok: false,
14052
+ reason: "host-not-plain"
14053
+ };
14054
+ if (parts.port !== null) {
14055
+ if (!Number.isInteger(parts.port) || parts.port < 1 || parts.port > 65535) return {
14056
+ ok: false,
14057
+ reason: "port-out-of-range"
14058
+ };
14059
+ }
14060
+ if (!parts.path.startsWith("/")) return {
14061
+ ok: false,
14062
+ reason: "path-not-absolute"
14063
+ };
14064
+ const query = parts.query ?? {};
14065
+ for (const key of Object.keys(query)) if (CREDENTIAL_QUERY_KEYS.includes(key.toLowerCase())) return {
14066
+ ok: false,
14067
+ reason: "credential-query-key"
14068
+ };
14069
+ const authority = parts.port === null || parts.port === defaultPortFor(parts.scheme) ? host : `${host}:${String(parts.port)}`;
14070
+ const search = new URLSearchParams();
14071
+ for (const [key, value] of Object.entries(query)) search.append(key, value);
14072
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
14073
+ const url = `${parts.scheme}://${authority}${parts.path}${suffix}`;
14074
+ if (CREDENTIAL_URL.test(url)) return {
14075
+ ok: false,
14076
+ reason: "userinfo-in-result"
14077
+ };
14078
+ return {
14079
+ ok: true,
14080
+ url
14081
+ };
14082
+ }
14083
+ function parseAdminBase(baseUrl) {
14084
+ const raw = baseUrl.trim();
14085
+ if (raw.length === 0) return {
14086
+ ok: false,
14087
+ reason: "empty"
14088
+ };
14089
+ let parsed;
14090
+ try {
14091
+ parsed = new URL(raw);
14092
+ } catch {
14093
+ return {
14094
+ ok: false,
14095
+ reason: "unparseable"
14096
+ };
14097
+ }
14098
+ if (parsed.username !== "" || parsed.password !== "") return {
14099
+ ok: false,
14100
+ reason: "base-carries-userinfo"
14101
+ };
14102
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return {
14103
+ ok: false,
14104
+ reason: "scheme-not-http"
14105
+ };
14106
+ const scheme = parsed.protocol === "https:" ? "https" : "http";
14107
+ const port = parsed.port === "" ? null : Number(parsed.port);
14108
+ const basePath = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/+$/, "");
14109
+ return {
14110
+ ok: true,
14111
+ host: parsed.hostname,
14112
+ port,
14113
+ scheme,
14114
+ basePath
14115
+ };
14116
+ }
14117
+ /**
13851
14118
  * `device-export` — collection cap for addons that export camstack
13852
14119
  * devices to external ecosystems (HomeAssistant via MQTT discovery,
13853
14120
  * HomeKit/HAP, Alexa Smart Home, …).
@@ -21749,7 +22016,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21749
22016
  ownerType: EventOwnerTypeSchema,
21750
22017
  eventId: number().int(),
21751
22018
  deviceId: number()
21752
- }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
22019
+ }), array(MediaFileInfoSchema).readonly()), method(object({
22020
+ deviceId: number(),
22021
+ ownerKind: MediaPresenceOwnerKindSchema,
22022
+ ownerIds: array(string()).max(5e3)
22023
+ }), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
21753
22024
  kind: "mutation",
21754
22025
  auth: "admin"
21755
22026
  }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
@@ -23416,10 +23687,9 @@ var ServerBootModeSchema = _enum([
23416
23687
  * - `idle` / `checking` / `staging` — steady / in-flight registry work.
23417
23688
  * - `pending-restart` — a version is staged and the node has NOT yet
23418
23689
  * restarted onto it (still running the OLD version).
23419
- * - `awaiting-confirmation` — the node HAS restarted onto the staged version
23420
- * (it is the active probation boot) and is waiting to confirm boot-health.
23421
- * Apply/rollback are refused in this state and the node must NOT be
23422
- * manually restarted, or the probation boot auto-rolls-back.
23690
+ * - `awaiting-confirmation` — RETIRED with the probation boot (single-copy
23691
+ * collapse, 2026-07-18). Kept in the enum so an older client still parses;
23692
+ * no provider emits it.
23423
23693
  */
23424
23694
  var ServerUpdateStateSchema = _enum([
23425
23695
  "idle",
@@ -23508,12 +23778,35 @@ var ServerRollbackInfoSchema = object({
23508
23778
  var ServerPackageStatusSchema = object({
23509
23779
  /** Root package name (`@camstack/server` on the hub). */
23510
23780
  packageName: string(),
23511
- /** Version of the code the running process ACTUALLY loaded. */
23781
+ /**
23782
+ * Version of the code THIS PROCESS loaded — captured once, at boot, from the
23783
+ * package.json of the copy the code was loaded from, and never re-read. A
23784
+ * copy replaced on disk under a live process must not move this number;
23785
+ * that divergence is what `installedVersion` is for. Before 2026-09-20 this
23786
+ * was a lazy disk read that would have followed such a swap silently.
23787
+ */
23512
23788
  runningVersion: string().nullable(),
23513
23789
  /** Node.js runtime version the node's process runs on (`process.versions.node`). */
23514
23790
  nodeRuntimeVersion: string().nullable(),
23515
23791
  /** Active data-dir root version; null when booted from seed/workspace. */
23516
23792
  activeVersion: string().nullable(),
23793
+ /**
23794
+ * Version of the root package in the data-root `current/` copy RIGHT NOW —
23795
+ * what the next boot would load if nothing is staged. Normally equal to
23796
+ * `runningVersion`; differs only when the copy on disk changed under the
23797
+ * live process. `null` when there is no data-root copy (baked seed or
23798
+ * workspace boot). Optional for version skew: an older provider omits it.
23799
+ */
23800
+ installedVersion: string().nullable().optional(),
23801
+ /**
23802
+ * True when the code that would run after a restart is NOT the code running
23803
+ * now: a version is staged (`pendingVersion`), or `installedVersion` differs
23804
+ * from `runningVersion`. Derived on every read, never stored. This is the
23805
+ * one field that lets an operator or a script say "1.2.353 is installed and
23806
+ * will run after a restart" instead of reading it off a number that means
23807
+ * "installed". Optional for version skew.
23808
+ */
23809
+ restartRequired: boolean().optional(),
23517
23810
  /** N-1 version kept for rollback; null when no previous version exists. */
23518
23811
  previousVersion: string().nullable(),
23519
23812
  /** Version of the immutable baked seed closure (image fallback). */
@@ -23548,19 +23841,71 @@ var ServerPackageStatusSchema = object({
23548
23841
  });
23549
23842
  var ServerUpdateCheckResultSchema = object({
23550
23843
  packageName: string(),
23844
+ /** Same meaning as on the status payload: what THIS process loaded. */
23551
23845
  runningVersion: string().nullable(),
23552
23846
  latestVersion: string().nullable(),
23847
+ /**
23848
+ * `latestVersion` is newer than `runningVersion` AND is not the version
23849
+ * already staged on this node. A staged-and-waiting version is not "an
23850
+ * update available" — applying it again is refused (`already-staged`), and
23851
+ * a button that announced "updating…" on this boolean then got refused
23852
+ * would be lying. It is reported through `pendingVersion` instead.
23853
+ */
23553
23854
  updateAvailable: boolean(),
23554
23855
  checkedAtMs: number(),
23555
23856
  /** Non-null when the registry lookup failed (offline, bad registry, …). */
23556
- error: string().nullable()
23857
+ error: string().nullable(),
23858
+ /**
23859
+ * Version staged on this node and awaiting its restart, when one is. Before
23860
+ * 2026-09-20 a check answered `{runningVersion, updateAvailable:false}` for a
23861
+ * node whose staged closure had never booted, and the caller had no way to
23862
+ * see the difference. Optional for version skew.
23863
+ */
23864
+ pendingVersion: string().nullable().optional(),
23865
+ /** See the status payload: staged, or installed-on-disk ≠ running. Optional for skew. */
23866
+ restartRequired: boolean().optional()
23557
23867
  });
23868
+ /**
23869
+ * What an action DID — the discriminant `accepted` cannot carry. Before
23870
+ * 2026-09-20 `applyServerUpdate` answered `accepted:false` both for a refusal
23871
+ * where nothing was wrong (already current, already staged) and for a staging
23872
+ * install that died on `npm error code ECONNRESET`; the only difference was
23873
+ * prose in `message`, and an operator restarted a container on the strength of
23874
+ * it, booting the previous version.
23875
+ *
23876
+ * - `staged` — the closure is installed and validated; the node
23877
+ * restarts to apply it (`restarting:true`). NOTHING
23878
+ * runs the new code until that restart completes.
23879
+ * - `restart-scheduled` — a plain, version-preserving restart was accepted.
23880
+ * - `already-current` — the target IS the running version; nothing done.
23881
+ * - `refused` — dropped by a gate; `reason` names which.
23882
+ * - `failed` — attempted and did not land; `error` says why.
23883
+ */
23884
+ var ServerUpdateOutcomeSchema = _enum([
23885
+ "staged",
23886
+ "restart-scheduled",
23887
+ "already-current",
23888
+ "refused",
23889
+ "failed"
23890
+ ]);
23558
23891
  var ServerUpdateActionResultSchema = object({
23559
23892
  accepted: boolean(),
23560
23893
  targetVersion: string().nullable(),
23561
23894
  /** True when a graceful restart was scheduled to apply the change. */
23562
23895
  restarting: boolean(),
23563
- message: string()
23896
+ message: string(),
23897
+ /** See {@link ServerUpdateOutcomeSchema}. Optional for version skew. */
23898
+ outcome: ServerUpdateOutcomeSchema.optional(),
23899
+ /**
23900
+ * Stable, greppable cause for `refused` / `failed` (the engine's
23901
+ * `UpdateRefusalReason`, `staging-failed`, …). `null` on success.
23902
+ */
23903
+ reason: string().nullable().optional(),
23904
+ /**
23905
+ * The failure itself, verbatim enough to act on (the npm output for a
23906
+ * staging install). Non-null only for `failed`.
23907
+ */
23908
+ error: string().nullable().optional()
23564
23909
  });
23565
23910
  method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
23566
23911
  kind: "mutation",
@@ -24669,6 +25014,18 @@ method(VectorDeclareIndexInputSchema, _void(), {
24669
25014
  kind: "mutation",
24670
25015
  auth: "admin"
24671
25016
  }), method(VectorStatsInputSchema, VectorStatsResultSchema, { auth: "admin" });
25017
+ _enum([
25018
+ "queue-full",
25019
+ "camera-backoff",
25020
+ "sleeping",
25021
+ "camera-refused",
25022
+ "no-keyframe",
25023
+ "no-catalog-row",
25024
+ "unsupported",
25025
+ "unknown-device",
25026
+ "uid-missing"
25027
+ ]);
25028
+ _enum(["deferred", "final"]);
24672
25029
  var ClipSchema = object({
24673
25030
  /** Opaque, provider-namespaced id. The default provider encodes the time
24674
25031
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -24743,29 +25100,455 @@ var ClipSchema = object({
24743
25100
  holes: array(object({
24744
25101
  startMs: number(),
24745
25102
  endMs: number()
24746
- })).optional()
25103
+ })).optional(),
25104
+ /**
25105
+ * A URL that MAY produce this clip's still, asked only for rows actually on
25106
+ * screen. The opposite of {@link ClipSchema.thumbnail}: that one is VOUCHED
25107
+ * (a JPEG is already on disk), this one is an offer. The route answers 200
25108
+ * with the image, or 204 with `x-camstack-reason` when it could not mint one
25109
+ * — a surface latches that refusal to the instant rather than retrying.
25110
+ *
25111
+ * Never both: a clip with a vouched `thumbnail` needs no mint.
25112
+ */
25113
+ thumbnailMint: string().optional(),
25114
+ /**
25115
+ * Why no still will be produced for this clip RIGHT NOW — set when the
25116
+ * provider already knows, so the surface draws the glyph and the reason
25117
+ * instead of firing a mint that cannot succeed.
25118
+ *
25119
+ * `sleeping` — a standalone battery camera; a read would be a wake (D549 4).
25120
+ * `camera-refused` — the camera answered the CoverPreview with a refusal.
25121
+ * `no-keyframe` — the window holds no decodable I-frame (an event that sits
25122
+ * inside no file is the measured case).
25123
+ * `unsupported` — this source cannot mint stills at all.
25124
+ */
25125
+ thumbnailUnavailable: object({ reason: _enum([
25126
+ "sleeping",
25127
+ "camera-refused",
25128
+ "no-keyframe",
25129
+ "unsupported"
25130
+ ]) }).optional(),
25131
+ /**
25132
+ * When the catalog this row came from was last CONFIRMED against the device.
25133
+ * Absent means "this row was read live". A persisted catalog served while a
25134
+ * camera sleeps carries the age it really has — a cached list is never drawn
25135
+ * as current (D549 13).
25136
+ */
25137
+ catalogAsOf: number().optional(),
25138
+ /**
25139
+ * The camera's OWN type strings for this clip, all of them, unmapped
25140
+ * (`md`, `people`, `dog_cat`, `sched`, …). Kept beside {@link labels}
25141
+ * because a firmware inventing a type must not vanish: the mapping into our
25142
+ * filter vocabulary is lossy on purpose and this is the lossless copy
25143
+ * (D549 20).
25144
+ */
25145
+ nativeTypes: array(string()).optional(),
25146
+ /**
25147
+ * What the subject DID — `crossline`, `intrude`, `loitering`. A behaviour
25148
+ * travels BESIDE a class, never instead of one, and the class filter ignores
25149
+ * it: "a person crossed a line" is still a person (D549 20).
25150
+ */
25151
+ behaviours: array(string()).optional(),
25152
+ /**
25153
+ * The same recording as two files — the sub twin (what the row and its
25154
+ * thumbnail are) and its main twin, matched at listing time so a quality
25155
+ * change never re-searches the camera (D549 15). `getClipPlayback`'s
25156
+ * `profile` picks between them: `low | mid` → sub, `high` → main.
25157
+ */
25158
+ streams: object({
25159
+ sub: object({
25160
+ id: string(),
25161
+ bytes: number().optional()
25162
+ }).optional(),
25163
+ main: object({
25164
+ id: string(),
25165
+ bytes: number().optional()
25166
+ }).optional()
25167
+ }).optional(),
25168
+ /** The camera says it holds a sub-stream copy of this recording. */
25169
+ supportSub: boolean().optional(),
25170
+ /**
25171
+ * Whether this row has bytes behind it. ABSENT means yes — every clip that
25172
+ * IS a file is playable, and only a source that lists EVENTS can produce a
25173
+ * row with nothing to play (the measured case: 1 of 29 hub events on 3628
25174
+ * fell inside no file). Such a row is shown, never dropped and never offered
25175
+ * as playable-then-failing (D549 19).
25176
+ */
25177
+ playable: boolean().optional(),
25178
+ /** Why {@link playable} is false, verbatim (`no-file-for-window`). */
25179
+ unplayableReason: string().optional(),
25180
+ /**
25181
+ * This clip is STILL BEING WRITTEN, so {@link ClipSchema.timeRange}`.endMs`
25182
+ * is not its end.
25183
+ *
25184
+ * Absent — the common case — means the row is closed and its `endMs` is the
25185
+ * end of the recording. Present and `true` means the source told us the file
25186
+ * has no end yet, and the `endMs` we carry is whatever the camera's index
25187
+ * entry happened to hold: on a Reolink E1 Outdoor PoE (592, measured
25188
+ * 2026-09-20) the newest file `…_192927_000000_…_0.mp4` reported an `endTime`
25189
+ * of 19:29:56 and STILL reported it eight minutes later, while the file went
25190
+ * on growing. A surface that drew 29 s there was lying about a clip that
25191
+ * plays for minutes, on the one row the operator looks at first.
25192
+ *
25193
+ * `endMs` is deliberately still a number: it is the best bound anything has
25194
+ * for a mint window or a byte fetch, and every consumer already requires it.
25195
+ * This flag says what it is WORTH, and a duration is not drawn from it.
25196
+ */
25197
+ inProgress: boolean().optional()
24747
25198
  });
24748
25199
  var ClipPlaybackSchema = object({
24749
- /** HLS master URL through the hub data-plane (Range + token in path). */
25200
+ /**
25201
+ * A media URL through the hub data-plane. {@link ClipPlaybackSchema.format}
25202
+ * says what kind — an HLS master playlist for a recording-derived clip, a
25203
+ * progressive MP4 for a native file the vendor addon muxed and serves with
25204
+ * `Range`. The URL is NOT a credential: both planes are registered
25205
+ * `access: 'authenticated'` and the hub's `/addon/<id>/<prefix>` proxy takes
25206
+ * the session cookie, so nothing is appended to it (D549 22).
25207
+ */
24750
25208
  playbackUrl: string(),
25209
+ /**
25210
+ * How to play {@link playbackUrl}. Absent means `hls` — the shape every
25211
+ * existing consumer already assumes. A player that branches on this is the
25212
+ * one change a native clip needs; a player that ignores it will hand an MP4
25213
+ * to hls.js and fail parsing it as a manifest.
25214
+ */
25215
+ format: _enum(["hls", "mp4"]).optional(),
25216
+ /**
25217
+ * Which twin was actually served. A clip that holds only one stream answers
25218
+ * with the one it has, and the surface SAYS so — a missing main twin is
25219
+ * never served silently as if it were the asked-for quality (D549 15).
25220
+ */
25221
+ served: CamProfileSchema.optional(),
24751
25222
  /** Optional LAN/remote alternates for the same clip. */
24752
25223
  playbackEndpoints: array(string()).optional(),
24753
25224
  token: string().optional()
24754
25225
  });
25226
+ /**
25227
+ * Why a source cannot answer right now — per SOURCE, never fleet-wide.
25228
+ *
25229
+ * `ok` is the only state whose clip list may be read as complete. The other
25230
+ * three exist because an empty list from a sleeping camera reads as "this
25231
+ * camera has no recordings", which is the defect this whole line of work is
25232
+ * about: a battery camera nobody woke (`sleeping`), a camera that could not be
25233
+ * reached at all (`unreachable`, also what a provider that THREW reports), a
25234
+ * camera whose SD card is not mounted (`no-storage`, measured on 640 —
25235
+ * `HddInfo mount 0`, honestly nothing to list rather than "no clips"), and a
25236
+ * camera whose OWN index disagrees with its OWN calendar (`index-empty`,
25237
+ * measured on 618: the calendar marks 3–20 September, days 6–20 list zero
25238
+ * files on both streams and both filters, with 177 GB free). That last one is
25239
+ * a defect ON THE CAMERA, and the only honest thing a provider can do is say
25240
+ * which days it asked for and got nothing — "no clips" would be a lie about a
25241
+ * card that is full of them.
25242
+ */
25243
+ /**
25244
+ * The operator's authorisation to WAKE a sleeping camera for one clip read.
25245
+ *
25246
+ * ONE value, absent by default, and it is a `force`-shaped operator signal in
25247
+ * exactly the sense `snapshot-wake-gate.ts` uses the word: *"`snapshot.getSnapshot`'s
25248
+ * `force` flag, and nothing else… a background caller must never set it…
25249
+ * Stale but honest beats woken"*. A scheduler, a retry, a reconcile and a
25250
+ * prefetch never set it; a surface sets it only behind the same confirm the
25251
+ * "Wake and refresh" gesture uses, and it is refused below 15 % battery
25252
+ * exactly as that gesture is.
25253
+ *
25254
+ * The gate is decided BEFORE `getApi()`, because on UDP the login IS the wake
25255
+ * (D549) — a read that opened a session and then checked would have woken the
25256
+ * camera to find out it was not allowed to.
25257
+ *
25258
+ * **One authorised yes is one wake.** `next-natural` — take the clip the next
25259
+ * time the camera is awake for its own reasons — is deliberately not a member:
25260
+ * it was proposed, it is free on the battery, and the operator declined it on
25261
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
25262
+ * D558 "Considered and not taken").
25263
+ */
25264
+ var ClipWakeSchema = _enum(["authorised"]);
25265
+ /**
25266
+ * A clip's finished bytes, inline — the twin of `recordingExport.readExportBytes`.
25267
+ *
25268
+ * `bytes` is the DECODED length, so nobody infers it from the base64 length,
25269
+ * and `served` says which twin the caller actually got.
25270
+ */
25271
+ var ClipBytesSchema = object({
25272
+ base64: string(),
25273
+ contentType: string(),
25274
+ /** Suggested filename, extension included. */
25275
+ name: string(),
25276
+ bytes: number().int().nonnegative(),
25277
+ /**
25278
+ * Which twin was actually served — the same contract
25279
+ * {@link ClipPlaybackSchema.served} carries, and REQUIRED here because the
25280
+ * export record persists it: a row read a week later must say the same thing
25281
+ * the panel said at the moment of the tap. A missing main twin is never
25282
+ * served silently as if it were the asked-for quality (D549 15).
25283
+ */
25284
+ served: CamProfileSchema,
25285
+ /**
25286
+ * The DECODED duration of the delivered file, when the fetch measured one.
25287
+ *
25288
+ * A clip fetch verifies its own completion against the catalog row and
25289
+ * retries a materially short pass (D568); this is that measurement, carried
25290
+ * so a consumer can say the same thing rather than re-deriving it. Absent
25291
+ * when the producer did not measure — never zero, which would say the file
25292
+ * is empty.
25293
+ */
25294
+ durationMs: number().positive().optional()
25295
+ });
25296
+ /**
25297
+ * Where a clip's STREAM can be dialled (D597) — the answer to
25298
+ * {@link videoclipsCapability.methods.dialClipStream}.
25299
+ *
25300
+ * `url` is plain `http://` on the provider's own token-gated listener, one
25301
+ * shot, short-lived. It is NOT the hub-proxied `/addon/<id>/clip-stream/…`
25302
+ * path: that plane takes only a user credential, which an addon does not
25303
+ * hold, and the framework offers no addon→addon byte transport — the one
25304
+ * precedent is the broker's own restream URLs (`getStreamWithCodec`), which
25305
+ * a consumer dials directly, and this is that precedent one addon over.
25306
+ */
25307
+ /**
25308
+ * The audio a clip stream can be asked for, and the audio it carries.
25309
+ *
25310
+ * The camera stores AAC and no WebRTC session speaks it, so a stream that
25311
+ * carries sound carries it TRANSCODED, muxed as a second track beside the
25312
+ * video by the one ffmpeg the provider already runs (D600). `opus` is
25313
+ * 48 kHz mono, `pcmu` is 8 kHz µ-law — the two codecs a broker session
25314
+ * negotiates — and `none` is a stream asked for, or served, without sound.
25315
+ */
25316
+ var ClipStreamAudioSchema = _enum([
25317
+ "opus",
25318
+ "pcmu",
25319
+ "none"
25320
+ ]);
25321
+ /**
25322
+ * Why a stream that was ASKED for sound is not carrying it (D600).
25323
+ *
25324
+ * Only ever set beside `servedAudio: 'none'` after a non-`none` ask. Absent
25325
+ * means the ask was honoured, or there was no ask.
25326
+ *
25327
+ * - `not-requested` — the caller asked for `none`.
25328
+ * - `unsupported-ask` — the provider cannot produce that codec.
25329
+ *
25330
+ * Whether the RECORDING has sound at all is not knowable here: the camera
25331
+ * declares no audio track before its first frame arrives. That answer is in
25332
+ * the stream itself — an fMP4 with no audio track — and the broker reads it
25333
+ * there. A dial that says `opus` promises the codec, never the content.
25334
+ */
25335
+ var ClipStreamAudioReasonSchema = _enum(["not-requested", "unsupported-ask"]);
25336
+ var ClipStreamDialSchema = object({
25337
+ url: string().min(1),
25338
+ /** After this the token is gone and the URL answers 404 `dial-expired`. */
25339
+ expiresAtMs: number(),
25340
+ /** What the stream IS — a consumer that cannot honour it must not dial. */
25341
+ transport: literal("forward-only"),
25342
+ /**
25343
+ * Which twin the stream ACTUALLY carries — not what was asked for.
25344
+ *
25345
+ * The stream serves every profile (D599), and a recording whose main twin
25346
+ * does not exist is streamed as its sub copy, which this field reports as
25347
+ * `low`. That is the whole reason the dial no longer refuses `high`:
25348
+ * stating what was served is what makes accepting honest, where a silent
25349
+ * substitution would be the 33x lie D576 forbids. Stated rather than
25350
+ * assumed, the way {@link ClipPlaybackSchema.served} is (D549 15).
25351
+ */
25352
+ served: CamProfileSchema,
25353
+ /**
25354
+ * Which audio the stream will ACTUALLY carry — `served`'s companion for
25355
+ * sound, and the same reasoning (D599, D600).
25356
+ *
25357
+ * The broker asks for the codec its WebRTC session negotiated, and the
25358
+ * provider either muxes that codec or carries none. Stating it is what
25359
+ * lets the broker wire the matching sink before the first byte, and what
25360
+ * keeps a session from being handed a codec it cannot send — the audio
25361
+ * shape of the substitution `served` exists to prevent.
25362
+ *
25363
+ * It is a promise about the CODEC, not about the content: a recording with
25364
+ * no microphone track answers `opus` here and then arrives with no audio
25365
+ * track at all. That second answer is in the stream, and the broker reads
25366
+ * it from the `moov`.
25367
+ */
25368
+ servedAudio: ClipStreamAudioSchema,
25369
+ /** Why `servedAudio` is `none` although sound was asked for. */
25370
+ audioReason: ClipStreamAudioReasonSchema.optional()
25371
+ });
25372
+ var ClipSourceAvailabilitySchema = object({
25373
+ state: _enum([
25374
+ "ok",
25375
+ "sleeping",
25376
+ "unreachable",
25377
+ "no-storage",
25378
+ "index-empty"
25379
+ ]),
25380
+ /** Free text, shown verbatim. Names the camera's own refusal when there is one. */
25381
+ reason: string().optional(),
25382
+ /** When this source's catalog was last CONFIRMED. A cached list is never
25383
+ * drawn as current: the surface shows the age whenever it is older than the
25384
+ * refresh interval. */
25385
+ catalogAsOf: number().optional()
25386
+ });
25387
+ /**
25388
+ * One SOURCE of clips for a camera — a row of the picker, and the namespace
25389
+ * every clip id from it is prefixed with (`analytics`,
25390
+ * `native:reolink:onboard`, `hksv`, …).
25391
+ *
25392
+ * A provider lists the sources IT serves for that device, and answers for each
25393
+ * of them whether it can answer at all. A provider with nothing to offer on a
25394
+ * camera returns `[]` — it is not that camera's business.
25395
+ */
25396
+ var ClipSourceSchema = object({
25397
+ /** The value this source stamps on {@link ClipSchema.source}, and the prefix
25398
+ * of every clip id it mints. `getClipPlayback` routes on it. */
25399
+ source: string(),
25400
+ /** Operator-facing name of the source ("CamStack recordings", "SD card"). */
25401
+ label: string(),
25402
+ /**
25403
+ * The addon that SERVES this row.
25404
+ *
25405
+ * A surface cannot otherwise resolve a source to who answers it, and the
25406
+ * alternative — a `native:reolink:* → provider-reolink` table inside the
25407
+ * widget — is a second authority on provider identity living in the one
25408
+ * package with no business knowing it, wrong the day a third source appears
25409
+ * (D557). One addon may serve SEVERAL sources, which is why the per-device
25410
+ * switch is keyed by SOURCE and not by this (D555) — `addon-provider-reolink`
25411
+ * served a hub child two of them until the two views were measured to be one
25412
+ * store read twice (D555) and then read ONE way (D565).
25413
+ *
25414
+ * Optional for version skew only. The collection dispatcher stamps it from
25415
+ * the registry, so a row that travelled through the fan-out carries the
25416
+ * authoritative id whatever the provider filled in.
25417
+ */
25418
+ addonId: string().optional(),
25419
+ availability: ClipSourceAvailabilitySchema
25420
+ });
24755
25421
  DeviceType.Camera, method(object({
24756
25422
  deviceId: number(),
24757
25423
  since: number(),
24758
25424
  until: number(),
24759
- limit: number().int().positive().optional()
25425
+ limit: number().int().positive().optional(),
25426
+ /**
25427
+ * WHICH provider to ask — the `addonId` a {@link ClipSourceSchema} row
25428
+ * carries, never a source id and never a list. **Required** (D554 amended).
25429
+ *
25430
+ * A provider the device is not bound to is refused by name rather than
25431
+ * answered by another one (D552's `rejectUnresolvedAddonPin` rule).
25432
+ *
25433
+ * It was optional, documented as "absent means the device's BOUND
25434
+ * provider, which is CamStack on every camera". No code implemented
25435
+ * that. Measured on the live hub 2026-09-20 — device 592, bound to
25436
+ * `recorder` AND `provider-reolink` — a bare call with `limit: 3`
25437
+ * answered SIX rows, three from each source, merged newest-first:
25438
+ * `device-collection-dispatch.ts` simply left the fan-out un-narrowed,
25439
+ * so absence bought the union this method exists not to be, and
25440
+ * `limit` meant `limit × sources`.
25441
+ *
25442
+ * There is nothing to restore the default to. `getBindings` answers a
25443
+ * collection cap with a DERIVED, PLURAL set (D554 amended, step 0);
25444
+ * `setWrapperActive` — the singleton authority that could name one —
25445
+ * throws for a collection cap by design. Naming CamStack here instead
25446
+ * would privilege one addon by id inside a surface whose premise is
25447
+ * that sources are peers, and would be wrong on the first camera with
25448
+ * no recorder binding. So absence is REFUSED, in the schema, where the
25449
+ * generated types make it unomittable rather than merely discouraged.
25450
+ *
25451
+ * The default belongs to the SURFACE, which has the `listSources` rows
25452
+ * and can say which one it picked (D569 § 1–3; the viewer already
25453
+ * always sends this).
25454
+ *
25455
+ * It replaced `sources?: string[]`, a VIEW filter over source ids that
25456
+ * assumed the answer was a fan-out over everything a camera has. The
25457
+ * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
25458
+ * solo"* — so the list is asked of one provider at a time and there is
25459
+ * nothing to filter out of it.
25460
+ */
25461
+ provider: string().min(1)
24760
25462
  }), array(ClipSchema).readonly(), {
24761
25463
  kind: "query",
24762
- auth: "admin"
25464
+ auth: "protected"
25465
+ }), method(object({ deviceId: number() }), array(ClipSourceSchema).readonly(), {
25466
+ kind: "query",
25467
+ auth: "protected"
24763
25468
  }), method(object({
24764
25469
  deviceId: number(),
24765
- clipId: string()
25470
+ clipId: string(),
25471
+ /**
25472
+ * Which twin to serve, on the ONE quality scale the system already has
25473
+ * (`CamProfileSchema`). `low | mid` → the sub file, `high` → the main
25474
+ * twin; both ids are already on the row so this never re-searches the
25475
+ * camera. Absent means the provider's own default (the sub file, which
25476
+ * every source is measured to hold).
25477
+ *
25478
+ * `auto` is deliberately NOT accepted here: a stored file has no
25479
+ * broker session, so the adaptive tier cannot be resolved for it. The
25480
+ * viewer resolves `auto` to a profile the same way live does, before
25481
+ * it calls (D549 15).
25482
+ */
25483
+ profile: CamProfileSchema.optional()
24766
25484
  }), ClipPlaybackSchema, {
24767
25485
  kind: "query",
24768
- auth: "admin"
25486
+ auth: "protected"
25487
+ }), optionalMethod(object({
25488
+ deviceId: number(),
25489
+ clipId: string().min(1),
25490
+ /**
25491
+ * WHICH provider holds the bytes — the `addonId` a
25492
+ * {@link ClipSourceSchema} row carries. **Required**, for the reason
25493
+ * `listClips` states one method above and for a second one this method
25494
+ * learned the hard way.
25495
+ *
25496
+ * The caller is the stream broker, and the broker lives in
25497
+ * `addon-pipeline` — the same addon that registers the RECORDER's
25498
+ * videoclips provider. With no provider named, the in-process
25499
+ * resolution handed it its own sibling, which implements this method
25500
+ * solely to explain that an analytics visit has no file behind it. So
25501
+ * every onboard clip answered with that sentence, a flash of video and
25502
+ * nothing more, while the SAME call from outside the addon reached the
25503
+ * right provider and returned the bytes (measured on 3628,
25504
+ * 2026-09-21).
25505
+ */
25506
+ provider: string().min(1),
25507
+ /**
25508
+ * Which twin to fetch, on the mapping D549 15 already fixed:
25509
+ * `low | mid` → the sub file, `high` → the main twin. `auto` is not
25510
+ * accepted here for the same reason it is not accepted by
25511
+ * `getClipPlayback` — a stored file has no broker session, so the
25512
+ * adaptive tier cannot be resolved for it.
25513
+ */
25514
+ profile: CamProfileSchema.optional(),
25515
+ /**
25516
+ * The CALLER's byte bound, so an over-size clip is refused before it is
25517
+ * read and encoded rather than after. Capped by
25518
+ * {@link VIDEOCLIPS_MAX_READ_BYTES} whatever is passed; absent means
25519
+ * that ceiling.
25520
+ */
25521
+ maxBytes: number().int().positive().optional(),
25522
+ /**
25523
+ * The operator's authorisation to wake a sleeping camera for this
25524
+ * read. Absent — the default — means a sleeping standalone battery
25525
+ * camera is REFUSED by name, before any session is opened.
25526
+ */
25527
+ wake: ClipWakeSchema.optional()
25528
+ }), ClipBytesSchema, {
25529
+ kind: "query",
25530
+ auth: "protected"
25531
+ }), optionalMethod(object({
25532
+ deviceId: number(),
25533
+ clipId: string().min(1),
25534
+ /** WHICH provider holds the camera — see `readClipBytes.provider`. */
25535
+ provider: string().min(1),
25536
+ /** Which twin — `high` → the main file, everything else the sub (D599). */
25537
+ profile: CamProfileSchema.optional(),
25538
+ /**
25539
+ * Which audio codec the caller's session negotiated, so the provider
25540
+ * can mux it (D600). The ask travels on the DIAL because the mux is
25541
+ * spawned once, before the first byte, and cannot gain a track later.
25542
+ *
25543
+ * ABSENT IS `none`, never a guess: a caller that does not state an
25544
+ * ask is served the video-only stream that shipped first, so a broker
25545
+ * older than this field keeps working unchanged (D382 — an unstated
25546
+ * ask is zero).
25547
+ */
25548
+ audio: ClipStreamAudioSchema.optional()
25549
+ }), ClipStreamDialSchema, {
25550
+ kind: "query",
25551
+ auth: "protected"
24769
25552
  });
24770
25553
  /**
24771
25554
  * Optional client-side hints sent at session creation to help the provider
@@ -27017,6 +27800,346 @@ var dayNightCapability = {
27017
27800
  volatileStateFields: ["lastFetchedAt"]
27018
27801
  };
27019
27802
  /**
27803
+ * Vendor-neutral **onboard** recording + storage cap — what the CAMERA
27804
+ * writes to the CAMERA's own card, on the camera's own schedule.
27805
+ *
27806
+ * This is NOT `recording.cap.ts`. That one is CamStack's recorder: our
27807
+ * footage ledger, our storage locations, our retention. This one has a
27808
+ * different authority — the camera's firmware — and per D62 it stores
27809
+ * nothing of its own. Every value here is read from the camera and every
27810
+ * write goes back to the camera; there is no CamStack-side mirror that
27811
+ * could disagree with the device.
27812
+ *
27813
+ * ## One shape, two firmwares
27814
+ *
27815
+ * Measured 2026-09-22 against the live fleet:
27816
+ *
27817
+ * | fact | Hikvision (ISAPI) | Reolink (Baichuan) |
27818
+ * | --- | --- | --- |
27819
+ * | storage | `ContentMgmt/Storage` `<hdd>` rows: status, capacity, freeSpace (MB) | `getHddInfoList` (cmd 102): `mount`, `format`, `capacity` GB + `capacityM` MB remainder |
27820
+ * | tracks | several (101 **and** 103 on both 1436 and 3833), each with its own schedule | one per channel |
27821
+ * | schedule | per track, 7 `ScheduleAction` blocks: DayOfWeek + TimeOfDay range + ONE `ActionRecordingMode` | per trigger type, a 168-char weekly HOUR mask |
27822
+ * | triggers | `CMR`, `MOTION` | `Normal`, `MD`, `people`, `vehicle`, `dog_cat`, `crossline`, `intrude`, `loitering` |
27823
+ * | pre-record | `PreRecordTimeSeconds` | `preRecordTime` |
27824
+ * | post-record | `PostRecordTimeSeconds` | `recordDelayTime` |
27825
+ * | overwrite | per track `LoopEnable` | `cycle`, with `cycleList` enumerating the accepted values |
27826
+ * | segment length | not exposed on V5.7.1 | `packageTime` (minutes) |
27827
+ *
27828
+ * The two schedule models look different and are the same thing in
27829
+ * different coordinates: both answer "for this trigger, during which
27830
+ * weekly windows does the camera record". {@link RecordWindow} is that
27831
+ * question in one shape — Hikvision's ranges map straight onto it,
27832
+ * Reolink's mask expands into hour-aligned windows.
27833
+ *
27834
+ * ## Union, not intersection
27835
+ *
27836
+ * **The same fields exist on every camera.** What differs per device is
27837
+ * which VALUES that device accepts, and that is what {@link
27838
+ * RecordingOnboardOptions} reports — a `{ readable, writable, reason }`
27839
+ * per field plus the schedule's own limits. A control a camera cannot
27840
+ * honour is rendered DISABLED WITH ITS REASON, never missing and never
27841
+ * dead: disabled must not look like broken.
27842
+ *
27843
+ * ## Refusal by name
27844
+ *
27845
+ * A write a camera cannot honour is refused with a sentence the operator
27846
+ * can read — never accepted and dropped. Both providers refuse through
27847
+ * {@link describeOnboardRefusal}, so the vocabulary is one function and
27848
+ * one test, not two hand-written vendor opinions.
27849
+ *
27850
+ * Follows the D14 `deviceConfig` archetype (see `stream-params.cap.ts`):
27851
+ * `getOptions` advertises per-camera availability, `getStatus` (auto-
27852
+ * injected from `status`) reports the live values, and a single
27853
+ * `setSettings` mutation applies a partial change. No hand-written
27854
+ * settings-contribution methods.
27855
+ */
27856
+ /**
27857
+ * What makes the camera start recording during a window.
27858
+ *
27859
+ * The union of both vendors' vocabularies. `continuous` is Hikvision's
27860
+ * `CMR` and Reolink's `Normal`; `motion` is `MOTION` / `MD`. The
27861
+ * object-class triggers are Reolink-only today and the smart-event ones
27862
+ * (`lineCrossing`, `intrusion`, `loitering`) are Reolink-only on the
27863
+ * firmwares measured — a camera that cannot record on a trigger simply
27864
+ * does not list it in `options.schedule.triggers`, and a window naming
27865
+ * it is REFUSED, not dropped.
27866
+ */
27867
+ var RecordTriggerSchema = _enum([
27868
+ "continuous",
27869
+ "motion",
27870
+ "person",
27871
+ "vehicle",
27872
+ "animal",
27873
+ "lineCrossing",
27874
+ "intrusion",
27875
+ "loitering",
27876
+ "alarmInput"
27877
+ ]);
27878
+ /**
27879
+ * One weekly recording window: "on `day`, from `startMinute` to
27880
+ * `endMinute`, record on `trigger`".
27881
+ *
27882
+ * `day` is 0 = Monday … 6 = Sunday (ISO order, which is also the order
27883
+ * both firmwares enumerate). Minutes are local camera time since
27884
+ * midnight; `endMinute` may be 1440, meaning end of day — that is
27885
+ * Hikvision's literal `24:00` and Reolink's 24th mask slot, and
27886
+ * collapsing it to 0 would turn a whole-day window into an empty one.
27887
+ */
27888
+ var RecordWindowSchema = object({
27889
+ trigger: RecordTriggerSchema,
27890
+ day: number().int().min(0).max(6),
27891
+ startMinute: number().int().min(0).max(1439),
27892
+ endMinute: number().int().min(1).max(1440)
27893
+ });
27894
+ /** Status of one physical volume, as the camera itself describes it. */
27895
+ var OnboardStorageVolumeSchema = object({
27896
+ /** The camera's own id for the volume (`hdd/id`, Reolink `HddInfo/number`). */
27897
+ id: string(),
27898
+ /** The camera's own name for it, when it gives one (`hddName`). */
27899
+ label: string().optional(),
27900
+ status: _enum([
27901
+ "ok",
27902
+ "unformatted",
27903
+ "error",
27904
+ "offline",
27905
+ "unknown"
27906
+ ]),
27907
+ /**
27908
+ * Total size in MB, or **null when the camera did not say**.
27909
+ *
27910
+ * Never 0 for an unreadable value: a measurement that failed is not a
27911
+ * measurement (D393), and a card whose size is unknown must not be
27912
+ * rendered as a card of size zero.
27913
+ */
27914
+ capacityMb: number().nullable(),
27915
+ /**
27916
+ * Free space in MB, or null when unknown.
27917
+ *
27918
+ * **Not a proxy for "has footage".** Measured 2026-09-22: 1436 and
27919
+ * 1439 both report exactly 11776 MB free — the fixed reserve a looping
27920
+ * card converges on once it has wrapped. At loop steady state the
27921
+ * number is identical whether the camera recorded yesterday or stopped
27922
+ * a month ago.
27923
+ */
27924
+ freeMb: number().nullable(),
27925
+ /** True when the camera reports the volume writable (`property` RW). */
27926
+ writable: boolean().optional()
27927
+ });
27928
+ /**
27929
+ * What the camera is doing with its own storage, right now.
27930
+ *
27931
+ * Every scalar is nullable and **null means the camera did not answer**,
27932
+ * never a default. A form that seeds `0` from an unanswered read invites
27933
+ * the operator to save that 0 back onto the camera.
27934
+ */
27935
+ var RecordingOnboardStatusSchema = object({
27936
+ storage: discriminatedUnion("kind", [
27937
+ object({
27938
+ kind: literal("present"),
27939
+ volumes: array(OnboardStorageVolumeSchema)
27940
+ }),
27941
+ object({
27942
+ kind: literal("absent"),
27943
+ reason: string()
27944
+ }),
27945
+ object({
27946
+ kind: literal("unknown"),
27947
+ reason: string()
27948
+ })
27949
+ ]),
27950
+ tracks: array(object({
27951
+ id: string(),
27952
+ enabled: boolean(),
27953
+ isVideo: boolean(),
27954
+ /** From the camera's own track description. Null when it does not say. */
27955
+ codec: string().nullable(),
27956
+ resolution: string().nullable(),
27957
+ /** Per-track overwrite flag, where the firmware keeps it per track. */
27958
+ overwriteWhenFull: boolean().nullable()
27959
+ })),
27960
+ /**
27961
+ * The track the write path targets — the enabled VIDEO one. Null when
27962
+ * no track could be identified, which is itself a refusal reason.
27963
+ */
27964
+ primaryTrackId: string().nullable(),
27965
+ /** Master "record to the card at all" switch. */
27966
+ enabled: boolean().nullable(),
27967
+ overwriteWhenFull: boolean().nullable(),
27968
+ preRecordSec: number().nullable(),
27969
+ postRecordSec: number().nullable(),
27970
+ /** Length of one recorded file, in minutes. */
27971
+ segmentMinutes: number().nullable(),
27972
+ /** The primary track's weekly windows, flattened. */
27973
+ windows: array(RecordWindowSchema),
27974
+ /**
27975
+ * How many windows the camera described that CamStack could NOT read —
27976
+ * an unrecognised trigger, an unparseable clock, a weekday it does not
27977
+ * name.
27978
+ *
27979
+ * A dropped window is work the reader threw away, and a schedule that
27980
+ * silently shows fewer rows than the camera holds is how an operator
27981
+ * saves back a schedule shorter than the one they were looking at
27982
+ * (D391). Non-zero means the window list is INCOMPLETE and a write
27983
+ * that replaces it would delete what was not shown — which is why a
27984
+ * provider reporting a non-zero count also reports the schedule as not
27985
+ * writable.
27986
+ */
27987
+ unreadableWindows: number(),
27988
+ /**
27989
+ * The camera is scheduled to record and has NO usable storage.
27990
+ *
27991
+ * A first-class fact because it is the fleet's most common silent
27992
+ * defect: measured 2026-09-22, 1441 and 3831 are both motion-recording
27993
+ * to a card that is not there. Neither the schedule nor the storage
27994
+ * read says anything wrong on its own; only the pair does.
27995
+ */
27996
+ recordingToNowhere: boolean(),
27997
+ lastFetchedAt: number()
27998
+ });
27999
+ /** Numeric range descriptor — `{ min, max, step }` per the getOptions convention. */
28000
+ var RangeSchema = object({
28001
+ min: number(),
28002
+ max: number(),
28003
+ step: number()
28004
+ });
28005
+ /**
28006
+ * The values a camera actually takes for a numeric field, when they are a SET
28007
+ * rather than a range.
28008
+ *
28009
+ * `{min,max,step}` cannot say what these two firmwares do. Measured on 1436
28010
+ * (I91DN) on 2026-09-22 by writing each value and reading it back:
28011
+ *
28012
+ * - pre-record: `0, 5, 10, 15, 20, 25, 30` and `2147483647` (INT32_MAX, the
28013
+ * camera's "no limit" — `-1` and `4294967295` both land on it);
28014
+ * - post-record: `5, 10, 30, 60, 120, 300, 600`.
28015
+ *
28016
+ * Neither is expressible as a step: the first has a sentinel two billion away
28017
+ * from its neighbours, the second doubles and then jumps. A range that tried
28018
+ * would forbid values the camera takes AND permit values it silently replaces
28019
+ * with 5 — wrong in both directions at once.
28020
+ *
28021
+ * `sentinel` names the member that is not a duration, so a surface can render
28022
+ * "no limit" instead of `2147483647` seconds.
28023
+ */
28024
+ var AllowedValuesSchema = object({
28025
+ values: array(number()).min(1),
28026
+ sentinel: object({
28027
+ value: number(),
28028
+ meaning: _enum(["no-limit", "disabled"])
28029
+ }).optional()
28030
+ });
28031
+ /**
28032
+ * Per-field availability on ONE camera.
28033
+ *
28034
+ * The field exists on every camera — this says whether this one can be
28035
+ * read and whether it can be written, and `reason` says why not when
28036
+ * either is false. The UI renders the control DISABLED with the reason
28037
+ * rather than hiding it, so a limitation is legible instead of looking
28038
+ * like a missing feature.
28039
+ */
28040
+ var OnboardFieldSupportSchema = object({
28041
+ readable: boolean(),
28042
+ writable: boolean(),
28043
+ /** Required whenever `readable` or `writable` is false. */
28044
+ reason: string().optional()
28045
+ });
28046
+ /** What this camera's schedule model can express. */
28047
+ var OnboardScheduleSupportSchema = object({
28048
+ support: OnboardFieldSupportSchema,
28049
+ /**
28050
+ * The smallest time step the camera can express, in minutes.
28051
+ *
28052
+ * Hikvision takes arbitrary minutes (`00:05:00`–`23:57:00` observed on
28053
+ * 1436's track 103). Reolink's schedule is a 7×24 HOUR mask, so 60. A
28054
+ * window whose edges are not a multiple of this is REFUSED rather than
28055
+ * quietly rounded — rounding is how an operator's 06:30 becomes 06:00
28056
+ * and nothing says so.
28057
+ */
28058
+ granularityMinutes: number(),
28059
+ /** Triggers this camera can record on. A window naming another is refused. */
28060
+ triggers: array(RecordTriggerSchema),
28061
+ /**
28062
+ * False when the camera stores ONE trigger per time range, so two
28063
+ * windows overlapping on the same day cannot carry different triggers.
28064
+ * True on Reolink, whose mask is per-trigger and independent.
28065
+ */
28066
+ supportsOverlappingTriggers: boolean()
28067
+ });
28068
+ var RecordingOnboardOptionsSchema = object({
28069
+ enabled: OnboardFieldSupportSchema,
28070
+ overwriteWhenFull: OnboardFieldSupportSchema,
28071
+ preRecordSec: OnboardFieldSupportSchema,
28072
+ preRecordSecRange: RangeSchema.optional(),
28073
+ /** Preferred over the range when the camera takes a SET, not a span. */
28074
+ preRecordSecAllowed: AllowedValuesSchema.optional(),
28075
+ postRecordSec: OnboardFieldSupportSchema,
28076
+ postRecordSecRange: RangeSchema.optional(),
28077
+ /** Preferred over the range when the camera takes a SET, not a span. */
28078
+ postRecordSecAllowed: AllowedValuesSchema.optional(),
28079
+ segmentMinutes: OnboardFieldSupportSchema,
28080
+ segmentMinutesRange: RangeSchema.optional(),
28081
+ /** Preferred over the range when the camera takes a SET, not a span. */
28082
+ segmentMinutesAllowed: AllowedValuesSchema.optional(),
28083
+ schedule: OnboardScheduleSupportSchema
28084
+ });
28085
+ /**
28086
+ * A partial change. Every field optional.
28087
+ *
28088
+ * Unlike the other `deviceConfig` caps, a provider here does **NOT**
28089
+ * silently ignore a field it cannot support — it refuses, by name,
28090
+ * through {@link describeOnboardRefusal}. Silence on a recording setting
28091
+ * is the failure D62 exists to prevent: the operator believes the camera
28092
+ * is recording the way the form says, and it is not.
28093
+ */
28094
+ var RecordingOnboardPatchSchema = object({
28095
+ enabled: boolean().optional(),
28096
+ overwriteWhenFull: boolean().optional(),
28097
+ preRecordSec: number().optional(),
28098
+ postRecordSec: number().optional(),
28099
+ segmentMinutes: number().optional(),
28100
+ /** The complete new window set for the primary track — not a delta. */
28101
+ windows: array(RecordWindowSchema).optional()
28102
+ });
28103
+ var recordingOnboardCapability = {
28104
+ name: "recording-onboard",
28105
+ scope: "device",
28106
+ deviceNative: true,
28107
+ mode: "singleton",
28108
+ deviceTypes: [DeviceType.Camera],
28109
+ deviceConfig: { ui: {
28110
+ kind: "derived-form",
28111
+ builderId: "recording-onboard",
28112
+ tab: "recording"
28113
+ } },
28114
+ methods: {
28115
+ getOptions: method(object({ deviceId: number() }), RecordingOnboardOptionsSchema),
28116
+ setSettings: method(object({
28117
+ deviceId: number(),
28118
+ settings: RecordingOnboardPatchSchema
28119
+ }), _void(), {
28120
+ kind: "mutation",
28121
+ auth: "admin"
28122
+ })
28123
+ },
28124
+ status: {
28125
+ schema: RecordingOnboardStatusSchema,
28126
+ kind: "poll"
28127
+ },
28128
+ runtimeState: RecordingOnboardStatusSchema,
28129
+ /**
28130
+ * Runtime-state durability: **restored** — operator-set camera-side
28131
+ * recording config; mutation-driven, and the storage half is the last
28132
+ * thing the camera said about its own card.
28133
+ *
28134
+ * See `RuntimeStateDurability`. Enforced by
28135
+ * `scripts/check-runtime-state-durability.ts`.
28136
+ */
28137
+ durability: "restored",
28138
+ /** Clock fields: written, but excluded from the compare that decides
28139
+ * whether persisting is worth a SQLite commit. */
28140
+ volatileStateFields: ["lastFetchedAt"]
28141
+ };
28142
+ /**
27020
28143
  * Generic device-level status snapshot. Auto-registered by `BaseDevice`
27021
28144
  * for every device, regardless of provider — the kernel needs a uniform
27022
28145
  * cap-keyed slice for the basic device flags every consumer expects to
@@ -32419,7 +33542,7 @@ method(object({
32419
33542
  auth: "admin"
32420
33543
  }), method(object({ deviceId: number() }), RecordingConfigSchema, {
32421
33544
  kind: "query",
32422
- auth: "admin"
33545
+ auth: "protected"
32423
33546
  }), method(object({
32424
33547
  deviceId: number(),
32425
33548
  profile: string(),
@@ -32649,13 +33772,101 @@ var ExportStateSchema = _enum([
32649
33772
  "expired",
32650
33773
  "deleted"
32651
33774
  ]);
32652
- /** One export job / history row. */
33775
+ /**
33776
+ * WHAT an export is — the authority, as opposed to the four top-level fields
33777
+ * the Library sorts and labels on (D558 § 2.2).
33778
+ *
33779
+ * Read it through {@link exportSubjectOf}, never off the record directly: the
33780
+ * field is optional for the history rows written before it existed, and that
33781
+ * absence has exactly one interpreter.
33782
+ */
33783
+ var ExportSubjectSchema = discriminatedUnion("kind", [object({
33784
+ kind: literal("footage"),
33785
+ deviceId: number(),
33786
+ profiles: array(string()).min(1),
33787
+ fromMs: number(),
33788
+ toMs: number()
33789
+ }), object({
33790
+ kind: literal("clip"),
33791
+ deviceId: number(),
33792
+ provider: string().min(1),
33793
+ source: string().min(1),
33794
+ sourceLabel: string().min(1),
33795
+ clipId: string().min(1),
33796
+ /**
33797
+ * WHERE IN THE CATALOG to confirm this clip — the day window the surface was
33798
+ * already listing when the operator picked the segment.
33799
+ *
33800
+ * It is **not** a time range control and it never becomes one: the record's
33801
+ * `fromMs`/`toMs` come from the catalog ROW and from nothing a caller
33802
+ * supplied (D558 § 5.4.3), and a window that does not contain the clip is a
33803
+ * `catalog-miss`, not a silently wider search. It exists because
33804
+ * `videoclips.listClips` takes `since`/`until` and has no by-handle twin:
33805
+ * the catalog check D558 asks for is literally that call, and a call needs a
33806
+ * window. The surface has one — `ClipsBrowser`'s `dayWindow`, one local
33807
+ * wall-clock day, which is also the only width measured to be cheap (one
33808
+ * day on 592 lists 171 clips in 712 ms, a busy day 546 in 2.0 s; twenty days
33809
+ * of one child's events took 3.6–5.4 s).
33810
+ */
33811
+ catalogWindow: object({
33812
+ sinceMs: number(),
33813
+ untilMs: number()
33814
+ }),
33815
+ profile: _enum([
33816
+ "high",
33817
+ "mid",
33818
+ "low"
33819
+ ]),
33820
+ /**
33821
+ * The operator's authorisation to wake a sleeping camera for this export.
33822
+ * ONE definition, in the cap that owns the clip read ({@link ClipWakeSchema}),
33823
+ * because the gate that honours it is the clip provider's sleep gate — a
33824
+ * second enum here would be a second contract. Absent by default; never
33825
+ * settable by a scheduler or a retry.
33826
+ *
33827
+ * **One authorised yes is ONE wake.** A failed clip export is retried only by
33828
+ * an operator act that asks again; a queued job that outlived its wake fails
33829
+ * with a reason rather than waking on its turn; and this subject carries
33830
+ * exactly one profile precisely so one tap is never two fetches (D558 § 5.2).
33831
+ */
33832
+ wake: ClipWakeSchema.optional()
33833
+ })]);
33834
+ /**
33835
+ * One export job / history row.
33836
+ *
33837
+ * **`subject` is the AUTHORITY on what was exported. `deviceId`, `profile`,
33838
+ * `fromMs` and `toMs` are its PROJECTION** — kept top-level because the whole
33839
+ * Library sorts and labels on them (`library-items.ts` orders an export by
33840
+ * `fromMs`; `export-format.ts` draws `rangeLabel` from the pair), and a row
33841
+ * that did not fill them would sort under the epoch and render a blank range.
33842
+ * Write to the subject and read from the projection and the two will disagree;
33843
+ * the projection is derived at creation and never edited afterwards.
33844
+ *
33845
+ * And they mean DIFFERENT FACTS for the two kinds, which is the part a reader
33846
+ * who knows only the recording export will get wrong:
33847
+ *
33848
+ * | field | `kind:'footage'` | `kind:'clip'` |
33849
+ * | --- | --- | --- |
33850
+ * | `fromMs`/`toMs` | the stretch the operator ASKED for | the camera's own boundaries, always `clip.timeRange`, never anything a caller supplied |
33851
+ * | `profile` | the stream rendered | the twin actually SERVED (`subject.profile` is the one asked for) |
33852
+ *
33853
+ * Same type, different fact — the shape this repo keeps getting wrong (D385's
33854
+ * two authorities, D224's second copy).
33855
+ */
32653
33856
  var ExportRecordSchema = object({
32654
33857
  id: string(),
32655
33858
  deviceId: number(),
32656
33859
  profile: string(),
32657
33860
  fromMs: number(),
32658
33861
  toMs: number(),
33862
+ /**
33863
+ * What this export IS. Optional ONLY for the rows written before D558: the
33864
+ * store parses every row through this schema on every read, so a required
33865
+ * field would make the export AUDIT — which is the whole reason rows survive
33866
+ * file deletion — unreadable in one release. Absence means `footage`, and
33867
+ * {@link exportSubjectOf} is the one place that says so.
33868
+ */
33869
+ subject: ExportSubjectSchema.optional(),
32659
33870
  options: ExportOptionsSchema,
32660
33871
  state: ExportStateSchema,
32661
33872
  /** 0–100 while rendering; null otherwise. */
@@ -32672,6 +33883,18 @@ var ExportRecordSchema = object({
32672
33883
  /** Failure reason when state is 'failed'; null otherwise. */
32673
33884
  error: string().nullable()
32674
33885
  });
33886
+ _enum([
33887
+ "catalog-miss",
33888
+ "catalog-unreachable",
33889
+ "no-file-for-window",
33890
+ "clip-in-progress",
33891
+ "unsupported-option",
33892
+ "sleeping",
33893
+ "camera-refused",
33894
+ "fetch-failed",
33895
+ "too-large-to-transfer",
33896
+ "wake-expired"
33897
+ ]);
32675
33898
  /** Candidate download URLs (LAN first, then operator extra hosts). */
32676
33899
  var ExportDownloadSchema = object({
32677
33900
  url: string(),
@@ -32690,16 +33913,50 @@ var ExportBytesSchema = object({
32690
33913
  name: string(),
32691
33914
  bytes: number().int().nonnegative()
32692
33915
  });
33916
+ /** Canonical `profiles[]`, falling back to the legacy singular `profile`. */
33917
+ function resolveExportProfiles(input) {
33918
+ if (input.profiles !== void 0 && input.profiles.length > 0) return [...input.profiles];
33919
+ if (typeof input.profile === "string" && input.profile.length > 0) return [input.profile];
33920
+ return [];
33921
+ }
32693
33922
  method(object({
32694
33923
  deviceId: number(),
32695
33924
  /** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
32696
33925
  profile: string().optional(),
32697
33926
  profiles: array(string()).min(1).optional(),
32698
- fromMs: number(),
32699
- toMs: number(),
33927
+ /** Footage only — a clip's boundaries are the camera's. */
33928
+ fromMs: number().optional(),
33929
+ toMs: number().optional(),
33930
+ /** What to export. Absent means the legacy flat footage request. */
33931
+ subject: ExportSubjectSchema.optional(),
32700
33932
  options: ExportOptionsSchema
32701
33933
  }).superRefine((v, ctx) => {
32702
- if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
33934
+ if (v.subject?.kind === "clip") {
33935
+ if (v.subject.deviceId !== v.deviceId) ctx.addIssue({
33936
+ code: ZodIssueCode.custom,
33937
+ message: `subject.deviceId (${v.subject.deviceId}) must equal deviceId (${v.deviceId}) — the top-level field is what per-device scope enforcement reads`,
33938
+ path: ["subject", "deviceId"]
33939
+ });
33940
+ if (v.fromMs !== void 0 || v.toMs !== void 0) ctx.addIssue({
33941
+ code: ZodIssueCode.custom,
33942
+ message: "a clip export asks for no time range: the camera chose the boundaries and they are read from the catalog row",
33943
+ path: ["fromMs"]
33944
+ });
33945
+ return;
33946
+ }
33947
+ if (v.subject?.kind === "footage" && v.subject.deviceId !== v.deviceId) ctx.addIssue({
33948
+ code: ZodIssueCode.custom,
33949
+ message: `subject.deviceId (${v.subject.deviceId}) must equal deviceId (${v.deviceId})`,
33950
+ path: ["subject", "deviceId"]
33951
+ });
33952
+ const fromMs = v.subject?.kind === "footage" ? v.subject.fromMs : v.fromMs;
33953
+ const toMs = v.subject?.kind === "footage" ? v.subject.toMs : v.toMs;
33954
+ if (typeof fromMs !== "number" || typeof toMs !== "number") ctx.addIssue({
33955
+ code: ZodIssueCode.custom,
33956
+ message: "a footage export needs fromMs and toMs",
33957
+ path: ["fromMs"]
33958
+ });
33959
+ if ((v.subject?.kind === "footage" ? [...v.subject.profiles] : resolveExportProfiles(v)).length < 1) ctx.addIssue({
32703
33960
  code: ZodIssueCode.custom,
32704
33961
  message: "pass profiles[] (min 1) or legacy profile",
32705
33962
  path: ["profiles"]
@@ -35511,6 +36768,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
35511
36768
  pressureSensor: pressureSensorCapability,
35512
36769
  privacyMask: privacyMaskCapability,
35513
36770
  ptzAutotrack: ptzAutotrackCapability,
36771
+ recordingOnboard: recordingOnboardCapability,
35514
36772
  recordingSignal: recordingSignalCapability,
35515
36773
  sceneMonitor: sceneMonitorCapability,
35516
36774
  scriptRunner: scriptRunnerCapability,
@@ -36672,7 +37930,1336 @@ var BaseDeviceProvider = class extends BaseAddon {
36672
37930
  DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder;
36673
37931
  new Set(Object.values(DeviceType));
36674
37932
  DeviceFeature.BatteryOperated;
37933
+ /**
37934
+ * The vocabulary, in the order a picker offers it.
37935
+ *
37936
+ * Adding a key here is not enough to make it pickable: both glyph tables must
37937
+ * gain it too, and their tests fail until they do. That is the intended
37938
+ * friction — it is the only thing that keeps the two surfaces honest.
37939
+ */
37940
+ var DISPLAY_ICON_VOCABULARY = [
37941
+ {
37942
+ key: "camera",
37943
+ group: "device",
37944
+ synonyms: [
37945
+ "telecamera",
37946
+ "videocamera",
37947
+ "camera",
37948
+ "cam"
37949
+ ]
37950
+ },
37951
+ {
37952
+ key: "network",
37953
+ group: "device",
37954
+ synonyms: [
37955
+ "rete",
37956
+ "network",
37957
+ "lan"
37958
+ ]
37959
+ },
37960
+ {
37961
+ key: "toggle-left",
37962
+ group: "device",
37963
+ synonyms: [
37964
+ "interruttore",
37965
+ "switch",
37966
+ "toggle"
37967
+ ]
37968
+ },
37969
+ {
37970
+ key: "toggle-right",
37971
+ group: "device",
37972
+ synonyms: [
37973
+ "interruttore",
37974
+ "switch",
37975
+ "toggle",
37976
+ "acceso"
37977
+ ]
37978
+ },
37979
+ {
37980
+ key: "circle-dot",
37981
+ group: "device",
37982
+ synonyms: [
37983
+ "pulsante",
37984
+ "bottone",
37985
+ "button",
37986
+ "dot"
37987
+ ]
37988
+ },
37989
+ {
37990
+ key: "refresh-cw",
37991
+ group: "device",
37992
+ synonyms: [
37993
+ "aggiorna",
37994
+ "ricarica",
37995
+ "refresh",
37996
+ "reload",
37997
+ "ciclo"
37998
+ ]
37999
+ },
38000
+ {
38001
+ key: "box",
38002
+ group: "device",
38003
+ synonyms: [
38004
+ "scatola",
38005
+ "box",
38006
+ "contenitore"
38007
+ ]
38008
+ },
38009
+ {
38010
+ key: "scroll-text",
38011
+ group: "device",
38012
+ synonyms: [
38013
+ "registro",
38014
+ "log",
38015
+ "testo"
38016
+ ]
38017
+ },
38018
+ {
38019
+ key: "workflow",
38020
+ group: "device",
38021
+ synonyms: [
38022
+ "automazione",
38023
+ "flusso",
38024
+ "workflow",
38025
+ "scenario"
38026
+ ]
38027
+ },
38028
+ {
38029
+ key: "lock",
38030
+ group: "device",
38031
+ synonyms: [
38032
+ "serratura",
38033
+ "chiave",
38034
+ "lucchetto",
38035
+ "chiuso",
38036
+ "locked"
38037
+ ]
38038
+ },
38039
+ {
38040
+ key: "lock-open",
38041
+ group: "device",
38042
+ synonyms: [
38043
+ "serratura",
38044
+ "aperta",
38045
+ "sbloccato",
38046
+ "unlocked"
38047
+ ]
38048
+ },
38049
+ {
38050
+ key: "blinds",
38051
+ group: "device",
38052
+ synonyms: [
38053
+ "tapparella",
38054
+ "persiana",
38055
+ "serranda",
38056
+ "avvolgibile",
38057
+ "shutter",
38058
+ "blind",
38059
+ "veneziana"
38060
+ ]
38061
+ },
38062
+ {
38063
+ key: "air-vent",
38064
+ group: "device",
38065
+ synonyms: [
38066
+ "bocchetta",
38067
+ "aria",
38068
+ "ventilazione",
38069
+ "condizionatore",
38070
+ "vent"
38071
+ ]
38072
+ },
38073
+ {
38074
+ key: "monitor-play",
38075
+ group: "device",
38076
+ synonyms: [
38077
+ "schermo",
38078
+ "riproduzione",
38079
+ "player",
38080
+ "display"
38081
+ ]
38082
+ },
38083
+ {
38084
+ key: "sliders-horizontal",
38085
+ group: "device",
38086
+ synonyms: [
38087
+ "regolazione",
38088
+ "cursori",
38089
+ "impostazioni",
38090
+ "slider"
38091
+ ]
38092
+ },
38093
+ {
38094
+ key: "map-pin",
38095
+ group: "device",
38096
+ synonyms: [
38097
+ "posizione",
38098
+ "luogo",
38099
+ "mappa",
38100
+ "pin"
38101
+ ]
38102
+ },
38103
+ {
38104
+ key: "bot",
38105
+ group: "device",
38106
+ synonyms: [
38107
+ "robot",
38108
+ "aspirapolvere",
38109
+ "bot"
38110
+ ]
38111
+ },
38112
+ {
38113
+ key: "tractor",
38114
+ group: "device",
38115
+ synonyms: [
38116
+ "trattore",
38117
+ "tosaerba",
38118
+ "giardino"
38119
+ ]
38120
+ },
38121
+ {
38122
+ key: "image",
38123
+ group: "device",
38124
+ synonyms: [
38125
+ "immagine",
38126
+ "foto",
38127
+ "snapshot"
38128
+ ]
38129
+ },
38130
+ {
38131
+ key: "bone",
38132
+ group: "device",
38133
+ synonyms: ["osso", "cane"]
38134
+ },
38135
+ {
38136
+ key: "door-closed",
38137
+ group: "device",
38138
+ synonyms: [
38139
+ "porta",
38140
+ "chiusa",
38141
+ "contatto"
38142
+ ]
38143
+ },
38144
+ {
38145
+ key: "door-open",
38146
+ group: "device",
38147
+ synonyms: [
38148
+ "porta",
38149
+ "aperta",
38150
+ "contatto",
38151
+ "ingresso",
38152
+ "uscita"
38153
+ ]
38154
+ },
38155
+ {
38156
+ key: "crosshair",
38157
+ group: "device",
38158
+ synonyms: [
38159
+ "mirino",
38160
+ "puntamento",
38161
+ "centro"
38162
+ ]
38163
+ },
38164
+ {
38165
+ key: "eye",
38166
+ group: "device",
38167
+ synonyms: [
38168
+ "occhio",
38169
+ "visibile",
38170
+ "visto"
38171
+ ]
38172
+ },
38173
+ {
38174
+ key: "eye-off",
38175
+ group: "device",
38176
+ synonyms: [
38177
+ "privacy",
38178
+ "nascosto",
38179
+ "oscurato",
38180
+ "mascherato",
38181
+ "invisibile"
38182
+ ]
38183
+ },
38184
+ {
38185
+ key: "smartphone",
38186
+ group: "device",
38187
+ synonyms: [
38188
+ "telefono",
38189
+ "cellulare",
38190
+ "smartphone"
38191
+ ]
38192
+ },
38193
+ {
38194
+ key: "phone",
38195
+ group: "device",
38196
+ synonyms: ["telefono", "chiamata"]
38197
+ },
38198
+ {
38199
+ key: "tablet",
38200
+ group: "device",
38201
+ synonyms: ["tablet", "ipad"]
38202
+ },
38203
+ {
38204
+ key: "laptop",
38205
+ group: "device",
38206
+ synonyms: [
38207
+ "portatile",
38208
+ "laptop",
38209
+ "notebook"
38210
+ ]
38211
+ },
38212
+ {
38213
+ key: "monitor",
38214
+ group: "device",
38215
+ synonyms: ["monitor", "schermo"]
38216
+ },
38217
+ {
38218
+ key: "tv",
38219
+ group: "device",
38220
+ synonyms: ["televisore", "tv"]
38221
+ },
38222
+ {
38223
+ key: "speaker",
38224
+ group: "device",
38225
+ synonyms: [
38226
+ "altoparlante",
38227
+ "cassa",
38228
+ "speaker"
38229
+ ]
38230
+ },
38231
+ {
38232
+ key: "printer",
38233
+ group: "device",
38234
+ synonyms: ["stampante"]
38235
+ },
38236
+ {
38237
+ key: "projector",
38238
+ group: "device",
38239
+ synonyms: ["proiettore"]
38240
+ },
38241
+ {
38242
+ key: "gamepad-2",
38243
+ group: "device",
38244
+ synonyms: [
38245
+ "console",
38246
+ "gioco",
38247
+ "joystick"
38248
+ ]
38249
+ },
38250
+ {
38251
+ key: "radio",
38252
+ group: "device",
38253
+ synonyms: ["radio"]
38254
+ },
38255
+ {
38256
+ key: "router",
38257
+ group: "device",
38258
+ synonyms: ["router", "modem"]
38259
+ },
38260
+ {
38261
+ key: "bluetooth",
38262
+ group: "device",
38263
+ synonyms: ["bluetooth"]
38264
+ },
38265
+ {
38266
+ key: "cpu",
38267
+ group: "device",
38268
+ synonyms: [
38269
+ "processore",
38270
+ "cpu",
38271
+ "chip",
38272
+ "dispositivo"
38273
+ ]
38274
+ },
38275
+ {
38276
+ key: "hard-drive",
38277
+ group: "device",
38278
+ synonyms: [
38279
+ "disco",
38280
+ "hdd",
38281
+ "archiviazione",
38282
+ "storage"
38283
+ ]
38284
+ },
38285
+ {
38286
+ key: "server",
38287
+ group: "device",
38288
+ synonyms: ["server", "nas"]
38289
+ },
38290
+ {
38291
+ key: "mic",
38292
+ group: "device",
38293
+ synonyms: ["microfono", "audio"]
38294
+ },
38295
+ {
38296
+ key: "mic-off",
38297
+ group: "device",
38298
+ synonyms: [
38299
+ "microfono",
38300
+ "spento",
38301
+ "muto"
38302
+ ]
38303
+ },
38304
+ {
38305
+ key: "video",
38306
+ group: "device",
38307
+ synonyms: ["video"]
38308
+ },
38309
+ {
38310
+ key: "video-off",
38311
+ group: "device",
38312
+ synonyms: ["video", "spento"]
38313
+ },
38314
+ {
38315
+ key: "music",
38316
+ group: "device",
38317
+ synonyms: ["musica"]
38318
+ },
38319
+ {
38320
+ key: "plug",
38321
+ group: "device",
38322
+ synonyms: [
38323
+ "presa",
38324
+ "spina",
38325
+ "plug"
38326
+ ]
38327
+ },
38328
+ {
38329
+ key: "plug-zap",
38330
+ group: "device",
38331
+ synonyms: ["presa", "alimentata"]
38332
+ },
38333
+ {
38334
+ key: "power",
38335
+ group: "device",
38336
+ synonyms: [
38337
+ "accensione",
38338
+ "alimentazione",
38339
+ "power"
38340
+ ]
38341
+ },
38342
+ {
38343
+ key: "power-off",
38344
+ group: "device",
38345
+ synonyms: ["spegnimento", "spento"]
38346
+ },
38347
+ {
38348
+ key: "cable",
38349
+ group: "device",
38350
+ synonyms: ["cavo"]
38351
+ },
38352
+ {
38353
+ key: "battery",
38354
+ group: "device",
38355
+ synonyms: ["batteria"]
38356
+ },
38357
+ {
38358
+ key: "battery-charging",
38359
+ group: "device",
38360
+ synonyms: [
38361
+ "batteria",
38362
+ "carica",
38363
+ "ricarica"
38364
+ ]
38365
+ },
38366
+ {
38367
+ key: "battery-full",
38368
+ group: "device",
38369
+ synonyms: [
38370
+ "batteria",
38371
+ "carica",
38372
+ "piena"
38373
+ ]
38374
+ },
38375
+ {
38376
+ key: "battery-low",
38377
+ group: "device",
38378
+ synonyms: ["batteria", "scarica"]
38379
+ },
38380
+ {
38381
+ key: "key",
38382
+ group: "device",
38383
+ synonyms: ["chiave"]
38384
+ },
38385
+ {
38386
+ key: "key-round",
38387
+ group: "device",
38388
+ synonyms: ["chiave", "tonda"]
38389
+ },
38390
+ {
38391
+ key: "mailbox",
38392
+ group: "device",
38393
+ synonyms: [
38394
+ "cassetta",
38395
+ "postale",
38396
+ "posta"
38397
+ ]
38398
+ },
38399
+ {
38400
+ key: "package",
38401
+ group: "device",
38402
+ synonyms: ["pacco", "consegna"]
38403
+ },
38404
+ {
38405
+ key: "shield",
38406
+ group: "device",
38407
+ synonyms: [
38408
+ "scudo",
38409
+ "protezione",
38410
+ "sicurezza",
38411
+ "allarme"
38412
+ ]
38413
+ },
38414
+ {
38415
+ key: "shield-check",
38416
+ group: "device",
38417
+ synonyms: [
38418
+ "protetto",
38419
+ "sicuro",
38420
+ "armato"
38421
+ ]
38422
+ },
38423
+ {
38424
+ key: "shield-off",
38425
+ group: "device",
38426
+ synonyms: [
38427
+ "protezione",
38428
+ "disattivata",
38429
+ "disarmato"
38430
+ ]
38431
+ },
38432
+ {
38433
+ key: "briefcase",
38434
+ group: "device",
38435
+ synonyms: [
38436
+ "ufficio",
38437
+ "lavoro",
38438
+ "valigetta",
38439
+ "studio"
38440
+ ]
38441
+ },
38442
+ {
38443
+ key: "lightbulb",
38444
+ group: "lighting",
38445
+ synonyms: [
38446
+ "luce",
38447
+ "lampadina",
38448
+ "lampada",
38449
+ "light"
38450
+ ]
38451
+ },
38452
+ {
38453
+ key: "lamp",
38454
+ group: "lighting",
38455
+ synonyms: ["lampada", "abatjour"]
38456
+ },
38457
+ {
38458
+ key: "lamp-ceiling",
38459
+ group: "lighting",
38460
+ synonyms: [
38461
+ "plafoniera",
38462
+ "lampadario",
38463
+ "luce",
38464
+ "soffitto"
38465
+ ]
38466
+ },
38467
+ {
38468
+ key: "lamp-desk",
38469
+ group: "lighting",
38470
+ synonyms: [
38471
+ "lampada",
38472
+ "scrivania",
38473
+ "abatjour"
38474
+ ]
38475
+ },
38476
+ {
38477
+ key: "lamp-floor",
38478
+ group: "lighting",
38479
+ synonyms: [
38480
+ "piantana",
38481
+ "lampada",
38482
+ "terra"
38483
+ ]
38484
+ },
38485
+ {
38486
+ key: "flashlight",
38487
+ group: "lighting",
38488
+ synonyms: [
38489
+ "faretto",
38490
+ "torcia",
38491
+ "spot",
38492
+ "faro"
38493
+ ]
38494
+ },
38495
+ {
38496
+ key: "heater",
38497
+ group: "lighting",
38498
+ synonyms: [
38499
+ "riscaldamento",
38500
+ "termosifone",
38501
+ "stufa",
38502
+ "calore"
38503
+ ]
38504
+ },
38505
+ {
38506
+ key: "snowflake",
38507
+ group: "lighting",
38508
+ synonyms: [
38509
+ "freddo",
38510
+ "raffrescamento",
38511
+ "aria",
38512
+ "condizionata",
38513
+ "neve"
38514
+ ]
38515
+ },
38516
+ {
38517
+ key: "thermometer-sun",
38518
+ group: "lighting",
38519
+ synonyms: [
38520
+ "caldo",
38521
+ "temperatura",
38522
+ "alta"
38523
+ ]
38524
+ },
38525
+ {
38526
+ key: "thermometer-snowflake",
38527
+ group: "lighting",
38528
+ synonyms: [
38529
+ "freddo",
38530
+ "temperatura",
38531
+ "bassa"
38532
+ ]
38533
+ },
38534
+ {
38535
+ key: "refrigerator",
38536
+ group: "lighting",
38537
+ synonyms: ["frigorifero", "frigo"]
38538
+ },
38539
+ {
38540
+ key: "microwave",
38541
+ group: "lighting",
38542
+ synonyms: ["microonde", "forno"]
38543
+ },
38544
+ {
38545
+ key: "washing-machine",
38546
+ group: "lighting",
38547
+ synonyms: [
38548
+ "lavatrice",
38549
+ "lavastoviglie",
38550
+ "asciugatrice"
38551
+ ]
38552
+ },
38553
+ {
38554
+ key: "cooking-pot",
38555
+ group: "lighting",
38556
+ synonyms: [
38557
+ "pentola",
38558
+ "cucina",
38559
+ "fornello"
38560
+ ]
38561
+ },
38562
+ {
38563
+ key: "utensils",
38564
+ group: "lighting",
38565
+ synonyms: [
38566
+ "cucina",
38567
+ "posate",
38568
+ "sala",
38569
+ "pranzo"
38570
+ ]
38571
+ },
38572
+ {
38573
+ key: "thermometer",
38574
+ group: "sensing",
38575
+ synonyms: [
38576
+ "temperatura",
38577
+ "termometro",
38578
+ "termostato"
38579
+ ]
38580
+ },
38581
+ {
38582
+ key: "droplets",
38583
+ group: "sensing",
38584
+ synonyms: [
38585
+ "umidita",
38586
+ "acqua",
38587
+ "allagamento",
38588
+ "perdita"
38589
+ ]
38590
+ },
38591
+ {
38592
+ key: "droplet",
38593
+ group: "sensing",
38594
+ synonyms: [
38595
+ "goccia",
38596
+ "acqua",
38597
+ "perdita",
38598
+ "allagamento"
38599
+ ]
38600
+ },
38601
+ {
38602
+ key: "gauge",
38603
+ group: "sensing",
38604
+ synonyms: [
38605
+ "misura",
38606
+ "livello",
38607
+ "pressione",
38608
+ "indicatore"
38609
+ ]
38610
+ },
38611
+ {
38612
+ key: "gauge2",
38613
+ group: "sensing",
38614
+ synonyms: [
38615
+ "misura",
38616
+ "livello",
38617
+ "pressione",
38618
+ "indicatore"
38619
+ ],
38620
+ aliasOf: "gauge"
38621
+ },
38622
+ {
38623
+ key: "sun",
38624
+ group: "sensing",
38625
+ synonyms: [
38626
+ "sole",
38627
+ "luminosita",
38628
+ "giorno",
38629
+ "esterno"
38630
+ ]
38631
+ },
38632
+ {
38633
+ key: "sun-medium",
38634
+ group: "sensing",
38635
+ synonyms: ["sole", "luminosita"]
38636
+ },
38637
+ {
38638
+ key: "sunrise",
38639
+ group: "sensing",
38640
+ synonyms: ["alba"]
38641
+ },
38642
+ {
38643
+ key: "moon",
38644
+ group: "sensing",
38645
+ synonyms: [
38646
+ "notte",
38647
+ "luna",
38648
+ "notturna",
38649
+ "nightvision"
38650
+ ]
38651
+ },
38652
+ {
38653
+ key: "wind",
38654
+ group: "sensing",
38655
+ synonyms: [
38656
+ "vento",
38657
+ "aria",
38658
+ "gas",
38659
+ "monossido",
38660
+ "co"
38661
+ ]
38662
+ },
38663
+ {
38664
+ key: "cloud",
38665
+ group: "sensing",
38666
+ synonyms: ["nuvola", "nuvoloso"]
38667
+ },
38668
+ {
38669
+ key: "cloud-rain",
38670
+ group: "sensing",
38671
+ synonyms: ["pioggia"]
38672
+ },
38673
+ {
38674
+ key: "cloud-sun",
38675
+ group: "sensing",
38676
+ synonyms: [
38677
+ "variabile",
38678
+ "sole",
38679
+ "nuvole"
38680
+ ]
38681
+ },
38682
+ {
38683
+ key: "waves",
38684
+ group: "sensing",
38685
+ synonyms: [
38686
+ "onde",
38687
+ "acqua",
38688
+ "vibrazione"
38689
+ ]
38690
+ },
38691
+ {
38692
+ key: "sprout",
38693
+ group: "sensing",
38694
+ synonyms: [
38695
+ "irrigazione",
38696
+ "pianta",
38697
+ "germoglio",
38698
+ "orto"
38699
+ ]
38700
+ },
38701
+ {
38702
+ key: "leaf",
38703
+ group: "sensing",
38704
+ synonyms: [
38705
+ "foglia",
38706
+ "pianta",
38707
+ "verde"
38708
+ ]
38709
+ },
38710
+ {
38711
+ key: "flower",
38712
+ group: "sensing",
38713
+ synonyms: ["fiore", "pianta"]
38714
+ },
38715
+ {
38716
+ key: "zap",
38717
+ group: "sensing",
38718
+ synonyms: [
38719
+ "energia",
38720
+ "corrente",
38721
+ "consumo",
38722
+ "watt",
38723
+ "elettricita"
38724
+ ]
38725
+ },
38726
+ {
38727
+ key: "flame",
38728
+ group: "sensing",
38729
+ synonyms: [
38730
+ "fiamma",
38731
+ "gas",
38732
+ "fuoco",
38733
+ "incendio"
38734
+ ]
38735
+ },
38736
+ {
38737
+ key: "bell",
38738
+ group: "sensing",
38739
+ synonyms: [
38740
+ "campanello",
38741
+ "suoneria",
38742
+ "notifica",
38743
+ "bell",
38744
+ "chime"
38745
+ ]
38746
+ },
38747
+ {
38748
+ key: "bell-ring",
38749
+ group: "sensing",
38750
+ synonyms: [
38751
+ "campanello",
38752
+ "suona",
38753
+ "doorbell",
38754
+ "notifica"
38755
+ ]
38756
+ },
38757
+ {
38758
+ key: "bell-off",
38759
+ group: "sensing",
38760
+ synonyms: [
38761
+ "silenzioso",
38762
+ "notifiche",
38763
+ "disattivate",
38764
+ "muto"
38765
+ ]
38766
+ },
38767
+ {
38768
+ key: "siren",
38769
+ group: "sensing",
38770
+ synonyms: ["sirena", "allarme"]
38771
+ },
38772
+ {
38773
+ key: "radar",
38774
+ group: "sensing",
38775
+ synonyms: [
38776
+ "movimento",
38777
+ "presenza",
38778
+ "pir",
38779
+ "rilevamento",
38780
+ "radar"
38781
+ ]
38782
+ },
38783
+ {
38784
+ key: "activity",
38785
+ group: "sensing",
38786
+ synonyms: [
38787
+ "attivita",
38788
+ "sensore",
38789
+ "generico"
38790
+ ]
38791
+ },
38792
+ {
38793
+ key: "hash",
38794
+ group: "sensing",
38795
+ synonyms: [
38796
+ "numero",
38797
+ "contatore",
38798
+ "valore"
38799
+ ]
38800
+ },
38801
+ {
38802
+ key: "clock",
38803
+ group: "sensing",
38804
+ synonyms: [
38805
+ "orologio",
38806
+ "ora",
38807
+ "tempo"
38808
+ ]
38809
+ },
38810
+ {
38811
+ key: "timer",
38812
+ group: "sensing",
38813
+ synonyms: [
38814
+ "timer",
38815
+ "conto",
38816
+ "rovescia"
38817
+ ]
38818
+ },
38819
+ {
38820
+ key: "calendar",
38821
+ group: "sensing",
38822
+ synonyms: [
38823
+ "calendario",
38824
+ "data",
38825
+ "pianificazione"
38826
+ ]
38827
+ },
38828
+ {
38829
+ key: "pipette",
38830
+ group: "sensing",
38831
+ synonyms: [
38832
+ "qualita",
38833
+ "aria",
38834
+ "misura",
38835
+ "campione"
38836
+ ]
38837
+ },
38838
+ {
38839
+ key: "fan",
38840
+ group: "sensing",
38841
+ synonyms: [
38842
+ "ventilatore",
38843
+ "ventola",
38844
+ "aria"
38845
+ ]
38846
+ },
38847
+ {
38848
+ key: "bath",
38849
+ group: "sensing",
38850
+ synonyms: [
38851
+ "bagno",
38852
+ "vasca",
38853
+ "doccia"
38854
+ ]
38855
+ },
38856
+ {
38857
+ key: "footprints",
38858
+ group: "sensing",
38859
+ synonyms: [
38860
+ "movimento",
38861
+ "passi",
38862
+ "presenza",
38863
+ "motion"
38864
+ ]
38865
+ },
38866
+ {
38867
+ key: "alarm-smoke",
38868
+ group: "sensing",
38869
+ synonyms: [
38870
+ "fumo",
38871
+ "antincendio",
38872
+ "rilevatore"
38873
+ ]
38874
+ },
38875
+ {
38876
+ key: "shield-alert",
38877
+ group: "sensing",
38878
+ synonyms: [
38879
+ "manomissione",
38880
+ "allarme",
38881
+ "tamper",
38882
+ "avviso"
38883
+ ]
38884
+ },
38885
+ {
38886
+ key: "vibrate",
38887
+ group: "sensing",
38888
+ synonyms: [
38889
+ "vibrazione",
38890
+ "urto",
38891
+ "tamper"
38892
+ ]
38893
+ },
38894
+ {
38895
+ key: "wifi",
38896
+ group: "sensing",
38897
+ synonyms: [
38898
+ "wifi",
38899
+ "rete",
38900
+ "segnale"
38901
+ ]
38902
+ },
38903
+ {
38904
+ key: "wifi-off",
38905
+ group: "sensing",
38906
+ synonyms: [
38907
+ "wifi",
38908
+ "assente",
38909
+ "offline",
38910
+ "disconnesso"
38911
+ ]
38912
+ },
38913
+ {
38914
+ key: "volume-2",
38915
+ group: "sensing",
38916
+ synonyms: [
38917
+ "volume",
38918
+ "audio",
38919
+ "altoparlante",
38920
+ "suono"
38921
+ ]
38922
+ },
38923
+ {
38924
+ key: "volume-x",
38925
+ group: "sensing",
38926
+ synonyms: [
38927
+ "muto",
38928
+ "silenziato",
38929
+ "volume",
38930
+ "spento"
38931
+ ]
38932
+ },
38933
+ {
38934
+ key: "heart",
38935
+ group: "sensing",
38936
+ synonyms: [
38937
+ "cuore",
38938
+ "preferito",
38939
+ "salute"
38940
+ ]
38941
+ },
38942
+ {
38943
+ key: "heart-pulse",
38944
+ group: "sensing",
38945
+ synonyms: [
38946
+ "battito",
38947
+ "salute",
38948
+ "monitoraggio"
38949
+ ]
38950
+ },
38951
+ {
38952
+ key: "star",
38953
+ group: "sensing",
38954
+ synonyms: [
38955
+ "preferito",
38956
+ "stella",
38957
+ "importante"
38958
+ ]
38959
+ },
38960
+ {
38961
+ key: "sparkles",
38962
+ group: "sensing",
38963
+ synonyms: [
38964
+ "automatico",
38965
+ "intelligente",
38966
+ "ai",
38967
+ "scena"
38968
+ ]
38969
+ },
38970
+ {
38971
+ key: "house",
38972
+ group: "place",
38973
+ synonyms: [
38974
+ "casa",
38975
+ "abitazione",
38976
+ "home"
38977
+ ]
38978
+ },
38979
+ {
38980
+ key: "sofa",
38981
+ group: "place",
38982
+ synonyms: [
38983
+ "salone",
38984
+ "soggiorno",
38985
+ "divano",
38986
+ "living"
38987
+ ]
38988
+ },
38989
+ {
38990
+ key: "bed-double",
38991
+ group: "place",
38992
+ synonyms: [
38993
+ "camera",
38994
+ "letto",
38995
+ "matrimoniale"
38996
+ ]
38997
+ },
38998
+ {
38999
+ key: "bed",
39000
+ group: "place",
39001
+ synonyms: [
39002
+ "cameretta",
39003
+ "letto",
39004
+ "singolo"
39005
+ ]
39006
+ },
39007
+ {
39008
+ key: "warehouse",
39009
+ group: "place",
39010
+ synonyms: [
39011
+ "magazzino",
39012
+ "capanno",
39013
+ "deposito",
39014
+ "cantina"
39015
+ ]
39016
+ },
39017
+ {
39018
+ key: "trees",
39019
+ group: "place",
39020
+ synonyms: [
39021
+ "giardino",
39022
+ "alberi",
39023
+ "esterno",
39024
+ "parco"
39025
+ ]
39026
+ },
39027
+ {
39028
+ key: "tree-pine",
39029
+ group: "place",
39030
+ synonyms: [
39031
+ "albero",
39032
+ "pino",
39033
+ "giardino"
39034
+ ]
39035
+ },
39036
+ {
39037
+ key: "fence",
39038
+ group: "place",
39039
+ synonyms: [
39040
+ "recinto",
39041
+ "cancello",
39042
+ "staccionata",
39043
+ "perimetro"
39044
+ ]
39045
+ },
39046
+ {
39047
+ key: "store",
39048
+ group: "place",
39049
+ synonyms: [
39050
+ "negozio",
39051
+ "ingresso",
39052
+ "vetrina"
39053
+ ]
39054
+ },
39055
+ {
39056
+ key: "building",
39057
+ group: "place",
39058
+ synonyms: ["edificio", "palazzo"]
39059
+ },
39060
+ {
39061
+ key: "building-2",
39062
+ group: "place",
39063
+ synonyms: [
39064
+ "edificio",
39065
+ "uffici",
39066
+ "condominio"
39067
+ ]
39068
+ },
39069
+ {
39070
+ key: "mountain",
39071
+ group: "place",
39072
+ synonyms: [
39073
+ "montagna",
39074
+ "esterno",
39075
+ "panorama"
39076
+ ]
39077
+ },
39078
+ {
39079
+ key: "umbrella",
39080
+ group: "place",
39081
+ synonyms: [
39082
+ "tenda",
39083
+ "markise",
39084
+ "ombrellone",
39085
+ "pergola",
39086
+ "veranda"
39087
+ ]
39088
+ },
39089
+ {
39090
+ key: "dumbbell",
39091
+ group: "place",
39092
+ synonyms: ["palestra", "attrezzi"]
39093
+ },
39094
+ {
39095
+ key: "wrench",
39096
+ group: "place",
39097
+ synonyms: [
39098
+ "officina",
39099
+ "manutenzione",
39100
+ "attrezzi",
39101
+ "utenze"
39102
+ ]
39103
+ },
39104
+ {
39105
+ key: "hammer",
39106
+ group: "place",
39107
+ synonyms: [
39108
+ "officina",
39109
+ "martello",
39110
+ "lavori"
39111
+ ]
39112
+ },
39113
+ {
39114
+ key: "drill",
39115
+ group: "place",
39116
+ synonyms: [
39117
+ "officina",
39118
+ "trapano",
39119
+ "lavori"
39120
+ ]
39121
+ },
39122
+ {
39123
+ key: "waves-ladder",
39124
+ group: "place",
39125
+ synonyms: ["piscina", "vasca"]
39126
+ },
39127
+ {
39128
+ key: "circle-parking",
39129
+ group: "place",
39130
+ synonyms: [
39131
+ "parcheggio",
39132
+ "posto",
39133
+ "auto",
39134
+ "garage"
39135
+ ]
39136
+ },
39137
+ {
39138
+ key: "route",
39139
+ group: "place",
39140
+ synonyms: [
39141
+ "percorso",
39142
+ "strada",
39143
+ "vialetto",
39144
+ "tragitto"
39145
+ ]
39146
+ },
39147
+ {
39148
+ key: "car",
39149
+ group: "life",
39150
+ synonyms: [
39151
+ "auto",
39152
+ "macchina",
39153
+ "veicolo",
39154
+ "parcheggio"
39155
+ ]
39156
+ },
39157
+ {
39158
+ key: "bike",
39159
+ group: "life",
39160
+ synonyms: [
39161
+ "bicicletta",
39162
+ "bici",
39163
+ "moto"
39164
+ ]
39165
+ },
39166
+ {
39167
+ key: "bus",
39168
+ group: "life",
39169
+ synonyms: ["autobus", "pullman"]
39170
+ },
39171
+ {
39172
+ key: "truck",
39173
+ group: "life",
39174
+ synonyms: [
39175
+ "camion",
39176
+ "furgone",
39177
+ "consegna"
39178
+ ]
39179
+ },
39180
+ {
39181
+ key: "plane",
39182
+ group: "life",
39183
+ synonyms: ["aereo"]
39184
+ },
39185
+ {
39186
+ key: "train-front",
39187
+ group: "life",
39188
+ synonyms: ["treno"]
39189
+ },
39190
+ {
39191
+ key: "sailboat",
39192
+ group: "life",
39193
+ synonyms: ["barca", "imbarcazione"]
39194
+ },
39195
+ {
39196
+ key: "dog",
39197
+ group: "life",
39198
+ synonyms: ["cane", "animale"]
39199
+ },
39200
+ {
39201
+ key: "cat",
39202
+ group: "life",
39203
+ synonyms: ["gatto", "animale"]
39204
+ },
39205
+ {
39206
+ key: "bird",
39207
+ group: "life",
39208
+ synonyms: ["uccello", "animale"]
39209
+ },
39210
+ {
39211
+ key: "paw-print",
39212
+ group: "life",
39213
+ synonyms: [
39214
+ "animale",
39215
+ "zampa",
39216
+ "pet"
39217
+ ]
39218
+ },
39219
+ {
39220
+ key: "baby",
39221
+ group: "life",
39222
+ synonyms: [
39223
+ "neonato",
39224
+ "bimbo",
39225
+ "cameretta",
39226
+ "babymonitor"
39227
+ ]
39228
+ },
39229
+ {
39230
+ key: "user",
39231
+ group: "life",
39232
+ synonyms: ["persona", "utente"]
39233
+ },
39234
+ {
39235
+ key: "users",
39236
+ group: "life",
39237
+ synonyms: [
39238
+ "persone",
39239
+ "gruppo",
39240
+ "famiglia"
39241
+ ]
39242
+ },
39243
+ {
39244
+ key: "person-standing",
39245
+ group: "life",
39246
+ synonyms: [
39247
+ "persona",
39248
+ "piedi",
39249
+ "presenza"
39250
+ ]
39251
+ }
39252
+ ];
39253
+ DISPLAY_ICON_VOCABULARY.map((e) => e.key);
39254
+ DISPLAY_ICON_VOCABULARY.filter((e) => e.aliasOf === void 0).map((e) => e.key);
39255
+ new Map(DISPLAY_ICON_VOCABULARY.map((e) => [e.key, e]));
36675
39256
  Object.freeze({
39257
+ "accessories.getStatus": {
39258
+ capName: "accessories",
39259
+ capScope: "device",
39260
+ addonId: null,
39261
+ access: "view"
39262
+ },
36676
39263
  "accessories.setChildHidden": {
36677
39264
  capName: "accessories",
36678
39265
  capScope: "device",
@@ -36955,6 +39542,12 @@ Object.freeze({
36955
39542
  addonId: null,
36956
39543
  access: "view"
36957
39544
  },
39545
+ "airQualitySensor.getStatus": {
39546
+ capName: "air-quality-sensor",
39547
+ capScope: "device",
39548
+ addonId: null,
39549
+ access: "view"
39550
+ },
36958
39551
  "alarmPanel.arm": {
36959
39552
  capName: "alarm-panel",
36960
39553
  capScope: "device",
@@ -36967,6 +39560,12 @@ Object.freeze({
36967
39560
  addonId: null,
36968
39561
  access: "create"
36969
39562
  },
39563
+ "alarmPanel.getStatus": {
39564
+ capName: "alarm-panel",
39565
+ capScope: "device",
39566
+ addonId: null,
39567
+ access: "view"
39568
+ },
36970
39569
  "alarmPanel.trigger": {
36971
39570
  capName: "alarm-panel",
36972
39571
  capScope: "device",
@@ -37015,6 +39614,30 @@ Object.freeze({
37015
39614
  addonId: null,
37016
39615
  access: "create"
37017
39616
  },
39617
+ "ambientLightSensor.getStatus": {
39618
+ capName: "ambient-light-sensor",
39619
+ capScope: "device",
39620
+ addonId: null,
39621
+ access: "view"
39622
+ },
39623
+ "audioAnalysis.applyDeviceSettingsPatch": {
39624
+ capName: "audio-analysis",
39625
+ capScope: "device",
39626
+ addonId: null,
39627
+ access: "create"
39628
+ },
39629
+ "audioAnalysis.getDeviceLiveContribution": {
39630
+ capName: "audio-analysis",
39631
+ capScope: "device",
39632
+ addonId: null,
39633
+ access: "view"
39634
+ },
39635
+ "audioAnalysis.getDeviceSettingsContribution": {
39636
+ capName: "audio-analysis",
39637
+ capScope: "device",
39638
+ addonId: null,
39639
+ access: "view"
39640
+ },
37018
39641
  "audioAnalysis.resolveDeviceSettings": {
37019
39642
  capName: "audio-analysis",
37020
39643
  capScope: "device",
@@ -37177,6 +39800,12 @@ Object.freeze({
37177
39800
  addonId: null,
37178
39801
  access: "create"
37179
39802
  },
39803
+ "automationControl.getStatus": {
39804
+ capName: "automation-control",
39805
+ capScope: "device",
39806
+ addonId: null,
39807
+ access: "view"
39808
+ },
37180
39809
  "automationControl.trigger": {
37181
39810
  capName: "automation-control",
37182
39811
  capScope: "device",
@@ -37273,12 +39902,30 @@ Object.freeze({
37273
39902
  addonId: null,
37274
39903
  access: "create"
37275
39904
  },
39905
+ "battery.getStatus": {
39906
+ capName: "battery",
39907
+ capScope: "device",
39908
+ addonId: null,
39909
+ access: "view"
39910
+ },
37276
39911
  "battery.wakeForStream": {
37277
39912
  capName: "battery",
37278
39913
  capScope: "device",
37279
39914
  addonId: null,
37280
39915
  access: "create"
37281
39916
  },
39917
+ "binary.getStatus": {
39918
+ capName: "binary",
39919
+ capScope: "device",
39920
+ addonId: null,
39921
+ access: "view"
39922
+ },
39923
+ "brightness.getStatus": {
39924
+ capName: "brightness",
39925
+ capScope: "device",
39926
+ addonId: null,
39927
+ access: "view"
39928
+ },
37282
39929
  "brightness.setBrightness": {
37283
39930
  capName: "brightness",
37284
39931
  capScope: "device",
@@ -37393,6 +40040,30 @@ Object.freeze({
37393
40040
  addonId: null,
37394
40041
  access: "view"
37395
40042
  },
40043
+ "cameraCredentials.getStatus": {
40044
+ capName: "camera-credentials",
40045
+ capScope: "device",
40046
+ addonId: null,
40047
+ access: "view"
40048
+ },
40049
+ "cameraGridLayout.applyDeviceSettingsPatch": {
40050
+ capName: "camera-grid-layout",
40051
+ capScope: "device",
40052
+ addonId: null,
40053
+ access: "create"
40054
+ },
40055
+ "cameraGridLayout.getDeviceLiveContribution": {
40056
+ capName: "camera-grid-layout",
40057
+ capScope: "device",
40058
+ addonId: null,
40059
+ access: "view"
40060
+ },
40061
+ "cameraGridLayout.getDeviceSettingsContribution": {
40062
+ capName: "camera-grid-layout",
40063
+ capScope: "device",
40064
+ addonId: null,
40065
+ access: "view"
40066
+ },
37396
40067
  "cameraGridLayout.getLayout": {
37397
40068
  capName: "camera-grid-layout",
37398
40069
  capScope: "device",
@@ -37405,6 +40076,24 @@ Object.freeze({
37405
40076
  addonId: null,
37406
40077
  access: "create"
37407
40078
  },
40079
+ "cameraPipelineConfig.applyDeviceSettingsPatch": {
40080
+ capName: "camera-pipeline-config",
40081
+ capScope: "device",
40082
+ addonId: null,
40083
+ access: "create"
40084
+ },
40085
+ "cameraPipelineConfig.getDeviceLiveContribution": {
40086
+ capName: "camera-pipeline-config",
40087
+ capScope: "device",
40088
+ addonId: null,
40089
+ access: "view"
40090
+ },
40091
+ "cameraPipelineConfig.getDeviceSettingsContribution": {
40092
+ capName: "camera-pipeline-config",
40093
+ capScope: "device",
40094
+ addonId: null,
40095
+ access: "view"
40096
+ },
37408
40097
  "cameraStreams.getBrokerStreams": {
37409
40098
  capName: "camera-streams",
37410
40099
  capScope: "device",
@@ -37435,6 +40124,18 @@ Object.freeze({
37435
40124
  addonId: null,
37436
40125
  access: "view"
37437
40126
  },
40127
+ "carbonMonoxide.getStatus": {
40128
+ capName: "carbon-monoxide",
40129
+ capScope: "device",
40130
+ addonId: null,
40131
+ access: "view"
40132
+ },
40133
+ "climateControl.getStatus": {
40134
+ capName: "climate-control",
40135
+ capScope: "device",
40136
+ addonId: null,
40137
+ access: "view"
40138
+ },
37438
40139
  "climateControl.setFanMode": {
37439
40140
  capName: "climate-control",
37440
40141
  capScope: "device",
@@ -37483,6 +40184,12 @@ Object.freeze({
37483
40184
  addonId: null,
37484
40185
  access: "create"
37485
40186
  },
40187
+ "color.getStatus": {
40188
+ capName: "color",
40189
+ capScope: "device",
40190
+ addonId: null,
40191
+ access: "view"
40192
+ },
37486
40193
  "color.setColor": {
37487
40194
  capName: "color",
37488
40195
  capScope: "device",
@@ -37501,12 +40208,36 @@ Object.freeze({
37501
40208
  addonId: null,
37502
40209
  access: "create"
37503
40210
  },
40211
+ "connectivity.getStatus": {
40212
+ capName: "connectivity",
40213
+ capScope: "device",
40214
+ addonId: null,
40215
+ access: "view"
40216
+ },
40217
+ "consumables.getStatus": {
40218
+ capName: "consumables",
40219
+ capScope: "device",
40220
+ addonId: null,
40221
+ access: "view"
40222
+ },
37504
40223
  "consumables.reset": {
37505
40224
  capName: "consumables",
37506
40225
  capScope: "device",
37507
40226
  addonId: null,
37508
40227
  access: "delete"
37509
40228
  },
40229
+ "contact.getStatus": {
40230
+ capName: "contact",
40231
+ capScope: "device",
40232
+ addonId: null,
40233
+ access: "view"
40234
+ },
40235
+ "control.getStatus": {
40236
+ capName: "control",
40237
+ capScope: "device",
40238
+ addonId: null,
40239
+ access: "view"
40240
+ },
37510
40241
  "control.setValue": {
37511
40242
  capName: "control",
37512
40243
  capScope: "device",
@@ -37573,6 +40304,12 @@ Object.freeze({
37573
40304
  addonId: null,
37574
40305
  access: "create"
37575
40306
  },
40307
+ "cover.getStatus": {
40308
+ capName: "cover",
40309
+ capScope: "device",
40310
+ addonId: null,
40311
+ access: "view"
40312
+ },
37576
40313
  "cover.open": {
37577
40314
  capName: "cover",
37578
40315
  capScope: "device",
@@ -37705,6 +40442,12 @@ Object.freeze({
37705
40442
  addonId: null,
37706
40443
  access: "view"
37707
40444
  },
40445
+ "dayNight.getStatus": {
40446
+ capName: "day-night",
40447
+ capScope: "device",
40448
+ addonId: null,
40449
+ access: "view"
40450
+ },
37708
40451
  "dayNight.setSettings": {
37709
40452
  capName: "day-night",
37710
40453
  capScope: "device",
@@ -37795,6 +40538,30 @@ Object.freeze({
37795
40538
  addonId: null,
37796
40539
  access: "view"
37797
40540
  },
40541
+ "detectionPipeline.applyDeviceSettingsPatch": {
40542
+ capName: "detection-pipeline",
40543
+ capScope: "device",
40544
+ addonId: null,
40545
+ access: "create"
40546
+ },
40547
+ "detectionPipeline.getDeviceLiveContribution": {
40548
+ capName: "detection-pipeline",
40549
+ capScope: "device",
40550
+ addonId: null,
40551
+ access: "view"
40552
+ },
40553
+ "detectionPipeline.getDeviceSettingsContribution": {
40554
+ capName: "detection-pipeline",
40555
+ capScope: "device",
40556
+ addonId: null,
40557
+ access: "view"
40558
+ },
40559
+ "deviceAdminLink.getAdminLink": {
40560
+ capName: "device-admin-link",
40561
+ capScope: "device",
40562
+ addonId: null,
40563
+ access: "view"
40564
+ },
37798
40565
  "deviceAdoption.adopt": {
37799
40566
  capName: "device-adoption",
37800
40567
  capScope: "system",
@@ -37807,6 +40574,12 @@ Object.freeze({
37807
40574
  addonId: null,
37808
40575
  access: "view"
37809
40576
  },
40577
+ "deviceAdoption.getStatus": {
40578
+ capName: "device-adoption",
40579
+ capScope: "system",
40580
+ addonId: null,
40581
+ access: "view"
40582
+ },
37810
40583
  "deviceAdoption.listCandidateFilters": {
37811
40584
  capName: "device-adoption",
37812
40585
  capScope: "system",
@@ -37843,6 +40616,12 @@ Object.freeze({
37843
40616
  addonId: null,
37844
40617
  access: "create"
37845
40618
  },
40619
+ "deviceDiscovery.getStatus": {
40620
+ capName: "device-discovery",
40621
+ capScope: "device",
40622
+ addonId: null,
40623
+ access: "view"
40624
+ },
37846
40625
  "deviceDiscovery.listDiscovered": {
37847
40626
  capName: "device-discovery",
37848
40627
  capScope: "device",
@@ -37861,12 +40640,30 @@ Object.freeze({
37861
40640
  addonId: null,
37862
40641
  access: "create"
37863
40642
  },
40643
+ "deviceExport.applyDeviceSettingsPatch": {
40644
+ capName: "device-export",
40645
+ capScope: "system",
40646
+ addonId: null,
40647
+ access: "create"
40648
+ },
37864
40649
  "deviceExport.exposeDevice": {
37865
40650
  capName: "device-export",
37866
40651
  capScope: "system",
37867
40652
  addonId: null,
37868
40653
  access: "create"
37869
40654
  },
40655
+ "deviceExport.getDeviceLiveContribution": {
40656
+ capName: "device-export",
40657
+ capScope: "system",
40658
+ addonId: null,
40659
+ access: "view"
40660
+ },
40661
+ "deviceExport.getDeviceSettingsContribution": {
40662
+ capName: "device-export",
40663
+ capScope: "system",
40664
+ addonId: null,
40665
+ access: "view"
40666
+ },
37870
40667
  "deviceExport.getStatus": {
37871
40668
  capName: "device-export",
37872
40669
  capScope: "system",
@@ -37963,6 +40760,12 @@ Object.freeze({
37963
40760
  addonId: null,
37964
40761
  access: "create"
37965
40762
  },
40763
+ "deviceManager.applyDeviceSettingsPatch": {
40764
+ capName: "device-manager",
40765
+ capScope: "system",
40766
+ addonId: null,
40767
+ access: "create"
40768
+ },
37966
40769
  "deviceManager.applyInitialMeta": {
37967
40770
  capName: "device-manager",
37968
40771
  capScope: "system",
@@ -38065,6 +40868,12 @@ Object.freeze({
38065
40868
  addonId: null,
38066
40869
  access: "view"
38067
40870
  },
40871
+ "deviceManager.getDeviceLiveContribution": {
40872
+ capName: "device-manager",
40873
+ capScope: "system",
40874
+ addonId: null,
40875
+ access: "view"
40876
+ },
38068
40877
  "deviceManager.getDeviceLiveInfoAggregate": {
38069
40878
  capName: "device-manager",
38070
40879
  capScope: "system",
@@ -38077,6 +40886,12 @@ Object.freeze({
38077
40886
  addonId: null,
38078
40887
  access: "view"
38079
40888
  },
40889
+ "deviceManager.getDeviceSettingsContribution": {
40890
+ capName: "device-manager",
40891
+ capScope: "system",
40892
+ addonId: null,
40893
+ access: "view"
40894
+ },
38080
40895
  "deviceManager.getDeviceStatusAggregate": {
38081
40896
  capName: "device-manager",
38082
40897
  capScope: "system",
@@ -38569,6 +41384,36 @@ Object.freeze({
38569
41384
  addonId: null,
38570
41385
  access: "create"
38571
41386
  },
41387
+ "deviceStatus.getStatus": {
41388
+ capName: "device-status",
41389
+ capScope: "device",
41390
+ addonId: null,
41391
+ access: "view"
41392
+ },
41393
+ "doorbell.applyDeviceSettingsPatch": {
41394
+ capName: "doorbell",
41395
+ capScope: "device",
41396
+ addonId: null,
41397
+ access: "create"
41398
+ },
41399
+ "doorbell.getDeviceLiveContribution": {
41400
+ capName: "doorbell",
41401
+ capScope: "device",
41402
+ addonId: null,
41403
+ access: "view"
41404
+ },
41405
+ "doorbell.getDeviceSettingsContribution": {
41406
+ capName: "doorbell",
41407
+ capScope: "device",
41408
+ addonId: null,
41409
+ access: "view"
41410
+ },
41411
+ "doorbell.getStatus": {
41412
+ capName: "doorbell",
41413
+ capScope: "device",
41414
+ addonId: null,
41415
+ access: "view"
41416
+ },
38572
41417
  "embeddingEncoder.encode": {
38573
41418
  capName: "embedding-encoder",
38574
41419
  capScope: "system",
@@ -38587,6 +41432,18 @@ Object.freeze({
38587
41432
  addonId: null,
38588
41433
  access: "view"
38589
41434
  },
41435
+ "enumSensor.getStatus": {
41436
+ capName: "enum-sensor",
41437
+ capScope: "device",
41438
+ addonId: null,
41439
+ access: "view"
41440
+ },
41441
+ "eventEmitter.getStatus": {
41442
+ capName: "event-emitter",
41443
+ capScope: "device",
41444
+ addonId: null,
41445
+ access: "view"
41446
+ },
38590
41447
  "events.getEventClipUrl": {
38591
41448
  capName: "events",
38592
41449
  capScope: "device",
@@ -38707,6 +41564,12 @@ Object.freeze({
38707
41564
  addonId: null,
38708
41565
  access: "view"
38709
41566
  },
41567
+ "fanControl.getStatus": {
41568
+ capName: "fan-control",
41569
+ capScope: "device",
41570
+ addonId: null,
41571
+ access: "view"
41572
+ },
38710
41573
  "fanControl.setDirection": {
38711
41574
  capName: "fan-control",
38712
41575
  capScope: "device",
@@ -38731,6 +41594,12 @@ Object.freeze({
38731
41594
  addonId: null,
38732
41595
  access: "create"
38733
41596
  },
41597
+ "featureProbe.getStatus": {
41598
+ capName: "feature-probe",
41599
+ capScope: "device",
41600
+ addonId: null,
41601
+ access: "view"
41602
+ },
38734
41603
  "filesystemBrowse.browse": {
38735
41604
  capName: "filesystem-browse",
38736
41605
  capScope: "system",
@@ -38749,6 +41618,24 @@ Object.freeze({
38749
41618
  addonId: null,
38750
41619
  access: "view"
38751
41620
  },
41621
+ "flood.getStatus": {
41622
+ capName: "flood",
41623
+ capScope: "device",
41624
+ addonId: null,
41625
+ access: "view"
41626
+ },
41627
+ "gas.getStatus": {
41628
+ capName: "gas",
41629
+ capScope: "device",
41630
+ addonId: null,
41631
+ access: "view"
41632
+ },
41633
+ "humidifier.getStatus": {
41634
+ capName: "humidifier",
41635
+ capScope: "device",
41636
+ addonId: null,
41637
+ access: "view"
41638
+ },
38752
41639
  "humidifier.setMode": {
38753
41640
  capName: "humidifier",
38754
41641
  capScope: "device",
@@ -38767,12 +41654,30 @@ Object.freeze({
38767
41654
  addonId: null,
38768
41655
  access: "create"
38769
41656
  },
41657
+ "humiditySensor.getStatus": {
41658
+ capName: "humidity-sensor",
41659
+ capScope: "device",
41660
+ addonId: null,
41661
+ access: "view"
41662
+ },
41663
+ "image.getStatus": {
41664
+ capName: "image",
41665
+ capScope: "device",
41666
+ addonId: null,
41667
+ access: "view"
41668
+ },
38770
41669
  "imageSettings.getOptions": {
38771
41670
  capName: "image-settings",
38772
41671
  capScope: "device",
38773
41672
  addonId: null,
38774
41673
  access: "view"
38775
41674
  },
41675
+ "imageSettings.getStatus": {
41676
+ capName: "image-settings",
41677
+ capScope: "device",
41678
+ addonId: null,
41679
+ access: "view"
41680
+ },
38776
41681
  "imageSettings.setSettings": {
38777
41682
  capName: "image-settings",
38778
41683
  capScope: "device",
@@ -38845,6 +41750,12 @@ Object.freeze({
38845
41750
  addonId: null,
38846
41751
  access: "create"
38847
41752
  },
41753
+ "intercom.getStatus": {
41754
+ capName: "intercom",
41755
+ capScope: "device",
41756
+ addonId: null,
41757
+ access: "view"
41758
+ },
38848
41759
  "intercom.handleAnswer": {
38849
41760
  capName: "intercom",
38850
41761
  capScope: "device",
@@ -38881,6 +41792,12 @@ Object.freeze({
38881
41792
  addonId: null,
38882
41793
  access: "create"
38883
41794
  },
41795
+ "lawnMowerControl.getStatus": {
41796
+ capName: "lawn-mower-control",
41797
+ capScope: "device",
41798
+ addonId: null,
41799
+ access: "view"
41800
+ },
38884
41801
  "lawnMowerControl.pause": {
38885
41802
  capName: "lawn-mower-control",
38886
41803
  capScope: "device",
@@ -39163,6 +42080,12 @@ Object.freeze({
39163
42080
  addonId: null,
39164
42081
  access: "create"
39165
42082
  },
42083
+ "lockControl.getStatus": {
42084
+ capName: "lock-control",
42085
+ capScope: "device",
42086
+ addonId: null,
42087
+ access: "view"
42088
+ },
39166
42089
  "lockControl.lock": {
39167
42090
  capName: "lock-control",
39168
42091
  capScope: "device",
@@ -39211,6 +42134,12 @@ Object.freeze({
39211
42134
  addonId: null,
39212
42135
  access: "view"
39213
42136
  },
42137
+ "mediaPlayer.getStatus": {
42138
+ capName: "media-player",
42139
+ capScope: "device",
42140
+ addonId: null,
42141
+ access: "view"
42142
+ },
39214
42143
  "mediaPlayer.next": {
39215
42144
  capName: "media-player",
39216
42145
  capScope: "device",
@@ -39403,6 +42332,12 @@ Object.freeze({
39403
42332
  addonId: null,
39404
42333
  access: "create"
39405
42334
  },
42335
+ "motion.getStatus": {
42336
+ capName: "motion",
42337
+ capScope: "device",
42338
+ addonId: null,
42339
+ access: "view"
42340
+ },
39406
42341
  "motion.isDetected": {
39407
42342
  capName: "motion",
39408
42343
  capScope: "device",
@@ -39415,6 +42350,24 @@ Object.freeze({
39415
42350
  addonId: null,
39416
42351
  access: "create"
39417
42352
  },
42353
+ "motionDetection.applyDeviceSettingsPatch": {
42354
+ capName: "motion-detection",
42355
+ capScope: "device",
42356
+ addonId: null,
42357
+ access: "create"
42358
+ },
42359
+ "motionDetection.getDeviceLiveContribution": {
42360
+ capName: "motion-detection",
42361
+ capScope: "device",
42362
+ addonId: null,
42363
+ access: "view"
42364
+ },
42365
+ "motionDetection.getDeviceSettingsContribution": {
42366
+ capName: "motion-detection",
42367
+ capScope: "device",
42368
+ addonId: null,
42369
+ access: "view"
42370
+ },
39418
42371
  "motionDetection.removeCamera": {
39419
42372
  capName: "motion-detection",
39420
42373
  capScope: "device",
@@ -39427,6 +42380,12 @@ Object.freeze({
39427
42380
  addonId: null,
39428
42381
  access: "delete"
39429
42382
  },
42383
+ "motionTrigger.getStatus": {
42384
+ capName: "motion-trigger",
42385
+ capScope: "device",
42386
+ addonId: null,
42387
+ access: "view"
42388
+ },
39430
42389
  "motionTrigger.setMotionTrigger": {
39431
42390
  capName: "motion-trigger",
39432
42391
  capScope: "device",
@@ -39439,6 +42398,12 @@ Object.freeze({
39439
42398
  addonId: null,
39440
42399
  access: "view"
39441
42400
  },
42401
+ "motionZones.getStatus": {
42402
+ capName: "motion-zones",
42403
+ capScope: "device",
42404
+ addonId: null,
42405
+ access: "view"
42406
+ },
39442
42407
  "motionZones.setZone": {
39443
42408
  capName: "motion-zones",
39444
42409
  capScope: "device",
@@ -39499,6 +42464,12 @@ Object.freeze({
39499
42464
  addonId: null,
39500
42465
  access: "view"
39501
42466
  },
42467
+ "nativeObjectDetection.getStatus": {
42468
+ capName: "native-object-detection",
42469
+ capScope: "device",
42470
+ addonId: null,
42471
+ access: "view"
42472
+ },
39502
42473
  "nativeObjectDetection.setEnabled": {
39503
42474
  capName: "native-object-detection",
39504
42475
  capScope: "device",
@@ -39517,6 +42488,12 @@ Object.freeze({
39517
42488
  addonId: null,
39518
42489
  access: "view"
39519
42490
  },
42491
+ "navigation.getStatus": {
42492
+ capName: "navigation",
42493
+ capScope: "device",
42494
+ addonId: null,
42495
+ access: "view"
42496
+ },
39520
42497
  "navigation.goToPoint": {
39521
42498
  capName: "navigation",
39522
42499
  capScope: "device",
@@ -39601,6 +42578,12 @@ Object.freeze({
39601
42578
  addonId: null,
39602
42579
  access: "create"
39603
42580
  },
42581
+ "networkLink.getStatus": {
42582
+ capName: "network-link",
42583
+ capScope: "device",
42584
+ addonId: null,
42585
+ access: "view"
42586
+ },
39604
42587
  "networkQuality.getAllStats": {
39605
42588
  capName: "network-quality",
39606
42589
  capScope: "system",
@@ -39859,18 +42842,36 @@ Object.freeze({
39859
42842
  addonId: null,
39860
42843
  access: "create"
39861
42844
  },
42845
+ "notifier.getStatus": {
42846
+ capName: "notifier",
42847
+ capScope: "device",
42848
+ addonId: null,
42849
+ access: "view"
42850
+ },
39862
42851
  "notifier.send": {
39863
42852
  capName: "notifier",
39864
42853
  capScope: "device",
39865
42854
  addonId: null,
39866
42855
  access: "create"
39867
42856
  },
42857
+ "numericSensor.getStatus": {
42858
+ capName: "numeric-sensor",
42859
+ capScope: "device",
42860
+ addonId: null,
42861
+ access: "view"
42862
+ },
39868
42863
  "oauthIntegration.getDescriptor": {
39869
42864
  capName: "oauth-integration",
39870
42865
  capScope: "system",
39871
42866
  addonId: null,
39872
42867
  access: "view"
39873
42868
  },
42869
+ "osd.getStatus": {
42870
+ capName: "osd",
42871
+ capScope: "device",
42872
+ addonId: null,
42873
+ access: "view"
42874
+ },
39874
42875
  "osd.setOverlay": {
39875
42876
  capName: "osd",
39876
42877
  capScope: "device",
@@ -39943,6 +42944,12 @@ Object.freeze({
39943
42944
  addonId: null,
39944
42945
  access: "create"
39945
42946
  },
42947
+ "petFeeder.getStatus": {
42948
+ capName: "pet-feeder",
42949
+ capScope: "device",
42950
+ addonId: null,
42951
+ access: "view"
42952
+ },
39946
42953
  "petFeeder.markFoodReplenished": {
39947
42954
  capName: "pet-feeder",
39948
42955
  capScope: "device",
@@ -39985,6 +42992,12 @@ Object.freeze({
39985
42992
  addonId: null,
39986
42993
  access: "create"
39987
42994
  },
42995
+ "pipelineAnalytics.applyDeviceSettingsPatch": {
42996
+ capName: "pipeline-analytics",
42997
+ capScope: "device",
42998
+ addonId: null,
42999
+ access: "create"
43000
+ },
39988
43001
  "pipelineAnalytics.cancelRelocateMedia": {
39989
43002
  capName: "pipeline-analytics",
39990
43003
  capScope: "device",
@@ -40051,6 +43064,18 @@ Object.freeze({
40051
43064
  addonId: null,
40052
43065
  access: "view"
40053
43066
  },
43067
+ "pipelineAnalytics.getDeviceLiveContribution": {
43068
+ capName: "pipeline-analytics",
43069
+ capScope: "device",
43070
+ addonId: null,
43071
+ access: "view"
43072
+ },
43073
+ "pipelineAnalytics.getDeviceSettingsContribution": {
43074
+ capName: "pipeline-analytics",
43075
+ capScope: "device",
43076
+ addonId: null,
43077
+ access: "view"
43078
+ },
40054
43079
  "pipelineAnalytics.getEventDensity": {
40055
43080
  capName: "pipeline-analytics",
40056
43081
  capScope: "device",
@@ -40261,6 +43286,12 @@ Object.freeze({
40261
43286
  addonId: null,
40262
43287
  access: "view"
40263
43288
  },
43289
+ "pipelineAnalytics.ownersWithMedia": {
43290
+ capName: "pipeline-analytics",
43291
+ capScope: "device",
43292
+ addonId: null,
43293
+ access: "view"
43294
+ },
40264
43295
  "pipelineAnalytics.pauseForStorageMigration": {
40265
43296
  capName: "pipeline-analytics",
40266
43297
  capScope: "device",
@@ -40615,6 +43646,12 @@ Object.freeze({
40615
43646
  addonId: null,
40616
43647
  access: "view"
40617
43648
  },
43649
+ "pipelineOrchestrator.applyDeviceSettingsPatch": {
43650
+ capName: "pipeline-orchestrator",
43651
+ capScope: "system",
43652
+ addonId: null,
43653
+ access: "create"
43654
+ },
40618
43655
  "pipelineOrchestrator.assignAudio": {
40619
43656
  capName: "pipeline-orchestrator",
40620
43657
  capScope: "system",
@@ -40705,6 +43742,18 @@ Object.freeze({
40705
43742
  addonId: null,
40706
43743
  access: "view"
40707
43744
  },
43745
+ "pipelineOrchestrator.getDeviceLiveContribution": {
43746
+ capName: "pipeline-orchestrator",
43747
+ capScope: "system",
43748
+ addonId: null,
43749
+ access: "view"
43750
+ },
43751
+ "pipelineOrchestrator.getDeviceSettingsContribution": {
43752
+ capName: "pipeline-orchestrator",
43753
+ capScope: "system",
43754
+ addonId: null,
43755
+ access: "view"
43756
+ },
40708
43757
  "pipelineOrchestrator.getGlobalMetrics": {
40709
43758
  capName: "pipeline-orchestrator",
40710
43759
  capScope: "system",
@@ -41113,12 +44162,36 @@ Object.freeze({
41113
44162
  addonId: null,
41114
44163
  access: "view"
41115
44164
  },
44165
+ "powerMeter.getStatus": {
44166
+ capName: "power-meter",
44167
+ capScope: "device",
44168
+ addonId: null,
44169
+ access: "view"
44170
+ },
44171
+ "presence.getStatus": {
44172
+ capName: "presence",
44173
+ capScope: "device",
44174
+ addonId: null,
44175
+ access: "view"
44176
+ },
44177
+ "pressureSensor.getStatus": {
44178
+ capName: "pressure-sensor",
44179
+ capScope: "device",
44180
+ addonId: null,
44181
+ access: "view"
44182
+ },
41116
44183
  "privacyMask.getOptions": {
41117
44184
  capName: "privacy-mask",
41118
44185
  capScope: "device",
41119
44186
  addonId: null,
41120
44187
  access: "view"
41121
44188
  },
44189
+ "privacyMask.getStatus": {
44190
+ capName: "privacy-mask",
44191
+ capScope: "device",
44192
+ addonId: null,
44193
+ access: "view"
44194
+ },
41122
44195
  "privacyMask.setAudioEnabled": {
41123
44196
  capName: "privacy-mask",
41124
44197
  capScope: "device",
@@ -41179,6 +44252,12 @@ Object.freeze({
41179
44252
  addonId: null,
41180
44253
  access: "view"
41181
44254
  },
44255
+ "ptz.getStatus": {
44256
+ capName: "ptz",
44257
+ capScope: "device",
44258
+ addonId: null,
44259
+ access: "view"
44260
+ },
41182
44261
  "ptz.goHome": {
41183
44262
  capName: "ptz",
41184
44263
  capScope: "device",
@@ -41257,6 +44336,12 @@ Object.freeze({
41257
44336
  addonId: null,
41258
44337
  access: "create"
41259
44338
  },
44339
+ "recording.applyDeviceSettingsPatch": {
44340
+ capName: "recording",
44341
+ capScope: "system",
44342
+ addonId: null,
44343
+ access: "create"
44344
+ },
41260
44345
  "recording.cancelRelocateJob": {
41261
44346
  capName: "recording",
41262
44347
  capScope: "system",
@@ -41305,6 +44390,18 @@ Object.freeze({
41305
44390
  addonId: null,
41306
44391
  access: "view"
41307
44392
  },
44393
+ "recording.getDeviceLiveContribution": {
44394
+ capName: "recording",
44395
+ capScope: "system",
44396
+ addonId: null,
44397
+ access: "view"
44398
+ },
44399
+ "recording.getDeviceSettingsContribution": {
44400
+ capName: "recording",
44401
+ capScope: "system",
44402
+ addonId: null,
44403
+ access: "view"
44404
+ },
41308
44405
  "recording.getPlacement": {
41309
44406
  capName: "recording",
41310
44407
  capScope: "system",
@@ -41323,6 +44420,12 @@ Object.freeze({
41323
44420
  addonId: null,
41324
44421
  access: "view"
41325
44422
  },
44423
+ "recording.getStatus": {
44424
+ capName: "recording",
44425
+ capScope: "system",
44426
+ addonId: null,
44427
+ access: "view"
44428
+ },
41326
44429
  "recording.getStorageMigrationMoveStatus": {
41327
44430
  capName: "recording",
41328
44431
  capScope: "system",
@@ -41497,6 +44600,24 @@ Object.freeze({
41497
44600
  addonId: null,
41498
44601
  access: "view"
41499
44602
  },
44603
+ "recordingOnboard.getOptions": {
44604
+ capName: "recording-onboard",
44605
+ capScope: "device",
44606
+ addonId: null,
44607
+ access: "view"
44608
+ },
44609
+ "recordingOnboard.getStatus": {
44610
+ capName: "recording-onboard",
44611
+ capScope: "device",
44612
+ addonId: null,
44613
+ access: "view"
44614
+ },
44615
+ "recordingOnboard.setSettings": {
44616
+ capName: "recording-onboard",
44617
+ capScope: "device",
44618
+ addonId: null,
44619
+ access: "create"
44620
+ },
41500
44621
  "recordingSignal.getStatus": {
41501
44622
  capName: "recording-signal",
41502
44623
  capScope: "device",
@@ -41527,6 +44648,12 @@ Object.freeze({
41527
44648
  addonId: null,
41528
44649
  access: "delete"
41529
44650
  },
44651
+ "sceneMonitor.getStatus": {
44652
+ capName: "scene-monitor",
44653
+ capScope: "device",
44654
+ addonId: null,
44655
+ access: "view"
44656
+ },
41530
44657
  "sceneMonitor.listScenes": {
41531
44658
  capName: "scene-monitor",
41532
44659
  capScope: "device",
@@ -41557,6 +44684,12 @@ Object.freeze({
41557
44684
  addonId: null,
41558
44685
  access: "create"
41559
44686
  },
44687
+ "scriptRunner.getStatus": {
44688
+ capName: "script-runner",
44689
+ capScope: "device",
44690
+ addonId: null,
44691
+ access: "view"
44692
+ },
41560
44693
  "scriptRunner.run": {
41561
44694
  capName: "script-runner",
41562
44695
  capScope: "device",
@@ -41689,6 +44822,12 @@ Object.freeze({
41689
44822
  addonId: null,
41690
44823
  access: "create"
41691
44824
  },
44825
+ "smoke.getStatus": {
44826
+ capName: "smoke",
44827
+ capScope: "device",
44828
+ addonId: null,
44829
+ access: "view"
44830
+ },
41692
44831
  "smtpProvider.getStatus": {
41693
44832
  capName: "smtp-provider",
41694
44833
  capScope: "system",
@@ -41707,12 +44846,30 @@ Object.freeze({
41707
44846
  addonId: null,
41708
44847
  access: "view"
41709
44848
  },
44849
+ "snapshot.applyDeviceSettingsPatch": {
44850
+ capName: "snapshot",
44851
+ capScope: "device",
44852
+ addonId: null,
44853
+ access: "create"
44854
+ },
41710
44855
  "snapshot.getDebugState": {
41711
44856
  capName: "snapshot",
41712
44857
  capScope: "device",
41713
44858
  addonId: null,
41714
44859
  access: "view"
41715
44860
  },
44861
+ "snapshot.getDeviceLiveContribution": {
44862
+ capName: "snapshot",
44863
+ capScope: "device",
44864
+ addonId: null,
44865
+ access: "view"
44866
+ },
44867
+ "snapshot.getDeviceSettingsContribution": {
44868
+ capName: "snapshot",
44869
+ capScope: "device",
44870
+ addonId: null,
44871
+ access: "view"
44872
+ },
41716
44873
  "snapshot.getSnapshot": {
41717
44874
  capName: "snapshot",
41718
44875
  capScope: "device",
@@ -41731,6 +44888,12 @@ Object.freeze({
41731
44888
  addonId: null,
41732
44889
  access: "view"
41733
44890
  },
44891
+ "snapshot.getStatus": {
44892
+ capName: "snapshot",
44893
+ capScope: "device",
44894
+ addonId: null,
44895
+ access: "view"
44896
+ },
41734
44897
  "snapshot.invalidateCache": {
41735
44898
  capName: "snapshot",
41736
44899
  capScope: "device",
@@ -42067,6 +45230,12 @@ Object.freeze({
42067
45230
  addonId: null,
42068
45231
  access: "create"
42069
45232
  },
45233
+ "streamBroker.applyDeviceSettingsPatch": {
45234
+ capName: "stream-broker",
45235
+ capScope: "system",
45236
+ addonId: null,
45237
+ access: "create"
45238
+ },
42070
45239
  "streamBroker.assignProfile": {
42071
45240
  capName: "stream-broker",
42072
45241
  capScope: "system",
@@ -42103,6 +45272,18 @@ Object.freeze({
42103
45272
  addonId: null,
42104
45273
  access: "view"
42105
45274
  },
45275
+ "streamBroker.getDeviceLiveContribution": {
45276
+ capName: "stream-broker",
45277
+ capScope: "system",
45278
+ addonId: null,
45279
+ access: "view"
45280
+ },
45281
+ "streamBroker.getDeviceSettingsContribution": {
45282
+ capName: "stream-broker",
45283
+ capScope: "system",
45284
+ addonId: null,
45285
+ access: "view"
45286
+ },
42106
45287
  "streamBroker.getPreBufferInfo": {
42107
45288
  capName: "stream-broker",
42108
45289
  capScope: "system",
@@ -42301,6 +45482,12 @@ Object.freeze({
42301
45482
  addonId: null,
42302
45483
  access: "view"
42303
45484
  },
45485
+ "streamParams.getStatus": {
45486
+ capName: "stream-params",
45487
+ capScope: "device",
45488
+ addonId: null,
45489
+ access: "view"
45490
+ },
42304
45491
  "streamParams.setProfile": {
42305
45492
  capName: "stream-params",
42306
45493
  capScope: "device",
@@ -42313,6 +45500,12 @@ Object.freeze({
42313
45500
  addonId: null,
42314
45501
  access: "view"
42315
45502
  },
45503
+ "switch.getStatus": {
45504
+ capName: "switch",
45505
+ capScope: "device",
45506
+ addonId: null,
45507
+ access: "view"
45508
+ },
42316
45509
  "switch.setState": {
42317
45510
  capName: "switch",
42318
45511
  capScope: "device",
@@ -42409,6 +45602,18 @@ Object.freeze({
42409
45602
  addonId: null,
42410
45603
  access: "create"
42411
45604
  },
45605
+ "tamper.getStatus": {
45606
+ capName: "tamper",
45607
+ capScope: "device",
45608
+ addonId: null,
45609
+ access: "view"
45610
+ },
45611
+ "temperatureSensor.getStatus": {
45612
+ capName: "temperature-sensor",
45613
+ capScope: "device",
45614
+ addonId: null,
45615
+ access: "view"
45616
+ },
42412
45617
  "terminalSession.adoptLegacyMonitor": {
42413
45618
  capName: "terminal-session",
42414
45619
  capScope: "system",
@@ -42505,6 +45710,12 @@ Object.freeze({
42505
45710
  addonId: null,
42506
45711
  access: "view"
42507
45712
  },
45713
+ "update.getStatus": {
45714
+ capName: "update",
45715
+ capScope: "device",
45716
+ addonId: null,
45717
+ access: "view"
45718
+ },
42508
45719
  "update.installUpdate": {
42509
45720
  capName: "update",
42510
45721
  capScope: "device",
@@ -42727,6 +45938,12 @@ Object.freeze({
42727
45938
  addonId: null,
42728
45939
  access: "create"
42729
45940
  },
45941
+ "vacuumControl.getStatus": {
45942
+ capName: "vacuum-control",
45943
+ capScope: "device",
45944
+ addonId: null,
45945
+ access: "view"
45946
+ },
42730
45947
  "vacuumControl.locate": {
42731
45948
  capName: "vacuum-control",
42732
45949
  capScope: "device",
@@ -42769,6 +45986,12 @@ Object.freeze({
42769
45986
  addonId: null,
42770
45987
  access: "create"
42771
45988
  },
45989
+ "valve.getStatus": {
45990
+ capName: "valve",
45991
+ capScope: "device",
45992
+ addonId: null,
45993
+ access: "view"
45994
+ },
42772
45995
  "valve.open": {
42773
45996
  capName: "valve",
42774
45997
  capScope: "device",
@@ -42841,6 +46064,18 @@ Object.freeze({
42841
46064
  addonId: null,
42842
46065
  access: "create"
42843
46066
  },
46067
+ "vibration.getStatus": {
46068
+ capName: "vibration",
46069
+ capScope: "device",
46070
+ addonId: null,
46071
+ access: "view"
46072
+ },
46073
+ "videoclips.dialClipStream": {
46074
+ capName: "videoclips",
46075
+ capScope: "device",
46076
+ addonId: null,
46077
+ access: "view"
46078
+ },
42844
46079
  "videoclips.getClipPlayback": {
42845
46080
  capName: "videoclips",
42846
46081
  capScope: "device",
@@ -42853,6 +46088,18 @@ Object.freeze({
42853
46088
  addonId: null,
42854
46089
  access: "view"
42855
46090
  },
46091
+ "videoclips.listSources": {
46092
+ capName: "videoclips",
46093
+ capScope: "device",
46094
+ addonId: null,
46095
+ access: "view"
46096
+ },
46097
+ "videoclips.readClipBytes": {
46098
+ capName: "videoclips",
46099
+ capScope: "device",
46100
+ addonId: null,
46101
+ access: "view"
46102
+ },
42856
46103
  "viewerUi.getStaticDir": {
42857
46104
  capName: "viewer-ui",
42858
46105
  capScope: "system",
@@ -42865,6 +46112,12 @@ Object.freeze({
42865
46112
  addonId: null,
42866
46113
  access: "view"
42867
46114
  },
46115
+ "waterHeater.getStatus": {
46116
+ capName: "water-heater",
46117
+ capScope: "device",
46118
+ addonId: null,
46119
+ access: "view"
46120
+ },
42868
46121
  "waterHeater.setAway": {
42869
46122
  capName: "water-heater",
42870
46123
  capScope: "device",
@@ -42883,6 +46136,12 @@ Object.freeze({
42883
46136
  addonId: null,
42884
46137
  access: "create"
42885
46138
  },
46139
+ "weather.getStatus": {
46140
+ capName: "weather",
46141
+ capScope: "device",
46142
+ addonId: null,
46143
+ access: "view"
46144
+ },
42886
46145
  "webrtcSession.addIceCandidate": {
42887
46146
  capName: "webrtc-session",
42888
46147
  capScope: "device",
@@ -43245,6 +46504,11 @@ Object.freeze({
43245
46504
  form: "single",
43246
46505
  optional: true
43247
46506
  }],
46507
+ "deviceAdminLink.getAdminLink": [{
46508
+ name: "deviceId",
46509
+ form: "single",
46510
+ optional: false
46511
+ }],
43248
46512
  "deviceAdoption.release": [{
43249
46513
  name: "camDeviceId",
43250
46514
  form: "single",
@@ -44155,6 +47419,11 @@ Object.freeze({
44155
47419
  form: "single",
44156
47420
  optional: false
44157
47421
  }],
47422
+ "pipelineAnalytics.ownersWithMedia": [{
47423
+ name: "deviceId",
47424
+ form: "single",
47425
+ optional: false
47426
+ }],
44158
47427
  "pipelineAnalytics.proposeRetrainAnnotations": [{
44159
47428
  name: "deviceId",
44160
47429
  form: "single",
@@ -44625,6 +47894,16 @@ Object.freeze({
44625
47894
  form: "single",
44626
47895
  optional: true
44627
47896
  }],
47897
+ "recordingOnboard.getOptions": [{
47898
+ name: "deviceId",
47899
+ form: "single",
47900
+ optional: false
47901
+ }],
47902
+ "recordingOnboard.setSettings": [{
47903
+ name: "deviceId",
47904
+ form: "single",
47905
+ optional: false
47906
+ }],
44628
47907
  "recordingSignal.getStatus": [{
44629
47908
  name: "deviceId",
44630
47909
  form: "single",
@@ -44851,6 +48130,11 @@ Object.freeze({
44851
48130
  form: "single",
44852
48131
  optional: false
44853
48132
  }],
48133
+ "videoclips.dialClipStream": [{
48134
+ name: "deviceId",
48135
+ form: "single",
48136
+ optional: false
48137
+ }],
44854
48138
  "videoclips.getClipPlayback": [{
44855
48139
  name: "deviceId",
44856
48140
  form: "single",
@@ -44861,6 +48145,16 @@ Object.freeze({
44861
48145
  form: "single",
44862
48146
  optional: false
44863
48147
  }],
48148
+ "videoclips.listSources": [{
48149
+ name: "deviceId",
48150
+ form: "single",
48151
+ optional: false
48152
+ }],
48153
+ "videoclips.readClipBytes": [{
48154
+ name: "deviceId",
48155
+ form: "single",
48156
+ optional: false
48157
+ }],
44864
48158
  "waterHeater.setAway": [{
44865
48159
  name: "deviceId",
44866
48160
  form: "single",
@@ -45959,6 +49253,12 @@ Object.defineProperty(exports, "buildAddonRouteProvider", {
45959
49253
  return buildAddonRouteProvider;
45960
49254
  }
45961
49255
  });
49256
+ Object.defineProperty(exports, "buildDeviceAdminUrl", {
49257
+ enumerable: true,
49258
+ get: function() {
49259
+ return buildDeviceAdminUrl;
49260
+ }
49261
+ });
45962
49262
  Object.defineProperty(exports, "buttonCapability", {
45963
49263
  enumerable: true,
45964
49264
  get: function() {
@@ -46013,6 +49313,12 @@ Object.defineProperty(exports, "createEvent", {
46013
49313
  return createEvent;
46014
49314
  }
46015
49315
  });
49316
+ Object.defineProperty(exports, "deviceAdminLinkCapability", {
49317
+ enumerable: true,
49318
+ get: function() {
49319
+ return deviceAdminLinkCapability;
49320
+ }
49321
+ });
46016
49322
  Object.defineProperty(exports, "deviceAdoptionCapability", {
46017
49323
  enumerable: true,
46018
49324
  get: function() {
@@ -46163,6 +49469,12 @@ Object.defineProperty(exports, "object", {
46163
49469
  return object;
46164
49470
  }
46165
49471
  });
49472
+ Object.defineProperty(exports, "parseAdminBase", {
49473
+ enumerable: true,
49474
+ get: function() {
49475
+ return parseAdminBase;
49476
+ }
49477
+ });
46166
49478
  Object.defineProperty(exports, "powerMeterCapability", {
46167
49479
  enumerable: true,
46168
49480
  get: function() {