@camstack/addon-post-analysis 1.2.650 → 1.2.652
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/{clip-model-registry-B_mY0hd7.mjs → clip-model-registry-BrazZJvr.mjs} +2497 -726
- package/dist/{clip-model-registry-CC7pvitG.js → clip-model-registry-Bz1uQvQ8.js} +2497 -726
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-HjnmptAc.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D6DnF6Vk.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BKMr4IqL.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CtNyIYsM.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-BjUFFpLS.mjs → hostInit-DtrcY1yz.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +55 -4
- package/dist/pipeline-analytics/index.mjs +55 -5
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-YNYqWfWY.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-OPayQG8i.mjs +0 -26
|
@@ -5676,7 +5676,7 @@ var ZodIssueCode = {
|
|
|
5676
5676
|
var ZodFirstPartyTypeKind;
|
|
5677
5677
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5678
5678
|
//#endregion
|
|
5679
|
-
//#region ../types/dist/sleep-
|
|
5679
|
+
//#region ../types/dist/sleep-63gM_1JR.mjs
|
|
5680
5680
|
/**
|
|
5681
5681
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5682
5682
|
* window to float samples (D455).
|
|
@@ -8777,65 +8777,6 @@ var MODEL_FORMATS = [
|
|
|
8777
8777
|
"pt",
|
|
8778
8778
|
"gguf"
|
|
8779
8779
|
];
|
|
8780
|
-
/**
|
|
8781
|
-
* Per-call node pinning for `ctx.api` capability calls.
|
|
8782
|
-
*
|
|
8783
|
-
* A capability call normally resolves to its DEFAULT provider — a `singleton`
|
|
8784
|
-
* cap resolves to the hub, a device-scoped cap to the device's owning node. To
|
|
8785
|
-
* query a SPECIFIC node's provider instead (e.g. a remote agent's own
|
|
8786
|
-
* in-process `platform-probe` hardware, which the hub cannot probe), pin the
|
|
8787
|
-
* call to that node.
|
|
8788
|
-
*
|
|
8789
|
-
* The nodeId rides OUT-OF-BAND in the tRPC call context (NOT in the validated
|
|
8790
|
-
* method args), so capability method signatures stay `nodeId`-free — node
|
|
8791
|
-
* targeting is a property of the CALL, not of the method. The transport lifts
|
|
8792
|
-
* it from `op.context` onto the `CapCallInput.nodeId` field (`ipcParentLink`),
|
|
8793
|
-
* and the hub parent's `onUnownedCall` passes it to the `CapRouteResolver`,
|
|
8794
|
-
* which classifies a pinned agent node as `agent-child-forward`
|
|
8795
|
-
* (`$agent-cap-fwd.forward` → the agent's in-process provider).
|
|
8796
|
-
*
|
|
8797
|
-
* Usage at a call site:
|
|
8798
|
-
*
|
|
8799
|
-
* await api.platformProbe.getCapabilities.query(undefined, nodePin(nodeId))
|
|
8800
|
-
*/
|
|
8801
|
-
/** tRPC `op.context` key carrying a per-call node pin. */
|
|
8802
|
-
var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
8803
|
-
/**
|
|
8804
|
-
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
8805
|
-
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
8806
|
-
*
|
|
8807
|
-
* ## The id is normalised here, and it has to be
|
|
8808
|
-
*
|
|
8809
|
-
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
8810
|
-
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
8811
|
-
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
8812
|
-
* call fails with `no provider registered for cap "…"`. The local-first
|
|
8813
|
-
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
8814
|
-
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
8815
|
-
* the thing you reach for when you specifically need THIS node — silently
|
|
8816
|
-
* addressed a node that does not exist.
|
|
8817
|
-
*
|
|
8818
|
-
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
8819
|
-
* own node to read the host's hardware-decode backend. It never once answered,
|
|
8820
|
-
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
8821
|
-
* while D67's whole premise was that the decoder addon is the authority on
|
|
8822
|
-
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
8823
|
-
* hardware", which was false.
|
|
8824
|
-
*
|
|
8825
|
-
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
8826
|
-
* it is here and not at the call sites.
|
|
8827
|
-
*/
|
|
8828
|
-
function nodePin(nodeId) {
|
|
8829
|
-
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
8830
|
-
}
|
|
8831
|
-
/**
|
|
8832
|
-
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
8833
|
-
* is idempotent, so passing an already-clean id costs nothing.
|
|
8834
|
-
*/
|
|
8835
|
-
function toNodeId(idOrRunnerId) {
|
|
8836
|
-
const head = idOrRunnerId.split("/")[0];
|
|
8837
|
-
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
8838
|
-
}
|
|
8839
8780
|
object({
|
|
8840
8781
|
field: string().min(1),
|
|
8841
8782
|
on: literal("advance")
|
|
@@ -8928,6 +8869,111 @@ function optionalMethod(input, output, options) {
|
|
|
8928
8869
|
function event(data) {
|
|
8929
8870
|
return { data };
|
|
8930
8871
|
}
|
|
8872
|
+
/**
|
|
8873
|
+
* Per-call node pinning for `ctx.api` capability calls.
|
|
8874
|
+
*
|
|
8875
|
+
* A capability call normally resolves to its DEFAULT provider — a `singleton`
|
|
8876
|
+
* cap resolves to the hub, a device-scoped cap to the device's owning node. To
|
|
8877
|
+
* query a SPECIFIC node's provider instead (e.g. a remote agent's own
|
|
8878
|
+
* in-process `platform-probe` hardware, which the hub cannot probe), pin the
|
|
8879
|
+
* call to that node.
|
|
8880
|
+
*
|
|
8881
|
+
* The nodeId rides OUT-OF-BAND in the tRPC call context (NOT in the validated
|
|
8882
|
+
* method args), so capability method signatures stay `nodeId`-free — node
|
|
8883
|
+
* targeting is a property of the CALL, not of the method. The transport lifts
|
|
8884
|
+
* it from `op.context` onto the `CapCallInput.nodeId` field (`ipcParentLink`),
|
|
8885
|
+
* and the hub parent's `onUnownedCall` passes it to the `CapRouteResolver`,
|
|
8886
|
+
* which classifies a pinned agent node as `agent-child-forward`
|
|
8887
|
+
* (`$agent-cap-fwd.forward` → the agent's in-process provider).
|
|
8888
|
+
*
|
|
8889
|
+
* Usage at a call site:
|
|
8890
|
+
*
|
|
8891
|
+
* await api.platformProbe.getCapabilities.query(undefined, nodePin(nodeId))
|
|
8892
|
+
*/
|
|
8893
|
+
/** tRPC `op.context` key carrying a per-call node pin. */
|
|
8894
|
+
var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
8895
|
+
/**
|
|
8896
|
+
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
8897
|
+
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
8898
|
+
*
|
|
8899
|
+
* ## The id is normalised here, and it has to be
|
|
8900
|
+
*
|
|
8901
|
+
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
8902
|
+
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
8903
|
+
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
8904
|
+
* call fails with `no provider registered for cap "…"`. The local-first
|
|
8905
|
+
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
8906
|
+
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
8907
|
+
* the thing you reach for when you specifically need THIS node — silently
|
|
8908
|
+
* addressed a node that does not exist.
|
|
8909
|
+
*
|
|
8910
|
+
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
8911
|
+
* own node to read the host's hardware-decode backend. It never once answered,
|
|
8912
|
+
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
8913
|
+
* while D67's whole premise was that the decoder addon is the authority on
|
|
8914
|
+
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
8915
|
+
* hardware", which was false.
|
|
8916
|
+
*
|
|
8917
|
+
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
8918
|
+
* it is here and not at the call sites.
|
|
8919
|
+
*/
|
|
8920
|
+
function nodePin(nodeId) {
|
|
8921
|
+
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
8922
|
+
}
|
|
8923
|
+
/**
|
|
8924
|
+
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
8925
|
+
* is idempotent, so passing an already-clean id costs nothing.
|
|
8926
|
+
*/
|
|
8927
|
+
function toNodeId(idOrRunnerId) {
|
|
8928
|
+
const head = idOrRunnerId.split("/")[0];
|
|
8929
|
+
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
8930
|
+
}
|
|
8931
|
+
/**
|
|
8932
|
+
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
8933
|
+
* declarations. Every addon that contributes a UI page registers a
|
|
8934
|
+
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
8935
|
+
* see `addon-pages.cap.ts`) walks this collection, stamps versioned
|
|
8936
|
+
* `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
|
|
8937
|
+
* that admin-ui consumes.
|
|
8938
|
+
*
|
|
8939
|
+
* The split exists because the public listing has a different output
|
|
8940
|
+
* shape than the per-provider raw declarations, and we want both ends
|
|
8941
|
+
* to flow through codegen instead of relying on a hand-written wrapper.
|
|
8942
|
+
*/
|
|
8943
|
+
var AddonPageDeclarationSchema = object({
|
|
8944
|
+
id: string(),
|
|
8945
|
+
label: string(),
|
|
8946
|
+
icon: string(),
|
|
8947
|
+
path: string(),
|
|
8948
|
+
/**
|
|
8949
|
+
* Module Federation remote name — must match the `name` field on the
|
|
8950
|
+
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
8951
|
+
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
8952
|
+
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
8953
|
+
* contain hyphens).
|
|
8954
|
+
*/
|
|
8955
|
+
remoteName: string(),
|
|
8956
|
+
/**
|
|
8957
|
+
* Bundle filename inside the addon's `dist/` dir served at
|
|
8958
|
+
* `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
|
|
8959
|
+
* is always `'remoteEntry.js'`; the value is kept on the metadata so
|
|
8960
|
+
* the static-file route can compute an mtime-based cache-buster URL
|
|
8961
|
+
* without a separate filesystem stat.
|
|
8962
|
+
*/
|
|
8963
|
+
bundle: string(),
|
|
8964
|
+
/**
|
|
8965
|
+
* Sidebar section this page docks into. Well-known ids: `'detection'`,
|
|
8966
|
+
* `'cluster'`, `'administration'` — the page renders inside that group.
|
|
8967
|
+
* Any OTHER string creates (or joins) a custom section rendered after
|
|
8968
|
+
* the built-in groups; its label comes from `sectionLabel` (first
|
|
8969
|
+
* declaration wins), falling back to the id. Absent → the legacy
|
|
8970
|
+
* "Addon Pages" group.
|
|
8971
|
+
*/
|
|
8972
|
+
section: string().optional(),
|
|
8973
|
+
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
8974
|
+
sectionLabel: string().optional()
|
|
8975
|
+
});
|
|
8976
|
+
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
8931
8977
|
var StaticDirOutputSchema$1 = object({ staticDir: string() });
|
|
8932
8978
|
var VersionOutputSchema$1 = object({ version: string() });
|
|
8933
8979
|
method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
|
|
@@ -8953,6 +8999,27 @@ function customAction(input, output, options) {
|
|
|
8953
8999
|
...options?.caller ? { caller: "required" } : {}
|
|
8954
9000
|
};
|
|
8955
9001
|
}
|
|
9002
|
+
/**
|
|
9003
|
+
* The pipeline step-input shape and the run-result bridge, in a module that
|
|
9004
|
+
* imports nothing but zod and types.
|
|
9005
|
+
*
|
|
9006
|
+
* WHY A SEPARATE FILE (addon footprint, D28): both were declared in
|
|
9007
|
+
* `pipeline-executor.cap.ts`, whose import evaluates the whole executor schema
|
|
9008
|
+
* graph. An addon that only needs these two (the benchmark's `runPipelineTest`
|
|
9009
|
+
* action) paid ~130 MB of heap at load. `pipeline-executor.cap.ts` re-exports
|
|
9010
|
+
* them, so every existing import keeps working; addon server code takes them
|
|
9011
|
+
* from `@camstack/types/addon`.
|
|
9012
|
+
*/
|
|
9013
|
+
var PipelineStepInputSchema = lazy(() => object({
|
|
9014
|
+
addonId: string(),
|
|
9015
|
+
modelId: string().optional(),
|
|
9016
|
+
enabled: boolean().default(true),
|
|
9017
|
+
children: array(PipelineStepInputSchema).optional(),
|
|
9018
|
+
settings: record(string(), unknown()).optional(),
|
|
9019
|
+
jumpDeviceKey: string().optional()
|
|
9020
|
+
}));
|
|
9021
|
+
/** Typed bridge to the recursive `FrameResult` graph; validation is structural. */
|
|
9022
|
+
var PipelineRunResultBridge = custom();
|
|
8956
9023
|
var StaticDirOutputSchema = object({ staticDir: string() });
|
|
8957
9024
|
var VersionOutputSchema = object({ version: string() });
|
|
8958
9025
|
method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
|
|
@@ -9138,6 +9205,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9138
9205
|
"network-link",
|
|
9139
9206
|
"network-quality",
|
|
9140
9207
|
"node-certificates",
|
|
9208
|
+
"node-removal-hook",
|
|
9141
9209
|
"nodes",
|
|
9142
9210
|
"notification-output",
|
|
9143
9211
|
"notification-rules",
|
|
@@ -9148,6 +9216,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9148
9216
|
"osd",
|
|
9149
9217
|
"osd-manager",
|
|
9150
9218
|
"pet-feeder",
|
|
9219
|
+
"pet-fountain",
|
|
9151
9220
|
"pipeline-analytics",
|
|
9152
9221
|
"pipeline-executor",
|
|
9153
9222
|
"pipeline-orchestrator",
|
|
@@ -9168,6 +9237,8 @@ var CAP_NAMES = Object.freeze([
|
|
|
9168
9237
|
"recording-onboard",
|
|
9169
9238
|
"recording-signal",
|
|
9170
9239
|
"records",
|
|
9240
|
+
"remote",
|
|
9241
|
+
"remote-layout-components",
|
|
9171
9242
|
"running-state",
|
|
9172
9243
|
"scene-monitor",
|
|
9173
9244
|
"script-runner",
|
|
@@ -9191,6 +9262,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9191
9262
|
"system",
|
|
9192
9263
|
"tamper",
|
|
9193
9264
|
"temperature-sensor",
|
|
9265
|
+
"template",
|
|
9194
9266
|
"terminal-session",
|
|
9195
9267
|
"toast",
|
|
9196
9268
|
"training",
|
|
@@ -10131,6 +10203,11 @@ Object.freeze({
|
|
|
10131
10203
|
"standby"
|
|
10132
10204
|
]
|
|
10133
10205
|
},
|
|
10206
|
+
{
|
|
10207
|
+
"path": "stateSource",
|
|
10208
|
+
"valueType": "enum",
|
|
10209
|
+
"enumValues": ["reported", "assumed"]
|
|
10210
|
+
},
|
|
10134
10211
|
{
|
|
10135
10212
|
"path": "volumeLevel",
|
|
10136
10213
|
"valueType": "number"
|
|
@@ -10406,6 +10483,68 @@ Object.freeze({
|
|
|
10406
10483
|
{
|
|
10407
10484
|
"path": "volume",
|
|
10408
10485
|
"valueType": "number"
|
|
10486
|
+
},
|
|
10487
|
+
{
|
|
10488
|
+
"path": "lastFeedGrams",
|
|
10489
|
+
"valueType": "number"
|
|
10490
|
+
},
|
|
10491
|
+
{
|
|
10492
|
+
"path": "lastFeedHopper1",
|
|
10493
|
+
"valueType": "number"
|
|
10494
|
+
},
|
|
10495
|
+
{
|
|
10496
|
+
"path": "lastFeedHopper2",
|
|
10497
|
+
"valueType": "number"
|
|
10498
|
+
}
|
|
10499
|
+
],
|
|
10500
|
+
"pet-fountain": [
|
|
10501
|
+
{
|
|
10502
|
+
"path": "waterLevel",
|
|
10503
|
+
"valueType": "number"
|
|
10504
|
+
},
|
|
10505
|
+
{
|
|
10506
|
+
"path": "waterVolume",
|
|
10507
|
+
"valueType": "number"
|
|
10508
|
+
},
|
|
10509
|
+
{
|
|
10510
|
+
"path": "empty",
|
|
10511
|
+
"valueType": "boolean"
|
|
10512
|
+
},
|
|
10513
|
+
{
|
|
10514
|
+
"path": "pumpRunning",
|
|
10515
|
+
"valueType": "boolean"
|
|
10516
|
+
},
|
|
10517
|
+
{
|
|
10518
|
+
"path": "pumpIntervalEnabled",
|
|
10519
|
+
"valueType": "boolean"
|
|
10520
|
+
},
|
|
10521
|
+
{
|
|
10522
|
+
"path": "pumpOnTime",
|
|
10523
|
+
"valueType": "number"
|
|
10524
|
+
},
|
|
10525
|
+
{
|
|
10526
|
+
"path": "pumpOffTime",
|
|
10527
|
+
"valueType": "number"
|
|
10528
|
+
},
|
|
10529
|
+
{
|
|
10530
|
+
"path": "lastDrink",
|
|
10531
|
+
"valueType": "number"
|
|
10532
|
+
},
|
|
10533
|
+
{
|
|
10534
|
+
"path": "currentHourDrink",
|
|
10535
|
+
"valueType": "number"
|
|
10536
|
+
},
|
|
10537
|
+
{
|
|
10538
|
+
"path": "last24hDrink",
|
|
10539
|
+
"valueType": "number"
|
|
10540
|
+
},
|
|
10541
|
+
{
|
|
10542
|
+
"path": "todayDrink",
|
|
10543
|
+
"valueType": "number"
|
|
10544
|
+
},
|
|
10545
|
+
{
|
|
10546
|
+
"path": "error",
|
|
10547
|
+
"valueType": "string"
|
|
10409
10548
|
}
|
|
10410
10549
|
],
|
|
10411
10550
|
"power-meter": [
|
|
@@ -10947,7 +11086,7 @@ Object.freeze({
|
|
|
10947
11086
|
* can answer "may a rule actuate this?" without importing the schema barrel
|
|
10948
11087
|
* (~144MB RSS per runner, D28).
|
|
10949
11088
|
*
|
|
10950
|
-
* Coverage:
|
|
11089
|
+
* Coverage: 97 device-scoped capabilities.
|
|
10951
11090
|
*/
|
|
10952
11091
|
var DEVICE_SCOPED_CAPS = new Set([
|
|
10953
11092
|
"accessories",
|
|
@@ -11013,6 +11152,7 @@ var DEVICE_SCOPED_CAPS = new Set([
|
|
|
11013
11152
|
"numeric-sensor",
|
|
11014
11153
|
"osd",
|
|
11015
11154
|
"pet-feeder",
|
|
11155
|
+
"pet-fountain",
|
|
11016
11156
|
"pipeline-analytics",
|
|
11017
11157
|
"power-meter",
|
|
11018
11158
|
"presence",
|
|
@@ -11091,7 +11231,7 @@ function sleep(ms) {
|
|
|
11091
11231
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11092
11232
|
}
|
|
11093
11233
|
//#endregion
|
|
11094
|
-
//#region ../types/dist/err-msg-
|
|
11234
|
+
//#region ../types/dist/err-msg-DWXWc0Di.mjs
|
|
11095
11235
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
11096
11236
|
DeviceType["Camera"] = "camera";
|
|
11097
11237
|
DeviceType["Hub"] = "hub";
|
|
@@ -11183,6 +11323,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
11183
11323
|
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
11184
11324
|
* integrations sharing the same food/desiccant/hopper surface. */
|
|
11185
11325
|
DeviceType["PetFeeder"] = "pet-feeder";
|
|
11326
|
+
/** Pet water fountain — tank level, water drunk, pump and its duty cycle.
|
|
11327
|
+
* Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
|
|
11328
|
+
* of a container and is BUILT as a composed device (D967). */
|
|
11329
|
+
DeviceType["PetFountain"] = "pet-fountain";
|
|
11186
11330
|
/** Built-in dishwasher — program lifecycle, door / remote-start gates,
|
|
11187
11331
|
* salt and rinse-aid levels. Installed with the `dishwasher` cap.
|
|
11188
11332
|
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
@@ -11453,7 +11597,7 @@ function errMsg(err) {
|
|
|
11453
11597
|
return String(err);
|
|
11454
11598
|
}
|
|
11455
11599
|
//#endregion
|
|
11456
|
-
//#region ../types/dist/composition-
|
|
11600
|
+
//#region ../types/dist/composition-kSuOifuG.mjs
|
|
11457
11601
|
/**
|
|
11458
11602
|
* Deterministic SHA-256 hash of an arbitrary serialisable value. The
|
|
11459
11603
|
* canonical form sorts object keys alphabetically at every depth so two
|
|
@@ -12698,12 +12842,28 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
|
|
|
12698
12842
|
])
|
|
12699
12843
|
})
|
|
12700
12844
|
]);
|
|
12701
|
-
var
|
|
12845
|
+
var forwardArgUnion = union([
|
|
12846
|
+
string(),
|
|
12847
|
+
number(),
|
|
12848
|
+
boolean(),
|
|
12849
|
+
strictObject({ ["$input"]: string().min(1) }),
|
|
12850
|
+
record(string().min(1), lazy(() => forwardArgUnion))
|
|
12851
|
+
]);
|
|
12852
|
+
var CompositionForwardArgSchema = forwardArgUnion;
|
|
12853
|
+
/**
|
|
12854
|
+
* `forward` (D659, amended by D967): one method of a BUILT capability is one
|
|
12855
|
+
* call of `cap.method` on `source`. Without `args` the built method's input
|
|
12856
|
+
* passes through minus `deviceId`; with `args` the template IS the source's
|
|
12857
|
+
* whole input. `deviceId` is always the source's own, never an argument.
|
|
12858
|
+
*/
|
|
12859
|
+
var CompositionForwardCommandSchema = object({
|
|
12702
12860
|
kind: literal("forward"),
|
|
12703
12861
|
source: CompositionSourceRefSchema,
|
|
12704
12862
|
cap: string().min(1),
|
|
12705
|
-
method: string().min(1)
|
|
12706
|
-
|
|
12863
|
+
method: string().min(1),
|
|
12864
|
+
args: record(string().min(1), CompositionForwardArgSchema).optional()
|
|
12865
|
+
});
|
|
12866
|
+
var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
|
|
12707
12867
|
/** An item key inside an item-array cap (`consumables.items[<key>]`). */
|
|
12708
12868
|
var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
12709
12869
|
/**
|
|
@@ -12713,7 +12873,13 @@ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
|
12713
12873
|
*/
|
|
12714
12874
|
var CompositionItemEntrySchema = object({
|
|
12715
12875
|
label: string().min(1).max(80),
|
|
12716
|
-
fields: record(string().min(1), CompositionFieldSourceSchema)
|
|
12876
|
+
fields: record(string().min(1), CompositionFieldSourceSchema),
|
|
12877
|
+
/**
|
|
12878
|
+
* What the cap's `reset` does for THIS item (consumables). Present = the item
|
|
12879
|
+
* is resettable and a `reset` call naming its key forwards here. The item key
|
|
12880
|
+
* selects the command, so it is never an argument: there is no pass-through.
|
|
12881
|
+
*/
|
|
12882
|
+
reset: CompositionForwardCommandSchema.optional()
|
|
12717
12883
|
});
|
|
12718
12884
|
var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
12719
12885
|
kind: literal("fields"),
|
|
@@ -12725,7 +12891,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
|
12725
12891
|
* owns the WHOLE array field: the composed value holds exactly these items.
|
|
12726
12892
|
*/
|
|
12727
12893
|
items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
|
|
12728
|
-
/**
|
|
12894
|
+
/** Method name → what serves it. Only a BUILT capability takes commands (D967). */
|
|
12729
12895
|
commands: record(string().min(1), CompositionCommandTargetSchema).optional()
|
|
12730
12896
|
}), object({
|
|
12731
12897
|
kind: literal("passthrough"),
|
|
@@ -16868,6 +17034,538 @@ object({
|
|
|
16868
17034
|
path: ["expr"]
|
|
16869
17035
|
});
|
|
16870
17036
|
});
|
|
17037
|
+
/**
|
|
17038
|
+
* Identity envelope for a device's upstream-system metadata.
|
|
17039
|
+
*
|
|
17040
|
+
* Two jobs:
|
|
17041
|
+
*
|
|
17042
|
+
* 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
|
|
17043
|
+
* uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
|
|
17044
|
+
* WS-Discovery endpoint UUID, …). Provider code keeps a
|
|
17045
|
+
* `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
|
|
17046
|
+
* without round-tripping through `stableId` / `id` mappings.
|
|
17047
|
+
*
|
|
17048
|
+
* 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
|
|
17049
|
+
* one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
|
|
17050
|
+
* for vendors where MAC is the natural key). For systems where the
|
|
17051
|
+
* live dispatch key can be renamed (HA `entity_id` can be edited),
|
|
17052
|
+
* providers detect renames by matching `uniqueId` and patch `id`
|
|
17053
|
+
* in-place so dispatch keeps working.
|
|
17054
|
+
*
|
|
17055
|
+
* **Display unit and precision are NOT stored here.** They are a read-time
|
|
17056
|
+
* preference (D944). A polymorphic reading stores its `quantity` here once,
|
|
17057
|
+
* at adopt, and a date/time value stores `dateTimeShape` the same way —
|
|
17058
|
+
* neither is repeated on each sample. The `device_class` tag is an
|
|
17059
|
+
* HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
|
|
17060
|
+
* to derive the CamStack `DeviceRole`; it is never stored after that point.
|
|
17061
|
+
*
|
|
17062
|
+
* The `raw` escape hatch carries provider-specific attributes that don't
|
|
17063
|
+
* fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
|
|
17064
|
+
* details, …). Providers control what lands here.
|
|
17065
|
+
*/
|
|
17066
|
+
var SourceInfoSchema = object({
|
|
17067
|
+
/** Live dispatch key — mutable when the source system allows rename. */
|
|
17068
|
+
id: string(),
|
|
17069
|
+
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
17070
|
+
system: string(),
|
|
17071
|
+
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
17072
|
+
* Used to detect rename when `id` changes. */
|
|
17073
|
+
uniqueId: string().optional(),
|
|
17074
|
+
/** Quantity of a polymorphic reading, written once at adopt (D944). */
|
|
17075
|
+
quantity: ReadingQuantitySchema.optional(),
|
|
17076
|
+
/** Date / time / datetime shape of a string reading, written once at adopt. */
|
|
17077
|
+
dateTimeShape: _enum([
|
|
17078
|
+
"date",
|
|
17079
|
+
"time",
|
|
17080
|
+
"datetime"
|
|
17081
|
+
]).optional(),
|
|
17082
|
+
/** Provider-specific extras that don't fit the structured slots.
|
|
17083
|
+
* Whitelist what goes here per provider — do NOT dump entire HA
|
|
17084
|
+
* attribute blobs (would explode the meta JSON column). */
|
|
17085
|
+
raw: record(string(), unknown()).optional()
|
|
17086
|
+
});
|
|
17087
|
+
/**
|
|
17088
|
+
* Build the synthetic SourceInfo every existing provider falls back to
|
|
17089
|
+
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
17090
|
+
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
17091
|
+
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
17092
|
+
* id. Real upstream identity replaces this once a provider migrates and
|
|
17093
|
+
* calls `updateSourceInfo()` with concrete values.
|
|
17094
|
+
*/
|
|
17095
|
+
function synthesizeSourceInfo(input) {
|
|
17096
|
+
return {
|
|
17097
|
+
id: input.stableId,
|
|
17098
|
+
system: input.addonId
|
|
17099
|
+
};
|
|
17100
|
+
}
|
|
17101
|
+
/**
|
|
17102
|
+
* Shallow-merge a partial patch over a prior SourceInfo. `undefined`
|
|
17103
|
+
* patch values are ignored (no field clobber); to clear a field
|
|
17104
|
+
* explicitly, providers omit it from the patch and rebuild from scratch
|
|
17105
|
+
* via `updateSourceInfo({})` cycles — uncommon enough that we don't
|
|
17106
|
+
* bother with a `null`-means-delete sentinel.
|
|
17107
|
+
*
|
|
17108
|
+
* The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
|
|
17109
|
+
* merges over existing `raw` keys instead of replacing the whole bag.
|
|
17110
|
+
*/
|
|
17111
|
+
function mergeSourceInfo(prev, patch) {
|
|
17112
|
+
const next = { ...prev };
|
|
17113
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
17114
|
+
if (v === void 0) continue;
|
|
17115
|
+
if (k === "raw" && typeof v === "object" && v !== null) {
|
|
17116
|
+
next.raw = {
|
|
17117
|
+
...prev.raw,
|
|
17118
|
+
...v
|
|
17119
|
+
};
|
|
17120
|
+
continue;
|
|
17121
|
+
}
|
|
17122
|
+
next[k] = v;
|
|
17123
|
+
}
|
|
17124
|
+
return next;
|
|
17125
|
+
}
|
|
17126
|
+
/**
|
|
17127
|
+
* Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
|
|
17128
|
+
* blob is a free-form `Record<string, unknown>` shared with hardware-
|
|
17129
|
+
* identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
|
|
17130
|
+
* lives under a single nested key to keep it composable with those.
|
|
17131
|
+
*/
|
|
17132
|
+
var SOURCE_INFO_METADATA_KEY = "sourceInfo";
|
|
17133
|
+
/**
|
|
17134
|
+
* Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
|
|
17135
|
+
* with Zod. Returns `null` when the blob is missing or the nested
|
|
17136
|
+
* `sourceInfo` key fails validation — callers fall back to the
|
|
17137
|
+
* synthetic default in that case (see `synthesizeSourceInfo`).
|
|
17138
|
+
*/
|
|
17139
|
+
function extractSourceInfoFromMetadata(metadata) {
|
|
17140
|
+
if (!metadata) return null;
|
|
17141
|
+
const raw = metadata[SOURCE_INFO_METADATA_KEY];
|
|
17142
|
+
if (!raw) return null;
|
|
17143
|
+
const parsed = SourceInfoSchema.safeParse(raw);
|
|
17144
|
+
if (!parsed.success) return null;
|
|
17145
|
+
return parsed.data;
|
|
17146
|
+
}
|
|
17147
|
+
/**
|
|
17148
|
+
* Frozen unit → spec table. `•` in the doc marks each dimension's canonical
|
|
17149
|
+
* unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
|
|
17150
|
+
* `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
|
|
17151
|
+
* decimals). K derives from `°C = K − 273.15`.
|
|
17152
|
+
*/
|
|
17153
|
+
var UNIT_TABLE = {
|
|
17154
|
+
"°C": {
|
|
17155
|
+
dimension: "temperature",
|
|
17156
|
+
factor: 1,
|
|
17157
|
+
offset: 0
|
|
17158
|
+
},
|
|
17159
|
+
"°F": {
|
|
17160
|
+
dimension: "temperature",
|
|
17161
|
+
factor: 5 / 9,
|
|
17162
|
+
offset: -160 / 9
|
|
17163
|
+
},
|
|
17164
|
+
K: {
|
|
17165
|
+
dimension: "temperature",
|
|
17166
|
+
factor: 1,
|
|
17167
|
+
offset: -273.15
|
|
17168
|
+
},
|
|
17169
|
+
hPa: {
|
|
17170
|
+
dimension: "pressure",
|
|
17171
|
+
factor: 1,
|
|
17172
|
+
offset: 0
|
|
17173
|
+
},
|
|
17174
|
+
kPa: {
|
|
17175
|
+
dimension: "pressure",
|
|
17176
|
+
factor: 10,
|
|
17177
|
+
offset: 0
|
|
17178
|
+
},
|
|
17179
|
+
Pa: {
|
|
17180
|
+
dimension: "pressure",
|
|
17181
|
+
factor: .01,
|
|
17182
|
+
offset: 0
|
|
17183
|
+
},
|
|
17184
|
+
mbar: {
|
|
17185
|
+
dimension: "pressure",
|
|
17186
|
+
factor: 1,
|
|
17187
|
+
offset: 0
|
|
17188
|
+
},
|
|
17189
|
+
bar: {
|
|
17190
|
+
dimension: "pressure",
|
|
17191
|
+
factor: 1e3,
|
|
17192
|
+
offset: 0
|
|
17193
|
+
},
|
|
17194
|
+
inHg: {
|
|
17195
|
+
dimension: "pressure",
|
|
17196
|
+
factor: 33.8639,
|
|
17197
|
+
offset: 0
|
|
17198
|
+
},
|
|
17199
|
+
mmHg: {
|
|
17200
|
+
dimension: "pressure",
|
|
17201
|
+
factor: 1.33322,
|
|
17202
|
+
offset: 0
|
|
17203
|
+
},
|
|
17204
|
+
psi: {
|
|
17205
|
+
dimension: "pressure",
|
|
17206
|
+
factor: 68.9476,
|
|
17207
|
+
offset: 0
|
|
17208
|
+
},
|
|
17209
|
+
"m/s": {
|
|
17210
|
+
dimension: "speed",
|
|
17211
|
+
factor: 1,
|
|
17212
|
+
offset: 0
|
|
17213
|
+
},
|
|
17214
|
+
"km/h": {
|
|
17215
|
+
dimension: "speed",
|
|
17216
|
+
factor: 1 / 3.6,
|
|
17217
|
+
offset: 0
|
|
17218
|
+
},
|
|
17219
|
+
mph: {
|
|
17220
|
+
dimension: "speed",
|
|
17221
|
+
factor: .44704,
|
|
17222
|
+
offset: 0
|
|
17223
|
+
},
|
|
17224
|
+
kn: {
|
|
17225
|
+
dimension: "speed",
|
|
17226
|
+
factor: .514444,
|
|
17227
|
+
offset: 0
|
|
17228
|
+
},
|
|
17229
|
+
"mm/h": {
|
|
17230
|
+
dimension: "precipitation-rate",
|
|
17231
|
+
factor: 1,
|
|
17232
|
+
offset: 0
|
|
17233
|
+
},
|
|
17234
|
+
"in/h": {
|
|
17235
|
+
dimension: "precipitation-rate",
|
|
17236
|
+
factor: 25.4,
|
|
17237
|
+
offset: 0
|
|
17238
|
+
},
|
|
17239
|
+
m: {
|
|
17240
|
+
dimension: "length",
|
|
17241
|
+
factor: 1,
|
|
17242
|
+
offset: 0
|
|
17243
|
+
},
|
|
17244
|
+
mm: {
|
|
17245
|
+
dimension: "length",
|
|
17246
|
+
factor: .001,
|
|
17247
|
+
offset: 0
|
|
17248
|
+
},
|
|
17249
|
+
cm: {
|
|
17250
|
+
dimension: "length",
|
|
17251
|
+
factor: .01,
|
|
17252
|
+
offset: 0
|
|
17253
|
+
},
|
|
17254
|
+
km: {
|
|
17255
|
+
dimension: "length",
|
|
17256
|
+
factor: 1e3,
|
|
17257
|
+
offset: 0
|
|
17258
|
+
},
|
|
17259
|
+
in: {
|
|
17260
|
+
dimension: "length",
|
|
17261
|
+
factor: .0254,
|
|
17262
|
+
offset: 0
|
|
17263
|
+
},
|
|
17264
|
+
ft: {
|
|
17265
|
+
dimension: "length",
|
|
17266
|
+
factor: .3048,
|
|
17267
|
+
offset: 0
|
|
17268
|
+
},
|
|
17269
|
+
mi: {
|
|
17270
|
+
dimension: "length",
|
|
17271
|
+
factor: 1609.344,
|
|
17272
|
+
offset: 0
|
|
17273
|
+
},
|
|
17274
|
+
"m²": {
|
|
17275
|
+
dimension: "area",
|
|
17276
|
+
factor: 1,
|
|
17277
|
+
offset: 0
|
|
17278
|
+
},
|
|
17279
|
+
lx: {
|
|
17280
|
+
dimension: "illuminance",
|
|
17281
|
+
factor: 1,
|
|
17282
|
+
offset: 0
|
|
17283
|
+
},
|
|
17284
|
+
"W/m²": {
|
|
17285
|
+
dimension: "irradiance",
|
|
17286
|
+
factor: 1,
|
|
17287
|
+
offset: 0
|
|
17288
|
+
},
|
|
17289
|
+
W: {
|
|
17290
|
+
dimension: "power",
|
|
17291
|
+
factor: 1,
|
|
17292
|
+
offset: 0
|
|
17293
|
+
},
|
|
17294
|
+
kW: {
|
|
17295
|
+
dimension: "power",
|
|
17296
|
+
factor: 1e3,
|
|
17297
|
+
offset: 0
|
|
17298
|
+
},
|
|
17299
|
+
VA: {
|
|
17300
|
+
dimension: "apparent-power",
|
|
17301
|
+
factor: 1,
|
|
17302
|
+
offset: 0
|
|
17303
|
+
},
|
|
17304
|
+
Wh: {
|
|
17305
|
+
dimension: "energy",
|
|
17306
|
+
factor: 1,
|
|
17307
|
+
offset: 0
|
|
17308
|
+
},
|
|
17309
|
+
kWh: {
|
|
17310
|
+
dimension: "energy",
|
|
17311
|
+
factor: 1e3,
|
|
17312
|
+
offset: 0
|
|
17313
|
+
},
|
|
17314
|
+
MWh: {
|
|
17315
|
+
dimension: "energy",
|
|
17316
|
+
factor: 1e6,
|
|
17317
|
+
offset: 0
|
|
17318
|
+
},
|
|
17319
|
+
V: {
|
|
17320
|
+
dimension: "voltage",
|
|
17321
|
+
factor: 1,
|
|
17322
|
+
offset: 0
|
|
17323
|
+
},
|
|
17324
|
+
mV: {
|
|
17325
|
+
dimension: "voltage",
|
|
17326
|
+
factor: .001,
|
|
17327
|
+
offset: 0
|
|
17328
|
+
},
|
|
17329
|
+
A: {
|
|
17330
|
+
dimension: "current",
|
|
17331
|
+
factor: 1,
|
|
17332
|
+
offset: 0
|
|
17333
|
+
},
|
|
17334
|
+
mA: {
|
|
17335
|
+
dimension: "current",
|
|
17336
|
+
factor: .001,
|
|
17337
|
+
offset: 0
|
|
17338
|
+
},
|
|
17339
|
+
ppm: {
|
|
17340
|
+
dimension: "concentration-volume",
|
|
17341
|
+
factor: 1,
|
|
17342
|
+
offset: 0
|
|
17343
|
+
},
|
|
17344
|
+
ppb: {
|
|
17345
|
+
dimension: "concentration-volume",
|
|
17346
|
+
factor: .001,
|
|
17347
|
+
offset: 0
|
|
17348
|
+
},
|
|
17349
|
+
"µg/m³": {
|
|
17350
|
+
dimension: "concentration-mass",
|
|
17351
|
+
factor: 1,
|
|
17352
|
+
offset: 0
|
|
17353
|
+
},
|
|
17354
|
+
"mg/m³": {
|
|
17355
|
+
dimension: "concentration-mass",
|
|
17356
|
+
factor: 1e3,
|
|
17357
|
+
offset: 0
|
|
17358
|
+
},
|
|
17359
|
+
kg: {
|
|
17360
|
+
dimension: "mass",
|
|
17361
|
+
factor: 1,
|
|
17362
|
+
offset: 0
|
|
17363
|
+
},
|
|
17364
|
+
g: {
|
|
17365
|
+
dimension: "mass",
|
|
17366
|
+
factor: .001,
|
|
17367
|
+
offset: 0
|
|
17368
|
+
},
|
|
17369
|
+
lb: {
|
|
17370
|
+
dimension: "mass",
|
|
17371
|
+
factor: .453592,
|
|
17372
|
+
offset: 0
|
|
17373
|
+
},
|
|
17374
|
+
oz: {
|
|
17375
|
+
dimension: "mass",
|
|
17376
|
+
factor: .0283495,
|
|
17377
|
+
offset: 0
|
|
17378
|
+
},
|
|
17379
|
+
"%": {
|
|
17380
|
+
dimension: "percentage",
|
|
17381
|
+
factor: 1,
|
|
17382
|
+
offset: 0
|
|
17383
|
+
},
|
|
17384
|
+
L: {
|
|
17385
|
+
dimension: "volume",
|
|
17386
|
+
factor: 1,
|
|
17387
|
+
offset: 0
|
|
17388
|
+
},
|
|
17389
|
+
mL: {
|
|
17390
|
+
dimension: "volume",
|
|
17391
|
+
factor: .001,
|
|
17392
|
+
offset: 0
|
|
17393
|
+
},
|
|
17394
|
+
gal: {
|
|
17395
|
+
dimension: "volume",
|
|
17396
|
+
factor: 3.785411784,
|
|
17397
|
+
offset: 0
|
|
17398
|
+
},
|
|
17399
|
+
s: {
|
|
17400
|
+
dimension: "duration",
|
|
17401
|
+
factor: 1,
|
|
17402
|
+
offset: 0
|
|
17403
|
+
},
|
|
17404
|
+
ms: {
|
|
17405
|
+
dimension: "duration",
|
|
17406
|
+
factor: .001,
|
|
17407
|
+
offset: 0
|
|
17408
|
+
},
|
|
17409
|
+
min: {
|
|
17410
|
+
dimension: "duration",
|
|
17411
|
+
factor: 60,
|
|
17412
|
+
offset: 0
|
|
17413
|
+
},
|
|
17414
|
+
h: {
|
|
17415
|
+
dimension: "duration",
|
|
17416
|
+
factor: 3600,
|
|
17417
|
+
offset: 0
|
|
17418
|
+
},
|
|
17419
|
+
d: {
|
|
17420
|
+
dimension: "duration",
|
|
17421
|
+
factor: 86400,
|
|
17422
|
+
offset: 0
|
|
17423
|
+
},
|
|
17424
|
+
B: {
|
|
17425
|
+
dimension: "information",
|
|
17426
|
+
factor: 1,
|
|
17427
|
+
offset: 0
|
|
17428
|
+
},
|
|
17429
|
+
KiB: {
|
|
17430
|
+
dimension: "information",
|
|
17431
|
+
factor: 1024,
|
|
17432
|
+
offset: 0
|
|
17433
|
+
},
|
|
17434
|
+
MiB: {
|
|
17435
|
+
dimension: "information",
|
|
17436
|
+
factor: 1024 ** 2,
|
|
17437
|
+
offset: 0
|
|
17438
|
+
},
|
|
17439
|
+
GiB: {
|
|
17440
|
+
dimension: "information",
|
|
17441
|
+
factor: 1024 ** 3,
|
|
17442
|
+
offset: 0
|
|
17443
|
+
},
|
|
17444
|
+
TiB: {
|
|
17445
|
+
dimension: "information",
|
|
17446
|
+
factor: 1024 ** 4,
|
|
17447
|
+
offset: 0
|
|
17448
|
+
},
|
|
17449
|
+
kB: {
|
|
17450
|
+
dimension: "information",
|
|
17451
|
+
factor: 1e3,
|
|
17452
|
+
offset: 0
|
|
17453
|
+
},
|
|
17454
|
+
MB: {
|
|
17455
|
+
dimension: "information",
|
|
17456
|
+
factor: 1e6,
|
|
17457
|
+
offset: 0
|
|
17458
|
+
},
|
|
17459
|
+
GB: {
|
|
17460
|
+
dimension: "information",
|
|
17461
|
+
factor: 1e9,
|
|
17462
|
+
offset: 0
|
|
17463
|
+
},
|
|
17464
|
+
"°": {
|
|
17465
|
+
dimension: "angle",
|
|
17466
|
+
factor: 1,
|
|
17467
|
+
offset: 0
|
|
17468
|
+
},
|
|
17469
|
+
dBFS: {
|
|
17470
|
+
dimension: "sound-level",
|
|
17471
|
+
factor: 1,
|
|
17472
|
+
offset: 0
|
|
17473
|
+
},
|
|
17474
|
+
dBm: {
|
|
17475
|
+
dimension: "signal-level",
|
|
17476
|
+
factor: 1,
|
|
17477
|
+
offset: 0
|
|
17478
|
+
},
|
|
17479
|
+
mired: {
|
|
17480
|
+
dimension: "colour-temperature",
|
|
17481
|
+
factor: 1,
|
|
17482
|
+
offset: 0
|
|
17483
|
+
},
|
|
17484
|
+
kbps: {
|
|
17485
|
+
dimension: "bitrate",
|
|
17486
|
+
factor: 1,
|
|
17487
|
+
offset: 0
|
|
17488
|
+
},
|
|
17489
|
+
Mbps: {
|
|
17490
|
+
dimension: "bitrate",
|
|
17491
|
+
factor: 1e3,
|
|
17492
|
+
offset: 0
|
|
17493
|
+
},
|
|
17494
|
+
fps: {
|
|
17495
|
+
dimension: "rate",
|
|
17496
|
+
factor: 1,
|
|
17497
|
+
offset: 0
|
|
17498
|
+
}
|
|
17499
|
+
};
|
|
17500
|
+
/** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
|
|
17501
|
+
function specFor(unit) {
|
|
17502
|
+
return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
|
|
17503
|
+
}
|
|
17504
|
+
/** Value in `spec`'s unit → its dimension's canonical unit. */
|
|
17505
|
+
function toCanonical(value, spec) {
|
|
17506
|
+
return value * spec.factor + spec.offset;
|
|
17507
|
+
}
|
|
17508
|
+
/** Canonical-unit value → the unit described by `spec`. */
|
|
17509
|
+
function fromCanonical(canonical, spec) {
|
|
17510
|
+
return (canonical - spec.offset) / spec.factor;
|
|
17511
|
+
}
|
|
17512
|
+
/**
|
|
17513
|
+
* Graceful variant for the render path: returns the converted value, or
|
|
17514
|
+
* `undefined` when the conversion is refused (unknown unit / cross-dimension).
|
|
17515
|
+
* Never throws — a render caller falls back to the raw value + source unit.
|
|
17516
|
+
*/
|
|
17517
|
+
function tryConvertUnit(value, from, to) {
|
|
17518
|
+
if (from === to) return value;
|
|
17519
|
+
const fromSpec = specFor(from);
|
|
17520
|
+
if (!fromSpec) return void 0;
|
|
17521
|
+
const toSpec = specFor(to);
|
|
17522
|
+
if (!toSpec) return void 0;
|
|
17523
|
+
if (fromSpec.dimension !== toSpec.dimension) return void 0;
|
|
17524
|
+
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
17525
|
+
}
|
|
17526
|
+
/**
|
|
17527
|
+
* A number a person types for a physical quantity — the input side of the
|
|
17528
|
+
* value + unit contract (D972).
|
|
17529
|
+
*
|
|
17530
|
+
* Readings are stored in the base unit of their quantity (D944); a cap method
|
|
17531
|
+
* param is stored in the unit its input schema DECLARES (`paramMeta`). The
|
|
17532
|
+
* wire pair {@link QuantityValue} says "this number is expressed in this
|
|
17533
|
+
* unit"; {@link convertQuantityInput} brings it to the stored unit and refuses,
|
|
17534
|
+
* with a typed {@link QuantityUnitError}, a unit that cannot be that quantity.
|
|
17535
|
+
*/
|
|
17536
|
+
/** A unit that cannot be applied to the number it was given with. */
|
|
17537
|
+
var QuantityUnitError = class extends Error {
|
|
17538
|
+
code;
|
|
17539
|
+
constructor(code, message) {
|
|
17540
|
+
super(`[${code}] ${message}`);
|
|
17541
|
+
this.name = "QuantityUnitError";
|
|
17542
|
+
this.code = code;
|
|
17543
|
+
}
|
|
17544
|
+
};
|
|
17545
|
+
/** The wire pair: `value` is expressed in `unit`. */
|
|
17546
|
+
var QuantityValueSchema = object({
|
|
17547
|
+
value: number(),
|
|
17548
|
+
unit: string().min(1)
|
|
17549
|
+
});
|
|
17550
|
+
/**
|
|
17551
|
+
* Zod `.meta()` bag for a cap METHOD param that is a physical quantity — the
|
|
17552
|
+
* input-side twin of `readingMeta`. The value is carried in `unit` (a gram
|
|
17553
|
+
* portion stays in grams); the unit must belong to the quantity, checked once
|
|
17554
|
+
* when the cap is declared.
|
|
17555
|
+
*/
|
|
17556
|
+
function paramMeta(declared) {
|
|
17557
|
+
const base = baseUnitOf(declared.quantity);
|
|
17558
|
+
if (base === void 0) throw new QuantityUnitError("unknown-unit", `quantity "${declared.quantity}" has no unit`);
|
|
17559
|
+
if (tryConvertUnit(1, declared.unit, base) === void 0) throw new QuantityUnitError("cross-dimension", `"${declared.unit}" is not a unit of ${declared.quantity}`);
|
|
17560
|
+
return {
|
|
17561
|
+
quantity: declared.quantity,
|
|
17562
|
+
unit: declared.unit
|
|
17563
|
+
};
|
|
17564
|
+
}
|
|
17565
|
+
object({
|
|
17566
|
+
quantity: ReadingQuantitySchema,
|
|
17567
|
+
unit: string().min(1)
|
|
17568
|
+
});
|
|
16871
17569
|
/** How a leaf compares a device field to a value. Derived from the field's
|
|
16872
17570
|
* `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
|
|
16873
17571
|
var AutomationConditionOperatorSchema = _enum([
|
|
@@ -16890,8 +17588,10 @@ var AutomationConditionLeafSchema = object({
|
|
|
16890
17588
|
string(),
|
|
16891
17589
|
number(),
|
|
16892
17590
|
boolean(),
|
|
16893
|
-
array(union([string(), number()]))
|
|
16894
|
-
|
|
17591
|
+
array(union([string(), number()])),
|
|
17592
|
+
QuantityValueSchema
|
|
17593
|
+
]),
|
|
17594
|
+
unit: string().min(1).optional()
|
|
16895
17595
|
});
|
|
16896
17596
|
var AutomationConditionStationaryPresentSchema = object({
|
|
16897
17597
|
kind: literal("stationary-present"),
|
|
@@ -17044,7 +17744,13 @@ var AutomationCapStepSchema = object({
|
|
|
17044
17744
|
/** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
|
|
17045
17745
|
* SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
|
|
17046
17746
|
* the expression leaf. */
|
|
17047
|
-
args: record(string(), unknown()).optional()
|
|
17747
|
+
args: record(string(), unknown()).optional(),
|
|
17748
|
+
/**
|
|
17749
|
+
* The unit the person picked per arg (display only). `args` always hold the
|
|
17750
|
+
* value in the unit the method's input DECLARES (`paramMeta`); on a write an
|
|
17751
|
+
* arg may be a {@link QuantityValue}, which the save boundary converts.
|
|
17752
|
+
*/
|
|
17753
|
+
argUnits: record(string(), string().min(1)).optional()
|
|
17048
17754
|
});
|
|
17049
17755
|
/**
|
|
17050
17756
|
* The steps an internal Action may contain: cap and wait, and nothing that can
|
|
@@ -17446,6 +18152,437 @@ object({
|
|
|
17446
18152
|
lastRunAt: number().nullable(),
|
|
17447
18153
|
lastError: string().nullable()
|
|
17448
18154
|
});
|
|
18155
|
+
/** A bare number accepted as a base unit — the save succeeded, say so. */
|
|
18156
|
+
var UnitWarningSchema = object({
|
|
18157
|
+
/** Where in the saved record, e.g. `conditions.children.0` or `steps.2.args.grams`. */
|
|
18158
|
+
path: string(),
|
|
18159
|
+
deviceId: number().int().optional(),
|
|
18160
|
+
field: string(),
|
|
18161
|
+
value: number(),
|
|
18162
|
+
assumedUnit: string(),
|
|
18163
|
+
message: string()
|
|
18164
|
+
});
|
|
18165
|
+
/**
|
|
18166
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
18167
|
+
*
|
|
18168
|
+
* Apprise-derived model (see
|
|
18169
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
18170
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
18171
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
18172
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
18173
|
+
* message to what the kind supports — callers never special-case a service.
|
|
18174
|
+
*
|
|
18175
|
+
* DESIGN DECISIONS (locked):
|
|
18176
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
18177
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
18178
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
18179
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
18180
|
+
* alternative would fork the UI per addon and cannot host the
|
|
18181
|
+
* discovery→adopt flow.
|
|
18182
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
18183
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
18184
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
18185
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
18186
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
18187
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
18188
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
18189
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
18190
|
+
* base64 fallback needed.
|
|
18191
|
+
*
|
|
18192
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
18193
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
18194
|
+
* admin "Integrations" page.
|
|
18195
|
+
*/
|
|
18196
|
+
/**
|
|
18197
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
18198
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
18199
|
+
*/
|
|
18200
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
18201
|
+
"image",
|
|
18202
|
+
"video",
|
|
18203
|
+
"gif",
|
|
18204
|
+
"audio",
|
|
18205
|
+
"icon"
|
|
18206
|
+
]);
|
|
18207
|
+
/**
|
|
18208
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
18209
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
18210
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
18211
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
18212
|
+
*/
|
|
18213
|
+
var AttachmentSchema = object({
|
|
18214
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
18215
|
+
url: string().optional(),
|
|
18216
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
18217
|
+
mime: string().optional(),
|
|
18218
|
+
name: string().optional()
|
|
18219
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
18220
|
+
var NotificationFormatSchema = _enum([
|
|
18221
|
+
"text",
|
|
18222
|
+
"markdown",
|
|
18223
|
+
"html"
|
|
18224
|
+
]);
|
|
18225
|
+
/**
|
|
18226
|
+
* The CLOSED icon vocabulary an action button may use.
|
|
18227
|
+
*
|
|
18228
|
+
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
18229
|
+
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
18230
|
+
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
18231
|
+
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
18232
|
+
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
18233
|
+
*
|
|
18234
|
+
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
18235
|
+
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
18236
|
+
* differently.
|
|
18237
|
+
*
|
|
18238
|
+
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
18239
|
+
*
|
|
18240
|
+
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
18241
|
+
* the whole set onto its own renderer's vocabulary through a
|
|
18242
|
+
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
18243
|
+
* fallback, so adding a member here fails every adapter's build until someone
|
|
18244
|
+
* decides its glyph, which is the only place that decision can be made
|
|
18245
|
+
* honestly.
|
|
18246
|
+
*
|
|
18247
|
+
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
18248
|
+
* `disarm` straight through; iOS feeds that string to
|
|
18249
|
+
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
18250
|
+
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
18251
|
+
* mapping, and "the field is documented" is not "the value renders".
|
|
18252
|
+
*
|
|
18253
|
+
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
18254
|
+
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
18255
|
+
* so an addon that emits a token the running hub does not know does not lose an
|
|
18256
|
+
* icon — its whole `send` fails Zod validation and the notification never
|
|
18257
|
+
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
18258
|
+
*/
|
|
18259
|
+
var NotificationActionIconSchema = _enum([
|
|
18260
|
+
"acknowledge",
|
|
18261
|
+
"dismiss",
|
|
18262
|
+
"silence",
|
|
18263
|
+
"snooze",
|
|
18264
|
+
"view",
|
|
18265
|
+
"play",
|
|
18266
|
+
"open",
|
|
18267
|
+
"close",
|
|
18268
|
+
"lock",
|
|
18269
|
+
"unlock",
|
|
18270
|
+
"arm",
|
|
18271
|
+
"arm-home",
|
|
18272
|
+
"arm-away",
|
|
18273
|
+
"arm-night",
|
|
18274
|
+
"disarm",
|
|
18275
|
+
"light",
|
|
18276
|
+
"alert",
|
|
18277
|
+
"camera"
|
|
18278
|
+
]);
|
|
18279
|
+
/** A single tap-through action button. */
|
|
18280
|
+
var NotificationActionSchema = object({
|
|
18281
|
+
id: string(),
|
|
18282
|
+
label: string(),
|
|
18283
|
+
url: string().optional(),
|
|
18284
|
+
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
18285
|
+
icon: NotificationActionIconSchema.optional(),
|
|
18286
|
+
/**
|
|
18287
|
+
* Renders in a warning style where the notifier supports it.
|
|
18288
|
+
*
|
|
18289
|
+
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
18290
|
+
* else — see `notification-center/action-token.ts` for what that does and
|
|
18291
|
+
* does not buy.
|
|
18292
|
+
*/
|
|
18293
|
+
destructive: boolean().optional(),
|
|
18294
|
+
/**
|
|
18295
|
+
* How the tap should REACH the url.
|
|
18296
|
+
*
|
|
18297
|
+
* `navigate` (absent, and every button authored before this field) opens it:
|
|
18298
|
+
* the phone leaves the notification and shows whatever the callback returns.
|
|
18299
|
+
* That is right for a button whose answer the operator wants to read.
|
|
18300
|
+
*
|
|
18301
|
+
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
18302
|
+
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
18303
|
+
* an answer to the notification, and being thrown into a browser tab to
|
|
18304
|
+
* confirm it costs more attention than the notification did. A backend that
|
|
18305
|
+
* cannot do a background call renders it as an ordinary link (the adapters
|
|
18306
|
+
* fall back rather than dropping the button), so this is a preference, never
|
|
18307
|
+
* a requirement.
|
|
18308
|
+
*/
|
|
18309
|
+
mode: _enum(["navigate", "background"]).optional()
|
|
18310
|
+
});
|
|
18311
|
+
/** A delivery parameter value: what a notifier declared it takes. */
|
|
18312
|
+
var DeliveryParamValueSchema = union([
|
|
18313
|
+
string(),
|
|
18314
|
+
number(),
|
|
18315
|
+
boolean()
|
|
18316
|
+
]);
|
|
18317
|
+
/**
|
|
18318
|
+
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
18319
|
+
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
18320
|
+
* never handed one. The admin UI derives its control from this descriptor —
|
|
18321
|
+
* no editor knows a service by name.
|
|
18322
|
+
*
|
|
18323
|
+
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
18324
|
+
* run on the addon cap path).
|
|
18325
|
+
*/
|
|
18326
|
+
var DeliveryParamSchema = object({
|
|
18327
|
+
key: string().min(1),
|
|
18328
|
+
label: string(),
|
|
18329
|
+
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
18330
|
+
labelKey: string().optional(),
|
|
18331
|
+
type: _enum([
|
|
18332
|
+
"enum",
|
|
18333
|
+
"number",
|
|
18334
|
+
"boolean",
|
|
18335
|
+
"string"
|
|
18336
|
+
]),
|
|
18337
|
+
/** `enum` only: the closed set of accepted values. */
|
|
18338
|
+
options: array(object({
|
|
18339
|
+
value: string(),
|
|
18340
|
+
label: string()
|
|
18341
|
+
})).optional(),
|
|
18342
|
+
/** `number` only. */
|
|
18343
|
+
min: number().optional(),
|
|
18344
|
+
max: number().optional(),
|
|
18345
|
+
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
18346
|
+
default: DeliveryParamValueSchema.optional()
|
|
18347
|
+
});
|
|
18348
|
+
/**
|
|
18349
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
18350
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
18351
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
18352
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
18353
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
18354
|
+
* `priority` for that one target.
|
|
18355
|
+
*/
|
|
18356
|
+
var NotificationSchema = object({
|
|
18357
|
+
body: string(),
|
|
18358
|
+
title: string().optional(),
|
|
18359
|
+
format: NotificationFormatSchema.default("text"),
|
|
18360
|
+
priority: number().int().min(1).max(5).default(3),
|
|
18361
|
+
level: string().optional(),
|
|
18362
|
+
attachments: array(AttachmentSchema).optional(),
|
|
18363
|
+
clickUrl: string().optional(),
|
|
18364
|
+
actions: array(NotificationActionSchema).optional(),
|
|
18365
|
+
sound: string().optional(),
|
|
18366
|
+
ttl: number().optional(),
|
|
18367
|
+
tag: string().optional(),
|
|
18368
|
+
deviceId: number().optional(),
|
|
18369
|
+
eventId: string().optional(),
|
|
18370
|
+
metadata: record(string(), unknown()).optional(),
|
|
18371
|
+
/**
|
|
18372
|
+
* Values for the parameters the target's kind DECLARES in
|
|
18373
|
+
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
18374
|
+
* declaration by the sender; a notifier reads only the keys it declared.
|
|
18375
|
+
*/
|
|
18376
|
+
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
18377
|
+
});
|
|
18378
|
+
/** One declared native severity/priority level for a kind. */
|
|
18379
|
+
var TargetKindLevelSchema = object({
|
|
18380
|
+
id: string(),
|
|
18381
|
+
label: string(),
|
|
18382
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
18383
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
18384
|
+
flags: object({
|
|
18385
|
+
critical: boolean().optional(),
|
|
18386
|
+
silent: boolean().optional(),
|
|
18387
|
+
noPush: boolean().optional()
|
|
18388
|
+
}).optional(),
|
|
18389
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
18390
|
+
requires: array(string()).optional(),
|
|
18391
|
+
description: string().optional()
|
|
18392
|
+
});
|
|
18393
|
+
/** The full capability block consulted before dispatch. */
|
|
18394
|
+
var TargetKindCapsSchema = object({
|
|
18395
|
+
attachments: object({
|
|
18396
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
18397
|
+
mode: _enum([
|
|
18398
|
+
"url",
|
|
18399
|
+
"bytes",
|
|
18400
|
+
"both"
|
|
18401
|
+
]),
|
|
18402
|
+
max: number().int().nonnegative(),
|
|
18403
|
+
maxBytes: number().int().positive().optional()
|
|
18404
|
+
}),
|
|
18405
|
+
/** Max action buttons (0 = none). */
|
|
18406
|
+
actions: number().int().nonnegative(),
|
|
18407
|
+
/**
|
|
18408
|
+
* Whether this kind renders a per-action ICON.
|
|
18409
|
+
*
|
|
18410
|
+
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
18411
|
+
* run on the addon cap path — three production failures in one day taught
|
|
18412
|
+
* this repo that once. Absent is read as false by the degrade engine, which
|
|
18413
|
+
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
18414
|
+
* assumed and dropped costs the operator's trust in the field.
|
|
18415
|
+
*/
|
|
18416
|
+
actionIcons: boolean().optional(),
|
|
18417
|
+
levels: array(TargetKindLevelSchema),
|
|
18418
|
+
format: array(NotificationFormatSchema),
|
|
18419
|
+
clickUrl: boolean(),
|
|
18420
|
+
sound: boolean(),
|
|
18421
|
+
ttl: boolean(),
|
|
18422
|
+
bodyMaxLen: number().int().positive()
|
|
18423
|
+
});
|
|
18424
|
+
/**
|
|
18425
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
18426
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
18427
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
18428
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
18429
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
18430
|
+
*/
|
|
18431
|
+
var ConfigSchemaPassthrough$3 = unknown();
|
|
18432
|
+
var TargetKindSchema = object({
|
|
18433
|
+
kind: string(),
|
|
18434
|
+
label: string(),
|
|
18435
|
+
icon: string(),
|
|
18436
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
18437
|
+
addonId: string(),
|
|
18438
|
+
/**
|
|
18439
|
+
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
18440
|
+
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
18441
|
+
* when the addon bundles no icon for that kind — the client then falls back
|
|
18442
|
+
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
18443
|
+
*
|
|
18444
|
+
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
18445
|
+
* client, and a native client joins it onto its own hub base.
|
|
18446
|
+
*
|
|
18447
|
+
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
18448
|
+
* field that survived only because the runtime cap-router forwards provider
|
|
18449
|
+
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
18450
|
+
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
18451
|
+
* broken silently the moment output validation was tightened anywhere.
|
|
18452
|
+
*/
|
|
18453
|
+
iconUrl: string().optional(),
|
|
18454
|
+
/**
|
|
18455
|
+
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
18456
|
+
*
|
|
18457
|
+
* The server knows this and therefore says it, because the client cannot
|
|
18458
|
+
* safely guess: a React-Native client renders SVG and raster through two
|
|
18459
|
+
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
18460
|
+
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
18461
|
+
* placeholder glyph for every vector icon while the web build looked fine.
|
|
18462
|
+
*
|
|
18463
|
+
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
18464
|
+
* not been updated — a client that cannot determine the type should prefer
|
|
18465
|
+
* its raster path, which is the safe default for an unknown image.
|
|
18466
|
+
*/
|
|
18467
|
+
iconMediaType: string().optional(),
|
|
18468
|
+
configSchema: ConfigSchemaPassthrough$3,
|
|
18469
|
+
/**
|
|
18470
|
+
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
18471
|
+
* base URL, instance, chat, recipient). A save that changes any of them
|
|
18472
|
+
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
18473
|
+
* editor uses this to ask for the secret on exactly those edits instead of
|
|
18474
|
+
* on every edit.
|
|
18475
|
+
*
|
|
18476
|
+
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
18477
|
+
* kinds) and from a provider older than this field — an editor that finds
|
|
18478
|
+
* it absent must assume ANY edit may need the secret.
|
|
18479
|
+
*/
|
|
18480
|
+
destinationKeys: array(string()).optional(),
|
|
18481
|
+
supportsDiscovery: boolean(),
|
|
18482
|
+
/**
|
|
18483
|
+
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
18484
|
+
* Absent = none; the rule editor then offers nothing.
|
|
18485
|
+
*/
|
|
18486
|
+
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
18487
|
+
caps: TargetKindCapsSchema
|
|
18488
|
+
});
|
|
18489
|
+
/**
|
|
18490
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
18491
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
18492
|
+
* round-trip a stored secret to the UI.
|
|
18493
|
+
*/
|
|
18494
|
+
var TargetSchema = object({
|
|
18495
|
+
id: string(),
|
|
18496
|
+
name: string(),
|
|
18497
|
+
kind: string(),
|
|
18498
|
+
addonId: string(),
|
|
18499
|
+
enabled: boolean(),
|
|
18500
|
+
config: record(string(), unknown())
|
|
18501
|
+
});
|
|
18502
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
18503
|
+
var DiscoveredTargetSchema = object({
|
|
18504
|
+
kind: string(),
|
|
18505
|
+
suggestedName: string(),
|
|
18506
|
+
config: record(string(), unknown())
|
|
18507
|
+
});
|
|
18508
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
18509
|
+
var RenderedAsSchema = object({
|
|
18510
|
+
level: string(),
|
|
18511
|
+
format: NotificationFormatSchema,
|
|
18512
|
+
attachmentsSent: number().int().nonnegative(),
|
|
18513
|
+
actionsSent: number().int().nonnegative(),
|
|
18514
|
+
truncated: boolean(),
|
|
18515
|
+
dropped: array(string())
|
|
18516
|
+
});
|
|
18517
|
+
var SendResultSchema = object({
|
|
18518
|
+
success: boolean(),
|
|
18519
|
+
error: string().optional(),
|
|
18520
|
+
renderedAs: RenderedAsSchema.optional()
|
|
18521
|
+
});
|
|
18522
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
18523
|
+
var TestResultSchema = SendResultSchema;
|
|
18524
|
+
/**
|
|
18525
|
+
* Pre-save credential probe against a partial (unsaved) target config.
|
|
18526
|
+
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
18527
|
+
*/
|
|
18528
|
+
var ProbeTargetConfigResultSchema = object({
|
|
18529
|
+
ok: boolean(),
|
|
18530
|
+
resolvedUrl: string(),
|
|
18531
|
+
httpStatus: number().int().optional(),
|
|
18532
|
+
stateInstance: string().optional(),
|
|
18533
|
+
error: string().optional()
|
|
18534
|
+
});
|
|
18535
|
+
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
|
|
18536
|
+
kind: string(),
|
|
18537
|
+
config: record(string(), unknown()).optional(),
|
|
18538
|
+
/**
|
|
18539
|
+
* When editing / duplicating, the blank password field never round-trips
|
|
18540
|
+
* the stored token. Pass the source target id so the provider can restore
|
|
18541
|
+
* secrets server-side before dialling (same merge as upsert).
|
|
18542
|
+
*/
|
|
18543
|
+
targetId: string().optional()
|
|
18544
|
+
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
18545
|
+
kind: string(),
|
|
18546
|
+
config: record(string(), unknown()),
|
|
18547
|
+
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
18548
|
+
targetId: string().optional()
|
|
18549
|
+
}), ProbeTargetConfigResultSchema, {
|
|
18550
|
+
kind: "mutation",
|
|
18551
|
+
auth: "admin"
|
|
18552
|
+
}), method(object({
|
|
18553
|
+
targetId: string(),
|
|
18554
|
+
notification: NotificationSchema
|
|
18555
|
+
}), SendResultSchema, {
|
|
18556
|
+
kind: "mutation",
|
|
18557
|
+
auth: "admin"
|
|
18558
|
+
}), method(object({
|
|
18559
|
+
targetId: string(),
|
|
18560
|
+
sample: NotificationSchema.optional()
|
|
18561
|
+
}), TestResultSchema, {
|
|
18562
|
+
kind: "mutation",
|
|
18563
|
+
auth: "admin"
|
|
18564
|
+
}), method(object({
|
|
18565
|
+
target: TargetSchema,
|
|
18566
|
+
/**
|
|
18567
|
+
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
18568
|
+
* existing row. The UI never holds the stored token; blank / redacted
|
|
18569
|
+
* password fields are restored from this source (D634 still applies if
|
|
18570
|
+
* destinations diverge without a re-typed secret).
|
|
18571
|
+
*/
|
|
18572
|
+
cloneSecretsFrom: string().optional()
|
|
18573
|
+
}), TargetSchema, {
|
|
18574
|
+
kind: "mutation",
|
|
18575
|
+
auth: "admin"
|
|
18576
|
+
}), method(object({ targetId: string() }), _void(), {
|
|
18577
|
+
kind: "mutation",
|
|
18578
|
+
auth: "admin"
|
|
18579
|
+
}), method(object({
|
|
18580
|
+
targetId: string(),
|
|
18581
|
+
enabled: boolean()
|
|
18582
|
+
}), _void(), {
|
|
18583
|
+
kind: "mutation",
|
|
18584
|
+
auth: "admin"
|
|
18585
|
+
});
|
|
17449
18586
|
/**
|
|
17450
18587
|
* `addon-pages` — system-scoped singleton aggregator cap. Public-facing
|
|
17451
18588
|
* surface that admin-ui consumes through `useAddonPagesListPages()`.
|
|
@@ -17478,52 +18615,6 @@ var AddonPageInfoSchema = object({
|
|
|
17478
18615
|
bundleUrl: string()
|
|
17479
18616
|
});
|
|
17480
18617
|
method(_void(), array(AddonPageInfoSchema).readonly());
|
|
17481
|
-
/**
|
|
17482
|
-
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
17483
|
-
* declarations. Every addon that contributes a UI page registers a
|
|
17484
|
-
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
17485
|
-
* see `addon-pages.cap.ts`) walks this collection, stamps versioned
|
|
17486
|
-
* `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
|
|
17487
|
-
* that admin-ui consumes.
|
|
17488
|
-
*
|
|
17489
|
-
* The split exists because the public listing has a different output
|
|
17490
|
-
* shape than the per-provider raw declarations, and we want both ends
|
|
17491
|
-
* to flow through codegen instead of relying on a hand-written wrapper.
|
|
17492
|
-
*/
|
|
17493
|
-
var AddonPageDeclarationSchema = object({
|
|
17494
|
-
id: string(),
|
|
17495
|
-
label: string(),
|
|
17496
|
-
icon: string(),
|
|
17497
|
-
path: string(),
|
|
17498
|
-
/**
|
|
17499
|
-
* Module Federation remote name — must match the `name` field on the
|
|
17500
|
-
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
17501
|
-
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
17502
|
-
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
17503
|
-
* contain hyphens).
|
|
17504
|
-
*/
|
|
17505
|
-
remoteName: string(),
|
|
17506
|
-
/**
|
|
17507
|
-
* Bundle filename inside the addon's `dist/` dir served at
|
|
17508
|
-
* `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
|
|
17509
|
-
* is always `'remoteEntry.js'`; the value is kept on the metadata so
|
|
17510
|
-
* the static-file route can compute an mtime-based cache-buster URL
|
|
17511
|
-
* without a separate filesystem stat.
|
|
17512
|
-
*/
|
|
17513
|
-
bundle: string(),
|
|
17514
|
-
/**
|
|
17515
|
-
* Sidebar section this page docks into. Well-known ids: `'detection'`,
|
|
17516
|
-
* `'cluster'`, `'administration'` — the page renders inside that group.
|
|
17517
|
-
* Any OTHER string creates (or joins) a custom section rendered after
|
|
17518
|
-
* the built-in groups; its label comes from `sectionLabel` (first
|
|
17519
|
-
* declaration wins), falling back to the id. Absent → the legacy
|
|
17520
|
-
* "Addon Pages" group.
|
|
17521
|
-
*/
|
|
17522
|
-
section: string().optional(),
|
|
17523
|
-
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
17524
|
-
sectionLabel: string().optional()
|
|
17525
|
-
});
|
|
17526
|
-
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
17527
18618
|
var AddonHttpRouteSchema = object({
|
|
17528
18619
|
method: _enum([
|
|
17529
18620
|
"GET",
|
|
@@ -17824,9 +18915,21 @@ var AlertSeveritySchema = _enum([
|
|
|
17824
18915
|
"error",
|
|
17825
18916
|
"critical"
|
|
17826
18917
|
]);
|
|
18918
|
+
/**
|
|
18919
|
+
* An action that RUNS something instead of going somewhere: the admin UI calls
|
|
18920
|
+
* the addon's custom action (`addons.custom`) with exactly these fields. The
|
|
18921
|
+
* producer owns the meaning; the UI knows nothing about the addon or the kind.
|
|
18922
|
+
*/
|
|
18923
|
+
var AlertActionInvokeSchema = object({
|
|
18924
|
+
addonId: string().min(1),
|
|
18925
|
+
action: string().min(1),
|
|
18926
|
+
input: record(string(), unknown()).optional()
|
|
18927
|
+
});
|
|
18928
|
+
/** An action has `href` (a link) OR `invoke` (a button that runs a custom action). */
|
|
17827
18929
|
var AlertActionSchema = object({
|
|
17828
18930
|
label: string(),
|
|
17829
18931
|
href: string().optional(),
|
|
18932
|
+
invoke: AlertActionInvokeSchema.optional(),
|
|
17830
18933
|
/**
|
|
17831
18934
|
* How `href` is followed. Absent / `app`: an admin-ui route (client-side).
|
|
17832
18935
|
* `server`: a route the HUB serves (an addon HTTP route such as an OAuth
|
|
@@ -20183,7 +21286,11 @@ var CompositionProblemSchema = object({
|
|
|
20183
21286
|
"mixed-quantity",
|
|
20184
21287
|
"helper-mismatch",
|
|
20185
21288
|
"member-constraint",
|
|
20186
|
-
"retention"
|
|
21289
|
+
"retention",
|
|
21290
|
+
"command-method-unknown",
|
|
21291
|
+
"command-source-unknown",
|
|
21292
|
+
"command-input-unknown",
|
|
21293
|
+
"command-input-missing"
|
|
20187
21294
|
]),
|
|
20188
21295
|
path: string(),
|
|
20189
21296
|
message: string()
|
|
@@ -20222,7 +21329,9 @@ var CompositionFieldRoleSchema = _enum([
|
|
|
20222
21329
|
var CompositionFieldItemSchema = object({
|
|
20223
21330
|
arrayPath: string(),
|
|
20224
21331
|
key: string(),
|
|
20225
|
-
label: string()
|
|
21332
|
+
label: string(),
|
|
21333
|
+
/** True when the item carries a `reset` forward: the composer writes `resettable` from this (D967). */
|
|
21334
|
+
resettable: boolean().optional()
|
|
20226
21335
|
});
|
|
20227
21336
|
var CompositionFieldPlanSchema = object({
|
|
20228
21337
|
cap: string(),
|
|
@@ -20427,7 +21536,7 @@ Object.freeze({
|
|
|
20427
21536
|
* One catalog row per base reading a group helper can build (D944 §3).
|
|
20428
21537
|
* Data, not a form default written into the block.
|
|
20429
21538
|
*/
|
|
20430
|
-
var NOW$
|
|
21539
|
+
var NOW$3 = {
|
|
20431
21540
|
fieldPath: "lastFetchedAt",
|
|
20432
21541
|
expr: "now"
|
|
20433
21542
|
};
|
|
@@ -20479,7 +21588,7 @@ var GROUP_TEMPLATES = [
|
|
|
20479
21588
|
quantity: "temperature",
|
|
20480
21589
|
defaultAggregate: "mean",
|
|
20481
21590
|
empty: "null",
|
|
20482
|
-
companions: [NOW$
|
|
21591
|
+
companions: [NOW$3],
|
|
20483
21592
|
alsoFrom: {
|
|
20484
21593
|
deviceTypes: CLIMATE_DEVICES,
|
|
20485
21594
|
accept: {
|
|
@@ -20497,7 +21606,7 @@ var GROUP_TEMPLATES = [
|
|
|
20497
21606
|
quantity: "humidity",
|
|
20498
21607
|
defaultAggregate: "mean",
|
|
20499
21608
|
empty: "null",
|
|
20500
|
-
companions: [NOW$
|
|
21609
|
+
companions: [NOW$3],
|
|
20501
21610
|
alsoFrom: {
|
|
20502
21611
|
deviceTypes: CLIMATE_DEVICES,
|
|
20503
21612
|
accept: {
|
|
@@ -20515,7 +21624,7 @@ var GROUP_TEMPLATES = [
|
|
|
20515
21624
|
quantity: "pressure",
|
|
20516
21625
|
defaultAggregate: "mean",
|
|
20517
21626
|
empty: "null",
|
|
20518
|
-
companions: [NOW$
|
|
21627
|
+
companions: [NOW$3]
|
|
20519
21628
|
}),
|
|
20520
21629
|
numeric({
|
|
20521
21630
|
id: "illuminance",
|
|
@@ -20526,7 +21635,7 @@ var GROUP_TEMPLATES = [
|
|
|
20526
21635
|
quantity: "illuminance",
|
|
20527
21636
|
defaultAggregate: "mean",
|
|
20528
21637
|
empty: "null",
|
|
20529
|
-
companions: [NOW$
|
|
21638
|
+
companions: [NOW$3]
|
|
20530
21639
|
}),
|
|
20531
21640
|
numeric({
|
|
20532
21641
|
id: "power",
|
|
@@ -20537,7 +21646,7 @@ var GROUP_TEMPLATES = [
|
|
|
20537
21646
|
quantity: "power",
|
|
20538
21647
|
defaultAggregate: "sum",
|
|
20539
21648
|
empty: "omit",
|
|
20540
|
-
companions: [NOW$
|
|
21649
|
+
companions: [NOW$3]
|
|
20541
21650
|
}),
|
|
20542
21651
|
numeric({
|
|
20543
21652
|
id: "energy",
|
|
@@ -20548,7 +21657,7 @@ var GROUP_TEMPLATES = [
|
|
|
20548
21657
|
quantity: "energy",
|
|
20549
21658
|
defaultAggregate: "sum",
|
|
20550
21659
|
empty: "omit",
|
|
20551
|
-
companions: [NOW$
|
|
21660
|
+
companions: [NOW$3]
|
|
20552
21661
|
}),
|
|
20553
21662
|
numeric({
|
|
20554
21663
|
id: "battery",
|
|
@@ -20583,7 +21692,7 @@ var GROUP_TEMPLATES = [
|
|
|
20583
21692
|
quantity: "device",
|
|
20584
21693
|
defaultAggregate: "mean",
|
|
20585
21694
|
empty: "null",
|
|
20586
|
-
companions: [NOW$
|
|
21695
|
+
companions: [NOW$3]
|
|
20587
21696
|
}),
|
|
20588
21697
|
binary({
|
|
20589
21698
|
id: "motion",
|
|
@@ -22533,116 +23642,6 @@ var DeviceAdminLinkSchema = object({
|
|
|
22533
23642
|
});
|
|
22534
23643
|
method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" });
|
|
22535
23644
|
/**
|
|
22536
|
-
* Identity envelope for a device's upstream-system metadata.
|
|
22537
|
-
*
|
|
22538
|
-
* Two jobs:
|
|
22539
|
-
*
|
|
22540
|
-
* 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
|
|
22541
|
-
* uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
|
|
22542
|
-
* WS-Discovery endpoint UUID, …). Provider code keeps a
|
|
22543
|
-
* `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
|
|
22544
|
-
* without round-tripping through `stableId` / `id` mappings.
|
|
22545
|
-
*
|
|
22546
|
-
* 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
|
|
22547
|
-
* one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
|
|
22548
|
-
* for vendors where MAC is the natural key). For systems where the
|
|
22549
|
-
* live dispatch key can be renamed (HA `entity_id` can be edited),
|
|
22550
|
-
* providers detect renames by matching `uniqueId` and patch `id`
|
|
22551
|
-
* in-place so dispatch keeps working.
|
|
22552
|
-
*
|
|
22553
|
-
* **Display unit and precision are NOT stored here.** They are a read-time
|
|
22554
|
-
* preference (D944). A polymorphic reading stores its `quantity` here once,
|
|
22555
|
-
* at adopt, and a date/time value stores `dateTimeShape` the same way —
|
|
22556
|
-
* neither is repeated on each sample. The `device_class` tag is an
|
|
22557
|
-
* HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
|
|
22558
|
-
* to derive the CamStack `DeviceRole`; it is never stored after that point.
|
|
22559
|
-
*
|
|
22560
|
-
* The `raw` escape hatch carries provider-specific attributes that don't
|
|
22561
|
-
* fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
|
|
22562
|
-
* details, …). Providers control what lands here.
|
|
22563
|
-
*/
|
|
22564
|
-
var SourceInfoSchema = object({
|
|
22565
|
-
/** Live dispatch key — mutable when the source system allows rename. */
|
|
22566
|
-
id: string(),
|
|
22567
|
-
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
22568
|
-
system: string(),
|
|
22569
|
-
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
22570
|
-
* Used to detect rename when `id` changes. */
|
|
22571
|
-
uniqueId: string().optional(),
|
|
22572
|
-
/** Quantity of a polymorphic reading, written once at adopt (D944). */
|
|
22573
|
-
quantity: ReadingQuantitySchema.optional(),
|
|
22574
|
-
/** Date / time / datetime shape of a string reading, written once at adopt. */
|
|
22575
|
-
dateTimeShape: _enum([
|
|
22576
|
-
"date",
|
|
22577
|
-
"time",
|
|
22578
|
-
"datetime"
|
|
22579
|
-
]).optional(),
|
|
22580
|
-
/** Provider-specific extras that don't fit the structured slots.
|
|
22581
|
-
* Whitelist what goes here per provider — do NOT dump entire HA
|
|
22582
|
-
* attribute blobs (would explode the meta JSON column). */
|
|
22583
|
-
raw: record(string(), unknown()).optional()
|
|
22584
|
-
});
|
|
22585
|
-
/**
|
|
22586
|
-
* Build the synthetic SourceInfo every existing provider falls back to
|
|
22587
|
-
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
22588
|
-
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
22589
|
-
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
22590
|
-
* id. Real upstream identity replaces this once a provider migrates and
|
|
22591
|
-
* calls `updateSourceInfo()` with concrete values.
|
|
22592
|
-
*/
|
|
22593
|
-
function synthesizeSourceInfo(input) {
|
|
22594
|
-
return {
|
|
22595
|
-
id: input.stableId,
|
|
22596
|
-
system: input.addonId
|
|
22597
|
-
};
|
|
22598
|
-
}
|
|
22599
|
-
/**
|
|
22600
|
-
* Shallow-merge a partial patch over a prior SourceInfo. `undefined`
|
|
22601
|
-
* patch values are ignored (no field clobber); to clear a field
|
|
22602
|
-
* explicitly, providers omit it from the patch and rebuild from scratch
|
|
22603
|
-
* via `updateSourceInfo({})` cycles — uncommon enough that we don't
|
|
22604
|
-
* bother with a `null`-means-delete sentinel.
|
|
22605
|
-
*
|
|
22606
|
-
* The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
|
|
22607
|
-
* merges over existing `raw` keys instead of replacing the whole bag.
|
|
22608
|
-
*/
|
|
22609
|
-
function mergeSourceInfo(prev, patch) {
|
|
22610
|
-
const next = { ...prev };
|
|
22611
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
22612
|
-
if (v === void 0) continue;
|
|
22613
|
-
if (k === "raw" && typeof v === "object" && v !== null) {
|
|
22614
|
-
next.raw = {
|
|
22615
|
-
...prev.raw,
|
|
22616
|
-
...v
|
|
22617
|
-
};
|
|
22618
|
-
continue;
|
|
22619
|
-
}
|
|
22620
|
-
next[k] = v;
|
|
22621
|
-
}
|
|
22622
|
-
return next;
|
|
22623
|
-
}
|
|
22624
|
-
/**
|
|
22625
|
-
* Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
|
|
22626
|
-
* blob is a free-form `Record<string, unknown>` shared with hardware-
|
|
22627
|
-
* identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
|
|
22628
|
-
* lives under a single nested key to keep it composable with those.
|
|
22629
|
-
*/
|
|
22630
|
-
var SOURCE_INFO_METADATA_KEY = "sourceInfo";
|
|
22631
|
-
/**
|
|
22632
|
-
* Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
|
|
22633
|
-
* with Zod. Returns `null` when the blob is missing or the nested
|
|
22634
|
-
* `sourceInfo` key fails validation — callers fall back to the
|
|
22635
|
-
* synthetic default in that case (see `synthesizeSourceInfo`).
|
|
22636
|
-
*/
|
|
22637
|
-
function extractSourceInfoFromMetadata(metadata) {
|
|
22638
|
-
if (!metadata) return null;
|
|
22639
|
-
const raw = metadata[SOURCE_INFO_METADATA_KEY];
|
|
22640
|
-
if (!raw) return null;
|
|
22641
|
-
const parsed = SourceInfoSchema.safeParse(raw);
|
|
22642
|
-
if (!parsed.success) return null;
|
|
22643
|
-
return parsed.data;
|
|
22644
|
-
}
|
|
22645
|
-
/**
|
|
22646
23645
|
* device-discovery — device-scoped capability for parents that host /
|
|
22647
23646
|
* enumerate child devices (Reolink Hub / NVR, ONVIF gateway, future
|
|
22648
23647
|
* integrations). The parent advertises a list of discoverable children,
|
|
@@ -23047,12 +24046,48 @@ var ExposeInputSchema = object({
|
|
|
23047
24046
|
capabilities: array(string()).optional()
|
|
23048
24047
|
});
|
|
23049
24048
|
var UnexposeInputSchema = object({ deviceId: string() });
|
|
24049
|
+
/**
|
|
24050
|
+
* What an exporter may expose besides devices (D968). An action and a
|
|
24051
|
+
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
24052
|
+
* exporter's own selection, not the device registry.
|
|
24053
|
+
*/
|
|
24054
|
+
var ExportEntityKindSchema = _enum([
|
|
24055
|
+
"device",
|
|
24056
|
+
"action",
|
|
24057
|
+
"notifier"
|
|
24058
|
+
]);
|
|
24059
|
+
var ExportEntityRefSchema = discriminatedUnion("kind", [
|
|
24060
|
+
object({
|
|
24061
|
+
kind: literal("device"),
|
|
24062
|
+
deviceId: string()
|
|
24063
|
+
}),
|
|
24064
|
+
object({
|
|
24065
|
+
kind: literal("action"),
|
|
24066
|
+
actionId: string().min(1)
|
|
24067
|
+
}),
|
|
24068
|
+
object({
|
|
24069
|
+
kind: literal("notifier"),
|
|
24070
|
+
targetId: string().min(1)
|
|
24071
|
+
})
|
|
24072
|
+
]);
|
|
24073
|
+
var ExposedEntitySchema = object({
|
|
24074
|
+
entity: ExportEntityRefSchema,
|
|
24075
|
+
exposedAs: string().optional()
|
|
24076
|
+
});
|
|
24077
|
+
var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
24078
|
+
var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
23050
24079
|
method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
23051
24080
|
kind: "mutation",
|
|
23052
24081
|
auth: "admin"
|
|
23053
24082
|
}), method(UnexposeInputSchema, _void(), {
|
|
23054
24083
|
kind: "mutation",
|
|
23055
24084
|
auth: "admin"
|
|
24085
|
+
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
24086
|
+
kind: "mutation",
|
|
24087
|
+
auth: "admin"
|
|
24088
|
+
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
24089
|
+
kind: "mutation",
|
|
24090
|
+
auth: "admin"
|
|
23056
24091
|
});
|
|
23057
24092
|
/** Lucide category ids, alphabetical. `ICON_META` indexes into this. */
|
|
23058
24093
|
var ICON_CATEGORY_IDS = [
|
|
@@ -44268,6 +45303,14 @@ var embeddingEncoderCapability = {
|
|
|
44268
45303
|
})
|
|
44269
45304
|
}
|
|
44270
45305
|
};
|
|
45306
|
+
method(object({
|
|
45307
|
+
nodeId: string().min(1),
|
|
45308
|
+
/** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
|
|
45309
|
+
removedAt: number()
|
|
45310
|
+
}), object({ counts: record(string(), number().int().nonnegative()) }), {
|
|
45311
|
+
kind: "mutation",
|
|
45312
|
+
auth: "admin"
|
|
45313
|
+
});
|
|
44271
45314
|
/**
|
|
44272
45315
|
* `device-removal-hook` — the reliable way an addon learns a device was DELETED,
|
|
44273
45316
|
* so it can delete what it keeps for that device (D865).
|
|
@@ -45110,7 +46153,7 @@ var LlmProviderWidgetSchema = object({
|
|
|
45110
46153
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
45111
46154
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
45112
46155
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
45113
|
-
var ConfigSchemaPassthrough$
|
|
46156
|
+
var ConfigSchemaPassthrough$2 = unknown();
|
|
45114
46157
|
var LlmProfileKindDescriptorSchema = object({
|
|
45115
46158
|
kind: LlmProfileKindSchema,
|
|
45116
46159
|
label: string(),
|
|
@@ -45119,9 +46162,9 @@ var LlmProfileKindDescriptorSchema = object({
|
|
|
45119
46162
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
45120
46163
|
addonId: string(),
|
|
45121
46164
|
/** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
|
|
45122
|
-
connectionSchema: ConfigSchemaPassthrough$
|
|
46165
|
+
connectionSchema: ConfigSchemaPassthrough$2,
|
|
45123
46166
|
/** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
|
|
45124
|
-
advancedSchema: ConfigSchemaPassthrough$
|
|
46167
|
+
advancedSchema: ConfigSchemaPassthrough$2.optional(),
|
|
45125
46168
|
/** The kind's defaults; a model from `listModels` may override any of them. */
|
|
45126
46169
|
features: LlmModelFeaturesSchema,
|
|
45127
46170
|
/** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
|
|
@@ -45647,10 +46690,10 @@ var CpuBreakdownSchema = object({
|
|
|
45647
46690
|
cores: number()
|
|
45648
46691
|
});
|
|
45649
46692
|
var MemoryInfoSchema = object({
|
|
45650
|
-
percent: number(),
|
|
46693
|
+
percent: number().nullable(),
|
|
45651
46694
|
totalBytes: number(),
|
|
45652
|
-
usedBytes: number(),
|
|
45653
|
-
availableBytes: number(),
|
|
46695
|
+
usedBytes: number().nullable(),
|
|
46696
|
+
availableBytes: number().nullable(),
|
|
45654
46697
|
swapUsedBytes: number(),
|
|
45655
46698
|
swapTotalBytes: number()
|
|
45656
46699
|
});
|
|
@@ -45954,8 +46997,9 @@ var GetLoadSeriesInputSchema = object({
|
|
|
45954
46997
|
});
|
|
45955
46998
|
var SystemMetricsSchema = object({
|
|
45956
46999
|
cpuPercent: number(),
|
|
45957
|
-
|
|
45958
|
-
|
|
47000
|
+
/** `null` when the memory sample failed or was not taken yet (D393). */
|
|
47001
|
+
memoryPercent: number().nullable(),
|
|
47002
|
+
memoryUsedMB: number().nullable(),
|
|
45959
47003
|
memoryTotalMB: number(),
|
|
45960
47004
|
diskPercent: number().optional(),
|
|
45961
47005
|
temperature: number().optional(),
|
|
@@ -46191,427 +47235,6 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
|
46191
47235
|
sourcePort: number().optional()
|
|
46192
47236
|
});
|
|
46193
47237
|
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
46194
|
-
/**
|
|
46195
|
-
* notification-output — canonical, capability-gated notification delivery.
|
|
46196
|
-
*
|
|
46197
|
-
* Apprise-derived model (see
|
|
46198
|
-
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
46199
|
-
* callers emit ONE canonical `Notification`; each provider declares a
|
|
46200
|
-
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
46201
|
-
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
46202
|
-
* message to what the kind supports — callers never special-case a service.
|
|
46203
|
-
*
|
|
46204
|
-
* DESIGN DECISIONS (locked):
|
|
46205
|
-
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
46206
|
-
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
46207
|
-
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
46208
|
-
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
46209
|
-
* alternative would fork the UI per addon and cannot host the
|
|
46210
|
-
* discovery→adopt flow.
|
|
46211
|
-
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
46212
|
-
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
46213
|
-
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
46214
|
-
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
46215
|
-
* `addonId` the generated collection router extracts from the call input.
|
|
46216
|
-
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
46217
|
-
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
46218
|
-
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
46219
|
-
* base64 fallback needed.
|
|
46220
|
-
*
|
|
46221
|
-
* TODO (deferred, closed-set change — separate decision): add
|
|
46222
|
-
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
46223
|
-
* admin "Integrations" page.
|
|
46224
|
-
*/
|
|
46225
|
-
/**
|
|
46226
|
-
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
46227
|
-
* adapter picks what it supports and the degrade engine filters the rest.
|
|
46228
|
-
*/
|
|
46229
|
-
var AttachmentMediaTypeSchema = _enum([
|
|
46230
|
-
"image",
|
|
46231
|
-
"video",
|
|
46232
|
-
"gif",
|
|
46233
|
-
"audio",
|
|
46234
|
-
"icon"
|
|
46235
|
-
]);
|
|
46236
|
-
/**
|
|
46237
|
-
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
46238
|
-
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
46239
|
-
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
46240
|
-
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
46241
|
-
*/
|
|
46242
|
-
var AttachmentSchema = object({
|
|
46243
|
-
mediaType: AttachmentMediaTypeSchema,
|
|
46244
|
-
url: string().optional(),
|
|
46245
|
-
bytes: _instanceof(Uint8Array).optional(),
|
|
46246
|
-
mime: string().optional(),
|
|
46247
|
-
name: string().optional()
|
|
46248
|
-
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
46249
|
-
var NotificationFormatSchema = _enum([
|
|
46250
|
-
"text",
|
|
46251
|
-
"markdown",
|
|
46252
|
-
"html"
|
|
46253
|
-
]);
|
|
46254
|
-
/**
|
|
46255
|
-
* The CLOSED icon vocabulary an action button may use.
|
|
46256
|
-
*
|
|
46257
|
-
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
46258
|
-
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
46259
|
-
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
46260
|
-
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
46261
|
-
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
46262
|
-
*
|
|
46263
|
-
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
46264
|
-
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
46265
|
-
* differently.
|
|
46266
|
-
*
|
|
46267
|
-
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
46268
|
-
*
|
|
46269
|
-
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
46270
|
-
* the whole set onto its own renderer's vocabulary through a
|
|
46271
|
-
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
46272
|
-
* fallback, so adding a member here fails every adapter's build until someone
|
|
46273
|
-
* decides its glyph, which is the only place that decision can be made
|
|
46274
|
-
* honestly.
|
|
46275
|
-
*
|
|
46276
|
-
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
46277
|
-
* `disarm` straight through; iOS feeds that string to
|
|
46278
|
-
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
46279
|
-
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
46280
|
-
* mapping, and "the field is documented" is not "the value renders".
|
|
46281
|
-
*
|
|
46282
|
-
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
46283
|
-
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
46284
|
-
* so an addon that emits a token the running hub does not know does not lose an
|
|
46285
|
-
* icon — its whole `send` fails Zod validation and the notification never
|
|
46286
|
-
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
46287
|
-
*/
|
|
46288
|
-
var NotificationActionIconSchema = _enum([
|
|
46289
|
-
"acknowledge",
|
|
46290
|
-
"dismiss",
|
|
46291
|
-
"silence",
|
|
46292
|
-
"snooze",
|
|
46293
|
-
"view",
|
|
46294
|
-
"play",
|
|
46295
|
-
"open",
|
|
46296
|
-
"close",
|
|
46297
|
-
"lock",
|
|
46298
|
-
"unlock",
|
|
46299
|
-
"arm",
|
|
46300
|
-
"arm-home",
|
|
46301
|
-
"arm-away",
|
|
46302
|
-
"arm-night",
|
|
46303
|
-
"disarm",
|
|
46304
|
-
"light",
|
|
46305
|
-
"alert",
|
|
46306
|
-
"camera"
|
|
46307
|
-
]);
|
|
46308
|
-
/** A single tap-through action button. */
|
|
46309
|
-
var NotificationActionSchema = object({
|
|
46310
|
-
id: string(),
|
|
46311
|
-
label: string(),
|
|
46312
|
-
url: string().optional(),
|
|
46313
|
-
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
46314
|
-
icon: NotificationActionIconSchema.optional(),
|
|
46315
|
-
/**
|
|
46316
|
-
* Renders in a warning style where the notifier supports it.
|
|
46317
|
-
*
|
|
46318
|
-
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
46319
|
-
* else — see `notification-center/action-token.ts` for what that does and
|
|
46320
|
-
* does not buy.
|
|
46321
|
-
*/
|
|
46322
|
-
destructive: boolean().optional(),
|
|
46323
|
-
/**
|
|
46324
|
-
* How the tap should REACH the url.
|
|
46325
|
-
*
|
|
46326
|
-
* `navigate` (absent, and every button authored before this field) opens it:
|
|
46327
|
-
* the phone leaves the notification and shows whatever the callback returns.
|
|
46328
|
-
* That is right for a button whose answer the operator wants to read.
|
|
46329
|
-
*
|
|
46330
|
-
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
46331
|
-
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
46332
|
-
* an answer to the notification, and being thrown into a browser tab to
|
|
46333
|
-
* confirm it costs more attention than the notification did. A backend that
|
|
46334
|
-
* cannot do a background call renders it as an ordinary link (the adapters
|
|
46335
|
-
* fall back rather than dropping the button), so this is a preference, never
|
|
46336
|
-
* a requirement.
|
|
46337
|
-
*/
|
|
46338
|
-
mode: _enum(["navigate", "background"]).optional()
|
|
46339
|
-
});
|
|
46340
|
-
/** A delivery parameter value: what a notifier declared it takes. */
|
|
46341
|
-
var DeliveryParamValueSchema = union([
|
|
46342
|
-
string(),
|
|
46343
|
-
number(),
|
|
46344
|
-
boolean()
|
|
46345
|
-
]);
|
|
46346
|
-
/**
|
|
46347
|
-
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
46348
|
-
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
46349
|
-
* never handed one. The admin UI derives its control from this descriptor —
|
|
46350
|
-
* no editor knows a service by name.
|
|
46351
|
-
*
|
|
46352
|
-
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
46353
|
-
* run on the addon cap path).
|
|
46354
|
-
*/
|
|
46355
|
-
var DeliveryParamSchema = object({
|
|
46356
|
-
key: string().min(1),
|
|
46357
|
-
label: string(),
|
|
46358
|
-
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
46359
|
-
labelKey: string().optional(),
|
|
46360
|
-
type: _enum([
|
|
46361
|
-
"enum",
|
|
46362
|
-
"number",
|
|
46363
|
-
"boolean",
|
|
46364
|
-
"string"
|
|
46365
|
-
]),
|
|
46366
|
-
/** `enum` only: the closed set of accepted values. */
|
|
46367
|
-
options: array(object({
|
|
46368
|
-
value: string(),
|
|
46369
|
-
label: string()
|
|
46370
|
-
})).optional(),
|
|
46371
|
-
/** `number` only. */
|
|
46372
|
-
min: number().optional(),
|
|
46373
|
-
max: number().optional(),
|
|
46374
|
-
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
46375
|
-
default: DeliveryParamValueSchema.optional()
|
|
46376
|
-
});
|
|
46377
|
-
/**
|
|
46378
|
-
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
46379
|
-
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
46380
|
-
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
46381
|
-
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
46382
|
-
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
46383
|
-
* `priority` for that one target.
|
|
46384
|
-
*/
|
|
46385
|
-
var NotificationSchema = object({
|
|
46386
|
-
body: string(),
|
|
46387
|
-
title: string().optional(),
|
|
46388
|
-
format: NotificationFormatSchema.default("text"),
|
|
46389
|
-
priority: number().int().min(1).max(5).default(3),
|
|
46390
|
-
level: string().optional(),
|
|
46391
|
-
attachments: array(AttachmentSchema).optional(),
|
|
46392
|
-
clickUrl: string().optional(),
|
|
46393
|
-
actions: array(NotificationActionSchema).optional(),
|
|
46394
|
-
sound: string().optional(),
|
|
46395
|
-
ttl: number().optional(),
|
|
46396
|
-
tag: string().optional(),
|
|
46397
|
-
deviceId: number().optional(),
|
|
46398
|
-
eventId: string().optional(),
|
|
46399
|
-
metadata: record(string(), unknown()).optional(),
|
|
46400
|
-
/**
|
|
46401
|
-
* Values for the parameters the target's kind DECLARES in
|
|
46402
|
-
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
46403
|
-
* declaration by the sender; a notifier reads only the keys it declared.
|
|
46404
|
-
*/
|
|
46405
|
-
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
46406
|
-
});
|
|
46407
|
-
/** One declared native severity/priority level for a kind. */
|
|
46408
|
-
var TargetKindLevelSchema = object({
|
|
46409
|
-
id: string(),
|
|
46410
|
-
label: string(),
|
|
46411
|
-
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
46412
|
-
ordinal: number().int().min(1).max(5).nullable(),
|
|
46413
|
-
flags: object({
|
|
46414
|
-
critical: boolean().optional(),
|
|
46415
|
-
silent: boolean().optional(),
|
|
46416
|
-
noPush: boolean().optional()
|
|
46417
|
-
}).optional(),
|
|
46418
|
-
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
46419
|
-
requires: array(string()).optional(),
|
|
46420
|
-
description: string().optional()
|
|
46421
|
-
});
|
|
46422
|
-
/** The full capability block consulted before dispatch. */
|
|
46423
|
-
var TargetKindCapsSchema = object({
|
|
46424
|
-
attachments: object({
|
|
46425
|
-
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
46426
|
-
mode: _enum([
|
|
46427
|
-
"url",
|
|
46428
|
-
"bytes",
|
|
46429
|
-
"both"
|
|
46430
|
-
]),
|
|
46431
|
-
max: number().int().nonnegative(),
|
|
46432
|
-
maxBytes: number().int().positive().optional()
|
|
46433
|
-
}),
|
|
46434
|
-
/** Max action buttons (0 = none). */
|
|
46435
|
-
actions: number().int().nonnegative(),
|
|
46436
|
-
/**
|
|
46437
|
-
* Whether this kind renders a per-action ICON.
|
|
46438
|
-
*
|
|
46439
|
-
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
46440
|
-
* run on the addon cap path — three production failures in one day taught
|
|
46441
|
-
* this repo that once. Absent is read as false by the degrade engine, which
|
|
46442
|
-
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
46443
|
-
* assumed and dropped costs the operator's trust in the field.
|
|
46444
|
-
*/
|
|
46445
|
-
actionIcons: boolean().optional(),
|
|
46446
|
-
levels: array(TargetKindLevelSchema),
|
|
46447
|
-
format: array(NotificationFormatSchema),
|
|
46448
|
-
clickUrl: boolean(),
|
|
46449
|
-
sound: boolean(),
|
|
46450
|
-
ttl: boolean(),
|
|
46451
|
-
bodyMaxLen: number().int().positive()
|
|
46452
|
-
});
|
|
46453
|
-
/**
|
|
46454
|
-
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
46455
|
-
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
46456
|
-
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
46457
|
-
* the union is large and not meant for runtime validation here; the exported
|
|
46458
|
-
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
46459
|
-
*/
|
|
46460
|
-
var ConfigSchemaPassthrough$2 = unknown();
|
|
46461
|
-
var TargetKindSchema = object({
|
|
46462
|
-
kind: string(),
|
|
46463
|
-
label: string(),
|
|
46464
|
-
icon: string(),
|
|
46465
|
-
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
46466
|
-
addonId: string(),
|
|
46467
|
-
/**
|
|
46468
|
-
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
46469
|
-
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
46470
|
-
* when the addon bundles no icon for that kind — the client then falls back
|
|
46471
|
-
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
46472
|
-
*
|
|
46473
|
-
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
46474
|
-
* client, and a native client joins it onto its own hub base.
|
|
46475
|
-
*
|
|
46476
|
-
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
46477
|
-
* field that survived only because the runtime cap-router forwards provider
|
|
46478
|
-
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
46479
|
-
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
46480
|
-
* broken silently the moment output validation was tightened anywhere.
|
|
46481
|
-
*/
|
|
46482
|
-
iconUrl: string().optional(),
|
|
46483
|
-
/**
|
|
46484
|
-
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
46485
|
-
*
|
|
46486
|
-
* The server knows this and therefore says it, because the client cannot
|
|
46487
|
-
* safely guess: a React-Native client renders SVG and raster through two
|
|
46488
|
-
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
46489
|
-
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
46490
|
-
* placeholder glyph for every vector icon while the web build looked fine.
|
|
46491
|
-
*
|
|
46492
|
-
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
46493
|
-
* not been updated — a client that cannot determine the type should prefer
|
|
46494
|
-
* its raster path, which is the safe default for an unknown image.
|
|
46495
|
-
*/
|
|
46496
|
-
iconMediaType: string().optional(),
|
|
46497
|
-
configSchema: ConfigSchemaPassthrough$2,
|
|
46498
|
-
/**
|
|
46499
|
-
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
46500
|
-
* base URL, instance, chat, recipient). A save that changes any of them
|
|
46501
|
-
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
46502
|
-
* editor uses this to ask for the secret on exactly those edits instead of
|
|
46503
|
-
* on every edit.
|
|
46504
|
-
*
|
|
46505
|
-
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
46506
|
-
* kinds) and from a provider older than this field — an editor that finds
|
|
46507
|
-
* it absent must assume ANY edit may need the secret.
|
|
46508
|
-
*/
|
|
46509
|
-
destinationKeys: array(string()).optional(),
|
|
46510
|
-
supportsDiscovery: boolean(),
|
|
46511
|
-
/**
|
|
46512
|
-
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
46513
|
-
* Absent = none; the rule editor then offers nothing.
|
|
46514
|
-
*/
|
|
46515
|
-
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
46516
|
-
caps: TargetKindCapsSchema
|
|
46517
|
-
});
|
|
46518
|
-
/**
|
|
46519
|
-
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
46520
|
-
* (return a presence marker only) when serving `listTargets` — never
|
|
46521
|
-
* round-trip a stored secret to the UI.
|
|
46522
|
-
*/
|
|
46523
|
-
var TargetSchema = object({
|
|
46524
|
-
id: string(),
|
|
46525
|
-
name: string(),
|
|
46526
|
-
kind: string(),
|
|
46527
|
-
addonId: string(),
|
|
46528
|
-
enabled: boolean(),
|
|
46529
|
-
config: record(string(), unknown())
|
|
46530
|
-
});
|
|
46531
|
-
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
46532
|
-
var DiscoveredTargetSchema = object({
|
|
46533
|
-
kind: string(),
|
|
46534
|
-
suggestedName: string(),
|
|
46535
|
-
config: record(string(), unknown())
|
|
46536
|
-
});
|
|
46537
|
-
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
46538
|
-
var RenderedAsSchema = object({
|
|
46539
|
-
level: string(),
|
|
46540
|
-
format: NotificationFormatSchema,
|
|
46541
|
-
attachmentsSent: number().int().nonnegative(),
|
|
46542
|
-
actionsSent: number().int().nonnegative(),
|
|
46543
|
-
truncated: boolean(),
|
|
46544
|
-
dropped: array(string())
|
|
46545
|
-
});
|
|
46546
|
-
var SendResultSchema = object({
|
|
46547
|
-
success: boolean(),
|
|
46548
|
-
error: string().optional(),
|
|
46549
|
-
renderedAs: RenderedAsSchema.optional()
|
|
46550
|
-
});
|
|
46551
|
-
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
46552
|
-
var TestResultSchema = SendResultSchema;
|
|
46553
|
-
/**
|
|
46554
|
-
* Pre-save credential probe against a partial (unsaved) target config.
|
|
46555
|
-
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
46556
|
-
*/
|
|
46557
|
-
var ProbeTargetConfigResultSchema = object({
|
|
46558
|
-
ok: boolean(),
|
|
46559
|
-
resolvedUrl: string(),
|
|
46560
|
-
httpStatus: number().int().optional(),
|
|
46561
|
-
stateInstance: string().optional(),
|
|
46562
|
-
error: string().optional()
|
|
46563
|
-
});
|
|
46564
|
-
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
|
|
46565
|
-
kind: string(),
|
|
46566
|
-
config: record(string(), unknown()).optional(),
|
|
46567
|
-
/**
|
|
46568
|
-
* When editing / duplicating, the blank password field never round-trips
|
|
46569
|
-
* the stored token. Pass the source target id so the provider can restore
|
|
46570
|
-
* secrets server-side before dialling (same merge as upsert).
|
|
46571
|
-
*/
|
|
46572
|
-
targetId: string().optional()
|
|
46573
|
-
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
46574
|
-
kind: string(),
|
|
46575
|
-
config: record(string(), unknown()),
|
|
46576
|
-
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
46577
|
-
targetId: string().optional()
|
|
46578
|
-
}), ProbeTargetConfigResultSchema, {
|
|
46579
|
-
kind: "mutation",
|
|
46580
|
-
auth: "admin"
|
|
46581
|
-
}), method(object({
|
|
46582
|
-
targetId: string(),
|
|
46583
|
-
notification: NotificationSchema
|
|
46584
|
-
}), SendResultSchema, {
|
|
46585
|
-
kind: "mutation",
|
|
46586
|
-
auth: "admin"
|
|
46587
|
-
}), method(object({
|
|
46588
|
-
targetId: string(),
|
|
46589
|
-
sample: NotificationSchema.optional()
|
|
46590
|
-
}), TestResultSchema, {
|
|
46591
|
-
kind: "mutation",
|
|
46592
|
-
auth: "admin"
|
|
46593
|
-
}), method(object({
|
|
46594
|
-
target: TargetSchema,
|
|
46595
|
-
/**
|
|
46596
|
-
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
46597
|
-
* existing row. The UI never holds the stored token; blank / redacted
|
|
46598
|
-
* password fields are restored from this source (D634 still applies if
|
|
46599
|
-
* destinations diverge without a re-typed secret).
|
|
46600
|
-
*/
|
|
46601
|
-
cloneSecretsFrom: string().optional()
|
|
46602
|
-
}), TargetSchema, {
|
|
46603
|
-
kind: "mutation",
|
|
46604
|
-
auth: "admin"
|
|
46605
|
-
}), method(object({ targetId: string() }), _void(), {
|
|
46606
|
-
kind: "mutation",
|
|
46607
|
-
auth: "admin"
|
|
46608
|
-
}), method(object({
|
|
46609
|
-
targetId: string(),
|
|
46610
|
-
enabled: boolean()
|
|
46611
|
-
}), _void(), {
|
|
46612
|
-
kind: "mutation",
|
|
46613
|
-
auth: "admin"
|
|
46614
|
-
});
|
|
46615
47238
|
var AUDIO_MACRO_LABELS = [
|
|
46616
47239
|
{
|
|
46617
47240
|
id: "speech",
|
|
@@ -54856,14 +55479,6 @@ var EngineProvisioningSchema = object({
|
|
|
54856
55479
|
*/
|
|
54857
55480
|
configIssues: array(string()).optional()
|
|
54858
55481
|
});
|
|
54859
|
-
var PipelineStepInputSchema = lazy(() => object({
|
|
54860
|
-
addonId: string(),
|
|
54861
|
-
modelId: string().optional(),
|
|
54862
|
-
enabled: boolean().default(true),
|
|
54863
|
-
children: array(PipelineStepInputSchema).optional(),
|
|
54864
|
-
settings: record(string(), unknown()).optional(),
|
|
54865
|
-
jumpDeviceKey: string().optional()
|
|
54866
|
-
}));
|
|
54867
55482
|
var ModelSubstitutionSchema = object({
|
|
54868
55483
|
addonId: string(),
|
|
54869
55484
|
chosen: string(),
|
|
@@ -54942,7 +55557,6 @@ var ConfigUISchemaBridge = custom();
|
|
|
54942
55557
|
var ConfigUISchemaNullableBridge = custom();
|
|
54943
55558
|
var InferenceCapabilitiesBridge = custom();
|
|
54944
55559
|
var ModelAvailabilityListBridge = custom();
|
|
54945
|
-
var PipelineRunResultBridge = custom();
|
|
54946
55560
|
method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
|
|
54947
55561
|
modelId: string(),
|
|
54948
55562
|
settings: record(string(), unknown()).readonly()
|
|
@@ -57938,11 +58552,13 @@ var ImageContractSchema = object({
|
|
|
57938
58552
|
* The shell verdict (see {@link WrapperKindSchema}) with the proofs that
|
|
57939
58553
|
* produced it.
|
|
57940
58554
|
*
|
|
57941
|
-
* There is deliberately NO `canSelfUpdate`
|
|
57942
|
-
*
|
|
57943
|
-
*
|
|
57944
|
-
*
|
|
57945
|
-
*
|
|
58555
|
+
* There is deliberately NO `canSelfUpdate` boolean: a wrapper update is an
|
|
58556
|
+
* ANNOUNCEMENT. The notification body carries the exact command for a `docker`
|
|
58557
|
+
* shell and says the app restarts into the new build for `electron`, and that
|
|
58558
|
+
* sentence is chosen from `kind` alone — a boolean nothing reads is the defect
|
|
58559
|
+
* this repo keeps paying for. The one shell that CAN be acted on is the
|
|
58560
|
+
* Electron app, and only through `installDesktopUpdate` (see
|
|
58561
|
+
* {@link DesktopAppStatusSchema} for what the node reads from the app first).
|
|
57946
58562
|
*/
|
|
57947
58563
|
var WrapperIdentitySchema = object({
|
|
57948
58564
|
kind: _enum([
|
|
@@ -57990,6 +58606,49 @@ var ServerRollbackInfoSchema = object({
|
|
|
57990
58606
|
reason: string()
|
|
57991
58607
|
});
|
|
57992
58608
|
/**
|
|
58609
|
+
* What the native desktop app (Electron) that hosts this node says about
|
|
58610
|
+
* itself, read from `<dataDir>/server-root/desktop-app-state.json` (ADR-0971).
|
|
58611
|
+
* Absent on the status = no desktop app, or it never wrote the file.
|
|
58612
|
+
*
|
|
58613
|
+
* `stale` is the honest answer to an app that stopped writing (closed, hung,
|
|
58614
|
+
* crashed): the update facts are then the LAST ones known, not current ones,
|
|
58615
|
+
* and a consumer must not read `update.available: false` from a stale record
|
|
58616
|
+
* as "no update". `stateAtMs` is when the app last wrote.
|
|
58617
|
+
*/
|
|
58618
|
+
var DesktopAppStatusSchema = object({
|
|
58619
|
+
/** The Electron app's own version (not the node closure's). */
|
|
58620
|
+
version: string(),
|
|
58621
|
+
update: object({
|
|
58622
|
+
available: boolean(),
|
|
58623
|
+
/** The new build is downloaded and `installDesktopUpdate` can apply it. */
|
|
58624
|
+
downloaded: boolean(),
|
|
58625
|
+
version: string().nullable(),
|
|
58626
|
+
checking: boolean(),
|
|
58627
|
+
/** Why the app's last update check produced no answer; `null` when it did. */
|
|
58628
|
+
error: string().nullable()
|
|
58629
|
+
}),
|
|
58630
|
+
stateAtMs: number(),
|
|
58631
|
+
stale: boolean()
|
|
58632
|
+
});
|
|
58633
|
+
/**
|
|
58634
|
+
* Why `installDesktopUpdate` did nothing. Stable names a UI maps to a sentence:
|
|
58635
|
+
* - `not-electron` — this node does not run inside the desktop app.
|
|
58636
|
+
* - `no-app-state` — the app never published its state (or the file is unreadable).
|
|
58637
|
+
* - `app-state-stale` — the app stopped writing; it is probably not running.
|
|
58638
|
+
* - `not-downloaded` — no update is downloaded yet; nothing to install.
|
|
58639
|
+
*/
|
|
58640
|
+
var InstallDesktopUpdateReasonSchema = _enum([
|
|
58641
|
+
"not-electron",
|
|
58642
|
+
"no-app-state",
|
|
58643
|
+
"app-state-stale",
|
|
58644
|
+
"not-downloaded"
|
|
58645
|
+
]);
|
|
58646
|
+
var InstallDesktopUpdateResultSchema = object({
|
|
58647
|
+
accepted: boolean(),
|
|
58648
|
+
/** Non-null exactly when `accepted` is false. */
|
|
58649
|
+
reason: InstallDesktopUpdateReasonSchema.nullable()
|
|
58650
|
+
});
|
|
58651
|
+
/**
|
|
57993
58652
|
* The runner boot check (D877): the runners this node PLANNED (hub: one per
|
|
57994
58653
|
* `resolveRunnerId` over its forked addons; agent: the runners its process
|
|
57995
58654
|
* service holds, because only the hub plans) against the states the process
|
|
@@ -58093,7 +58752,12 @@ var ServerPackageStatusSchema = object({
|
|
|
58093
58752
|
* on a provider that predates it. Absent is "not checked yet", never "all
|
|
58094
58753
|
* well": a consumer that needs the answer treats it as pending.
|
|
58095
58754
|
*/
|
|
58096
|
-
runners: RunnerBootCheckSchema.optional()
|
|
58755
|
+
runners: RunnerBootCheckSchema.optional(),
|
|
58756
|
+
/**
|
|
58757
|
+
* The desktop app hosting this node (see {@link DesktopAppStatusSchema}).
|
|
58758
|
+
* Optional for version skew AND because most nodes have no desktop app.
|
|
58759
|
+
*/
|
|
58760
|
+
desktopApp: DesktopAppStatusSchema.optional()
|
|
58097
58761
|
});
|
|
58098
58762
|
var ServerUpdateCheckResultSchema = object({
|
|
58099
58763
|
packageName: string(),
|
|
@@ -58176,6 +58840,12 @@ var ServerUpdateActionResultSchema = object({
|
|
|
58176
58840
|
*/
|
|
58177
58841
|
blockers: array(UpdateBlockerSchema).optional()
|
|
58178
58842
|
});
|
|
58843
|
+
var LaunchOverridesSchema = object({
|
|
58844
|
+
/** The persisted heap ceiling in MB, or `null` when none is set. */
|
|
58845
|
+
heapMb: number().int().nullable() });
|
|
58846
|
+
var SetLaunchOverridesResultSchema = LaunchOverridesSchema.extend({
|
|
58847
|
+
/** Always true: the override is read at launch, not by the running process. */
|
|
58848
|
+
restartRequired: boolean() });
|
|
58179
58849
|
method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
|
|
58180
58850
|
kind: "mutation",
|
|
58181
58851
|
auth: "admin"
|
|
@@ -58192,12 +58862,18 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
|
|
|
58192
58862
|
kind: "mutation",
|
|
58193
58863
|
auth: "admin",
|
|
58194
58864
|
timeoutMs: 16 * 6e4
|
|
58865
|
+
}), method(_void(), InstallDesktopUpdateResultSchema, {
|
|
58866
|
+
kind: "mutation",
|
|
58867
|
+
auth: "admin"
|
|
58195
58868
|
}), method(_void(), ServerUpdateActionResultSchema, {
|
|
58196
58869
|
kind: "mutation",
|
|
58197
58870
|
auth: "admin"
|
|
58198
58871
|
}), method(_void(), ServerUpdateActionResultSchema, {
|
|
58199
58872
|
kind: "mutation",
|
|
58200
58873
|
auth: "admin"
|
|
58874
|
+
}), method(_void(), LaunchOverridesSchema, { auth: "admin" }), method(object({ heapMb: number().int().min(512).max(65536).nullable() }), SetLaunchOverridesResultSchema, {
|
|
58875
|
+
kind: "mutation",
|
|
58876
|
+
auth: "admin"
|
|
58201
58877
|
});
|
|
58202
58878
|
/**
|
|
58203
58879
|
* `smtp-provider` — pluggable email delivery surface.
|
|
@@ -61339,13 +62015,19 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
|
61339
62015
|
}), method(ActionIdInput, _void(), {
|
|
61340
62016
|
kind: "mutation",
|
|
61341
62017
|
auth: "admin"
|
|
61342
|
-
}), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({
|
|
62018
|
+
}), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({
|
|
62019
|
+
action: ActionEntrySchema,
|
|
62020
|
+
warnings: array(UnitWarningSchema).optional()
|
|
62021
|
+
}), {
|
|
61343
62022
|
kind: "mutation",
|
|
61344
62023
|
auth: "admin"
|
|
61345
62024
|
}), method(object({
|
|
61346
62025
|
actionId: string().min(1),
|
|
61347
62026
|
action: ActionInputSchema.partial()
|
|
61348
|
-
}), object({
|
|
62027
|
+
}), object({
|
|
62028
|
+
action: ActionEntrySchema,
|
|
62029
|
+
warnings: array(UnitWarningSchema).optional()
|
|
62030
|
+
}), {
|
|
61349
62031
|
kind: "mutation",
|
|
61350
62032
|
auth: "admin"
|
|
61351
62033
|
}), method(ActionIdInput, _void(), {
|
|
@@ -61797,6 +62479,510 @@ onBrightnessChanged: { data: object({
|
|
|
61797
62479
|
minIntervalMs: 0
|
|
61798
62480
|
} } }
|
|
61799
62481
|
};
|
|
62482
|
+
/**
|
|
62483
|
+
* The role vocabulary of a remote (D963): what a button MEANS, independent of
|
|
62484
|
+
* how it is wired. A layout template places buttons by role; a device binds a
|
|
62485
|
+
* role to an action; an exporter asks for a role by name. Roles are shared so
|
|
62486
|
+
* one layout fits every appliance and one voice command fits every binding.
|
|
62487
|
+
*
|
|
62488
|
+
* A role outside this list is `custom:<key>` — the operator's own button (a
|
|
62489
|
+
* SmartIR source, a channel preset). The key is a lowercase slug.
|
|
62490
|
+
*/
|
|
62491
|
+
var REMOTE_KNOWN_ROLES = [
|
|
62492
|
+
"power",
|
|
62493
|
+
"power_on",
|
|
62494
|
+
"power_off",
|
|
62495
|
+
"volume_up",
|
|
62496
|
+
"volume_down",
|
|
62497
|
+
"mute",
|
|
62498
|
+
"channel_up",
|
|
62499
|
+
"channel_down",
|
|
62500
|
+
"up",
|
|
62501
|
+
"down",
|
|
62502
|
+
"left",
|
|
62503
|
+
"right",
|
|
62504
|
+
"ok",
|
|
62505
|
+
"back",
|
|
62506
|
+
"home",
|
|
62507
|
+
"menu",
|
|
62508
|
+
"info",
|
|
62509
|
+
"guide",
|
|
62510
|
+
"exit",
|
|
62511
|
+
"input",
|
|
62512
|
+
"play",
|
|
62513
|
+
"pause",
|
|
62514
|
+
"play_pause",
|
|
62515
|
+
"stop",
|
|
62516
|
+
"next",
|
|
62517
|
+
"previous",
|
|
62518
|
+
"fast_forward",
|
|
62519
|
+
"rewind",
|
|
62520
|
+
"digit_0",
|
|
62521
|
+
"digit_1",
|
|
62522
|
+
"digit_2",
|
|
62523
|
+
"digit_3",
|
|
62524
|
+
"digit_4",
|
|
62525
|
+
"digit_5",
|
|
62526
|
+
"digit_6",
|
|
62527
|
+
"digit_7",
|
|
62528
|
+
"digit_8",
|
|
62529
|
+
"digit_9"
|
|
62530
|
+
];
|
|
62531
|
+
var CUSTOM_KEY_RE = /^[a-z0-9][a-z0-9_]{0,47}$/;
|
|
62532
|
+
var KNOWN_ROLES = new Set(REMOTE_KNOWN_ROLES);
|
|
62533
|
+
function isRemoteKnownRole(value) {
|
|
62534
|
+
return KNOWN_ROLES.has(value);
|
|
62535
|
+
}
|
|
62536
|
+
function isRemoteCustomKey(key) {
|
|
62537
|
+
return CUSTOM_KEY_RE.test(key);
|
|
62538
|
+
}
|
|
62539
|
+
/** The key of a `custom:<key>` role, or null for any other string. */
|
|
62540
|
+
function customKeyOfRole(role) {
|
|
62541
|
+
if (!role.startsWith("custom:")) return null;
|
|
62542
|
+
const key = role.slice(7);
|
|
62543
|
+
return isRemoteCustomKey(key) ? key : null;
|
|
62544
|
+
}
|
|
62545
|
+
function isRemoteRole(value) {
|
|
62546
|
+
return isRemoteKnownRole(value) || customKeyOfRole(value) !== null;
|
|
62547
|
+
}
|
|
62548
|
+
var RemoteRoleSchema = string().refine(isRemoteRole, { message: "a role is one of the known roles or custom:<key> (lowercase letters, digits, _)" });
|
|
62549
|
+
var RemoteBindingSchema = object({ action: AutomationCapStepSchema.extend({ deviceId: number().int().nonnegative() }) });
|
|
62550
|
+
var RemoteCustomButtonSchema = object({
|
|
62551
|
+
/** The slug after `custom:` in the role. */
|
|
62552
|
+
key: string().refine(isRemoteCustomKey, { message: "a lowercase slug" }),
|
|
62553
|
+
/** What the operator calls it, and what a voice command may name. */
|
|
62554
|
+
name: string().min(1).max(60)
|
|
62555
|
+
});
|
|
62556
|
+
var RemotePowerModeSchema = _enum(["toggle", "discrete"]);
|
|
62557
|
+
var RemoteConfigSchema = object({
|
|
62558
|
+
layoutTemplateId: string().min(1).nullable(),
|
|
62559
|
+
/**
|
|
62560
|
+
* The transmitter device new bindings and an import send through. A binding
|
|
62561
|
+
* names its own device, so one command may override it.
|
|
62562
|
+
*/
|
|
62563
|
+
defaultTransmitterId: number().int().nonnegative().nullable().default(null),
|
|
62564
|
+
/** Keyed by role. A role with no entry falls back to the native cap. */
|
|
62565
|
+
bindings: record(string(), RemoteBindingSchema),
|
|
62566
|
+
customButtons: array(RemoteCustomButtonSchema).max(100),
|
|
62567
|
+
power: object({ mode: RemotePowerModeSchema })
|
|
62568
|
+
}).superRefine((config, ctx) => {
|
|
62569
|
+
const roles = Object.keys(config.bindings);
|
|
62570
|
+
if (roles.length > 200) ctx.addIssue({
|
|
62571
|
+
code: "custom",
|
|
62572
|
+
message: "too many bindings",
|
|
62573
|
+
path: ["bindings"]
|
|
62574
|
+
});
|
|
62575
|
+
for (const role of roles) if (!isRemoteRole(role)) ctx.addIssue({
|
|
62576
|
+
code: "custom",
|
|
62577
|
+
message: `"${role}" is not a role`,
|
|
62578
|
+
path: ["bindings", role]
|
|
62579
|
+
});
|
|
62580
|
+
const keys = /* @__PURE__ */ new Set();
|
|
62581
|
+
config.customButtons.forEach((button, index) => {
|
|
62582
|
+
if (keys.has(button.key)) ctx.addIssue({
|
|
62583
|
+
code: "custom",
|
|
62584
|
+
message: `custom button "${button.key}" is listed twice`,
|
|
62585
|
+
path: [
|
|
62586
|
+
"customButtons",
|
|
62587
|
+
index,
|
|
62588
|
+
"key"
|
|
62589
|
+
]
|
|
62590
|
+
});
|
|
62591
|
+
keys.add(button.key);
|
|
62592
|
+
});
|
|
62593
|
+
for (const role of roles) {
|
|
62594
|
+
const key = customKeyOfRole(role);
|
|
62595
|
+
if (key !== null && !keys.has(key)) ctx.addIssue({
|
|
62596
|
+
code: "custom",
|
|
62597
|
+
message: `${role} is bound but has no entry in customButtons`,
|
|
62598
|
+
path: ["bindings", role]
|
|
62599
|
+
});
|
|
62600
|
+
}
|
|
62601
|
+
});
|
|
62602
|
+
object({
|
|
62603
|
+
power: object({
|
|
62604
|
+
state: _enum(["on", "off"]),
|
|
62605
|
+
at: number()
|
|
62606
|
+
}).nullable(),
|
|
62607
|
+
muted: object({
|
|
62608
|
+
value: boolean(),
|
|
62609
|
+
at: number()
|
|
62610
|
+
}).nullable()
|
|
62611
|
+
});
|
|
62612
|
+
var RemoteButtonShapeSchema = _enum([
|
|
62613
|
+
"rect",
|
|
62614
|
+
"rounded",
|
|
62615
|
+
"round",
|
|
62616
|
+
"pill"
|
|
62617
|
+
]);
|
|
62618
|
+
var RemoteButtonToneSchema = _enum([
|
|
62619
|
+
"neutral",
|
|
62620
|
+
"accent",
|
|
62621
|
+
"danger",
|
|
62622
|
+
"red",
|
|
62623
|
+
"green",
|
|
62624
|
+
"yellow",
|
|
62625
|
+
"blue"
|
|
62626
|
+
]);
|
|
62627
|
+
var RemoteGroupFrameSchema = _enum([
|
|
62628
|
+
"none",
|
|
62629
|
+
"panel",
|
|
62630
|
+
"divider",
|
|
62631
|
+
"dpad",
|
|
62632
|
+
"rocker-v",
|
|
62633
|
+
"rocker-h",
|
|
62634
|
+
"keypad"
|
|
62635
|
+
]);
|
|
62636
|
+
var RemoteLayoutButtonSchema = object({
|
|
62637
|
+
id: string().min(1).max(40),
|
|
62638
|
+
/** 0-based grid position. */
|
|
62639
|
+
row: number().int().min(0).max(24),
|
|
62640
|
+
col: number().int().min(0).max(24),
|
|
62641
|
+
rowSpan: number().int().min(1).max(24),
|
|
62642
|
+
colSpan: number().int().min(1).max(24),
|
|
62643
|
+
icon: string().min(1).max(60).optional(),
|
|
62644
|
+
label: string().min(1).max(40).optional(),
|
|
62645
|
+
role: RemoteRoleSchema,
|
|
62646
|
+
/** Absent = the plain rendering every layout had before shapes existed. */
|
|
62647
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
62648
|
+
tone: RemoteButtonToneSchema.optional(),
|
|
62649
|
+
/** The group this button belongs to; it must lie inside that group's rectangle. */
|
|
62650
|
+
groupId: string().min(1).max(40).optional()
|
|
62651
|
+
});
|
|
62652
|
+
/**
|
|
62653
|
+
* A rectangle of the grid that moves as ONE thing and is drawn with a frame: a
|
|
62654
|
+
* titled panel, a section divider, a D-pad ring. `componentId` names the palette
|
|
62655
|
+
* entry it came from (informational: the layout is self-contained).
|
|
62656
|
+
*/
|
|
62657
|
+
var RemoteLayoutGroupSchema = object({
|
|
62658
|
+
id: string().min(1).max(40),
|
|
62659
|
+
componentId: string().min(1).max(80),
|
|
62660
|
+
frame: RemoteGroupFrameSchema,
|
|
62661
|
+
row: number().int().min(0).max(24),
|
|
62662
|
+
col: number().int().min(0).max(24),
|
|
62663
|
+
rowSpan: number().int().min(1).max(24),
|
|
62664
|
+
colSpan: number().int().min(1).max(24),
|
|
62665
|
+
title: string().min(1).max(40).optional()
|
|
62666
|
+
});
|
|
62667
|
+
function rectsOverlap(a, b) {
|
|
62668
|
+
return a.row < b.row + b.rowSpan && b.row < a.row + a.rowSpan && a.col < b.col + b.colSpan && b.col < a.col + a.colSpan;
|
|
62669
|
+
}
|
|
62670
|
+
function rectContains(outer, inner) {
|
|
62671
|
+
return inner.row >= outer.row && inner.col >= outer.col && inner.row + inner.rowSpan <= outer.row + outer.rowSpan && inner.col + inner.colSpan <= outer.col + outer.colSpan;
|
|
62672
|
+
}
|
|
62673
|
+
/**
|
|
62674
|
+
* The group rules of a layout: inside the grid, no two groups overlap, a
|
|
62675
|
+
* member lies inside its group, a divider holds nothing, and a button that is
|
|
62676
|
+
* not a member never sits inside someone else's rectangle (a group moves as
|
|
62677
|
+
* one thing, so a stray key under it would be left behind).
|
|
62678
|
+
*/
|
|
62679
|
+
function refineGroups(grid, ctx) {
|
|
62680
|
+
const groups = grid.groups ?? [];
|
|
62681
|
+
const byId = /* @__PURE__ */ new Map();
|
|
62682
|
+
const fail = (message, path) => {
|
|
62683
|
+
ctx.addIssue({
|
|
62684
|
+
code: "custom",
|
|
62685
|
+
message,
|
|
62686
|
+
path
|
|
62687
|
+
});
|
|
62688
|
+
};
|
|
62689
|
+
groups.forEach((group, index) => {
|
|
62690
|
+
if (byId.has(group.id)) fail(`group id "${group.id}" is used twice`, [
|
|
62691
|
+
"groups",
|
|
62692
|
+
index,
|
|
62693
|
+
"id"
|
|
62694
|
+
]);
|
|
62695
|
+
byId.set(group.id, group);
|
|
62696
|
+
if (group.row + group.rowSpan > grid.rows || group.col + group.colSpan > grid.columns) fail(`group "${group.id}" leaves the grid`, ["groups", index]);
|
|
62697
|
+
for (let other = 0; other < index; other += 1) {
|
|
62698
|
+
const earlier = groups[other];
|
|
62699
|
+
if (earlier !== void 0 && rectsOverlap(earlier, group)) {
|
|
62700
|
+
fail(`groups "${earlier.id}" and "${group.id}" overlap`, ["groups", index]);
|
|
62701
|
+
return;
|
|
62702
|
+
}
|
|
62703
|
+
}
|
|
62704
|
+
});
|
|
62705
|
+
grid.buttons.forEach((button, index) => {
|
|
62706
|
+
if (button.groupId !== void 0) {
|
|
62707
|
+
const group = byId.get(button.groupId);
|
|
62708
|
+
if (group === void 0) {
|
|
62709
|
+
fail(`button "${button.id}" names the unknown group "${button.groupId}"`, [
|
|
62710
|
+
"buttons",
|
|
62711
|
+
index,
|
|
62712
|
+
"groupId"
|
|
62713
|
+
]);
|
|
62714
|
+
return;
|
|
62715
|
+
}
|
|
62716
|
+
if (group.frame === "divider") fail(`a divider group ("${group.id}") holds no buttons`, [
|
|
62717
|
+
"buttons",
|
|
62718
|
+
index,
|
|
62719
|
+
"groupId"
|
|
62720
|
+
]);
|
|
62721
|
+
if (!rectContains(group, button)) fail(`button "${button.id}" lies outside its group "${group.id}"`, ["buttons", index]);
|
|
62722
|
+
return;
|
|
62723
|
+
}
|
|
62724
|
+
const foreign = groups.find((group) => rectsOverlap(group, button));
|
|
62725
|
+
if (foreign !== void 0) fail(`button "${button.id}" lies inside group "${foreign.id}" but is not a member`, ["buttons", index]);
|
|
62726
|
+
});
|
|
62727
|
+
}
|
|
62728
|
+
object({
|
|
62729
|
+
columns: number().int().min(1).max(24),
|
|
62730
|
+
rows: number().int().min(1).max(24),
|
|
62731
|
+
buttons: array(RemoteLayoutButtonSchema).max(200),
|
|
62732
|
+
groups: array(RemoteLayoutGroupSchema).max(50).optional()
|
|
62733
|
+
}).superRefine((grid, ctx) => {
|
|
62734
|
+
const ids = /* @__PURE__ */ new Set();
|
|
62735
|
+
const taken = /* @__PURE__ */ new Map();
|
|
62736
|
+
grid.buttons.forEach((button, index) => {
|
|
62737
|
+
if (ids.has(button.id)) ctx.addIssue({
|
|
62738
|
+
code: "custom",
|
|
62739
|
+
message: `button id "${button.id}" is used twice`,
|
|
62740
|
+
path: [
|
|
62741
|
+
"buttons",
|
|
62742
|
+
index,
|
|
62743
|
+
"id"
|
|
62744
|
+
]
|
|
62745
|
+
});
|
|
62746
|
+
ids.add(button.id);
|
|
62747
|
+
if (button.row + button.rowSpan > grid.rows || button.col + button.colSpan > grid.columns) {
|
|
62748
|
+
ctx.addIssue({
|
|
62749
|
+
code: "custom",
|
|
62750
|
+
message: `button "${button.id}" leaves the grid`,
|
|
62751
|
+
path: ["buttons", index]
|
|
62752
|
+
});
|
|
62753
|
+
return;
|
|
62754
|
+
}
|
|
62755
|
+
for (let r = button.row; r < button.row + button.rowSpan; r += 1) for (let c = button.col; c < button.col + button.colSpan; c += 1) {
|
|
62756
|
+
const cell = `${r}:${c}`;
|
|
62757
|
+
const other = taken.get(cell);
|
|
62758
|
+
if (other !== void 0) {
|
|
62759
|
+
ctx.addIssue({
|
|
62760
|
+
code: "custom",
|
|
62761
|
+
message: `buttons "${other}" and "${button.id}" overlap`,
|
|
62762
|
+
path: ["buttons", index]
|
|
62763
|
+
});
|
|
62764
|
+
return;
|
|
62765
|
+
}
|
|
62766
|
+
taken.set(cell, button.id);
|
|
62767
|
+
}
|
|
62768
|
+
});
|
|
62769
|
+
refineGroups(grid, ctx);
|
|
62770
|
+
});
|
|
62771
|
+
var RemoteBoundCommandSchema = object({
|
|
62772
|
+
role: RemoteRoleSchema,
|
|
62773
|
+
/** The operator's name for a custom button; absent for a known role. */
|
|
62774
|
+
label: string().optional(),
|
|
62775
|
+
custom: boolean()
|
|
62776
|
+
});
|
|
62777
|
+
var RemoteCommandListSchema = object({
|
|
62778
|
+
layoutTemplateId: string().nullable(),
|
|
62779
|
+
power: object({ mode: RemotePowerModeSchema }),
|
|
62780
|
+
/** Only the roles that are BOUND — the ones a press can reach. */
|
|
62781
|
+
commands: array(RemoteBoundCommandSchema)
|
|
62782
|
+
});
|
|
62783
|
+
var RemoteRefusalReasonSchema = _enum([
|
|
62784
|
+
"no-remote",
|
|
62785
|
+
"unbound-role",
|
|
62786
|
+
"action-failed"
|
|
62787
|
+
]);
|
|
62788
|
+
var RemoteSendResultSchema = discriminatedUnion("status", [
|
|
62789
|
+
object({
|
|
62790
|
+
status: literal("sent"),
|
|
62791
|
+
/** How many times the action ran. */
|
|
62792
|
+
sends: number().int().min(1)
|
|
62793
|
+
}),
|
|
62794
|
+
object({
|
|
62795
|
+
status: literal("skipped"),
|
|
62796
|
+
reason: literal("already-in-state")
|
|
62797
|
+
}),
|
|
62798
|
+
object({
|
|
62799
|
+
status: literal("refused"),
|
|
62800
|
+
reason: RemoteRefusalReasonSchema,
|
|
62801
|
+
detail: string().optional()
|
|
62802
|
+
})
|
|
62803
|
+
]);
|
|
62804
|
+
var RemoteMediaCommandSchema = _enum([
|
|
62805
|
+
"play",
|
|
62806
|
+
"pause",
|
|
62807
|
+
"stop",
|
|
62808
|
+
"next",
|
|
62809
|
+
"previous",
|
|
62810
|
+
"setMute",
|
|
62811
|
+
"selectSource"
|
|
62812
|
+
]);
|
|
62813
|
+
/** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
|
|
62814
|
+
var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
|
|
62815
|
+
var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status: literal("saved") }), object({
|
|
62816
|
+
status: literal("refused"),
|
|
62817
|
+
reason: _enum(["unknown-template", "not-a-media-player"]),
|
|
62818
|
+
detail: string()
|
|
62819
|
+
})]);
|
|
62820
|
+
var deviceInput = object({ deviceId: number().int().nonnegative() });
|
|
62821
|
+
DeviceType.MediaPlayer;
|
|
62822
|
+
method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
|
|
62823
|
+
kind: "mutation",
|
|
62824
|
+
auth: "admin"
|
|
62825
|
+
}), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
|
|
62826
|
+
role: RemoteRoleSchema,
|
|
62827
|
+
/** Sends in a row (volume steps). */
|
|
62828
|
+
repeat: number().int().min(1).max(50).default(1)
|
|
62829
|
+
}), RemoteSendResultSchema, {
|
|
62830
|
+
kind: "mutation",
|
|
62831
|
+
auth: "protected"
|
|
62832
|
+
}), method(deviceInput.extend({
|
|
62833
|
+
command: RemoteMediaCommandSchema,
|
|
62834
|
+
muted: boolean().optional(),
|
|
62835
|
+
source: string().optional(),
|
|
62836
|
+
repeat: number().int().min(1).max(50).default(1)
|
|
62837
|
+
}), RemoteMediaCommandResultSchema, {
|
|
62838
|
+
kind: "mutation",
|
|
62839
|
+
auth: "protected"
|
|
62840
|
+
});
|
|
62841
|
+
var TemplateKindSchema = _enum(["remote"]);
|
|
62842
|
+
/**
|
|
62843
|
+
* template — the generic store of reusable, named operator data (D963). A
|
|
62844
|
+
* record is `{ id, kind, name, payload }`; the kinds (today only `remote`, the
|
|
62845
|
+
* button grid of a remote) and the schema each payload must satisfy are
|
|
62846
|
+
* declared by the system in `template-kinds.ts`. Nothing here is specific to a
|
|
62847
|
+
* kind: the engine validates a payload against its kind's schema and, on a
|
|
62848
|
+
* delete, asks the kind's owner whether anything still points at the record.
|
|
62849
|
+
*
|
|
62850
|
+
* Every refusal is a typed result — a UI says "unknown kind" or names the
|
|
62851
|
+
* devices that still use a template, without parsing text.
|
|
62852
|
+
*/
|
|
62853
|
+
var TemplateRecordSchema = object({
|
|
62854
|
+
id: string().min(1),
|
|
62855
|
+
kind: TemplateKindSchema,
|
|
62856
|
+
name: string().min(1).max(120),
|
|
62857
|
+
/** Shaped by the kind; `checkTemplatePayload(kind, payload)` reads it. */
|
|
62858
|
+
payload: unknown(),
|
|
62859
|
+
createdAt: number(),
|
|
62860
|
+
updatedAt: number()
|
|
62861
|
+
});
|
|
62862
|
+
var TemplateWriteResultSchema = discriminatedUnion("status", [object({
|
|
62863
|
+
status: literal("saved"),
|
|
62864
|
+
template: TemplateRecordSchema
|
|
62865
|
+
}), object({
|
|
62866
|
+
status: literal("refused"),
|
|
62867
|
+
reason: _enum(["invalid-payload", "not-found"]),
|
|
62868
|
+
detail: string()
|
|
62869
|
+
})]);
|
|
62870
|
+
/** A device still pointing at a template. */
|
|
62871
|
+
var TemplateUserSchema = object({
|
|
62872
|
+
deviceId: number().int().nonnegative(),
|
|
62873
|
+
name: string()
|
|
62874
|
+
});
|
|
62875
|
+
var TemplateDeleteResultSchema = discriminatedUnion("status", [
|
|
62876
|
+
object({ status: literal("deleted") }),
|
|
62877
|
+
object({ status: literal("not-found") }),
|
|
62878
|
+
object({
|
|
62879
|
+
status: literal("in-use"),
|
|
62880
|
+
devices: array(TemplateUserSchema).min(1)
|
|
62881
|
+
})
|
|
62882
|
+
]);
|
|
62883
|
+
method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSchema), { auth: "protected" }), method(object({ id: string().min(1) }), TemplateRecordSchema.nullable(), { auth: "protected" }), method(object({
|
|
62884
|
+
kind: TemplateKindSchema,
|
|
62885
|
+
name: string().min(1).max(120),
|
|
62886
|
+
payload: unknown()
|
|
62887
|
+
}), TemplateWriteResultSchema, {
|
|
62888
|
+
kind: "mutation",
|
|
62889
|
+
auth: "admin"
|
|
62890
|
+
}), method(object({
|
|
62891
|
+
id: string().min(1),
|
|
62892
|
+
name: string().min(1).max(120).optional(),
|
|
62893
|
+
payload: unknown().optional()
|
|
62894
|
+
}), TemplateWriteResultSchema, {
|
|
62895
|
+
kind: "mutation",
|
|
62896
|
+
auth: "admin"
|
|
62897
|
+
}), method(object({ id: string().min(1) }), TemplateDeleteResultSchema, {
|
|
62898
|
+
kind: "mutation",
|
|
62899
|
+
auth: "admin"
|
|
62900
|
+
});
|
|
62901
|
+
var RemoteComponentCategorySchema = _enum([
|
|
62902
|
+
"power",
|
|
62903
|
+
"navigation",
|
|
62904
|
+
"volume",
|
|
62905
|
+
"numbers",
|
|
62906
|
+
"media",
|
|
62907
|
+
"colour",
|
|
62908
|
+
"apps",
|
|
62909
|
+
"layout"
|
|
62910
|
+
]);
|
|
62911
|
+
/** `<namespace>:<name>`, both lowercase slugs — `core:dpad`, `<addonId>:<name>`. */
|
|
62912
|
+
var COMPONENT_ID_RE = /^[a-z0-9][a-z0-9_.-]*:[a-z0-9][a-z0-9_-]*$/;
|
|
62913
|
+
var RemoteComponentButtonSchema = object({
|
|
62914
|
+
/** Joined to the instance id when the component is inserted. */
|
|
62915
|
+
idSuffix: string().regex(/^[a-z0-9][a-z0-9_-]{0,19}$/, { message: "a short lowercase slug" }),
|
|
62916
|
+
row: number().int().min(0).max(24),
|
|
62917
|
+
col: number().int().min(0).max(24),
|
|
62918
|
+
rowSpan: number().int().min(1).max(24),
|
|
62919
|
+
colSpan: number().int().min(1).max(24),
|
|
62920
|
+
role: RemoteRoleSchema,
|
|
62921
|
+
icon: string().min(1).max(60).optional(),
|
|
62922
|
+
label: string().min(1).max(40).optional(),
|
|
62923
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
62924
|
+
tone: RemoteButtonToneSchema.optional()
|
|
62925
|
+
});
|
|
62926
|
+
var RemoteLayoutComponentSchema = object({
|
|
62927
|
+
id: string().max(80).regex(COMPONENT_ID_RE, { message: "<namespace>:<name>, lowercase" }),
|
|
62928
|
+
name: string().min(1).max(60),
|
|
62929
|
+
nameI18n: object({
|
|
62930
|
+
en: string().min(1).max(60),
|
|
62931
|
+
it: string().min(1).max(60)
|
|
62932
|
+
}).optional(),
|
|
62933
|
+
/** A display icon key, resolved by the UI like a button icon. */
|
|
62934
|
+
icon: string().min(1).max(60),
|
|
62935
|
+
category: RemoteComponentCategorySchema,
|
|
62936
|
+
size: object({
|
|
62937
|
+
rows: number().int().min(1).max(24),
|
|
62938
|
+
cols: number().int().min(1).max(24)
|
|
62939
|
+
}),
|
|
62940
|
+
/** The frame drawn around the whole component, or null for loose buttons. */
|
|
62941
|
+
group: object({
|
|
62942
|
+
frame: RemoteGroupFrameSchema,
|
|
62943
|
+
title: string().min(1).max(40).optional()
|
|
62944
|
+
}).nullable(),
|
|
62945
|
+
buttons: array(RemoteComponentButtonSchema).max(40)
|
|
62946
|
+
}).superRefine((component, ctx) => {
|
|
62947
|
+
const suffixes = /* @__PURE__ */ new Set();
|
|
62948
|
+
component.buttons.forEach((button, index) => {
|
|
62949
|
+
if (suffixes.has(button.idSuffix)) ctx.addIssue({
|
|
62950
|
+
code: "custom",
|
|
62951
|
+
message: `button "${button.idSuffix}" is listed twice`,
|
|
62952
|
+
path: [
|
|
62953
|
+
"buttons",
|
|
62954
|
+
index,
|
|
62955
|
+
"idSuffix"
|
|
62956
|
+
]
|
|
62957
|
+
});
|
|
62958
|
+
suffixes.add(button.idSuffix);
|
|
62959
|
+
if (button.row + button.rowSpan > component.size.rows || button.col + button.colSpan > component.size.cols) {
|
|
62960
|
+
ctx.addIssue({
|
|
62961
|
+
code: "custom",
|
|
62962
|
+
message: `button "${button.idSuffix}" leaves the component`,
|
|
62963
|
+
path: ["buttons", index]
|
|
62964
|
+
});
|
|
62965
|
+
return;
|
|
62966
|
+
}
|
|
62967
|
+
for (let other = 0; other < index; other += 1) {
|
|
62968
|
+
const earlier = component.buttons[other];
|
|
62969
|
+
if (earlier !== void 0 && rectsOverlap(earlier, button)) {
|
|
62970
|
+
ctx.addIssue({
|
|
62971
|
+
code: "custom",
|
|
62972
|
+
message: `buttons "${earlier.idSuffix}" and "${button.idSuffix}" overlap`,
|
|
62973
|
+
path: ["buttons", index]
|
|
62974
|
+
});
|
|
62975
|
+
return;
|
|
62976
|
+
}
|
|
62977
|
+
}
|
|
62978
|
+
});
|
|
62979
|
+
if (component.group?.frame === "divider" && component.buttons.length > 0) ctx.addIssue({
|
|
62980
|
+
code: "custom",
|
|
62981
|
+
message: "a divider component holds no buttons",
|
|
62982
|
+
path: ["buttons"]
|
|
62983
|
+
});
|
|
62984
|
+
});
|
|
62985
|
+
method(object({}), array(RemoteLayoutComponentSchema), { auth: "protected" });
|
|
61800
62986
|
DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
61801
62987
|
kind: "mutation",
|
|
61802
62988
|
auth: "protected"
|
|
@@ -62852,14 +64038,13 @@ reset: method(object({
|
|
|
62852
64038
|
auth: "admin"
|
|
62853
64039
|
}) },
|
|
62854
64040
|
/**
|
|
62855
|
-
* A
|
|
62856
|
-
*
|
|
62857
|
-
*
|
|
62858
|
-
* commands (`forward`) arrive in slice 3 (D663).
|
|
64041
|
+
* A consumable ADDED to an existing device (a graft) refuses `reset` by
|
|
64042
|
+
* name: only a composed device of a NEW type forwards an item's reset to a
|
|
64043
|
+
* source button, and marks that item `resettable` (D967).
|
|
62859
64044
|
*/
|
|
62860
64045
|
composedMethods: { reset: {
|
|
62861
64046
|
kind: "refuse",
|
|
62862
|
-
reason: "a
|
|
64047
|
+
reason: "a consumable added to an existing device is not resettable — only a new composed device forwards a reset"
|
|
62863
64048
|
} },
|
|
62864
64049
|
status: {
|
|
62865
64050
|
schema: ConsumablesStatusSchema,
|
|
@@ -63475,7 +64660,7 @@ var DishwasherStatusSchema = object({
|
|
|
63475
64660
|
/** Ms epoch of the last value received from the appliance; 0 = never. */
|
|
63476
64661
|
lastFetchedAt: number()
|
|
63477
64662
|
});
|
|
63478
|
-
var DeviceIdSchema = number().int().nonnegative();
|
|
64663
|
+
var DeviceIdSchema$1 = number().int().nonnegative();
|
|
63479
64664
|
/** A value a program option or setting can take. */
|
|
63480
64665
|
var DishwasherValueSchema = union([
|
|
63481
64666
|
boolean(),
|
|
@@ -63550,77 +64735,77 @@ var dishwasherCapability = {
|
|
|
63550
64735
|
* option feature keys; each is checked against the program's constraints.
|
|
63551
64736
|
*/
|
|
63552
64737
|
start: method(object({
|
|
63553
|
-
deviceId: DeviceIdSchema,
|
|
64738
|
+
deviceId: DeviceIdSchema$1,
|
|
63554
64739
|
program: string().min(1).optional(),
|
|
63555
64740
|
options: record(string(), DishwasherValueSchema).optional()
|
|
63556
64741
|
}), _void(), {
|
|
63557
64742
|
kind: "mutation",
|
|
63558
64743
|
auth: "protected"
|
|
63559
64744
|
}),
|
|
63560
|
-
pause: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64745
|
+
pause: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63561
64746
|
kind: "mutation",
|
|
63562
64747
|
auth: "protected"
|
|
63563
64748
|
}),
|
|
63564
|
-
resume: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64749
|
+
resume: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63565
64750
|
kind: "mutation",
|
|
63566
64751
|
auth: "protected"
|
|
63567
64752
|
}),
|
|
63568
|
-
stop: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64753
|
+
stop: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63569
64754
|
kind: "mutation",
|
|
63570
64755
|
auth: "protected"
|
|
63571
64756
|
}),
|
|
63572
64757
|
/** Select (not start) a program; refused when the appliance does not offer it. */
|
|
63573
64758
|
setProgram: method(object({
|
|
63574
|
-
deviceId: DeviceIdSchema,
|
|
64759
|
+
deviceId: DeviceIdSchema$1,
|
|
63575
64760
|
program: string().min(1)
|
|
63576
64761
|
}), _void(), {
|
|
63577
64762
|
kind: "mutation",
|
|
63578
64763
|
auth: "protected"
|
|
63579
64764
|
}),
|
|
63580
64765
|
setPower: method(object({
|
|
63581
|
-
deviceId: DeviceIdSchema,
|
|
64766
|
+
deviceId: DeviceIdSchema$1,
|
|
63582
64767
|
on: boolean()
|
|
63583
64768
|
}), _void(), {
|
|
63584
64769
|
kind: "mutation",
|
|
63585
64770
|
auth: "protected"
|
|
63586
64771
|
}),
|
|
63587
64772
|
setHalfLoad: method(object({
|
|
63588
|
-
deviceId: DeviceIdSchema,
|
|
64773
|
+
deviceId: DeviceIdSchema$1,
|
|
63589
64774
|
on: boolean()
|
|
63590
64775
|
}), _void(), {
|
|
63591
64776
|
kind: "mutation",
|
|
63592
64777
|
auth: "protected"
|
|
63593
64778
|
}),
|
|
63594
64779
|
setHygienePlus: method(object({
|
|
63595
|
-
deviceId: DeviceIdSchema,
|
|
64780
|
+
deviceId: DeviceIdSchema$1,
|
|
63596
64781
|
on: boolean()
|
|
63597
64782
|
}), _void(), {
|
|
63598
64783
|
kind: "mutation",
|
|
63599
64784
|
auth: "protected"
|
|
63600
64785
|
}),
|
|
63601
64786
|
setExtraDry: method(object({
|
|
63602
|
-
deviceId: DeviceIdSchema,
|
|
64787
|
+
deviceId: DeviceIdSchema$1,
|
|
63603
64788
|
on: boolean()
|
|
63604
64789
|
}), _void(), {
|
|
63605
64790
|
kind: "mutation",
|
|
63606
64791
|
auth: "protected"
|
|
63607
64792
|
}),
|
|
63608
64793
|
setIntensiveZone: method(object({
|
|
63609
|
-
deviceId: DeviceIdSchema,
|
|
64794
|
+
deviceId: DeviceIdSchema$1,
|
|
63610
64795
|
on: boolean()
|
|
63611
64796
|
}), _void(), {
|
|
63612
64797
|
kind: "mutation",
|
|
63613
64798
|
auth: "protected"
|
|
63614
64799
|
}),
|
|
63615
64800
|
setVarioSpeedPlus: method(object({
|
|
63616
|
-
deviceId: DeviceIdSchema,
|
|
64801
|
+
deviceId: DeviceIdSchema$1,
|
|
63617
64802
|
on: boolean()
|
|
63618
64803
|
}), _void(), {
|
|
63619
64804
|
kind: "mutation",
|
|
63620
64805
|
auth: "protected"
|
|
63621
64806
|
}),
|
|
63622
64807
|
setChildLock: method(object({
|
|
63623
|
-
deviceId: DeviceIdSchema,
|
|
64808
|
+
deviceId: DeviceIdSchema$1,
|
|
63624
64809
|
on: boolean()
|
|
63625
64810
|
}), _void(), {
|
|
63626
64811
|
kind: "mutation",
|
|
@@ -63632,7 +64817,7 @@ var dishwasherCapability = {
|
|
|
63632
64817
|
* its declared range / members.
|
|
63633
64818
|
*/
|
|
63634
64819
|
setOption: method(object({
|
|
63635
|
-
deviceId: DeviceIdSchema,
|
|
64820
|
+
deviceId: DeviceIdSchema$1,
|
|
63636
64821
|
key: string().min(1),
|
|
63637
64822
|
value: DishwasherValueSchema
|
|
63638
64823
|
}), _void(), {
|
|
@@ -63640,7 +64825,7 @@ var dishwasherCapability = {
|
|
|
63640
64825
|
auth: "protected"
|
|
63641
64826
|
}),
|
|
63642
64827
|
/** What this appliance can do, from its own description — the UI is derived from it. */
|
|
63643
|
-
getOptions: method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" })
|
|
64828
|
+
getOptions: method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" })
|
|
63644
64829
|
},
|
|
63645
64830
|
status: {
|
|
63646
64831
|
schema: DishwasherStatusSchema,
|
|
@@ -66072,9 +67257,35 @@ var MediaInfoSchema = object({
|
|
|
66072
67257
|
/** Optional cover-art / thumbnail URL. */
|
|
66073
67258
|
imageUrl: string().optional()
|
|
66074
67259
|
});
|
|
67260
|
+
/**
|
|
67261
|
+
* Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
|
|
67262
|
+
* the device said so. `assumed`: the driver has no feedback (infrared) and
|
|
67263
|
+
* tracks what it last SENT; it moves only after a send was accepted, and a
|
|
67264
|
+
* human at the real remote makes it wrong. A UI shows an assumed value as a
|
|
67265
|
+
* guess, never as a reading.
|
|
67266
|
+
*/
|
|
67267
|
+
var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
|
|
67268
|
+
/** The fields of a media-player status a driver can only guess. */
|
|
67269
|
+
var MediaPlayerAssumedFieldSchema = _enum([
|
|
67270
|
+
"state",
|
|
67271
|
+
"volumeLevel",
|
|
67272
|
+
"isMuted",
|
|
67273
|
+
"source"
|
|
67274
|
+
]);
|
|
66075
67275
|
var MediaPlayerStatusSchema = object({
|
|
66076
67276
|
/** Playback lifecycle state. */
|
|
66077
67277
|
state: MediaPlayerStateSchema,
|
|
67278
|
+
/**
|
|
67279
|
+
* Whether the state fields are readings or the driver's own bookkeeping.
|
|
67280
|
+
* A slice written before this field existed parses as `reported`.
|
|
67281
|
+
*/
|
|
67282
|
+
stateSource: MediaPlayerStateSourceSchema.default("reported"),
|
|
67283
|
+
/**
|
|
67284
|
+
* Which fields are the driver's own guess. `stateSource` is `assumed` when
|
|
67285
|
+
* this is not empty; a field not listed here is a reading even then (power
|
|
67286
|
+
* from a running-state source, mute guessed from the last send).
|
|
67287
|
+
*/
|
|
67288
|
+
assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
|
|
66078
67289
|
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
66079
67290
|
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
66080
67291
|
volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
@@ -67391,7 +68602,8 @@ var TopologyNodeSchema = object({
|
|
|
67391
68602
|
isHub: boolean(),
|
|
67392
68603
|
isOnline: boolean(),
|
|
67393
68604
|
cpuPercent: number(),
|
|
67394
|
-
|
|
68605
|
+
/** `null` when the node's memory read failed or it has not reported one (D393). */
|
|
68606
|
+
memoryPercent: number().nullable(),
|
|
67395
68607
|
uptime: number(),
|
|
67396
68608
|
lastSeen: string(),
|
|
67397
68609
|
localIps: array(string()).readonly(),
|
|
@@ -68002,6 +69214,14 @@ var PetFeederDeviceStatusSchema = _enum([
|
|
|
68002
69214
|
"on_batteries"
|
|
68003
69215
|
]);
|
|
68004
69216
|
var gramsPortion = number().int().min(4).max(200);
|
|
69217
|
+
/** A portion REQUESTED of the feeder is a mass the method takes in GRAMS (the
|
|
69218
|
+
* hardware's own unit). The bounds are grams; a caller entering another unit
|
|
69219
|
+
* converts to grams first (`paramMeta`, D972). The STATUS fields above stay
|
|
69220
|
+
* plain grams — they are not readings in the kilogram base. */
|
|
69221
|
+
var gramsParam = gramsPortion.meta(paramMeta({
|
|
69222
|
+
quantity: "mass",
|
|
69223
|
+
unit: "g"
|
|
69224
|
+
}));
|
|
68005
69225
|
var PetFeederStatusSchema = object({
|
|
68006
69226
|
/** Food in the TANK (hopper), as the feeder's own sensor reports it
|
|
68007
69227
|
* (`state.food`). A coarse level code, not grams — `0` is empty. The
|
|
@@ -68009,9 +69229,10 @@ var PetFeederStatusSchema = object({
|
|
|
68009
69229
|
* combined reading; per-hopper levels live in `food1`/`food2`. Null when
|
|
68010
69230
|
* the device has not reported yet. */
|
|
68011
69231
|
foodLevel: number().nullable().meta(readingMeta("none")),
|
|
68012
|
-
/** Food in the BOWL
|
|
68013
|
-
* feeder
|
|
68014
|
-
|
|
69232
|
+
/** Food in the BOWL, a mass (stored in kilograms, D944). Reported only by a
|
|
69233
|
+
* feeder with a bowl scale; null when the model has none or its reading is
|
|
69234
|
+
* of an unproven unit. */
|
|
69235
|
+
bowlFood: number().nullable().meta(readingMeta("mass")),
|
|
68015
69236
|
/** True while the pet is eating (`state.eating`). */
|
|
68016
69237
|
eating: boolean(),
|
|
68017
69238
|
/** Whether `markFoodReplenished` does anything on this model. When false
|
|
@@ -68062,6 +69283,14 @@ var PetFeederStatusSchema = object({
|
|
|
68062
69283
|
feedSound: boolean(),
|
|
68063
69284
|
/** Speaker / prompt volume level (device-scaled integer). */
|
|
68064
69285
|
volume: number().meta(readingMeta("none")),
|
|
69286
|
+
/** Portion (grams) of the last manual feed sent from a single-hopper
|
|
69287
|
+
* stepper — the default the next manual feed starts from. Persisted per
|
|
69288
|
+
* device by the provider; null until a manual feed has been sent. */
|
|
69289
|
+
lastFeedGrams: gramsPortion.nullable(),
|
|
69290
|
+
/** Hopper-1 portion of the last manual feed (dual-hopper); null until sent. */
|
|
69291
|
+
lastFeedHopper1: gramsPortion.nullable(),
|
|
69292
|
+
/** Hopper-2 portion of the last manual feed (dual-hopper); null until sent. */
|
|
69293
|
+
lastFeedHopper2: gramsPortion.nullable(),
|
|
68065
69294
|
/** Ms epoch when the slice was last refreshed from the cloud. */
|
|
68066
69295
|
lastFetchedAt: number()
|
|
68067
69296
|
});
|
|
@@ -68081,9 +69310,9 @@ var petFeederCapability = {
|
|
|
68081
69310
|
*/
|
|
68082
69311
|
feed: method(object({
|
|
68083
69312
|
deviceId: number().int().nonnegative(),
|
|
68084
|
-
grams:
|
|
68085
|
-
hopper1:
|
|
68086
|
-
hopper2:
|
|
69313
|
+
grams: gramsParam.optional(),
|
|
69314
|
+
hopper1: gramsParam.optional(),
|
|
69315
|
+
hopper2: gramsParam.optional()
|
|
68087
69316
|
}), _void(), {
|
|
68088
69317
|
kind: "mutation",
|
|
68089
69318
|
auth: "protected"
|
|
@@ -68187,6 +69416,137 @@ var petFeederCapability = {
|
|
|
68187
69416
|
}
|
|
68188
69417
|
} }
|
|
68189
69418
|
};
|
|
69419
|
+
/**
|
|
69420
|
+
* Pet water-fountain cap. Models the telemetry and the control surface of a
|
|
69421
|
+
* circulating drinking fountain: how much water is in the tank, how much the
|
|
69422
|
+
* pet drank, whether the pump runs and on what duty cycle.
|
|
69423
|
+
*
|
|
69424
|
+
* It has no native vendor provider of its own: a fountain reaches CamStack as
|
|
69425
|
+
* a container of loose entities (a pump switch, two number boxes, a "filter
|
|
69426
|
+
* changed" button), and an operator BUILDS the device from them — a composed
|
|
69427
|
+
* device of this type (D659, D967). Every method is therefore a forward to ONE
|
|
69428
|
+
* method of ONE source entity, and every reading is a field derived from a
|
|
69429
|
+
* source entity; the provider-shaped interface below exists so the composed
|
|
69430
|
+
* device and any future native provider answer the same contract.
|
|
69431
|
+
*
|
|
69432
|
+
* Quantities are stored in the base unit of their kind (D944): volumes in
|
|
69433
|
+
* LITRES, durations in SECONDS, the tank level in percent. A source that
|
|
69434
|
+
* reports millilitres or minutes is converted in the composition's expression,
|
|
69435
|
+
* never here. Every reading may be `null`: a source that is offline or does
|
|
69436
|
+
* not report is unknown, never zero.
|
|
69437
|
+
*
|
|
69438
|
+
* The filter is not a field of this cap: it is an item of the `consumables`
|
|
69439
|
+
* cap on the same device (kind `wear`), reset by the same forward mechanism.
|
|
69440
|
+
*/
|
|
69441
|
+
var PetFountainStatusSchema = object({
|
|
69442
|
+
/** Water in the tank, 0..100 (%). Null while the source has not reported. */
|
|
69443
|
+
waterLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
69444
|
+
/** Water in the tank, in litres. Null while the source has not reported. */
|
|
69445
|
+
waterVolume: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69446
|
+
/** True when the tank is empty. Null while unknown. */
|
|
69447
|
+
empty: boolean().nullable(),
|
|
69448
|
+
/** True while the pump is running. Null while unknown. */
|
|
69449
|
+
pumpRunning: boolean().nullable(),
|
|
69450
|
+
/** True when the pump works on an on/off duty cycle rather than continuously. */
|
|
69451
|
+
pumpIntervalEnabled: boolean().nullable(),
|
|
69452
|
+
/** How long the pump runs per cycle, in seconds. */
|
|
69453
|
+
pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
69454
|
+
/** How long the pump rests per cycle, in seconds. */
|
|
69455
|
+
pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
69456
|
+
/** Water drunk in the last drink, in litres. */
|
|
69457
|
+
lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69458
|
+
/** Water drunk in the current hour, in litres. */
|
|
69459
|
+
currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69460
|
+
/** Water drunk over the last 24 hours (rolling), in litres. */
|
|
69461
|
+
last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69462
|
+
/** Water drunk since local midnight, in litres. */
|
|
69463
|
+
todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69464
|
+
/** Decoded fault message. Null means healthy; a non-null string is an active fault. */
|
|
69465
|
+
error: string().nullable(),
|
|
69466
|
+
/** Ms epoch when the slice was last refreshed. */
|
|
69467
|
+
lastFetchedAt: number()
|
|
69468
|
+
});
|
|
69469
|
+
var DeviceIdSchema = number().int().nonnegative();
|
|
69470
|
+
var MinutesSchema = number().int().min(0);
|
|
69471
|
+
var petFountainCapability = {
|
|
69472
|
+
name: "pet-fountain",
|
|
69473
|
+
scope: "device",
|
|
69474
|
+
deviceNative: true,
|
|
69475
|
+
mode: "singleton",
|
|
69476
|
+
deviceTypes: [DeviceType.PetFountain],
|
|
69477
|
+
methods: {
|
|
69478
|
+
/** Start or stop the pump. */
|
|
69479
|
+
setPump: method(object({
|
|
69480
|
+
deviceId: DeviceIdSchema,
|
|
69481
|
+
on: boolean()
|
|
69482
|
+
}), _void(), {
|
|
69483
|
+
kind: "mutation",
|
|
69484
|
+
auth: "protected"
|
|
69485
|
+
}),
|
|
69486
|
+
/** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
|
|
69487
|
+
setPumpInterval: method(object({
|
|
69488
|
+
deviceId: DeviceIdSchema,
|
|
69489
|
+
on: boolean()
|
|
69490
|
+
}), _void(), {
|
|
69491
|
+
kind: "mutation",
|
|
69492
|
+
auth: "protected"
|
|
69493
|
+
}),
|
|
69494
|
+
/** Set how many minutes the pump runs per cycle. */
|
|
69495
|
+
setPumpOnMinutes: method(object({
|
|
69496
|
+
deviceId: DeviceIdSchema,
|
|
69497
|
+
minutes: MinutesSchema
|
|
69498
|
+
}), _void(), {
|
|
69499
|
+
kind: "mutation",
|
|
69500
|
+
auth: "protected"
|
|
69501
|
+
}),
|
|
69502
|
+
/** Set how many minutes the pump rests per cycle. */
|
|
69503
|
+
setPumpOffMinutes: method(object({
|
|
69504
|
+
deviceId: DeviceIdSchema,
|
|
69505
|
+
minutes: MinutesSchema
|
|
69506
|
+
}), _void(), {
|
|
69507
|
+
kind: "mutation",
|
|
69508
|
+
auth: "protected"
|
|
69509
|
+
}),
|
|
69510
|
+
/** Clear the rolling daily consumption counters on the device. */
|
|
69511
|
+
resetDailyConsumption: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
69512
|
+
kind: "mutation",
|
|
69513
|
+
auth: "protected"
|
|
69514
|
+
})
|
|
69515
|
+
},
|
|
69516
|
+
status: {
|
|
69517
|
+
schema: PetFountainStatusSchema,
|
|
69518
|
+
kind: "push"
|
|
69519
|
+
},
|
|
69520
|
+
/** Runtime-state slice — mirrored by the kernel; the hero card reads it whole. */
|
|
69521
|
+
runtimeState: PetFountainStatusSchema,
|
|
69522
|
+
/**
|
|
69523
|
+
* Runtime-state durability: **session** — live appliance state re-published on connect.
|
|
69524
|
+
*
|
|
69525
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
69526
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
69527
|
+
*/
|
|
69528
|
+
durability: "session",
|
|
69529
|
+
/** History (D850): recorded series. */
|
|
69530
|
+
history: { fields: {
|
|
69531
|
+
waterLevel: {
|
|
69532
|
+
kind: "gauge",
|
|
69533
|
+
unit: "%",
|
|
69534
|
+
deadband: 1
|
|
69535
|
+
},
|
|
69536
|
+
waterVolume: {
|
|
69537
|
+
kind: "gauge",
|
|
69538
|
+
unit: "L"
|
|
69539
|
+
},
|
|
69540
|
+
todayDrink: {
|
|
69541
|
+
kind: "gauge",
|
|
69542
|
+
unit: "L"
|
|
69543
|
+
},
|
|
69544
|
+
last24hDrink: {
|
|
69545
|
+
kind: "gauge",
|
|
69546
|
+
unit: "L"
|
|
69547
|
+
}
|
|
69548
|
+
} }
|
|
69549
|
+
};
|
|
68190
69550
|
var VehicleSchema = object({
|
|
68191
69551
|
id: string(),
|
|
68192
69552
|
name: string(),
|
|
@@ -74184,9 +75544,98 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
|
|
|
74184
75544
|
kind: "mutation",
|
|
74185
75545
|
auth: "admin"
|
|
74186
75546
|
});
|
|
75547
|
+
var btn = (idSuffix, row, col, init) => ({
|
|
75548
|
+
idSuffix,
|
|
75549
|
+
row,
|
|
75550
|
+
col,
|
|
75551
|
+
rowSpan: 1,
|
|
75552
|
+
colSpan: 1,
|
|
75553
|
+
...init
|
|
75554
|
+
});
|
|
75555
|
+
function rocker(id, name, it, frame, title, buttons, size) {
|
|
75556
|
+
return {
|
|
75557
|
+
id,
|
|
75558
|
+
name,
|
|
75559
|
+
nameI18n: {
|
|
75560
|
+
en: name,
|
|
75561
|
+
it
|
|
75562
|
+
},
|
|
75563
|
+
icon: frame === "rocker-v" ? "chevrons-up-down" : "chevrons-left-right",
|
|
75564
|
+
category: id.includes("channel") || id.includes("volume") ? "volume" : "navigation",
|
|
75565
|
+
size,
|
|
75566
|
+
group: title === null ? { frame } : {
|
|
75567
|
+
frame,
|
|
75568
|
+
title
|
|
75569
|
+
},
|
|
75570
|
+
buttons
|
|
75571
|
+
};
|
|
75572
|
+
}
|
|
75573
|
+
btn("power", 0, 0, {
|
|
75574
|
+
role: "power",
|
|
75575
|
+
shape: "round",
|
|
75576
|
+
tone: "danger"
|
|
75577
|
+
}), btn("up", 0, 1, { role: "up" }), btn("left", 1, 0, { role: "left" }), btn("ok", 1, 1, {
|
|
75578
|
+
role: "ok",
|
|
75579
|
+
shape: "round",
|
|
75580
|
+
tone: "accent"
|
|
75581
|
+
}), btn("right", 1, 2, { role: "right" }), btn("down", 2, 1, { role: "down" }), rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
|
|
75582
|
+
role: "volume_up",
|
|
75583
|
+
shape: "pill"
|
|
75584
|
+
}), btn("down", 1, 0, {
|
|
75585
|
+
role: "volume_down",
|
|
75586
|
+
shape: "pill"
|
|
75587
|
+
})], {
|
|
75588
|
+
rows: 2,
|
|
75589
|
+
cols: 1
|
|
75590
|
+
}), rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
|
|
75591
|
+
role: "channel_up",
|
|
75592
|
+
shape: "pill"
|
|
75593
|
+
}), btn("down", 1, 0, {
|
|
75594
|
+
role: "channel_down",
|
|
75595
|
+
shape: "pill"
|
|
75596
|
+
})], {
|
|
75597
|
+
rows: 2,
|
|
75598
|
+
cols: 1
|
|
75599
|
+
}), rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
|
|
75600
|
+
role: "custom:minus",
|
|
75601
|
+
label: "−",
|
|
75602
|
+
shape: "pill"
|
|
75603
|
+
}), btn("plus", 0, 1, {
|
|
75604
|
+
role: "custom:plus",
|
|
75605
|
+
label: "+",
|
|
75606
|
+
shape: "pill"
|
|
75607
|
+
})], {
|
|
75608
|
+
rows: 1,
|
|
75609
|
+
cols: 2
|
|
75610
|
+
}), [...Array.from({ length: 9 }, (_, i) => btn(`d${i + 1}`, Math.floor(i / 3), i % 3, { role: `digit_${i + 1}` })), btn("d0", 3, 1, { role: "digit_0" })], btn("red", 0, 0, {
|
|
75611
|
+
role: "custom:color_red",
|
|
75612
|
+
tone: "red"
|
|
75613
|
+
}), btn("green", 0, 1, {
|
|
75614
|
+
role: "custom:color_green",
|
|
75615
|
+
tone: "green"
|
|
75616
|
+
}), btn("yellow", 0, 2, {
|
|
75617
|
+
role: "custom:color_yellow",
|
|
75618
|
+
tone: "yellow"
|
|
75619
|
+
}), btn("blue", 0, 3, {
|
|
75620
|
+
role: "custom:color_blue",
|
|
75621
|
+
tone: "blue"
|
|
75622
|
+
}), btn("rewind", 0, 0, { role: "rewind" }), btn("play", 0, 1, { role: "play" }), btn("pause", 0, 2, { role: "pause" }), btn("forward", 0, 3, { role: "fast_forward" }), btn("stop", 0, 4, { role: "stop" }), [
|
|
75623
|
+
1,
|
|
75624
|
+
2,
|
|
75625
|
+
3,
|
|
75626
|
+
4
|
|
75627
|
+
].map((n) => btn(`app${n}`, 0, n - 1, { role: `custom:app_slot_${n}` })), btn("back", 0, 0, { role: "back" }), btn("home", 0, 1, { role: "home" }), btn("menu", 0, 2, { role: "menu" });
|
|
75628
|
+
discriminatedUnion("kind", [object({
|
|
75629
|
+
kind: literal("raw"),
|
|
75630
|
+
timings: array(number().int()),
|
|
75631
|
+
settings: TransmitSettingsSchema.optional()
|
|
75632
|
+
}), object({
|
|
75633
|
+
kind: literal("pronto"),
|
|
75634
|
+
code: string()
|
|
75635
|
+
})]);
|
|
74187
75636
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
74188
75637
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
74189
|
-
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
75638
|
+
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
74190
75639
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
74191
75640
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
74192
75641
|
new Set(CAP_NAMES);
|
|
@@ -74590,6 +76039,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
74590
76039
|
notifier: notifierCapability,
|
|
74591
76040
|
numericSensor: numericSensorCapability,
|
|
74592
76041
|
petFeeder: petFeederCapability,
|
|
76042
|
+
petFountain: petFountainCapability,
|
|
74593
76043
|
powerMeter: powerMeterCapability,
|
|
74594
76044
|
presence: presenceCapability,
|
|
74595
76045
|
pressureSensor: pressureSensorCapability,
|
|
@@ -75357,7 +76807,7 @@ var DeclaredDevices = class {
|
|
|
75357
76807
|
return removed;
|
|
75358
76808
|
}
|
|
75359
76809
|
};
|
|
75360
|
-
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
76810
|
+
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
75361
76811
|
new Set(Object.values(DeviceType));
|
|
75362
76812
|
Object.freeze({
|
|
75363
76813
|
"accessories.getStatus": {
|
|
@@ -77292,6 +78742,13 @@ Object.freeze({
|
|
|
77292
78742
|
access: "create",
|
|
77293
78743
|
role: "admin"
|
|
77294
78744
|
},
|
|
78745
|
+
"deviceExport.exposeEntity": {
|
|
78746
|
+
capName: "device-export",
|
|
78747
|
+
capScope: "system",
|
|
78748
|
+
addonId: null,
|
|
78749
|
+
access: "create",
|
|
78750
|
+
role: "admin"
|
|
78751
|
+
},
|
|
77295
78752
|
"deviceExport.getDeviceLiveContribution": {
|
|
77296
78753
|
capName: "device-export",
|
|
77297
78754
|
capScope: "system",
|
|
@@ -77320,6 +78777,13 @@ Object.freeze({
|
|
|
77320
78777
|
access: "view",
|
|
77321
78778
|
role: "admin"
|
|
77322
78779
|
},
|
|
78780
|
+
"deviceExport.listExposedEntities": {
|
|
78781
|
+
capName: "device-export",
|
|
78782
|
+
capScope: "system",
|
|
78783
|
+
addonId: null,
|
|
78784
|
+
access: "view",
|
|
78785
|
+
role: "admin"
|
|
78786
|
+
},
|
|
77323
78787
|
"deviceExport.listSupportedDeviceKinds": {
|
|
77324
78788
|
capName: "device-export",
|
|
77325
78789
|
capScope: "system",
|
|
@@ -77327,6 +78791,13 @@ Object.freeze({
|
|
|
77327
78791
|
access: "view",
|
|
77328
78792
|
role: "admin"
|
|
77329
78793
|
},
|
|
78794
|
+
"deviceExport.listSupportedEntityKinds": {
|
|
78795
|
+
capName: "device-export",
|
|
78796
|
+
capScope: "system",
|
|
78797
|
+
addonId: null,
|
|
78798
|
+
access: "view",
|
|
78799
|
+
role: "admin"
|
|
78800
|
+
},
|
|
77330
78801
|
"deviceExport.unexposeDevice": {
|
|
77331
78802
|
capName: "device-export",
|
|
77332
78803
|
capScope: "system",
|
|
@@ -77334,6 +78805,13 @@ Object.freeze({
|
|
|
77334
78805
|
access: "create",
|
|
77335
78806
|
role: "admin"
|
|
77336
78807
|
},
|
|
78808
|
+
"deviceExport.unexposeEntity": {
|
|
78809
|
+
capName: "device-export",
|
|
78810
|
+
capScope: "system",
|
|
78811
|
+
addonId: null,
|
|
78812
|
+
access: "create",
|
|
78813
|
+
role: "admin"
|
|
78814
|
+
},
|
|
77337
78815
|
"deviceExtension.listCandidates": {
|
|
77338
78816
|
capName: "device-extension",
|
|
77339
78817
|
capScope: "device",
|
|
@@ -80155,6 +81633,13 @@ Object.freeze({
|
|
|
80155
81633
|
access: "create",
|
|
80156
81634
|
role: "admin"
|
|
80157
81635
|
},
|
|
81636
|
+
"nodeRemovalHook.onNodeRemoved": {
|
|
81637
|
+
capName: "node-removal-hook",
|
|
81638
|
+
capScope: "system",
|
|
81639
|
+
addonId: null,
|
|
81640
|
+
access: "create",
|
|
81641
|
+
role: "admin"
|
|
81642
|
+
},
|
|
80158
81643
|
"nodes.clusterAddonStatus": {
|
|
80159
81644
|
capName: "nodes",
|
|
80160
81645
|
capScope: "system",
|
|
@@ -80792,6 +82277,48 @@ Object.freeze({
|
|
|
80792
82277
|
access: "create",
|
|
80793
82278
|
role: "viewer"
|
|
80794
82279
|
},
|
|
82280
|
+
"petFountain.getStatus": {
|
|
82281
|
+
capName: "pet-fountain",
|
|
82282
|
+
capScope: "device",
|
|
82283
|
+
addonId: null,
|
|
82284
|
+
access: "view",
|
|
82285
|
+
role: "viewer"
|
|
82286
|
+
},
|
|
82287
|
+
"petFountain.resetDailyConsumption": {
|
|
82288
|
+
capName: "pet-fountain",
|
|
82289
|
+
capScope: "device",
|
|
82290
|
+
addonId: null,
|
|
82291
|
+
access: "delete",
|
|
82292
|
+
role: "viewer"
|
|
82293
|
+
},
|
|
82294
|
+
"petFountain.setPump": {
|
|
82295
|
+
capName: "pet-fountain",
|
|
82296
|
+
capScope: "device",
|
|
82297
|
+
addonId: null,
|
|
82298
|
+
access: "create",
|
|
82299
|
+
role: "viewer"
|
|
82300
|
+
},
|
|
82301
|
+
"petFountain.setPumpInterval": {
|
|
82302
|
+
capName: "pet-fountain",
|
|
82303
|
+
capScope: "device",
|
|
82304
|
+
addonId: null,
|
|
82305
|
+
access: "create",
|
|
82306
|
+
role: "viewer"
|
|
82307
|
+
},
|
|
82308
|
+
"petFountain.setPumpOffMinutes": {
|
|
82309
|
+
capName: "pet-fountain",
|
|
82310
|
+
capScope: "device",
|
|
82311
|
+
addonId: null,
|
|
82312
|
+
access: "create",
|
|
82313
|
+
role: "viewer"
|
|
82314
|
+
},
|
|
82315
|
+
"petFountain.setPumpOnMinutes": {
|
|
82316
|
+
capName: "pet-fountain",
|
|
82317
|
+
capScope: "device",
|
|
82318
|
+
addonId: null,
|
|
82319
|
+
access: "create",
|
|
82320
|
+
role: "viewer"
|
|
82321
|
+
},
|
|
80795
82322
|
"pipelineAnalytics.addRetrainFrameFromRecording": {
|
|
80796
82323
|
capName: "pipeline-analytics",
|
|
80797
82324
|
capScope: "device",
|
|
@@ -83046,6 +84573,48 @@ Object.freeze({
|
|
|
83046
84573
|
access: "create",
|
|
83047
84574
|
role: "admin"
|
|
83048
84575
|
},
|
|
84576
|
+
"remote.getConfig": {
|
|
84577
|
+
capName: "remote",
|
|
84578
|
+
capScope: "system",
|
|
84579
|
+
addonId: null,
|
|
84580
|
+
access: "view",
|
|
84581
|
+
role: "admin"
|
|
84582
|
+
},
|
|
84583
|
+
"remote.listCommands": {
|
|
84584
|
+
capName: "remote",
|
|
84585
|
+
capScope: "system",
|
|
84586
|
+
addonId: null,
|
|
84587
|
+
access: "view",
|
|
84588
|
+
role: "admin"
|
|
84589
|
+
},
|
|
84590
|
+
"remote.sendCommand": {
|
|
84591
|
+
capName: "remote",
|
|
84592
|
+
capScope: "system",
|
|
84593
|
+
addonId: null,
|
|
84594
|
+
access: "create",
|
|
84595
|
+
role: "admin"
|
|
84596
|
+
},
|
|
84597
|
+
"remote.sendMediaCommand": {
|
|
84598
|
+
capName: "remote",
|
|
84599
|
+
capScope: "system",
|
|
84600
|
+
addonId: null,
|
|
84601
|
+
access: "create",
|
|
84602
|
+
role: "admin"
|
|
84603
|
+
},
|
|
84604
|
+
"remote.setConfig": {
|
|
84605
|
+
capName: "remote",
|
|
84606
|
+
capScope: "system",
|
|
84607
|
+
addonId: null,
|
|
84608
|
+
access: "create",
|
|
84609
|
+
role: "admin"
|
|
84610
|
+
},
|
|
84611
|
+
"remoteLayoutComponents.listComponents": {
|
|
84612
|
+
capName: "remote-layout-components",
|
|
84613
|
+
capScope: "system",
|
|
84614
|
+
addonId: null,
|
|
84615
|
+
access: "view",
|
|
84616
|
+
role: "admin"
|
|
84617
|
+
},
|
|
83049
84618
|
"runningState.getStatus": {
|
|
83050
84619
|
capName: "running-state",
|
|
83051
84620
|
capScope: "device",
|
|
@@ -83158,6 +84727,13 @@ Object.freeze({
|
|
|
83158
84727
|
access: "create",
|
|
83159
84728
|
role: "admin"
|
|
83160
84729
|
},
|
|
84730
|
+
"serverManagement.getLaunchOverrides": {
|
|
84731
|
+
capName: "server-management",
|
|
84732
|
+
capScope: "system",
|
|
84733
|
+
addonId: null,
|
|
84734
|
+
access: "view",
|
|
84735
|
+
role: "admin"
|
|
84736
|
+
},
|
|
83161
84737
|
"serverManagement.getServerPackageStatus": {
|
|
83162
84738
|
capName: "server-management",
|
|
83163
84739
|
capScope: "system",
|
|
@@ -83165,6 +84741,13 @@ Object.freeze({
|
|
|
83165
84741
|
access: "view",
|
|
83166
84742
|
role: "admin"
|
|
83167
84743
|
},
|
|
84744
|
+
"serverManagement.installDesktopUpdate": {
|
|
84745
|
+
capName: "server-management",
|
|
84746
|
+
capScope: "system",
|
|
84747
|
+
addonId: null,
|
|
84748
|
+
access: "create",
|
|
84749
|
+
role: "admin"
|
|
84750
|
+
},
|
|
83168
84751
|
"serverManagement.restartServer": {
|
|
83169
84752
|
capName: "server-management",
|
|
83170
84753
|
capScope: "system",
|
|
@@ -83179,6 +84762,13 @@ Object.freeze({
|
|
|
83179
84762
|
access: "create",
|
|
83180
84763
|
role: "admin"
|
|
83181
84764
|
},
|
|
84765
|
+
"serverManagement.setLaunchOverrides": {
|
|
84766
|
+
capName: "server-management",
|
|
84767
|
+
capScope: "system",
|
|
84768
|
+
addonId: null,
|
|
84769
|
+
access: "create",
|
|
84770
|
+
role: "admin"
|
|
84771
|
+
},
|
|
83182
84772
|
"settingsStore.aggregate": {
|
|
83183
84773
|
capName: "settings-store",
|
|
83184
84774
|
capScope: "system",
|
|
@@ -84236,6 +85826,41 @@ Object.freeze({
|
|
|
84236
85826
|
access: "view",
|
|
84237
85827
|
role: "viewer"
|
|
84238
85828
|
},
|
|
85829
|
+
"template.create": {
|
|
85830
|
+
capName: "template",
|
|
85831
|
+
capScope: "system",
|
|
85832
|
+
addonId: null,
|
|
85833
|
+
access: "create",
|
|
85834
|
+
role: "admin"
|
|
85835
|
+
},
|
|
85836
|
+
"template.delete": {
|
|
85837
|
+
capName: "template",
|
|
85838
|
+
capScope: "system",
|
|
85839
|
+
addonId: null,
|
|
85840
|
+
access: "delete",
|
|
85841
|
+
role: "admin"
|
|
85842
|
+
},
|
|
85843
|
+
"template.get": {
|
|
85844
|
+
capName: "template",
|
|
85845
|
+
capScope: "system",
|
|
85846
|
+
addonId: null,
|
|
85847
|
+
access: "view",
|
|
85848
|
+
role: "admin"
|
|
85849
|
+
},
|
|
85850
|
+
"template.list": {
|
|
85851
|
+
capName: "template",
|
|
85852
|
+
capScope: "system",
|
|
85853
|
+
addonId: null,
|
|
85854
|
+
access: "view",
|
|
85855
|
+
role: "admin"
|
|
85856
|
+
},
|
|
85857
|
+
"template.update": {
|
|
85858
|
+
capName: "template",
|
|
85859
|
+
capScope: "system",
|
|
85860
|
+
addonId: null,
|
|
85861
|
+
access: "create",
|
|
85862
|
+
role: "admin"
|
|
85863
|
+
},
|
|
84239
85864
|
"terminalSession.adoptLegacyMonitor": {
|
|
84240
85865
|
capName: "terminal-session",
|
|
84241
85866
|
capScope: "system",
|
|
@@ -87033,7 +88658,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87033
88658
|
min: 4,
|
|
87034
88659
|
max: 200,
|
|
87035
88660
|
step: 1,
|
|
87036
|
-
unit: "g"
|
|
88661
|
+
unit: "g",
|
|
88662
|
+
quantity: "mass"
|
|
87037
88663
|
},
|
|
87038
88664
|
optional: true
|
|
87039
88665
|
}]
|
|
@@ -87052,7 +88678,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87052
88678
|
min: 4,
|
|
87053
88679
|
max: 200,
|
|
87054
88680
|
step: 1,
|
|
87055
|
-
unit: "g"
|
|
88681
|
+
unit: "g",
|
|
88682
|
+
quantity: "mass"
|
|
87056
88683
|
},
|
|
87057
88684
|
optional: true
|
|
87058
88685
|
}, {
|
|
@@ -87063,7 +88690,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87063
88690
|
min: 4,
|
|
87064
88691
|
max: 200,
|
|
87065
88692
|
step: 1,
|
|
87066
|
-
unit: "g"
|
|
88693
|
+
unit: "g",
|
|
88694
|
+
quantity: "mass"
|
|
87067
88695
|
},
|
|
87068
88696
|
optional: true
|
|
87069
88697
|
}]
|
|
@@ -87204,6 +88832,100 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87204
88832
|
}
|
|
87205
88833
|
]
|
|
87206
88834
|
},
|
|
88835
|
+
"pet-fountain": {
|
|
88836
|
+
scope: "device",
|
|
88837
|
+
noun: t("Pet fountain", "Fontana per animali"),
|
|
88838
|
+
actions: [
|
|
88839
|
+
{
|
|
88840
|
+
id: "petFountain.pumpOn",
|
|
88841
|
+
method: "setPump",
|
|
88842
|
+
label: t("Pump on", "Pompa accesa"),
|
|
88843
|
+
phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
|
|
88844
|
+
icon: "play",
|
|
88845
|
+
args: { on: true }
|
|
88846
|
+
},
|
|
88847
|
+
{
|
|
88848
|
+
id: "petFountain.pumpOff",
|
|
88849
|
+
method: "setPump",
|
|
88850
|
+
label: t("Pump off", "Pompa spenta"),
|
|
88851
|
+
phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
|
|
88852
|
+
icon: "square",
|
|
88853
|
+
args: { on: false }
|
|
88854
|
+
},
|
|
88855
|
+
{
|
|
88856
|
+
id: "petFountain.intervalOn",
|
|
88857
|
+
method: "setPumpInterval",
|
|
88858
|
+
label: t("Pump cycle on", "Ciclo pompa attivo"),
|
|
88859
|
+
phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
|
|
88860
|
+
icon: "timer",
|
|
88861
|
+
args: { on: true }
|
|
88862
|
+
},
|
|
88863
|
+
{
|
|
88864
|
+
id: "petFountain.intervalOff",
|
|
88865
|
+
method: "setPumpInterval",
|
|
88866
|
+
label: t("Pump cycle off", "Ciclo pompa spento"),
|
|
88867
|
+
phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
|
|
88868
|
+
icon: "timer-off",
|
|
88869
|
+
args: { on: false }
|
|
88870
|
+
},
|
|
88871
|
+
{
|
|
88872
|
+
id: "petFountain.pumpOnMinutes",
|
|
88873
|
+
method: "setPumpOnMinutes",
|
|
88874
|
+
label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
|
|
88875
|
+
phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
|
|
88876
|
+
icon: "timer",
|
|
88877
|
+
openArgs: [{
|
|
88878
|
+
key: "minutes",
|
|
88879
|
+
label: t("Minutes", "Minuti"),
|
|
88880
|
+
offer: {
|
|
88881
|
+
kind: "number",
|
|
88882
|
+
min: 0,
|
|
88883
|
+
step: 1,
|
|
88884
|
+
unit: "min"
|
|
88885
|
+
}
|
|
88886
|
+
}]
|
|
88887
|
+
},
|
|
88888
|
+
{
|
|
88889
|
+
id: "petFountain.pumpOffMinutes",
|
|
88890
|
+
method: "setPumpOffMinutes",
|
|
88891
|
+
label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
|
|
88892
|
+
phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
|
|
88893
|
+
icon: "timer-off",
|
|
88894
|
+
openArgs: [{
|
|
88895
|
+
key: "minutes",
|
|
88896
|
+
label: t("Minutes", "Minuti"),
|
|
88897
|
+
offer: {
|
|
88898
|
+
kind: "number",
|
|
88899
|
+
min: 0,
|
|
88900
|
+
step: 1,
|
|
88901
|
+
unit: "min"
|
|
88902
|
+
}
|
|
88903
|
+
}]
|
|
88904
|
+
},
|
|
88905
|
+
{
|
|
88906
|
+
id: "petFountain.resetDailyConsumption",
|
|
88907
|
+
method: "resetDailyConsumption",
|
|
88908
|
+
label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
|
|
88909
|
+
phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
|
|
88910
|
+
icon: "rotate-ccw"
|
|
88911
|
+
}
|
|
88912
|
+
],
|
|
88913
|
+
state: [{
|
|
88914
|
+
kind: "boolean",
|
|
88915
|
+
field: "empty",
|
|
88916
|
+
label: t("Tank empty", "Serbatoio vuoto"),
|
|
88917
|
+
whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
|
|
88918
|
+
whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
|
|
88919
|
+
icon: "droplet"
|
|
88920
|
+
}, {
|
|
88921
|
+
kind: "numeric",
|
|
88922
|
+
field: "waterLevel",
|
|
88923
|
+
quantity: t("the water level", "il livello dell’acqua"),
|
|
88924
|
+
label: t("Water level", "Livello dell’acqua"),
|
|
88925
|
+
unit: "%",
|
|
88926
|
+
icon: "droplets"
|
|
88927
|
+
}]
|
|
88928
|
+
},
|
|
87207
88929
|
"media-player": {
|
|
87208
88930
|
scope: "device",
|
|
87209
88931
|
noun: t("Media player", "Lettore multimediale"),
|
|
@@ -89028,6 +90750,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
89028
90750
|
"device-manager": system("the device registry itself — rename, move and remove are administration"),
|
|
89029
90751
|
"device-provider": system(INTERNAL),
|
|
89030
90752
|
"device-removal-hook": system(INTERNAL),
|
|
90753
|
+
"node-removal-hook": system(INTERNAL),
|
|
89031
90754
|
"device-state": system("the runtime-state mirror every trigger already reads through"),
|
|
89032
90755
|
"display-palette": system("the cluster display palette — one colour table for every client, not a device action"),
|
|
89033
90756
|
"embedding-encoder": system(ML),
|
|
@@ -89067,6 +90790,8 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
89067
90790
|
"recording-archive": system(STORAGE),
|
|
89068
90791
|
"recording-export": system("recording exports — requested from the timeline, not an automation step"),
|
|
89069
90792
|
records: system(INTERNAL),
|
|
90793
|
+
remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
|
|
90794
|
+
"remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
|
|
89070
90795
|
"server-management": system(CLUSTER),
|
|
89071
90796
|
"settings-store": system(INTERNAL),
|
|
89072
90797
|
"smtp-provider": system(INTERNAL),
|
|
@@ -89079,6 +90804,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
89079
90804
|
"storage-provider": system(STORAGE),
|
|
89080
90805
|
"stream-broker": system(PIPELINE),
|
|
89081
90806
|
system: system(CLUSTER),
|
|
90807
|
+
template: system("operator-authored templates — edited in their own editor, never actuated"),
|
|
89082
90808
|
"terminal-session": system(CLUSTER),
|
|
89083
90809
|
toast: system(UI_PLUMBING),
|
|
89084
90810
|
training: system(ML),
|
|
@@ -89138,7 +90864,7 @@ function projectAction(cap, action) {
|
|
|
89138
90864
|
};
|
|
89139
90865
|
}
|
|
89140
90866
|
Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
|
|
89141
|
-
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
90867
|
+
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
89142
90868
|
DeviceFeature.BatteryOperated;
|
|
89143
90869
|
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
89144
90870
|
var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";
|
|
@@ -90689,6 +92415,36 @@ Object.freeze({
|
|
|
90689
92415
|
form: "single",
|
|
90690
92416
|
optional: false
|
|
90691
92417
|
}],
|
|
92418
|
+
"petFountain.getStatus": [{
|
|
92419
|
+
name: "deviceId",
|
|
92420
|
+
form: "single",
|
|
92421
|
+
optional: false
|
|
92422
|
+
}],
|
|
92423
|
+
"petFountain.resetDailyConsumption": [{
|
|
92424
|
+
name: "deviceId",
|
|
92425
|
+
form: "single",
|
|
92426
|
+
optional: false
|
|
92427
|
+
}],
|
|
92428
|
+
"petFountain.setPump": [{
|
|
92429
|
+
name: "deviceId",
|
|
92430
|
+
form: "single",
|
|
92431
|
+
optional: false
|
|
92432
|
+
}],
|
|
92433
|
+
"petFountain.setPumpInterval": [{
|
|
92434
|
+
name: "deviceId",
|
|
92435
|
+
form: "single",
|
|
92436
|
+
optional: false
|
|
92437
|
+
}],
|
|
92438
|
+
"petFountain.setPumpOffMinutes": [{
|
|
92439
|
+
name: "deviceId",
|
|
92440
|
+
form: "single",
|
|
92441
|
+
optional: false
|
|
92442
|
+
}],
|
|
92443
|
+
"petFountain.setPumpOnMinutes": [{
|
|
92444
|
+
name: "deviceId",
|
|
92445
|
+
form: "single",
|
|
92446
|
+
optional: false
|
|
92447
|
+
}],
|
|
90692
92448
|
"pipelineAnalytics.addRetrainFrameFromRecording": [{
|
|
90693
92449
|
name: "deviceId",
|
|
90694
92450
|
form: "single",
|
|
@@ -91505,6 +93261,31 @@ Object.freeze({
|
|
|
91505
93261
|
form: "single",
|
|
91506
93262
|
optional: true
|
|
91507
93263
|
}],
|
|
93264
|
+
"remote.getConfig": [{
|
|
93265
|
+
name: "deviceId",
|
|
93266
|
+
form: "single",
|
|
93267
|
+
optional: false
|
|
93268
|
+
}],
|
|
93269
|
+
"remote.listCommands": [{
|
|
93270
|
+
name: "deviceId",
|
|
93271
|
+
form: "single",
|
|
93272
|
+
optional: false
|
|
93273
|
+
}],
|
|
93274
|
+
"remote.sendCommand": [{
|
|
93275
|
+
name: "deviceId",
|
|
93276
|
+
form: "single",
|
|
93277
|
+
optional: false
|
|
93278
|
+
}],
|
|
93279
|
+
"remote.sendMediaCommand": [{
|
|
93280
|
+
name: "deviceId",
|
|
93281
|
+
form: "single",
|
|
93282
|
+
optional: false
|
|
93283
|
+
}],
|
|
93284
|
+
"remote.setConfig": [{
|
|
93285
|
+
name: "deviceId",
|
|
93286
|
+
form: "single",
|
|
93287
|
+
optional: false
|
|
93288
|
+
}],
|
|
91508
93289
|
"runningState.getStatus": [{
|
|
91509
93290
|
name: "deviceId",
|
|
91510
93291
|
form: "single",
|
|
@@ -95060,8 +96841,6 @@ var NC_TEMPLATE_VARS = [
|
|
|
95060
96841
|
* delete it from here then, and the served catalog names it again.
|
|
95061
96842
|
*/
|
|
95062
96843
|
var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
|
|
95063
|
-
"node-restarting",
|
|
95064
|
-
"node-updating",
|
|
95065
96844
|
"alert-critical",
|
|
95066
96845
|
"alert-error",
|
|
95067
96846
|
"alert-critical-resolved",
|
|
@@ -96874,14 +98653,6 @@ function armRemovalReconcile(opts) {
|
|
|
96874
98653
|
timer.unref?.();
|
|
96875
98654
|
return () => clearInterval(timer);
|
|
96876
98655
|
}
|
|
96877
|
-
discriminatedUnion("kind", [object({
|
|
96878
|
-
kind: literal("raw"),
|
|
96879
|
-
timings: array(number().int()),
|
|
96880
|
-
settings: TransmitSettingsSchema.optional()
|
|
96881
|
-
}), object({
|
|
96882
|
-
kind: literal("pronto"),
|
|
96883
|
-
code: string()
|
|
96884
|
-
})]);
|
|
96885
98656
|
object({
|
|
96886
98657
|
manufacturer: string().optional(),
|
|
96887
98658
|
supportedModels: array(string()).optional(),
|