@camstack/addon-provider-homeassistant 1.2.635 → 1.2.637
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 +17 -0
- package/dist/addon.js +5 -1
- package/dist/addon.mjs +5 -1
- package/dist/{dist-CYl0u8cF.mjs → dist-Co6d8Y_P.mjs} +2022 -922
- package/dist/{dist-C_PMvyYa.js → dist-DYCWN6ge.js} +2039 -921
- package/dist/ha-export/ha-export.addon.js +304 -3
- package/dist/ha-export/ha-export.addon.mjs +304 -3
- package/package.json +1 -1
|
@@ -5200,6 +5200,14 @@ function object(shape, params) {
|
|
|
5200
5200
|
...normalizeParams(params)
|
|
5201
5201
|
});
|
|
5202
5202
|
}
|
|
5203
|
+
function strictObject(shape, params) {
|
|
5204
|
+
return new ZodObject({
|
|
5205
|
+
type: "object",
|
|
5206
|
+
shape,
|
|
5207
|
+
catchall: never(),
|
|
5208
|
+
...normalizeParams(params)
|
|
5209
|
+
});
|
|
5210
|
+
}
|
|
5203
5211
|
function looseObject(shape, params) {
|
|
5204
5212
|
return new ZodObject({
|
|
5205
5213
|
type: "object",
|
|
@@ -5579,7 +5587,7 @@ var ZodIssueCode = {
|
|
|
5579
5587
|
var ZodFirstPartyTypeKind;
|
|
5580
5588
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5581
5589
|
//#endregion
|
|
5582
|
-
//#region ../types/dist/sleep-
|
|
5590
|
+
//#region ../types/dist/sleep-6eG9C8Xl.mjs
|
|
5583
5591
|
/**
|
|
5584
5592
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5585
5593
|
* window to float samples (D455).
|
|
@@ -8725,65 +8733,6 @@ var MODEL_FORMATS = [
|
|
|
8725
8733
|
"pt",
|
|
8726
8734
|
"gguf"
|
|
8727
8735
|
];
|
|
8728
|
-
/**
|
|
8729
|
-
* Per-call node pinning for `ctx.api` capability calls.
|
|
8730
|
-
*
|
|
8731
|
-
* A capability call normally resolves to its DEFAULT provider — a `singleton`
|
|
8732
|
-
* cap resolves to the hub, a device-scoped cap to the device's owning node. To
|
|
8733
|
-
* query a SPECIFIC node's provider instead (e.g. a remote agent's own
|
|
8734
|
-
* in-process `platform-probe` hardware, which the hub cannot probe), pin the
|
|
8735
|
-
* call to that node.
|
|
8736
|
-
*
|
|
8737
|
-
* The nodeId rides OUT-OF-BAND in the tRPC call context (NOT in the validated
|
|
8738
|
-
* method args), so capability method signatures stay `nodeId`-free — node
|
|
8739
|
-
* targeting is a property of the CALL, not of the method. The transport lifts
|
|
8740
|
-
* it from `op.context` onto the `CapCallInput.nodeId` field (`ipcParentLink`),
|
|
8741
|
-
* and the hub parent's `onUnownedCall` passes it to the `CapRouteResolver`,
|
|
8742
|
-
* which classifies a pinned agent node as `agent-child-forward`
|
|
8743
|
-
* (`$agent-cap-fwd.forward` → the agent's in-process provider).
|
|
8744
|
-
*
|
|
8745
|
-
* Usage at a call site:
|
|
8746
|
-
*
|
|
8747
|
-
* await api.platformProbe.getCapabilities.query(undefined, nodePin(nodeId))
|
|
8748
|
-
*/
|
|
8749
|
-
/** tRPC `op.context` key carrying a per-call node pin. */
|
|
8750
|
-
var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
8751
|
-
/**
|
|
8752
|
-
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
8753
|
-
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
8754
|
-
*
|
|
8755
|
-
* ## The id is normalised here, and it has to be
|
|
8756
|
-
*
|
|
8757
|
-
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
8758
|
-
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
8759
|
-
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
8760
|
-
* call fails with `no provider registered for cap "…"`. The local-first
|
|
8761
|
-
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
8762
|
-
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
8763
|
-
* the thing you reach for when you specifically need THIS node — silently
|
|
8764
|
-
* addressed a node that does not exist.
|
|
8765
|
-
*
|
|
8766
|
-
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
8767
|
-
* own node to read the host's hardware-decode backend. It never once answered,
|
|
8768
|
-
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
8769
|
-
* while D67's whole premise was that the decoder addon is the authority on
|
|
8770
|
-
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
8771
|
-
* hardware", which was false.
|
|
8772
|
-
*
|
|
8773
|
-
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
8774
|
-
* it is here and not at the call sites.
|
|
8775
|
-
*/
|
|
8776
|
-
function nodePin(nodeId) {
|
|
8777
|
-
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
8778
|
-
}
|
|
8779
|
-
/**
|
|
8780
|
-
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
8781
|
-
* is idempotent, so passing an already-clean id costs nothing.
|
|
8782
|
-
*/
|
|
8783
|
-
function toNodeId(idOrRunnerId) {
|
|
8784
|
-
const head = idOrRunnerId.split("/")[0];
|
|
8785
|
-
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
8786
|
-
}
|
|
8787
8736
|
object({
|
|
8788
8737
|
field: string().min(1),
|
|
8789
8738
|
on: literal("advance")
|
|
@@ -8876,9 +8825,135 @@ function optionalMethod(input, output, options) {
|
|
|
8876
8825
|
function event(data) {
|
|
8877
8826
|
return { data };
|
|
8878
8827
|
}
|
|
8828
|
+
/**
|
|
8829
|
+
* Per-call node pinning for `ctx.api` capability calls.
|
|
8830
|
+
*
|
|
8831
|
+
* A capability call normally resolves to its DEFAULT provider — a `singleton`
|
|
8832
|
+
* cap resolves to the hub, a device-scoped cap to the device's owning node. To
|
|
8833
|
+
* query a SPECIFIC node's provider instead (e.g. a remote agent's own
|
|
8834
|
+
* in-process `platform-probe` hardware, which the hub cannot probe), pin the
|
|
8835
|
+
* call to that node.
|
|
8836
|
+
*
|
|
8837
|
+
* The nodeId rides OUT-OF-BAND in the tRPC call context (NOT in the validated
|
|
8838
|
+
* method args), so capability method signatures stay `nodeId`-free — node
|
|
8839
|
+
* targeting is a property of the CALL, not of the method. The transport lifts
|
|
8840
|
+
* it from `op.context` onto the `CapCallInput.nodeId` field (`ipcParentLink`),
|
|
8841
|
+
* and the hub parent's `onUnownedCall` passes it to the `CapRouteResolver`,
|
|
8842
|
+
* which classifies a pinned agent node as `agent-child-forward`
|
|
8843
|
+
* (`$agent-cap-fwd.forward` → the agent's in-process provider).
|
|
8844
|
+
*
|
|
8845
|
+
* Usage at a call site:
|
|
8846
|
+
*
|
|
8847
|
+
* await api.platformProbe.getCapabilities.query(undefined, nodePin(nodeId))
|
|
8848
|
+
*/
|
|
8849
|
+
/** tRPC `op.context` key carrying a per-call node pin. */
|
|
8850
|
+
var CAP_NODE_PIN_CONTEXT_KEY = "__camstackNodePin";
|
|
8851
|
+
/**
|
|
8852
|
+
* Build the tRPC request options that pin a single capability call to `nodeId`.
|
|
8853
|
+
* Pass as the second argument to `.query(input, …)` / `.mutate(input, …)`.
|
|
8854
|
+
*
|
|
8855
|
+
* ## The id is normalised here, and it has to be
|
|
8856
|
+
*
|
|
8857
|
+
* A forked addon reads its own node from `ctx.kernel.localNodeId`, and inside a
|
|
8858
|
+
* worker that value is a RUNNER id — `hub/export-hap`, not `hub`. Routing
|
|
8859
|
+
* compares a pin against real node ids, so such a pin matches nothing and the
|
|
8860
|
+
* call fails with `no provider registered for cap "…"`. The local-first
|
|
8861
|
+
* resolver already guarded against this (`localNodeId.split('/')[0]`), which
|
|
8862
|
+
* made the hazard invisible: unpinned calls worked, and only an explicit pin —
|
|
8863
|
+
* the thing you reach for when you specifically need THIS node — silently
|
|
8864
|
+
* addressed a node that does not exist.
|
|
8865
|
+
*
|
|
8866
|
+
* Cost of it being missing: `addon-export-hap` pinned `decoder.getInfo` to its
|
|
8867
|
+
* own node to read the host's hardware-decode backend. It never once answered,
|
|
8868
|
+
* so every HomeKit egress transcode decoded in SOFTWARE — including 4K H.265 —
|
|
8869
|
+
* while D67's whole premise was that the decoder addon is the authority on
|
|
8870
|
+
* hardware. The warn said `decoding in SOFTWARE` and read as "this node has no
|
|
8871
|
+
* hardware", which was false.
|
|
8872
|
+
*
|
|
8873
|
+
* Normalising in the ONE constructor fixes every caller at once, which is why
|
|
8874
|
+
* it is here and not at the call sites.
|
|
8875
|
+
*/
|
|
8876
|
+
function nodePin(nodeId) {
|
|
8877
|
+
return { context: { [CAP_NODE_PIN_CONTEXT_KEY]: toNodeId(nodeId) } };
|
|
8878
|
+
}
|
|
8879
|
+
/**
|
|
8880
|
+
* A runner id is `<nodeId>/<addonId>`; a node id has no slash. Taking the head
|
|
8881
|
+
* is idempotent, so passing an already-clean id costs nothing.
|
|
8882
|
+
*/
|
|
8883
|
+
function toNodeId(idOrRunnerId) {
|
|
8884
|
+
const head = idOrRunnerId.split("/")[0];
|
|
8885
|
+
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
8886
|
+
}
|
|
8887
|
+
/**
|
|
8888
|
+
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
8889
|
+
* declarations. Every addon that contributes a UI page registers a
|
|
8890
|
+
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
8891
|
+
* see `addon-pages.cap.ts`) walks this collection, stamps versioned
|
|
8892
|
+
* `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
|
|
8893
|
+
* that admin-ui consumes.
|
|
8894
|
+
*
|
|
8895
|
+
* The split exists because the public listing has a different output
|
|
8896
|
+
* shape than the per-provider raw declarations, and we want both ends
|
|
8897
|
+
* to flow through codegen instead of relying on a hand-written wrapper.
|
|
8898
|
+
*/
|
|
8899
|
+
var AddonPageDeclarationSchema = object({
|
|
8900
|
+
id: string(),
|
|
8901
|
+
label: string(),
|
|
8902
|
+
icon: string(),
|
|
8903
|
+
path: string(),
|
|
8904
|
+
/**
|
|
8905
|
+
* Module Federation remote name — must match the `name` field on the
|
|
8906
|
+
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
8907
|
+
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
8908
|
+
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
8909
|
+
* contain hyphens).
|
|
8910
|
+
*/
|
|
8911
|
+
remoteName: string(),
|
|
8912
|
+
/**
|
|
8913
|
+
* Bundle filename inside the addon's `dist/` dir served at
|
|
8914
|
+
* `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
|
|
8915
|
+
* is always `'remoteEntry.js'`; the value is kept on the metadata so
|
|
8916
|
+
* the static-file route can compute an mtime-based cache-buster URL
|
|
8917
|
+
* without a separate filesystem stat.
|
|
8918
|
+
*/
|
|
8919
|
+
bundle: string(),
|
|
8920
|
+
/**
|
|
8921
|
+
* Sidebar section this page docks into. Well-known ids: `'detection'`,
|
|
8922
|
+
* `'cluster'`, `'administration'` — the page renders inside that group.
|
|
8923
|
+
* Any OTHER string creates (or joins) a custom section rendered after
|
|
8924
|
+
* the built-in groups; its label comes from `sectionLabel` (first
|
|
8925
|
+
* declaration wins), falling back to the id. Absent → the legacy
|
|
8926
|
+
* "Addon Pages" group.
|
|
8927
|
+
*/
|
|
8928
|
+
section: string().optional(),
|
|
8929
|
+
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
8930
|
+
sectionLabel: string().optional()
|
|
8931
|
+
});
|
|
8932
|
+
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
8879
8933
|
var StaticDirOutputSchema$1 = object({ staticDir: string() });
|
|
8880
8934
|
var VersionOutputSchema$1 = object({ version: string() });
|
|
8881
8935
|
method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
|
|
8936
|
+
/**
|
|
8937
|
+
* The pipeline step-input shape and the run-result bridge, in a module that
|
|
8938
|
+
* imports nothing but zod and types.
|
|
8939
|
+
*
|
|
8940
|
+
* WHY A SEPARATE FILE (addon footprint, D28): both were declared in
|
|
8941
|
+
* `pipeline-executor.cap.ts`, whose import evaluates the whole executor schema
|
|
8942
|
+
* graph. An addon that only needs these two (the benchmark's `runPipelineTest`
|
|
8943
|
+
* action) paid ~130 MB of heap at load. `pipeline-executor.cap.ts` re-exports
|
|
8944
|
+
* them, so every existing import keeps working; addon server code takes them
|
|
8945
|
+
* from `@camstack/types/addon`.
|
|
8946
|
+
*/
|
|
8947
|
+
var PipelineStepInputSchema = lazy(() => object({
|
|
8948
|
+
addonId: string(),
|
|
8949
|
+
modelId: string().optional(),
|
|
8950
|
+
enabled: boolean().default(true),
|
|
8951
|
+
children: array(PipelineStepInputSchema).optional(),
|
|
8952
|
+
settings: record(string(), unknown()).optional(),
|
|
8953
|
+
jumpDeviceKey: string().optional()
|
|
8954
|
+
}));
|
|
8955
|
+
/** Typed bridge to the recursive `FrameResult` graph; validation is structural. */
|
|
8956
|
+
var PipelineRunResultBridge = custom();
|
|
8882
8957
|
var StaticDirOutputSchema = object({ staticDir: string() });
|
|
8883
8958
|
var VersionOutputSchema = object({ version: string() });
|
|
8884
8959
|
method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
|
|
@@ -9063,6 +9138,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9063
9138
|
"network-link",
|
|
9064
9139
|
"network-quality",
|
|
9065
9140
|
"node-certificates",
|
|
9141
|
+
"node-removal-hook",
|
|
9066
9142
|
"nodes",
|
|
9067
9143
|
"notification-output",
|
|
9068
9144
|
"notification-rules",
|
|
@@ -9073,6 +9149,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9073
9149
|
"osd",
|
|
9074
9150
|
"osd-manager",
|
|
9075
9151
|
"pet-feeder",
|
|
9152
|
+
"pet-fountain",
|
|
9076
9153
|
"pipeline-analytics",
|
|
9077
9154
|
"pipeline-executor",
|
|
9078
9155
|
"pipeline-orchestrator",
|
|
@@ -9093,6 +9170,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9093
9170
|
"recording-onboard",
|
|
9094
9171
|
"recording-signal",
|
|
9095
9172
|
"records",
|
|
9173
|
+
"remote",
|
|
9096
9174
|
"running-state",
|
|
9097
9175
|
"scene-monitor",
|
|
9098
9176
|
"script-runner",
|
|
@@ -9116,6 +9194,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9116
9194
|
"system",
|
|
9117
9195
|
"tamper",
|
|
9118
9196
|
"temperature-sensor",
|
|
9197
|
+
"template",
|
|
9119
9198
|
"terminal-session",
|
|
9120
9199
|
"toast",
|
|
9121
9200
|
"training",
|
|
@@ -10056,6 +10135,11 @@ Object.freeze({
|
|
|
10056
10135
|
"standby"
|
|
10057
10136
|
]
|
|
10058
10137
|
},
|
|
10138
|
+
{
|
|
10139
|
+
"path": "stateSource",
|
|
10140
|
+
"valueType": "enum",
|
|
10141
|
+
"enumValues": ["reported", "assumed"]
|
|
10142
|
+
},
|
|
10059
10143
|
{
|
|
10060
10144
|
"path": "volumeLevel",
|
|
10061
10145
|
"valueType": "number"
|
|
@@ -10331,6 +10415,68 @@ Object.freeze({
|
|
|
10331
10415
|
{
|
|
10332
10416
|
"path": "volume",
|
|
10333
10417
|
"valueType": "number"
|
|
10418
|
+
},
|
|
10419
|
+
{
|
|
10420
|
+
"path": "lastFeedGrams",
|
|
10421
|
+
"valueType": "number"
|
|
10422
|
+
},
|
|
10423
|
+
{
|
|
10424
|
+
"path": "lastFeedHopper1",
|
|
10425
|
+
"valueType": "number"
|
|
10426
|
+
},
|
|
10427
|
+
{
|
|
10428
|
+
"path": "lastFeedHopper2",
|
|
10429
|
+
"valueType": "number"
|
|
10430
|
+
}
|
|
10431
|
+
],
|
|
10432
|
+
"pet-fountain": [
|
|
10433
|
+
{
|
|
10434
|
+
"path": "waterLevel",
|
|
10435
|
+
"valueType": "number"
|
|
10436
|
+
},
|
|
10437
|
+
{
|
|
10438
|
+
"path": "waterVolume",
|
|
10439
|
+
"valueType": "number"
|
|
10440
|
+
},
|
|
10441
|
+
{
|
|
10442
|
+
"path": "empty",
|
|
10443
|
+
"valueType": "boolean"
|
|
10444
|
+
},
|
|
10445
|
+
{
|
|
10446
|
+
"path": "pumpRunning",
|
|
10447
|
+
"valueType": "boolean"
|
|
10448
|
+
},
|
|
10449
|
+
{
|
|
10450
|
+
"path": "pumpIntervalEnabled",
|
|
10451
|
+
"valueType": "boolean"
|
|
10452
|
+
},
|
|
10453
|
+
{
|
|
10454
|
+
"path": "pumpOnTime",
|
|
10455
|
+
"valueType": "number"
|
|
10456
|
+
},
|
|
10457
|
+
{
|
|
10458
|
+
"path": "pumpOffTime",
|
|
10459
|
+
"valueType": "number"
|
|
10460
|
+
},
|
|
10461
|
+
{
|
|
10462
|
+
"path": "lastDrink",
|
|
10463
|
+
"valueType": "number"
|
|
10464
|
+
},
|
|
10465
|
+
{
|
|
10466
|
+
"path": "currentHourDrink",
|
|
10467
|
+
"valueType": "number"
|
|
10468
|
+
},
|
|
10469
|
+
{
|
|
10470
|
+
"path": "last24hDrink",
|
|
10471
|
+
"valueType": "number"
|
|
10472
|
+
},
|
|
10473
|
+
{
|
|
10474
|
+
"path": "todayDrink",
|
|
10475
|
+
"valueType": "number"
|
|
10476
|
+
},
|
|
10477
|
+
{
|
|
10478
|
+
"path": "error",
|
|
10479
|
+
"valueType": "string"
|
|
10334
10480
|
}
|
|
10335
10481
|
],
|
|
10336
10482
|
"power-meter": [
|
|
@@ -10870,7 +11016,7 @@ object({
|
|
|
10870
11016
|
params: record(string(), union([string(), number()])).optional()
|
|
10871
11017
|
});
|
|
10872
11018
|
//#endregion
|
|
10873
|
-
//#region ../types/dist/err-msg-
|
|
11019
|
+
//#region ../types/dist/err-msg-DWXWc0Di.mjs
|
|
10874
11020
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
10875
11021
|
DeviceType["Camera"] = "camera";
|
|
10876
11022
|
DeviceType["Hub"] = "hub";
|
|
@@ -10962,6 +11108,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
10962
11108
|
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
10963
11109
|
* integrations sharing the same food/desiccant/hopper surface. */
|
|
10964
11110
|
DeviceType["PetFeeder"] = "pet-feeder";
|
|
11111
|
+
/** Pet water fountain — tank level, water drunk, pump and its duty cycle.
|
|
11112
|
+
* Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
|
|
11113
|
+
* of a container and is BUILT as a composed device (D967). */
|
|
11114
|
+
DeviceType["PetFountain"] = "pet-fountain";
|
|
10965
11115
|
/** Built-in dishwasher — program lifecycle, door / remote-start gates,
|
|
10966
11116
|
* salt and rinse-aid levels. Installed with the `dishwasher` cap.
|
|
10967
11117
|
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
@@ -11232,7 +11382,7 @@ function errMsg(err) {
|
|
|
11232
11382
|
return String(err);
|
|
11233
11383
|
}
|
|
11234
11384
|
//#endregion
|
|
11235
|
-
//#region ../types/dist/composition-
|
|
11385
|
+
//#region ../types/dist/composition-kSuOifuG.mjs
|
|
11236
11386
|
/**
|
|
11237
11387
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11238
11388
|
*
|
|
@@ -12317,12 +12467,28 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
|
|
|
12317
12467
|
])
|
|
12318
12468
|
})
|
|
12319
12469
|
]);
|
|
12320
|
-
var
|
|
12470
|
+
var forwardArgUnion = union([
|
|
12471
|
+
string(),
|
|
12472
|
+
number(),
|
|
12473
|
+
boolean(),
|
|
12474
|
+
strictObject({ ["$input"]: string().min(1) }),
|
|
12475
|
+
record(string().min(1), lazy(() => forwardArgUnion))
|
|
12476
|
+
]);
|
|
12477
|
+
var CompositionForwardArgSchema = forwardArgUnion;
|
|
12478
|
+
/**
|
|
12479
|
+
* `forward` (D659, amended by D967): one method of a BUILT capability is one
|
|
12480
|
+
* call of `cap.method` on `source`. Without `args` the built method's input
|
|
12481
|
+
* passes through minus `deviceId`; with `args` the template IS the source's
|
|
12482
|
+
* whole input. `deviceId` is always the source's own, never an argument.
|
|
12483
|
+
*/
|
|
12484
|
+
var CompositionForwardCommandSchema = object({
|
|
12321
12485
|
kind: literal("forward"),
|
|
12322
12486
|
source: CompositionSourceRefSchema,
|
|
12323
12487
|
cap: string().min(1),
|
|
12324
|
-
method: string().min(1)
|
|
12325
|
-
|
|
12488
|
+
method: string().min(1),
|
|
12489
|
+
args: record(string().min(1), CompositionForwardArgSchema).optional()
|
|
12490
|
+
});
|
|
12491
|
+
var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
|
|
12326
12492
|
/** An item key inside an item-array cap (`consumables.items[<key>]`). */
|
|
12327
12493
|
var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
12328
12494
|
/**
|
|
@@ -12332,7 +12498,13 @@ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
|
12332
12498
|
*/
|
|
12333
12499
|
var CompositionItemEntrySchema = object({
|
|
12334
12500
|
label: string().min(1).max(80),
|
|
12335
|
-
fields: record(string().min(1), CompositionFieldSourceSchema)
|
|
12501
|
+
fields: record(string().min(1), CompositionFieldSourceSchema),
|
|
12502
|
+
/**
|
|
12503
|
+
* What the cap's `reset` does for THIS item (consumables). Present = the item
|
|
12504
|
+
* is resettable and a `reset` call naming its key forwards here. The item key
|
|
12505
|
+
* selects the command, so it is never an argument: there is no pass-through.
|
|
12506
|
+
*/
|
|
12507
|
+
reset: CompositionForwardCommandSchema.optional()
|
|
12336
12508
|
});
|
|
12337
12509
|
var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
12338
12510
|
kind: literal("fields"),
|
|
@@ -12344,7 +12516,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
|
12344
12516
|
* owns the WHOLE array field: the composed value holds exactly these items.
|
|
12345
12517
|
*/
|
|
12346
12518
|
items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
|
|
12347
|
-
/**
|
|
12519
|
+
/** Method name → what serves it. Only a BUILT capability takes commands (D967). */
|
|
12348
12520
|
commands: record(string().min(1), CompositionCommandTargetSchema).optional()
|
|
12349
12521
|
}), object({
|
|
12350
12522
|
kind: literal("passthrough"),
|
|
@@ -16428,119 +16600,654 @@ object({
|
|
|
16428
16600
|
lastError: string().nullable()
|
|
16429
16601
|
});
|
|
16430
16602
|
/**
|
|
16431
|
-
*
|
|
16432
|
-
* surface that admin-ui consumes through `useAddonPagesListPages()`.
|
|
16603
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
16433
16604
|
*
|
|
16434
|
-
*
|
|
16435
|
-
*
|
|
16436
|
-
*
|
|
16437
|
-
*
|
|
16438
|
-
*
|
|
16605
|
+
* Apprise-derived model (see
|
|
16606
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
16607
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
16608
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
16609
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
16610
|
+
* message to what the kind supports — callers never special-case a service.
|
|
16439
16611
|
*
|
|
16440
|
-
*
|
|
16441
|
-
*
|
|
16442
|
-
* provider
|
|
16443
|
-
*
|
|
16444
|
-
*
|
|
16612
|
+
* DESIGN DECISIONS (locked):
|
|
16613
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
16614
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
16615
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
16616
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
16617
|
+
* alternative would fork the UI per addon and cannot host the
|
|
16618
|
+
* discovery→adopt flow.
|
|
16619
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
16620
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
16621
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
16622
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
16623
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
16624
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
16625
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
16626
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
16627
|
+
* base64 fallback needed.
|
|
16628
|
+
*
|
|
16629
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
16630
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
16631
|
+
* admin "Integrations" page.
|
|
16445
16632
|
*/
|
|
16446
|
-
var AddonPageDeclarationSchema$1 = object({
|
|
16447
|
-
id: string(),
|
|
16448
|
-
label: string(),
|
|
16449
|
-
icon: string(),
|
|
16450
|
-
path: string(),
|
|
16451
|
-
remoteName: string(),
|
|
16452
|
-
bundle: string(),
|
|
16453
|
-
section: string().optional(),
|
|
16454
|
-
sectionLabel: string().optional()
|
|
16455
|
-
});
|
|
16456
|
-
var AddonPageInfoSchema = object({
|
|
16457
|
-
addonId: string(),
|
|
16458
|
-
page: AddonPageDeclarationSchema$1,
|
|
16459
|
-
bundleUrl: string()
|
|
16460
|
-
});
|
|
16461
|
-
method(_void(), array(AddonPageInfoSchema).readonly());
|
|
16462
16633
|
/**
|
|
16463
|
-
*
|
|
16464
|
-
*
|
|
16465
|
-
|
|
16466
|
-
|
|
16467
|
-
|
|
16468
|
-
|
|
16634
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
16635
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
16636
|
+
*/
|
|
16637
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
16638
|
+
"image",
|
|
16639
|
+
"video",
|
|
16640
|
+
"gif",
|
|
16641
|
+
"audio",
|
|
16642
|
+
"icon"
|
|
16643
|
+
]);
|
|
16644
|
+
/**
|
|
16645
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
16646
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
16647
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
16648
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
16649
|
+
*/
|
|
16650
|
+
var AttachmentSchema = object({
|
|
16651
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
16652
|
+
url: string().optional(),
|
|
16653
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
16654
|
+
mime: string().optional(),
|
|
16655
|
+
name: string().optional()
|
|
16656
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
16657
|
+
var NotificationFormatSchema = _enum([
|
|
16658
|
+
"text",
|
|
16659
|
+
"markdown",
|
|
16660
|
+
"html"
|
|
16661
|
+
]);
|
|
16662
|
+
/**
|
|
16663
|
+
* The CLOSED icon vocabulary an action button may use.
|
|
16469
16664
|
*
|
|
16470
|
-
*
|
|
16471
|
-
*
|
|
16472
|
-
*
|
|
16665
|
+
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
16666
|
+
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
16667
|
+
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
16668
|
+
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
16669
|
+
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
16670
|
+
*
|
|
16671
|
+
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
16672
|
+
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
16673
|
+
* differently.
|
|
16674
|
+
*
|
|
16675
|
+
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
16676
|
+
*
|
|
16677
|
+
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
16678
|
+
* the whole set onto its own renderer's vocabulary through a
|
|
16679
|
+
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
16680
|
+
* fallback, so adding a member here fails every adapter's build until someone
|
|
16681
|
+
* decides its glyph, which is the only place that decision can be made
|
|
16682
|
+
* honestly.
|
|
16683
|
+
*
|
|
16684
|
+
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
16685
|
+
* `disarm` straight through; iOS feeds that string to
|
|
16686
|
+
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
16687
|
+
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
16688
|
+
* mapping, and "the field is documented" is not "the value renders".
|
|
16689
|
+
*
|
|
16690
|
+
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
16691
|
+
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
16692
|
+
* so an addon that emits a token the running hub does not know does not lose an
|
|
16693
|
+
* icon — its whole `send` fails Zod validation and the notification never
|
|
16694
|
+
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
16473
16695
|
*/
|
|
16474
|
-
var
|
|
16696
|
+
var NotificationActionIconSchema = _enum([
|
|
16697
|
+
"acknowledge",
|
|
16698
|
+
"dismiss",
|
|
16699
|
+
"silence",
|
|
16700
|
+
"snooze",
|
|
16701
|
+
"view",
|
|
16702
|
+
"play",
|
|
16703
|
+
"open",
|
|
16704
|
+
"close",
|
|
16705
|
+
"lock",
|
|
16706
|
+
"unlock",
|
|
16707
|
+
"arm",
|
|
16708
|
+
"arm-home",
|
|
16709
|
+
"arm-away",
|
|
16710
|
+
"arm-night",
|
|
16711
|
+
"disarm",
|
|
16712
|
+
"light",
|
|
16713
|
+
"alert",
|
|
16714
|
+
"camera"
|
|
16715
|
+
]);
|
|
16716
|
+
/** A single tap-through action button. */
|
|
16717
|
+
var NotificationActionSchema = object({
|
|
16475
16718
|
id: string(),
|
|
16476
16719
|
label: string(),
|
|
16477
|
-
|
|
16478
|
-
|
|
16479
|
-
|
|
16480
|
-
* Module Federation remote name — must match the `name` field on the
|
|
16481
|
-
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
16482
|
-
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
16483
|
-
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
16484
|
-
* contain hyphens).
|
|
16485
|
-
*/
|
|
16486
|
-
remoteName: string(),
|
|
16720
|
+
url: string().optional(),
|
|
16721
|
+
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
16722
|
+
icon: NotificationActionIconSchema.optional(),
|
|
16487
16723
|
/**
|
|
16488
|
-
*
|
|
16489
|
-
*
|
|
16490
|
-
*
|
|
16491
|
-
*
|
|
16492
|
-
*
|
|
16724
|
+
* Renders in a warning style where the notifier supports it.
|
|
16725
|
+
*
|
|
16726
|
+
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
16727
|
+
* else — see `notification-center/action-token.ts` for what that does and
|
|
16728
|
+
* does not buy.
|
|
16493
16729
|
*/
|
|
16494
|
-
|
|
16730
|
+
destructive: boolean().optional(),
|
|
16495
16731
|
/**
|
|
16496
|
-
*
|
|
16497
|
-
*
|
|
16498
|
-
*
|
|
16499
|
-
* the
|
|
16500
|
-
*
|
|
16501
|
-
*
|
|
16732
|
+
* How the tap should REACH the url.
|
|
16733
|
+
*
|
|
16734
|
+
* `navigate` (absent, and every button authored before this field) opens it:
|
|
16735
|
+
* the phone leaves the notification and shows whatever the callback returns.
|
|
16736
|
+
* That is right for a button whose answer the operator wants to read.
|
|
16737
|
+
*
|
|
16738
|
+
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
16739
|
+
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
16740
|
+
* an answer to the notification, and being thrown into a browser tab to
|
|
16741
|
+
* confirm it costs more attention than the notification did. A backend that
|
|
16742
|
+
* cannot do a background call renders it as an ordinary link (the adapters
|
|
16743
|
+
* fall back rather than dropping the button), so this is a preference, never
|
|
16744
|
+
* a requirement.
|
|
16502
16745
|
*/
|
|
16503
|
-
|
|
16504
|
-
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
16505
|
-
sectionLabel: string().optional()
|
|
16746
|
+
mode: _enum(["navigate", "background"]).optional()
|
|
16506
16747
|
});
|
|
16507
|
-
|
|
16508
|
-
var
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
|
|
16514
|
-
|
|
16748
|
+
/** A delivery parameter value: what a notifier declared it takes. */
|
|
16749
|
+
var DeliveryParamValueSchema = union([
|
|
16750
|
+
string(),
|
|
16751
|
+
number(),
|
|
16752
|
+
boolean()
|
|
16753
|
+
]);
|
|
16754
|
+
/**
|
|
16755
|
+
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
16756
|
+
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
16757
|
+
* never handed one. The admin UI derives its control from this descriptor —
|
|
16758
|
+
* no editor knows a service by name.
|
|
16759
|
+
*
|
|
16760
|
+
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
16761
|
+
* run on the addon cap path).
|
|
16762
|
+
*/
|
|
16763
|
+
var DeliveryParamSchema = object({
|
|
16764
|
+
key: string().min(1),
|
|
16765
|
+
label: string(),
|
|
16766
|
+
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
16767
|
+
labelKey: string().optional(),
|
|
16768
|
+
type: _enum([
|
|
16769
|
+
"enum",
|
|
16770
|
+
"number",
|
|
16771
|
+
"boolean",
|
|
16772
|
+
"string"
|
|
16515
16773
|
]),
|
|
16516
|
-
|
|
16517
|
-
|
|
16518
|
-
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
|
|
16522
|
-
|
|
16523
|
-
|
|
16524
|
-
|
|
16774
|
+
/** `enum` only: the closed set of accepted values. */
|
|
16775
|
+
options: array(object({
|
|
16776
|
+
value: string(),
|
|
16777
|
+
label: string()
|
|
16778
|
+
})).optional(),
|
|
16779
|
+
/** `number` only. */
|
|
16780
|
+
min: number().optional(),
|
|
16781
|
+
max: number().optional(),
|
|
16782
|
+
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
16783
|
+
default: DeliveryParamValueSchema.optional()
|
|
16525
16784
|
});
|
|
16526
16785
|
/**
|
|
16527
|
-
*
|
|
16528
|
-
*
|
|
16529
|
-
*
|
|
16530
|
-
*
|
|
16531
|
-
*
|
|
16532
|
-
*
|
|
16786
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
16787
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
16788
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
16789
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
16790
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
16791
|
+
* `priority` for that one target.
|
|
16533
16792
|
*/
|
|
16534
|
-
var
|
|
16535
|
-
|
|
16536
|
-
|
|
16537
|
-
|
|
16538
|
-
|
|
16539
|
-
|
|
16540
|
-
|
|
16541
|
-
|
|
16542
|
-
|
|
16543
|
-
|
|
16793
|
+
var NotificationSchema = object({
|
|
16794
|
+
body: string(),
|
|
16795
|
+
title: string().optional(),
|
|
16796
|
+
format: NotificationFormatSchema.default("text"),
|
|
16797
|
+
priority: number().int().min(1).max(5).default(3),
|
|
16798
|
+
level: string().optional(),
|
|
16799
|
+
attachments: array(AttachmentSchema).optional(),
|
|
16800
|
+
clickUrl: string().optional(),
|
|
16801
|
+
actions: array(NotificationActionSchema).optional(),
|
|
16802
|
+
sound: string().optional(),
|
|
16803
|
+
ttl: number().optional(),
|
|
16804
|
+
tag: string().optional(),
|
|
16805
|
+
deviceId: number().optional(),
|
|
16806
|
+
eventId: string().optional(),
|
|
16807
|
+
metadata: record(string(), unknown()).optional(),
|
|
16808
|
+
/**
|
|
16809
|
+
* Values for the parameters the target's kind DECLARES in
|
|
16810
|
+
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
16811
|
+
* declaration by the sender; a notifier reads only the keys it declared.
|
|
16812
|
+
*/
|
|
16813
|
+
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
16814
|
+
});
|
|
16815
|
+
/** One declared native severity/priority level for a kind. */
|
|
16816
|
+
var TargetKindLevelSchema = object({
|
|
16817
|
+
id: string(),
|
|
16818
|
+
label: string(),
|
|
16819
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
16820
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
16821
|
+
flags: object({
|
|
16822
|
+
critical: boolean().optional(),
|
|
16823
|
+
silent: boolean().optional(),
|
|
16824
|
+
noPush: boolean().optional()
|
|
16825
|
+
}).optional(),
|
|
16826
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
16827
|
+
requires: array(string()).optional(),
|
|
16828
|
+
description: string().optional()
|
|
16829
|
+
});
|
|
16830
|
+
/** The full capability block consulted before dispatch. */
|
|
16831
|
+
var TargetKindCapsSchema = object({
|
|
16832
|
+
attachments: object({
|
|
16833
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
16834
|
+
mode: _enum([
|
|
16835
|
+
"url",
|
|
16836
|
+
"bytes",
|
|
16837
|
+
"both"
|
|
16838
|
+
]),
|
|
16839
|
+
max: number().int().nonnegative(),
|
|
16840
|
+
maxBytes: number().int().positive().optional()
|
|
16841
|
+
}),
|
|
16842
|
+
/** Max action buttons (0 = none). */
|
|
16843
|
+
actions: number().int().nonnegative(),
|
|
16844
|
+
/**
|
|
16845
|
+
* Whether this kind renders a per-action ICON.
|
|
16846
|
+
*
|
|
16847
|
+
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
16848
|
+
* run on the addon cap path — three production failures in one day taught
|
|
16849
|
+
* this repo that once. Absent is read as false by the degrade engine, which
|
|
16850
|
+
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
16851
|
+
* assumed and dropped costs the operator's trust in the field.
|
|
16852
|
+
*/
|
|
16853
|
+
actionIcons: boolean().optional(),
|
|
16854
|
+
levels: array(TargetKindLevelSchema),
|
|
16855
|
+
format: array(NotificationFormatSchema),
|
|
16856
|
+
clickUrl: boolean(),
|
|
16857
|
+
sound: boolean(),
|
|
16858
|
+
ttl: boolean(),
|
|
16859
|
+
bodyMaxLen: number().int().positive()
|
|
16860
|
+
});
|
|
16861
|
+
/**
|
|
16862
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
16863
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
16864
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
16865
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
16866
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
16867
|
+
*/
|
|
16868
|
+
var ConfigSchemaPassthrough$3 = unknown();
|
|
16869
|
+
var TargetKindSchema = object({
|
|
16870
|
+
kind: string(),
|
|
16871
|
+
label: string(),
|
|
16872
|
+
icon: string(),
|
|
16873
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
16874
|
+
addonId: string(),
|
|
16875
|
+
/**
|
|
16876
|
+
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
16877
|
+
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
16878
|
+
* when the addon bundles no icon for that kind — the client then falls back
|
|
16879
|
+
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
16880
|
+
*
|
|
16881
|
+
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
16882
|
+
* client, and a native client joins it onto its own hub base.
|
|
16883
|
+
*
|
|
16884
|
+
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
16885
|
+
* field that survived only because the runtime cap-router forwards provider
|
|
16886
|
+
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
16887
|
+
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
16888
|
+
* broken silently the moment output validation was tightened anywhere.
|
|
16889
|
+
*/
|
|
16890
|
+
iconUrl: string().optional(),
|
|
16891
|
+
/**
|
|
16892
|
+
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
16893
|
+
*
|
|
16894
|
+
* The server knows this and therefore says it, because the client cannot
|
|
16895
|
+
* safely guess: a React-Native client renders SVG and raster through two
|
|
16896
|
+
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
16897
|
+
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
16898
|
+
* placeholder glyph for every vector icon while the web build looked fine.
|
|
16899
|
+
*
|
|
16900
|
+
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
16901
|
+
* not been updated — a client that cannot determine the type should prefer
|
|
16902
|
+
* its raster path, which is the safe default for an unknown image.
|
|
16903
|
+
*/
|
|
16904
|
+
iconMediaType: string().optional(),
|
|
16905
|
+
configSchema: ConfigSchemaPassthrough$3,
|
|
16906
|
+
/**
|
|
16907
|
+
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
16908
|
+
* base URL, instance, chat, recipient). A save that changes any of them
|
|
16909
|
+
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
16910
|
+
* editor uses this to ask for the secret on exactly those edits instead of
|
|
16911
|
+
* on every edit.
|
|
16912
|
+
*
|
|
16913
|
+
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
16914
|
+
* kinds) and from a provider older than this field — an editor that finds
|
|
16915
|
+
* it absent must assume ANY edit may need the secret.
|
|
16916
|
+
*/
|
|
16917
|
+
destinationKeys: array(string()).optional(),
|
|
16918
|
+
supportsDiscovery: boolean(),
|
|
16919
|
+
/**
|
|
16920
|
+
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
16921
|
+
* Absent = none; the rule editor then offers nothing.
|
|
16922
|
+
*/
|
|
16923
|
+
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
16924
|
+
caps: TargetKindCapsSchema
|
|
16925
|
+
});
|
|
16926
|
+
/**
|
|
16927
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
16928
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
16929
|
+
* round-trip a stored secret to the UI.
|
|
16930
|
+
*/
|
|
16931
|
+
var TargetSchema = object({
|
|
16932
|
+
id: string(),
|
|
16933
|
+
name: string(),
|
|
16934
|
+
kind: string(),
|
|
16935
|
+
addonId: string(),
|
|
16936
|
+
enabled: boolean(),
|
|
16937
|
+
config: record(string(), unknown())
|
|
16938
|
+
});
|
|
16939
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
16940
|
+
var DiscoveredTargetSchema = object({
|
|
16941
|
+
kind: string(),
|
|
16942
|
+
suggestedName: string(),
|
|
16943
|
+
config: record(string(), unknown())
|
|
16944
|
+
});
|
|
16945
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
16946
|
+
var RenderedAsSchema = object({
|
|
16947
|
+
level: string(),
|
|
16948
|
+
format: NotificationFormatSchema,
|
|
16949
|
+
attachmentsSent: number().int().nonnegative(),
|
|
16950
|
+
actionsSent: number().int().nonnegative(),
|
|
16951
|
+
truncated: boolean(),
|
|
16952
|
+
dropped: array(string())
|
|
16953
|
+
});
|
|
16954
|
+
var SendResultSchema = object({
|
|
16955
|
+
success: boolean(),
|
|
16956
|
+
error: string().optional(),
|
|
16957
|
+
renderedAs: RenderedAsSchema.optional()
|
|
16958
|
+
});
|
|
16959
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
16960
|
+
var TestResultSchema = SendResultSchema;
|
|
16961
|
+
/**
|
|
16962
|
+
* Pre-save credential probe against a partial (unsaved) target config.
|
|
16963
|
+
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
16964
|
+
*/
|
|
16965
|
+
var ProbeTargetConfigResultSchema = object({
|
|
16966
|
+
ok: boolean(),
|
|
16967
|
+
resolvedUrl: string(),
|
|
16968
|
+
httpStatus: number().int().optional(),
|
|
16969
|
+
stateInstance: string().optional(),
|
|
16970
|
+
error: string().optional()
|
|
16971
|
+
});
|
|
16972
|
+
var notificationOutputCapability = {
|
|
16973
|
+
name: "notification-output",
|
|
16974
|
+
scope: "system",
|
|
16975
|
+
mode: "collection",
|
|
16976
|
+
methods: {
|
|
16977
|
+
listTargetKinds: method(object({}), array(TargetKindSchema)),
|
|
16978
|
+
listTargets: method(object({}), array(TargetSchema), { auth: "admin" }),
|
|
16979
|
+
/**
|
|
16980
|
+
* One target WITH secrets — admin edit / duplicate hydration only.
|
|
16981
|
+
* `listTargets` stays redacted; the editor loads this so password fields
|
|
16982
|
+
* are filled (masked in the input) and a save can round-trip the token
|
|
16983
|
+
* when a destination changes (Duplicate with a new chatId, etc.).
|
|
16984
|
+
*/
|
|
16985
|
+
getTarget: method(object({ targetId: string() }), TargetSchema, { auth: "admin" }),
|
|
16986
|
+
discoverTargets: method(object({
|
|
16987
|
+
kind: string(),
|
|
16988
|
+
config: record(string(), unknown()).optional(),
|
|
16989
|
+
/**
|
|
16990
|
+
* When editing / duplicating, the blank password field never round-trips
|
|
16991
|
+
* the stored token. Pass the source target id so the provider can restore
|
|
16992
|
+
* secrets server-side before dialling (same merge as upsert).
|
|
16993
|
+
*/
|
|
16994
|
+
targetId: string().optional()
|
|
16995
|
+
}), array(DiscoveredTargetSchema), { auth: "admin" }),
|
|
16996
|
+
probeTargetConfig: method(object({
|
|
16997
|
+
kind: string(),
|
|
16998
|
+
config: record(string(), unknown()),
|
|
16999
|
+
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
17000
|
+
targetId: string().optional()
|
|
17001
|
+
}), ProbeTargetConfigResultSchema, {
|
|
17002
|
+
kind: "mutation",
|
|
17003
|
+
auth: "admin"
|
|
17004
|
+
}),
|
|
17005
|
+
send: method(object({
|
|
17006
|
+
targetId: string(),
|
|
17007
|
+
notification: NotificationSchema
|
|
17008
|
+
}), SendResultSchema, {
|
|
17009
|
+
kind: "mutation",
|
|
17010
|
+
auth: "admin"
|
|
17011
|
+
}),
|
|
17012
|
+
testTarget: method(object({
|
|
17013
|
+
targetId: string(),
|
|
17014
|
+
sample: NotificationSchema.optional()
|
|
17015
|
+
}), TestResultSchema, {
|
|
17016
|
+
kind: "mutation",
|
|
17017
|
+
auth: "admin"
|
|
17018
|
+
}),
|
|
17019
|
+
upsertTarget: method(object({
|
|
17020
|
+
target: TargetSchema,
|
|
17021
|
+
/**
|
|
17022
|
+
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
17023
|
+
* existing row. The UI never holds the stored token; blank / redacted
|
|
17024
|
+
* password fields are restored from this source (D634 still applies if
|
|
17025
|
+
* destinations diverge without a re-typed secret).
|
|
17026
|
+
*/
|
|
17027
|
+
cloneSecretsFrom: string().optional()
|
|
17028
|
+
}), TargetSchema, {
|
|
17029
|
+
kind: "mutation",
|
|
17030
|
+
auth: "admin"
|
|
17031
|
+
}),
|
|
17032
|
+
deleteTarget: method(object({ targetId: string() }), _void(), {
|
|
17033
|
+
kind: "mutation",
|
|
17034
|
+
auth: "admin"
|
|
17035
|
+
}),
|
|
17036
|
+
setTargetEnabled: method(object({
|
|
17037
|
+
targetId: string(),
|
|
17038
|
+
enabled: boolean()
|
|
17039
|
+
}), _void(), {
|
|
17040
|
+
kind: "mutation",
|
|
17041
|
+
auth: "admin"
|
|
17042
|
+
})
|
|
17043
|
+
}
|
|
17044
|
+
};
|
|
17045
|
+
function errorText(err) {
|
|
17046
|
+
return err instanceof Error ? err.message : String(err);
|
|
17047
|
+
}
|
|
17048
|
+
/** `actions.run` flattens a refusal to `[code] text`; the code leads. */
|
|
17049
|
+
function refusalFromThrown(message) {
|
|
17050
|
+
if (message.startsWith("[busy]")) return "already-running";
|
|
17051
|
+
if (message.startsWith("[not-found]")) return "not-found";
|
|
17052
|
+
return null;
|
|
17053
|
+
}
|
|
17054
|
+
async function runExportedAction(api, log, input) {
|
|
17055
|
+
const { exporter, actionId } = input;
|
|
17056
|
+
const tags = { actionId };
|
|
17057
|
+
const refuse = (reason, detail) => {
|
|
17058
|
+
log.warn(`${exporter}: exported action not run — ${reason}`, {
|
|
17059
|
+
tags,
|
|
17060
|
+
meta: {
|
|
17061
|
+
reason,
|
|
17062
|
+
...detail ? { detail } : {}
|
|
17063
|
+
}
|
|
17064
|
+
});
|
|
17065
|
+
return {
|
|
17066
|
+
status: "refused",
|
|
17067
|
+
reason
|
|
17068
|
+
};
|
|
17069
|
+
};
|
|
17070
|
+
try {
|
|
17071
|
+
const result = await api.actions.run.mutate({ actionId });
|
|
17072
|
+
if (result.outcome === "skipped-overlap") return refuse("already-running", result.message);
|
|
17073
|
+
if (result.outcome === "skipped-cooldown") return refuse("cooldown", result.message);
|
|
17074
|
+
if (result.outcome === "failed" || result.outcome === "aborted") {
|
|
17075
|
+
const message = result.message ?? result.outcome;
|
|
17076
|
+
log.warn(`${exporter}: exported action failed`, {
|
|
17077
|
+
tags,
|
|
17078
|
+
meta: {
|
|
17079
|
+
outcome: result.outcome,
|
|
17080
|
+
message
|
|
17081
|
+
}
|
|
17082
|
+
});
|
|
17083
|
+
return {
|
|
17084
|
+
status: "failed",
|
|
17085
|
+
message
|
|
17086
|
+
};
|
|
17087
|
+
}
|
|
17088
|
+
log.info(`${exporter}: exported action ran`, {
|
|
17089
|
+
tags,
|
|
17090
|
+
meta: { outcome: result.outcome }
|
|
17091
|
+
});
|
|
17092
|
+
return { status: "started" };
|
|
17093
|
+
} catch (err) {
|
|
17094
|
+
const message = errorText(err);
|
|
17095
|
+
const reason = refusalFromThrown(message);
|
|
17096
|
+
if (reason !== null) return refuse(reason, message);
|
|
17097
|
+
log.warn(`${exporter}: exported action failed`, {
|
|
17098
|
+
tags,
|
|
17099
|
+
meta: { message }
|
|
17100
|
+
});
|
|
17101
|
+
return {
|
|
17102
|
+
status: "failed",
|
|
17103
|
+
message
|
|
17104
|
+
};
|
|
17105
|
+
}
|
|
17106
|
+
}
|
|
17107
|
+
/** Send through one CamStack notification target on behalf of an exporter. */
|
|
17108
|
+
async function sendToExportedTarget(api, log, input) {
|
|
17109
|
+
const { exporter, targetId } = input;
|
|
17110
|
+
const tags = { targetId };
|
|
17111
|
+
try {
|
|
17112
|
+
const target = (await api.notificationOutput.listTargets.query({})).find((t) => t.id === targetId);
|
|
17113
|
+
if (target === void 0) {
|
|
17114
|
+
log.warn(`${exporter}: exported notifier not used — target-gone`, {
|
|
17115
|
+
tags,
|
|
17116
|
+
meta: { reason: "target-gone" }
|
|
17117
|
+
});
|
|
17118
|
+
return {
|
|
17119
|
+
status: "refused",
|
|
17120
|
+
reason: "target-gone"
|
|
17121
|
+
};
|
|
17122
|
+
}
|
|
17123
|
+
if (!target.enabled) {
|
|
17124
|
+
log.warn(`${exporter}: exported notifier not used — target-disabled`, {
|
|
17125
|
+
tags,
|
|
17126
|
+
meta: { reason: "target-disabled" }
|
|
17127
|
+
});
|
|
17128
|
+
return {
|
|
17129
|
+
status: "refused",
|
|
17130
|
+
reason: "target-disabled"
|
|
17131
|
+
};
|
|
17132
|
+
}
|
|
17133
|
+
const result = await api.notificationOutput.send.mutate({
|
|
17134
|
+
targetId,
|
|
17135
|
+
addonId: target.addonId,
|
|
17136
|
+
notification: NotificationSchema.parse(input.notification)
|
|
17137
|
+
});
|
|
17138
|
+
if (!result.success) {
|
|
17139
|
+
const message = result.error ?? "send failed";
|
|
17140
|
+
log.warn(`${exporter}: exported notifier send failed`, {
|
|
17141
|
+
tags,
|
|
17142
|
+
meta: { message }
|
|
17143
|
+
});
|
|
17144
|
+
return {
|
|
17145
|
+
status: "failed",
|
|
17146
|
+
message
|
|
17147
|
+
};
|
|
17148
|
+
}
|
|
17149
|
+
return { status: "sent" };
|
|
17150
|
+
} catch (err) {
|
|
17151
|
+
const message = errorText(err);
|
|
17152
|
+
log.warn(`${exporter}: exported notifier send failed`, {
|
|
17153
|
+
tags,
|
|
17154
|
+
meta: { message }
|
|
17155
|
+
});
|
|
17156
|
+
return {
|
|
17157
|
+
status: "failed",
|
|
17158
|
+
message
|
|
17159
|
+
};
|
|
17160
|
+
}
|
|
17161
|
+
}
|
|
17162
|
+
/**
|
|
17163
|
+
* Split an exporter's selection into what still exists and what was deleted.
|
|
17164
|
+
* Devices are never judged here (their lifecycle is the tombstone, D865).
|
|
17165
|
+
* Callers pass ids from a query that ANSWERED — an unanswered list must not
|
|
17166
|
+
* reach this function (D49: a failed read changes nothing).
|
|
17167
|
+
*/
|
|
17168
|
+
function pruneDeletedEntities(selection, live, log, exporter) {
|
|
17169
|
+
const kept = [];
|
|
17170
|
+
const dropped = [];
|
|
17171
|
+
for (const ref of selection) if (ref.kind === "action" && !live.actionIds.has(ref.actionId)) {
|
|
17172
|
+
dropped.push(ref);
|
|
17173
|
+
log.warn(`${exporter}: exported action was deleted — dropped from the export`, { tags: { actionId: ref.actionId } });
|
|
17174
|
+
} else if (ref.kind === "notifier" && !live.targetIds.has(ref.targetId)) {
|
|
17175
|
+
dropped.push(ref);
|
|
17176
|
+
log.warn(`${exporter}: exported notifier target was deleted — dropped from the export`, { tags: { targetId: ref.targetId } });
|
|
17177
|
+
} else kept.push(ref);
|
|
17178
|
+
return {
|
|
17179
|
+
kept,
|
|
17180
|
+
dropped
|
|
17181
|
+
};
|
|
17182
|
+
}
|
|
17183
|
+
/**
|
|
17184
|
+
* `addon-pages` — system-scoped singleton aggregator cap. Public-facing
|
|
17185
|
+
* surface that admin-ui consumes through `useAddonPagesListPages()`.
|
|
17186
|
+
*
|
|
17187
|
+
* The provider iterates every `addon-pages-source` (collection) provider
|
|
17188
|
+
* and emits `AddonPageInfo[]` enriched with versioned `bundleUrl` strings
|
|
17189
|
+
* pointing at `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. The
|
|
17190
|
+
* filesystem `mtime` cache-buster lets the browser pick up addon
|
|
17191
|
+
* rebuilds without manual reload.
|
|
17192
|
+
*
|
|
17193
|
+
* The hub-local builtin `addon-pages-aggregator` (see
|
|
17194
|
+
* `@camstack/system/builtins/addon-pages-aggregator`) registers the
|
|
17195
|
+
* provider. Splitting the public aggregator from the raw collection
|
|
17196
|
+
* keeps both ends in codegen — there's no hand-written
|
|
17197
|
+
* `addon-pages.router.ts` wrapper anymore.
|
|
17198
|
+
*/
|
|
17199
|
+
var AddonPageDeclarationSchema$1 = object({
|
|
17200
|
+
id: string(),
|
|
17201
|
+
label: string(),
|
|
17202
|
+
icon: string(),
|
|
17203
|
+
path: string(),
|
|
17204
|
+
remoteName: string(),
|
|
17205
|
+
bundle: string(),
|
|
17206
|
+
section: string().optional(),
|
|
17207
|
+
sectionLabel: string().optional()
|
|
17208
|
+
});
|
|
17209
|
+
var AddonPageInfoSchema = object({
|
|
17210
|
+
addonId: string(),
|
|
17211
|
+
page: AddonPageDeclarationSchema$1,
|
|
17212
|
+
bundleUrl: string()
|
|
17213
|
+
});
|
|
17214
|
+
method(_void(), array(AddonPageInfoSchema).readonly());
|
|
17215
|
+
var AddonHttpRouteSchema = object({
|
|
17216
|
+
method: _enum([
|
|
17217
|
+
"GET",
|
|
17218
|
+
"POST",
|
|
17219
|
+
"PUT",
|
|
17220
|
+
"DELETE",
|
|
17221
|
+
"PATCH"
|
|
17222
|
+
]),
|
|
17223
|
+
path: string(),
|
|
17224
|
+
access: _enum([
|
|
17225
|
+
"public",
|
|
17226
|
+
"authenticated",
|
|
17227
|
+
"admin"
|
|
17228
|
+
]).optional(),
|
|
17229
|
+
description: string().optional(),
|
|
17230
|
+
/** The hub limits this route per client address before the handler runs. */
|
|
17231
|
+
rateLimit: _enum(["auth"]).optional()
|
|
17232
|
+
});
|
|
17233
|
+
/**
|
|
17234
|
+
* Cross-process route invocation envelope. The hub captures the
|
|
17235
|
+
* request as plain data, ships it to the worker via Moleculer, and
|
|
17236
|
+
* the worker runs the local handler against a capturing reply. The
|
|
17237
|
+
* envelope returned describes what the handler intended (status,
|
|
17238
|
+
* headers, body, or a redirect) so the hub can translate it back to
|
|
17239
|
+
* the Fastify reply that's actually wired to the socket.
|
|
17240
|
+
*/
|
|
17241
|
+
var InvokeRequestSchema = object({
|
|
17242
|
+
method: string(),
|
|
17243
|
+
path: string(),
|
|
17244
|
+
params: record(string(), string()),
|
|
17245
|
+
query: record(string(), string()),
|
|
17246
|
+
body: unknown(),
|
|
17247
|
+
headers: record(string(), string()),
|
|
17248
|
+
user: object({
|
|
17249
|
+
id: string(),
|
|
17250
|
+
username: string(),
|
|
16544
17251
|
isAdmin: boolean()
|
|
16545
17252
|
}).optional(),
|
|
16546
17253
|
scopedToken: unknown().optional()
|
|
@@ -16805,9 +17512,21 @@ var AlertSeveritySchema = _enum([
|
|
|
16805
17512
|
"error",
|
|
16806
17513
|
"critical"
|
|
16807
17514
|
]);
|
|
17515
|
+
/**
|
|
17516
|
+
* An action that RUNS something instead of going somewhere: the admin UI calls
|
|
17517
|
+
* the addon's custom action (`addons.custom`) with exactly these fields. The
|
|
17518
|
+
* producer owns the meaning; the UI knows nothing about the addon or the kind.
|
|
17519
|
+
*/
|
|
17520
|
+
var AlertActionInvokeSchema = object({
|
|
17521
|
+
addonId: string().min(1),
|
|
17522
|
+
action: string().min(1),
|
|
17523
|
+
input: record(string(), unknown()).optional()
|
|
17524
|
+
});
|
|
17525
|
+
/** An action has `href` (a link) OR `invoke` (a button that runs a custom action). */
|
|
16808
17526
|
var AlertActionSchema = object({
|
|
16809
17527
|
label: string(),
|
|
16810
17528
|
href: string().optional(),
|
|
17529
|
+
invoke: AlertActionInvokeSchema.optional(),
|
|
16811
17530
|
/**
|
|
16812
17531
|
* How `href` is followed. Absent / `app`: an admin-ui route (client-side).
|
|
16813
17532
|
* `server`: a route the HUB serves (an addon HTTP route such as an OAuth
|
|
@@ -19209,7 +19928,11 @@ var CompositionProblemSchema = object({
|
|
|
19209
19928
|
"mixed-quantity",
|
|
19210
19929
|
"helper-mismatch",
|
|
19211
19930
|
"member-constraint",
|
|
19212
|
-
"retention"
|
|
19931
|
+
"retention",
|
|
19932
|
+
"command-method-unknown",
|
|
19933
|
+
"command-source-unknown",
|
|
19934
|
+
"command-input-unknown",
|
|
19935
|
+
"command-input-missing"
|
|
19213
19936
|
]),
|
|
19214
19937
|
path: string(),
|
|
19215
19938
|
message: string()
|
|
@@ -19248,7 +19971,9 @@ var CompositionFieldRoleSchema = _enum([
|
|
|
19248
19971
|
var CompositionFieldItemSchema = object({
|
|
19249
19972
|
arrayPath: string(),
|
|
19250
19973
|
key: string(),
|
|
19251
|
-
label: string()
|
|
19974
|
+
label: string(),
|
|
19975
|
+
/** True when the item carries a `reset` forward: the composer writes `resettable` from this (D967). */
|
|
19976
|
+
resettable: boolean().optional()
|
|
19252
19977
|
});
|
|
19253
19978
|
var CompositionFieldPlanSchema = object({
|
|
19254
19979
|
cap: string(),
|
|
@@ -19453,7 +20178,7 @@ Object.freeze({
|
|
|
19453
20178
|
* One catalog row per base reading a group helper can build (D944 §3).
|
|
19454
20179
|
* Data, not a form default written into the block.
|
|
19455
20180
|
*/
|
|
19456
|
-
var NOW$
|
|
20181
|
+
var NOW$3 = {
|
|
19457
20182
|
fieldPath: "lastFetchedAt",
|
|
19458
20183
|
expr: "now"
|
|
19459
20184
|
};
|
|
@@ -19505,7 +20230,7 @@ var GROUP_TEMPLATES = [
|
|
|
19505
20230
|
quantity: "temperature",
|
|
19506
20231
|
defaultAggregate: "mean",
|
|
19507
20232
|
empty: "null",
|
|
19508
|
-
companions: [NOW$
|
|
20233
|
+
companions: [NOW$3],
|
|
19509
20234
|
alsoFrom: {
|
|
19510
20235
|
deviceTypes: CLIMATE_DEVICES,
|
|
19511
20236
|
accept: {
|
|
@@ -19523,7 +20248,7 @@ var GROUP_TEMPLATES = [
|
|
|
19523
20248
|
quantity: "humidity",
|
|
19524
20249
|
defaultAggregate: "mean",
|
|
19525
20250
|
empty: "null",
|
|
19526
|
-
companions: [NOW$
|
|
20251
|
+
companions: [NOW$3],
|
|
19527
20252
|
alsoFrom: {
|
|
19528
20253
|
deviceTypes: CLIMATE_DEVICES,
|
|
19529
20254
|
accept: {
|
|
@@ -19541,7 +20266,7 @@ var GROUP_TEMPLATES = [
|
|
|
19541
20266
|
quantity: "pressure",
|
|
19542
20267
|
defaultAggregate: "mean",
|
|
19543
20268
|
empty: "null",
|
|
19544
|
-
companions: [NOW$
|
|
20269
|
+
companions: [NOW$3]
|
|
19545
20270
|
}),
|
|
19546
20271
|
numeric({
|
|
19547
20272
|
id: "illuminance",
|
|
@@ -19552,7 +20277,7 @@ var GROUP_TEMPLATES = [
|
|
|
19552
20277
|
quantity: "illuminance",
|
|
19553
20278
|
defaultAggregate: "mean",
|
|
19554
20279
|
empty: "null",
|
|
19555
|
-
companions: [NOW$
|
|
20280
|
+
companions: [NOW$3]
|
|
19556
20281
|
}),
|
|
19557
20282
|
numeric({
|
|
19558
20283
|
id: "power",
|
|
@@ -19563,7 +20288,7 @@ var GROUP_TEMPLATES = [
|
|
|
19563
20288
|
quantity: "power",
|
|
19564
20289
|
defaultAggregate: "sum",
|
|
19565
20290
|
empty: "omit",
|
|
19566
|
-
companions: [NOW$
|
|
20291
|
+
companions: [NOW$3]
|
|
19567
20292
|
}),
|
|
19568
20293
|
numeric({
|
|
19569
20294
|
id: "energy",
|
|
@@ -19574,7 +20299,7 @@ var GROUP_TEMPLATES = [
|
|
|
19574
20299
|
quantity: "energy",
|
|
19575
20300
|
defaultAggregate: "sum",
|
|
19576
20301
|
empty: "omit",
|
|
19577
|
-
companions: [NOW$
|
|
20302
|
+
companions: [NOW$3]
|
|
19578
20303
|
}),
|
|
19579
20304
|
numeric({
|
|
19580
20305
|
id: "battery",
|
|
@@ -19609,7 +20334,7 @@ var GROUP_TEMPLATES = [
|
|
|
19609
20334
|
quantity: "device",
|
|
19610
20335
|
defaultAggregate: "mean",
|
|
19611
20336
|
empty: "null",
|
|
19612
|
-
companions: [NOW$
|
|
20337
|
+
companions: [NOW$3]
|
|
19613
20338
|
}),
|
|
19614
20339
|
binary({
|
|
19615
20340
|
id: "motion",
|
|
@@ -22304,6 +23029,36 @@ var ExposeInputSchema = object({
|
|
|
22304
23029
|
capabilities: array(string()).optional()
|
|
22305
23030
|
});
|
|
22306
23031
|
var UnexposeInputSchema = object({ deviceId: string() });
|
|
23032
|
+
/**
|
|
23033
|
+
* What an exporter may expose besides devices (D968). An action and a
|
|
23034
|
+
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
23035
|
+
* exporter's own selection, not the device registry.
|
|
23036
|
+
*/
|
|
23037
|
+
var ExportEntityKindSchema = _enum([
|
|
23038
|
+
"device",
|
|
23039
|
+
"action",
|
|
23040
|
+
"notifier"
|
|
23041
|
+
]);
|
|
23042
|
+
var ExportEntityRefSchema = discriminatedUnion("kind", [
|
|
23043
|
+
object({
|
|
23044
|
+
kind: literal("device"),
|
|
23045
|
+
deviceId: string()
|
|
23046
|
+
}),
|
|
23047
|
+
object({
|
|
23048
|
+
kind: literal("action"),
|
|
23049
|
+
actionId: string().min(1)
|
|
23050
|
+
}),
|
|
23051
|
+
object({
|
|
23052
|
+
kind: literal("notifier"),
|
|
23053
|
+
targetId: string().min(1)
|
|
23054
|
+
})
|
|
23055
|
+
]);
|
|
23056
|
+
var ExposedEntitySchema = object({
|
|
23057
|
+
entity: ExportEntityRefSchema,
|
|
23058
|
+
exposedAs: string().optional()
|
|
23059
|
+
});
|
|
23060
|
+
var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
23061
|
+
var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
22307
23062
|
var deviceExportCapability = {
|
|
22308
23063
|
name: "device-export",
|
|
22309
23064
|
scope: "system",
|
|
@@ -22333,6 +23088,16 @@ var deviceExportCapability = {
|
|
|
22333
23088
|
unexposeDevice: method(UnexposeInputSchema, _void(), {
|
|
22334
23089
|
kind: "mutation",
|
|
22335
23090
|
auth: "admin"
|
|
23091
|
+
}),
|
|
23092
|
+
listSupportedEntityKinds: optionalMethod(_void(), array(ExportEntityKindSchema)),
|
|
23093
|
+
listExposedEntities: optionalMethod(_void(), array(ExposedEntitySchema)),
|
|
23094
|
+
exposeEntity: optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
23095
|
+
kind: "mutation",
|
|
23096
|
+
auth: "admin"
|
|
23097
|
+
}),
|
|
23098
|
+
unexposeEntity: optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
23099
|
+
kind: "mutation",
|
|
23100
|
+
auth: "admin"
|
|
22336
23101
|
})
|
|
22337
23102
|
}
|
|
22338
23103
|
};
|
|
@@ -43556,6 +44321,14 @@ method(object({
|
|
|
43556
44321
|
kind: "mutation",
|
|
43557
44322
|
auth: "admin"
|
|
43558
44323
|
});
|
|
44324
|
+
method(object({
|
|
44325
|
+
nodeId: string().min(1),
|
|
44326
|
+
/** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
|
|
44327
|
+
removedAt: number()
|
|
44328
|
+
}), object({ counts: record(string(), number().int().nonnegative()) }), {
|
|
44329
|
+
kind: "mutation",
|
|
44330
|
+
auth: "admin"
|
|
44331
|
+
});
|
|
43559
44332
|
/**
|
|
43560
44333
|
* `device-removal-hook` — the reliable way an addon learns a device was DELETED,
|
|
43561
44334
|
* so it can delete what it keeps for that device (D865).
|
|
@@ -44276,7 +45049,7 @@ var LlmProviderWidgetSchema = object({
|
|
|
44276
45049
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
44277
45050
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
44278
45051
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
44279
|
-
var ConfigSchemaPassthrough$
|
|
45052
|
+
var ConfigSchemaPassthrough$2 = unknown();
|
|
44280
45053
|
var LlmProfileKindDescriptorSchema = object({
|
|
44281
45054
|
kind: LlmProfileKindSchema,
|
|
44282
45055
|
label: string(),
|
|
@@ -44285,9 +45058,9 @@ var LlmProfileKindDescriptorSchema = object({
|
|
|
44285
45058
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
44286
45059
|
addonId: string(),
|
|
44287
45060
|
/** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
|
|
44288
|
-
connectionSchema: ConfigSchemaPassthrough$
|
|
45061
|
+
connectionSchema: ConfigSchemaPassthrough$2,
|
|
44289
45062
|
/** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
|
|
44290
|
-
advancedSchema: ConfigSchemaPassthrough$
|
|
45063
|
+
advancedSchema: ConfigSchemaPassthrough$2.optional(),
|
|
44291
45064
|
/** The kind's defaults; a model from `listModels` may override any of them. */
|
|
44292
45065
|
features: LlmModelFeaturesSchema,
|
|
44293
45066
|
/** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
|
|
@@ -44813,10 +45586,10 @@ var CpuBreakdownSchema = object({
|
|
|
44813
45586
|
cores: number()
|
|
44814
45587
|
});
|
|
44815
45588
|
var MemoryInfoSchema = object({
|
|
44816
|
-
percent: number(),
|
|
45589
|
+
percent: number().nullable(),
|
|
44817
45590
|
totalBytes: number(),
|
|
44818
|
-
usedBytes: number(),
|
|
44819
|
-
availableBytes: number(),
|
|
45591
|
+
usedBytes: number().nullable(),
|
|
45592
|
+
availableBytes: number().nullable(),
|
|
44820
45593
|
swapUsedBytes: number(),
|
|
44821
45594
|
swapTotalBytes: number()
|
|
44822
45595
|
});
|
|
@@ -45084,722 +45857,280 @@ var NodeLoadSeriesSchema = object({
|
|
|
45084
45857
|
/** The fixed sampling cadence in force on the cluster, in ms. */
|
|
45085
45858
|
cadenceMs: number(),
|
|
45086
45859
|
/**
|
|
45087
|
-
* Did the DURABLE tier contribute? `false` means the answer is the hot ring
|
|
45088
|
-
* alone — an agent (which holds no table), or a store that refused.
|
|
45089
|
-
* Reported because "the last hour" and "the last six hours" are different
|
|
45090
|
-
* questions and an operator must not have to guess which was answered.
|
|
45091
|
-
*/
|
|
45092
|
-
durable: boolean()
|
|
45093
|
-
});
|
|
45094
|
-
var GetLoadSeriesInputSchema = object({
|
|
45095
|
-
/**
|
|
45096
|
-
* The node whose series is wanted.
|
|
45097
|
-
*
|
|
45098
|
-
* NOT named `nodeId`: the generated cap router strips a top-level
|
|
45099
|
-
* `nodeId` from every method input and uses it to ROUTE the call to
|
|
45100
|
-
* that node's provider (`generated-cap-routers.ts`). A series target
|
|
45101
|
-
* called `nodeId` would silently become a routing pin and never reach
|
|
45102
|
-
* the provider. The hub holds every node it hears from, so the
|
|
45103
|
-
* ordinary call is unpinned — answered by the hub, for any node.
|
|
45104
|
-
*/
|
|
45105
|
-
forNodeId: string(),
|
|
45106
|
-
/**
|
|
45107
|
-
* EXCLUSIVE lower bound. A caller passes the newest `atMs` it already
|
|
45108
|
-
* holds and receives only what it is missing, so seeding a live chart
|
|
45109
|
-
* from this method cannot double a point already drawn.
|
|
45110
|
-
*/
|
|
45111
|
-
sinceMs: number().optional(),
|
|
45112
|
-
/**
|
|
45113
|
-
* Most points the caller wants PER FUNCTION. The window is reduced to fit,
|
|
45114
|
-
* preserving min and max per bucket.
|
|
45115
|
-
*
|
|
45116
|
-
* Absent means NO reduction — legitimate for a short window and a trap for a
|
|
45117
|
-
* long one, which is why a chart passes its own pixel width.
|
|
45118
|
-
*/
|
|
45119
|
-
maxPoints: number().int().positive().optional()
|
|
45120
|
-
});
|
|
45121
|
-
var SystemMetricsSchema = object({
|
|
45122
|
-
cpuPercent: number(),
|
|
45123
|
-
memoryPercent: number(),
|
|
45124
|
-
memoryUsedMB: number(),
|
|
45125
|
-
memoryTotalMB: number(),
|
|
45126
|
-
diskPercent: number().optional(),
|
|
45127
|
-
temperature: number().optional(),
|
|
45128
|
-
gpuPercent: number().optional(),
|
|
45129
|
-
gpuMemoryPercent: number().optional()
|
|
45130
|
-
});
|
|
45131
|
-
method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema);
|
|
45132
|
-
method(object({
|
|
45133
|
-
sourceUrl: string(),
|
|
45134
|
-
metadata: ModelConvertMetadataSchema,
|
|
45135
|
-
targets: array(ConvertTargetSchema).min(1).readonly(),
|
|
45136
|
-
calibrationRef: string().optional(),
|
|
45137
|
-
sessionId: string().optional()
|
|
45138
|
-
}), ConvertResultSchema, {
|
|
45139
|
-
kind: "mutation",
|
|
45140
|
-
auth: "admin",
|
|
45141
|
-
timeoutMs: 6e5
|
|
45142
|
-
});
|
|
45143
|
-
method(object({
|
|
45144
|
-
targetNodeId: string(),
|
|
45145
|
-
modelId: string(),
|
|
45146
|
-
format: _enum(MODEL_FORMATS),
|
|
45147
|
-
entry: ModelCatalogEntrySchema
|
|
45148
|
-
}), object({
|
|
45149
|
-
ok: boolean(),
|
|
45150
|
-
/** sha256 of the staged tarball (empty for a hub-local no-op). */
|
|
45151
|
-
sha256: string(),
|
|
45152
|
-
bytes: number(),
|
|
45153
|
-
/** The target node's modelsDir the artifact landed in. */
|
|
45154
|
-
path: string()
|
|
45155
|
-
}), {
|
|
45156
|
-
kind: "mutation",
|
|
45157
|
-
auth: "admin"
|
|
45158
|
-
});
|
|
45159
|
-
var MotionRegionSchema = object({
|
|
45160
|
-
bbox: BoundingBoxSchema,
|
|
45161
|
-
pixelCount: number(),
|
|
45162
|
-
intensity: number()
|
|
45163
|
-
});
|
|
45164
|
-
var MotionAnalysisResultSchema = object({
|
|
45165
|
-
detected: boolean(),
|
|
45166
|
-
regionCount: number(),
|
|
45167
|
-
/** Regions passing minArea filter — used for pipeline triggering. */
|
|
45168
|
-
regions: array(MotionRegionSchema).readonly(),
|
|
45169
|
-
/** All raw regions from CCL before minArea filter — used for UI debug overlay. */
|
|
45170
|
-
rawRegions: array(MotionRegionSchema).readonly(),
|
|
45171
|
-
frameWidth: number(),
|
|
45172
|
-
frameHeight: number(),
|
|
45173
|
-
analysisMs: number()
|
|
45174
|
-
});
|
|
45175
|
-
DeviceType.Camera, method(object({
|
|
45176
|
-
deviceId: number(),
|
|
45177
|
-
frame: FrameInputSchema.optional(),
|
|
45178
|
-
frameHandle: FrameHandleSchema.optional()
|
|
45179
|
-
}), MotionAnalysisResultSchema, {
|
|
45180
|
-
role: "admin",
|
|
45181
|
-
kind: "mutation"
|
|
45182
|
-
}), method(object({ deviceId: number() }), _void(), {
|
|
45183
|
-
role: "admin",
|
|
45184
|
-
kind: "mutation"
|
|
45185
|
-
}), method(_void(), _void(), {
|
|
45186
|
-
role: "admin",
|
|
45187
|
-
kind: "mutation"
|
|
45188
|
-
}), object({
|
|
45189
|
-
deviceId: number(),
|
|
45190
|
-
detected: boolean(),
|
|
45191
|
-
regionCount: number()
|
|
45192
|
-
});
|
|
45193
|
-
/**
|
|
45194
|
-
* `mqtt-broker` — broker-registry cap.
|
|
45195
|
-
*
|
|
45196
|
-
* NOT a pub/sub proxy. The cap exposes (a) a registry of configured
|
|
45197
|
-
* MQTT brokers (external + optionally an embedded `aedes`-backed one)
|
|
45198
|
-
* and (b) the connection details a consumer addon needs to spin up
|
|
45199
|
-
* its OWN `mqtt.js` client.
|
|
45200
|
-
*
|
|
45201
|
-
* Why: pub/sub routing over the system event-bus loses fidelity
|
|
45202
|
-
* (callback shape, QoS guarantees, will/retain semantics) and adds
|
|
45203
|
-
* refcount bookkeeping that addons would rather own themselves. The
|
|
45204
|
-
* canonical consumer needs raw `mqtt.js`
|
|
45205
|
-
* features anyway — give it the connection config, get out of the way.
|
|
45206
|
-
*
|
|
45207
|
-
* Consumer flow:
|
|
45208
|
-
* const cfg = await ctx.api.mqttBroker.getBrokerConfig({ id })
|
|
45209
|
-
* const client = mqtt.connect(cfg.url, { username: cfg.username, … })
|
|
45210
|
-
* client.subscribe('zigbee2mqtt/+')
|
|
45211
|
-
*
|
|
45212
|
-
* Collection mode: multiple brokers (e.g. one local mosquitto + one
|
|
45213
|
-
* cloud bridge). The "embedded" entry (when present) is just another
|
|
45214
|
-
* broker in the registry — its lifecycle is owned by the addon that
|
|
45215
|
-
* spawned it.
|
|
45216
|
-
*/
|
|
45217
|
-
var BrokerKindSchema = _enum(["external", "embedded"]);
|
|
45218
|
-
/**
|
|
45219
|
-
* Broker live-probe status.
|
|
45220
|
-
*
|
|
45221
|
-
* - `connected` — last probe completed a clean CONNACK
|
|
45222
|
-
* - `disconnected` — no probe has run yet (cold cache)
|
|
45223
|
-
* - `auth-failed` — CONNACK refused with auth error (RC 4 / 5)
|
|
45224
|
-
* - `unreachable` — TCP connect timed out / refused
|
|
45225
|
-
* - `tls-error` — TLS handshake failed (cert / SNI / cipher)
|
|
45226
|
-
*/
|
|
45227
|
-
var BrokerStatusSchema$1 = _enum([
|
|
45228
|
-
"connected",
|
|
45229
|
-
"disconnected",
|
|
45230
|
-
"auth-failed",
|
|
45231
|
-
"unreachable",
|
|
45232
|
-
"tls-error"
|
|
45233
|
-
]);
|
|
45234
|
-
var BrokerInfoSchema$1 = object({
|
|
45235
|
-
id: string(),
|
|
45236
|
-
name: string(),
|
|
45237
|
-
url: string(),
|
|
45238
|
-
kind: BrokerKindSchema,
|
|
45239
|
-
status: BrokerStatusSchema$1,
|
|
45240
|
-
latencyMs: number().nullable(),
|
|
45241
|
-
error: string().optional(),
|
|
45242
|
-
/** Embedded brokers only: number of MQTT clients currently connected. */
|
|
45243
|
-
connectedClients: number().int().nonnegative().optional(),
|
|
45244
|
-
/** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
|
|
45245
|
-
lastCheckedAt: number().optional()
|
|
45246
|
-
});
|
|
45247
|
-
/**
|
|
45248
|
-
* Connection details — what a consumer needs to call
|
|
45249
|
-
* `mqtt.connect(url, options)`. We split URL + credentials so the
|
|
45250
|
-
* consumer can pass them as `mqtt.connect(url, { username, password })`
|
|
45251
|
-
* instead of stuffing creds into the URL (which leaks them into logs).
|
|
45252
|
-
*/
|
|
45253
|
-
var BrokerConnectionDetailsSchema = object({
|
|
45254
|
-
url: string(),
|
|
45255
|
-
username: string().optional(),
|
|
45256
|
-
password: string().optional(),
|
|
45257
|
-
/**
|
|
45258
|
-
* Suggested prefix for `clientId`. Each consumer should suffix this
|
|
45259
|
-
* with its own discriminator (addon id, instance id) so reconnects
|
|
45260
|
-
* don't kick each other off (MQTT spec: clientId must be unique per
|
|
45261
|
-
* broker).
|
|
45262
|
-
*/
|
|
45263
|
-
clientIdPrefix: string().optional()
|
|
45264
|
-
});
|
|
45265
|
-
var AddBrokerInputSchema = object({
|
|
45266
|
-
name: string().min(1),
|
|
45267
|
-
url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
|
|
45268
|
-
username: string().optional(),
|
|
45269
|
-
password: string().optional(),
|
|
45270
|
-
clientIdPrefix: string().optional()
|
|
45271
|
-
});
|
|
45272
|
-
var AddBrokerResultSchema = object({ id: string() });
|
|
45273
|
-
var IdInputSchema = object({ id: string() });
|
|
45274
|
-
var TestResultSchema$1 = discriminatedUnion("ok", [object({
|
|
45275
|
-
ok: literal(true),
|
|
45276
|
-
latencyMs: number()
|
|
45277
|
-
}), object({
|
|
45278
|
-
ok: literal(false),
|
|
45279
|
-
error: string()
|
|
45280
|
-
})]);
|
|
45281
|
-
var StartEmbeddedInputSchema = object({
|
|
45282
|
-
port: number().int().min(1).max(65535).default(1883),
|
|
45283
|
-
/** Allow anonymous connect (no username/password). Default: false. */
|
|
45284
|
-
allowAnonymous: boolean().default(false),
|
|
45285
|
-
/** Optional shared username/password for clients. */
|
|
45286
|
-
username: string().optional(),
|
|
45287
|
-
password: string().optional()
|
|
45288
|
-
});
|
|
45289
|
-
var StartEmbeddedResultSchema = object({
|
|
45290
|
-
id: string(),
|
|
45291
|
-
url: string()
|
|
45292
|
-
});
|
|
45293
|
-
var StatusSchema = object({
|
|
45294
|
-
brokerCount: number(),
|
|
45295
|
-
embeddedRunning: boolean()
|
|
45296
|
-
});
|
|
45297
|
-
method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
45298
|
-
kind: "mutation",
|
|
45299
|
-
auth: "admin"
|
|
45300
|
-
}), method(IdInputSchema, _void(), {
|
|
45301
|
-
kind: "mutation",
|
|
45302
|
-
auth: "admin"
|
|
45303
|
-
}), method(IdInputSchema, TestResultSchema$1, {
|
|
45304
|
-
kind: "mutation",
|
|
45305
|
-
auth: "admin"
|
|
45306
|
-
}), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
|
|
45307
|
-
kind: "mutation",
|
|
45308
|
-
auth: "admin"
|
|
45309
|
-
}), method(IdInputSchema, _void(), {
|
|
45310
|
-
kind: "mutation",
|
|
45311
|
-
auth: "admin"
|
|
45312
|
-
}), method(_void(), StatusSchema);
|
|
45313
|
-
var NetworkEndpointSchema = object({
|
|
45314
|
-
url: string(),
|
|
45315
|
-
hostname: string(),
|
|
45316
|
-
port: number(),
|
|
45317
|
-
protocol: _enum(["http", "https"]),
|
|
45318
|
-
/**
|
|
45319
|
-
* Whether this address survives a restart of its provider. `false` = it is
|
|
45320
|
-
* re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
|
|
45321
|
-
* hub never selects such an address AUTOMATICALLY for a role (D860), though
|
|
45322
|
-
* it stays visible and an operator may still flag it. Absent = stable: every
|
|
45323
|
-
* provider except a Quick tunnel owns a name that does not change.
|
|
45324
|
-
*/
|
|
45325
|
-
stable: boolean().optional()
|
|
45326
|
-
});
|
|
45327
|
-
object({
|
|
45328
|
-
url: string(),
|
|
45329
|
-
/** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
|
|
45330
|
-
stable: boolean().optional(),
|
|
45331
|
-
updated: boolean().optional()
|
|
45332
|
-
});
|
|
45333
|
-
var NetworkAccessStatusSchema = object({
|
|
45334
|
-
connected: boolean(),
|
|
45335
|
-
endpoint: NetworkEndpointSchema.nullable(),
|
|
45336
|
-
error: string().optional()
|
|
45337
|
-
});
|
|
45338
|
-
/**
|
|
45339
|
-
* Optional, richer endpoint shape returned by providers that expose
|
|
45340
|
-
* MORE than one ingress concurrently (Tailscale Funnel with mixed
|
|
45341
|
-
* serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
|
|
45342
|
-
* the originating provider config (mode + sourcePort) so the
|
|
45343
|
-
* orchestrator UI can label rows distinctly. Providers that expose only
|
|
45344
|
-
* one endpoint just omit `listEndpoints` from their provider impl.
|
|
45345
|
-
*/
|
|
45346
|
-
var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
45347
|
-
/**
|
|
45348
|
-
* Stable id within the provider — typically `<mode>-<sourcePort>` so
|
|
45349
|
-
* the orchestrator can dedupe across `listEndpoints` polls.
|
|
45350
|
-
*/
|
|
45351
|
-
id: string(),
|
|
45352
|
-
/** Operator-facing label (mirrors `MeshEndpoint.label`). */
|
|
45353
|
-
label: string(),
|
|
45354
|
-
/** Optional provider-specific mode tag, used for icon/colour in admin UI. */
|
|
45355
|
-
mode: string().optional(),
|
|
45356
|
-
/** Originating local port the ingress fronts (informational). */
|
|
45357
|
-
sourcePort: number().optional()
|
|
45358
|
-
});
|
|
45359
|
-
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
45360
|
-
/**
|
|
45361
|
-
* notification-output — canonical, capability-gated notification delivery.
|
|
45362
|
-
*
|
|
45363
|
-
* Apprise-derived model (see
|
|
45364
|
-
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
45365
|
-
* callers emit ONE canonical `Notification`; each provider declares a
|
|
45366
|
-
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
45367
|
-
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
45368
|
-
* message to what the kind supports — callers never special-case a service.
|
|
45369
|
-
*
|
|
45370
|
-
* DESIGN DECISIONS (locked):
|
|
45371
|
-
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
45372
|
-
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
45373
|
-
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
45374
|
-
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
45375
|
-
* alternative would fork the UI per addon and cannot host the
|
|
45376
|
-
* discovery→adopt flow.
|
|
45377
|
-
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
45378
|
-
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
45379
|
-
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
45380
|
-
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
45381
|
-
* `addonId` the generated collection router extracts from the call input.
|
|
45382
|
-
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
45383
|
-
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
45384
|
-
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
45385
|
-
* base64 fallback needed.
|
|
45386
|
-
*
|
|
45387
|
-
* TODO (deferred, closed-set change — separate decision): add
|
|
45388
|
-
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
45389
|
-
* admin "Integrations" page.
|
|
45390
|
-
*/
|
|
45391
|
-
/**
|
|
45392
|
-
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
45393
|
-
* adapter picks what it supports and the degrade engine filters the rest.
|
|
45394
|
-
*/
|
|
45395
|
-
var AttachmentMediaTypeSchema = _enum([
|
|
45396
|
-
"image",
|
|
45397
|
-
"video",
|
|
45398
|
-
"gif",
|
|
45399
|
-
"audio",
|
|
45400
|
-
"icon"
|
|
45401
|
-
]);
|
|
45402
|
-
/**
|
|
45403
|
-
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
45404
|
-
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
45405
|
-
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
45406
|
-
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
45407
|
-
*/
|
|
45408
|
-
var AttachmentSchema = object({
|
|
45409
|
-
mediaType: AttachmentMediaTypeSchema,
|
|
45410
|
-
url: string().optional(),
|
|
45411
|
-
bytes: _instanceof(Uint8Array).optional(),
|
|
45412
|
-
mime: string().optional(),
|
|
45413
|
-
name: string().optional()
|
|
45414
|
-
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
45415
|
-
var NotificationFormatSchema = _enum([
|
|
45416
|
-
"text",
|
|
45417
|
-
"markdown",
|
|
45418
|
-
"html"
|
|
45419
|
-
]);
|
|
45420
|
-
/**
|
|
45421
|
-
* The CLOSED icon vocabulary an action button may use.
|
|
45422
|
-
*
|
|
45423
|
-
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
45424
|
-
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
45425
|
-
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
45426
|
-
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
45427
|
-
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
45428
|
-
*
|
|
45429
|
-
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
45430
|
-
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
45431
|
-
* differently.
|
|
45432
|
-
*
|
|
45433
|
-
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
45434
|
-
*
|
|
45435
|
-
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
45436
|
-
* the whole set onto its own renderer's vocabulary through a
|
|
45437
|
-
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
45438
|
-
* fallback, so adding a member here fails every adapter's build until someone
|
|
45439
|
-
* decides its glyph, which is the only place that decision can be made
|
|
45440
|
-
* honestly.
|
|
45441
|
-
*
|
|
45442
|
-
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
45443
|
-
* `disarm` straight through; iOS feeds that string to
|
|
45444
|
-
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
45445
|
-
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
45446
|
-
* mapping, and "the field is documented" is not "the value renders".
|
|
45447
|
-
*
|
|
45448
|
-
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
45449
|
-
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
45450
|
-
* so an addon that emits a token the running hub does not know does not lose an
|
|
45451
|
-
* icon — its whole `send` fails Zod validation and the notification never
|
|
45452
|
-
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
45453
|
-
*/
|
|
45454
|
-
var NotificationActionIconSchema = _enum([
|
|
45455
|
-
"acknowledge",
|
|
45456
|
-
"dismiss",
|
|
45457
|
-
"silence",
|
|
45458
|
-
"snooze",
|
|
45459
|
-
"view",
|
|
45460
|
-
"play",
|
|
45461
|
-
"open",
|
|
45462
|
-
"close",
|
|
45463
|
-
"lock",
|
|
45464
|
-
"unlock",
|
|
45465
|
-
"arm",
|
|
45466
|
-
"arm-home",
|
|
45467
|
-
"arm-away",
|
|
45468
|
-
"arm-night",
|
|
45469
|
-
"disarm",
|
|
45470
|
-
"light",
|
|
45471
|
-
"alert",
|
|
45472
|
-
"camera"
|
|
45473
|
-
]);
|
|
45474
|
-
/** A single tap-through action button. */
|
|
45475
|
-
var NotificationActionSchema = object({
|
|
45476
|
-
id: string(),
|
|
45477
|
-
label: string(),
|
|
45478
|
-
url: string().optional(),
|
|
45479
|
-
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
45480
|
-
icon: NotificationActionIconSchema.optional(),
|
|
45481
|
-
/**
|
|
45482
|
-
* Renders in a warning style where the notifier supports it.
|
|
45483
|
-
*
|
|
45484
|
-
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
45485
|
-
* else — see `notification-center/action-token.ts` for what that does and
|
|
45486
|
-
* does not buy.
|
|
45487
|
-
*/
|
|
45488
|
-
destructive: boolean().optional(),
|
|
45489
|
-
/**
|
|
45490
|
-
* How the tap should REACH the url.
|
|
45491
|
-
*
|
|
45492
|
-
* `navigate` (absent, and every button authored before this field) opens it:
|
|
45493
|
-
* the phone leaves the notification and shows whatever the callback returns.
|
|
45494
|
-
* That is right for a button whose answer the operator wants to read.
|
|
45495
|
-
*
|
|
45496
|
-
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
45497
|
-
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
45498
|
-
* an answer to the notification, and being thrown into a browser tab to
|
|
45499
|
-
* confirm it costs more attention than the notification did. A backend that
|
|
45500
|
-
* cannot do a background call renders it as an ordinary link (the adapters
|
|
45501
|
-
* fall back rather than dropping the button), so this is a preference, never
|
|
45502
|
-
* a requirement.
|
|
45503
|
-
*/
|
|
45504
|
-
mode: _enum(["navigate", "background"]).optional()
|
|
45505
|
-
});
|
|
45506
|
-
/** A delivery parameter value: what a notifier declared it takes. */
|
|
45507
|
-
var DeliveryParamValueSchema = union([
|
|
45508
|
-
string(),
|
|
45509
|
-
number(),
|
|
45510
|
-
boolean()
|
|
45511
|
-
]);
|
|
45512
|
-
/**
|
|
45513
|
-
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
45514
|
-
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
45515
|
-
* never handed one. The admin UI derives its control from this descriptor —
|
|
45516
|
-
* no editor knows a service by name.
|
|
45517
|
-
*
|
|
45518
|
-
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
45519
|
-
* run on the addon cap path).
|
|
45520
|
-
*/
|
|
45521
|
-
var DeliveryParamSchema = object({
|
|
45522
|
-
key: string().min(1),
|
|
45523
|
-
label: string(),
|
|
45524
|
-
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
45525
|
-
labelKey: string().optional(),
|
|
45526
|
-
type: _enum([
|
|
45527
|
-
"enum",
|
|
45528
|
-
"number",
|
|
45529
|
-
"boolean",
|
|
45530
|
-
"string"
|
|
45531
|
-
]),
|
|
45532
|
-
/** `enum` only: the closed set of accepted values. */
|
|
45533
|
-
options: array(object({
|
|
45534
|
-
value: string(),
|
|
45535
|
-
label: string()
|
|
45536
|
-
})).optional(),
|
|
45537
|
-
/** `number` only. */
|
|
45538
|
-
min: number().optional(),
|
|
45539
|
-
max: number().optional(),
|
|
45540
|
-
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
45541
|
-
default: DeliveryParamValueSchema.optional()
|
|
45542
|
-
});
|
|
45543
|
-
/**
|
|
45544
|
-
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
45545
|
-
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
45546
|
-
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
45547
|
-
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
45548
|
-
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
45549
|
-
* `priority` for that one target.
|
|
45550
|
-
*/
|
|
45551
|
-
var NotificationSchema = object({
|
|
45552
|
-
body: string(),
|
|
45553
|
-
title: string().optional(),
|
|
45554
|
-
format: NotificationFormatSchema.default("text"),
|
|
45555
|
-
priority: number().int().min(1).max(5).default(3),
|
|
45556
|
-
level: string().optional(),
|
|
45557
|
-
attachments: array(AttachmentSchema).optional(),
|
|
45558
|
-
clickUrl: string().optional(),
|
|
45559
|
-
actions: array(NotificationActionSchema).optional(),
|
|
45560
|
-
sound: string().optional(),
|
|
45561
|
-
ttl: number().optional(),
|
|
45562
|
-
tag: string().optional(),
|
|
45563
|
-
deviceId: number().optional(),
|
|
45564
|
-
eventId: string().optional(),
|
|
45565
|
-
metadata: record(string(), unknown()).optional(),
|
|
45566
|
-
/**
|
|
45567
|
-
* Values for the parameters the target's kind DECLARES in
|
|
45568
|
-
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
45569
|
-
* declaration by the sender; a notifier reads only the keys it declared.
|
|
45570
|
-
*/
|
|
45571
|
-
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
45572
|
-
});
|
|
45573
|
-
/** One declared native severity/priority level for a kind. */
|
|
45574
|
-
var TargetKindLevelSchema = object({
|
|
45575
|
-
id: string(),
|
|
45576
|
-
label: string(),
|
|
45577
|
-
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
45578
|
-
ordinal: number().int().min(1).max(5).nullable(),
|
|
45579
|
-
flags: object({
|
|
45580
|
-
critical: boolean().optional(),
|
|
45581
|
-
silent: boolean().optional(),
|
|
45582
|
-
noPush: boolean().optional()
|
|
45583
|
-
}).optional(),
|
|
45584
|
-
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
45585
|
-
requires: array(string()).optional(),
|
|
45586
|
-
description: string().optional()
|
|
45587
|
-
});
|
|
45588
|
-
/** The full capability block consulted before dispatch. */
|
|
45589
|
-
var TargetKindCapsSchema = object({
|
|
45590
|
-
attachments: object({
|
|
45591
|
-
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
45592
|
-
mode: _enum([
|
|
45593
|
-
"url",
|
|
45594
|
-
"bytes",
|
|
45595
|
-
"both"
|
|
45596
|
-
]),
|
|
45597
|
-
max: number().int().nonnegative(),
|
|
45598
|
-
maxBytes: number().int().positive().optional()
|
|
45599
|
-
}),
|
|
45600
|
-
/** Max action buttons (0 = none). */
|
|
45601
|
-
actions: number().int().nonnegative(),
|
|
45602
|
-
/**
|
|
45603
|
-
* Whether this kind renders a per-action ICON.
|
|
45604
|
-
*
|
|
45605
|
-
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
45606
|
-
* run on the addon cap path — three production failures in one day taught
|
|
45607
|
-
* this repo that once. Absent is read as false by the degrade engine, which
|
|
45608
|
-
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
45609
|
-
* assumed and dropped costs the operator's trust in the field.
|
|
45610
|
-
*/
|
|
45611
|
-
actionIcons: boolean().optional(),
|
|
45612
|
-
levels: array(TargetKindLevelSchema),
|
|
45613
|
-
format: array(NotificationFormatSchema),
|
|
45614
|
-
clickUrl: boolean(),
|
|
45615
|
-
sound: boolean(),
|
|
45616
|
-
ttl: boolean(),
|
|
45617
|
-
bodyMaxLen: number().int().positive()
|
|
45618
|
-
});
|
|
45619
|
-
/**
|
|
45620
|
-
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
45621
|
-
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
45622
|
-
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
45623
|
-
* the union is large and not meant for runtime validation here; the exported
|
|
45624
|
-
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
45625
|
-
*/
|
|
45626
|
-
var ConfigSchemaPassthrough$2 = unknown();
|
|
45627
|
-
var TargetKindSchema = object({
|
|
45628
|
-
kind: string(),
|
|
45629
|
-
label: string(),
|
|
45630
|
-
icon: string(),
|
|
45631
|
-
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
45632
|
-
addonId: string(),
|
|
45633
|
-
/**
|
|
45634
|
-
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
45635
|
-
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
45636
|
-
* when the addon bundles no icon for that kind — the client then falls back
|
|
45637
|
-
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
45638
|
-
*
|
|
45639
|
-
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
45640
|
-
* client, and a native client joins it onto its own hub base.
|
|
45641
|
-
*
|
|
45642
|
-
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
45643
|
-
* field that survived only because the runtime cap-router forwards provider
|
|
45644
|
-
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
45645
|
-
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
45646
|
-
* broken silently the moment output validation was tightened anywhere.
|
|
45647
|
-
*/
|
|
45648
|
-
iconUrl: string().optional(),
|
|
45649
|
-
/**
|
|
45650
|
-
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
45651
|
-
*
|
|
45652
|
-
* The server knows this and therefore says it, because the client cannot
|
|
45653
|
-
* safely guess: a React-Native client renders SVG and raster through two
|
|
45654
|
-
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
45655
|
-
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
45656
|
-
* placeholder glyph for every vector icon while the web build looked fine.
|
|
45657
|
-
*
|
|
45658
|
-
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
45659
|
-
* not been updated — a client that cannot determine the type should prefer
|
|
45660
|
-
* its raster path, which is the safe default for an unknown image.
|
|
45860
|
+
* Did the DURABLE tier contribute? `false` means the answer is the hot ring
|
|
45861
|
+
* alone — an agent (which holds no table), or a store that refused.
|
|
45862
|
+
* Reported because "the last hour" and "the last six hours" are different
|
|
45863
|
+
* questions and an operator must not have to guess which was answered.
|
|
45661
45864
|
*/
|
|
45662
|
-
|
|
45663
|
-
|
|
45865
|
+
durable: boolean()
|
|
45866
|
+
});
|
|
45867
|
+
var GetLoadSeriesInputSchema = object({
|
|
45664
45868
|
/**
|
|
45665
|
-
* The
|
|
45666
|
-
* base URL, instance, chat, recipient). A save that changes any of them
|
|
45667
|
-
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
45668
|
-
* editor uses this to ask for the secret on exactly those edits instead of
|
|
45669
|
-
* on every edit.
|
|
45869
|
+
* The node whose series is wanted.
|
|
45670
45870
|
*
|
|
45671
|
-
*
|
|
45672
|
-
*
|
|
45673
|
-
*
|
|
45871
|
+
* NOT named `nodeId`: the generated cap router strips a top-level
|
|
45872
|
+
* `nodeId` from every method input and uses it to ROUTE the call to
|
|
45873
|
+
* that node's provider (`generated-cap-routers.ts`). A series target
|
|
45874
|
+
* called `nodeId` would silently become a routing pin and never reach
|
|
45875
|
+
* the provider. The hub holds every node it hears from, so the
|
|
45876
|
+
* ordinary call is unpinned — answered by the hub, for any node.
|
|
45674
45877
|
*/
|
|
45675
|
-
|
|
45676
|
-
supportsDiscovery: boolean(),
|
|
45878
|
+
forNodeId: string(),
|
|
45677
45879
|
/**
|
|
45678
|
-
*
|
|
45679
|
-
*
|
|
45880
|
+
* EXCLUSIVE lower bound. A caller passes the newest `atMs` it already
|
|
45881
|
+
* holds and receives only what it is missing, so seeding a live chart
|
|
45882
|
+
* from this method cannot double a point already drawn.
|
|
45680
45883
|
*/
|
|
45681
|
-
|
|
45682
|
-
|
|
45884
|
+
sinceMs: number().optional(),
|
|
45885
|
+
/**
|
|
45886
|
+
* Most points the caller wants PER FUNCTION. The window is reduced to fit,
|
|
45887
|
+
* preserving min and max per bucket.
|
|
45888
|
+
*
|
|
45889
|
+
* Absent means NO reduction — legitimate for a short window and a trap for a
|
|
45890
|
+
* long one, which is why a chart passes its own pixel width.
|
|
45891
|
+
*/
|
|
45892
|
+
maxPoints: number().int().positive().optional()
|
|
45893
|
+
});
|
|
45894
|
+
var SystemMetricsSchema = object({
|
|
45895
|
+
cpuPercent: number(),
|
|
45896
|
+
/** `null` when the memory sample failed or was not taken yet (D393). */
|
|
45897
|
+
memoryPercent: number().nullable(),
|
|
45898
|
+
memoryUsedMB: number().nullable(),
|
|
45899
|
+
memoryTotalMB: number(),
|
|
45900
|
+
diskPercent: number().optional(),
|
|
45901
|
+
temperature: number().optional(),
|
|
45902
|
+
gpuPercent: number().optional(),
|
|
45903
|
+
gpuMemoryPercent: number().optional()
|
|
45904
|
+
});
|
|
45905
|
+
method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema);
|
|
45906
|
+
method(object({
|
|
45907
|
+
sourceUrl: string(),
|
|
45908
|
+
metadata: ModelConvertMetadataSchema,
|
|
45909
|
+
targets: array(ConvertTargetSchema).min(1).readonly(),
|
|
45910
|
+
calibrationRef: string().optional(),
|
|
45911
|
+
sessionId: string().optional()
|
|
45912
|
+
}), ConvertResultSchema, {
|
|
45913
|
+
kind: "mutation",
|
|
45914
|
+
auth: "admin",
|
|
45915
|
+
timeoutMs: 6e5
|
|
45916
|
+
});
|
|
45917
|
+
method(object({
|
|
45918
|
+
targetNodeId: string(),
|
|
45919
|
+
modelId: string(),
|
|
45920
|
+
format: _enum(MODEL_FORMATS),
|
|
45921
|
+
entry: ModelCatalogEntrySchema
|
|
45922
|
+
}), object({
|
|
45923
|
+
ok: boolean(),
|
|
45924
|
+
/** sha256 of the staged tarball (empty for a hub-local no-op). */
|
|
45925
|
+
sha256: string(),
|
|
45926
|
+
bytes: number(),
|
|
45927
|
+
/** The target node's modelsDir the artifact landed in. */
|
|
45928
|
+
path: string()
|
|
45929
|
+
}), {
|
|
45930
|
+
kind: "mutation",
|
|
45931
|
+
auth: "admin"
|
|
45932
|
+
});
|
|
45933
|
+
var MotionRegionSchema = object({
|
|
45934
|
+
bbox: BoundingBoxSchema,
|
|
45935
|
+
pixelCount: number(),
|
|
45936
|
+
intensity: number()
|
|
45937
|
+
});
|
|
45938
|
+
var MotionAnalysisResultSchema = object({
|
|
45939
|
+
detected: boolean(),
|
|
45940
|
+
regionCount: number(),
|
|
45941
|
+
/** Regions passing minArea filter — used for pipeline triggering. */
|
|
45942
|
+
regions: array(MotionRegionSchema).readonly(),
|
|
45943
|
+
/** All raw regions from CCL before minArea filter — used for UI debug overlay. */
|
|
45944
|
+
rawRegions: array(MotionRegionSchema).readonly(),
|
|
45945
|
+
frameWidth: number(),
|
|
45946
|
+
frameHeight: number(),
|
|
45947
|
+
analysisMs: number()
|
|
45948
|
+
});
|
|
45949
|
+
DeviceType.Camera, method(object({
|
|
45950
|
+
deviceId: number(),
|
|
45951
|
+
frame: FrameInputSchema.optional(),
|
|
45952
|
+
frameHandle: FrameHandleSchema.optional()
|
|
45953
|
+
}), MotionAnalysisResultSchema, {
|
|
45954
|
+
role: "admin",
|
|
45955
|
+
kind: "mutation"
|
|
45956
|
+
}), method(object({ deviceId: number() }), _void(), {
|
|
45957
|
+
role: "admin",
|
|
45958
|
+
kind: "mutation"
|
|
45959
|
+
}), method(_void(), _void(), {
|
|
45960
|
+
role: "admin",
|
|
45961
|
+
kind: "mutation"
|
|
45962
|
+
}), object({
|
|
45963
|
+
deviceId: number(),
|
|
45964
|
+
detected: boolean(),
|
|
45965
|
+
regionCount: number()
|
|
45683
45966
|
});
|
|
45684
45967
|
/**
|
|
45685
|
-
*
|
|
45686
|
-
*
|
|
45687
|
-
*
|
|
45968
|
+
* `mqtt-broker` — broker-registry cap.
|
|
45969
|
+
*
|
|
45970
|
+
* NOT a pub/sub proxy. The cap exposes (a) a registry of configured
|
|
45971
|
+
* MQTT brokers (external + optionally an embedded `aedes`-backed one)
|
|
45972
|
+
* and (b) the connection details a consumer addon needs to spin up
|
|
45973
|
+
* its OWN `mqtt.js` client.
|
|
45974
|
+
*
|
|
45975
|
+
* Why: pub/sub routing over the system event-bus loses fidelity
|
|
45976
|
+
* (callback shape, QoS guarantees, will/retain semantics) and adds
|
|
45977
|
+
* refcount bookkeeping that addons would rather own themselves. The
|
|
45978
|
+
* canonical consumer needs raw `mqtt.js`
|
|
45979
|
+
* features anyway — give it the connection config, get out of the way.
|
|
45980
|
+
*
|
|
45981
|
+
* Consumer flow:
|
|
45982
|
+
* const cfg = await ctx.api.mqttBroker.getBrokerConfig({ id })
|
|
45983
|
+
* const client = mqtt.connect(cfg.url, { username: cfg.username, … })
|
|
45984
|
+
* client.subscribe('zigbee2mqtt/+')
|
|
45985
|
+
*
|
|
45986
|
+
* Collection mode: multiple brokers (e.g. one local mosquitto + one
|
|
45987
|
+
* cloud bridge). The "embedded" entry (when present) is just another
|
|
45988
|
+
* broker in the registry — its lifecycle is owned by the addon that
|
|
45989
|
+
* spawned it.
|
|
45688
45990
|
*/
|
|
45689
|
-
var
|
|
45991
|
+
var BrokerKindSchema = _enum(["external", "embedded"]);
|
|
45992
|
+
/**
|
|
45993
|
+
* Broker live-probe status.
|
|
45994
|
+
*
|
|
45995
|
+
* - `connected` — last probe completed a clean CONNACK
|
|
45996
|
+
* - `disconnected` — no probe has run yet (cold cache)
|
|
45997
|
+
* - `auth-failed` — CONNACK refused with auth error (RC 4 / 5)
|
|
45998
|
+
* - `unreachable` — TCP connect timed out / refused
|
|
45999
|
+
* - `tls-error` — TLS handshake failed (cert / SNI / cipher)
|
|
46000
|
+
*/
|
|
46001
|
+
var BrokerStatusSchema$1 = _enum([
|
|
46002
|
+
"connected",
|
|
46003
|
+
"disconnected",
|
|
46004
|
+
"auth-failed",
|
|
46005
|
+
"unreachable",
|
|
46006
|
+
"tls-error"
|
|
46007
|
+
]);
|
|
46008
|
+
var BrokerInfoSchema$1 = object({
|
|
45690
46009
|
id: string(),
|
|
45691
46010
|
name: string(),
|
|
45692
|
-
|
|
45693
|
-
|
|
45694
|
-
|
|
45695
|
-
|
|
46011
|
+
url: string(),
|
|
46012
|
+
kind: BrokerKindSchema,
|
|
46013
|
+
status: BrokerStatusSchema$1,
|
|
46014
|
+
latencyMs: number().nullable(),
|
|
46015
|
+
error: string().optional(),
|
|
46016
|
+
/** Embedded brokers only: number of MQTT clients currently connected. */
|
|
46017
|
+
connectedClients: number().int().nonnegative().optional(),
|
|
46018
|
+
/** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
|
|
46019
|
+
lastCheckedAt: number().optional()
|
|
45696
46020
|
});
|
|
45697
|
-
/**
|
|
45698
|
-
|
|
45699
|
-
|
|
45700
|
-
|
|
45701
|
-
|
|
46021
|
+
/**
|
|
46022
|
+
* Connection details — what a consumer needs to call
|
|
46023
|
+
* `mqtt.connect(url, options)`. We split URL + credentials so the
|
|
46024
|
+
* consumer can pass them as `mqtt.connect(url, { username, password })`
|
|
46025
|
+
* instead of stuffing creds into the URL (which leaks them into logs).
|
|
46026
|
+
*/
|
|
46027
|
+
var BrokerConnectionDetailsSchema = object({
|
|
46028
|
+
url: string(),
|
|
46029
|
+
username: string().optional(),
|
|
46030
|
+
password: string().optional(),
|
|
46031
|
+
/**
|
|
46032
|
+
* Suggested prefix for `clientId`. Each consumer should suffix this
|
|
46033
|
+
* with its own discriminator (addon id, instance id) so reconnects
|
|
46034
|
+
* don't kick each other off (MQTT spec: clientId must be unique per
|
|
46035
|
+
* broker).
|
|
46036
|
+
*/
|
|
46037
|
+
clientIdPrefix: string().optional()
|
|
45702
46038
|
});
|
|
45703
|
-
|
|
45704
|
-
|
|
45705
|
-
|
|
45706
|
-
|
|
45707
|
-
|
|
45708
|
-
|
|
45709
|
-
truncated: boolean(),
|
|
45710
|
-
dropped: array(string())
|
|
46039
|
+
var AddBrokerInputSchema = object({
|
|
46040
|
+
name: string().min(1),
|
|
46041
|
+
url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
|
|
46042
|
+
username: string().optional(),
|
|
46043
|
+
password: string().optional(),
|
|
46044
|
+
clientIdPrefix: string().optional()
|
|
45711
46045
|
});
|
|
45712
|
-
var
|
|
45713
|
-
|
|
45714
|
-
|
|
45715
|
-
|
|
46046
|
+
var AddBrokerResultSchema = object({ id: string() });
|
|
46047
|
+
var IdInputSchema = object({ id: string() });
|
|
46048
|
+
var TestResultSchema$1 = discriminatedUnion("ok", [object({
|
|
46049
|
+
ok: literal(true),
|
|
46050
|
+
latencyMs: number()
|
|
46051
|
+
}), object({
|
|
46052
|
+
ok: literal(false),
|
|
46053
|
+
error: string()
|
|
46054
|
+
})]);
|
|
46055
|
+
var StartEmbeddedInputSchema = object({
|
|
46056
|
+
port: number().int().min(1).max(65535).default(1883),
|
|
46057
|
+
/** Allow anonymous connect (no username/password). Default: false. */
|
|
46058
|
+
allowAnonymous: boolean().default(false),
|
|
46059
|
+
/** Optional shared username/password for clients. */
|
|
46060
|
+
username: string().optional(),
|
|
46061
|
+
password: string().optional()
|
|
46062
|
+
});
|
|
46063
|
+
var StartEmbeddedResultSchema = object({
|
|
46064
|
+
id: string(),
|
|
46065
|
+
url: string()
|
|
46066
|
+
});
|
|
46067
|
+
var StatusSchema = object({
|
|
46068
|
+
brokerCount: number(),
|
|
46069
|
+
embeddedRunning: boolean()
|
|
46070
|
+
});
|
|
46071
|
+
method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
46072
|
+
kind: "mutation",
|
|
46073
|
+
auth: "admin"
|
|
46074
|
+
}), method(IdInputSchema, _void(), {
|
|
46075
|
+
kind: "mutation",
|
|
46076
|
+
auth: "admin"
|
|
46077
|
+
}), method(IdInputSchema, TestResultSchema$1, {
|
|
46078
|
+
kind: "mutation",
|
|
46079
|
+
auth: "admin"
|
|
46080
|
+
}), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
|
|
46081
|
+
kind: "mutation",
|
|
46082
|
+
auth: "admin"
|
|
46083
|
+
}), method(IdInputSchema, _void(), {
|
|
46084
|
+
kind: "mutation",
|
|
46085
|
+
auth: "admin"
|
|
46086
|
+
}), method(_void(), StatusSchema);
|
|
46087
|
+
var NetworkEndpointSchema = object({
|
|
46088
|
+
url: string(),
|
|
46089
|
+
hostname: string(),
|
|
46090
|
+
port: number(),
|
|
46091
|
+
protocol: _enum(["http", "https"]),
|
|
46092
|
+
/**
|
|
46093
|
+
* Whether this address survives a restart of its provider. `false` = it is
|
|
46094
|
+
* re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
|
|
46095
|
+
* hub never selects such an address AUTOMATICALLY for a role (D860), though
|
|
46096
|
+
* it stays visible and an operator may still flag it. Absent = stable: every
|
|
46097
|
+
* provider except a Quick tunnel owns a name that does not change.
|
|
46098
|
+
*/
|
|
46099
|
+
stable: boolean().optional()
|
|
46100
|
+
});
|
|
46101
|
+
object({
|
|
46102
|
+
url: string(),
|
|
46103
|
+
/** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
|
|
46104
|
+
stable: boolean().optional(),
|
|
46105
|
+
updated: boolean().optional()
|
|
46106
|
+
});
|
|
46107
|
+
var NetworkAccessStatusSchema = object({
|
|
46108
|
+
connected: boolean(),
|
|
46109
|
+
endpoint: NetworkEndpointSchema.nullable(),
|
|
46110
|
+
error: string().optional()
|
|
45716
46111
|
});
|
|
45717
|
-
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
45718
|
-
var TestResultSchema = SendResultSchema;
|
|
45719
46112
|
/**
|
|
45720
|
-
*
|
|
45721
|
-
*
|
|
46113
|
+
* Optional, richer endpoint shape returned by providers that expose
|
|
46114
|
+
* MORE than one ingress concurrently (Tailscale Funnel with mixed
|
|
46115
|
+
* serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
|
|
46116
|
+
* the originating provider config (mode + sourcePort) so the
|
|
46117
|
+
* orchestrator UI can label rows distinctly. Providers that expose only
|
|
46118
|
+
* one endpoint just omit `listEndpoints` from their provider impl.
|
|
45722
46119
|
*/
|
|
45723
|
-
var
|
|
45724
|
-
|
|
45725
|
-
|
|
45726
|
-
|
|
45727
|
-
|
|
45728
|
-
|
|
46120
|
+
var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
46121
|
+
/**
|
|
46122
|
+
* Stable id within the provider — typically `<mode>-<sourcePort>` so
|
|
46123
|
+
* the orchestrator can dedupe across `listEndpoints` polls.
|
|
46124
|
+
*/
|
|
46125
|
+
id: string(),
|
|
46126
|
+
/** Operator-facing label (mirrors `MeshEndpoint.label`). */
|
|
46127
|
+
label: string(),
|
|
46128
|
+
/** Optional provider-specific mode tag, used for icon/colour in admin UI. */
|
|
46129
|
+
mode: string().optional(),
|
|
46130
|
+
/** Originating local port the ingress fronts (informational). */
|
|
46131
|
+
sourcePort: number().optional()
|
|
45729
46132
|
});
|
|
45730
|
-
|
|
45731
|
-
name: "notification-output",
|
|
45732
|
-
scope: "system",
|
|
45733
|
-
mode: "collection",
|
|
45734
|
-
methods: {
|
|
45735
|
-
listTargetKinds: method(object({}), array(TargetKindSchema)),
|
|
45736
|
-
listTargets: method(object({}), array(TargetSchema), { auth: "admin" }),
|
|
45737
|
-
/**
|
|
45738
|
-
* One target WITH secrets — admin edit / duplicate hydration only.
|
|
45739
|
-
* `listTargets` stays redacted; the editor loads this so password fields
|
|
45740
|
-
* are filled (masked in the input) and a save can round-trip the token
|
|
45741
|
-
* when a destination changes (Duplicate with a new chatId, etc.).
|
|
45742
|
-
*/
|
|
45743
|
-
getTarget: method(object({ targetId: string() }), TargetSchema, { auth: "admin" }),
|
|
45744
|
-
discoverTargets: method(object({
|
|
45745
|
-
kind: string(),
|
|
45746
|
-
config: record(string(), unknown()).optional(),
|
|
45747
|
-
/**
|
|
45748
|
-
* When editing / duplicating, the blank password field never round-trips
|
|
45749
|
-
* the stored token. Pass the source target id so the provider can restore
|
|
45750
|
-
* secrets server-side before dialling (same merge as upsert).
|
|
45751
|
-
*/
|
|
45752
|
-
targetId: string().optional()
|
|
45753
|
-
}), array(DiscoveredTargetSchema), { auth: "admin" }),
|
|
45754
|
-
probeTargetConfig: method(object({
|
|
45755
|
-
kind: string(),
|
|
45756
|
-
config: record(string(), unknown()),
|
|
45757
|
-
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
45758
|
-
targetId: string().optional()
|
|
45759
|
-
}), ProbeTargetConfigResultSchema, {
|
|
45760
|
-
kind: "mutation",
|
|
45761
|
-
auth: "admin"
|
|
45762
|
-
}),
|
|
45763
|
-
send: method(object({
|
|
45764
|
-
targetId: string(),
|
|
45765
|
-
notification: NotificationSchema
|
|
45766
|
-
}), SendResultSchema, {
|
|
45767
|
-
kind: "mutation",
|
|
45768
|
-
auth: "admin"
|
|
45769
|
-
}),
|
|
45770
|
-
testTarget: method(object({
|
|
45771
|
-
targetId: string(),
|
|
45772
|
-
sample: NotificationSchema.optional()
|
|
45773
|
-
}), TestResultSchema, {
|
|
45774
|
-
kind: "mutation",
|
|
45775
|
-
auth: "admin"
|
|
45776
|
-
}),
|
|
45777
|
-
upsertTarget: method(object({
|
|
45778
|
-
target: TargetSchema,
|
|
45779
|
-
/**
|
|
45780
|
-
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
45781
|
-
* existing row. The UI never holds the stored token; blank / redacted
|
|
45782
|
-
* password fields are restored from this source (D634 still applies if
|
|
45783
|
-
* destinations diverge without a re-typed secret).
|
|
45784
|
-
*/
|
|
45785
|
-
cloneSecretsFrom: string().optional()
|
|
45786
|
-
}), TargetSchema, {
|
|
45787
|
-
kind: "mutation",
|
|
45788
|
-
auth: "admin"
|
|
45789
|
-
}),
|
|
45790
|
-
deleteTarget: method(object({ targetId: string() }), _void(), {
|
|
45791
|
-
kind: "mutation",
|
|
45792
|
-
auth: "admin"
|
|
45793
|
-
}),
|
|
45794
|
-
setTargetEnabled: method(object({
|
|
45795
|
-
targetId: string(),
|
|
45796
|
-
enabled: boolean()
|
|
45797
|
-
}), _void(), {
|
|
45798
|
-
kind: "mutation",
|
|
45799
|
-
auth: "admin"
|
|
45800
|
-
})
|
|
45801
|
-
}
|
|
45802
|
-
};
|
|
46133
|
+
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
45803
46134
|
var AUDIO_MACRO_LABELS = [
|
|
45804
46135
|
{
|
|
45805
46136
|
id: "speech",
|
|
@@ -52260,14 +52591,6 @@ var EngineProvisioningSchema = object({
|
|
|
52260
52591
|
*/
|
|
52261
52592
|
configIssues: array(string()).optional()
|
|
52262
52593
|
});
|
|
52263
|
-
var PipelineStepInputSchema = lazy(() => object({
|
|
52264
|
-
addonId: string(),
|
|
52265
|
-
modelId: string().optional(),
|
|
52266
|
-
enabled: boolean().default(true),
|
|
52267
|
-
children: array(PipelineStepInputSchema).optional(),
|
|
52268
|
-
settings: record(string(), unknown()).optional(),
|
|
52269
|
-
jumpDeviceKey: string().optional()
|
|
52270
|
-
}));
|
|
52271
52594
|
var ModelSubstitutionSchema = object({
|
|
52272
52595
|
addonId: string(),
|
|
52273
52596
|
chosen: string(),
|
|
@@ -52346,7 +52669,6 @@ var ConfigUISchemaBridge = custom();
|
|
|
52346
52669
|
var ConfigUISchemaNullableBridge = custom();
|
|
52347
52670
|
var InferenceCapabilitiesBridge = custom();
|
|
52348
52671
|
var ModelAvailabilityListBridge = custom();
|
|
52349
|
-
var PipelineRunResultBridge = custom();
|
|
52350
52672
|
method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
|
|
52351
52673
|
modelId: string(),
|
|
52352
52674
|
settings: record(string(), unknown()).readonly()
|
|
@@ -59212,6 +59534,318 @@ onBrightnessChanged: { data: object({
|
|
|
59212
59534
|
} } }
|
|
59213
59535
|
};
|
|
59214
59536
|
/**
|
|
59537
|
+
* The role vocabulary of a remote (D963): what a button MEANS, independent of
|
|
59538
|
+
* how it is wired. A layout template places buttons by role; a device binds a
|
|
59539
|
+
* role to an action; an exporter asks for a role by name. Roles are shared so
|
|
59540
|
+
* one layout fits every appliance and one voice command fits every binding.
|
|
59541
|
+
*
|
|
59542
|
+
* A role outside this list is `custom:<key>` — the operator's own button (a
|
|
59543
|
+
* SmartIR source, a channel preset). The key is a lowercase slug.
|
|
59544
|
+
*/
|
|
59545
|
+
var REMOTE_KNOWN_ROLES = [
|
|
59546
|
+
"power",
|
|
59547
|
+
"power_on",
|
|
59548
|
+
"power_off",
|
|
59549
|
+
"volume_up",
|
|
59550
|
+
"volume_down",
|
|
59551
|
+
"mute",
|
|
59552
|
+
"channel_up",
|
|
59553
|
+
"channel_down",
|
|
59554
|
+
"up",
|
|
59555
|
+
"down",
|
|
59556
|
+
"left",
|
|
59557
|
+
"right",
|
|
59558
|
+
"ok",
|
|
59559
|
+
"back",
|
|
59560
|
+
"home",
|
|
59561
|
+
"menu",
|
|
59562
|
+
"info",
|
|
59563
|
+
"guide",
|
|
59564
|
+
"exit",
|
|
59565
|
+
"input",
|
|
59566
|
+
"play",
|
|
59567
|
+
"pause",
|
|
59568
|
+
"play_pause",
|
|
59569
|
+
"stop",
|
|
59570
|
+
"next",
|
|
59571
|
+
"previous",
|
|
59572
|
+
"fast_forward",
|
|
59573
|
+
"rewind",
|
|
59574
|
+
"digit_0",
|
|
59575
|
+
"digit_1",
|
|
59576
|
+
"digit_2",
|
|
59577
|
+
"digit_3",
|
|
59578
|
+
"digit_4",
|
|
59579
|
+
"digit_5",
|
|
59580
|
+
"digit_6",
|
|
59581
|
+
"digit_7",
|
|
59582
|
+
"digit_8",
|
|
59583
|
+
"digit_9"
|
|
59584
|
+
];
|
|
59585
|
+
var CUSTOM_KEY_RE = /^[a-z0-9][a-z0-9_]{0,47}$/;
|
|
59586
|
+
var KNOWN_ROLES = new Set(REMOTE_KNOWN_ROLES);
|
|
59587
|
+
function isRemoteKnownRole(value) {
|
|
59588
|
+
return KNOWN_ROLES.has(value);
|
|
59589
|
+
}
|
|
59590
|
+
function isRemoteCustomKey(key) {
|
|
59591
|
+
return CUSTOM_KEY_RE.test(key);
|
|
59592
|
+
}
|
|
59593
|
+
/** The key of a `custom:<key>` role, or null for any other string. */
|
|
59594
|
+
function customKeyOfRole(role) {
|
|
59595
|
+
if (!role.startsWith("custom:")) return null;
|
|
59596
|
+
const key = role.slice(7);
|
|
59597
|
+
return isRemoteCustomKey(key) ? key : null;
|
|
59598
|
+
}
|
|
59599
|
+
function isRemoteRole(value) {
|
|
59600
|
+
return isRemoteKnownRole(value) || customKeyOfRole(value) !== null;
|
|
59601
|
+
}
|
|
59602
|
+
var RemoteRoleSchema = string().refine(isRemoteRole, { message: "a role is one of the known roles or custom:<key> (lowercase letters, digits, _)" });
|
|
59603
|
+
var RemoteBindingSchema = object({ action: AutomationCapStepSchema.extend({ deviceId: number().int().nonnegative() }) });
|
|
59604
|
+
var RemoteCustomButtonSchema = object({
|
|
59605
|
+
/** The slug after `custom:` in the role. */
|
|
59606
|
+
key: string().refine(isRemoteCustomKey, { message: "a lowercase slug" }),
|
|
59607
|
+
/** What the operator calls it, and what a voice command may name. */
|
|
59608
|
+
name: string().min(1).max(60)
|
|
59609
|
+
});
|
|
59610
|
+
var RemotePowerModeSchema = _enum(["toggle", "discrete"]);
|
|
59611
|
+
var RemoteConfigSchema = object({
|
|
59612
|
+
layoutTemplateId: string().min(1).nullable(),
|
|
59613
|
+
/**
|
|
59614
|
+
* The transmitter device new bindings and an import send through. A binding
|
|
59615
|
+
* names its own device, so one command may override it.
|
|
59616
|
+
*/
|
|
59617
|
+
defaultTransmitterId: number().int().nonnegative().nullable().default(null),
|
|
59618
|
+
/** Keyed by role. A role with no entry falls back to the native cap. */
|
|
59619
|
+
bindings: record(string(), RemoteBindingSchema),
|
|
59620
|
+
customButtons: array(RemoteCustomButtonSchema).max(100),
|
|
59621
|
+
power: object({ mode: RemotePowerModeSchema })
|
|
59622
|
+
}).superRefine((config, ctx) => {
|
|
59623
|
+
const roles = Object.keys(config.bindings);
|
|
59624
|
+
if (roles.length > 200) ctx.addIssue({
|
|
59625
|
+
code: "custom",
|
|
59626
|
+
message: "too many bindings",
|
|
59627
|
+
path: ["bindings"]
|
|
59628
|
+
});
|
|
59629
|
+
for (const role of roles) if (!isRemoteRole(role)) ctx.addIssue({
|
|
59630
|
+
code: "custom",
|
|
59631
|
+
message: `"${role}" is not a role`,
|
|
59632
|
+
path: ["bindings", role]
|
|
59633
|
+
});
|
|
59634
|
+
const keys = /* @__PURE__ */ new Set();
|
|
59635
|
+
config.customButtons.forEach((button, index) => {
|
|
59636
|
+
if (keys.has(button.key)) ctx.addIssue({
|
|
59637
|
+
code: "custom",
|
|
59638
|
+
message: `custom button "${button.key}" is listed twice`,
|
|
59639
|
+
path: [
|
|
59640
|
+
"customButtons",
|
|
59641
|
+
index,
|
|
59642
|
+
"key"
|
|
59643
|
+
]
|
|
59644
|
+
});
|
|
59645
|
+
keys.add(button.key);
|
|
59646
|
+
});
|
|
59647
|
+
for (const role of roles) {
|
|
59648
|
+
const key = customKeyOfRole(role);
|
|
59649
|
+
if (key !== null && !keys.has(key)) ctx.addIssue({
|
|
59650
|
+
code: "custom",
|
|
59651
|
+
message: `${role} is bound but has no entry in customButtons`,
|
|
59652
|
+
path: ["bindings", role]
|
|
59653
|
+
});
|
|
59654
|
+
}
|
|
59655
|
+
});
|
|
59656
|
+
object({
|
|
59657
|
+
power: object({
|
|
59658
|
+
state: _enum(["on", "off"]),
|
|
59659
|
+
at: number()
|
|
59660
|
+
}).nullable(),
|
|
59661
|
+
muted: object({
|
|
59662
|
+
value: boolean(),
|
|
59663
|
+
at: number()
|
|
59664
|
+
}).nullable()
|
|
59665
|
+
});
|
|
59666
|
+
var RemoteLayoutButtonSchema = object({
|
|
59667
|
+
id: string().min(1).max(40),
|
|
59668
|
+
/** 0-based grid position. */
|
|
59669
|
+
row: number().int().min(0).max(24),
|
|
59670
|
+
col: number().int().min(0).max(24),
|
|
59671
|
+
rowSpan: number().int().min(1).max(24),
|
|
59672
|
+
colSpan: number().int().min(1).max(24),
|
|
59673
|
+
icon: string().min(1).max(60).optional(),
|
|
59674
|
+
label: string().min(1).max(40).optional(),
|
|
59675
|
+
role: RemoteRoleSchema
|
|
59676
|
+
});
|
|
59677
|
+
object({
|
|
59678
|
+
columns: number().int().min(1).max(24),
|
|
59679
|
+
rows: number().int().min(1).max(24),
|
|
59680
|
+
buttons: array(RemoteLayoutButtonSchema).max(200)
|
|
59681
|
+
}).superRefine((grid, ctx) => {
|
|
59682
|
+
const ids = /* @__PURE__ */ new Set();
|
|
59683
|
+
const taken = /* @__PURE__ */ new Map();
|
|
59684
|
+
grid.buttons.forEach((button, index) => {
|
|
59685
|
+
if (ids.has(button.id)) ctx.addIssue({
|
|
59686
|
+
code: "custom",
|
|
59687
|
+
message: `button id "${button.id}" is used twice`,
|
|
59688
|
+
path: [
|
|
59689
|
+
"buttons",
|
|
59690
|
+
index,
|
|
59691
|
+
"id"
|
|
59692
|
+
]
|
|
59693
|
+
});
|
|
59694
|
+
ids.add(button.id);
|
|
59695
|
+
if (button.row + button.rowSpan > grid.rows || button.col + button.colSpan > grid.columns) {
|
|
59696
|
+
ctx.addIssue({
|
|
59697
|
+
code: "custom",
|
|
59698
|
+
message: `button "${button.id}" leaves the grid`,
|
|
59699
|
+
path: ["buttons", index]
|
|
59700
|
+
});
|
|
59701
|
+
return;
|
|
59702
|
+
}
|
|
59703
|
+
for (let r = button.row; r < button.row + button.rowSpan; r += 1) for (let c = button.col; c < button.col + button.colSpan; c += 1) {
|
|
59704
|
+
const cell = `${r}:${c}`;
|
|
59705
|
+
const other = taken.get(cell);
|
|
59706
|
+
if (other !== void 0) {
|
|
59707
|
+
ctx.addIssue({
|
|
59708
|
+
code: "custom",
|
|
59709
|
+
message: `buttons "${other}" and "${button.id}" overlap`,
|
|
59710
|
+
path: ["buttons", index]
|
|
59711
|
+
});
|
|
59712
|
+
return;
|
|
59713
|
+
}
|
|
59714
|
+
taken.set(cell, button.id);
|
|
59715
|
+
}
|
|
59716
|
+
});
|
|
59717
|
+
});
|
|
59718
|
+
var RemoteBoundCommandSchema = object({
|
|
59719
|
+
role: RemoteRoleSchema,
|
|
59720
|
+
/** The operator's name for a custom button; absent for a known role. */
|
|
59721
|
+
label: string().optional(),
|
|
59722
|
+
custom: boolean()
|
|
59723
|
+
});
|
|
59724
|
+
var RemoteCommandListSchema = object({
|
|
59725
|
+
layoutTemplateId: string().nullable(),
|
|
59726
|
+
power: object({ mode: RemotePowerModeSchema }),
|
|
59727
|
+
/** Only the roles that are BOUND — the ones a press can reach. */
|
|
59728
|
+
commands: array(RemoteBoundCommandSchema)
|
|
59729
|
+
});
|
|
59730
|
+
var RemoteRefusalReasonSchema = _enum([
|
|
59731
|
+
"no-remote",
|
|
59732
|
+
"unbound-role",
|
|
59733
|
+
"action-failed"
|
|
59734
|
+
]);
|
|
59735
|
+
var RemoteSendResultSchema = discriminatedUnion("status", [
|
|
59736
|
+
object({
|
|
59737
|
+
status: literal("sent"),
|
|
59738
|
+
/** How many times the action ran. */
|
|
59739
|
+
sends: number().int().min(1)
|
|
59740
|
+
}),
|
|
59741
|
+
object({
|
|
59742
|
+
status: literal("skipped"),
|
|
59743
|
+
reason: literal("already-in-state")
|
|
59744
|
+
}),
|
|
59745
|
+
object({
|
|
59746
|
+
status: literal("refused"),
|
|
59747
|
+
reason: RemoteRefusalReasonSchema,
|
|
59748
|
+
detail: string().optional()
|
|
59749
|
+
})
|
|
59750
|
+
]);
|
|
59751
|
+
var RemoteMediaCommandSchema = _enum([
|
|
59752
|
+
"play",
|
|
59753
|
+
"pause",
|
|
59754
|
+
"stop",
|
|
59755
|
+
"next",
|
|
59756
|
+
"previous",
|
|
59757
|
+
"setMute",
|
|
59758
|
+
"selectSource"
|
|
59759
|
+
]);
|
|
59760
|
+
/** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
|
|
59761
|
+
var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
|
|
59762
|
+
var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status: literal("saved") }), object({
|
|
59763
|
+
status: literal("refused"),
|
|
59764
|
+
reason: _enum(["unknown-template", "not-a-media-player"]),
|
|
59765
|
+
detail: string()
|
|
59766
|
+
})]);
|
|
59767
|
+
var deviceInput = object({ deviceId: number().int().nonnegative() });
|
|
59768
|
+
DeviceType.MediaPlayer;
|
|
59769
|
+
method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
|
|
59770
|
+
kind: "mutation",
|
|
59771
|
+
auth: "admin"
|
|
59772
|
+
}), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
|
|
59773
|
+
role: RemoteRoleSchema,
|
|
59774
|
+
/** Sends in a row (volume steps). */
|
|
59775
|
+
repeat: number().int().min(1).max(50).default(1)
|
|
59776
|
+
}), RemoteSendResultSchema, {
|
|
59777
|
+
kind: "mutation",
|
|
59778
|
+
auth: "protected"
|
|
59779
|
+
}), method(deviceInput.extend({
|
|
59780
|
+
command: RemoteMediaCommandSchema,
|
|
59781
|
+
muted: boolean().optional(),
|
|
59782
|
+
source: string().optional(),
|
|
59783
|
+
repeat: number().int().min(1).max(50).default(1)
|
|
59784
|
+
}), RemoteMediaCommandResultSchema, {
|
|
59785
|
+
kind: "mutation",
|
|
59786
|
+
auth: "protected"
|
|
59787
|
+
});
|
|
59788
|
+
var TemplateKindSchema = _enum(["remote"]);
|
|
59789
|
+
/**
|
|
59790
|
+
* template — the generic store of reusable, named operator data (D963). A
|
|
59791
|
+
* record is `{ id, kind, name, payload }`; the kinds (today only `remote`, the
|
|
59792
|
+
* button grid of a remote) and the schema each payload must satisfy are
|
|
59793
|
+
* declared by the system in `template-kinds.ts`. Nothing here is specific to a
|
|
59794
|
+
* kind: the engine validates a payload against its kind's schema and, on a
|
|
59795
|
+
* delete, asks the kind's owner whether anything still points at the record.
|
|
59796
|
+
*
|
|
59797
|
+
* Every refusal is a typed result — a UI says "unknown kind" or names the
|
|
59798
|
+
* devices that still use a template, without parsing text.
|
|
59799
|
+
*/
|
|
59800
|
+
var TemplateRecordSchema = object({
|
|
59801
|
+
id: string().min(1),
|
|
59802
|
+
kind: TemplateKindSchema,
|
|
59803
|
+
name: string().min(1).max(120),
|
|
59804
|
+
/** Shaped by the kind; `checkTemplatePayload(kind, payload)` reads it. */
|
|
59805
|
+
payload: unknown(),
|
|
59806
|
+
createdAt: number(),
|
|
59807
|
+
updatedAt: number()
|
|
59808
|
+
});
|
|
59809
|
+
var TemplateWriteResultSchema = discriminatedUnion("status", [object({
|
|
59810
|
+
status: literal("saved"),
|
|
59811
|
+
template: TemplateRecordSchema
|
|
59812
|
+
}), object({
|
|
59813
|
+
status: literal("refused"),
|
|
59814
|
+
reason: _enum(["invalid-payload", "not-found"]),
|
|
59815
|
+
detail: string()
|
|
59816
|
+
})]);
|
|
59817
|
+
/** A device still pointing at a template. */
|
|
59818
|
+
var TemplateUserSchema = object({
|
|
59819
|
+
deviceId: number().int().nonnegative(),
|
|
59820
|
+
name: string()
|
|
59821
|
+
});
|
|
59822
|
+
var TemplateDeleteResultSchema = discriminatedUnion("status", [
|
|
59823
|
+
object({ status: literal("deleted") }),
|
|
59824
|
+
object({ status: literal("not-found") }),
|
|
59825
|
+
object({
|
|
59826
|
+
status: literal("in-use"),
|
|
59827
|
+
devices: array(TemplateUserSchema).min(1)
|
|
59828
|
+
})
|
|
59829
|
+
]);
|
|
59830
|
+
method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSchema), { auth: "protected" }), method(object({ id: string().min(1) }), TemplateRecordSchema.nullable(), { auth: "protected" }), method(object({
|
|
59831
|
+
kind: TemplateKindSchema,
|
|
59832
|
+
name: string().min(1).max(120),
|
|
59833
|
+
payload: unknown()
|
|
59834
|
+
}), TemplateWriteResultSchema, {
|
|
59835
|
+
kind: "mutation",
|
|
59836
|
+
auth: "admin"
|
|
59837
|
+
}), method(object({
|
|
59838
|
+
id: string().min(1),
|
|
59839
|
+
name: string().min(1).max(120).optional(),
|
|
59840
|
+
payload: unknown().optional()
|
|
59841
|
+
}), TemplateWriteResultSchema, {
|
|
59842
|
+
kind: "mutation",
|
|
59843
|
+
auth: "admin"
|
|
59844
|
+
}), method(object({ id: string().min(1) }), TemplateDeleteResultSchema, {
|
|
59845
|
+
kind: "mutation",
|
|
59846
|
+
auth: "admin"
|
|
59847
|
+
});
|
|
59848
|
+
/**
|
|
59215
59849
|
* button — device-scoped capability for HA `button.*` / `input_button.*`
|
|
59216
59850
|
* fire-only actuators. A button entity has no readable state — pressing it
|
|
59217
59851
|
* triggers an action on the HA side. There is no `status` / `runtimeState`:
|
|
@@ -60266,14 +60900,13 @@ reset: method(object({
|
|
|
60266
60900
|
auth: "admin"
|
|
60267
60901
|
}) },
|
|
60268
60902
|
/**
|
|
60269
|
-
* A
|
|
60270
|
-
*
|
|
60271
|
-
*
|
|
60272
|
-
* commands (`forward`) arrive in slice 3 (D663).
|
|
60903
|
+
* A consumable ADDED to an existing device (a graft) refuses `reset` by
|
|
60904
|
+
* name: only a composed device of a NEW type forwards an item's reset to a
|
|
60905
|
+
* source button, and marks that item `resettable` (D967).
|
|
60273
60906
|
*/
|
|
60274
60907
|
composedMethods: { reset: {
|
|
60275
60908
|
kind: "refuse",
|
|
60276
|
-
reason: "a
|
|
60909
|
+
reason: "a consumable added to an existing device is not resettable — only a new composed device forwards a reset"
|
|
60277
60910
|
} },
|
|
60278
60911
|
status: {
|
|
60279
60912
|
schema: ConsumablesStatusSchema,
|
|
@@ -60867,7 +61500,7 @@ var DishwasherStatusSchema = object({
|
|
|
60867
61500
|
/** Ms epoch of the last value received from the appliance; 0 = never. */
|
|
60868
61501
|
lastFetchedAt: number()
|
|
60869
61502
|
});
|
|
60870
|
-
var DeviceIdSchema = number().int().nonnegative();
|
|
61503
|
+
var DeviceIdSchema$1 = number().int().nonnegative();
|
|
60871
61504
|
/** A value a program option or setting can take. */
|
|
60872
61505
|
var DishwasherValueSchema = union([
|
|
60873
61506
|
boolean(),
|
|
@@ -60942,77 +61575,77 @@ var dishwasherCapability = {
|
|
|
60942
61575
|
* option feature keys; each is checked against the program's constraints.
|
|
60943
61576
|
*/
|
|
60944
61577
|
start: method(object({
|
|
60945
|
-
deviceId: DeviceIdSchema,
|
|
61578
|
+
deviceId: DeviceIdSchema$1,
|
|
60946
61579
|
program: string().min(1).optional(),
|
|
60947
61580
|
options: record(string(), DishwasherValueSchema).optional()
|
|
60948
61581
|
}), _void(), {
|
|
60949
61582
|
kind: "mutation",
|
|
60950
61583
|
auth: "protected"
|
|
60951
61584
|
}),
|
|
60952
|
-
pause: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
61585
|
+
pause: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
60953
61586
|
kind: "mutation",
|
|
60954
61587
|
auth: "protected"
|
|
60955
61588
|
}),
|
|
60956
|
-
resume: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
61589
|
+
resume: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
60957
61590
|
kind: "mutation",
|
|
60958
61591
|
auth: "protected"
|
|
60959
61592
|
}),
|
|
60960
|
-
stop: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
61593
|
+
stop: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
60961
61594
|
kind: "mutation",
|
|
60962
61595
|
auth: "protected"
|
|
60963
61596
|
}),
|
|
60964
61597
|
/** Select (not start) a program; refused when the appliance does not offer it. */
|
|
60965
61598
|
setProgram: method(object({
|
|
60966
|
-
deviceId: DeviceIdSchema,
|
|
61599
|
+
deviceId: DeviceIdSchema$1,
|
|
60967
61600
|
program: string().min(1)
|
|
60968
61601
|
}), _void(), {
|
|
60969
61602
|
kind: "mutation",
|
|
60970
61603
|
auth: "protected"
|
|
60971
61604
|
}),
|
|
60972
61605
|
setPower: method(object({
|
|
60973
|
-
deviceId: DeviceIdSchema,
|
|
61606
|
+
deviceId: DeviceIdSchema$1,
|
|
60974
61607
|
on: boolean()
|
|
60975
61608
|
}), _void(), {
|
|
60976
61609
|
kind: "mutation",
|
|
60977
61610
|
auth: "protected"
|
|
60978
61611
|
}),
|
|
60979
61612
|
setHalfLoad: method(object({
|
|
60980
|
-
deviceId: DeviceIdSchema,
|
|
61613
|
+
deviceId: DeviceIdSchema$1,
|
|
60981
61614
|
on: boolean()
|
|
60982
61615
|
}), _void(), {
|
|
60983
61616
|
kind: "mutation",
|
|
60984
61617
|
auth: "protected"
|
|
60985
61618
|
}),
|
|
60986
61619
|
setHygienePlus: method(object({
|
|
60987
|
-
deviceId: DeviceIdSchema,
|
|
61620
|
+
deviceId: DeviceIdSchema$1,
|
|
60988
61621
|
on: boolean()
|
|
60989
61622
|
}), _void(), {
|
|
60990
61623
|
kind: "mutation",
|
|
60991
61624
|
auth: "protected"
|
|
60992
61625
|
}),
|
|
60993
61626
|
setExtraDry: method(object({
|
|
60994
|
-
deviceId: DeviceIdSchema,
|
|
61627
|
+
deviceId: DeviceIdSchema$1,
|
|
60995
61628
|
on: boolean()
|
|
60996
61629
|
}), _void(), {
|
|
60997
61630
|
kind: "mutation",
|
|
60998
61631
|
auth: "protected"
|
|
60999
61632
|
}),
|
|
61000
61633
|
setIntensiveZone: method(object({
|
|
61001
|
-
deviceId: DeviceIdSchema,
|
|
61634
|
+
deviceId: DeviceIdSchema$1,
|
|
61002
61635
|
on: boolean()
|
|
61003
61636
|
}), _void(), {
|
|
61004
61637
|
kind: "mutation",
|
|
61005
61638
|
auth: "protected"
|
|
61006
61639
|
}),
|
|
61007
61640
|
setVarioSpeedPlus: method(object({
|
|
61008
|
-
deviceId: DeviceIdSchema,
|
|
61641
|
+
deviceId: DeviceIdSchema$1,
|
|
61009
61642
|
on: boolean()
|
|
61010
61643
|
}), _void(), {
|
|
61011
61644
|
kind: "mutation",
|
|
61012
61645
|
auth: "protected"
|
|
61013
61646
|
}),
|
|
61014
61647
|
setChildLock: method(object({
|
|
61015
|
-
deviceId: DeviceIdSchema,
|
|
61648
|
+
deviceId: DeviceIdSchema$1,
|
|
61016
61649
|
on: boolean()
|
|
61017
61650
|
}), _void(), {
|
|
61018
61651
|
kind: "mutation",
|
|
@@ -61024,7 +61657,7 @@ var dishwasherCapability = {
|
|
|
61024
61657
|
* its declared range / members.
|
|
61025
61658
|
*/
|
|
61026
61659
|
setOption: method(object({
|
|
61027
|
-
deviceId: DeviceIdSchema,
|
|
61660
|
+
deviceId: DeviceIdSchema$1,
|
|
61028
61661
|
key: string().min(1),
|
|
61029
61662
|
value: DishwasherValueSchema
|
|
61030
61663
|
}), _void(), {
|
|
@@ -61032,7 +61665,7 @@ var dishwasherCapability = {
|
|
|
61032
61665
|
auth: "protected"
|
|
61033
61666
|
}),
|
|
61034
61667
|
/** What this appliance can do, from its own description — the UI is derived from it. */
|
|
61035
|
-
getOptions: method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" })
|
|
61668
|
+
getOptions: method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" })
|
|
61036
61669
|
},
|
|
61037
61670
|
status: {
|
|
61038
61671
|
schema: DishwasherStatusSchema,
|
|
@@ -63413,9 +64046,35 @@ var MediaInfoSchema = object({
|
|
|
63413
64046
|
/** Optional cover-art / thumbnail URL. */
|
|
63414
64047
|
imageUrl: string().optional()
|
|
63415
64048
|
});
|
|
64049
|
+
/**
|
|
64050
|
+
* Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
|
|
64051
|
+
* the device said so. `assumed`: the driver has no feedback (infrared) and
|
|
64052
|
+
* tracks what it last SENT; it moves only after a send was accepted, and a
|
|
64053
|
+
* human at the real remote makes it wrong. A UI shows an assumed value as a
|
|
64054
|
+
* guess, never as a reading.
|
|
64055
|
+
*/
|
|
64056
|
+
var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
|
|
64057
|
+
/** The fields of a media-player status a driver can only guess. */
|
|
64058
|
+
var MediaPlayerAssumedFieldSchema = _enum([
|
|
64059
|
+
"state",
|
|
64060
|
+
"volumeLevel",
|
|
64061
|
+
"isMuted",
|
|
64062
|
+
"source"
|
|
64063
|
+
]);
|
|
63416
64064
|
var MediaPlayerStatusSchema = object({
|
|
63417
64065
|
/** Playback lifecycle state. */
|
|
63418
64066
|
state: MediaPlayerStateSchema,
|
|
64067
|
+
/**
|
|
64068
|
+
* Whether the state fields are readings or the driver's own bookkeeping.
|
|
64069
|
+
* A slice written before this field existed parses as `reported`.
|
|
64070
|
+
*/
|
|
64071
|
+
stateSource: MediaPlayerStateSourceSchema.default("reported"),
|
|
64072
|
+
/**
|
|
64073
|
+
* Which fields are the driver's own guess. `stateSource` is `assumed` when
|
|
64074
|
+
* this is not empty; a field not listed here is a reading even then (power
|
|
64075
|
+
* from a running-state source, mute guessed from the last send).
|
|
64076
|
+
*/
|
|
64077
|
+
assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
|
|
63419
64078
|
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
63420
64079
|
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
63421
64080
|
volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
@@ -64707,7 +65366,8 @@ var TopologyNodeSchema = object({
|
|
|
64707
65366
|
isHub: boolean(),
|
|
64708
65367
|
isOnline: boolean(),
|
|
64709
65368
|
cpuPercent: number(),
|
|
64710
|
-
|
|
65369
|
+
/** `null` when the node's memory read failed or it has not reported one (D393). */
|
|
65370
|
+
memoryPercent: number().nullable(),
|
|
64711
65371
|
uptime: number(),
|
|
64712
65372
|
lastSeen: string(),
|
|
64713
65373
|
localIps: array(string()).readonly(),
|
|
@@ -65325,9 +65985,10 @@ var PetFeederStatusSchema = object({
|
|
|
65325
65985
|
* combined reading; per-hopper levels live in `food1`/`food2`. Null when
|
|
65326
65986
|
* the device has not reported yet. */
|
|
65327
65987
|
foodLevel: number().nullable().meta(readingMeta("none")),
|
|
65328
|
-
/** Food in the BOWL
|
|
65329
|
-
* feeder
|
|
65330
|
-
|
|
65988
|
+
/** Food in the BOWL, a mass (stored in kilograms, D944). Reported only by a
|
|
65989
|
+
* feeder with a bowl scale; null when the model has none or its reading is
|
|
65990
|
+
* of an unproven unit. */
|
|
65991
|
+
bowlFood: number().nullable().meta(readingMeta("mass")),
|
|
65331
65992
|
/** True while the pet is eating (`state.eating`). */
|
|
65332
65993
|
eating: boolean(),
|
|
65333
65994
|
/** Whether `markFoodReplenished` does anything on this model. When false
|
|
@@ -65378,6 +66039,14 @@ var PetFeederStatusSchema = object({
|
|
|
65378
66039
|
feedSound: boolean(),
|
|
65379
66040
|
/** Speaker / prompt volume level (device-scaled integer). */
|
|
65380
66041
|
volume: number().meta(readingMeta("none")),
|
|
66042
|
+
/** Portion (grams) of the last manual feed sent from a single-hopper
|
|
66043
|
+
* stepper — the default the next manual feed starts from. Persisted per
|
|
66044
|
+
* device by the provider; null until a manual feed has been sent. */
|
|
66045
|
+
lastFeedGrams: gramsPortion.nullable(),
|
|
66046
|
+
/** Hopper-1 portion of the last manual feed (dual-hopper); null until sent. */
|
|
66047
|
+
lastFeedHopper1: gramsPortion.nullable(),
|
|
66048
|
+
/** Hopper-2 portion of the last manual feed (dual-hopper); null until sent. */
|
|
66049
|
+
lastFeedHopper2: gramsPortion.nullable(),
|
|
65381
66050
|
/** Ms epoch when the slice was last refreshed from the cloud. */
|
|
65382
66051
|
lastFetchedAt: number()
|
|
65383
66052
|
});
|
|
@@ -65503,6 +66172,137 @@ var petFeederCapability = {
|
|
|
65503
66172
|
}
|
|
65504
66173
|
} }
|
|
65505
66174
|
};
|
|
66175
|
+
/**
|
|
66176
|
+
* Pet water-fountain cap. Models the telemetry and the control surface of a
|
|
66177
|
+
* circulating drinking fountain: how much water is in the tank, how much the
|
|
66178
|
+
* pet drank, whether the pump runs and on what duty cycle.
|
|
66179
|
+
*
|
|
66180
|
+
* It has no native vendor provider of its own: a fountain reaches CamStack as
|
|
66181
|
+
* a container of loose entities (a pump switch, two number boxes, a "filter
|
|
66182
|
+
* changed" button), and an operator BUILDS the device from them — a composed
|
|
66183
|
+
* device of this type (D659, D967). Every method is therefore a forward to ONE
|
|
66184
|
+
* method of ONE source entity, and every reading is a field derived from a
|
|
66185
|
+
* source entity; the provider-shaped interface below exists so the composed
|
|
66186
|
+
* device and any future native provider answer the same contract.
|
|
66187
|
+
*
|
|
66188
|
+
* Quantities are stored in the base unit of their kind (D944): volumes in
|
|
66189
|
+
* LITRES, durations in SECONDS, the tank level in percent. A source that
|
|
66190
|
+
* reports millilitres or minutes is converted in the composition's expression,
|
|
66191
|
+
* never here. Every reading may be `null`: a source that is offline or does
|
|
66192
|
+
* not report is unknown, never zero.
|
|
66193
|
+
*
|
|
66194
|
+
* The filter is not a field of this cap: it is an item of the `consumables`
|
|
66195
|
+
* cap on the same device (kind `wear`), reset by the same forward mechanism.
|
|
66196
|
+
*/
|
|
66197
|
+
var PetFountainStatusSchema = object({
|
|
66198
|
+
/** Water in the tank, 0..100 (%). Null while the source has not reported. */
|
|
66199
|
+
waterLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
66200
|
+
/** Water in the tank, in litres. Null while the source has not reported. */
|
|
66201
|
+
waterVolume: number().min(0).nullable().meta(readingMeta("volume")),
|
|
66202
|
+
/** True when the tank is empty. Null while unknown. */
|
|
66203
|
+
empty: boolean().nullable(),
|
|
66204
|
+
/** True while the pump is running. Null while unknown. */
|
|
66205
|
+
pumpRunning: boolean().nullable(),
|
|
66206
|
+
/** True when the pump works on an on/off duty cycle rather than continuously. */
|
|
66207
|
+
pumpIntervalEnabled: boolean().nullable(),
|
|
66208
|
+
/** How long the pump runs per cycle, in seconds. */
|
|
66209
|
+
pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
66210
|
+
/** How long the pump rests per cycle, in seconds. */
|
|
66211
|
+
pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
66212
|
+
/** Water drunk in the last drink, in litres. */
|
|
66213
|
+
lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
66214
|
+
/** Water drunk in the current hour, in litres. */
|
|
66215
|
+
currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
66216
|
+
/** Water drunk over the last 24 hours (rolling), in litres. */
|
|
66217
|
+
last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
66218
|
+
/** Water drunk since local midnight, in litres. */
|
|
66219
|
+
todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
66220
|
+
/** Decoded fault message. Null means healthy; a non-null string is an active fault. */
|
|
66221
|
+
error: string().nullable(),
|
|
66222
|
+
/** Ms epoch when the slice was last refreshed. */
|
|
66223
|
+
lastFetchedAt: number()
|
|
66224
|
+
});
|
|
66225
|
+
var DeviceIdSchema = number().int().nonnegative();
|
|
66226
|
+
var MinutesSchema = number().int().min(0);
|
|
66227
|
+
var petFountainCapability = {
|
|
66228
|
+
name: "pet-fountain",
|
|
66229
|
+
scope: "device",
|
|
66230
|
+
deviceNative: true,
|
|
66231
|
+
mode: "singleton",
|
|
66232
|
+
deviceTypes: [DeviceType.PetFountain],
|
|
66233
|
+
methods: {
|
|
66234
|
+
/** Start or stop the pump. */
|
|
66235
|
+
setPump: method(object({
|
|
66236
|
+
deviceId: DeviceIdSchema,
|
|
66237
|
+
on: boolean()
|
|
66238
|
+
}), _void(), {
|
|
66239
|
+
kind: "mutation",
|
|
66240
|
+
auth: "protected"
|
|
66241
|
+
}),
|
|
66242
|
+
/** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
|
|
66243
|
+
setPumpInterval: method(object({
|
|
66244
|
+
deviceId: DeviceIdSchema,
|
|
66245
|
+
on: boolean()
|
|
66246
|
+
}), _void(), {
|
|
66247
|
+
kind: "mutation",
|
|
66248
|
+
auth: "protected"
|
|
66249
|
+
}),
|
|
66250
|
+
/** Set how many minutes the pump runs per cycle. */
|
|
66251
|
+
setPumpOnMinutes: method(object({
|
|
66252
|
+
deviceId: DeviceIdSchema,
|
|
66253
|
+
minutes: MinutesSchema
|
|
66254
|
+
}), _void(), {
|
|
66255
|
+
kind: "mutation",
|
|
66256
|
+
auth: "protected"
|
|
66257
|
+
}),
|
|
66258
|
+
/** Set how many minutes the pump rests per cycle. */
|
|
66259
|
+
setPumpOffMinutes: method(object({
|
|
66260
|
+
deviceId: DeviceIdSchema,
|
|
66261
|
+
minutes: MinutesSchema
|
|
66262
|
+
}), _void(), {
|
|
66263
|
+
kind: "mutation",
|
|
66264
|
+
auth: "protected"
|
|
66265
|
+
}),
|
|
66266
|
+
/** Clear the rolling daily consumption counters on the device. */
|
|
66267
|
+
resetDailyConsumption: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
66268
|
+
kind: "mutation",
|
|
66269
|
+
auth: "protected"
|
|
66270
|
+
})
|
|
66271
|
+
},
|
|
66272
|
+
status: {
|
|
66273
|
+
schema: PetFountainStatusSchema,
|
|
66274
|
+
kind: "push"
|
|
66275
|
+
},
|
|
66276
|
+
/** Runtime-state slice — mirrored by the kernel; the hero card reads it whole. */
|
|
66277
|
+
runtimeState: PetFountainStatusSchema,
|
|
66278
|
+
/**
|
|
66279
|
+
* Runtime-state durability: **session** — live appliance state re-published on connect.
|
|
66280
|
+
*
|
|
66281
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
66282
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
66283
|
+
*/
|
|
66284
|
+
durability: "session",
|
|
66285
|
+
/** History (D850): recorded series. */
|
|
66286
|
+
history: { fields: {
|
|
66287
|
+
waterLevel: {
|
|
66288
|
+
kind: "gauge",
|
|
66289
|
+
unit: "%",
|
|
66290
|
+
deadband: 1
|
|
66291
|
+
},
|
|
66292
|
+
waterVolume: {
|
|
66293
|
+
kind: "gauge",
|
|
66294
|
+
unit: "L"
|
|
66295
|
+
},
|
|
66296
|
+
todayDrink: {
|
|
66297
|
+
kind: "gauge",
|
|
66298
|
+
unit: "L"
|
|
66299
|
+
},
|
|
66300
|
+
last24hDrink: {
|
|
66301
|
+
kind: "gauge",
|
|
66302
|
+
unit: "L"
|
|
66303
|
+
}
|
|
66304
|
+
} }
|
|
66305
|
+
};
|
|
65506
66306
|
var VehicleSchema = object({
|
|
65507
66307
|
id: string(),
|
|
65508
66308
|
name: string(),
|
|
@@ -71423,6 +72223,14 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
|
|
|
71423
72223
|
kind: "mutation",
|
|
71424
72224
|
auth: "admin"
|
|
71425
72225
|
});
|
|
72226
|
+
discriminatedUnion("kind", [object({
|
|
72227
|
+
kind: literal("raw"),
|
|
72228
|
+
timings: array(number().int()),
|
|
72229
|
+
settings: TransmitSettingsSchema.optional()
|
|
72230
|
+
}), object({
|
|
72231
|
+
kind: literal("pronto"),
|
|
72232
|
+
code: string()
|
|
72233
|
+
})]);
|
|
71426
72234
|
function resolveSince(running, previous, transitionAtMs, bootstrapSinceMs) {
|
|
71427
72235
|
if (running === null) return null;
|
|
71428
72236
|
const prevRunning = previous?.running ?? null;
|
|
@@ -71860,7 +72668,7 @@ function tryConvertDelta(value, from, to) {
|
|
|
71860
72668
|
return shifted - origin;
|
|
71861
72669
|
}
|
|
71862
72670
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
71863
|
-
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;
|
|
72671
|
+
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;
|
|
71864
72672
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
71865
72673
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
71866
72674
|
new Set(CAP_NAMES);
|
|
@@ -72211,6 +73019,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
72211
73019
|
notifier: notifierCapability,
|
|
72212
73020
|
numericSensor: numericSensorCapability,
|
|
72213
73021
|
petFeeder: petFeederCapability,
|
|
73022
|
+
petFountain: petFountainCapability,
|
|
72214
73023
|
powerMeter: powerMeterCapability,
|
|
72215
73024
|
presence: presenceCapability,
|
|
72216
73025
|
pressureSensor: pressureSensorCapability,
|
|
@@ -73461,7 +74270,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
73461
74270
|
return toDeviceSummary(device, this.addonId);
|
|
73462
74271
|
}
|
|
73463
74272
|
};
|
|
73464
|
-
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;
|
|
74273
|
+
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;
|
|
73465
74274
|
new Set(Object.values(DeviceType));
|
|
73466
74275
|
Object.freeze({
|
|
73467
74276
|
"accessories.getStatus": {
|
|
@@ -75396,6 +76205,13 @@ Object.freeze({
|
|
|
75396
76205
|
access: "create",
|
|
75397
76206
|
role: "admin"
|
|
75398
76207
|
},
|
|
76208
|
+
"deviceExport.exposeEntity": {
|
|
76209
|
+
capName: "device-export",
|
|
76210
|
+
capScope: "system",
|
|
76211
|
+
addonId: null,
|
|
76212
|
+
access: "create",
|
|
76213
|
+
role: "admin"
|
|
76214
|
+
},
|
|
75399
76215
|
"deviceExport.getDeviceLiveContribution": {
|
|
75400
76216
|
capName: "device-export",
|
|
75401
76217
|
capScope: "system",
|
|
@@ -75424,6 +76240,13 @@ Object.freeze({
|
|
|
75424
76240
|
access: "view",
|
|
75425
76241
|
role: "admin"
|
|
75426
76242
|
},
|
|
76243
|
+
"deviceExport.listExposedEntities": {
|
|
76244
|
+
capName: "device-export",
|
|
76245
|
+
capScope: "system",
|
|
76246
|
+
addonId: null,
|
|
76247
|
+
access: "view",
|
|
76248
|
+
role: "admin"
|
|
76249
|
+
},
|
|
75427
76250
|
"deviceExport.listSupportedDeviceKinds": {
|
|
75428
76251
|
capName: "device-export",
|
|
75429
76252
|
capScope: "system",
|
|
@@ -75431,6 +76254,13 @@ Object.freeze({
|
|
|
75431
76254
|
access: "view",
|
|
75432
76255
|
role: "admin"
|
|
75433
76256
|
},
|
|
76257
|
+
"deviceExport.listSupportedEntityKinds": {
|
|
76258
|
+
capName: "device-export",
|
|
76259
|
+
capScope: "system",
|
|
76260
|
+
addonId: null,
|
|
76261
|
+
access: "view",
|
|
76262
|
+
role: "admin"
|
|
76263
|
+
},
|
|
75434
76264
|
"deviceExport.unexposeDevice": {
|
|
75435
76265
|
capName: "device-export",
|
|
75436
76266
|
capScope: "system",
|
|
@@ -75438,6 +76268,13 @@ Object.freeze({
|
|
|
75438
76268
|
access: "create",
|
|
75439
76269
|
role: "admin"
|
|
75440
76270
|
},
|
|
76271
|
+
"deviceExport.unexposeEntity": {
|
|
76272
|
+
capName: "device-export",
|
|
76273
|
+
capScope: "system",
|
|
76274
|
+
addonId: null,
|
|
76275
|
+
access: "create",
|
|
76276
|
+
role: "admin"
|
|
76277
|
+
},
|
|
75441
76278
|
"deviceExtension.listCandidates": {
|
|
75442
76279
|
capName: "device-extension",
|
|
75443
76280
|
capScope: "device",
|
|
@@ -78259,6 +79096,13 @@ Object.freeze({
|
|
|
78259
79096
|
access: "create",
|
|
78260
79097
|
role: "admin"
|
|
78261
79098
|
},
|
|
79099
|
+
"nodeRemovalHook.onNodeRemoved": {
|
|
79100
|
+
capName: "node-removal-hook",
|
|
79101
|
+
capScope: "system",
|
|
79102
|
+
addonId: null,
|
|
79103
|
+
access: "create",
|
|
79104
|
+
role: "admin"
|
|
79105
|
+
},
|
|
78262
79106
|
"nodes.clusterAddonStatus": {
|
|
78263
79107
|
capName: "nodes",
|
|
78264
79108
|
capScope: "system",
|
|
@@ -78896,6 +79740,48 @@ Object.freeze({
|
|
|
78896
79740
|
access: "create",
|
|
78897
79741
|
role: "viewer"
|
|
78898
79742
|
},
|
|
79743
|
+
"petFountain.getStatus": {
|
|
79744
|
+
capName: "pet-fountain",
|
|
79745
|
+
capScope: "device",
|
|
79746
|
+
addonId: null,
|
|
79747
|
+
access: "view",
|
|
79748
|
+
role: "viewer"
|
|
79749
|
+
},
|
|
79750
|
+
"petFountain.resetDailyConsumption": {
|
|
79751
|
+
capName: "pet-fountain",
|
|
79752
|
+
capScope: "device",
|
|
79753
|
+
addonId: null,
|
|
79754
|
+
access: "delete",
|
|
79755
|
+
role: "viewer"
|
|
79756
|
+
},
|
|
79757
|
+
"petFountain.setPump": {
|
|
79758
|
+
capName: "pet-fountain",
|
|
79759
|
+
capScope: "device",
|
|
79760
|
+
addonId: null,
|
|
79761
|
+
access: "create",
|
|
79762
|
+
role: "viewer"
|
|
79763
|
+
},
|
|
79764
|
+
"petFountain.setPumpInterval": {
|
|
79765
|
+
capName: "pet-fountain",
|
|
79766
|
+
capScope: "device",
|
|
79767
|
+
addonId: null,
|
|
79768
|
+
access: "create",
|
|
79769
|
+
role: "viewer"
|
|
79770
|
+
},
|
|
79771
|
+
"petFountain.setPumpOffMinutes": {
|
|
79772
|
+
capName: "pet-fountain",
|
|
79773
|
+
capScope: "device",
|
|
79774
|
+
addonId: null,
|
|
79775
|
+
access: "create",
|
|
79776
|
+
role: "viewer"
|
|
79777
|
+
},
|
|
79778
|
+
"petFountain.setPumpOnMinutes": {
|
|
79779
|
+
capName: "pet-fountain",
|
|
79780
|
+
capScope: "device",
|
|
79781
|
+
addonId: null,
|
|
79782
|
+
access: "create",
|
|
79783
|
+
role: "viewer"
|
|
79784
|
+
},
|
|
78899
79785
|
"pipelineAnalytics.addRetrainFrameFromRecording": {
|
|
78900
79786
|
capName: "pipeline-analytics",
|
|
78901
79787
|
capScope: "device",
|
|
@@ -81150,6 +82036,41 @@ Object.freeze({
|
|
|
81150
82036
|
access: "create",
|
|
81151
82037
|
role: "admin"
|
|
81152
82038
|
},
|
|
82039
|
+
"remote.getConfig": {
|
|
82040
|
+
capName: "remote",
|
|
82041
|
+
capScope: "system",
|
|
82042
|
+
addonId: null,
|
|
82043
|
+
access: "view",
|
|
82044
|
+
role: "admin"
|
|
82045
|
+
},
|
|
82046
|
+
"remote.listCommands": {
|
|
82047
|
+
capName: "remote",
|
|
82048
|
+
capScope: "system",
|
|
82049
|
+
addonId: null,
|
|
82050
|
+
access: "view",
|
|
82051
|
+
role: "admin"
|
|
82052
|
+
},
|
|
82053
|
+
"remote.sendCommand": {
|
|
82054
|
+
capName: "remote",
|
|
82055
|
+
capScope: "system",
|
|
82056
|
+
addonId: null,
|
|
82057
|
+
access: "create",
|
|
82058
|
+
role: "admin"
|
|
82059
|
+
},
|
|
82060
|
+
"remote.sendMediaCommand": {
|
|
82061
|
+
capName: "remote",
|
|
82062
|
+
capScope: "system",
|
|
82063
|
+
addonId: null,
|
|
82064
|
+
access: "create",
|
|
82065
|
+
role: "admin"
|
|
82066
|
+
},
|
|
82067
|
+
"remote.setConfig": {
|
|
82068
|
+
capName: "remote",
|
|
82069
|
+
capScope: "system",
|
|
82070
|
+
addonId: null,
|
|
82071
|
+
access: "create",
|
|
82072
|
+
role: "admin"
|
|
82073
|
+
},
|
|
81153
82074
|
"runningState.getStatus": {
|
|
81154
82075
|
capName: "running-state",
|
|
81155
82076
|
capScope: "device",
|
|
@@ -82340,6 +83261,41 @@ Object.freeze({
|
|
|
82340
83261
|
access: "view",
|
|
82341
83262
|
role: "viewer"
|
|
82342
83263
|
},
|
|
83264
|
+
"template.create": {
|
|
83265
|
+
capName: "template",
|
|
83266
|
+
capScope: "system",
|
|
83267
|
+
addonId: null,
|
|
83268
|
+
access: "create",
|
|
83269
|
+
role: "admin"
|
|
83270
|
+
},
|
|
83271
|
+
"template.delete": {
|
|
83272
|
+
capName: "template",
|
|
83273
|
+
capScope: "system",
|
|
83274
|
+
addonId: null,
|
|
83275
|
+
access: "delete",
|
|
83276
|
+
role: "admin"
|
|
83277
|
+
},
|
|
83278
|
+
"template.get": {
|
|
83279
|
+
capName: "template",
|
|
83280
|
+
capScope: "system",
|
|
83281
|
+
addonId: null,
|
|
83282
|
+
access: "view",
|
|
83283
|
+
role: "admin"
|
|
83284
|
+
},
|
|
83285
|
+
"template.list": {
|
|
83286
|
+
capName: "template",
|
|
83287
|
+
capScope: "system",
|
|
83288
|
+
addonId: null,
|
|
83289
|
+
access: "view",
|
|
83290
|
+
role: "admin"
|
|
83291
|
+
},
|
|
83292
|
+
"template.update": {
|
|
83293
|
+
capName: "template",
|
|
83294
|
+
capScope: "system",
|
|
83295
|
+
addonId: null,
|
|
83296
|
+
access: "create",
|
|
83297
|
+
role: "admin"
|
|
83298
|
+
},
|
|
82343
83299
|
"terminalSession.adoptLegacyMonitor": {
|
|
82344
83300
|
capName: "terminal-session",
|
|
82345
83301
|
capScope: "system",
|
|
@@ -85308,6 +86264,100 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
85308
86264
|
}
|
|
85309
86265
|
]
|
|
85310
86266
|
},
|
|
86267
|
+
"pet-fountain": {
|
|
86268
|
+
scope: "device",
|
|
86269
|
+
noun: t("Pet fountain", "Fontana per animali"),
|
|
86270
|
+
actions: [
|
|
86271
|
+
{
|
|
86272
|
+
id: "petFountain.pumpOn",
|
|
86273
|
+
method: "setPump",
|
|
86274
|
+
label: t("Pump on", "Pompa accesa"),
|
|
86275
|
+
phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
|
|
86276
|
+
icon: "play",
|
|
86277
|
+
args: { on: true }
|
|
86278
|
+
},
|
|
86279
|
+
{
|
|
86280
|
+
id: "petFountain.pumpOff",
|
|
86281
|
+
method: "setPump",
|
|
86282
|
+
label: t("Pump off", "Pompa spenta"),
|
|
86283
|
+
phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
|
|
86284
|
+
icon: "square",
|
|
86285
|
+
args: { on: false }
|
|
86286
|
+
},
|
|
86287
|
+
{
|
|
86288
|
+
id: "petFountain.intervalOn",
|
|
86289
|
+
method: "setPumpInterval",
|
|
86290
|
+
label: t("Pump cycle on", "Ciclo pompa attivo"),
|
|
86291
|
+
phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
|
|
86292
|
+
icon: "timer",
|
|
86293
|
+
args: { on: true }
|
|
86294
|
+
},
|
|
86295
|
+
{
|
|
86296
|
+
id: "petFountain.intervalOff",
|
|
86297
|
+
method: "setPumpInterval",
|
|
86298
|
+
label: t("Pump cycle off", "Ciclo pompa spento"),
|
|
86299
|
+
phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
|
|
86300
|
+
icon: "timer-off",
|
|
86301
|
+
args: { on: false }
|
|
86302
|
+
},
|
|
86303
|
+
{
|
|
86304
|
+
id: "petFountain.pumpOnMinutes",
|
|
86305
|
+
method: "setPumpOnMinutes",
|
|
86306
|
+
label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
|
|
86307
|
+
phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
|
|
86308
|
+
icon: "timer",
|
|
86309
|
+
openArgs: [{
|
|
86310
|
+
key: "minutes",
|
|
86311
|
+
label: t("Minutes", "Minuti"),
|
|
86312
|
+
offer: {
|
|
86313
|
+
kind: "number",
|
|
86314
|
+
min: 0,
|
|
86315
|
+
step: 1,
|
|
86316
|
+
unit: "min"
|
|
86317
|
+
}
|
|
86318
|
+
}]
|
|
86319
|
+
},
|
|
86320
|
+
{
|
|
86321
|
+
id: "petFountain.pumpOffMinutes",
|
|
86322
|
+
method: "setPumpOffMinutes",
|
|
86323
|
+
label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
|
|
86324
|
+
phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
|
|
86325
|
+
icon: "timer-off",
|
|
86326
|
+
openArgs: [{
|
|
86327
|
+
key: "minutes",
|
|
86328
|
+
label: t("Minutes", "Minuti"),
|
|
86329
|
+
offer: {
|
|
86330
|
+
kind: "number",
|
|
86331
|
+
min: 0,
|
|
86332
|
+
step: 1,
|
|
86333
|
+
unit: "min"
|
|
86334
|
+
}
|
|
86335
|
+
}]
|
|
86336
|
+
},
|
|
86337
|
+
{
|
|
86338
|
+
id: "petFountain.resetDailyConsumption",
|
|
86339
|
+
method: "resetDailyConsumption",
|
|
86340
|
+
label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
|
|
86341
|
+
phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
|
|
86342
|
+
icon: "rotate-ccw"
|
|
86343
|
+
}
|
|
86344
|
+
],
|
|
86345
|
+
state: [{
|
|
86346
|
+
kind: "boolean",
|
|
86347
|
+
field: "empty",
|
|
86348
|
+
label: t("Tank empty", "Serbatoio vuoto"),
|
|
86349
|
+
whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
|
|
86350
|
+
whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
|
|
86351
|
+
icon: "droplet"
|
|
86352
|
+
}, {
|
|
86353
|
+
kind: "numeric",
|
|
86354
|
+
field: "waterLevel",
|
|
86355
|
+
quantity: t("the water level", "il livello dell’acqua"),
|
|
86356
|
+
label: t("Water level", "Livello dell’acqua"),
|
|
86357
|
+
unit: "%",
|
|
86358
|
+
icon: "droplets"
|
|
86359
|
+
}]
|
|
86360
|
+
},
|
|
85311
86361
|
"media-player": {
|
|
85312
86362
|
scope: "device",
|
|
85313
86363
|
noun: t("Media player", "Lettore multimediale"),
|
|
@@ -87132,6 +88182,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
87132
88182
|
"device-manager": system("the device registry itself — rename, move and remove are administration"),
|
|
87133
88183
|
"device-provider": system(INTERNAL),
|
|
87134
88184
|
"device-removal-hook": system(INTERNAL),
|
|
88185
|
+
"node-removal-hook": system(INTERNAL),
|
|
87135
88186
|
"device-state": system("the runtime-state mirror every trigger already reads through"),
|
|
87136
88187
|
"display-palette": system("the cluster display palette — one colour table for every client, not a device action"),
|
|
87137
88188
|
"embedding-encoder": system(ML),
|
|
@@ -87171,6 +88222,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
87171
88222
|
"recording-archive": system(STORAGE),
|
|
87172
88223
|
"recording-export": system("recording exports — requested from the timeline, not an automation step"),
|
|
87173
88224
|
records: system(INTERNAL),
|
|
88225
|
+
remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
|
|
87174
88226
|
"server-management": system(CLUSTER),
|
|
87175
88227
|
"settings-store": system(INTERNAL),
|
|
87176
88228
|
"smtp-provider": system(INTERNAL),
|
|
@@ -87183,6 +88235,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
87183
88235
|
"storage-provider": system(STORAGE),
|
|
87184
88236
|
"stream-broker": system(PIPELINE),
|
|
87185
88237
|
system: system(CLUSTER),
|
|
88238
|
+
template: system("operator-authored templates — edited in their own editor, never actuated"),
|
|
87186
88239
|
"terminal-session": system(CLUSTER),
|
|
87187
88240
|
toast: system(UI_PLUMBING),
|
|
87188
88241
|
training: system(ML),
|
|
@@ -87242,7 +88295,7 @@ function projectAction(cap, action) {
|
|
|
87242
88295
|
};
|
|
87243
88296
|
}
|
|
87244
88297
|
Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
|
|
87245
|
-
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;
|
|
88298
|
+
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;
|
|
87246
88299
|
DeviceFeature.BatteryOperated;
|
|
87247
88300
|
/**
|
|
87248
88301
|
* How a washer, dryer or dishwasher leaves CamStack.
|
|
@@ -88804,6 +89857,36 @@ Object.freeze({
|
|
|
88804
89857
|
form: "single",
|
|
88805
89858
|
optional: false
|
|
88806
89859
|
}],
|
|
89860
|
+
"petFountain.getStatus": [{
|
|
89861
|
+
name: "deviceId",
|
|
89862
|
+
form: "single",
|
|
89863
|
+
optional: false
|
|
89864
|
+
}],
|
|
89865
|
+
"petFountain.resetDailyConsumption": [{
|
|
89866
|
+
name: "deviceId",
|
|
89867
|
+
form: "single",
|
|
89868
|
+
optional: false
|
|
89869
|
+
}],
|
|
89870
|
+
"petFountain.setPump": [{
|
|
89871
|
+
name: "deviceId",
|
|
89872
|
+
form: "single",
|
|
89873
|
+
optional: false
|
|
89874
|
+
}],
|
|
89875
|
+
"petFountain.setPumpInterval": [{
|
|
89876
|
+
name: "deviceId",
|
|
89877
|
+
form: "single",
|
|
89878
|
+
optional: false
|
|
89879
|
+
}],
|
|
89880
|
+
"petFountain.setPumpOffMinutes": [{
|
|
89881
|
+
name: "deviceId",
|
|
89882
|
+
form: "single",
|
|
89883
|
+
optional: false
|
|
89884
|
+
}],
|
|
89885
|
+
"petFountain.setPumpOnMinutes": [{
|
|
89886
|
+
name: "deviceId",
|
|
89887
|
+
form: "single",
|
|
89888
|
+
optional: false
|
|
89889
|
+
}],
|
|
88807
89890
|
"pipelineAnalytics.addRetrainFrameFromRecording": [{
|
|
88808
89891
|
name: "deviceId",
|
|
88809
89892
|
form: "single",
|
|
@@ -89620,6 +90703,31 @@ Object.freeze({
|
|
|
89620
90703
|
form: "single",
|
|
89621
90704
|
optional: true
|
|
89622
90705
|
}],
|
|
90706
|
+
"remote.getConfig": [{
|
|
90707
|
+
name: "deviceId",
|
|
90708
|
+
form: "single",
|
|
90709
|
+
optional: false
|
|
90710
|
+
}],
|
|
90711
|
+
"remote.listCommands": [{
|
|
90712
|
+
name: "deviceId",
|
|
90713
|
+
form: "single",
|
|
90714
|
+
optional: false
|
|
90715
|
+
}],
|
|
90716
|
+
"remote.sendCommand": [{
|
|
90717
|
+
name: "deviceId",
|
|
90718
|
+
form: "single",
|
|
90719
|
+
optional: false
|
|
90720
|
+
}],
|
|
90721
|
+
"remote.sendMediaCommand": [{
|
|
90722
|
+
name: "deviceId",
|
|
90723
|
+
form: "single",
|
|
90724
|
+
optional: false
|
|
90725
|
+
}],
|
|
90726
|
+
"remote.setConfig": [{
|
|
90727
|
+
name: "deviceId",
|
|
90728
|
+
form: "single",
|
|
90729
|
+
optional: false
|
|
90730
|
+
}],
|
|
89623
90731
|
"runningState.getStatus": [{
|
|
89624
90732
|
name: "deviceId",
|
|
89625
90733
|
form: "single",
|
|
@@ -91777,14 +92885,6 @@ function bestLocationMatch(externalName, existing, threshold = .8) {
|
|
|
91777
92885
|
String.raw`(?<=^|["'(\s>,\[{])`;
|
|
91778
92886
|
var MB = 1024 * 1024;
|
|
91779
92887
|
1024 * MB, 3072 * MB;
|
|
91780
|
-
discriminatedUnion("kind", [object({
|
|
91781
|
-
kind: literal("raw"),
|
|
91782
|
-
timings: array(number().int()),
|
|
91783
|
-
settings: TransmitSettingsSchema.optional()
|
|
91784
|
-
}), object({
|
|
91785
|
-
kind: literal("pronto"),
|
|
91786
|
-
code: string()
|
|
91787
|
-
})]);
|
|
91788
92888
|
object({
|
|
91789
92889
|
manufacturer: string().optional(),
|
|
91790
92890
|
supportedModels: array(string()).optional(),
|
|
@@ -91794,4 +92894,4 @@ object({
|
|
|
91794
92894
|
commands: record(string(), unknown())
|
|
91795
92895
|
});
|
|
91796
92896
|
//#endregion
|
|
91797
|
-
export { lawnMowerControlCapability as $,
|
|
92897
|
+
export { lawnMowerControlCapability as $, object as $t, buttonCapability as A, valveCapability as At, coverCapability as B, baseUnitOf as Bt, batteryCapability as C, switchCapability as Ct, brokerCapability as D, tryConvertUnit as Dt, brightnessCapability as E, tryConvertDelta as Et, connectivityCapability as F, DeviceRole as Ft, eventEmitterCapability as G, isReadingQuantity as Gt, deviceAdoptionCapability as H, defineEntryKind as Ht, consumableFormOf as I, DeviceType as It, gasCapability as J, array as Jt, fanControlCapability as K, nodePin as Kt, consumableKindOf as L, errMsg as Lt, carbonMonoxideCapability as M, waterHeaterCapability as Mt, climateControlCapability as N, weatherCapability as Nt, buildAddonRouteProvider as O, updateCapability as Ot, colorCapability as P, DeviceFeature as Pt, ingestReading as Q, number as Qt, contactCapability as R, BaseAddon as Rt, automationControlCapability as S, smokeCapability as St, binaryCapability as T, temperatureSensorCapability as Tt, deviceExportCapability as U, defineRecordKinds as Ut, deviceAdminLinkCapability as V, createEvent as Vt, enumSensorCapability as W, entryBody as Wt, humiditySensorCapability as X, discriminatedUnion as Xt, humidifierCapability as Y, boolean as Yt, imageCapability as Z, literal as Zt, accessoriesCapability as _, runningFromMediaPlayer as _t, COCO_TO_MACRO as a, EventCategory as an, notifierCapability as at, alarmPanelCapability as b, scriptRunnerCapability as bt, ConsumableItemSchema as c, parseAdminBase as ct, FanDirectionSchema as d, presenceCapability as dt, record as en, lockControlCapability as et, HvacModeSchema as f, pressureSensorCapability as ft, TargetSchema as g, runningFromLawnMower as gt, RUNNING_STATE_COLD as h, runExportedAction as ht, CAMERA_SWITCH_ORDER as i, url as in, notificationOutputCapability as it, calendarSourceCapability as j, vibrationCapability as jt, buildDeviceAdminUrl as k, vacuumControlCapability as kt, ConsumableStatusSchema as l, powerMeterCapability as lt, PLUG_APPLIANCE_TYPES as m, quantityFromUnit as mt, BaseDevice as n, union as nn, motionCapability as nt, CameraSwitchIdSchema as o, numericSensorCapability as ot, MediaPlayerRepeatSchema as p, pruneDeletedEntities as pt, floodCapability as q, _enum as qt, BaseDeviceProvider as r, unknown as rn, normalizeUnit as rt, ConsumableFormSchema as s, oauthIntegrationCapability as st, AlarmArmModeSchema as t, string as tn, mediaPlayerCapability as tt, EnumSensorDateTimeFormatSchema as u, prepareNotification as ut, addonRoutesCapability as v, runningFromVacuumState as vt, bestLocationMatch as w, tamperCapability as wt, ambientLightSensorCapability as x, sendToExportedTarget as xt, airQualitySensorCapability as y, runningStateCapability as yt, controlCapability as z, RecordSecretUnavailableError as zt };
|