@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
|
@@ -5645,7 +5645,7 @@ var ZodIssueCode = {
|
|
|
5645
5645
|
var ZodFirstPartyTypeKind;
|
|
5646
5646
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5647
5647
|
//#endregion
|
|
5648
|
-
//#region ../types/dist/sleep-
|
|
5648
|
+
//#region ../types/dist/sleep-63gM_1JR.mjs
|
|
5649
5649
|
/**
|
|
5650
5650
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5651
5651
|
* window to float samples (D455).
|
|
@@ -8746,65 +8746,6 @@ var MODEL_FORMATS = [
|
|
|
8746
8746
|
"pt",
|
|
8747
8747
|
"gguf"
|
|
8748
8748
|
];
|
|
8749
|
-
/**
|
|
8750
|
-
* Per-call node pinning for `ctx.api` capability calls.
|
|
8751
|
-
*
|
|
8752
|
-
* A capability call normally resolves to its DEFAULT provider — a `singleton`
|
|
8753
|
-
* cap resolves to the hub, a device-scoped cap to the device's owning node. To
|
|
8754
|
-
* query a SPECIFIC node's provider instead (e.g. a remote agent's own
|
|
8755
|
-
* in-process `platform-probe` hardware, which the hub cannot probe), pin the
|
|
8756
|
-
* call to that node.
|
|
8757
|
-
*
|
|
8758
|
-
* The nodeId rides OUT-OF-BAND in the tRPC call context (NOT in the validated
|
|
8759
|
-
* method args), so capability method signatures stay `nodeId`-free — node
|
|
8760
|
-
* targeting is a property of the CALL, not of the method. The transport lifts
|
|
8761
|
-
* it from `op.context` onto the `CapCallInput.nodeId` field (`ipcParentLink`),
|
|
8762
|
-
* and the hub parent's `onUnownedCall` passes it to the `CapRouteResolver`,
|
|
8763
|
-
* which classifies a pinned agent node as `agent-child-forward`
|
|
8764
|
-
* (`$agent-cap-fwd.forward` → the agent's in-process provider).
|
|
8765
|
-
*
|
|
8766
|
-
* Usage at a call site:
|
|
8767
|
-
*
|
|
8768
|
-
* await api.platformProbe.getCapabilities.query(undefined, nodePin(nodeId))
|
|
8769
|
-
*/
|
|
8770
|
-
/** tRPC `op.context` key carrying a per-call node pin. */
|
|
8771
|
-
var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
8772
|
-
/**
|
|
8773
|
-
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
8774
|
-
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
8775
|
-
*
|
|
8776
|
-
* ## The id is normalised here, and it has to be
|
|
8777
|
-
*
|
|
8778
|
-
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
8779
|
-
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
8780
|
-
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
8781
|
-
* call fails with `no provider registered for cap "…"`. The local-first
|
|
8782
|
-
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
8783
|
-
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
8784
|
-
* the thing you reach for when you specifically need THIS node — silently
|
|
8785
|
-
* addressed a node that does not exist.
|
|
8786
|
-
*
|
|
8787
|
-
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
8788
|
-
* own node to read the host's hardware-decode backend. It never once answered,
|
|
8789
|
-
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
8790
|
-
* while D67's whole premise was that the decoder addon is the authority on
|
|
8791
|
-
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
8792
|
-
* hardware", which was false.
|
|
8793
|
-
*
|
|
8794
|
-
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
8795
|
-
* it is here and not at the call sites.
|
|
8796
|
-
*/
|
|
8797
|
-
function nodePin(nodeId) {
|
|
8798
|
-
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
8799
|
-
}
|
|
8800
|
-
/**
|
|
8801
|
-
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
8802
|
-
* is idempotent, so passing an already-clean id costs nothing.
|
|
8803
|
-
*/
|
|
8804
|
-
function toNodeId(idOrRunnerId) {
|
|
8805
|
-
const head = idOrRunnerId.split("/")[0];
|
|
8806
|
-
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
8807
|
-
}
|
|
8808
8749
|
object({
|
|
8809
8750
|
field: string().min(1),
|
|
8810
8751
|
on: literal("advance")
|
|
@@ -8897,6 +8838,111 @@ function optionalMethod(input, output, options) {
|
|
|
8897
8838
|
function event(data) {
|
|
8898
8839
|
return { data };
|
|
8899
8840
|
}
|
|
8841
|
+
/**
|
|
8842
|
+
* Per-call node pinning for `ctx.api` capability calls.
|
|
8843
|
+
*
|
|
8844
|
+
* A capability call normally resolves to its DEFAULT provider — a `singleton`
|
|
8845
|
+
* cap resolves to the hub, a device-scoped cap to the device's owning node. To
|
|
8846
|
+
* query a SPECIFIC node's provider instead (e.g. a remote agent's own
|
|
8847
|
+
* in-process `platform-probe` hardware, which the hub cannot probe), pin the
|
|
8848
|
+
* call to that node.
|
|
8849
|
+
*
|
|
8850
|
+
* The nodeId rides OUT-OF-BAND in the tRPC call context (NOT in the validated
|
|
8851
|
+
* method args), so capability method signatures stay `nodeId`-free — node
|
|
8852
|
+
* targeting is a property of the CALL, not of the method. The transport lifts
|
|
8853
|
+
* it from `op.context` onto the `CapCallInput.nodeId` field (`ipcParentLink`),
|
|
8854
|
+
* and the hub parent's `onUnownedCall` passes it to the `CapRouteResolver`,
|
|
8855
|
+
* which classifies a pinned agent node as `agent-child-forward`
|
|
8856
|
+
* (`$agent-cap-fwd.forward` → the agent's in-process provider).
|
|
8857
|
+
*
|
|
8858
|
+
* Usage at a call site:
|
|
8859
|
+
*
|
|
8860
|
+
* await api.platformProbe.getCapabilities.query(undefined, nodePin(nodeId))
|
|
8861
|
+
*/
|
|
8862
|
+
/** tRPC `op.context` key carrying a per-call node pin. */
|
|
8863
|
+
var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
8864
|
+
/**
|
|
8865
|
+
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
8866
|
+
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
8867
|
+
*
|
|
8868
|
+
* ## The id is normalised here, and it has to be
|
|
8869
|
+
*
|
|
8870
|
+
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
8871
|
+
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
8872
|
+
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
8873
|
+
* call fails with `no provider registered for cap "…"`. The local-first
|
|
8874
|
+
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
8875
|
+
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
8876
|
+
* the thing you reach for when you specifically need THIS node — silently
|
|
8877
|
+
* addressed a node that does not exist.
|
|
8878
|
+
*
|
|
8879
|
+
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
8880
|
+
* own node to read the host's hardware-decode backend. It never once answered,
|
|
8881
|
+
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
8882
|
+
* while D67's whole premise was that the decoder addon is the authority on
|
|
8883
|
+
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
8884
|
+
* hardware", which was false.
|
|
8885
|
+
*
|
|
8886
|
+
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
8887
|
+
* it is here and not at the call sites.
|
|
8888
|
+
*/
|
|
8889
|
+
function nodePin(nodeId) {
|
|
8890
|
+
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
8891
|
+
}
|
|
8892
|
+
/**
|
|
8893
|
+
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
8894
|
+
* is idempotent, so passing an already-clean id costs nothing.
|
|
8895
|
+
*/
|
|
8896
|
+
function toNodeId(idOrRunnerId) {
|
|
8897
|
+
const head = idOrRunnerId.split("/")[0];
|
|
8898
|
+
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
8899
|
+
}
|
|
8900
|
+
/**
|
|
8901
|
+
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
8902
|
+
* declarations. Every addon that contributes a UI page registers a
|
|
8903
|
+
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
8904
|
+
* see `addon-pages.cap.ts`) walks this collection, stamps versioned
|
|
8905
|
+
* `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
|
|
8906
|
+
* that admin-ui consumes.
|
|
8907
|
+
*
|
|
8908
|
+
* The split exists because the public listing has a different output
|
|
8909
|
+
* shape than the per-provider raw declarations, and we want both ends
|
|
8910
|
+
* to flow through codegen instead of relying on a hand-written wrapper.
|
|
8911
|
+
*/
|
|
8912
|
+
var AddonPageDeclarationSchema = object({
|
|
8913
|
+
id: string(),
|
|
8914
|
+
label: string(),
|
|
8915
|
+
icon: string(),
|
|
8916
|
+
path: string(),
|
|
8917
|
+
/**
|
|
8918
|
+
* Module Federation remote name — must match the `name` field on the
|
|
8919
|
+
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
8920
|
+
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
8921
|
+
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
8922
|
+
* contain hyphens).
|
|
8923
|
+
*/
|
|
8924
|
+
remoteName: string(),
|
|
8925
|
+
/**
|
|
8926
|
+
* Bundle filename inside the addon's `dist/` dir served at
|
|
8927
|
+
* `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
|
|
8928
|
+
* is always `'remoteEntry.js'`; the value is kept on the metadata so
|
|
8929
|
+
* the static-file route can compute an mtime-based cache-buster URL
|
|
8930
|
+
* without a separate filesystem stat.
|
|
8931
|
+
*/
|
|
8932
|
+
bundle: string(),
|
|
8933
|
+
/**
|
|
8934
|
+
* Sidebar section this page docks into. Well-known ids: `'detection'`,
|
|
8935
|
+
* `'cluster'`, `'administration'` — the page renders inside that group.
|
|
8936
|
+
* Any OTHER string creates (or joins) a custom section rendered after
|
|
8937
|
+
* the built-in groups; its label comes from `sectionLabel` (first
|
|
8938
|
+
* declaration wins), falling back to the id. Absent → the legacy
|
|
8939
|
+
* "Addon Pages" group.
|
|
8940
|
+
*/
|
|
8941
|
+
section: string().optional(),
|
|
8942
|
+
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
8943
|
+
sectionLabel: string().optional()
|
|
8944
|
+
});
|
|
8945
|
+
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
8900
8946
|
var StaticDirOutputSchema$1 = object({ staticDir: string() });
|
|
8901
8947
|
var VersionOutputSchema$1 = object({ version: string() });
|
|
8902
8948
|
method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
|
|
@@ -8922,6 +8968,27 @@ function customAction(input, output, options) {
|
|
|
8922
8968
|
...options?.caller ? { caller: "required" } : {}
|
|
8923
8969
|
};
|
|
8924
8970
|
}
|
|
8971
|
+
/**
|
|
8972
|
+
* The pipeline step-input shape and the run-result bridge, in a module that
|
|
8973
|
+
* imports nothing but zod and types.
|
|
8974
|
+
*
|
|
8975
|
+
* WHY A SEPARATE FILE (addon footprint, D28): both were declared in
|
|
8976
|
+
* `pipeline-executor.cap.ts`, whose import evaluates the whole executor schema
|
|
8977
|
+
* graph. An addon that only needs these two (the benchmark's `runPipelineTest`
|
|
8978
|
+
* action) paid ~130 MB of heap at load. `pipeline-executor.cap.ts` re-exports
|
|
8979
|
+
* them, so every existing import keeps working; addon server code takes them
|
|
8980
|
+
* from `@camstack/types/addon`.
|
|
8981
|
+
*/
|
|
8982
|
+
var PipelineStepInputSchema = lazy(() => object({
|
|
8983
|
+
addonId: string(),
|
|
8984
|
+
modelId: string().optional(),
|
|
8985
|
+
enabled: boolean().default(true),
|
|
8986
|
+
children: array(PipelineStepInputSchema).optional(),
|
|
8987
|
+
settings: record(string(), unknown()).optional(),
|
|
8988
|
+
jumpDeviceKey: string().optional()
|
|
8989
|
+
}));
|
|
8990
|
+
/** Typed bridge to the recursive `FrameResult` graph; validation is structural. */
|
|
8991
|
+
var PipelineRunResultBridge = custom();
|
|
8925
8992
|
var StaticDirOutputSchema = object({ staticDir: string() });
|
|
8926
8993
|
var VersionOutputSchema = object({ version: string() });
|
|
8927
8994
|
method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
|
|
@@ -9107,6 +9174,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9107
9174
|
"network-link",
|
|
9108
9175
|
"network-quality",
|
|
9109
9176
|
"node-certificates",
|
|
9177
|
+
"node-removal-hook",
|
|
9110
9178
|
"nodes",
|
|
9111
9179
|
"notification-output",
|
|
9112
9180
|
"notification-rules",
|
|
@@ -9117,6 +9185,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9117
9185
|
"osd",
|
|
9118
9186
|
"osd-manager",
|
|
9119
9187
|
"pet-feeder",
|
|
9188
|
+
"pet-fountain",
|
|
9120
9189
|
"pipeline-analytics",
|
|
9121
9190
|
"pipeline-executor",
|
|
9122
9191
|
"pipeline-orchestrator",
|
|
@@ -9137,6 +9206,8 @@ var CAP_NAMES = Object.freeze([
|
|
|
9137
9206
|
"recording-onboard",
|
|
9138
9207
|
"recording-signal",
|
|
9139
9208
|
"records",
|
|
9209
|
+
"remote",
|
|
9210
|
+
"remote-layout-components",
|
|
9140
9211
|
"running-state",
|
|
9141
9212
|
"scene-monitor",
|
|
9142
9213
|
"script-runner",
|
|
@@ -9160,6 +9231,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9160
9231
|
"system",
|
|
9161
9232
|
"tamper",
|
|
9162
9233
|
"temperature-sensor",
|
|
9234
|
+
"template",
|
|
9163
9235
|
"terminal-session",
|
|
9164
9236
|
"toast",
|
|
9165
9237
|
"training",
|
|
@@ -10100,6 +10172,11 @@ Object.freeze({
|
|
|
10100
10172
|
"standby"
|
|
10101
10173
|
]
|
|
10102
10174
|
},
|
|
10175
|
+
{
|
|
10176
|
+
"path": "stateSource",
|
|
10177
|
+
"valueType": "enum",
|
|
10178
|
+
"enumValues": ["reported", "assumed"]
|
|
10179
|
+
},
|
|
10103
10180
|
{
|
|
10104
10181
|
"path": "volumeLevel",
|
|
10105
10182
|
"valueType": "number"
|
|
@@ -10375,6 +10452,68 @@ Object.freeze({
|
|
|
10375
10452
|
{
|
|
10376
10453
|
"path": "volume",
|
|
10377
10454
|
"valueType": "number"
|
|
10455
|
+
},
|
|
10456
|
+
{
|
|
10457
|
+
"path": "lastFeedGrams",
|
|
10458
|
+
"valueType": "number"
|
|
10459
|
+
},
|
|
10460
|
+
{
|
|
10461
|
+
"path": "lastFeedHopper1",
|
|
10462
|
+
"valueType": "number"
|
|
10463
|
+
},
|
|
10464
|
+
{
|
|
10465
|
+
"path": "lastFeedHopper2",
|
|
10466
|
+
"valueType": "number"
|
|
10467
|
+
}
|
|
10468
|
+
],
|
|
10469
|
+
"pet-fountain": [
|
|
10470
|
+
{
|
|
10471
|
+
"path": "waterLevel",
|
|
10472
|
+
"valueType": "number"
|
|
10473
|
+
},
|
|
10474
|
+
{
|
|
10475
|
+
"path": "waterVolume",
|
|
10476
|
+
"valueType": "number"
|
|
10477
|
+
},
|
|
10478
|
+
{
|
|
10479
|
+
"path": "empty",
|
|
10480
|
+
"valueType": "boolean"
|
|
10481
|
+
},
|
|
10482
|
+
{
|
|
10483
|
+
"path": "pumpRunning",
|
|
10484
|
+
"valueType": "boolean"
|
|
10485
|
+
},
|
|
10486
|
+
{
|
|
10487
|
+
"path": "pumpIntervalEnabled",
|
|
10488
|
+
"valueType": "boolean"
|
|
10489
|
+
},
|
|
10490
|
+
{
|
|
10491
|
+
"path": "pumpOnTime",
|
|
10492
|
+
"valueType": "number"
|
|
10493
|
+
},
|
|
10494
|
+
{
|
|
10495
|
+
"path": "pumpOffTime",
|
|
10496
|
+
"valueType": "number"
|
|
10497
|
+
},
|
|
10498
|
+
{
|
|
10499
|
+
"path": "lastDrink",
|
|
10500
|
+
"valueType": "number"
|
|
10501
|
+
},
|
|
10502
|
+
{
|
|
10503
|
+
"path": "currentHourDrink",
|
|
10504
|
+
"valueType": "number"
|
|
10505
|
+
},
|
|
10506
|
+
{
|
|
10507
|
+
"path": "last24hDrink",
|
|
10508
|
+
"valueType": "number"
|
|
10509
|
+
},
|
|
10510
|
+
{
|
|
10511
|
+
"path": "todayDrink",
|
|
10512
|
+
"valueType": "number"
|
|
10513
|
+
},
|
|
10514
|
+
{
|
|
10515
|
+
"path": "error",
|
|
10516
|
+
"valueType": "string"
|
|
10378
10517
|
}
|
|
10379
10518
|
],
|
|
10380
10519
|
"power-meter": [
|
|
@@ -10916,7 +11055,7 @@ Object.freeze({
|
|
|
10916
11055
|
* can answer "may a rule actuate this?" without importing the schema barrel
|
|
10917
11056
|
* (~144MB RSS per runner, D28).
|
|
10918
11057
|
*
|
|
10919
|
-
* Coverage:
|
|
11058
|
+
* Coverage: 97 device-scoped capabilities.
|
|
10920
11059
|
*/
|
|
10921
11060
|
var DEVICE_SCOPED_CAPS = new Set([
|
|
10922
11061
|
"accessories",
|
|
@@ -10982,6 +11121,7 @@ var DEVICE_SCOPED_CAPS = new Set([
|
|
|
10982
11121
|
"numeric-sensor",
|
|
10983
11122
|
"osd",
|
|
10984
11123
|
"pet-feeder",
|
|
11124
|
+
"pet-fountain",
|
|
10985
11125
|
"pipeline-analytics",
|
|
10986
11126
|
"power-meter",
|
|
10987
11127
|
"presence",
|
|
@@ -11060,7 +11200,7 @@ function sleep(ms) {
|
|
|
11060
11200
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11061
11201
|
}
|
|
11062
11202
|
//#endregion
|
|
11063
|
-
//#region ../types/dist/err-msg-
|
|
11203
|
+
//#region ../types/dist/err-msg-DWXWc0Di.mjs
|
|
11064
11204
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
11065
11205
|
DeviceType["Camera"] = "camera";
|
|
11066
11206
|
DeviceType["Hub"] = "hub";
|
|
@@ -11152,6 +11292,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
11152
11292
|
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
11153
11293
|
* integrations sharing the same food/desiccant/hopper surface. */
|
|
11154
11294
|
DeviceType["PetFeeder"] = "pet-feeder";
|
|
11295
|
+
/** Pet water fountain — tank level, water drunk, pump and its duty cycle.
|
|
11296
|
+
* Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
|
|
11297
|
+
* of a container and is BUILT as a composed device (D967). */
|
|
11298
|
+
DeviceType["PetFountain"] = "pet-fountain";
|
|
11155
11299
|
/** Built-in dishwasher — program lifecycle, door / remote-start gates,
|
|
11156
11300
|
* salt and rinse-aid levels. Installed with the `dishwasher` cap.
|
|
11157
11301
|
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
@@ -11422,7 +11566,7 @@ function errMsg(err) {
|
|
|
11422
11566
|
return String(err);
|
|
11423
11567
|
}
|
|
11424
11568
|
//#endregion
|
|
11425
|
-
//#region ../types/dist/composition-
|
|
11569
|
+
//#region ../types/dist/composition-kSuOifuG.mjs
|
|
11426
11570
|
/**
|
|
11427
11571
|
* Deterministic SHA-256 hash of an arbitrary serialisable value. The
|
|
11428
11572
|
* canonical form sorts object keys alphabetically at every depth so two
|
|
@@ -12667,12 +12811,28 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
|
|
|
12667
12811
|
])
|
|
12668
12812
|
})
|
|
12669
12813
|
]);
|
|
12670
|
-
var
|
|
12814
|
+
var forwardArgUnion = union([
|
|
12815
|
+
string(),
|
|
12816
|
+
number(),
|
|
12817
|
+
boolean(),
|
|
12818
|
+
strictObject({ ["$input"]: string().min(1) }),
|
|
12819
|
+
record(string().min(1), lazy(() => forwardArgUnion))
|
|
12820
|
+
]);
|
|
12821
|
+
var CompositionForwardArgSchema = forwardArgUnion;
|
|
12822
|
+
/**
|
|
12823
|
+
* `forward` (D659, amended by D967): one method of a BUILT capability is one
|
|
12824
|
+
* call of `cap.method` on `source`. Without `args` the built method's input
|
|
12825
|
+
* passes through minus `deviceId`; with `args` the template IS the source's
|
|
12826
|
+
* whole input. `deviceId` is always the source's own, never an argument.
|
|
12827
|
+
*/
|
|
12828
|
+
var CompositionForwardCommandSchema = object({
|
|
12671
12829
|
kind: literal("forward"),
|
|
12672
12830
|
source: CompositionSourceRefSchema,
|
|
12673
12831
|
cap: string().min(1),
|
|
12674
|
-
method: string().min(1)
|
|
12675
|
-
|
|
12832
|
+
method: string().min(1),
|
|
12833
|
+
args: record(string().min(1), CompositionForwardArgSchema).optional()
|
|
12834
|
+
});
|
|
12835
|
+
var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
|
|
12676
12836
|
/** An item key inside an item-array cap (`consumables.items[<key>]`). */
|
|
12677
12837
|
var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
12678
12838
|
/**
|
|
@@ -12682,7 +12842,13 @@ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
|
12682
12842
|
*/
|
|
12683
12843
|
var CompositionItemEntrySchema = object({
|
|
12684
12844
|
label: string().min(1).max(80),
|
|
12685
|
-
fields: record(string().min(1), CompositionFieldSourceSchema)
|
|
12845
|
+
fields: record(string().min(1), CompositionFieldSourceSchema),
|
|
12846
|
+
/**
|
|
12847
|
+
* What the cap's `reset` does for THIS item (consumables). Present = the item
|
|
12848
|
+
* is resettable and a `reset` call naming its key forwards here. The item key
|
|
12849
|
+
* selects the command, so it is never an argument: there is no pass-through.
|
|
12850
|
+
*/
|
|
12851
|
+
reset: CompositionForwardCommandSchema.optional()
|
|
12686
12852
|
});
|
|
12687
12853
|
var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
12688
12854
|
kind: literal("fields"),
|
|
@@ -12694,7 +12860,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
|
12694
12860
|
* owns the WHOLE array field: the composed value holds exactly these items.
|
|
12695
12861
|
*/
|
|
12696
12862
|
items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
|
|
12697
|
-
/**
|
|
12863
|
+
/** Method name → what serves it. Only a BUILT capability takes commands (D967). */
|
|
12698
12864
|
commands: record(string().min(1), CompositionCommandTargetSchema).optional()
|
|
12699
12865
|
}), object({
|
|
12700
12866
|
kind: literal("passthrough"),
|
|
@@ -16837,6 +17003,538 @@ object({
|
|
|
16837
17003
|
path: ["expr"]
|
|
16838
17004
|
});
|
|
16839
17005
|
});
|
|
17006
|
+
/**
|
|
17007
|
+
* Identity envelope for a device's upstream-system metadata.
|
|
17008
|
+
*
|
|
17009
|
+
* Two jobs:
|
|
17010
|
+
*
|
|
17011
|
+
* 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
|
|
17012
|
+
* uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
|
|
17013
|
+
* WS-Discovery endpoint UUID, …). Provider code keeps a
|
|
17014
|
+
* `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
|
|
17015
|
+
* without round-tripping through `stableId` / `id` mappings.
|
|
17016
|
+
*
|
|
17017
|
+
* 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
|
|
17018
|
+
* one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
|
|
17019
|
+
* for vendors where MAC is the natural key). For systems where the
|
|
17020
|
+
* live dispatch key can be renamed (HA `entity_id` can be edited),
|
|
17021
|
+
* providers detect renames by matching `uniqueId` and patch `id`
|
|
17022
|
+
* in-place so dispatch keeps working.
|
|
17023
|
+
*
|
|
17024
|
+
* **Display unit and precision are NOT stored here.** They are a read-time
|
|
17025
|
+
* preference (D944). A polymorphic reading stores its `quantity` here once,
|
|
17026
|
+
* at adopt, and a date/time value stores `dateTimeShape` the same way —
|
|
17027
|
+
* neither is repeated on each sample. The `device_class` tag is an
|
|
17028
|
+
* HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
|
|
17029
|
+
* to derive the CamStack `DeviceRole`; it is never stored after that point.
|
|
17030
|
+
*
|
|
17031
|
+
* The `raw` escape hatch carries provider-specific attributes that don't
|
|
17032
|
+
* fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
|
|
17033
|
+
* details, …). Providers control what lands here.
|
|
17034
|
+
*/
|
|
17035
|
+
var SourceInfoSchema = object({
|
|
17036
|
+
/** Live dispatch key — mutable when the source system allows rename. */
|
|
17037
|
+
id: string(),
|
|
17038
|
+
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
17039
|
+
system: string(),
|
|
17040
|
+
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
17041
|
+
* Used to detect rename when `id` changes. */
|
|
17042
|
+
uniqueId: string().optional(),
|
|
17043
|
+
/** Quantity of a polymorphic reading, written once at adopt (D944). */
|
|
17044
|
+
quantity: ReadingQuantitySchema.optional(),
|
|
17045
|
+
/** Date / time / datetime shape of a string reading, written once at adopt. */
|
|
17046
|
+
dateTimeShape: _enum([
|
|
17047
|
+
"date",
|
|
17048
|
+
"time",
|
|
17049
|
+
"datetime"
|
|
17050
|
+
]).optional(),
|
|
17051
|
+
/** Provider-specific extras that don't fit the structured slots.
|
|
17052
|
+
* Whitelist what goes here per provider — do NOT dump entire HA
|
|
17053
|
+
* attribute blobs (would explode the meta JSON column). */
|
|
17054
|
+
raw: record(string(), unknown()).optional()
|
|
17055
|
+
});
|
|
17056
|
+
/**
|
|
17057
|
+
* Build the synthetic SourceInfo every existing provider falls back to
|
|
17058
|
+
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
17059
|
+
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
17060
|
+
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
17061
|
+
* id. Real upstream identity replaces this once a provider migrates and
|
|
17062
|
+
* calls `updateSourceInfo()` with concrete values.
|
|
17063
|
+
*/
|
|
17064
|
+
function synthesizeSourceInfo(input) {
|
|
17065
|
+
return {
|
|
17066
|
+
id: input.stableId,
|
|
17067
|
+
system: input.addonId
|
|
17068
|
+
};
|
|
17069
|
+
}
|
|
17070
|
+
/**
|
|
17071
|
+
* Shallow-merge a partial patch over a prior SourceInfo. `undefined`
|
|
17072
|
+
* patch values are ignored (no field clobber); to clear a field
|
|
17073
|
+
* explicitly, providers omit it from the patch and rebuild from scratch
|
|
17074
|
+
* via `updateSourceInfo({})` cycles — uncommon enough that we don't
|
|
17075
|
+
* bother with a `null`-means-delete sentinel.
|
|
17076
|
+
*
|
|
17077
|
+
* The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
|
|
17078
|
+
* merges over existing `raw` keys instead of replacing the whole bag.
|
|
17079
|
+
*/
|
|
17080
|
+
function mergeSourceInfo(prev, patch) {
|
|
17081
|
+
const next = { ...prev };
|
|
17082
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
17083
|
+
if (v === void 0) continue;
|
|
17084
|
+
if (k === "raw" && typeof v === "object" && v !== null) {
|
|
17085
|
+
next.raw = {
|
|
17086
|
+
...prev.raw,
|
|
17087
|
+
...v
|
|
17088
|
+
};
|
|
17089
|
+
continue;
|
|
17090
|
+
}
|
|
17091
|
+
next[k] = v;
|
|
17092
|
+
}
|
|
17093
|
+
return next;
|
|
17094
|
+
}
|
|
17095
|
+
/**
|
|
17096
|
+
* Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
|
|
17097
|
+
* blob is a free-form `Record<string, unknown>` shared with hardware-
|
|
17098
|
+
* identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
|
|
17099
|
+
* lives under a single nested key to keep it composable with those.
|
|
17100
|
+
*/
|
|
17101
|
+
var SOURCE_INFO_METADATA_KEY = "sourceInfo";
|
|
17102
|
+
/**
|
|
17103
|
+
* Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
|
|
17104
|
+
* with Zod. Returns `null` when the blob is missing or the nested
|
|
17105
|
+
* `sourceInfo` key fails validation — callers fall back to the
|
|
17106
|
+
* synthetic default in that case (see `synthesizeSourceInfo`).
|
|
17107
|
+
*/
|
|
17108
|
+
function extractSourceInfoFromMetadata(metadata) {
|
|
17109
|
+
if (!metadata) return null;
|
|
17110
|
+
const raw = metadata[SOURCE_INFO_METADATA_KEY];
|
|
17111
|
+
if (!raw) return null;
|
|
17112
|
+
const parsed = SourceInfoSchema.safeParse(raw);
|
|
17113
|
+
if (!parsed.success) return null;
|
|
17114
|
+
return parsed.data;
|
|
17115
|
+
}
|
|
17116
|
+
/**
|
|
17117
|
+
* Frozen unit → spec table. `•` in the doc marks each dimension's canonical
|
|
17118
|
+
* unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
|
|
17119
|
+
* `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
|
|
17120
|
+
* decimals). K derives from `°C = K − 273.15`.
|
|
17121
|
+
*/
|
|
17122
|
+
var UNIT_TABLE = {
|
|
17123
|
+
"°C": {
|
|
17124
|
+
dimension: "temperature",
|
|
17125
|
+
factor: 1,
|
|
17126
|
+
offset: 0
|
|
17127
|
+
},
|
|
17128
|
+
"°F": {
|
|
17129
|
+
dimension: "temperature",
|
|
17130
|
+
factor: 5 / 9,
|
|
17131
|
+
offset: -160 / 9
|
|
17132
|
+
},
|
|
17133
|
+
K: {
|
|
17134
|
+
dimension: "temperature",
|
|
17135
|
+
factor: 1,
|
|
17136
|
+
offset: -273.15
|
|
17137
|
+
},
|
|
17138
|
+
hPa: {
|
|
17139
|
+
dimension: "pressure",
|
|
17140
|
+
factor: 1,
|
|
17141
|
+
offset: 0
|
|
17142
|
+
},
|
|
17143
|
+
kPa: {
|
|
17144
|
+
dimension: "pressure",
|
|
17145
|
+
factor: 10,
|
|
17146
|
+
offset: 0
|
|
17147
|
+
},
|
|
17148
|
+
Pa: {
|
|
17149
|
+
dimension: "pressure",
|
|
17150
|
+
factor: .01,
|
|
17151
|
+
offset: 0
|
|
17152
|
+
},
|
|
17153
|
+
mbar: {
|
|
17154
|
+
dimension: "pressure",
|
|
17155
|
+
factor: 1,
|
|
17156
|
+
offset: 0
|
|
17157
|
+
},
|
|
17158
|
+
bar: {
|
|
17159
|
+
dimension: "pressure",
|
|
17160
|
+
factor: 1e3,
|
|
17161
|
+
offset: 0
|
|
17162
|
+
},
|
|
17163
|
+
inHg: {
|
|
17164
|
+
dimension: "pressure",
|
|
17165
|
+
factor: 33.8639,
|
|
17166
|
+
offset: 0
|
|
17167
|
+
},
|
|
17168
|
+
mmHg: {
|
|
17169
|
+
dimension: "pressure",
|
|
17170
|
+
factor: 1.33322,
|
|
17171
|
+
offset: 0
|
|
17172
|
+
},
|
|
17173
|
+
psi: {
|
|
17174
|
+
dimension: "pressure",
|
|
17175
|
+
factor: 68.9476,
|
|
17176
|
+
offset: 0
|
|
17177
|
+
},
|
|
17178
|
+
"m/s": {
|
|
17179
|
+
dimension: "speed",
|
|
17180
|
+
factor: 1,
|
|
17181
|
+
offset: 0
|
|
17182
|
+
},
|
|
17183
|
+
"km/h": {
|
|
17184
|
+
dimension: "speed",
|
|
17185
|
+
factor: 1 / 3.6,
|
|
17186
|
+
offset: 0
|
|
17187
|
+
},
|
|
17188
|
+
mph: {
|
|
17189
|
+
dimension: "speed",
|
|
17190
|
+
factor: .44704,
|
|
17191
|
+
offset: 0
|
|
17192
|
+
},
|
|
17193
|
+
kn: {
|
|
17194
|
+
dimension: "speed",
|
|
17195
|
+
factor: .514444,
|
|
17196
|
+
offset: 0
|
|
17197
|
+
},
|
|
17198
|
+
"mm/h": {
|
|
17199
|
+
dimension: "precipitation-rate",
|
|
17200
|
+
factor: 1,
|
|
17201
|
+
offset: 0
|
|
17202
|
+
},
|
|
17203
|
+
"in/h": {
|
|
17204
|
+
dimension: "precipitation-rate",
|
|
17205
|
+
factor: 25.4,
|
|
17206
|
+
offset: 0
|
|
17207
|
+
},
|
|
17208
|
+
m: {
|
|
17209
|
+
dimension: "length",
|
|
17210
|
+
factor: 1,
|
|
17211
|
+
offset: 0
|
|
17212
|
+
},
|
|
17213
|
+
mm: {
|
|
17214
|
+
dimension: "length",
|
|
17215
|
+
factor: .001,
|
|
17216
|
+
offset: 0
|
|
17217
|
+
},
|
|
17218
|
+
cm: {
|
|
17219
|
+
dimension: "length",
|
|
17220
|
+
factor: .01,
|
|
17221
|
+
offset: 0
|
|
17222
|
+
},
|
|
17223
|
+
km: {
|
|
17224
|
+
dimension: "length",
|
|
17225
|
+
factor: 1e3,
|
|
17226
|
+
offset: 0
|
|
17227
|
+
},
|
|
17228
|
+
in: {
|
|
17229
|
+
dimension: "length",
|
|
17230
|
+
factor: .0254,
|
|
17231
|
+
offset: 0
|
|
17232
|
+
},
|
|
17233
|
+
ft: {
|
|
17234
|
+
dimension: "length",
|
|
17235
|
+
factor: .3048,
|
|
17236
|
+
offset: 0
|
|
17237
|
+
},
|
|
17238
|
+
mi: {
|
|
17239
|
+
dimension: "length",
|
|
17240
|
+
factor: 1609.344,
|
|
17241
|
+
offset: 0
|
|
17242
|
+
},
|
|
17243
|
+
"m²": {
|
|
17244
|
+
dimension: "area",
|
|
17245
|
+
factor: 1,
|
|
17246
|
+
offset: 0
|
|
17247
|
+
},
|
|
17248
|
+
lx: {
|
|
17249
|
+
dimension: "illuminance",
|
|
17250
|
+
factor: 1,
|
|
17251
|
+
offset: 0
|
|
17252
|
+
},
|
|
17253
|
+
"W/m²": {
|
|
17254
|
+
dimension: "irradiance",
|
|
17255
|
+
factor: 1,
|
|
17256
|
+
offset: 0
|
|
17257
|
+
},
|
|
17258
|
+
W: {
|
|
17259
|
+
dimension: "power",
|
|
17260
|
+
factor: 1,
|
|
17261
|
+
offset: 0
|
|
17262
|
+
},
|
|
17263
|
+
kW: {
|
|
17264
|
+
dimension: "power",
|
|
17265
|
+
factor: 1e3,
|
|
17266
|
+
offset: 0
|
|
17267
|
+
},
|
|
17268
|
+
VA: {
|
|
17269
|
+
dimension: "apparent-power",
|
|
17270
|
+
factor: 1,
|
|
17271
|
+
offset: 0
|
|
17272
|
+
},
|
|
17273
|
+
Wh: {
|
|
17274
|
+
dimension: "energy",
|
|
17275
|
+
factor: 1,
|
|
17276
|
+
offset: 0
|
|
17277
|
+
},
|
|
17278
|
+
kWh: {
|
|
17279
|
+
dimension: "energy",
|
|
17280
|
+
factor: 1e3,
|
|
17281
|
+
offset: 0
|
|
17282
|
+
},
|
|
17283
|
+
MWh: {
|
|
17284
|
+
dimension: "energy",
|
|
17285
|
+
factor: 1e6,
|
|
17286
|
+
offset: 0
|
|
17287
|
+
},
|
|
17288
|
+
V: {
|
|
17289
|
+
dimension: "voltage",
|
|
17290
|
+
factor: 1,
|
|
17291
|
+
offset: 0
|
|
17292
|
+
},
|
|
17293
|
+
mV: {
|
|
17294
|
+
dimension: "voltage",
|
|
17295
|
+
factor: .001,
|
|
17296
|
+
offset: 0
|
|
17297
|
+
},
|
|
17298
|
+
A: {
|
|
17299
|
+
dimension: "current",
|
|
17300
|
+
factor: 1,
|
|
17301
|
+
offset: 0
|
|
17302
|
+
},
|
|
17303
|
+
mA: {
|
|
17304
|
+
dimension: "current",
|
|
17305
|
+
factor: .001,
|
|
17306
|
+
offset: 0
|
|
17307
|
+
},
|
|
17308
|
+
ppm: {
|
|
17309
|
+
dimension: "concentration-volume",
|
|
17310
|
+
factor: 1,
|
|
17311
|
+
offset: 0
|
|
17312
|
+
},
|
|
17313
|
+
ppb: {
|
|
17314
|
+
dimension: "concentration-volume",
|
|
17315
|
+
factor: .001,
|
|
17316
|
+
offset: 0
|
|
17317
|
+
},
|
|
17318
|
+
"µg/m³": {
|
|
17319
|
+
dimension: "concentration-mass",
|
|
17320
|
+
factor: 1,
|
|
17321
|
+
offset: 0
|
|
17322
|
+
},
|
|
17323
|
+
"mg/m³": {
|
|
17324
|
+
dimension: "concentration-mass",
|
|
17325
|
+
factor: 1e3,
|
|
17326
|
+
offset: 0
|
|
17327
|
+
},
|
|
17328
|
+
kg: {
|
|
17329
|
+
dimension: "mass",
|
|
17330
|
+
factor: 1,
|
|
17331
|
+
offset: 0
|
|
17332
|
+
},
|
|
17333
|
+
g: {
|
|
17334
|
+
dimension: "mass",
|
|
17335
|
+
factor: .001,
|
|
17336
|
+
offset: 0
|
|
17337
|
+
},
|
|
17338
|
+
lb: {
|
|
17339
|
+
dimension: "mass",
|
|
17340
|
+
factor: .453592,
|
|
17341
|
+
offset: 0
|
|
17342
|
+
},
|
|
17343
|
+
oz: {
|
|
17344
|
+
dimension: "mass",
|
|
17345
|
+
factor: .0283495,
|
|
17346
|
+
offset: 0
|
|
17347
|
+
},
|
|
17348
|
+
"%": {
|
|
17349
|
+
dimension: "percentage",
|
|
17350
|
+
factor: 1,
|
|
17351
|
+
offset: 0
|
|
17352
|
+
},
|
|
17353
|
+
L: {
|
|
17354
|
+
dimension: "volume",
|
|
17355
|
+
factor: 1,
|
|
17356
|
+
offset: 0
|
|
17357
|
+
},
|
|
17358
|
+
mL: {
|
|
17359
|
+
dimension: "volume",
|
|
17360
|
+
factor: .001,
|
|
17361
|
+
offset: 0
|
|
17362
|
+
},
|
|
17363
|
+
gal: {
|
|
17364
|
+
dimension: "volume",
|
|
17365
|
+
factor: 3.785411784,
|
|
17366
|
+
offset: 0
|
|
17367
|
+
},
|
|
17368
|
+
s: {
|
|
17369
|
+
dimension: "duration",
|
|
17370
|
+
factor: 1,
|
|
17371
|
+
offset: 0
|
|
17372
|
+
},
|
|
17373
|
+
ms: {
|
|
17374
|
+
dimension: "duration",
|
|
17375
|
+
factor: .001,
|
|
17376
|
+
offset: 0
|
|
17377
|
+
},
|
|
17378
|
+
min: {
|
|
17379
|
+
dimension: "duration",
|
|
17380
|
+
factor: 60,
|
|
17381
|
+
offset: 0
|
|
17382
|
+
},
|
|
17383
|
+
h: {
|
|
17384
|
+
dimension: "duration",
|
|
17385
|
+
factor: 3600,
|
|
17386
|
+
offset: 0
|
|
17387
|
+
},
|
|
17388
|
+
d: {
|
|
17389
|
+
dimension: "duration",
|
|
17390
|
+
factor: 86400,
|
|
17391
|
+
offset: 0
|
|
17392
|
+
},
|
|
17393
|
+
B: {
|
|
17394
|
+
dimension: "information",
|
|
17395
|
+
factor: 1,
|
|
17396
|
+
offset: 0
|
|
17397
|
+
},
|
|
17398
|
+
KiB: {
|
|
17399
|
+
dimension: "information",
|
|
17400
|
+
factor: 1024,
|
|
17401
|
+
offset: 0
|
|
17402
|
+
},
|
|
17403
|
+
MiB: {
|
|
17404
|
+
dimension: "information",
|
|
17405
|
+
factor: 1024 ** 2,
|
|
17406
|
+
offset: 0
|
|
17407
|
+
},
|
|
17408
|
+
GiB: {
|
|
17409
|
+
dimension: "information",
|
|
17410
|
+
factor: 1024 ** 3,
|
|
17411
|
+
offset: 0
|
|
17412
|
+
},
|
|
17413
|
+
TiB: {
|
|
17414
|
+
dimension: "information",
|
|
17415
|
+
factor: 1024 ** 4,
|
|
17416
|
+
offset: 0
|
|
17417
|
+
},
|
|
17418
|
+
kB: {
|
|
17419
|
+
dimension: "information",
|
|
17420
|
+
factor: 1e3,
|
|
17421
|
+
offset: 0
|
|
17422
|
+
},
|
|
17423
|
+
MB: {
|
|
17424
|
+
dimension: "information",
|
|
17425
|
+
factor: 1e6,
|
|
17426
|
+
offset: 0
|
|
17427
|
+
},
|
|
17428
|
+
GB: {
|
|
17429
|
+
dimension: "information",
|
|
17430
|
+
factor: 1e9,
|
|
17431
|
+
offset: 0
|
|
17432
|
+
},
|
|
17433
|
+
"°": {
|
|
17434
|
+
dimension: "angle",
|
|
17435
|
+
factor: 1,
|
|
17436
|
+
offset: 0
|
|
17437
|
+
},
|
|
17438
|
+
dBFS: {
|
|
17439
|
+
dimension: "sound-level",
|
|
17440
|
+
factor: 1,
|
|
17441
|
+
offset: 0
|
|
17442
|
+
},
|
|
17443
|
+
dBm: {
|
|
17444
|
+
dimension: "signal-level",
|
|
17445
|
+
factor: 1,
|
|
17446
|
+
offset: 0
|
|
17447
|
+
},
|
|
17448
|
+
mired: {
|
|
17449
|
+
dimension: "colour-temperature",
|
|
17450
|
+
factor: 1,
|
|
17451
|
+
offset: 0
|
|
17452
|
+
},
|
|
17453
|
+
kbps: {
|
|
17454
|
+
dimension: "bitrate",
|
|
17455
|
+
factor: 1,
|
|
17456
|
+
offset: 0
|
|
17457
|
+
},
|
|
17458
|
+
Mbps: {
|
|
17459
|
+
dimension: "bitrate",
|
|
17460
|
+
factor: 1e3,
|
|
17461
|
+
offset: 0
|
|
17462
|
+
},
|
|
17463
|
+
fps: {
|
|
17464
|
+
dimension: "rate",
|
|
17465
|
+
factor: 1,
|
|
17466
|
+
offset: 0
|
|
17467
|
+
}
|
|
17468
|
+
};
|
|
17469
|
+
/** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
|
|
17470
|
+
function specFor(unit) {
|
|
17471
|
+
return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
|
|
17472
|
+
}
|
|
17473
|
+
/** Value in `spec`'s unit → its dimension's canonical unit. */
|
|
17474
|
+
function toCanonical(value, spec) {
|
|
17475
|
+
return value * spec.factor + spec.offset;
|
|
17476
|
+
}
|
|
17477
|
+
/** Canonical-unit value → the unit described by `spec`. */
|
|
17478
|
+
function fromCanonical(canonical, spec) {
|
|
17479
|
+
return (canonical - spec.offset) / spec.factor;
|
|
17480
|
+
}
|
|
17481
|
+
/**
|
|
17482
|
+
* Graceful variant for the render path: returns the converted value, or
|
|
17483
|
+
* `undefined` when the conversion is refused (unknown unit / cross-dimension).
|
|
17484
|
+
* Never throws — a render caller falls back to the raw value + source unit.
|
|
17485
|
+
*/
|
|
17486
|
+
function tryConvertUnit(value, from, to) {
|
|
17487
|
+
if (from === to) return value;
|
|
17488
|
+
const fromSpec = specFor(from);
|
|
17489
|
+
if (!fromSpec) return void 0;
|
|
17490
|
+
const toSpec = specFor(to);
|
|
17491
|
+
if (!toSpec) return void 0;
|
|
17492
|
+
if (fromSpec.dimension !== toSpec.dimension) return void 0;
|
|
17493
|
+
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
17494
|
+
}
|
|
17495
|
+
/**
|
|
17496
|
+
* A number a person types for a physical quantity — the input side of the
|
|
17497
|
+
* value + unit contract (D972).
|
|
17498
|
+
*
|
|
17499
|
+
* Readings are stored in the base unit of their quantity (D944); a cap method
|
|
17500
|
+
* param is stored in the unit its input schema DECLARES (`paramMeta`). The
|
|
17501
|
+
* wire pair {@link QuantityValue} says "this number is expressed in this
|
|
17502
|
+
* unit"; {@link convertQuantityInput} brings it to the stored unit and refuses,
|
|
17503
|
+
* with a typed {@link QuantityUnitError}, a unit that cannot be that quantity.
|
|
17504
|
+
*/
|
|
17505
|
+
/** A unit that cannot be applied to the number it was given with. */
|
|
17506
|
+
var QuantityUnitError = class extends Error {
|
|
17507
|
+
code;
|
|
17508
|
+
constructor(code, message) {
|
|
17509
|
+
super(`[${code}] ${message}`);
|
|
17510
|
+
this.name = "QuantityUnitError";
|
|
17511
|
+
this.code = code;
|
|
17512
|
+
}
|
|
17513
|
+
};
|
|
17514
|
+
/** The wire pair: `value` is expressed in `unit`. */
|
|
17515
|
+
var QuantityValueSchema = object({
|
|
17516
|
+
value: number(),
|
|
17517
|
+
unit: string().min(1)
|
|
17518
|
+
});
|
|
17519
|
+
/**
|
|
17520
|
+
* Zod `.meta()` bag for a cap METHOD param that is a physical quantity — the
|
|
17521
|
+
* input-side twin of `readingMeta`. The value is carried in `unit` (a gram
|
|
17522
|
+
* portion stays in grams); the unit must belong to the quantity, checked once
|
|
17523
|
+
* when the cap is declared.
|
|
17524
|
+
*/
|
|
17525
|
+
function paramMeta(declared) {
|
|
17526
|
+
const base = baseUnitOf(declared.quantity);
|
|
17527
|
+
if (base === void 0) throw new QuantityUnitError("unknown-unit", `quantity "${declared.quantity}" has no unit`);
|
|
17528
|
+
if (tryConvertUnit(1, declared.unit, base) === void 0) throw new QuantityUnitError("cross-dimension", `"${declared.unit}" is not a unit of ${declared.quantity}`);
|
|
17529
|
+
return {
|
|
17530
|
+
quantity: declared.quantity,
|
|
17531
|
+
unit: declared.unit
|
|
17532
|
+
};
|
|
17533
|
+
}
|
|
17534
|
+
object({
|
|
17535
|
+
quantity: ReadingQuantitySchema,
|
|
17536
|
+
unit: string().min(1)
|
|
17537
|
+
});
|
|
16840
17538
|
/** How a leaf compares a device field to a value. Derived from the field's
|
|
16841
17539
|
* `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
|
|
16842
17540
|
var AutomationConditionOperatorSchema = _enum([
|
|
@@ -16859,8 +17557,10 @@ var AutomationConditionLeafSchema = object({
|
|
|
16859
17557
|
string(),
|
|
16860
17558
|
number(),
|
|
16861
17559
|
boolean(),
|
|
16862
|
-
array(union([string(), number()]))
|
|
16863
|
-
|
|
17560
|
+
array(union([string(), number()])),
|
|
17561
|
+
QuantityValueSchema
|
|
17562
|
+
]),
|
|
17563
|
+
unit: string().min(1).optional()
|
|
16864
17564
|
});
|
|
16865
17565
|
var AutomationConditionStationaryPresentSchema = object({
|
|
16866
17566
|
kind: literal("stationary-present"),
|
|
@@ -17013,7 +17713,13 @@ var AutomationCapStepSchema = object({
|
|
|
17013
17713
|
/** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
|
|
17014
17714
|
* SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
|
|
17015
17715
|
* the expression leaf. */
|
|
17016
|
-
args: record(string(), unknown()).optional()
|
|
17716
|
+
args: record(string(), unknown()).optional(),
|
|
17717
|
+
/**
|
|
17718
|
+
* The unit the person picked per arg (display only). `args` always hold the
|
|
17719
|
+
* value in the unit the method's input DECLARES (`paramMeta`); on a write an
|
|
17720
|
+
* arg may be a {@link QuantityValue}, which the save boundary converts.
|
|
17721
|
+
*/
|
|
17722
|
+
argUnits: record(string(), string().min(1)).optional()
|
|
17017
17723
|
});
|
|
17018
17724
|
/**
|
|
17019
17725
|
* The steps an internal Action may contain: cap and wait, and nothing that can
|
|
@@ -17415,6 +18121,437 @@ object({
|
|
|
17415
18121
|
lastRunAt: number().nullable(),
|
|
17416
18122
|
lastError: string().nullable()
|
|
17417
18123
|
});
|
|
18124
|
+
/** A bare number accepted as a base unit — the save succeeded, say so. */
|
|
18125
|
+
var UnitWarningSchema = object({
|
|
18126
|
+
/** Where in the saved record, e.g. `conditions.children.0` or `steps.2.args.grams`. */
|
|
18127
|
+
path: string(),
|
|
18128
|
+
deviceId: number().int().optional(),
|
|
18129
|
+
field: string(),
|
|
18130
|
+
value: number(),
|
|
18131
|
+
assumedUnit: string(),
|
|
18132
|
+
message: string()
|
|
18133
|
+
});
|
|
18134
|
+
/**
|
|
18135
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
18136
|
+
*
|
|
18137
|
+
* Apprise-derived model (see
|
|
18138
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
18139
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
18140
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
18141
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
18142
|
+
* message to what the kind supports — callers never special-case a service.
|
|
18143
|
+
*
|
|
18144
|
+
* DESIGN DECISIONS (locked):
|
|
18145
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
18146
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
18147
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
18148
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
18149
|
+
* alternative would fork the UI per addon and cannot host the
|
|
18150
|
+
* discovery→adopt flow.
|
|
18151
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
18152
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
18153
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
18154
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
18155
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
18156
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
18157
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
18158
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
18159
|
+
* base64 fallback needed.
|
|
18160
|
+
*
|
|
18161
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
18162
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
18163
|
+
* admin "Integrations" page.
|
|
18164
|
+
*/
|
|
18165
|
+
/**
|
|
18166
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
18167
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
18168
|
+
*/
|
|
18169
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
18170
|
+
"image",
|
|
18171
|
+
"video",
|
|
18172
|
+
"gif",
|
|
18173
|
+
"audio",
|
|
18174
|
+
"icon"
|
|
18175
|
+
]);
|
|
18176
|
+
/**
|
|
18177
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
18178
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
18179
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
18180
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
18181
|
+
*/
|
|
18182
|
+
var AttachmentSchema = object({
|
|
18183
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
18184
|
+
url: string().optional(),
|
|
18185
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
18186
|
+
mime: string().optional(),
|
|
18187
|
+
name: string().optional()
|
|
18188
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
18189
|
+
var NotificationFormatSchema = _enum([
|
|
18190
|
+
"text",
|
|
18191
|
+
"markdown",
|
|
18192
|
+
"html"
|
|
18193
|
+
]);
|
|
18194
|
+
/**
|
|
18195
|
+
* The CLOSED icon vocabulary an action button may use.
|
|
18196
|
+
*
|
|
18197
|
+
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
18198
|
+
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
18199
|
+
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
18200
|
+
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
18201
|
+
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
18202
|
+
*
|
|
18203
|
+
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
18204
|
+
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
18205
|
+
* differently.
|
|
18206
|
+
*
|
|
18207
|
+
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
18208
|
+
*
|
|
18209
|
+
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
18210
|
+
* the whole set onto its own renderer's vocabulary through a
|
|
18211
|
+
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
18212
|
+
* fallback, so adding a member here fails every adapter's build until someone
|
|
18213
|
+
* decides its glyph, which is the only place that decision can be made
|
|
18214
|
+
* honestly.
|
|
18215
|
+
*
|
|
18216
|
+
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
18217
|
+
* `disarm` straight through; iOS feeds that string to
|
|
18218
|
+
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
18219
|
+
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
18220
|
+
* mapping, and "the field is documented" is not "the value renders".
|
|
18221
|
+
*
|
|
18222
|
+
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
18223
|
+
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
18224
|
+
* so an addon that emits a token the running hub does not know does not lose an
|
|
18225
|
+
* icon — its whole `send` fails Zod validation and the notification never
|
|
18226
|
+
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
18227
|
+
*/
|
|
18228
|
+
var NotificationActionIconSchema = _enum([
|
|
18229
|
+
"acknowledge",
|
|
18230
|
+
"dismiss",
|
|
18231
|
+
"silence",
|
|
18232
|
+
"snooze",
|
|
18233
|
+
"view",
|
|
18234
|
+
"play",
|
|
18235
|
+
"open",
|
|
18236
|
+
"close",
|
|
18237
|
+
"lock",
|
|
18238
|
+
"unlock",
|
|
18239
|
+
"arm",
|
|
18240
|
+
"arm-home",
|
|
18241
|
+
"arm-away",
|
|
18242
|
+
"arm-night",
|
|
18243
|
+
"disarm",
|
|
18244
|
+
"light",
|
|
18245
|
+
"alert",
|
|
18246
|
+
"camera"
|
|
18247
|
+
]);
|
|
18248
|
+
/** A single tap-through action button. */
|
|
18249
|
+
var NotificationActionSchema = object({
|
|
18250
|
+
id: string(),
|
|
18251
|
+
label: string(),
|
|
18252
|
+
url: string().optional(),
|
|
18253
|
+
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
18254
|
+
icon: NotificationActionIconSchema.optional(),
|
|
18255
|
+
/**
|
|
18256
|
+
* Renders in a warning style where the notifier supports it.
|
|
18257
|
+
*
|
|
18258
|
+
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
18259
|
+
* else — see `notification-center/action-token.ts` for what that does and
|
|
18260
|
+
* does not buy.
|
|
18261
|
+
*/
|
|
18262
|
+
destructive: boolean().optional(),
|
|
18263
|
+
/**
|
|
18264
|
+
* How the tap should REACH the url.
|
|
18265
|
+
*
|
|
18266
|
+
* `navigate` (absent, and every button authored before this field) opens it:
|
|
18267
|
+
* the phone leaves the notification and shows whatever the callback returns.
|
|
18268
|
+
* That is right for a button whose answer the operator wants to read.
|
|
18269
|
+
*
|
|
18270
|
+
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
18271
|
+
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
18272
|
+
* an answer to the notification, and being thrown into a browser tab to
|
|
18273
|
+
* confirm it costs more attention than the notification did. A backend that
|
|
18274
|
+
* cannot do a background call renders it as an ordinary link (the adapters
|
|
18275
|
+
* fall back rather than dropping the button), so this is a preference, never
|
|
18276
|
+
* a requirement.
|
|
18277
|
+
*/
|
|
18278
|
+
mode: _enum(["navigate", "background"]).optional()
|
|
18279
|
+
});
|
|
18280
|
+
/** A delivery parameter value: what a notifier declared it takes. */
|
|
18281
|
+
var DeliveryParamValueSchema = union([
|
|
18282
|
+
string(),
|
|
18283
|
+
number(),
|
|
18284
|
+
boolean()
|
|
18285
|
+
]);
|
|
18286
|
+
/**
|
|
18287
|
+
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
18288
|
+
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
18289
|
+
* never handed one. The admin UI derives its control from this descriptor —
|
|
18290
|
+
* no editor knows a service by name.
|
|
18291
|
+
*
|
|
18292
|
+
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
18293
|
+
* run on the addon cap path).
|
|
18294
|
+
*/
|
|
18295
|
+
var DeliveryParamSchema = object({
|
|
18296
|
+
key: string().min(1),
|
|
18297
|
+
label: string(),
|
|
18298
|
+
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
18299
|
+
labelKey: string().optional(),
|
|
18300
|
+
type: _enum([
|
|
18301
|
+
"enum",
|
|
18302
|
+
"number",
|
|
18303
|
+
"boolean",
|
|
18304
|
+
"string"
|
|
18305
|
+
]),
|
|
18306
|
+
/** `enum` only: the closed set of accepted values. */
|
|
18307
|
+
options: array(object({
|
|
18308
|
+
value: string(),
|
|
18309
|
+
label: string()
|
|
18310
|
+
})).optional(),
|
|
18311
|
+
/** `number` only. */
|
|
18312
|
+
min: number().optional(),
|
|
18313
|
+
max: number().optional(),
|
|
18314
|
+
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
18315
|
+
default: DeliveryParamValueSchema.optional()
|
|
18316
|
+
});
|
|
18317
|
+
/**
|
|
18318
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
18319
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
18320
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
18321
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
18322
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
18323
|
+
* `priority` for that one target.
|
|
18324
|
+
*/
|
|
18325
|
+
var NotificationSchema = object({
|
|
18326
|
+
body: string(),
|
|
18327
|
+
title: string().optional(),
|
|
18328
|
+
format: NotificationFormatSchema.default("text"),
|
|
18329
|
+
priority: number().int().min(1).max(5).default(3),
|
|
18330
|
+
level: string().optional(),
|
|
18331
|
+
attachments: array(AttachmentSchema).optional(),
|
|
18332
|
+
clickUrl: string().optional(),
|
|
18333
|
+
actions: array(NotificationActionSchema).optional(),
|
|
18334
|
+
sound: string().optional(),
|
|
18335
|
+
ttl: number().optional(),
|
|
18336
|
+
tag: string().optional(),
|
|
18337
|
+
deviceId: number().optional(),
|
|
18338
|
+
eventId: string().optional(),
|
|
18339
|
+
metadata: record(string(), unknown()).optional(),
|
|
18340
|
+
/**
|
|
18341
|
+
* Values for the parameters the target's kind DECLARES in
|
|
18342
|
+
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
18343
|
+
* declaration by the sender; a notifier reads only the keys it declared.
|
|
18344
|
+
*/
|
|
18345
|
+
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
18346
|
+
});
|
|
18347
|
+
/** One declared native severity/priority level for a kind. */
|
|
18348
|
+
var TargetKindLevelSchema = object({
|
|
18349
|
+
id: string(),
|
|
18350
|
+
label: string(),
|
|
18351
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
18352
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
18353
|
+
flags: object({
|
|
18354
|
+
critical: boolean().optional(),
|
|
18355
|
+
silent: boolean().optional(),
|
|
18356
|
+
noPush: boolean().optional()
|
|
18357
|
+
}).optional(),
|
|
18358
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
18359
|
+
requires: array(string()).optional(),
|
|
18360
|
+
description: string().optional()
|
|
18361
|
+
});
|
|
18362
|
+
/** The full capability block consulted before dispatch. */
|
|
18363
|
+
var TargetKindCapsSchema = object({
|
|
18364
|
+
attachments: object({
|
|
18365
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
18366
|
+
mode: _enum([
|
|
18367
|
+
"url",
|
|
18368
|
+
"bytes",
|
|
18369
|
+
"both"
|
|
18370
|
+
]),
|
|
18371
|
+
max: number().int().nonnegative(),
|
|
18372
|
+
maxBytes: number().int().positive().optional()
|
|
18373
|
+
}),
|
|
18374
|
+
/** Max action buttons (0 = none). */
|
|
18375
|
+
actions: number().int().nonnegative(),
|
|
18376
|
+
/**
|
|
18377
|
+
* Whether this kind renders a per-action ICON.
|
|
18378
|
+
*
|
|
18379
|
+
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
18380
|
+
* run on the addon cap path — three production failures in one day taught
|
|
18381
|
+
* this repo that once. Absent is read as false by the degrade engine, which
|
|
18382
|
+
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
18383
|
+
* assumed and dropped costs the operator's trust in the field.
|
|
18384
|
+
*/
|
|
18385
|
+
actionIcons: boolean().optional(),
|
|
18386
|
+
levels: array(TargetKindLevelSchema),
|
|
18387
|
+
format: array(NotificationFormatSchema),
|
|
18388
|
+
clickUrl: boolean(),
|
|
18389
|
+
sound: boolean(),
|
|
18390
|
+
ttl: boolean(),
|
|
18391
|
+
bodyMaxLen: number().int().positive()
|
|
18392
|
+
});
|
|
18393
|
+
/**
|
|
18394
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
18395
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
18396
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
18397
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
18398
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
18399
|
+
*/
|
|
18400
|
+
var ConfigSchemaPassthrough$3 = unknown();
|
|
18401
|
+
var TargetKindSchema = object({
|
|
18402
|
+
kind: string(),
|
|
18403
|
+
label: string(),
|
|
18404
|
+
icon: string(),
|
|
18405
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
18406
|
+
addonId: string(),
|
|
18407
|
+
/**
|
|
18408
|
+
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
18409
|
+
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
18410
|
+
* when the addon bundles no icon for that kind — the client then falls back
|
|
18411
|
+
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
18412
|
+
*
|
|
18413
|
+
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
18414
|
+
* client, and a native client joins it onto its own hub base.
|
|
18415
|
+
*
|
|
18416
|
+
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
18417
|
+
* field that survived only because the runtime cap-router forwards provider
|
|
18418
|
+
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
18419
|
+
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
18420
|
+
* broken silently the moment output validation was tightened anywhere.
|
|
18421
|
+
*/
|
|
18422
|
+
iconUrl: string().optional(),
|
|
18423
|
+
/**
|
|
18424
|
+
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
18425
|
+
*
|
|
18426
|
+
* The server knows this and therefore says it, because the client cannot
|
|
18427
|
+
* safely guess: a React-Native client renders SVG and raster through two
|
|
18428
|
+
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
18429
|
+
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
18430
|
+
* placeholder glyph for every vector icon while the web build looked fine.
|
|
18431
|
+
*
|
|
18432
|
+
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
18433
|
+
* not been updated — a client that cannot determine the type should prefer
|
|
18434
|
+
* its raster path, which is the safe default for an unknown image.
|
|
18435
|
+
*/
|
|
18436
|
+
iconMediaType: string().optional(),
|
|
18437
|
+
configSchema: ConfigSchemaPassthrough$3,
|
|
18438
|
+
/**
|
|
18439
|
+
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
18440
|
+
* base URL, instance, chat, recipient). A save that changes any of them
|
|
18441
|
+
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
18442
|
+
* editor uses this to ask for the secret on exactly those edits instead of
|
|
18443
|
+
* on every edit.
|
|
18444
|
+
*
|
|
18445
|
+
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
18446
|
+
* kinds) and from a provider older than this field — an editor that finds
|
|
18447
|
+
* it absent must assume ANY edit may need the secret.
|
|
18448
|
+
*/
|
|
18449
|
+
destinationKeys: array(string()).optional(),
|
|
18450
|
+
supportsDiscovery: boolean(),
|
|
18451
|
+
/**
|
|
18452
|
+
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
18453
|
+
* Absent = none; the rule editor then offers nothing.
|
|
18454
|
+
*/
|
|
18455
|
+
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
18456
|
+
caps: TargetKindCapsSchema
|
|
18457
|
+
});
|
|
18458
|
+
/**
|
|
18459
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
18460
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
18461
|
+
* round-trip a stored secret to the UI.
|
|
18462
|
+
*/
|
|
18463
|
+
var TargetSchema = object({
|
|
18464
|
+
id: string(),
|
|
18465
|
+
name: string(),
|
|
18466
|
+
kind: string(),
|
|
18467
|
+
addonId: string(),
|
|
18468
|
+
enabled: boolean(),
|
|
18469
|
+
config: record(string(), unknown())
|
|
18470
|
+
});
|
|
18471
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
18472
|
+
var DiscoveredTargetSchema = object({
|
|
18473
|
+
kind: string(),
|
|
18474
|
+
suggestedName: string(),
|
|
18475
|
+
config: record(string(), unknown())
|
|
18476
|
+
});
|
|
18477
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
18478
|
+
var RenderedAsSchema = object({
|
|
18479
|
+
level: string(),
|
|
18480
|
+
format: NotificationFormatSchema,
|
|
18481
|
+
attachmentsSent: number().int().nonnegative(),
|
|
18482
|
+
actionsSent: number().int().nonnegative(),
|
|
18483
|
+
truncated: boolean(),
|
|
18484
|
+
dropped: array(string())
|
|
18485
|
+
});
|
|
18486
|
+
var SendResultSchema = object({
|
|
18487
|
+
success: boolean(),
|
|
18488
|
+
error: string().optional(),
|
|
18489
|
+
renderedAs: RenderedAsSchema.optional()
|
|
18490
|
+
});
|
|
18491
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
18492
|
+
var TestResultSchema = SendResultSchema;
|
|
18493
|
+
/**
|
|
18494
|
+
* Pre-save credential probe against a partial (unsaved) target config.
|
|
18495
|
+
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
18496
|
+
*/
|
|
18497
|
+
var ProbeTargetConfigResultSchema = object({
|
|
18498
|
+
ok: boolean(),
|
|
18499
|
+
resolvedUrl: string(),
|
|
18500
|
+
httpStatus: number().int().optional(),
|
|
18501
|
+
stateInstance: string().optional(),
|
|
18502
|
+
error: string().optional()
|
|
18503
|
+
});
|
|
18504
|
+
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
|
|
18505
|
+
kind: string(),
|
|
18506
|
+
config: record(string(), unknown()).optional(),
|
|
18507
|
+
/**
|
|
18508
|
+
* When editing / duplicating, the blank password field never round-trips
|
|
18509
|
+
* the stored token. Pass the source target id so the provider can restore
|
|
18510
|
+
* secrets server-side before dialling (same merge as upsert).
|
|
18511
|
+
*/
|
|
18512
|
+
targetId: string().optional()
|
|
18513
|
+
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
18514
|
+
kind: string(),
|
|
18515
|
+
config: record(string(), unknown()),
|
|
18516
|
+
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
18517
|
+
targetId: string().optional()
|
|
18518
|
+
}), ProbeTargetConfigResultSchema, {
|
|
18519
|
+
kind: "mutation",
|
|
18520
|
+
auth: "admin"
|
|
18521
|
+
}), method(object({
|
|
18522
|
+
targetId: string(),
|
|
18523
|
+
notification: NotificationSchema
|
|
18524
|
+
}), SendResultSchema, {
|
|
18525
|
+
kind: "mutation",
|
|
18526
|
+
auth: "admin"
|
|
18527
|
+
}), method(object({
|
|
18528
|
+
targetId: string(),
|
|
18529
|
+
sample: NotificationSchema.optional()
|
|
18530
|
+
}), TestResultSchema, {
|
|
18531
|
+
kind: "mutation",
|
|
18532
|
+
auth: "admin"
|
|
18533
|
+
}), method(object({
|
|
18534
|
+
target: TargetSchema,
|
|
18535
|
+
/**
|
|
18536
|
+
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
18537
|
+
* existing row. The UI never holds the stored token; blank / redacted
|
|
18538
|
+
* password fields are restored from this source (D634 still applies if
|
|
18539
|
+
* destinations diverge without a re-typed secret).
|
|
18540
|
+
*/
|
|
18541
|
+
cloneSecretsFrom: string().optional()
|
|
18542
|
+
}), TargetSchema, {
|
|
18543
|
+
kind: "mutation",
|
|
18544
|
+
auth: "admin"
|
|
18545
|
+
}), method(object({ targetId: string() }), _void(), {
|
|
18546
|
+
kind: "mutation",
|
|
18547
|
+
auth: "admin"
|
|
18548
|
+
}), method(object({
|
|
18549
|
+
targetId: string(),
|
|
18550
|
+
enabled: boolean()
|
|
18551
|
+
}), _void(), {
|
|
18552
|
+
kind: "mutation",
|
|
18553
|
+
auth: "admin"
|
|
18554
|
+
});
|
|
17418
18555
|
/**
|
|
17419
18556
|
* `addon-pages` — system-scoped singleton aggregator cap. Public-facing
|
|
17420
18557
|
* surface that admin-ui consumes through `useAddonPagesListPages()`.
|
|
@@ -17447,52 +18584,6 @@ var AddonPageInfoSchema = object({
|
|
|
17447
18584
|
bundleUrl: string()
|
|
17448
18585
|
});
|
|
17449
18586
|
method(_void(), array(AddonPageInfoSchema).readonly());
|
|
17450
|
-
/**
|
|
17451
|
-
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
17452
|
-
* declarations. Every addon that contributes a UI page registers a
|
|
17453
|
-
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
17454
|
-
* see `addon-pages.cap.ts`) walks this collection, stamps versioned
|
|
17455
|
-
* `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
|
|
17456
|
-
* that admin-ui consumes.
|
|
17457
|
-
*
|
|
17458
|
-
* The split exists because the public listing has a different output
|
|
17459
|
-
* shape than the per-provider raw declarations, and we want both ends
|
|
17460
|
-
* to flow through codegen instead of relying on a hand-written wrapper.
|
|
17461
|
-
*/
|
|
17462
|
-
var AddonPageDeclarationSchema = object({
|
|
17463
|
-
id: string(),
|
|
17464
|
-
label: string(),
|
|
17465
|
-
icon: string(),
|
|
17466
|
-
path: string(),
|
|
17467
|
-
/**
|
|
17468
|
-
* Module Federation remote name — must match the `name` field on the
|
|
17469
|
-
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
17470
|
-
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
17471
|
-
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
17472
|
-
* contain hyphens).
|
|
17473
|
-
*/
|
|
17474
|
-
remoteName: string(),
|
|
17475
|
-
/**
|
|
17476
|
-
* Bundle filename inside the addon's `dist/` dir served at
|
|
17477
|
-
* `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
|
|
17478
|
-
* is always `'remoteEntry.js'`; the value is kept on the metadata so
|
|
17479
|
-
* the static-file route can compute an mtime-based cache-buster URL
|
|
17480
|
-
* without a separate filesystem stat.
|
|
17481
|
-
*/
|
|
17482
|
-
bundle: string(),
|
|
17483
|
-
/**
|
|
17484
|
-
* Sidebar section this page docks into. Well-known ids: `'detection'`,
|
|
17485
|
-
* `'cluster'`, `'administration'` — the page renders inside that group.
|
|
17486
|
-
* Any OTHER string creates (or joins) a custom section rendered after
|
|
17487
|
-
* the built-in groups; its label comes from `sectionLabel` (first
|
|
17488
|
-
* declaration wins), falling back to the id. Absent → the legacy
|
|
17489
|
-
* "Addon Pages" group.
|
|
17490
|
-
*/
|
|
17491
|
-
section: string().optional(),
|
|
17492
|
-
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
17493
|
-
sectionLabel: string().optional()
|
|
17494
|
-
});
|
|
17495
|
-
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
17496
18587
|
var AddonHttpRouteSchema = object({
|
|
17497
18588
|
method: _enum([
|
|
17498
18589
|
"GET",
|
|
@@ -17793,9 +18884,21 @@ var AlertSeveritySchema = _enum([
|
|
|
17793
18884
|
"error",
|
|
17794
18885
|
"critical"
|
|
17795
18886
|
]);
|
|
18887
|
+
/**
|
|
18888
|
+
* An action that RUNS something instead of going somewhere: the admin UI calls
|
|
18889
|
+
* the addon's custom action (`addons.custom`) with exactly these fields. The
|
|
18890
|
+
* producer owns the meaning; the UI knows nothing about the addon or the kind.
|
|
18891
|
+
*/
|
|
18892
|
+
var AlertActionInvokeSchema = object({
|
|
18893
|
+
addonId: string().min(1),
|
|
18894
|
+
action: string().min(1),
|
|
18895
|
+
input: record(string(), unknown()).optional()
|
|
18896
|
+
});
|
|
18897
|
+
/** An action has `href` (a link) OR `invoke` (a button that runs a custom action). */
|
|
17796
18898
|
var AlertActionSchema = object({
|
|
17797
18899
|
label: string(),
|
|
17798
18900
|
href: string().optional(),
|
|
18901
|
+
invoke: AlertActionInvokeSchema.optional(),
|
|
17799
18902
|
/**
|
|
17800
18903
|
* How `href` is followed. Absent / `app`: an admin-ui route (client-side).
|
|
17801
18904
|
* `server`: a route the HUB serves (an addon HTTP route such as an OAuth
|
|
@@ -20152,7 +21255,11 @@ var CompositionProblemSchema = object({
|
|
|
20152
21255
|
"mixed-quantity",
|
|
20153
21256
|
"helper-mismatch",
|
|
20154
21257
|
"member-constraint",
|
|
20155
|
-
"retention"
|
|
21258
|
+
"retention",
|
|
21259
|
+
"command-method-unknown",
|
|
21260
|
+
"command-source-unknown",
|
|
21261
|
+
"command-input-unknown",
|
|
21262
|
+
"command-input-missing"
|
|
20156
21263
|
]),
|
|
20157
21264
|
path: string(),
|
|
20158
21265
|
message: string()
|
|
@@ -20191,7 +21298,9 @@ var CompositionFieldRoleSchema = _enum([
|
|
|
20191
21298
|
var CompositionFieldItemSchema = object({
|
|
20192
21299
|
arrayPath: string(),
|
|
20193
21300
|
key: string(),
|
|
20194
|
-
label: string()
|
|
21301
|
+
label: string(),
|
|
21302
|
+
/** True when the item carries a `reset` forward: the composer writes `resettable` from this (D967). */
|
|
21303
|
+
resettable: boolean().optional()
|
|
20195
21304
|
});
|
|
20196
21305
|
var CompositionFieldPlanSchema = object({
|
|
20197
21306
|
cap: string(),
|
|
@@ -20396,7 +21505,7 @@ Object.freeze({
|
|
|
20396
21505
|
* One catalog row per base reading a group helper can build (D944 §3).
|
|
20397
21506
|
* Data, not a form default written into the block.
|
|
20398
21507
|
*/
|
|
20399
|
-
var NOW$
|
|
21508
|
+
var NOW$3 = {
|
|
20400
21509
|
fieldPath: "lastFetchedAt",
|
|
20401
21510
|
expr: "now"
|
|
20402
21511
|
};
|
|
@@ -20448,7 +21557,7 @@ var GROUP_TEMPLATES = [
|
|
|
20448
21557
|
quantity: "temperature",
|
|
20449
21558
|
defaultAggregate: "mean",
|
|
20450
21559
|
empty: "null",
|
|
20451
|
-
companions: [NOW$
|
|
21560
|
+
companions: [NOW$3],
|
|
20452
21561
|
alsoFrom: {
|
|
20453
21562
|
deviceTypes: CLIMATE_DEVICES,
|
|
20454
21563
|
accept: {
|
|
@@ -20466,7 +21575,7 @@ var GROUP_TEMPLATES = [
|
|
|
20466
21575
|
quantity: "humidity",
|
|
20467
21576
|
defaultAggregate: "mean",
|
|
20468
21577
|
empty: "null",
|
|
20469
|
-
companions: [NOW$
|
|
21578
|
+
companions: [NOW$3],
|
|
20470
21579
|
alsoFrom: {
|
|
20471
21580
|
deviceTypes: CLIMATE_DEVICES,
|
|
20472
21581
|
accept: {
|
|
@@ -20484,7 +21593,7 @@ var GROUP_TEMPLATES = [
|
|
|
20484
21593
|
quantity: "pressure",
|
|
20485
21594
|
defaultAggregate: "mean",
|
|
20486
21595
|
empty: "null",
|
|
20487
|
-
companions: [NOW$
|
|
21596
|
+
companions: [NOW$3]
|
|
20488
21597
|
}),
|
|
20489
21598
|
numeric({
|
|
20490
21599
|
id: "illuminance",
|
|
@@ -20495,7 +21604,7 @@ var GROUP_TEMPLATES = [
|
|
|
20495
21604
|
quantity: "illuminance",
|
|
20496
21605
|
defaultAggregate: "mean",
|
|
20497
21606
|
empty: "null",
|
|
20498
|
-
companions: [NOW$
|
|
21607
|
+
companions: [NOW$3]
|
|
20499
21608
|
}),
|
|
20500
21609
|
numeric({
|
|
20501
21610
|
id: "power",
|
|
@@ -20506,7 +21615,7 @@ var GROUP_TEMPLATES = [
|
|
|
20506
21615
|
quantity: "power",
|
|
20507
21616
|
defaultAggregate: "sum",
|
|
20508
21617
|
empty: "omit",
|
|
20509
|
-
companions: [NOW$
|
|
21618
|
+
companions: [NOW$3]
|
|
20510
21619
|
}),
|
|
20511
21620
|
numeric({
|
|
20512
21621
|
id: "energy",
|
|
@@ -20517,7 +21626,7 @@ var GROUP_TEMPLATES = [
|
|
|
20517
21626
|
quantity: "energy",
|
|
20518
21627
|
defaultAggregate: "sum",
|
|
20519
21628
|
empty: "omit",
|
|
20520
|
-
companions: [NOW$
|
|
21629
|
+
companions: [NOW$3]
|
|
20521
21630
|
}),
|
|
20522
21631
|
numeric({
|
|
20523
21632
|
id: "battery",
|
|
@@ -20552,7 +21661,7 @@ var GROUP_TEMPLATES = [
|
|
|
20552
21661
|
quantity: "device",
|
|
20553
21662
|
defaultAggregate: "mean",
|
|
20554
21663
|
empty: "null",
|
|
20555
|
-
companions: [NOW$
|
|
21664
|
+
companions: [NOW$3]
|
|
20556
21665
|
}),
|
|
20557
21666
|
binary({
|
|
20558
21667
|
id: "motion",
|
|
@@ -22502,116 +23611,6 @@ var DeviceAdminLinkSchema = object({
|
|
|
22502
23611
|
});
|
|
22503
23612
|
method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" });
|
|
22504
23613
|
/**
|
|
22505
|
-
* Identity envelope for a device's upstream-system metadata.
|
|
22506
|
-
*
|
|
22507
|
-
* Two jobs:
|
|
22508
|
-
*
|
|
22509
|
-
* 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
|
|
22510
|
-
* uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
|
|
22511
|
-
* WS-Discovery endpoint UUID, …). Provider code keeps a
|
|
22512
|
-
* `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
|
|
22513
|
-
* without round-tripping through `stableId` / `id` mappings.
|
|
22514
|
-
*
|
|
22515
|
-
* 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
|
|
22516
|
-
* one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
|
|
22517
|
-
* for vendors where MAC is the natural key). For systems where the
|
|
22518
|
-
* live dispatch key can be renamed (HA `entity_id` can be edited),
|
|
22519
|
-
* providers detect renames by matching `uniqueId` and patch `id`
|
|
22520
|
-
* in-place so dispatch keeps working.
|
|
22521
|
-
*
|
|
22522
|
-
* **Display unit and precision are NOT stored here.** They are a read-time
|
|
22523
|
-
* preference (D944). A polymorphic reading stores its `quantity` here once,
|
|
22524
|
-
* at adopt, and a date/time value stores `dateTimeShape` the same way —
|
|
22525
|
-
* neither is repeated on each sample. The `device_class` tag is an
|
|
22526
|
-
* HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
|
|
22527
|
-
* to derive the CamStack `DeviceRole`; it is never stored after that point.
|
|
22528
|
-
*
|
|
22529
|
-
* The `raw` escape hatch carries provider-specific attributes that don't
|
|
22530
|
-
* fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
|
|
22531
|
-
* details, …). Providers control what lands here.
|
|
22532
|
-
*/
|
|
22533
|
-
var SourceInfoSchema = object({
|
|
22534
|
-
/** Live dispatch key — mutable when the source system allows rename. */
|
|
22535
|
-
id: string(),
|
|
22536
|
-
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
22537
|
-
system: string(),
|
|
22538
|
-
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
22539
|
-
* Used to detect rename when `id` changes. */
|
|
22540
|
-
uniqueId: string().optional(),
|
|
22541
|
-
/** Quantity of a polymorphic reading, written once at adopt (D944). */
|
|
22542
|
-
quantity: ReadingQuantitySchema.optional(),
|
|
22543
|
-
/** Date / time / datetime shape of a string reading, written once at adopt. */
|
|
22544
|
-
dateTimeShape: _enum([
|
|
22545
|
-
"date",
|
|
22546
|
-
"time",
|
|
22547
|
-
"datetime"
|
|
22548
|
-
]).optional(),
|
|
22549
|
-
/** Provider-specific extras that don't fit the structured slots.
|
|
22550
|
-
* Whitelist what goes here per provider — do NOT dump entire HA
|
|
22551
|
-
* attribute blobs (would explode the meta JSON column). */
|
|
22552
|
-
raw: record(string(), unknown()).optional()
|
|
22553
|
-
});
|
|
22554
|
-
/**
|
|
22555
|
-
* Build the synthetic SourceInfo every existing provider falls back to
|
|
22556
|
-
* when no upstream value has been persisted. Keeps non-migrated providers
|
|
22557
|
-
* (Reolink / Hikvision / ONVIF / RTSP) functional without code
|
|
22558
|
-
* changes — `id` reuses the CamStack stableId, `system` reuses the addon
|
|
22559
|
-
* id. Real upstream identity replaces this once a provider migrates and
|
|
22560
|
-
* calls `updateSourceInfo()` with concrete values.
|
|
22561
|
-
*/
|
|
22562
|
-
function synthesizeSourceInfo(input) {
|
|
22563
|
-
return {
|
|
22564
|
-
id: input.stableId,
|
|
22565
|
-
system: input.addonId
|
|
22566
|
-
};
|
|
22567
|
-
}
|
|
22568
|
-
/**
|
|
22569
|
-
* Shallow-merge a partial patch over a prior SourceInfo. `undefined`
|
|
22570
|
-
* patch values are ignored (no field clobber); to clear a field
|
|
22571
|
-
* explicitly, providers omit it from the patch and rebuild from scratch
|
|
22572
|
-
* via `updateSourceInfo({})` cycles — uncommon enough that we don't
|
|
22573
|
-
* bother with a `null`-means-delete sentinel.
|
|
22574
|
-
*
|
|
22575
|
-
* The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
|
|
22576
|
-
* merges over existing `raw` keys instead of replacing the whole bag.
|
|
22577
|
-
*/
|
|
22578
|
-
function mergeSourceInfo(prev, patch) {
|
|
22579
|
-
const next = { ...prev };
|
|
22580
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
22581
|
-
if (v === void 0) continue;
|
|
22582
|
-
if (k === "raw" && typeof v === "object" && v !== null) {
|
|
22583
|
-
next.raw = {
|
|
22584
|
-
...prev.raw,
|
|
22585
|
-
...v
|
|
22586
|
-
};
|
|
22587
|
-
continue;
|
|
22588
|
-
}
|
|
22589
|
-
next[k] = v;
|
|
22590
|
-
}
|
|
22591
|
-
return next;
|
|
22592
|
-
}
|
|
22593
|
-
/**
|
|
22594
|
-
* Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
|
|
22595
|
-
* blob is a free-form `Record<string, unknown>` shared with hardware-
|
|
22596
|
-
* identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
|
|
22597
|
-
* lives under a single nested key to keep it composable with those.
|
|
22598
|
-
*/
|
|
22599
|
-
var SOURCE_INFO_METADATA_KEY = "sourceInfo";
|
|
22600
|
-
/**
|
|
22601
|
-
* Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
|
|
22602
|
-
* with Zod. Returns `null` when the blob is missing or the nested
|
|
22603
|
-
* `sourceInfo` key fails validation — callers fall back to the
|
|
22604
|
-
* synthetic default in that case (see `synthesizeSourceInfo`).
|
|
22605
|
-
*/
|
|
22606
|
-
function extractSourceInfoFromMetadata(metadata) {
|
|
22607
|
-
if (!metadata) return null;
|
|
22608
|
-
const raw = metadata[SOURCE_INFO_METADATA_KEY];
|
|
22609
|
-
if (!raw) return null;
|
|
22610
|
-
const parsed = SourceInfoSchema.safeParse(raw);
|
|
22611
|
-
if (!parsed.success) return null;
|
|
22612
|
-
return parsed.data;
|
|
22613
|
-
}
|
|
22614
|
-
/**
|
|
22615
23614
|
* device-discovery — device-scoped capability for parents that host /
|
|
22616
23615
|
* enumerate child devices (Reolink Hub / NVR, ONVIF gateway, future
|
|
22617
23616
|
* integrations). The parent advertises a list of discoverable children,
|
|
@@ -23016,12 +24015,48 @@ var ExposeInputSchema = object({
|
|
|
23016
24015
|
capabilities: array(string()).optional()
|
|
23017
24016
|
});
|
|
23018
24017
|
var UnexposeInputSchema = object({ deviceId: string() });
|
|
24018
|
+
/**
|
|
24019
|
+
* What an exporter may expose besides devices (D968). An action and a
|
|
24020
|
+
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
24021
|
+
* exporter's own selection, not the device registry.
|
|
24022
|
+
*/
|
|
24023
|
+
var ExportEntityKindSchema = _enum([
|
|
24024
|
+
"device",
|
|
24025
|
+
"action",
|
|
24026
|
+
"notifier"
|
|
24027
|
+
]);
|
|
24028
|
+
var ExportEntityRefSchema = discriminatedUnion("kind", [
|
|
24029
|
+
object({
|
|
24030
|
+
kind: literal("device"),
|
|
24031
|
+
deviceId: string()
|
|
24032
|
+
}),
|
|
24033
|
+
object({
|
|
24034
|
+
kind: literal("action"),
|
|
24035
|
+
actionId: string().min(1)
|
|
24036
|
+
}),
|
|
24037
|
+
object({
|
|
24038
|
+
kind: literal("notifier"),
|
|
24039
|
+
targetId: string().min(1)
|
|
24040
|
+
})
|
|
24041
|
+
]);
|
|
24042
|
+
var ExposedEntitySchema = object({
|
|
24043
|
+
entity: ExportEntityRefSchema,
|
|
24044
|
+
exposedAs: string().optional()
|
|
24045
|
+
});
|
|
24046
|
+
var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
24047
|
+
var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
23019
24048
|
method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
23020
24049
|
kind: "mutation",
|
|
23021
24050
|
auth: "admin"
|
|
23022
24051
|
}), method(UnexposeInputSchema, _void(), {
|
|
23023
24052
|
kind: "mutation",
|
|
23024
24053
|
auth: "admin"
|
|
24054
|
+
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
24055
|
+
kind: "mutation",
|
|
24056
|
+
auth: "admin"
|
|
24057
|
+
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
24058
|
+
kind: "mutation",
|
|
24059
|
+
auth: "admin"
|
|
23025
24060
|
});
|
|
23026
24061
|
/** Lucide category ids, alphabetical. `ICON_META` indexes into this. */
|
|
23027
24062
|
var ICON_CATEGORY_IDS = [
|
|
@@ -44237,6 +45272,14 @@ var embeddingEncoderCapability = {
|
|
|
44237
45272
|
})
|
|
44238
45273
|
}
|
|
44239
45274
|
};
|
|
45275
|
+
method(object({
|
|
45276
|
+
nodeId: string().min(1),
|
|
45277
|
+
/** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
|
|
45278
|
+
removedAt: number()
|
|
45279
|
+
}), object({ counts: record(string(), number().int().nonnegative()) }), {
|
|
45280
|
+
kind: "mutation",
|
|
45281
|
+
auth: "admin"
|
|
45282
|
+
});
|
|
44240
45283
|
/**
|
|
44241
45284
|
* `device-removal-hook` — the reliable way an addon learns a device was DELETED,
|
|
44242
45285
|
* so it can delete what it keeps for that device (D865).
|
|
@@ -45079,7 +46122,7 @@ var LlmProviderWidgetSchema = object({
|
|
|
45079
46122
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
45080
46123
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
45081
46124
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
45082
|
-
var ConfigSchemaPassthrough$
|
|
46125
|
+
var ConfigSchemaPassthrough$2 = unknown();
|
|
45083
46126
|
var LlmProfileKindDescriptorSchema = object({
|
|
45084
46127
|
kind: LlmProfileKindSchema,
|
|
45085
46128
|
label: string(),
|
|
@@ -45088,9 +46131,9 @@ var LlmProfileKindDescriptorSchema = object({
|
|
|
45088
46131
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
45089
46132
|
addonId: string(),
|
|
45090
46133
|
/** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
|
|
45091
|
-
connectionSchema: ConfigSchemaPassthrough$
|
|
46134
|
+
connectionSchema: ConfigSchemaPassthrough$2,
|
|
45092
46135
|
/** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
|
|
45093
|
-
advancedSchema: ConfigSchemaPassthrough$
|
|
46136
|
+
advancedSchema: ConfigSchemaPassthrough$2.optional(),
|
|
45094
46137
|
/** The kind's defaults; a model from `listModels` may override any of them. */
|
|
45095
46138
|
features: LlmModelFeaturesSchema,
|
|
45096
46139
|
/** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
|
|
@@ -45616,10 +46659,10 @@ var CpuBreakdownSchema = object({
|
|
|
45616
46659
|
cores: number()
|
|
45617
46660
|
});
|
|
45618
46661
|
var MemoryInfoSchema = object({
|
|
45619
|
-
percent: number(),
|
|
46662
|
+
percent: number().nullable(),
|
|
45620
46663
|
totalBytes: number(),
|
|
45621
|
-
usedBytes: number(),
|
|
45622
|
-
availableBytes: number(),
|
|
46664
|
+
usedBytes: number().nullable(),
|
|
46665
|
+
availableBytes: number().nullable(),
|
|
45623
46666
|
swapUsedBytes: number(),
|
|
45624
46667
|
swapTotalBytes: number()
|
|
45625
46668
|
});
|
|
@@ -45923,8 +46966,9 @@ var GetLoadSeriesInputSchema = object({
|
|
|
45923
46966
|
});
|
|
45924
46967
|
var SystemMetricsSchema = object({
|
|
45925
46968
|
cpuPercent: number(),
|
|
45926
|
-
|
|
45927
|
-
|
|
46969
|
+
/** `null` when the memory sample failed or was not taken yet (D393). */
|
|
46970
|
+
memoryPercent: number().nullable(),
|
|
46971
|
+
memoryUsedMB: number().nullable(),
|
|
45928
46972
|
memoryTotalMB: number(),
|
|
45929
46973
|
diskPercent: number().optional(),
|
|
45930
46974
|
temperature: number().optional(),
|
|
@@ -46160,427 +47204,6 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
|
46160
47204
|
sourcePort: number().optional()
|
|
46161
47205
|
});
|
|
46162
47206
|
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
46163
|
-
/**
|
|
46164
|
-
* notification-output — canonical, capability-gated notification delivery.
|
|
46165
|
-
*
|
|
46166
|
-
* Apprise-derived model (see
|
|
46167
|
-
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
46168
|
-
* callers emit ONE canonical `Notification`; each provider declares a
|
|
46169
|
-
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
46170
|
-
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
46171
|
-
* message to what the kind supports — callers never special-case a service.
|
|
46172
|
-
*
|
|
46173
|
-
* DESIGN DECISIONS (locked):
|
|
46174
|
-
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
46175
|
-
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
46176
|
-
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
46177
|
-
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
46178
|
-
* alternative would fork the UI per addon and cannot host the
|
|
46179
|
-
* discovery→adopt flow.
|
|
46180
|
-
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
46181
|
-
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
46182
|
-
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
46183
|
-
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
46184
|
-
* `addonId` the generated collection router extracts from the call input.
|
|
46185
|
-
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
46186
|
-
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
46187
|
-
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
46188
|
-
* base64 fallback needed.
|
|
46189
|
-
*
|
|
46190
|
-
* TODO (deferred, closed-set change — separate decision): add
|
|
46191
|
-
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
46192
|
-
* admin "Integrations" page.
|
|
46193
|
-
*/
|
|
46194
|
-
/**
|
|
46195
|
-
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
46196
|
-
* adapter picks what it supports and the degrade engine filters the rest.
|
|
46197
|
-
*/
|
|
46198
|
-
var AttachmentMediaTypeSchema = _enum([
|
|
46199
|
-
"image",
|
|
46200
|
-
"video",
|
|
46201
|
-
"gif",
|
|
46202
|
-
"audio",
|
|
46203
|
-
"icon"
|
|
46204
|
-
]);
|
|
46205
|
-
/**
|
|
46206
|
-
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
46207
|
-
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
46208
|
-
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
46209
|
-
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
46210
|
-
*/
|
|
46211
|
-
var AttachmentSchema = object({
|
|
46212
|
-
mediaType: AttachmentMediaTypeSchema,
|
|
46213
|
-
url: string().optional(),
|
|
46214
|
-
bytes: _instanceof(Uint8Array).optional(),
|
|
46215
|
-
mime: string().optional(),
|
|
46216
|
-
name: string().optional()
|
|
46217
|
-
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
46218
|
-
var NotificationFormatSchema = _enum([
|
|
46219
|
-
"text",
|
|
46220
|
-
"markdown",
|
|
46221
|
-
"html"
|
|
46222
|
-
]);
|
|
46223
|
-
/**
|
|
46224
|
-
* The CLOSED icon vocabulary an action button may use.
|
|
46225
|
-
*
|
|
46226
|
-
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
46227
|
-
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
46228
|
-
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
46229
|
-
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
46230
|
-
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
46231
|
-
*
|
|
46232
|
-
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
46233
|
-
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
46234
|
-
* differently.
|
|
46235
|
-
*
|
|
46236
|
-
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
46237
|
-
*
|
|
46238
|
-
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
46239
|
-
* the whole set onto its own renderer's vocabulary through a
|
|
46240
|
-
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
46241
|
-
* fallback, so adding a member here fails every adapter's build until someone
|
|
46242
|
-
* decides its glyph, which is the only place that decision can be made
|
|
46243
|
-
* honestly.
|
|
46244
|
-
*
|
|
46245
|
-
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
46246
|
-
* `disarm` straight through; iOS feeds that string to
|
|
46247
|
-
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
46248
|
-
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
46249
|
-
* mapping, and "the field is documented" is not "the value renders".
|
|
46250
|
-
*
|
|
46251
|
-
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
46252
|
-
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
46253
|
-
* so an addon that emits a token the running hub does not know does not lose an
|
|
46254
|
-
* icon — its whole `send` fails Zod validation and the notification never
|
|
46255
|
-
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
46256
|
-
*/
|
|
46257
|
-
var NotificationActionIconSchema = _enum([
|
|
46258
|
-
"acknowledge",
|
|
46259
|
-
"dismiss",
|
|
46260
|
-
"silence",
|
|
46261
|
-
"snooze",
|
|
46262
|
-
"view",
|
|
46263
|
-
"play",
|
|
46264
|
-
"open",
|
|
46265
|
-
"close",
|
|
46266
|
-
"lock",
|
|
46267
|
-
"unlock",
|
|
46268
|
-
"arm",
|
|
46269
|
-
"arm-home",
|
|
46270
|
-
"arm-away",
|
|
46271
|
-
"arm-night",
|
|
46272
|
-
"disarm",
|
|
46273
|
-
"light",
|
|
46274
|
-
"alert",
|
|
46275
|
-
"camera"
|
|
46276
|
-
]);
|
|
46277
|
-
/** A single tap-through action button. */
|
|
46278
|
-
var NotificationActionSchema = object({
|
|
46279
|
-
id: string(),
|
|
46280
|
-
label: string(),
|
|
46281
|
-
url: string().optional(),
|
|
46282
|
-
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
46283
|
-
icon: NotificationActionIconSchema.optional(),
|
|
46284
|
-
/**
|
|
46285
|
-
* Renders in a warning style where the notifier supports it.
|
|
46286
|
-
*
|
|
46287
|
-
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
46288
|
-
* else — see `notification-center/action-token.ts` for what that does and
|
|
46289
|
-
* does not buy.
|
|
46290
|
-
*/
|
|
46291
|
-
destructive: boolean().optional(),
|
|
46292
|
-
/**
|
|
46293
|
-
* How the tap should REACH the url.
|
|
46294
|
-
*
|
|
46295
|
-
* `navigate` (absent, and every button authored before this field) opens it:
|
|
46296
|
-
* the phone leaves the notification and shows whatever the callback returns.
|
|
46297
|
-
* That is right for a button whose answer the operator wants to read.
|
|
46298
|
-
*
|
|
46299
|
-
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
46300
|
-
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
46301
|
-
* an answer to the notification, and being thrown into a browser tab to
|
|
46302
|
-
* confirm it costs more attention than the notification did. A backend that
|
|
46303
|
-
* cannot do a background call renders it as an ordinary link (the adapters
|
|
46304
|
-
* fall back rather than dropping the button), so this is a preference, never
|
|
46305
|
-
* a requirement.
|
|
46306
|
-
*/
|
|
46307
|
-
mode: _enum(["navigate", "background"]).optional()
|
|
46308
|
-
});
|
|
46309
|
-
/** A delivery parameter value: what a notifier declared it takes. */
|
|
46310
|
-
var DeliveryParamValueSchema = union([
|
|
46311
|
-
string(),
|
|
46312
|
-
number(),
|
|
46313
|
-
boolean()
|
|
46314
|
-
]);
|
|
46315
|
-
/**
|
|
46316
|
-
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
46317
|
-
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
46318
|
-
* never handed one. The admin UI derives its control from this descriptor —
|
|
46319
|
-
* no editor knows a service by name.
|
|
46320
|
-
*
|
|
46321
|
-
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
46322
|
-
* run on the addon cap path).
|
|
46323
|
-
*/
|
|
46324
|
-
var DeliveryParamSchema = object({
|
|
46325
|
-
key: string().min(1),
|
|
46326
|
-
label: string(),
|
|
46327
|
-
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
46328
|
-
labelKey: string().optional(),
|
|
46329
|
-
type: _enum([
|
|
46330
|
-
"enum",
|
|
46331
|
-
"number",
|
|
46332
|
-
"boolean",
|
|
46333
|
-
"string"
|
|
46334
|
-
]),
|
|
46335
|
-
/** `enum` only: the closed set of accepted values. */
|
|
46336
|
-
options: array(object({
|
|
46337
|
-
value: string(),
|
|
46338
|
-
label: string()
|
|
46339
|
-
})).optional(),
|
|
46340
|
-
/** `number` only. */
|
|
46341
|
-
min: number().optional(),
|
|
46342
|
-
max: number().optional(),
|
|
46343
|
-
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
46344
|
-
default: DeliveryParamValueSchema.optional()
|
|
46345
|
-
});
|
|
46346
|
-
/**
|
|
46347
|
-
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
46348
|
-
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
46349
|
-
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
46350
|
-
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
46351
|
-
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
46352
|
-
* `priority` for that one target.
|
|
46353
|
-
*/
|
|
46354
|
-
var NotificationSchema = object({
|
|
46355
|
-
body: string(),
|
|
46356
|
-
title: string().optional(),
|
|
46357
|
-
format: NotificationFormatSchema.default("text"),
|
|
46358
|
-
priority: number().int().min(1).max(5).default(3),
|
|
46359
|
-
level: string().optional(),
|
|
46360
|
-
attachments: array(AttachmentSchema).optional(),
|
|
46361
|
-
clickUrl: string().optional(),
|
|
46362
|
-
actions: array(NotificationActionSchema).optional(),
|
|
46363
|
-
sound: string().optional(),
|
|
46364
|
-
ttl: number().optional(),
|
|
46365
|
-
tag: string().optional(),
|
|
46366
|
-
deviceId: number().optional(),
|
|
46367
|
-
eventId: string().optional(),
|
|
46368
|
-
metadata: record(string(), unknown()).optional(),
|
|
46369
|
-
/**
|
|
46370
|
-
* Values for the parameters the target's kind DECLARES in
|
|
46371
|
-
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
46372
|
-
* declaration by the sender; a notifier reads only the keys it declared.
|
|
46373
|
-
*/
|
|
46374
|
-
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
46375
|
-
});
|
|
46376
|
-
/** One declared native severity/priority level for a kind. */
|
|
46377
|
-
var TargetKindLevelSchema = object({
|
|
46378
|
-
id: string(),
|
|
46379
|
-
label: string(),
|
|
46380
|
-
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
46381
|
-
ordinal: number().int().min(1).max(5).nullable(),
|
|
46382
|
-
flags: object({
|
|
46383
|
-
critical: boolean().optional(),
|
|
46384
|
-
silent: boolean().optional(),
|
|
46385
|
-
noPush: boolean().optional()
|
|
46386
|
-
}).optional(),
|
|
46387
|
-
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
46388
|
-
requires: array(string()).optional(),
|
|
46389
|
-
description: string().optional()
|
|
46390
|
-
});
|
|
46391
|
-
/** The full capability block consulted before dispatch. */
|
|
46392
|
-
var TargetKindCapsSchema = object({
|
|
46393
|
-
attachments: object({
|
|
46394
|
-
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
46395
|
-
mode: _enum([
|
|
46396
|
-
"url",
|
|
46397
|
-
"bytes",
|
|
46398
|
-
"both"
|
|
46399
|
-
]),
|
|
46400
|
-
max: number().int().nonnegative(),
|
|
46401
|
-
maxBytes: number().int().positive().optional()
|
|
46402
|
-
}),
|
|
46403
|
-
/** Max action buttons (0 = none). */
|
|
46404
|
-
actions: number().int().nonnegative(),
|
|
46405
|
-
/**
|
|
46406
|
-
* Whether this kind renders a per-action ICON.
|
|
46407
|
-
*
|
|
46408
|
-
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
46409
|
-
* run on the addon cap path — three production failures in one day taught
|
|
46410
|
-
* this repo that once. Absent is read as false by the degrade engine, which
|
|
46411
|
-
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
46412
|
-
* assumed and dropped costs the operator's trust in the field.
|
|
46413
|
-
*/
|
|
46414
|
-
actionIcons: boolean().optional(),
|
|
46415
|
-
levels: array(TargetKindLevelSchema),
|
|
46416
|
-
format: array(NotificationFormatSchema),
|
|
46417
|
-
clickUrl: boolean(),
|
|
46418
|
-
sound: boolean(),
|
|
46419
|
-
ttl: boolean(),
|
|
46420
|
-
bodyMaxLen: number().int().positive()
|
|
46421
|
-
});
|
|
46422
|
-
/**
|
|
46423
|
-
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
46424
|
-
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
46425
|
-
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
46426
|
-
* the union is large and not meant for runtime validation here; the exported
|
|
46427
|
-
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
46428
|
-
*/
|
|
46429
|
-
var ConfigSchemaPassthrough$2 = unknown();
|
|
46430
|
-
var TargetKindSchema = object({
|
|
46431
|
-
kind: string(),
|
|
46432
|
-
label: string(),
|
|
46433
|
-
icon: string(),
|
|
46434
|
-
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
46435
|
-
addonId: string(),
|
|
46436
|
-
/**
|
|
46437
|
-
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
46438
|
-
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
46439
|
-
* when the addon bundles no icon for that kind — the client then falls back
|
|
46440
|
-
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
46441
|
-
*
|
|
46442
|
-
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
46443
|
-
* client, and a native client joins it onto its own hub base.
|
|
46444
|
-
*
|
|
46445
|
-
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
46446
|
-
* field that survived only because the runtime cap-router forwards provider
|
|
46447
|
-
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
46448
|
-
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
46449
|
-
* broken silently the moment output validation was tightened anywhere.
|
|
46450
|
-
*/
|
|
46451
|
-
iconUrl: string().optional(),
|
|
46452
|
-
/**
|
|
46453
|
-
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
46454
|
-
*
|
|
46455
|
-
* The server knows this and therefore says it, because the client cannot
|
|
46456
|
-
* safely guess: a React-Native client renders SVG and raster through two
|
|
46457
|
-
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
46458
|
-
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
46459
|
-
* placeholder glyph for every vector icon while the web build looked fine.
|
|
46460
|
-
*
|
|
46461
|
-
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
46462
|
-
* not been updated — a client that cannot determine the type should prefer
|
|
46463
|
-
* its raster path, which is the safe default for an unknown image.
|
|
46464
|
-
*/
|
|
46465
|
-
iconMediaType: string().optional(),
|
|
46466
|
-
configSchema: ConfigSchemaPassthrough$2,
|
|
46467
|
-
/**
|
|
46468
|
-
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
46469
|
-
* base URL, instance, chat, recipient). A save that changes any of them
|
|
46470
|
-
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
46471
|
-
* editor uses this to ask for the secret on exactly those edits instead of
|
|
46472
|
-
* on every edit.
|
|
46473
|
-
*
|
|
46474
|
-
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
46475
|
-
* kinds) and from a provider older than this field — an editor that finds
|
|
46476
|
-
* it absent must assume ANY edit may need the secret.
|
|
46477
|
-
*/
|
|
46478
|
-
destinationKeys: array(string()).optional(),
|
|
46479
|
-
supportsDiscovery: boolean(),
|
|
46480
|
-
/**
|
|
46481
|
-
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
46482
|
-
* Absent = none; the rule editor then offers nothing.
|
|
46483
|
-
*/
|
|
46484
|
-
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
46485
|
-
caps: TargetKindCapsSchema
|
|
46486
|
-
});
|
|
46487
|
-
/**
|
|
46488
|
-
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
46489
|
-
* (return a presence marker only) when serving `listTargets` — never
|
|
46490
|
-
* round-trip a stored secret to the UI.
|
|
46491
|
-
*/
|
|
46492
|
-
var TargetSchema = object({
|
|
46493
|
-
id: string(),
|
|
46494
|
-
name: string(),
|
|
46495
|
-
kind: string(),
|
|
46496
|
-
addonId: string(),
|
|
46497
|
-
enabled: boolean(),
|
|
46498
|
-
config: record(string(), unknown())
|
|
46499
|
-
});
|
|
46500
|
-
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
46501
|
-
var DiscoveredTargetSchema = object({
|
|
46502
|
-
kind: string(),
|
|
46503
|
-
suggestedName: string(),
|
|
46504
|
-
config: record(string(), unknown())
|
|
46505
|
-
});
|
|
46506
|
-
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
46507
|
-
var RenderedAsSchema = object({
|
|
46508
|
-
level: string(),
|
|
46509
|
-
format: NotificationFormatSchema,
|
|
46510
|
-
attachmentsSent: number().int().nonnegative(),
|
|
46511
|
-
actionsSent: number().int().nonnegative(),
|
|
46512
|
-
truncated: boolean(),
|
|
46513
|
-
dropped: array(string())
|
|
46514
|
-
});
|
|
46515
|
-
var SendResultSchema = object({
|
|
46516
|
-
success: boolean(),
|
|
46517
|
-
error: string().optional(),
|
|
46518
|
-
renderedAs: RenderedAsSchema.optional()
|
|
46519
|
-
});
|
|
46520
|
-
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
46521
|
-
var TestResultSchema = SendResultSchema;
|
|
46522
|
-
/**
|
|
46523
|
-
* Pre-save credential probe against a partial (unsaved) target config.
|
|
46524
|
-
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
46525
|
-
*/
|
|
46526
|
-
var ProbeTargetConfigResultSchema = object({
|
|
46527
|
-
ok: boolean(),
|
|
46528
|
-
resolvedUrl: string(),
|
|
46529
|
-
httpStatus: number().int().optional(),
|
|
46530
|
-
stateInstance: string().optional(),
|
|
46531
|
-
error: string().optional()
|
|
46532
|
-
});
|
|
46533
|
-
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
|
|
46534
|
-
kind: string(),
|
|
46535
|
-
config: record(string(), unknown()).optional(),
|
|
46536
|
-
/**
|
|
46537
|
-
* When editing / duplicating, the blank password field never round-trips
|
|
46538
|
-
* the stored token. Pass the source target id so the provider can restore
|
|
46539
|
-
* secrets server-side before dialling (same merge as upsert).
|
|
46540
|
-
*/
|
|
46541
|
-
targetId: string().optional()
|
|
46542
|
-
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
46543
|
-
kind: string(),
|
|
46544
|
-
config: record(string(), unknown()),
|
|
46545
|
-
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
46546
|
-
targetId: string().optional()
|
|
46547
|
-
}), ProbeTargetConfigResultSchema, {
|
|
46548
|
-
kind: "mutation",
|
|
46549
|
-
auth: "admin"
|
|
46550
|
-
}), method(object({
|
|
46551
|
-
targetId: string(),
|
|
46552
|
-
notification: NotificationSchema
|
|
46553
|
-
}), SendResultSchema, {
|
|
46554
|
-
kind: "mutation",
|
|
46555
|
-
auth: "admin"
|
|
46556
|
-
}), method(object({
|
|
46557
|
-
targetId: string(),
|
|
46558
|
-
sample: NotificationSchema.optional()
|
|
46559
|
-
}), TestResultSchema, {
|
|
46560
|
-
kind: "mutation",
|
|
46561
|
-
auth: "admin"
|
|
46562
|
-
}), method(object({
|
|
46563
|
-
target: TargetSchema,
|
|
46564
|
-
/**
|
|
46565
|
-
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
46566
|
-
* existing row. The UI never holds the stored token; blank / redacted
|
|
46567
|
-
* password fields are restored from this source (D634 still applies if
|
|
46568
|
-
* destinations diverge without a re-typed secret).
|
|
46569
|
-
*/
|
|
46570
|
-
cloneSecretsFrom: string().optional()
|
|
46571
|
-
}), TargetSchema, {
|
|
46572
|
-
kind: "mutation",
|
|
46573
|
-
auth: "admin"
|
|
46574
|
-
}), method(object({ targetId: string() }), _void(), {
|
|
46575
|
-
kind: "mutation",
|
|
46576
|
-
auth: "admin"
|
|
46577
|
-
}), method(object({
|
|
46578
|
-
targetId: string(),
|
|
46579
|
-
enabled: boolean()
|
|
46580
|
-
}), _void(), {
|
|
46581
|
-
kind: "mutation",
|
|
46582
|
-
auth: "admin"
|
|
46583
|
-
});
|
|
46584
47207
|
var AUDIO_MACRO_LABELS = [
|
|
46585
47208
|
{
|
|
46586
47209
|
id: "speech",
|
|
@@ -54825,14 +55448,6 @@ var EngineProvisioningSchema = object({
|
|
|
54825
55448
|
*/
|
|
54826
55449
|
configIssues: array(string()).optional()
|
|
54827
55450
|
});
|
|
54828
|
-
var PipelineStepInputSchema = lazy(() => object({
|
|
54829
|
-
addonId: string(),
|
|
54830
|
-
modelId: string().optional(),
|
|
54831
|
-
enabled: boolean().default(true),
|
|
54832
|
-
children: array(PipelineStepInputSchema).optional(),
|
|
54833
|
-
settings: record(string(), unknown()).optional(),
|
|
54834
|
-
jumpDeviceKey: string().optional()
|
|
54835
|
-
}));
|
|
54836
55451
|
var ModelSubstitutionSchema = object({
|
|
54837
55452
|
addonId: string(),
|
|
54838
55453
|
chosen: string(),
|
|
@@ -54911,7 +55526,6 @@ var ConfigUISchemaBridge = custom();
|
|
|
54911
55526
|
var ConfigUISchemaNullableBridge = custom();
|
|
54912
55527
|
var InferenceCapabilitiesBridge = custom();
|
|
54913
55528
|
var ModelAvailabilityListBridge = custom();
|
|
54914
|
-
var PipelineRunResultBridge = custom();
|
|
54915
55529
|
method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
|
|
54916
55530
|
modelId: string(),
|
|
54917
55531
|
settings: record(string(), unknown()).readonly()
|
|
@@ -57907,11 +58521,13 @@ var ImageContractSchema = object({
|
|
|
57907
58521
|
* The shell verdict (see {@link WrapperKindSchema}) with the proofs that
|
|
57908
58522
|
* produced it.
|
|
57909
58523
|
*
|
|
57910
|
-
* There is deliberately NO `canSelfUpdate`
|
|
57911
|
-
*
|
|
57912
|
-
*
|
|
57913
|
-
*
|
|
57914
|
-
*
|
|
58524
|
+
* There is deliberately NO `canSelfUpdate` boolean: a wrapper update is an
|
|
58525
|
+
* ANNOUNCEMENT. The notification body carries the exact command for a `docker`
|
|
58526
|
+
* shell and says the app restarts into the new build for `electron`, and that
|
|
58527
|
+
* sentence is chosen from `kind` alone — a boolean nothing reads is the defect
|
|
58528
|
+
* this repo keeps paying for. The one shell that CAN be acted on is the
|
|
58529
|
+
* Electron app, and only through `installDesktopUpdate` (see
|
|
58530
|
+
* {@link DesktopAppStatusSchema} for what the node reads from the app first).
|
|
57915
58531
|
*/
|
|
57916
58532
|
var WrapperIdentitySchema = object({
|
|
57917
58533
|
kind: _enum([
|
|
@@ -57959,6 +58575,49 @@ var ServerRollbackInfoSchema = object({
|
|
|
57959
58575
|
reason: string()
|
|
57960
58576
|
});
|
|
57961
58577
|
/**
|
|
58578
|
+
* What the native desktop app (Electron) that hosts this node says about
|
|
58579
|
+
* itself, read from `<dataDir>/server-root/desktop-app-state.json` (ADR-0971).
|
|
58580
|
+
* Absent on the status = no desktop app, or it never wrote the file.
|
|
58581
|
+
*
|
|
58582
|
+
* `stale` is the honest answer to an app that stopped writing (closed, hung,
|
|
58583
|
+
* crashed): the update facts are then the LAST ones known, not current ones,
|
|
58584
|
+
* and a consumer must not read `update.available: false` from a stale record
|
|
58585
|
+
* as "no update". `stateAtMs` is when the app last wrote.
|
|
58586
|
+
*/
|
|
58587
|
+
var DesktopAppStatusSchema = object({
|
|
58588
|
+
/** The Electron app's own version (not the node closure's). */
|
|
58589
|
+
version: string(),
|
|
58590
|
+
update: object({
|
|
58591
|
+
available: boolean(),
|
|
58592
|
+
/** The new build is downloaded and `installDesktopUpdate` can apply it. */
|
|
58593
|
+
downloaded: boolean(),
|
|
58594
|
+
version: string().nullable(),
|
|
58595
|
+
checking: boolean(),
|
|
58596
|
+
/** Why the app's last update check produced no answer; `null` when it did. */
|
|
58597
|
+
error: string().nullable()
|
|
58598
|
+
}),
|
|
58599
|
+
stateAtMs: number(),
|
|
58600
|
+
stale: boolean()
|
|
58601
|
+
});
|
|
58602
|
+
/**
|
|
58603
|
+
* Why `installDesktopUpdate` did nothing. Stable names a UI maps to a sentence:
|
|
58604
|
+
* - `not-electron` — this node does not run inside the desktop app.
|
|
58605
|
+
* - `no-app-state` — the app never published its state (or the file is unreadable).
|
|
58606
|
+
* - `app-state-stale` — the app stopped writing; it is probably not running.
|
|
58607
|
+
* - `not-downloaded` — no update is downloaded yet; nothing to install.
|
|
58608
|
+
*/
|
|
58609
|
+
var InstallDesktopUpdateReasonSchema = _enum([
|
|
58610
|
+
"not-electron",
|
|
58611
|
+
"no-app-state",
|
|
58612
|
+
"app-state-stale",
|
|
58613
|
+
"not-downloaded"
|
|
58614
|
+
]);
|
|
58615
|
+
var InstallDesktopUpdateResultSchema = object({
|
|
58616
|
+
accepted: boolean(),
|
|
58617
|
+
/** Non-null exactly when `accepted` is false. */
|
|
58618
|
+
reason: InstallDesktopUpdateReasonSchema.nullable()
|
|
58619
|
+
});
|
|
58620
|
+
/**
|
|
57962
58621
|
* The runner boot check (D877): the runners this node PLANNED (hub: one per
|
|
57963
58622
|
* `resolveRunnerId` over its forked addons; agent: the runners its process
|
|
57964
58623
|
* service holds, because only the hub plans) against the states the process
|
|
@@ -58062,7 +58721,12 @@ var ServerPackageStatusSchema = object({
|
|
|
58062
58721
|
* on a provider that predates it. Absent is "not checked yet", never "all
|
|
58063
58722
|
* well": a consumer that needs the answer treats it as pending.
|
|
58064
58723
|
*/
|
|
58065
|
-
runners: RunnerBootCheckSchema.optional()
|
|
58724
|
+
runners: RunnerBootCheckSchema.optional(),
|
|
58725
|
+
/**
|
|
58726
|
+
* The desktop app hosting this node (see {@link DesktopAppStatusSchema}).
|
|
58727
|
+
* Optional for version skew AND because most nodes have no desktop app.
|
|
58728
|
+
*/
|
|
58729
|
+
desktopApp: DesktopAppStatusSchema.optional()
|
|
58066
58730
|
});
|
|
58067
58731
|
var ServerUpdateCheckResultSchema = object({
|
|
58068
58732
|
packageName: string(),
|
|
@@ -58145,6 +58809,12 @@ var ServerUpdateActionResultSchema = object({
|
|
|
58145
58809
|
*/
|
|
58146
58810
|
blockers: array(UpdateBlockerSchema).optional()
|
|
58147
58811
|
});
|
|
58812
|
+
var LaunchOverridesSchema = object({
|
|
58813
|
+
/** The persisted heap ceiling in MB, or `null` when none is set. */
|
|
58814
|
+
heapMb: number().int().nullable() });
|
|
58815
|
+
var SetLaunchOverridesResultSchema = LaunchOverridesSchema.extend({
|
|
58816
|
+
/** Always true: the override is read at launch, not by the running process. */
|
|
58817
|
+
restartRequired: boolean() });
|
|
58148
58818
|
method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
|
|
58149
58819
|
kind: "mutation",
|
|
58150
58820
|
auth: "admin"
|
|
@@ -58161,12 +58831,18 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
|
|
|
58161
58831
|
kind: "mutation",
|
|
58162
58832
|
auth: "admin",
|
|
58163
58833
|
timeoutMs: 16 * 6e4
|
|
58834
|
+
}), method(_void(), InstallDesktopUpdateResultSchema, {
|
|
58835
|
+
kind: "mutation",
|
|
58836
|
+
auth: "admin"
|
|
58164
58837
|
}), method(_void(), ServerUpdateActionResultSchema, {
|
|
58165
58838
|
kind: "mutation",
|
|
58166
58839
|
auth: "admin"
|
|
58167
58840
|
}), method(_void(), ServerUpdateActionResultSchema, {
|
|
58168
58841
|
kind: "mutation",
|
|
58169
58842
|
auth: "admin"
|
|
58843
|
+
}), method(_void(), LaunchOverridesSchema, { auth: "admin" }), method(object({ heapMb: number().int().min(512).max(65536).nullable() }), SetLaunchOverridesResultSchema, {
|
|
58844
|
+
kind: "mutation",
|
|
58845
|
+
auth: "admin"
|
|
58170
58846
|
});
|
|
58171
58847
|
/**
|
|
58172
58848
|
* `smtp-provider` — pluggable email delivery surface.
|
|
@@ -61308,13 +61984,19 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
|
61308
61984
|
}), method(ActionIdInput, _void(), {
|
|
61309
61985
|
kind: "mutation",
|
|
61310
61986
|
auth: "admin"
|
|
61311
|
-
}), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({
|
|
61987
|
+
}), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({
|
|
61988
|
+
action: ActionEntrySchema,
|
|
61989
|
+
warnings: array(UnitWarningSchema).optional()
|
|
61990
|
+
}), {
|
|
61312
61991
|
kind: "mutation",
|
|
61313
61992
|
auth: "admin"
|
|
61314
61993
|
}), method(object({
|
|
61315
61994
|
actionId: string().min(1),
|
|
61316
61995
|
action: ActionInputSchema.partial()
|
|
61317
|
-
}), object({
|
|
61996
|
+
}), object({
|
|
61997
|
+
action: ActionEntrySchema,
|
|
61998
|
+
warnings: array(UnitWarningSchema).optional()
|
|
61999
|
+
}), {
|
|
61318
62000
|
kind: "mutation",
|
|
61319
62001
|
auth: "admin"
|
|
61320
62002
|
}), method(ActionIdInput, _void(), {
|
|
@@ -61766,6 +62448,510 @@ onBrightnessChanged: { data: object({
|
|
|
61766
62448
|
minIntervalMs: 0
|
|
61767
62449
|
} } }
|
|
61768
62450
|
};
|
|
62451
|
+
/**
|
|
62452
|
+
* The role vocabulary of a remote (D963): what a button MEANS, independent of
|
|
62453
|
+
* how it is wired. A layout template places buttons by role; a device binds a
|
|
62454
|
+
* role to an action; an exporter asks for a role by name. Roles are shared so
|
|
62455
|
+
* one layout fits every appliance and one voice command fits every binding.
|
|
62456
|
+
*
|
|
62457
|
+
* A role outside this list is `custom:<key>` — the operator's own button (a
|
|
62458
|
+
* SmartIR source, a channel preset). The key is a lowercase slug.
|
|
62459
|
+
*/
|
|
62460
|
+
var REMOTE_KNOWN_ROLES = [
|
|
62461
|
+
"power",
|
|
62462
|
+
"power_on",
|
|
62463
|
+
"power_off",
|
|
62464
|
+
"volume_up",
|
|
62465
|
+
"volume_down",
|
|
62466
|
+
"mute",
|
|
62467
|
+
"channel_up",
|
|
62468
|
+
"channel_down",
|
|
62469
|
+
"up",
|
|
62470
|
+
"down",
|
|
62471
|
+
"left",
|
|
62472
|
+
"right",
|
|
62473
|
+
"ok",
|
|
62474
|
+
"back",
|
|
62475
|
+
"home",
|
|
62476
|
+
"menu",
|
|
62477
|
+
"info",
|
|
62478
|
+
"guide",
|
|
62479
|
+
"exit",
|
|
62480
|
+
"input",
|
|
62481
|
+
"play",
|
|
62482
|
+
"pause",
|
|
62483
|
+
"play_pause",
|
|
62484
|
+
"stop",
|
|
62485
|
+
"next",
|
|
62486
|
+
"previous",
|
|
62487
|
+
"fast_forward",
|
|
62488
|
+
"rewind",
|
|
62489
|
+
"digit_0",
|
|
62490
|
+
"digit_1",
|
|
62491
|
+
"digit_2",
|
|
62492
|
+
"digit_3",
|
|
62493
|
+
"digit_4",
|
|
62494
|
+
"digit_5",
|
|
62495
|
+
"digit_6",
|
|
62496
|
+
"digit_7",
|
|
62497
|
+
"digit_8",
|
|
62498
|
+
"digit_9"
|
|
62499
|
+
];
|
|
62500
|
+
var CUSTOM_KEY_RE = /^[a-z0-9][a-z0-9_]{0,47}$/;
|
|
62501
|
+
var KNOWN_ROLES = new Set(REMOTE_KNOWN_ROLES);
|
|
62502
|
+
function isRemoteKnownRole(value) {
|
|
62503
|
+
return KNOWN_ROLES.has(value);
|
|
62504
|
+
}
|
|
62505
|
+
function isRemoteCustomKey(key) {
|
|
62506
|
+
return CUSTOM_KEY_RE.test(key);
|
|
62507
|
+
}
|
|
62508
|
+
/** The key of a `custom:<key>` role, or null for any other string. */
|
|
62509
|
+
function customKeyOfRole(role) {
|
|
62510
|
+
if (!role.startsWith("custom:")) return null;
|
|
62511
|
+
const key = role.slice(7);
|
|
62512
|
+
return isRemoteCustomKey(key) ? key : null;
|
|
62513
|
+
}
|
|
62514
|
+
function isRemoteRole(value) {
|
|
62515
|
+
return isRemoteKnownRole(value) || customKeyOfRole(value) !== null;
|
|
62516
|
+
}
|
|
62517
|
+
var RemoteRoleSchema = string().refine(isRemoteRole, { message: "a role is one of the known roles or custom:<key> (lowercase letters, digits, _)" });
|
|
62518
|
+
var RemoteBindingSchema = object({ action: AutomationCapStepSchema.extend({ deviceId: number().int().nonnegative() }) });
|
|
62519
|
+
var RemoteCustomButtonSchema = object({
|
|
62520
|
+
/** The slug after `custom:` in the role. */
|
|
62521
|
+
key: string().refine(isRemoteCustomKey, { message: "a lowercase slug" }),
|
|
62522
|
+
/** What the operator calls it, and what a voice command may name. */
|
|
62523
|
+
name: string().min(1).max(60)
|
|
62524
|
+
});
|
|
62525
|
+
var RemotePowerModeSchema = _enum(["toggle", "discrete"]);
|
|
62526
|
+
var RemoteConfigSchema = object({
|
|
62527
|
+
layoutTemplateId: string().min(1).nullable(),
|
|
62528
|
+
/**
|
|
62529
|
+
* The transmitter device new bindings and an import send through. A binding
|
|
62530
|
+
* names its own device, so one command may override it.
|
|
62531
|
+
*/
|
|
62532
|
+
defaultTransmitterId: number().int().nonnegative().nullable().default(null),
|
|
62533
|
+
/** Keyed by role. A role with no entry falls back to the native cap. */
|
|
62534
|
+
bindings: record(string(), RemoteBindingSchema),
|
|
62535
|
+
customButtons: array(RemoteCustomButtonSchema).max(100),
|
|
62536
|
+
power: object({ mode: RemotePowerModeSchema })
|
|
62537
|
+
}).superRefine((config, ctx) => {
|
|
62538
|
+
const roles = Object.keys(config.bindings);
|
|
62539
|
+
if (roles.length > 200) ctx.addIssue({
|
|
62540
|
+
code: "custom",
|
|
62541
|
+
message: "too many bindings",
|
|
62542
|
+
path: ["bindings"]
|
|
62543
|
+
});
|
|
62544
|
+
for (const role of roles) if (!isRemoteRole(role)) ctx.addIssue({
|
|
62545
|
+
code: "custom",
|
|
62546
|
+
message: `"${role}" is not a role`,
|
|
62547
|
+
path: ["bindings", role]
|
|
62548
|
+
});
|
|
62549
|
+
const keys = /* @__PURE__ */ new Set();
|
|
62550
|
+
config.customButtons.forEach((button, index) => {
|
|
62551
|
+
if (keys.has(button.key)) ctx.addIssue({
|
|
62552
|
+
code: "custom",
|
|
62553
|
+
message: `custom button "${button.key}" is listed twice`,
|
|
62554
|
+
path: [
|
|
62555
|
+
"customButtons",
|
|
62556
|
+
index,
|
|
62557
|
+
"key"
|
|
62558
|
+
]
|
|
62559
|
+
});
|
|
62560
|
+
keys.add(button.key);
|
|
62561
|
+
});
|
|
62562
|
+
for (const role of roles) {
|
|
62563
|
+
const key = customKeyOfRole(role);
|
|
62564
|
+
if (key !== null && !keys.has(key)) ctx.addIssue({
|
|
62565
|
+
code: "custom",
|
|
62566
|
+
message: `${role} is bound but has no entry in customButtons`,
|
|
62567
|
+
path: ["bindings", role]
|
|
62568
|
+
});
|
|
62569
|
+
}
|
|
62570
|
+
});
|
|
62571
|
+
object({
|
|
62572
|
+
power: object({
|
|
62573
|
+
state: _enum(["on", "off"]),
|
|
62574
|
+
at: number()
|
|
62575
|
+
}).nullable(),
|
|
62576
|
+
muted: object({
|
|
62577
|
+
value: boolean(),
|
|
62578
|
+
at: number()
|
|
62579
|
+
}).nullable()
|
|
62580
|
+
});
|
|
62581
|
+
var RemoteButtonShapeSchema = _enum([
|
|
62582
|
+
"rect",
|
|
62583
|
+
"rounded",
|
|
62584
|
+
"round",
|
|
62585
|
+
"pill"
|
|
62586
|
+
]);
|
|
62587
|
+
var RemoteButtonToneSchema = _enum([
|
|
62588
|
+
"neutral",
|
|
62589
|
+
"accent",
|
|
62590
|
+
"danger",
|
|
62591
|
+
"red",
|
|
62592
|
+
"green",
|
|
62593
|
+
"yellow",
|
|
62594
|
+
"blue"
|
|
62595
|
+
]);
|
|
62596
|
+
var RemoteGroupFrameSchema = _enum([
|
|
62597
|
+
"none",
|
|
62598
|
+
"panel",
|
|
62599
|
+
"divider",
|
|
62600
|
+
"dpad",
|
|
62601
|
+
"rocker-v",
|
|
62602
|
+
"rocker-h",
|
|
62603
|
+
"keypad"
|
|
62604
|
+
]);
|
|
62605
|
+
var RemoteLayoutButtonSchema = object({
|
|
62606
|
+
id: string().min(1).max(40),
|
|
62607
|
+
/** 0-based grid position. */
|
|
62608
|
+
row: number().int().min(0).max(24),
|
|
62609
|
+
col: number().int().min(0).max(24),
|
|
62610
|
+
rowSpan: number().int().min(1).max(24),
|
|
62611
|
+
colSpan: number().int().min(1).max(24),
|
|
62612
|
+
icon: string().min(1).max(60).optional(),
|
|
62613
|
+
label: string().min(1).max(40).optional(),
|
|
62614
|
+
role: RemoteRoleSchema,
|
|
62615
|
+
/** Absent = the plain rendering every layout had before shapes existed. */
|
|
62616
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
62617
|
+
tone: RemoteButtonToneSchema.optional(),
|
|
62618
|
+
/** The group this button belongs to; it must lie inside that group's rectangle. */
|
|
62619
|
+
groupId: string().min(1).max(40).optional()
|
|
62620
|
+
});
|
|
62621
|
+
/**
|
|
62622
|
+
* A rectangle of the grid that moves as ONE thing and is drawn with a frame: a
|
|
62623
|
+
* titled panel, a section divider, a D-pad ring. `componentId` names the palette
|
|
62624
|
+
* entry it came from (informational: the layout is self-contained).
|
|
62625
|
+
*/
|
|
62626
|
+
var RemoteLayoutGroupSchema = object({
|
|
62627
|
+
id: string().min(1).max(40),
|
|
62628
|
+
componentId: string().min(1).max(80),
|
|
62629
|
+
frame: RemoteGroupFrameSchema,
|
|
62630
|
+
row: number().int().min(0).max(24),
|
|
62631
|
+
col: number().int().min(0).max(24),
|
|
62632
|
+
rowSpan: number().int().min(1).max(24),
|
|
62633
|
+
colSpan: number().int().min(1).max(24),
|
|
62634
|
+
title: string().min(1).max(40).optional()
|
|
62635
|
+
});
|
|
62636
|
+
function rectsOverlap(a, b) {
|
|
62637
|
+
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;
|
|
62638
|
+
}
|
|
62639
|
+
function rectContains(outer, inner) {
|
|
62640
|
+
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;
|
|
62641
|
+
}
|
|
62642
|
+
/**
|
|
62643
|
+
* The group rules of a layout: inside the grid, no two groups overlap, a
|
|
62644
|
+
* member lies inside its group, a divider holds nothing, and a button that is
|
|
62645
|
+
* not a member never sits inside someone else's rectangle (a group moves as
|
|
62646
|
+
* one thing, so a stray key under it would be left behind).
|
|
62647
|
+
*/
|
|
62648
|
+
function refineGroups(grid, ctx) {
|
|
62649
|
+
const groups = grid.groups ?? [];
|
|
62650
|
+
const byId = /* @__PURE__ */ new Map();
|
|
62651
|
+
const fail = (message, path) => {
|
|
62652
|
+
ctx.addIssue({
|
|
62653
|
+
code: "custom",
|
|
62654
|
+
message,
|
|
62655
|
+
path
|
|
62656
|
+
});
|
|
62657
|
+
};
|
|
62658
|
+
groups.forEach((group, index) => {
|
|
62659
|
+
if (byId.has(group.id)) fail(`group id "${group.id}" is used twice`, [
|
|
62660
|
+
"groups",
|
|
62661
|
+
index,
|
|
62662
|
+
"id"
|
|
62663
|
+
]);
|
|
62664
|
+
byId.set(group.id, group);
|
|
62665
|
+
if (group.row + group.rowSpan > grid.rows || group.col + group.colSpan > grid.columns) fail(`group "${group.id}" leaves the grid`, ["groups", index]);
|
|
62666
|
+
for (let other = 0; other < index; other += 1) {
|
|
62667
|
+
const earlier = groups[other];
|
|
62668
|
+
if (earlier !== void 0 && rectsOverlap(earlier, group)) {
|
|
62669
|
+
fail(`groups "${earlier.id}" and "${group.id}" overlap`, ["groups", index]);
|
|
62670
|
+
return;
|
|
62671
|
+
}
|
|
62672
|
+
}
|
|
62673
|
+
});
|
|
62674
|
+
grid.buttons.forEach((button, index) => {
|
|
62675
|
+
if (button.groupId !== void 0) {
|
|
62676
|
+
const group = byId.get(button.groupId);
|
|
62677
|
+
if (group === void 0) {
|
|
62678
|
+
fail(`button "${button.id}" names the unknown group "${button.groupId}"`, [
|
|
62679
|
+
"buttons",
|
|
62680
|
+
index,
|
|
62681
|
+
"groupId"
|
|
62682
|
+
]);
|
|
62683
|
+
return;
|
|
62684
|
+
}
|
|
62685
|
+
if (group.frame === "divider") fail(`a divider group ("${group.id}") holds no buttons`, [
|
|
62686
|
+
"buttons",
|
|
62687
|
+
index,
|
|
62688
|
+
"groupId"
|
|
62689
|
+
]);
|
|
62690
|
+
if (!rectContains(group, button)) fail(`button "${button.id}" lies outside its group "${group.id}"`, ["buttons", index]);
|
|
62691
|
+
return;
|
|
62692
|
+
}
|
|
62693
|
+
const foreign = groups.find((group) => rectsOverlap(group, button));
|
|
62694
|
+
if (foreign !== void 0) fail(`button "${button.id}" lies inside group "${foreign.id}" but is not a member`, ["buttons", index]);
|
|
62695
|
+
});
|
|
62696
|
+
}
|
|
62697
|
+
object({
|
|
62698
|
+
columns: number().int().min(1).max(24),
|
|
62699
|
+
rows: number().int().min(1).max(24),
|
|
62700
|
+
buttons: array(RemoteLayoutButtonSchema).max(200),
|
|
62701
|
+
groups: array(RemoteLayoutGroupSchema).max(50).optional()
|
|
62702
|
+
}).superRefine((grid, ctx) => {
|
|
62703
|
+
const ids = /* @__PURE__ */ new Set();
|
|
62704
|
+
const taken = /* @__PURE__ */ new Map();
|
|
62705
|
+
grid.buttons.forEach((button, index) => {
|
|
62706
|
+
if (ids.has(button.id)) ctx.addIssue({
|
|
62707
|
+
code: "custom",
|
|
62708
|
+
message: `button id "${button.id}" is used twice`,
|
|
62709
|
+
path: [
|
|
62710
|
+
"buttons",
|
|
62711
|
+
index,
|
|
62712
|
+
"id"
|
|
62713
|
+
]
|
|
62714
|
+
});
|
|
62715
|
+
ids.add(button.id);
|
|
62716
|
+
if (button.row + button.rowSpan > grid.rows || button.col + button.colSpan > grid.columns) {
|
|
62717
|
+
ctx.addIssue({
|
|
62718
|
+
code: "custom",
|
|
62719
|
+
message: `button "${button.id}" leaves the grid`,
|
|
62720
|
+
path: ["buttons", index]
|
|
62721
|
+
});
|
|
62722
|
+
return;
|
|
62723
|
+
}
|
|
62724
|
+
for (let r = button.row; r < button.row + button.rowSpan; r += 1) for (let c = button.col; c < button.col + button.colSpan; c += 1) {
|
|
62725
|
+
const cell = `${r}:${c}`;
|
|
62726
|
+
const other = taken.get(cell);
|
|
62727
|
+
if (other !== void 0) {
|
|
62728
|
+
ctx.addIssue({
|
|
62729
|
+
code: "custom",
|
|
62730
|
+
message: `buttons "${other}" and "${button.id}" overlap`,
|
|
62731
|
+
path: ["buttons", index]
|
|
62732
|
+
});
|
|
62733
|
+
return;
|
|
62734
|
+
}
|
|
62735
|
+
taken.set(cell, button.id);
|
|
62736
|
+
}
|
|
62737
|
+
});
|
|
62738
|
+
refineGroups(grid, ctx);
|
|
62739
|
+
});
|
|
62740
|
+
var RemoteBoundCommandSchema = object({
|
|
62741
|
+
role: RemoteRoleSchema,
|
|
62742
|
+
/** The operator's name for a custom button; absent for a known role. */
|
|
62743
|
+
label: string().optional(),
|
|
62744
|
+
custom: boolean()
|
|
62745
|
+
});
|
|
62746
|
+
var RemoteCommandListSchema = object({
|
|
62747
|
+
layoutTemplateId: string().nullable(),
|
|
62748
|
+
power: object({ mode: RemotePowerModeSchema }),
|
|
62749
|
+
/** Only the roles that are BOUND — the ones a press can reach. */
|
|
62750
|
+
commands: array(RemoteBoundCommandSchema)
|
|
62751
|
+
});
|
|
62752
|
+
var RemoteRefusalReasonSchema = _enum([
|
|
62753
|
+
"no-remote",
|
|
62754
|
+
"unbound-role",
|
|
62755
|
+
"action-failed"
|
|
62756
|
+
]);
|
|
62757
|
+
var RemoteSendResultSchema = discriminatedUnion("status", [
|
|
62758
|
+
object({
|
|
62759
|
+
status: literal("sent"),
|
|
62760
|
+
/** How many times the action ran. */
|
|
62761
|
+
sends: number().int().min(1)
|
|
62762
|
+
}),
|
|
62763
|
+
object({
|
|
62764
|
+
status: literal("skipped"),
|
|
62765
|
+
reason: literal("already-in-state")
|
|
62766
|
+
}),
|
|
62767
|
+
object({
|
|
62768
|
+
status: literal("refused"),
|
|
62769
|
+
reason: RemoteRefusalReasonSchema,
|
|
62770
|
+
detail: string().optional()
|
|
62771
|
+
})
|
|
62772
|
+
]);
|
|
62773
|
+
var RemoteMediaCommandSchema = _enum([
|
|
62774
|
+
"play",
|
|
62775
|
+
"pause",
|
|
62776
|
+
"stop",
|
|
62777
|
+
"next",
|
|
62778
|
+
"previous",
|
|
62779
|
+
"setMute",
|
|
62780
|
+
"selectSource"
|
|
62781
|
+
]);
|
|
62782
|
+
/** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
|
|
62783
|
+
var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
|
|
62784
|
+
var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status: literal("saved") }), object({
|
|
62785
|
+
status: literal("refused"),
|
|
62786
|
+
reason: _enum(["unknown-template", "not-a-media-player"]),
|
|
62787
|
+
detail: string()
|
|
62788
|
+
})]);
|
|
62789
|
+
var deviceInput = object({ deviceId: number().int().nonnegative() });
|
|
62790
|
+
DeviceType.MediaPlayer;
|
|
62791
|
+
method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
|
|
62792
|
+
kind: "mutation",
|
|
62793
|
+
auth: "admin"
|
|
62794
|
+
}), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
|
|
62795
|
+
role: RemoteRoleSchema,
|
|
62796
|
+
/** Sends in a row (volume steps). */
|
|
62797
|
+
repeat: number().int().min(1).max(50).default(1)
|
|
62798
|
+
}), RemoteSendResultSchema, {
|
|
62799
|
+
kind: "mutation",
|
|
62800
|
+
auth: "protected"
|
|
62801
|
+
}), method(deviceInput.extend({
|
|
62802
|
+
command: RemoteMediaCommandSchema,
|
|
62803
|
+
muted: boolean().optional(),
|
|
62804
|
+
source: string().optional(),
|
|
62805
|
+
repeat: number().int().min(1).max(50).default(1)
|
|
62806
|
+
}), RemoteMediaCommandResultSchema, {
|
|
62807
|
+
kind: "mutation",
|
|
62808
|
+
auth: "protected"
|
|
62809
|
+
});
|
|
62810
|
+
var TemplateKindSchema = _enum(["remote"]);
|
|
62811
|
+
/**
|
|
62812
|
+
* template — the generic store of reusable, named operator data (D963). A
|
|
62813
|
+
* record is `{ id, kind, name, payload }`; the kinds (today only `remote`, the
|
|
62814
|
+
* button grid of a remote) and the schema each payload must satisfy are
|
|
62815
|
+
* declared by the system in `template-kinds.ts`. Nothing here is specific to a
|
|
62816
|
+
* kind: the engine validates a payload against its kind's schema and, on a
|
|
62817
|
+
* delete, asks the kind's owner whether anything still points at the record.
|
|
62818
|
+
*
|
|
62819
|
+
* Every refusal is a typed result — a UI says "unknown kind" or names the
|
|
62820
|
+
* devices that still use a template, without parsing text.
|
|
62821
|
+
*/
|
|
62822
|
+
var TemplateRecordSchema = object({
|
|
62823
|
+
id: string().min(1),
|
|
62824
|
+
kind: TemplateKindSchema,
|
|
62825
|
+
name: string().min(1).max(120),
|
|
62826
|
+
/** Shaped by the kind; `checkTemplatePayload(kind, payload)` reads it. */
|
|
62827
|
+
payload: unknown(),
|
|
62828
|
+
createdAt: number(),
|
|
62829
|
+
updatedAt: number()
|
|
62830
|
+
});
|
|
62831
|
+
var TemplateWriteResultSchema = discriminatedUnion("status", [object({
|
|
62832
|
+
status: literal("saved"),
|
|
62833
|
+
template: TemplateRecordSchema
|
|
62834
|
+
}), object({
|
|
62835
|
+
status: literal("refused"),
|
|
62836
|
+
reason: _enum(["invalid-payload", "not-found"]),
|
|
62837
|
+
detail: string()
|
|
62838
|
+
})]);
|
|
62839
|
+
/** A device still pointing at a template. */
|
|
62840
|
+
var TemplateUserSchema = object({
|
|
62841
|
+
deviceId: number().int().nonnegative(),
|
|
62842
|
+
name: string()
|
|
62843
|
+
});
|
|
62844
|
+
var TemplateDeleteResultSchema = discriminatedUnion("status", [
|
|
62845
|
+
object({ status: literal("deleted") }),
|
|
62846
|
+
object({ status: literal("not-found") }),
|
|
62847
|
+
object({
|
|
62848
|
+
status: literal("in-use"),
|
|
62849
|
+
devices: array(TemplateUserSchema).min(1)
|
|
62850
|
+
})
|
|
62851
|
+
]);
|
|
62852
|
+
method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSchema), { auth: "protected" }), method(object({ id: string().min(1) }), TemplateRecordSchema.nullable(), { auth: "protected" }), method(object({
|
|
62853
|
+
kind: TemplateKindSchema,
|
|
62854
|
+
name: string().min(1).max(120),
|
|
62855
|
+
payload: unknown()
|
|
62856
|
+
}), TemplateWriteResultSchema, {
|
|
62857
|
+
kind: "mutation",
|
|
62858
|
+
auth: "admin"
|
|
62859
|
+
}), method(object({
|
|
62860
|
+
id: string().min(1),
|
|
62861
|
+
name: string().min(1).max(120).optional(),
|
|
62862
|
+
payload: unknown().optional()
|
|
62863
|
+
}), TemplateWriteResultSchema, {
|
|
62864
|
+
kind: "mutation",
|
|
62865
|
+
auth: "admin"
|
|
62866
|
+
}), method(object({ id: string().min(1) }), TemplateDeleteResultSchema, {
|
|
62867
|
+
kind: "mutation",
|
|
62868
|
+
auth: "admin"
|
|
62869
|
+
});
|
|
62870
|
+
var RemoteComponentCategorySchema = _enum([
|
|
62871
|
+
"power",
|
|
62872
|
+
"navigation",
|
|
62873
|
+
"volume",
|
|
62874
|
+
"numbers",
|
|
62875
|
+
"media",
|
|
62876
|
+
"colour",
|
|
62877
|
+
"apps",
|
|
62878
|
+
"layout"
|
|
62879
|
+
]);
|
|
62880
|
+
/** `<namespace>:<name>`, both lowercase slugs — `core:dpad`, `<addonId>:<name>`. */
|
|
62881
|
+
var COMPONENT_ID_RE = /^[a-z0-9][a-z0-9_.-]*:[a-z0-9][a-z0-9_-]*$/;
|
|
62882
|
+
var RemoteComponentButtonSchema = object({
|
|
62883
|
+
/** Joined to the instance id when the component is inserted. */
|
|
62884
|
+
idSuffix: string().regex(/^[a-z0-9][a-z0-9_-]{0,19}$/, { message: "a short lowercase slug" }),
|
|
62885
|
+
row: number().int().min(0).max(24),
|
|
62886
|
+
col: number().int().min(0).max(24),
|
|
62887
|
+
rowSpan: number().int().min(1).max(24),
|
|
62888
|
+
colSpan: number().int().min(1).max(24),
|
|
62889
|
+
role: RemoteRoleSchema,
|
|
62890
|
+
icon: string().min(1).max(60).optional(),
|
|
62891
|
+
label: string().min(1).max(40).optional(),
|
|
62892
|
+
shape: RemoteButtonShapeSchema.optional(),
|
|
62893
|
+
tone: RemoteButtonToneSchema.optional()
|
|
62894
|
+
});
|
|
62895
|
+
var RemoteLayoutComponentSchema = object({
|
|
62896
|
+
id: string().max(80).regex(COMPONENT_ID_RE, { message: "<namespace>:<name>, lowercase" }),
|
|
62897
|
+
name: string().min(1).max(60),
|
|
62898
|
+
nameI18n: object({
|
|
62899
|
+
en: string().min(1).max(60),
|
|
62900
|
+
it: string().min(1).max(60)
|
|
62901
|
+
}).optional(),
|
|
62902
|
+
/** A display icon key, resolved by the UI like a button icon. */
|
|
62903
|
+
icon: string().min(1).max(60),
|
|
62904
|
+
category: RemoteComponentCategorySchema,
|
|
62905
|
+
size: object({
|
|
62906
|
+
rows: number().int().min(1).max(24),
|
|
62907
|
+
cols: number().int().min(1).max(24)
|
|
62908
|
+
}),
|
|
62909
|
+
/** The frame drawn around the whole component, or null for loose buttons. */
|
|
62910
|
+
group: object({
|
|
62911
|
+
frame: RemoteGroupFrameSchema,
|
|
62912
|
+
title: string().min(1).max(40).optional()
|
|
62913
|
+
}).nullable(),
|
|
62914
|
+
buttons: array(RemoteComponentButtonSchema).max(40)
|
|
62915
|
+
}).superRefine((component, ctx) => {
|
|
62916
|
+
const suffixes = /* @__PURE__ */ new Set();
|
|
62917
|
+
component.buttons.forEach((button, index) => {
|
|
62918
|
+
if (suffixes.has(button.idSuffix)) ctx.addIssue({
|
|
62919
|
+
code: "custom",
|
|
62920
|
+
message: `button "${button.idSuffix}" is listed twice`,
|
|
62921
|
+
path: [
|
|
62922
|
+
"buttons",
|
|
62923
|
+
index,
|
|
62924
|
+
"idSuffix"
|
|
62925
|
+
]
|
|
62926
|
+
});
|
|
62927
|
+
suffixes.add(button.idSuffix);
|
|
62928
|
+
if (button.row + button.rowSpan > component.size.rows || button.col + button.colSpan > component.size.cols) {
|
|
62929
|
+
ctx.addIssue({
|
|
62930
|
+
code: "custom",
|
|
62931
|
+
message: `button "${button.idSuffix}" leaves the component`,
|
|
62932
|
+
path: ["buttons", index]
|
|
62933
|
+
});
|
|
62934
|
+
return;
|
|
62935
|
+
}
|
|
62936
|
+
for (let other = 0; other < index; other += 1) {
|
|
62937
|
+
const earlier = component.buttons[other];
|
|
62938
|
+
if (earlier !== void 0 && rectsOverlap(earlier, button)) {
|
|
62939
|
+
ctx.addIssue({
|
|
62940
|
+
code: "custom",
|
|
62941
|
+
message: `buttons "${earlier.idSuffix}" and "${button.idSuffix}" overlap`,
|
|
62942
|
+
path: ["buttons", index]
|
|
62943
|
+
});
|
|
62944
|
+
return;
|
|
62945
|
+
}
|
|
62946
|
+
}
|
|
62947
|
+
});
|
|
62948
|
+
if (component.group?.frame === "divider" && component.buttons.length > 0) ctx.addIssue({
|
|
62949
|
+
code: "custom",
|
|
62950
|
+
message: "a divider component holds no buttons",
|
|
62951
|
+
path: ["buttons"]
|
|
62952
|
+
});
|
|
62953
|
+
});
|
|
62954
|
+
method(object({}), array(RemoteLayoutComponentSchema), { auth: "protected" });
|
|
61769
62955
|
DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
61770
62956
|
kind: "mutation",
|
|
61771
62957
|
auth: "protected"
|
|
@@ -62821,14 +64007,13 @@ reset: method(object({
|
|
|
62821
64007
|
auth: "admin"
|
|
62822
64008
|
}) },
|
|
62823
64009
|
/**
|
|
62824
|
-
* A
|
|
62825
|
-
*
|
|
62826
|
-
*
|
|
62827
|
-
* commands (`forward`) arrive in slice 3 (D663).
|
|
64010
|
+
* A consumable ADDED to an existing device (a graft) refuses `reset` by
|
|
64011
|
+
* name: only a composed device of a NEW type forwards an item's reset to a
|
|
64012
|
+
* source button, and marks that item `resettable` (D967).
|
|
62828
64013
|
*/
|
|
62829
64014
|
composedMethods: { reset: {
|
|
62830
64015
|
kind: "refuse",
|
|
62831
|
-
reason: "a
|
|
64016
|
+
reason: "a consumable added to an existing device is not resettable — only a new composed device forwards a reset"
|
|
62832
64017
|
} },
|
|
62833
64018
|
status: {
|
|
62834
64019
|
schema: ConsumablesStatusSchema,
|
|
@@ -63444,7 +64629,7 @@ var DishwasherStatusSchema = object({
|
|
|
63444
64629
|
/** Ms epoch of the last value received from the appliance; 0 = never. */
|
|
63445
64630
|
lastFetchedAt: number()
|
|
63446
64631
|
});
|
|
63447
|
-
var DeviceIdSchema = number().int().nonnegative();
|
|
64632
|
+
var DeviceIdSchema$1 = number().int().nonnegative();
|
|
63448
64633
|
/** A value a program option or setting can take. */
|
|
63449
64634
|
var DishwasherValueSchema = union([
|
|
63450
64635
|
boolean(),
|
|
@@ -63519,77 +64704,77 @@ var dishwasherCapability = {
|
|
|
63519
64704
|
* option feature keys; each is checked against the program's constraints.
|
|
63520
64705
|
*/
|
|
63521
64706
|
start: method(object({
|
|
63522
|
-
deviceId: DeviceIdSchema,
|
|
64707
|
+
deviceId: DeviceIdSchema$1,
|
|
63523
64708
|
program: string().min(1).optional(),
|
|
63524
64709
|
options: record(string(), DishwasherValueSchema).optional()
|
|
63525
64710
|
}), _void(), {
|
|
63526
64711
|
kind: "mutation",
|
|
63527
64712
|
auth: "protected"
|
|
63528
64713
|
}),
|
|
63529
|
-
pause: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64714
|
+
pause: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63530
64715
|
kind: "mutation",
|
|
63531
64716
|
auth: "protected"
|
|
63532
64717
|
}),
|
|
63533
|
-
resume: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64718
|
+
resume: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63534
64719
|
kind: "mutation",
|
|
63535
64720
|
auth: "protected"
|
|
63536
64721
|
}),
|
|
63537
|
-
stop: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
64722
|
+
stop: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
63538
64723
|
kind: "mutation",
|
|
63539
64724
|
auth: "protected"
|
|
63540
64725
|
}),
|
|
63541
64726
|
/** Select (not start) a program; refused when the appliance does not offer it. */
|
|
63542
64727
|
setProgram: method(object({
|
|
63543
|
-
deviceId: DeviceIdSchema,
|
|
64728
|
+
deviceId: DeviceIdSchema$1,
|
|
63544
64729
|
program: string().min(1)
|
|
63545
64730
|
}), _void(), {
|
|
63546
64731
|
kind: "mutation",
|
|
63547
64732
|
auth: "protected"
|
|
63548
64733
|
}),
|
|
63549
64734
|
setPower: method(object({
|
|
63550
|
-
deviceId: DeviceIdSchema,
|
|
64735
|
+
deviceId: DeviceIdSchema$1,
|
|
63551
64736
|
on: boolean()
|
|
63552
64737
|
}), _void(), {
|
|
63553
64738
|
kind: "mutation",
|
|
63554
64739
|
auth: "protected"
|
|
63555
64740
|
}),
|
|
63556
64741
|
setHalfLoad: method(object({
|
|
63557
|
-
deviceId: DeviceIdSchema,
|
|
64742
|
+
deviceId: DeviceIdSchema$1,
|
|
63558
64743
|
on: boolean()
|
|
63559
64744
|
}), _void(), {
|
|
63560
64745
|
kind: "mutation",
|
|
63561
64746
|
auth: "protected"
|
|
63562
64747
|
}),
|
|
63563
64748
|
setHygienePlus: method(object({
|
|
63564
|
-
deviceId: DeviceIdSchema,
|
|
64749
|
+
deviceId: DeviceIdSchema$1,
|
|
63565
64750
|
on: boolean()
|
|
63566
64751
|
}), _void(), {
|
|
63567
64752
|
kind: "mutation",
|
|
63568
64753
|
auth: "protected"
|
|
63569
64754
|
}),
|
|
63570
64755
|
setExtraDry: method(object({
|
|
63571
|
-
deviceId: DeviceIdSchema,
|
|
64756
|
+
deviceId: DeviceIdSchema$1,
|
|
63572
64757
|
on: boolean()
|
|
63573
64758
|
}), _void(), {
|
|
63574
64759
|
kind: "mutation",
|
|
63575
64760
|
auth: "protected"
|
|
63576
64761
|
}),
|
|
63577
64762
|
setIntensiveZone: method(object({
|
|
63578
|
-
deviceId: DeviceIdSchema,
|
|
64763
|
+
deviceId: DeviceIdSchema$1,
|
|
63579
64764
|
on: boolean()
|
|
63580
64765
|
}), _void(), {
|
|
63581
64766
|
kind: "mutation",
|
|
63582
64767
|
auth: "protected"
|
|
63583
64768
|
}),
|
|
63584
64769
|
setVarioSpeedPlus: method(object({
|
|
63585
|
-
deviceId: DeviceIdSchema,
|
|
64770
|
+
deviceId: DeviceIdSchema$1,
|
|
63586
64771
|
on: boolean()
|
|
63587
64772
|
}), _void(), {
|
|
63588
64773
|
kind: "mutation",
|
|
63589
64774
|
auth: "protected"
|
|
63590
64775
|
}),
|
|
63591
64776
|
setChildLock: method(object({
|
|
63592
|
-
deviceId: DeviceIdSchema,
|
|
64777
|
+
deviceId: DeviceIdSchema$1,
|
|
63593
64778
|
on: boolean()
|
|
63594
64779
|
}), _void(), {
|
|
63595
64780
|
kind: "mutation",
|
|
@@ -63601,7 +64786,7 @@ var dishwasherCapability = {
|
|
|
63601
64786
|
* its declared range / members.
|
|
63602
64787
|
*/
|
|
63603
64788
|
setOption: method(object({
|
|
63604
|
-
deviceId: DeviceIdSchema,
|
|
64789
|
+
deviceId: DeviceIdSchema$1,
|
|
63605
64790
|
key: string().min(1),
|
|
63606
64791
|
value: DishwasherValueSchema
|
|
63607
64792
|
}), _void(), {
|
|
@@ -63609,7 +64794,7 @@ var dishwasherCapability = {
|
|
|
63609
64794
|
auth: "protected"
|
|
63610
64795
|
}),
|
|
63611
64796
|
/** What this appliance can do, from its own description — the UI is derived from it. */
|
|
63612
|
-
getOptions: method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" })
|
|
64797
|
+
getOptions: method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" })
|
|
63613
64798
|
},
|
|
63614
64799
|
status: {
|
|
63615
64800
|
schema: DishwasherStatusSchema,
|
|
@@ -66041,9 +67226,35 @@ var MediaInfoSchema = object({
|
|
|
66041
67226
|
/** Optional cover-art / thumbnail URL. */
|
|
66042
67227
|
imageUrl: string().optional()
|
|
66043
67228
|
});
|
|
67229
|
+
/**
|
|
67230
|
+
* Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
|
|
67231
|
+
* the device said so. `assumed`: the driver has no feedback (infrared) and
|
|
67232
|
+
* tracks what it last SENT; it moves only after a send was accepted, and a
|
|
67233
|
+
* human at the real remote makes it wrong. A UI shows an assumed value as a
|
|
67234
|
+
* guess, never as a reading.
|
|
67235
|
+
*/
|
|
67236
|
+
var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
|
|
67237
|
+
/** The fields of a media-player status a driver can only guess. */
|
|
67238
|
+
var MediaPlayerAssumedFieldSchema = _enum([
|
|
67239
|
+
"state",
|
|
67240
|
+
"volumeLevel",
|
|
67241
|
+
"isMuted",
|
|
67242
|
+
"source"
|
|
67243
|
+
]);
|
|
66044
67244
|
var MediaPlayerStatusSchema = object({
|
|
66045
67245
|
/** Playback lifecycle state. */
|
|
66046
67246
|
state: MediaPlayerStateSchema,
|
|
67247
|
+
/**
|
|
67248
|
+
* Whether the state fields are readings or the driver's own bookkeeping.
|
|
67249
|
+
* A slice written before this field existed parses as `reported`.
|
|
67250
|
+
*/
|
|
67251
|
+
stateSource: MediaPlayerStateSourceSchema.default("reported"),
|
|
67252
|
+
/**
|
|
67253
|
+
* Which fields are the driver's own guess. `stateSource` is `assumed` when
|
|
67254
|
+
* this is not empty; a field not listed here is a reading even then (power
|
|
67255
|
+
* from a running-state source, mute guessed from the last send).
|
|
67256
|
+
*/
|
|
67257
|
+
assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
|
|
66047
67258
|
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
66048
67259
|
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
66049
67260
|
volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
@@ -67360,7 +68571,8 @@ var TopologyNodeSchema = object({
|
|
|
67360
68571
|
isHub: boolean(),
|
|
67361
68572
|
isOnline: boolean(),
|
|
67362
68573
|
cpuPercent: number(),
|
|
67363
|
-
|
|
68574
|
+
/** `null` when the node's memory read failed or it has not reported one (D393). */
|
|
68575
|
+
memoryPercent: number().nullable(),
|
|
67364
68576
|
uptime: number(),
|
|
67365
68577
|
lastSeen: string(),
|
|
67366
68578
|
localIps: array(string()).readonly(),
|
|
@@ -67971,6 +69183,14 @@ var PetFeederDeviceStatusSchema = _enum([
|
|
|
67971
69183
|
"on_batteries"
|
|
67972
69184
|
]);
|
|
67973
69185
|
var gramsPortion = number().int().min(4).max(200);
|
|
69186
|
+
/** A portion REQUESTED of the feeder is a mass the method takes in GRAMS (the
|
|
69187
|
+
* hardware's own unit). The bounds are grams; a caller entering another unit
|
|
69188
|
+
* converts to grams first (`paramMeta`, D972). The STATUS fields above stay
|
|
69189
|
+
* plain grams — they are not readings in the kilogram base. */
|
|
69190
|
+
var gramsParam = gramsPortion.meta(paramMeta({
|
|
69191
|
+
quantity: "mass",
|
|
69192
|
+
unit: "g"
|
|
69193
|
+
}));
|
|
67974
69194
|
var PetFeederStatusSchema = object({
|
|
67975
69195
|
/** Food in the TANK (hopper), as the feeder's own sensor reports it
|
|
67976
69196
|
* (`state.food`). A coarse level code, not grams — `0` is empty. The
|
|
@@ -67978,9 +69198,10 @@ var PetFeederStatusSchema = object({
|
|
|
67978
69198
|
* combined reading; per-hopper levels live in `food1`/`food2`. Null when
|
|
67979
69199
|
* the device has not reported yet. */
|
|
67980
69200
|
foodLevel: number().nullable().meta(readingMeta("none")),
|
|
67981
|
-
/** Food in the BOWL
|
|
67982
|
-
* feeder
|
|
67983
|
-
|
|
69201
|
+
/** Food in the BOWL, a mass (stored in kilograms, D944). Reported only by a
|
|
69202
|
+
* feeder with a bowl scale; null when the model has none or its reading is
|
|
69203
|
+
* of an unproven unit. */
|
|
69204
|
+
bowlFood: number().nullable().meta(readingMeta("mass")),
|
|
67984
69205
|
/** True while the pet is eating (`state.eating`). */
|
|
67985
69206
|
eating: boolean(),
|
|
67986
69207
|
/** Whether `markFoodReplenished` does anything on this model. When false
|
|
@@ -68031,6 +69252,14 @@ var PetFeederStatusSchema = object({
|
|
|
68031
69252
|
feedSound: boolean(),
|
|
68032
69253
|
/** Speaker / prompt volume level (device-scaled integer). */
|
|
68033
69254
|
volume: number().meta(readingMeta("none")),
|
|
69255
|
+
/** Portion (grams) of the last manual feed sent from a single-hopper
|
|
69256
|
+
* stepper — the default the next manual feed starts from. Persisted per
|
|
69257
|
+
* device by the provider; null until a manual feed has been sent. */
|
|
69258
|
+
lastFeedGrams: gramsPortion.nullable(),
|
|
69259
|
+
/** Hopper-1 portion of the last manual feed (dual-hopper); null until sent. */
|
|
69260
|
+
lastFeedHopper1: gramsPortion.nullable(),
|
|
69261
|
+
/** Hopper-2 portion of the last manual feed (dual-hopper); null until sent. */
|
|
69262
|
+
lastFeedHopper2: gramsPortion.nullable(),
|
|
68034
69263
|
/** Ms epoch when the slice was last refreshed from the cloud. */
|
|
68035
69264
|
lastFetchedAt: number()
|
|
68036
69265
|
});
|
|
@@ -68050,9 +69279,9 @@ var petFeederCapability = {
|
|
|
68050
69279
|
*/
|
|
68051
69280
|
feed: method(object({
|
|
68052
69281
|
deviceId: number().int().nonnegative(),
|
|
68053
|
-
grams:
|
|
68054
|
-
hopper1:
|
|
68055
|
-
hopper2:
|
|
69282
|
+
grams: gramsParam.optional(),
|
|
69283
|
+
hopper1: gramsParam.optional(),
|
|
69284
|
+
hopper2: gramsParam.optional()
|
|
68056
69285
|
}), _void(), {
|
|
68057
69286
|
kind: "mutation",
|
|
68058
69287
|
auth: "protected"
|
|
@@ -68156,6 +69385,137 @@ var petFeederCapability = {
|
|
|
68156
69385
|
}
|
|
68157
69386
|
} }
|
|
68158
69387
|
};
|
|
69388
|
+
/**
|
|
69389
|
+
* Pet water-fountain cap. Models the telemetry and the control surface of a
|
|
69390
|
+
* circulating drinking fountain: how much water is in the tank, how much the
|
|
69391
|
+
* pet drank, whether the pump runs and on what duty cycle.
|
|
69392
|
+
*
|
|
69393
|
+
* It has no native vendor provider of its own: a fountain reaches CamStack as
|
|
69394
|
+
* a container of loose entities (a pump switch, two number boxes, a "filter
|
|
69395
|
+
* changed" button), and an operator BUILDS the device from them — a composed
|
|
69396
|
+
* device of this type (D659, D967). Every method is therefore a forward to ONE
|
|
69397
|
+
* method of ONE source entity, and every reading is a field derived from a
|
|
69398
|
+
* source entity; the provider-shaped interface below exists so the composed
|
|
69399
|
+
* device and any future native provider answer the same contract.
|
|
69400
|
+
*
|
|
69401
|
+
* Quantities are stored in the base unit of their kind (D944): volumes in
|
|
69402
|
+
* LITRES, durations in SECONDS, the tank level in percent. A source that
|
|
69403
|
+
* reports millilitres or minutes is converted in the composition's expression,
|
|
69404
|
+
* never here. Every reading may be `null`: a source that is offline or does
|
|
69405
|
+
* not report is unknown, never zero.
|
|
69406
|
+
*
|
|
69407
|
+
* The filter is not a field of this cap: it is an item of the `consumables`
|
|
69408
|
+
* cap on the same device (kind `wear`), reset by the same forward mechanism.
|
|
69409
|
+
*/
|
|
69410
|
+
var PetFountainStatusSchema = object({
|
|
69411
|
+
/** Water in the tank, 0..100 (%). Null while the source has not reported. */
|
|
69412
|
+
waterLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
69413
|
+
/** Water in the tank, in litres. Null while the source has not reported. */
|
|
69414
|
+
waterVolume: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69415
|
+
/** True when the tank is empty. Null while unknown. */
|
|
69416
|
+
empty: boolean().nullable(),
|
|
69417
|
+
/** True while the pump is running. Null while unknown. */
|
|
69418
|
+
pumpRunning: boolean().nullable(),
|
|
69419
|
+
/** True when the pump works on an on/off duty cycle rather than continuously. */
|
|
69420
|
+
pumpIntervalEnabled: boolean().nullable(),
|
|
69421
|
+
/** How long the pump runs per cycle, in seconds. */
|
|
69422
|
+
pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
69423
|
+
/** How long the pump rests per cycle, in seconds. */
|
|
69424
|
+
pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
69425
|
+
/** Water drunk in the last drink, in litres. */
|
|
69426
|
+
lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69427
|
+
/** Water drunk in the current hour, in litres. */
|
|
69428
|
+
currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69429
|
+
/** Water drunk over the last 24 hours (rolling), in litres. */
|
|
69430
|
+
last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69431
|
+
/** Water drunk since local midnight, in litres. */
|
|
69432
|
+
todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
69433
|
+
/** Decoded fault message. Null means healthy; a non-null string is an active fault. */
|
|
69434
|
+
error: string().nullable(),
|
|
69435
|
+
/** Ms epoch when the slice was last refreshed. */
|
|
69436
|
+
lastFetchedAt: number()
|
|
69437
|
+
});
|
|
69438
|
+
var DeviceIdSchema = number().int().nonnegative();
|
|
69439
|
+
var MinutesSchema = number().int().min(0);
|
|
69440
|
+
var petFountainCapability = {
|
|
69441
|
+
name: "pet-fountain",
|
|
69442
|
+
scope: "device",
|
|
69443
|
+
deviceNative: true,
|
|
69444
|
+
mode: "singleton",
|
|
69445
|
+
deviceTypes: [DeviceType.PetFountain],
|
|
69446
|
+
methods: {
|
|
69447
|
+
/** Start or stop the pump. */
|
|
69448
|
+
setPump: method(object({
|
|
69449
|
+
deviceId: DeviceIdSchema,
|
|
69450
|
+
on: boolean()
|
|
69451
|
+
}), _void(), {
|
|
69452
|
+
kind: "mutation",
|
|
69453
|
+
auth: "protected"
|
|
69454
|
+
}),
|
|
69455
|
+
/** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
|
|
69456
|
+
setPumpInterval: method(object({
|
|
69457
|
+
deviceId: DeviceIdSchema,
|
|
69458
|
+
on: boolean()
|
|
69459
|
+
}), _void(), {
|
|
69460
|
+
kind: "mutation",
|
|
69461
|
+
auth: "protected"
|
|
69462
|
+
}),
|
|
69463
|
+
/** Set how many minutes the pump runs per cycle. */
|
|
69464
|
+
setPumpOnMinutes: method(object({
|
|
69465
|
+
deviceId: DeviceIdSchema,
|
|
69466
|
+
minutes: MinutesSchema
|
|
69467
|
+
}), _void(), {
|
|
69468
|
+
kind: "mutation",
|
|
69469
|
+
auth: "protected"
|
|
69470
|
+
}),
|
|
69471
|
+
/** Set how many minutes the pump rests per cycle. */
|
|
69472
|
+
setPumpOffMinutes: method(object({
|
|
69473
|
+
deviceId: DeviceIdSchema,
|
|
69474
|
+
minutes: MinutesSchema
|
|
69475
|
+
}), _void(), {
|
|
69476
|
+
kind: "mutation",
|
|
69477
|
+
auth: "protected"
|
|
69478
|
+
}),
|
|
69479
|
+
/** Clear the rolling daily consumption counters on the device. */
|
|
69480
|
+
resetDailyConsumption: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
69481
|
+
kind: "mutation",
|
|
69482
|
+
auth: "protected"
|
|
69483
|
+
})
|
|
69484
|
+
},
|
|
69485
|
+
status: {
|
|
69486
|
+
schema: PetFountainStatusSchema,
|
|
69487
|
+
kind: "push"
|
|
69488
|
+
},
|
|
69489
|
+
/** Runtime-state slice — mirrored by the kernel; the hero card reads it whole. */
|
|
69490
|
+
runtimeState: PetFountainStatusSchema,
|
|
69491
|
+
/**
|
|
69492
|
+
* Runtime-state durability: **session** — live appliance state re-published on connect.
|
|
69493
|
+
*
|
|
69494
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
69495
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
69496
|
+
*/
|
|
69497
|
+
durability: "session",
|
|
69498
|
+
/** History (D850): recorded series. */
|
|
69499
|
+
history: { fields: {
|
|
69500
|
+
waterLevel: {
|
|
69501
|
+
kind: "gauge",
|
|
69502
|
+
unit: "%",
|
|
69503
|
+
deadband: 1
|
|
69504
|
+
},
|
|
69505
|
+
waterVolume: {
|
|
69506
|
+
kind: "gauge",
|
|
69507
|
+
unit: "L"
|
|
69508
|
+
},
|
|
69509
|
+
todayDrink: {
|
|
69510
|
+
kind: "gauge",
|
|
69511
|
+
unit: "L"
|
|
69512
|
+
},
|
|
69513
|
+
last24hDrink: {
|
|
69514
|
+
kind: "gauge",
|
|
69515
|
+
unit: "L"
|
|
69516
|
+
}
|
|
69517
|
+
} }
|
|
69518
|
+
};
|
|
68159
69519
|
var VehicleSchema = object({
|
|
68160
69520
|
id: string(),
|
|
68161
69521
|
name: string(),
|
|
@@ -74153,9 +75513,98 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
|
|
|
74153
75513
|
kind: "mutation",
|
|
74154
75514
|
auth: "admin"
|
|
74155
75515
|
});
|
|
75516
|
+
var btn = (idSuffix, row, col, init) => ({
|
|
75517
|
+
idSuffix,
|
|
75518
|
+
row,
|
|
75519
|
+
col,
|
|
75520
|
+
rowSpan: 1,
|
|
75521
|
+
colSpan: 1,
|
|
75522
|
+
...init
|
|
75523
|
+
});
|
|
75524
|
+
function rocker(id, name, it, frame, title, buttons, size) {
|
|
75525
|
+
return {
|
|
75526
|
+
id,
|
|
75527
|
+
name,
|
|
75528
|
+
nameI18n: {
|
|
75529
|
+
en: name,
|
|
75530
|
+
it
|
|
75531
|
+
},
|
|
75532
|
+
icon: frame === "rocker-v" ? "chevrons-up-down" : "chevrons-left-right",
|
|
75533
|
+
category: id.includes("channel") || id.includes("volume") ? "volume" : "navigation",
|
|
75534
|
+
size,
|
|
75535
|
+
group: title === null ? { frame } : {
|
|
75536
|
+
frame,
|
|
75537
|
+
title
|
|
75538
|
+
},
|
|
75539
|
+
buttons
|
|
75540
|
+
};
|
|
75541
|
+
}
|
|
75542
|
+
btn("power", 0, 0, {
|
|
75543
|
+
role: "power",
|
|
75544
|
+
shape: "round",
|
|
75545
|
+
tone: "danger"
|
|
75546
|
+
}), btn("up", 0, 1, { role: "up" }), btn("left", 1, 0, { role: "left" }), btn("ok", 1, 1, {
|
|
75547
|
+
role: "ok",
|
|
75548
|
+
shape: "round",
|
|
75549
|
+
tone: "accent"
|
|
75550
|
+
}), 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, {
|
|
75551
|
+
role: "volume_up",
|
|
75552
|
+
shape: "pill"
|
|
75553
|
+
}), btn("down", 1, 0, {
|
|
75554
|
+
role: "volume_down",
|
|
75555
|
+
shape: "pill"
|
|
75556
|
+
})], {
|
|
75557
|
+
rows: 2,
|
|
75558
|
+
cols: 1
|
|
75559
|
+
}), rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
|
|
75560
|
+
role: "channel_up",
|
|
75561
|
+
shape: "pill"
|
|
75562
|
+
}), btn("down", 1, 0, {
|
|
75563
|
+
role: "channel_down",
|
|
75564
|
+
shape: "pill"
|
|
75565
|
+
})], {
|
|
75566
|
+
rows: 2,
|
|
75567
|
+
cols: 1
|
|
75568
|
+
}), rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
|
|
75569
|
+
role: "custom:minus",
|
|
75570
|
+
label: "−",
|
|
75571
|
+
shape: "pill"
|
|
75572
|
+
}), btn("plus", 0, 1, {
|
|
75573
|
+
role: "custom:plus",
|
|
75574
|
+
label: "+",
|
|
75575
|
+
shape: "pill"
|
|
75576
|
+
})], {
|
|
75577
|
+
rows: 1,
|
|
75578
|
+
cols: 2
|
|
75579
|
+
}), [...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, {
|
|
75580
|
+
role: "custom:color_red",
|
|
75581
|
+
tone: "red"
|
|
75582
|
+
}), btn("green", 0, 1, {
|
|
75583
|
+
role: "custom:color_green",
|
|
75584
|
+
tone: "green"
|
|
75585
|
+
}), btn("yellow", 0, 2, {
|
|
75586
|
+
role: "custom:color_yellow",
|
|
75587
|
+
tone: "yellow"
|
|
75588
|
+
}), btn("blue", 0, 3, {
|
|
75589
|
+
role: "custom:color_blue",
|
|
75590
|
+
tone: "blue"
|
|
75591
|
+
}), 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" }), [
|
|
75592
|
+
1,
|
|
75593
|
+
2,
|
|
75594
|
+
3,
|
|
75595
|
+
4
|
|
75596
|
+
].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" });
|
|
75597
|
+
discriminatedUnion("kind", [object({
|
|
75598
|
+
kind: literal("raw"),
|
|
75599
|
+
timings: array(number().int()),
|
|
75600
|
+
settings: TransmitSettingsSchema.optional()
|
|
75601
|
+
}), object({
|
|
75602
|
+
kind: literal("pronto"),
|
|
75603
|
+
code: string()
|
|
75604
|
+
})]);
|
|
74156
75605
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
74157
75606
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
74158
|
-
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;
|
|
75607
|
+
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;
|
|
74159
75608
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
74160
75609
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
74161
75610
|
new Set(CAP_NAMES);
|
|
@@ -74559,6 +76008,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
74559
76008
|
notifier: notifierCapability,
|
|
74560
76009
|
numericSensor: numericSensorCapability,
|
|
74561
76010
|
petFeeder: petFeederCapability,
|
|
76011
|
+
petFountain: petFountainCapability,
|
|
74562
76012
|
powerMeter: powerMeterCapability,
|
|
74563
76013
|
presence: presenceCapability,
|
|
74564
76014
|
pressureSensor: pressureSensorCapability,
|
|
@@ -75326,7 +76776,7 @@ var DeclaredDevices = class {
|
|
|
75326
76776
|
return removed;
|
|
75327
76777
|
}
|
|
75328
76778
|
};
|
|
75329
|
-
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;
|
|
76779
|
+
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;
|
|
75330
76780
|
new Set(Object.values(DeviceType));
|
|
75331
76781
|
Object.freeze({
|
|
75332
76782
|
"accessories.getStatus": {
|
|
@@ -77261,6 +78711,13 @@ Object.freeze({
|
|
|
77261
78711
|
access: "create",
|
|
77262
78712
|
role: "admin"
|
|
77263
78713
|
},
|
|
78714
|
+
"deviceExport.exposeEntity": {
|
|
78715
|
+
capName: "device-export",
|
|
78716
|
+
capScope: "system",
|
|
78717
|
+
addonId: null,
|
|
78718
|
+
access: "create",
|
|
78719
|
+
role: "admin"
|
|
78720
|
+
},
|
|
77264
78721
|
"deviceExport.getDeviceLiveContribution": {
|
|
77265
78722
|
capName: "device-export",
|
|
77266
78723
|
capScope: "system",
|
|
@@ -77289,6 +78746,13 @@ Object.freeze({
|
|
|
77289
78746
|
access: "view",
|
|
77290
78747
|
role: "admin"
|
|
77291
78748
|
},
|
|
78749
|
+
"deviceExport.listExposedEntities": {
|
|
78750
|
+
capName: "device-export",
|
|
78751
|
+
capScope: "system",
|
|
78752
|
+
addonId: null,
|
|
78753
|
+
access: "view",
|
|
78754
|
+
role: "admin"
|
|
78755
|
+
},
|
|
77292
78756
|
"deviceExport.listSupportedDeviceKinds": {
|
|
77293
78757
|
capName: "device-export",
|
|
77294
78758
|
capScope: "system",
|
|
@@ -77296,6 +78760,13 @@ Object.freeze({
|
|
|
77296
78760
|
access: "view",
|
|
77297
78761
|
role: "admin"
|
|
77298
78762
|
},
|
|
78763
|
+
"deviceExport.listSupportedEntityKinds": {
|
|
78764
|
+
capName: "device-export",
|
|
78765
|
+
capScope: "system",
|
|
78766
|
+
addonId: null,
|
|
78767
|
+
access: "view",
|
|
78768
|
+
role: "admin"
|
|
78769
|
+
},
|
|
77299
78770
|
"deviceExport.unexposeDevice": {
|
|
77300
78771
|
capName: "device-export",
|
|
77301
78772
|
capScope: "system",
|
|
@@ -77303,6 +78774,13 @@ Object.freeze({
|
|
|
77303
78774
|
access: "create",
|
|
77304
78775
|
role: "admin"
|
|
77305
78776
|
},
|
|
78777
|
+
"deviceExport.unexposeEntity": {
|
|
78778
|
+
capName: "device-export",
|
|
78779
|
+
capScope: "system",
|
|
78780
|
+
addonId: null,
|
|
78781
|
+
access: "create",
|
|
78782
|
+
role: "admin"
|
|
78783
|
+
},
|
|
77306
78784
|
"deviceExtension.listCandidates": {
|
|
77307
78785
|
capName: "device-extension",
|
|
77308
78786
|
capScope: "device",
|
|
@@ -80124,6 +81602,13 @@ Object.freeze({
|
|
|
80124
81602
|
access: "create",
|
|
80125
81603
|
role: "admin"
|
|
80126
81604
|
},
|
|
81605
|
+
"nodeRemovalHook.onNodeRemoved": {
|
|
81606
|
+
capName: "node-removal-hook",
|
|
81607
|
+
capScope: "system",
|
|
81608
|
+
addonId: null,
|
|
81609
|
+
access: "create",
|
|
81610
|
+
role: "admin"
|
|
81611
|
+
},
|
|
80127
81612
|
"nodes.clusterAddonStatus": {
|
|
80128
81613
|
capName: "nodes",
|
|
80129
81614
|
capScope: "system",
|
|
@@ -80761,6 +82246,48 @@ Object.freeze({
|
|
|
80761
82246
|
access: "create",
|
|
80762
82247
|
role: "viewer"
|
|
80763
82248
|
},
|
|
82249
|
+
"petFountain.getStatus": {
|
|
82250
|
+
capName: "pet-fountain",
|
|
82251
|
+
capScope: "device",
|
|
82252
|
+
addonId: null,
|
|
82253
|
+
access: "view",
|
|
82254
|
+
role: "viewer"
|
|
82255
|
+
},
|
|
82256
|
+
"petFountain.resetDailyConsumption": {
|
|
82257
|
+
capName: "pet-fountain",
|
|
82258
|
+
capScope: "device",
|
|
82259
|
+
addonId: null,
|
|
82260
|
+
access: "delete",
|
|
82261
|
+
role: "viewer"
|
|
82262
|
+
},
|
|
82263
|
+
"petFountain.setPump": {
|
|
82264
|
+
capName: "pet-fountain",
|
|
82265
|
+
capScope: "device",
|
|
82266
|
+
addonId: null,
|
|
82267
|
+
access: "create",
|
|
82268
|
+
role: "viewer"
|
|
82269
|
+
},
|
|
82270
|
+
"petFountain.setPumpInterval": {
|
|
82271
|
+
capName: "pet-fountain",
|
|
82272
|
+
capScope: "device",
|
|
82273
|
+
addonId: null,
|
|
82274
|
+
access: "create",
|
|
82275
|
+
role: "viewer"
|
|
82276
|
+
},
|
|
82277
|
+
"petFountain.setPumpOffMinutes": {
|
|
82278
|
+
capName: "pet-fountain",
|
|
82279
|
+
capScope: "device",
|
|
82280
|
+
addonId: null,
|
|
82281
|
+
access: "create",
|
|
82282
|
+
role: "viewer"
|
|
82283
|
+
},
|
|
82284
|
+
"petFountain.setPumpOnMinutes": {
|
|
82285
|
+
capName: "pet-fountain",
|
|
82286
|
+
capScope: "device",
|
|
82287
|
+
addonId: null,
|
|
82288
|
+
access: "create",
|
|
82289
|
+
role: "viewer"
|
|
82290
|
+
},
|
|
80764
82291
|
"pipelineAnalytics.addRetrainFrameFromRecording": {
|
|
80765
82292
|
capName: "pipeline-analytics",
|
|
80766
82293
|
capScope: "device",
|
|
@@ -83015,6 +84542,48 @@ Object.freeze({
|
|
|
83015
84542
|
access: "create",
|
|
83016
84543
|
role: "admin"
|
|
83017
84544
|
},
|
|
84545
|
+
"remote.getConfig": {
|
|
84546
|
+
capName: "remote",
|
|
84547
|
+
capScope: "system",
|
|
84548
|
+
addonId: null,
|
|
84549
|
+
access: "view",
|
|
84550
|
+
role: "admin"
|
|
84551
|
+
},
|
|
84552
|
+
"remote.listCommands": {
|
|
84553
|
+
capName: "remote",
|
|
84554
|
+
capScope: "system",
|
|
84555
|
+
addonId: null,
|
|
84556
|
+
access: "view",
|
|
84557
|
+
role: "admin"
|
|
84558
|
+
},
|
|
84559
|
+
"remote.sendCommand": {
|
|
84560
|
+
capName: "remote",
|
|
84561
|
+
capScope: "system",
|
|
84562
|
+
addonId: null,
|
|
84563
|
+
access: "create",
|
|
84564
|
+
role: "admin"
|
|
84565
|
+
},
|
|
84566
|
+
"remote.sendMediaCommand": {
|
|
84567
|
+
capName: "remote",
|
|
84568
|
+
capScope: "system",
|
|
84569
|
+
addonId: null,
|
|
84570
|
+
access: "create",
|
|
84571
|
+
role: "admin"
|
|
84572
|
+
},
|
|
84573
|
+
"remote.setConfig": {
|
|
84574
|
+
capName: "remote",
|
|
84575
|
+
capScope: "system",
|
|
84576
|
+
addonId: null,
|
|
84577
|
+
access: "create",
|
|
84578
|
+
role: "admin"
|
|
84579
|
+
},
|
|
84580
|
+
"remoteLayoutComponents.listComponents": {
|
|
84581
|
+
capName: "remote-layout-components",
|
|
84582
|
+
capScope: "system",
|
|
84583
|
+
addonId: null,
|
|
84584
|
+
access: "view",
|
|
84585
|
+
role: "admin"
|
|
84586
|
+
},
|
|
83018
84587
|
"runningState.getStatus": {
|
|
83019
84588
|
capName: "running-state",
|
|
83020
84589
|
capScope: "device",
|
|
@@ -83127,6 +84696,13 @@ Object.freeze({
|
|
|
83127
84696
|
access: "create",
|
|
83128
84697
|
role: "admin"
|
|
83129
84698
|
},
|
|
84699
|
+
"serverManagement.getLaunchOverrides": {
|
|
84700
|
+
capName: "server-management",
|
|
84701
|
+
capScope: "system",
|
|
84702
|
+
addonId: null,
|
|
84703
|
+
access: "view",
|
|
84704
|
+
role: "admin"
|
|
84705
|
+
},
|
|
83130
84706
|
"serverManagement.getServerPackageStatus": {
|
|
83131
84707
|
capName: "server-management",
|
|
83132
84708
|
capScope: "system",
|
|
@@ -83134,6 +84710,13 @@ Object.freeze({
|
|
|
83134
84710
|
access: "view",
|
|
83135
84711
|
role: "admin"
|
|
83136
84712
|
},
|
|
84713
|
+
"serverManagement.installDesktopUpdate": {
|
|
84714
|
+
capName: "server-management",
|
|
84715
|
+
capScope: "system",
|
|
84716
|
+
addonId: null,
|
|
84717
|
+
access: "create",
|
|
84718
|
+
role: "admin"
|
|
84719
|
+
},
|
|
83137
84720
|
"serverManagement.restartServer": {
|
|
83138
84721
|
capName: "server-management",
|
|
83139
84722
|
capScope: "system",
|
|
@@ -83148,6 +84731,13 @@ Object.freeze({
|
|
|
83148
84731
|
access: "create",
|
|
83149
84732
|
role: "admin"
|
|
83150
84733
|
},
|
|
84734
|
+
"serverManagement.setLaunchOverrides": {
|
|
84735
|
+
capName: "server-management",
|
|
84736
|
+
capScope: "system",
|
|
84737
|
+
addonId: null,
|
|
84738
|
+
access: "create",
|
|
84739
|
+
role: "admin"
|
|
84740
|
+
},
|
|
83151
84741
|
"settingsStore.aggregate": {
|
|
83152
84742
|
capName: "settings-store",
|
|
83153
84743
|
capScope: "system",
|
|
@@ -84205,6 +85795,41 @@ Object.freeze({
|
|
|
84205
85795
|
access: "view",
|
|
84206
85796
|
role: "viewer"
|
|
84207
85797
|
},
|
|
85798
|
+
"template.create": {
|
|
85799
|
+
capName: "template",
|
|
85800
|
+
capScope: "system",
|
|
85801
|
+
addonId: null,
|
|
85802
|
+
access: "create",
|
|
85803
|
+
role: "admin"
|
|
85804
|
+
},
|
|
85805
|
+
"template.delete": {
|
|
85806
|
+
capName: "template",
|
|
85807
|
+
capScope: "system",
|
|
85808
|
+
addonId: null,
|
|
85809
|
+
access: "delete",
|
|
85810
|
+
role: "admin"
|
|
85811
|
+
},
|
|
85812
|
+
"template.get": {
|
|
85813
|
+
capName: "template",
|
|
85814
|
+
capScope: "system",
|
|
85815
|
+
addonId: null,
|
|
85816
|
+
access: "view",
|
|
85817
|
+
role: "admin"
|
|
85818
|
+
},
|
|
85819
|
+
"template.list": {
|
|
85820
|
+
capName: "template",
|
|
85821
|
+
capScope: "system",
|
|
85822
|
+
addonId: null,
|
|
85823
|
+
access: "view",
|
|
85824
|
+
role: "admin"
|
|
85825
|
+
},
|
|
85826
|
+
"template.update": {
|
|
85827
|
+
capName: "template",
|
|
85828
|
+
capScope: "system",
|
|
85829
|
+
addonId: null,
|
|
85830
|
+
access: "create",
|
|
85831
|
+
role: "admin"
|
|
85832
|
+
},
|
|
84208
85833
|
"terminalSession.adoptLegacyMonitor": {
|
|
84209
85834
|
capName: "terminal-session",
|
|
84210
85835
|
capScope: "system",
|
|
@@ -87002,7 +88627,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87002
88627
|
min: 4,
|
|
87003
88628
|
max: 200,
|
|
87004
88629
|
step: 1,
|
|
87005
|
-
unit: "g"
|
|
88630
|
+
unit: "g",
|
|
88631
|
+
quantity: "mass"
|
|
87006
88632
|
},
|
|
87007
88633
|
optional: true
|
|
87008
88634
|
}]
|
|
@@ -87021,7 +88647,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87021
88647
|
min: 4,
|
|
87022
88648
|
max: 200,
|
|
87023
88649
|
step: 1,
|
|
87024
|
-
unit: "g"
|
|
88650
|
+
unit: "g",
|
|
88651
|
+
quantity: "mass"
|
|
87025
88652
|
},
|
|
87026
88653
|
optional: true
|
|
87027
88654
|
}, {
|
|
@@ -87032,7 +88659,8 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87032
88659
|
min: 4,
|
|
87033
88660
|
max: 200,
|
|
87034
88661
|
step: 1,
|
|
87035
|
-
unit: "g"
|
|
88662
|
+
unit: "g",
|
|
88663
|
+
quantity: "mass"
|
|
87036
88664
|
},
|
|
87037
88665
|
optional: true
|
|
87038
88666
|
}]
|
|
@@ -87173,6 +88801,100 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
87173
88801
|
}
|
|
87174
88802
|
]
|
|
87175
88803
|
},
|
|
88804
|
+
"pet-fountain": {
|
|
88805
|
+
scope: "device",
|
|
88806
|
+
noun: t("Pet fountain", "Fontana per animali"),
|
|
88807
|
+
actions: [
|
|
88808
|
+
{
|
|
88809
|
+
id: "petFountain.pumpOn",
|
|
88810
|
+
method: "setPump",
|
|
88811
|
+
label: t("Pump on", "Pompa accesa"),
|
|
88812
|
+
phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
|
|
88813
|
+
icon: "play",
|
|
88814
|
+
args: { on: true }
|
|
88815
|
+
},
|
|
88816
|
+
{
|
|
88817
|
+
id: "petFountain.pumpOff",
|
|
88818
|
+
method: "setPump",
|
|
88819
|
+
label: t("Pump off", "Pompa spenta"),
|
|
88820
|
+
phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
|
|
88821
|
+
icon: "square",
|
|
88822
|
+
args: { on: false }
|
|
88823
|
+
},
|
|
88824
|
+
{
|
|
88825
|
+
id: "petFountain.intervalOn",
|
|
88826
|
+
method: "setPumpInterval",
|
|
88827
|
+
label: t("Pump cycle on", "Ciclo pompa attivo"),
|
|
88828
|
+
phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
|
|
88829
|
+
icon: "timer",
|
|
88830
|
+
args: { on: true }
|
|
88831
|
+
},
|
|
88832
|
+
{
|
|
88833
|
+
id: "petFountain.intervalOff",
|
|
88834
|
+
method: "setPumpInterval",
|
|
88835
|
+
label: t("Pump cycle off", "Ciclo pompa spento"),
|
|
88836
|
+
phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
|
|
88837
|
+
icon: "timer-off",
|
|
88838
|
+
args: { on: false }
|
|
88839
|
+
},
|
|
88840
|
+
{
|
|
88841
|
+
id: "petFountain.pumpOnMinutes",
|
|
88842
|
+
method: "setPumpOnMinutes",
|
|
88843
|
+
label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
|
|
88844
|
+
phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
|
|
88845
|
+
icon: "timer",
|
|
88846
|
+
openArgs: [{
|
|
88847
|
+
key: "minutes",
|
|
88848
|
+
label: t("Minutes", "Minuti"),
|
|
88849
|
+
offer: {
|
|
88850
|
+
kind: "number",
|
|
88851
|
+
min: 0,
|
|
88852
|
+
step: 1,
|
|
88853
|
+
unit: "min"
|
|
88854
|
+
}
|
|
88855
|
+
}]
|
|
88856
|
+
},
|
|
88857
|
+
{
|
|
88858
|
+
id: "petFountain.pumpOffMinutes",
|
|
88859
|
+
method: "setPumpOffMinutes",
|
|
88860
|
+
label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
|
|
88861
|
+
phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
|
|
88862
|
+
icon: "timer-off",
|
|
88863
|
+
openArgs: [{
|
|
88864
|
+
key: "minutes",
|
|
88865
|
+
label: t("Minutes", "Minuti"),
|
|
88866
|
+
offer: {
|
|
88867
|
+
kind: "number",
|
|
88868
|
+
min: 0,
|
|
88869
|
+
step: 1,
|
|
88870
|
+
unit: "min"
|
|
88871
|
+
}
|
|
88872
|
+
}]
|
|
88873
|
+
},
|
|
88874
|
+
{
|
|
88875
|
+
id: "petFountain.resetDailyConsumption",
|
|
88876
|
+
method: "resetDailyConsumption",
|
|
88877
|
+
label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
|
|
88878
|
+
phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
|
|
88879
|
+
icon: "rotate-ccw"
|
|
88880
|
+
}
|
|
88881
|
+
],
|
|
88882
|
+
state: [{
|
|
88883
|
+
kind: "boolean",
|
|
88884
|
+
field: "empty",
|
|
88885
|
+
label: t("Tank empty", "Serbatoio vuoto"),
|
|
88886
|
+
whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
|
|
88887
|
+
whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
|
|
88888
|
+
icon: "droplet"
|
|
88889
|
+
}, {
|
|
88890
|
+
kind: "numeric",
|
|
88891
|
+
field: "waterLevel",
|
|
88892
|
+
quantity: t("the water level", "il livello dell’acqua"),
|
|
88893
|
+
label: t("Water level", "Livello dell’acqua"),
|
|
88894
|
+
unit: "%",
|
|
88895
|
+
icon: "droplets"
|
|
88896
|
+
}]
|
|
88897
|
+
},
|
|
87176
88898
|
"media-player": {
|
|
87177
88899
|
scope: "device",
|
|
87178
88900
|
noun: t("Media player", "Lettore multimediale"),
|
|
@@ -88997,6 +90719,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
88997
90719
|
"device-manager": system("the device registry itself — rename, move and remove are administration"),
|
|
88998
90720
|
"device-provider": system(INTERNAL),
|
|
88999
90721
|
"device-removal-hook": system(INTERNAL),
|
|
90722
|
+
"node-removal-hook": system(INTERNAL),
|
|
89000
90723
|
"device-state": system("the runtime-state mirror every trigger already reads through"),
|
|
89001
90724
|
"display-palette": system("the cluster display palette — one colour table for every client, not a device action"),
|
|
89002
90725
|
"embedding-encoder": system(ML),
|
|
@@ -89036,6 +90759,8 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
89036
90759
|
"recording-archive": system(STORAGE),
|
|
89037
90760
|
"recording-export": system("recording exports — requested from the timeline, not an automation step"),
|
|
89038
90761
|
records: system(INTERNAL),
|
|
90762
|
+
remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
|
|
90763
|
+
"remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
|
|
89039
90764
|
"server-management": system(CLUSTER),
|
|
89040
90765
|
"settings-store": system(INTERNAL),
|
|
89041
90766
|
"smtp-provider": system(INTERNAL),
|
|
@@ -89048,6 +90773,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
89048
90773
|
"storage-provider": system(STORAGE),
|
|
89049
90774
|
"stream-broker": system(PIPELINE),
|
|
89050
90775
|
system: system(CLUSTER),
|
|
90776
|
+
template: system("operator-authored templates — edited in their own editor, never actuated"),
|
|
89051
90777
|
"terminal-session": system(CLUSTER),
|
|
89052
90778
|
toast: system(UI_PLUMBING),
|
|
89053
90779
|
training: system(ML),
|
|
@@ -89107,7 +90833,7 @@ function projectAction(cap, action) {
|
|
|
89107
90833
|
};
|
|
89108
90834
|
}
|
|
89109
90835
|
Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
|
|
89110
|
-
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;
|
|
90836
|
+
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;
|
|
89111
90837
|
DeviceFeature.BatteryOperated;
|
|
89112
90838
|
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
89113
90839
|
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.";
|
|
@@ -90658,6 +92384,36 @@ Object.freeze({
|
|
|
90658
92384
|
form: "single",
|
|
90659
92385
|
optional: false
|
|
90660
92386
|
}],
|
|
92387
|
+
"petFountain.getStatus": [{
|
|
92388
|
+
name: "deviceId",
|
|
92389
|
+
form: "single",
|
|
92390
|
+
optional: false
|
|
92391
|
+
}],
|
|
92392
|
+
"petFountain.resetDailyConsumption": [{
|
|
92393
|
+
name: "deviceId",
|
|
92394
|
+
form: "single",
|
|
92395
|
+
optional: false
|
|
92396
|
+
}],
|
|
92397
|
+
"petFountain.setPump": [{
|
|
92398
|
+
name: "deviceId",
|
|
92399
|
+
form: "single",
|
|
92400
|
+
optional: false
|
|
92401
|
+
}],
|
|
92402
|
+
"petFountain.setPumpInterval": [{
|
|
92403
|
+
name: "deviceId",
|
|
92404
|
+
form: "single",
|
|
92405
|
+
optional: false
|
|
92406
|
+
}],
|
|
92407
|
+
"petFountain.setPumpOffMinutes": [{
|
|
92408
|
+
name: "deviceId",
|
|
92409
|
+
form: "single",
|
|
92410
|
+
optional: false
|
|
92411
|
+
}],
|
|
92412
|
+
"petFountain.setPumpOnMinutes": [{
|
|
92413
|
+
name: "deviceId",
|
|
92414
|
+
form: "single",
|
|
92415
|
+
optional: false
|
|
92416
|
+
}],
|
|
90661
92417
|
"pipelineAnalytics.addRetrainFrameFromRecording": [{
|
|
90662
92418
|
name: "deviceId",
|
|
90663
92419
|
form: "single",
|
|
@@ -91474,6 +93230,31 @@ Object.freeze({
|
|
|
91474
93230
|
form: "single",
|
|
91475
93231
|
optional: true
|
|
91476
93232
|
}],
|
|
93233
|
+
"remote.getConfig": [{
|
|
93234
|
+
name: "deviceId",
|
|
93235
|
+
form: "single",
|
|
93236
|
+
optional: false
|
|
93237
|
+
}],
|
|
93238
|
+
"remote.listCommands": [{
|
|
93239
|
+
name: "deviceId",
|
|
93240
|
+
form: "single",
|
|
93241
|
+
optional: false
|
|
93242
|
+
}],
|
|
93243
|
+
"remote.sendCommand": [{
|
|
93244
|
+
name: "deviceId",
|
|
93245
|
+
form: "single",
|
|
93246
|
+
optional: false
|
|
93247
|
+
}],
|
|
93248
|
+
"remote.sendMediaCommand": [{
|
|
93249
|
+
name: "deviceId",
|
|
93250
|
+
form: "single",
|
|
93251
|
+
optional: false
|
|
93252
|
+
}],
|
|
93253
|
+
"remote.setConfig": [{
|
|
93254
|
+
name: "deviceId",
|
|
93255
|
+
form: "single",
|
|
93256
|
+
optional: false
|
|
93257
|
+
}],
|
|
91477
93258
|
"runningState.getStatus": [{
|
|
91478
93259
|
name: "deviceId",
|
|
91479
93260
|
form: "single",
|
|
@@ -95029,8 +96810,6 @@ var NC_TEMPLATE_VARS = [
|
|
|
95029
96810
|
* delete it from here then, and the served catalog names it again.
|
|
95030
96811
|
*/
|
|
95031
96812
|
var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
|
|
95032
|
-
"node-restarting",
|
|
95033
|
-
"node-updating",
|
|
95034
96813
|
"alert-critical",
|
|
95035
96814
|
"alert-error",
|
|
95036
96815
|
"alert-critical-resolved",
|
|
@@ -96843,14 +98622,6 @@ function armRemovalReconcile(opts) {
|
|
|
96843
98622
|
timer.unref?.();
|
|
96844
98623
|
return () => clearInterval(timer);
|
|
96845
98624
|
}
|
|
96846
|
-
discriminatedUnion("kind", [object({
|
|
96847
|
-
kind: literal("raw"),
|
|
96848
|
-
timings: array(number().int()),
|
|
96849
|
-
settings: TransmitSettingsSchema.optional()
|
|
96850
|
-
}), object({
|
|
96851
|
-
kind: literal("pronto"),
|
|
96852
|
-
code: string()
|
|
96853
|
-
})]);
|
|
96854
98625
|
object({
|
|
96855
98626
|
manufacturer: string().optional(),
|
|
96856
98627
|
supportedModels: array(string()).optional(),
|