@camstack/addon-import-alexa 0.2.165 → 0.2.167
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/addon.js +1942 -677
- package/dist/addon.mjs +1942 -677
- package/package.json +1 -1
package/dist/addon.mjs
CHANGED
|
@@ -5247,6 +5247,14 @@ function object(shape, params) {
|
|
|
5247
5247
|
...normalizeParams(params)
|
|
5248
5248
|
});
|
|
5249
5249
|
}
|
|
5250
|
+
function strictObject(shape, params) {
|
|
5251
|
+
return new ZodObject({
|
|
5252
|
+
type: "object",
|
|
5253
|
+
shape,
|
|
5254
|
+
catchall: never(),
|
|
5255
|
+
...normalizeParams(params)
|
|
5256
|
+
});
|
|
5257
|
+
}
|
|
5250
5258
|
function looseObject(shape, params) {
|
|
5251
5259
|
return new ZodObject({
|
|
5252
5260
|
type: "object",
|
|
@@ -5626,7 +5634,7 @@ var ZodIssueCode = {
|
|
|
5626
5634
|
var ZodFirstPartyTypeKind;
|
|
5627
5635
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5628
5636
|
//#endregion
|
|
5629
|
-
//#region ../types/dist/sleep-
|
|
5637
|
+
//#region ../types/dist/sleep-6eG9C8Xl.mjs
|
|
5630
5638
|
/**
|
|
5631
5639
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5632
5640
|
* window to float samples (D455).
|
|
@@ -8861,9 +8869,76 @@ function optionalMethod(input, output, options) {
|
|
|
8861
8869
|
function event(data) {
|
|
8862
8870
|
return { data };
|
|
8863
8871
|
}
|
|
8872
|
+
/**
|
|
8873
|
+
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
8874
|
+
* declarations. Every addon that contributes a UI page registers a
|
|
8875
|
+
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
8876
|
+
* see `addon-pages.cap.ts`) walks this collection, stamps versioned
|
|
8877
|
+
* `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
|
|
8878
|
+
* that admin-ui consumes.
|
|
8879
|
+
*
|
|
8880
|
+
* The split exists because the public listing has a different output
|
|
8881
|
+
* shape than the per-provider raw declarations, and we want both ends
|
|
8882
|
+
* to flow through codegen instead of relying on a hand-written wrapper.
|
|
8883
|
+
*/
|
|
8884
|
+
var AddonPageDeclarationSchema = object({
|
|
8885
|
+
id: string(),
|
|
8886
|
+
label: string(),
|
|
8887
|
+
icon: string(),
|
|
8888
|
+
path: string(),
|
|
8889
|
+
/**
|
|
8890
|
+
* Module Federation remote name — must match the `name` field on the
|
|
8891
|
+
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
8892
|
+
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
8893
|
+
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
8894
|
+
* contain hyphens).
|
|
8895
|
+
*/
|
|
8896
|
+
remoteName: string(),
|
|
8897
|
+
/**
|
|
8898
|
+
* Bundle filename inside the addon's `dist/` dir served at
|
|
8899
|
+
* `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
|
|
8900
|
+
* is always `'remoteEntry.js'`; the value is kept on the metadata so
|
|
8901
|
+
* the static-file route can compute an mtime-based cache-buster URL
|
|
8902
|
+
* without a separate filesystem stat.
|
|
8903
|
+
*/
|
|
8904
|
+
bundle: string(),
|
|
8905
|
+
/**
|
|
8906
|
+
* Sidebar section this page docks into. Well-known ids: `'detection'`,
|
|
8907
|
+
* `'cluster'`, `'administration'` — the page renders inside that group.
|
|
8908
|
+
* Any OTHER string creates (or joins) a custom section rendered after
|
|
8909
|
+
* the built-in groups; its label comes from `sectionLabel` (first
|
|
8910
|
+
* declaration wins), falling back to the id. Absent → the legacy
|
|
8911
|
+
* "Addon Pages" group.
|
|
8912
|
+
*/
|
|
8913
|
+
section: string().optional(),
|
|
8914
|
+
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
8915
|
+
sectionLabel: string().optional()
|
|
8916
|
+
});
|
|
8917
|
+
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
8864
8918
|
var StaticDirOutputSchema$1 = object({ staticDir: string() });
|
|
8865
8919
|
var VersionOutputSchema$1 = object({ version: string() });
|
|
8866
8920
|
method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
|
|
8921
|
+
/**
|
|
8922
|
+
* The pipeline step-input shape and the run-result bridge, in a module that
|
|
8923
|
+
* imports nothing but zod and types.
|
|
8924
|
+
*
|
|
8925
|
+
* WHY A SEPARATE FILE (addon footprint, D28): both were declared in
|
|
8926
|
+
* `pipeline-executor.cap.ts`, whose import evaluates the whole executor schema
|
|
8927
|
+
* graph. An addon that only needs these two (the benchmark's `runPipelineTest`
|
|
8928
|
+
* action) paid ~130 MB of heap at load. `pipeline-executor.cap.ts` re-exports
|
|
8929
|
+
* them, so every existing import keeps working; addon server code takes them
|
|
8930
|
+
* from `@camstack/types/addon`.
|
|
8931
|
+
*/
|
|
8932
|
+
var PipelineStepInputSchema = lazy(() => object({
|
|
8933
|
+
addonId: string(),
|
|
8934
|
+
modelId: string().optional(),
|
|
8935
|
+
enabled: boolean().default(true),
|
|
8936
|
+
children: array(PipelineStepInputSchema).optional(),
|
|
8937
|
+
settings: record(string(), unknown()).optional(),
|
|
8938
|
+
jumpDeviceKey: string().optional()
|
|
8939
|
+
}));
|
|
8940
|
+
/** Typed bridge to the recursive `FrameResult` graph; validation is structural. */
|
|
8941
|
+
var PipelineRunResultBridge = custom();
|
|
8867
8942
|
var StaticDirOutputSchema = object({ staticDir: string() });
|
|
8868
8943
|
var VersionOutputSchema = object({ version: string() });
|
|
8869
8944
|
method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
|
|
@@ -9019,6 +9094,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9019
9094
|
"image-settings",
|
|
9020
9095
|
"integrations",
|
|
9021
9096
|
"intercom",
|
|
9097
|
+
"ir-transmitter",
|
|
9022
9098
|
"keychain",
|
|
9023
9099
|
"keychain-admin",
|
|
9024
9100
|
"lawn-mower-control",
|
|
@@ -9047,6 +9123,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9047
9123
|
"network-link",
|
|
9048
9124
|
"network-quality",
|
|
9049
9125
|
"node-certificates",
|
|
9126
|
+
"node-removal-hook",
|
|
9050
9127
|
"nodes",
|
|
9051
9128
|
"notification-output",
|
|
9052
9129
|
"notification-rules",
|
|
@@ -9057,6 +9134,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9057
9134
|
"osd",
|
|
9058
9135
|
"osd-manager",
|
|
9059
9136
|
"pet-feeder",
|
|
9137
|
+
"pet-fountain",
|
|
9060
9138
|
"pipeline-analytics",
|
|
9061
9139
|
"pipeline-executor",
|
|
9062
9140
|
"pipeline-orchestrator",
|
|
@@ -9077,6 +9155,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9077
9155
|
"recording-onboard",
|
|
9078
9156
|
"recording-signal",
|
|
9079
9157
|
"records",
|
|
9158
|
+
"remote",
|
|
9080
9159
|
"running-state",
|
|
9081
9160
|
"scene-monitor",
|
|
9082
9161
|
"script-runner",
|
|
@@ -9100,6 +9179,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9100
9179
|
"system",
|
|
9101
9180
|
"tamper",
|
|
9102
9181
|
"temperature-sensor",
|
|
9182
|
+
"template",
|
|
9103
9183
|
"terminal-session",
|
|
9104
9184
|
"toast",
|
|
9105
9185
|
"training",
|
|
@@ -10040,6 +10120,11 @@ Object.freeze({
|
|
|
10040
10120
|
"standby"
|
|
10041
10121
|
]
|
|
10042
10122
|
},
|
|
10123
|
+
{
|
|
10124
|
+
"path": "stateSource",
|
|
10125
|
+
"valueType": "enum",
|
|
10126
|
+
"enumValues": ["reported", "assumed"]
|
|
10127
|
+
},
|
|
10043
10128
|
{
|
|
10044
10129
|
"path": "volumeLevel",
|
|
10045
10130
|
"valueType": "number"
|
|
@@ -10266,6 +10351,11 @@ Object.freeze({
|
|
|
10266
10351
|
"path": "batteryPower",
|
|
10267
10352
|
"valueType": "number"
|
|
10268
10353
|
},
|
|
10354
|
+
{
|
|
10355
|
+
"path": "powerSource",
|
|
10356
|
+
"valueType": "enum",
|
|
10357
|
+
"enumValues": ["mains", "battery"]
|
|
10358
|
+
},
|
|
10269
10359
|
{
|
|
10270
10360
|
"path": "desiccantLeftDays",
|
|
10271
10361
|
"valueType": "number"
|
|
@@ -10310,6 +10400,68 @@ Object.freeze({
|
|
|
10310
10400
|
{
|
|
10311
10401
|
"path": "volume",
|
|
10312
10402
|
"valueType": "number"
|
|
10403
|
+
},
|
|
10404
|
+
{
|
|
10405
|
+
"path": "lastFeedGrams",
|
|
10406
|
+
"valueType": "number"
|
|
10407
|
+
},
|
|
10408
|
+
{
|
|
10409
|
+
"path": "lastFeedHopper1",
|
|
10410
|
+
"valueType": "number"
|
|
10411
|
+
},
|
|
10412
|
+
{
|
|
10413
|
+
"path": "lastFeedHopper2",
|
|
10414
|
+
"valueType": "number"
|
|
10415
|
+
}
|
|
10416
|
+
],
|
|
10417
|
+
"pet-fountain": [
|
|
10418
|
+
{
|
|
10419
|
+
"path": "waterLevel",
|
|
10420
|
+
"valueType": "number"
|
|
10421
|
+
},
|
|
10422
|
+
{
|
|
10423
|
+
"path": "waterVolume",
|
|
10424
|
+
"valueType": "number"
|
|
10425
|
+
},
|
|
10426
|
+
{
|
|
10427
|
+
"path": "empty",
|
|
10428
|
+
"valueType": "boolean"
|
|
10429
|
+
},
|
|
10430
|
+
{
|
|
10431
|
+
"path": "pumpRunning",
|
|
10432
|
+
"valueType": "boolean"
|
|
10433
|
+
},
|
|
10434
|
+
{
|
|
10435
|
+
"path": "pumpIntervalEnabled",
|
|
10436
|
+
"valueType": "boolean"
|
|
10437
|
+
},
|
|
10438
|
+
{
|
|
10439
|
+
"path": "pumpOnTime",
|
|
10440
|
+
"valueType": "number"
|
|
10441
|
+
},
|
|
10442
|
+
{
|
|
10443
|
+
"path": "pumpOffTime",
|
|
10444
|
+
"valueType": "number"
|
|
10445
|
+
},
|
|
10446
|
+
{
|
|
10447
|
+
"path": "lastDrink",
|
|
10448
|
+
"valueType": "number"
|
|
10449
|
+
},
|
|
10450
|
+
{
|
|
10451
|
+
"path": "currentHourDrink",
|
|
10452
|
+
"valueType": "number"
|
|
10453
|
+
},
|
|
10454
|
+
{
|
|
10455
|
+
"path": "last24hDrink",
|
|
10456
|
+
"valueType": "number"
|
|
10457
|
+
},
|
|
10458
|
+
{
|
|
10459
|
+
"path": "todayDrink",
|
|
10460
|
+
"valueType": "number"
|
|
10461
|
+
},
|
|
10462
|
+
{
|
|
10463
|
+
"path": "error",
|
|
10464
|
+
"valueType": "string"
|
|
10313
10465
|
}
|
|
10314
10466
|
],
|
|
10315
10467
|
"power-meter": [
|
|
@@ -10849,7 +11001,7 @@ object({
|
|
|
10849
11001
|
params: record(string(), union([string(), number()])).optional()
|
|
10850
11002
|
});
|
|
10851
11003
|
//#endregion
|
|
10852
|
-
//#region ../types/dist/err-msg-
|
|
11004
|
+
//#region ../types/dist/err-msg-DWXWc0Di.mjs
|
|
10853
11005
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
10854
11006
|
DeviceType["Camera"] = "camera";
|
|
10855
11007
|
DeviceType["Hub"] = "hub";
|
|
@@ -10941,6 +11093,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
10941
11093
|
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
10942
11094
|
* integrations sharing the same food/desiccant/hopper surface. */
|
|
10943
11095
|
DeviceType["PetFeeder"] = "pet-feeder";
|
|
11096
|
+
/** Pet water fountain — tank level, water drunk, pump and its duty cycle.
|
|
11097
|
+
* Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
|
|
11098
|
+
* of a container and is BUILT as a composed device (D967). */
|
|
11099
|
+
DeviceType["PetFountain"] = "pet-fountain";
|
|
10944
11100
|
/** Built-in dishwasher — program lifecycle, door / remote-start gates,
|
|
10945
11101
|
* salt and rinse-aid levels. Installed with the `dishwasher` cap.
|
|
10946
11102
|
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
@@ -11195,6 +11351,9 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
|
11195
11351
|
/** Fallback when the notifier service name doesn't match a known
|
|
11196
11352
|
* pattern. */
|
|
11197
11353
|
DeviceRole["GenericNotifier"] = "generic-notifier";
|
|
11354
|
+
/** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
|
|
11355
|
+
* cap. A `DeviceType.Generic` device. */
|
|
11356
|
+
DeviceRole["IrTransmitter"] = "ir-transmitter";
|
|
11198
11357
|
return DeviceRole;
|
|
11199
11358
|
}({});
|
|
11200
11359
|
/**
|
|
@@ -11208,7 +11367,7 @@ function errMsg(err) {
|
|
|
11208
11367
|
return String(err);
|
|
11209
11368
|
}
|
|
11210
11369
|
//#endregion
|
|
11211
|
-
//#region ../types/dist/composition-
|
|
11370
|
+
//#region ../types/dist/composition-kSuOifuG.mjs
|
|
11212
11371
|
/**
|
|
11213
11372
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11214
11373
|
*
|
|
@@ -11256,11 +11415,11 @@ function errMsg(err) {
|
|
|
11256
11415
|
*/
|
|
11257
11416
|
/** The pinned jellyfin-ffmpeg release. Changing this is the version change. */
|
|
11258
11417
|
var FFMPEG_RELEASE = "8.1.2-5";
|
|
11259
|
-
var BASE = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
|
|
11418
|
+
var BASE$1 = `https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v${FFMPEG_RELEASE}`;
|
|
11260
11419
|
function portable(target) {
|
|
11261
|
-
return `${BASE}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
|
|
11420
|
+
return `${BASE$1}/jellyfin-ffmpeg_${FFMPEG_RELEASE}_portable_${target}-gpl.tar.xz`;
|
|
11262
11421
|
}
|
|
11263
|
-
portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64"), `${BASE}${FFMPEG_RELEASE}`;
|
|
11422
|
+
portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac64"), `${BASE$1}${FFMPEG_RELEASE}`;
|
|
11264
11423
|
/**
|
|
11265
11424
|
* THE table "which boolean of this device type counts as HIGH", and the
|
|
11266
11425
|
* pure evaluator of its EDGE.
|
|
@@ -12293,12 +12452,28 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
|
|
|
12293
12452
|
])
|
|
12294
12453
|
})
|
|
12295
12454
|
]);
|
|
12296
|
-
var
|
|
12455
|
+
var forwardArgUnion = union([
|
|
12456
|
+
string(),
|
|
12457
|
+
number(),
|
|
12458
|
+
boolean(),
|
|
12459
|
+
strictObject({ ["$input"]: string().min(1) }),
|
|
12460
|
+
record(string().min(1), lazy(() => forwardArgUnion))
|
|
12461
|
+
]);
|
|
12462
|
+
var CompositionForwardArgSchema = forwardArgUnion;
|
|
12463
|
+
/**
|
|
12464
|
+
* `forward` (D659, amended by D967): one method of a BUILT capability is one
|
|
12465
|
+
* call of `cap.method` on `source`. Without `args` the built method's input
|
|
12466
|
+
* passes through minus `deviceId`; with `args` the template IS the source's
|
|
12467
|
+
* whole input. `deviceId` is always the source's own, never an argument.
|
|
12468
|
+
*/
|
|
12469
|
+
var CompositionForwardCommandSchema = object({
|
|
12297
12470
|
kind: literal("forward"),
|
|
12298
12471
|
source: CompositionSourceRefSchema,
|
|
12299
12472
|
cap: string().min(1),
|
|
12300
|
-
method: string().min(1)
|
|
12301
|
-
|
|
12473
|
+
method: string().min(1),
|
|
12474
|
+
args: record(string().min(1), CompositionForwardArgSchema).optional()
|
|
12475
|
+
});
|
|
12476
|
+
var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
|
|
12302
12477
|
/** An item key inside an item-array cap (`consumables.items[<key>]`). */
|
|
12303
12478
|
var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
12304
12479
|
/**
|
|
@@ -12308,7 +12483,13 @@ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
|
12308
12483
|
*/
|
|
12309
12484
|
var CompositionItemEntrySchema = object({
|
|
12310
12485
|
label: string().min(1).max(80),
|
|
12311
|
-
fields: record(string().min(1), CompositionFieldSourceSchema)
|
|
12486
|
+
fields: record(string().min(1), CompositionFieldSourceSchema),
|
|
12487
|
+
/**
|
|
12488
|
+
* What the cap's `reset` does for THIS item (consumables). Present = the item
|
|
12489
|
+
* is resettable and a `reset` call naming its key forwards here. The item key
|
|
12490
|
+
* selects the command, so it is never an argument: there is no pass-through.
|
|
12491
|
+
*/
|
|
12492
|
+
reset: CompositionForwardCommandSchema.optional()
|
|
12312
12493
|
});
|
|
12313
12494
|
var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
12314
12495
|
kind: literal("fields"),
|
|
@@ -12320,7 +12501,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
|
12320
12501
|
* owns the WHOLE array field: the composed value holds exactly these items.
|
|
12321
12502
|
*/
|
|
12322
12503
|
items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
|
|
12323
|
-
/**
|
|
12504
|
+
/** Method name → what serves it. Only a BUILT capability takes commands (D967). */
|
|
12324
12505
|
commands: record(string().min(1), CompositionCommandTargetSchema).optional()
|
|
12325
12506
|
}), object({
|
|
12326
12507
|
kind: literal("passthrough"),
|
|
@@ -16377,6 +16558,449 @@ object({
|
|
|
16377
16558
|
lastError: string().nullable()
|
|
16378
16559
|
});
|
|
16379
16560
|
/**
|
|
16561
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
16562
|
+
*
|
|
16563
|
+
* Apprise-derived model (see
|
|
16564
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
16565
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
16566
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
16567
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
16568
|
+
* message to what the kind supports — callers never special-case a service.
|
|
16569
|
+
*
|
|
16570
|
+
* DESIGN DECISIONS (locked):
|
|
16571
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
16572
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
16573
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
16574
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
16575
|
+
* alternative would fork the UI per addon and cannot host the
|
|
16576
|
+
* discovery→adopt flow.
|
|
16577
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
16578
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
16579
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
16580
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
16581
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
16582
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
16583
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
16584
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
16585
|
+
* base64 fallback needed.
|
|
16586
|
+
*
|
|
16587
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
16588
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
16589
|
+
* admin "Integrations" page.
|
|
16590
|
+
*/
|
|
16591
|
+
/**
|
|
16592
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
16593
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
16594
|
+
*/
|
|
16595
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
16596
|
+
"image",
|
|
16597
|
+
"video",
|
|
16598
|
+
"gif",
|
|
16599
|
+
"audio",
|
|
16600
|
+
"icon"
|
|
16601
|
+
]);
|
|
16602
|
+
/**
|
|
16603
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
16604
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
16605
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
16606
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
16607
|
+
*/
|
|
16608
|
+
var AttachmentSchema = object({
|
|
16609
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
16610
|
+
url: string().optional(),
|
|
16611
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
16612
|
+
mime: string().optional(),
|
|
16613
|
+
name: string().optional()
|
|
16614
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
16615
|
+
var NotificationFormatSchema = _enum([
|
|
16616
|
+
"text",
|
|
16617
|
+
"markdown",
|
|
16618
|
+
"html"
|
|
16619
|
+
]);
|
|
16620
|
+
/**
|
|
16621
|
+
* The CLOSED icon vocabulary an action button may use.
|
|
16622
|
+
*
|
|
16623
|
+
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
16624
|
+
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
16625
|
+
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
16626
|
+
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
16627
|
+
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
16628
|
+
*
|
|
16629
|
+
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
16630
|
+
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
16631
|
+
* differently.
|
|
16632
|
+
*
|
|
16633
|
+
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
16634
|
+
*
|
|
16635
|
+
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
16636
|
+
* the whole set onto its own renderer's vocabulary through a
|
|
16637
|
+
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
16638
|
+
* fallback, so adding a member here fails every adapter's build until someone
|
|
16639
|
+
* decides its glyph, which is the only place that decision can be made
|
|
16640
|
+
* honestly.
|
|
16641
|
+
*
|
|
16642
|
+
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
16643
|
+
* `disarm` straight through; iOS feeds that string to
|
|
16644
|
+
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
16645
|
+
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
16646
|
+
* mapping, and "the field is documented" is not "the value renders".
|
|
16647
|
+
*
|
|
16648
|
+
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
16649
|
+
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
16650
|
+
* so an addon that emits a token the running hub does not know does not lose an
|
|
16651
|
+
* icon — its whole `send` fails Zod validation and the notification never
|
|
16652
|
+
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
16653
|
+
*/
|
|
16654
|
+
var NotificationActionIconSchema = _enum([
|
|
16655
|
+
"acknowledge",
|
|
16656
|
+
"dismiss",
|
|
16657
|
+
"silence",
|
|
16658
|
+
"snooze",
|
|
16659
|
+
"view",
|
|
16660
|
+
"play",
|
|
16661
|
+
"open",
|
|
16662
|
+
"close",
|
|
16663
|
+
"lock",
|
|
16664
|
+
"unlock",
|
|
16665
|
+
"arm",
|
|
16666
|
+
"arm-home",
|
|
16667
|
+
"arm-away",
|
|
16668
|
+
"arm-night",
|
|
16669
|
+
"disarm",
|
|
16670
|
+
"light",
|
|
16671
|
+
"alert",
|
|
16672
|
+
"camera"
|
|
16673
|
+
]);
|
|
16674
|
+
/** A single tap-through action button. */
|
|
16675
|
+
var NotificationActionSchema = object({
|
|
16676
|
+
id: string(),
|
|
16677
|
+
label: string(),
|
|
16678
|
+
url: string().optional(),
|
|
16679
|
+
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
16680
|
+
icon: NotificationActionIconSchema.optional(),
|
|
16681
|
+
/**
|
|
16682
|
+
* Renders in a warning style where the notifier supports it.
|
|
16683
|
+
*
|
|
16684
|
+
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
16685
|
+
* else — see `notification-center/action-token.ts` for what that does and
|
|
16686
|
+
* does not buy.
|
|
16687
|
+
*/
|
|
16688
|
+
destructive: boolean().optional(),
|
|
16689
|
+
/**
|
|
16690
|
+
* How the tap should REACH the url.
|
|
16691
|
+
*
|
|
16692
|
+
* `navigate` (absent, and every button authored before this field) opens it:
|
|
16693
|
+
* the phone leaves the notification and shows whatever the callback returns.
|
|
16694
|
+
* That is right for a button whose answer the operator wants to read.
|
|
16695
|
+
*
|
|
16696
|
+
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
16697
|
+
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
16698
|
+
* an answer to the notification, and being thrown into a browser tab to
|
|
16699
|
+
* confirm it costs more attention than the notification did. A backend that
|
|
16700
|
+
* cannot do a background call renders it as an ordinary link (the adapters
|
|
16701
|
+
* fall back rather than dropping the button), so this is a preference, never
|
|
16702
|
+
* a requirement.
|
|
16703
|
+
*/
|
|
16704
|
+
mode: _enum(["navigate", "background"]).optional()
|
|
16705
|
+
});
|
|
16706
|
+
/** A delivery parameter value: what a notifier declared it takes. */
|
|
16707
|
+
var DeliveryParamValueSchema = union([
|
|
16708
|
+
string(),
|
|
16709
|
+
number(),
|
|
16710
|
+
boolean()
|
|
16711
|
+
]);
|
|
16712
|
+
/**
|
|
16713
|
+
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
16714
|
+
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
16715
|
+
* never handed one. The admin UI derives its control from this descriptor —
|
|
16716
|
+
* no editor knows a service by name.
|
|
16717
|
+
*
|
|
16718
|
+
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
16719
|
+
* run on the addon cap path).
|
|
16720
|
+
*/
|
|
16721
|
+
var DeliveryParamSchema = object({
|
|
16722
|
+
key: string().min(1),
|
|
16723
|
+
label: string(),
|
|
16724
|
+
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
16725
|
+
labelKey: string().optional(),
|
|
16726
|
+
type: _enum([
|
|
16727
|
+
"enum",
|
|
16728
|
+
"number",
|
|
16729
|
+
"boolean",
|
|
16730
|
+
"string"
|
|
16731
|
+
]),
|
|
16732
|
+
/** `enum` only: the closed set of accepted values. */
|
|
16733
|
+
options: array(object({
|
|
16734
|
+
value: string(),
|
|
16735
|
+
label: string()
|
|
16736
|
+
})).optional(),
|
|
16737
|
+
/** `number` only. */
|
|
16738
|
+
min: number().optional(),
|
|
16739
|
+
max: number().optional(),
|
|
16740
|
+
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
16741
|
+
default: DeliveryParamValueSchema.optional()
|
|
16742
|
+
});
|
|
16743
|
+
/**
|
|
16744
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
16745
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
16746
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
16747
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
16748
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
16749
|
+
* `priority` for that one target.
|
|
16750
|
+
*/
|
|
16751
|
+
var NotificationSchema = object({
|
|
16752
|
+
body: string(),
|
|
16753
|
+
title: string().optional(),
|
|
16754
|
+
format: NotificationFormatSchema.default("text"),
|
|
16755
|
+
priority: number().int().min(1).max(5).default(3),
|
|
16756
|
+
level: string().optional(),
|
|
16757
|
+
attachments: array(AttachmentSchema).optional(),
|
|
16758
|
+
clickUrl: string().optional(),
|
|
16759
|
+
actions: array(NotificationActionSchema).optional(),
|
|
16760
|
+
sound: string().optional(),
|
|
16761
|
+
ttl: number().optional(),
|
|
16762
|
+
tag: string().optional(),
|
|
16763
|
+
deviceId: number().optional(),
|
|
16764
|
+
eventId: string().optional(),
|
|
16765
|
+
metadata: record(string(), unknown()).optional(),
|
|
16766
|
+
/**
|
|
16767
|
+
* Values for the parameters the target's kind DECLARES in
|
|
16768
|
+
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
16769
|
+
* declaration by the sender; a notifier reads only the keys it declared.
|
|
16770
|
+
*/
|
|
16771
|
+
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
16772
|
+
});
|
|
16773
|
+
/** One declared native severity/priority level for a kind. */
|
|
16774
|
+
var TargetKindLevelSchema = object({
|
|
16775
|
+
id: string(),
|
|
16776
|
+
label: string(),
|
|
16777
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
16778
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
16779
|
+
flags: object({
|
|
16780
|
+
critical: boolean().optional(),
|
|
16781
|
+
silent: boolean().optional(),
|
|
16782
|
+
noPush: boolean().optional()
|
|
16783
|
+
}).optional(),
|
|
16784
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
16785
|
+
requires: array(string()).optional(),
|
|
16786
|
+
description: string().optional()
|
|
16787
|
+
});
|
|
16788
|
+
/** The full capability block consulted before dispatch. */
|
|
16789
|
+
var TargetKindCapsSchema = object({
|
|
16790
|
+
attachments: object({
|
|
16791
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
16792
|
+
mode: _enum([
|
|
16793
|
+
"url",
|
|
16794
|
+
"bytes",
|
|
16795
|
+
"both"
|
|
16796
|
+
]),
|
|
16797
|
+
max: number().int().nonnegative(),
|
|
16798
|
+
maxBytes: number().int().positive().optional()
|
|
16799
|
+
}),
|
|
16800
|
+
/** Max action buttons (0 = none). */
|
|
16801
|
+
actions: number().int().nonnegative(),
|
|
16802
|
+
/**
|
|
16803
|
+
* Whether this kind renders a per-action ICON.
|
|
16804
|
+
*
|
|
16805
|
+
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
16806
|
+
* run on the addon cap path — three production failures in one day taught
|
|
16807
|
+
* this repo that once. Absent is read as false by the degrade engine, which
|
|
16808
|
+
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
16809
|
+
* assumed and dropped costs the operator's trust in the field.
|
|
16810
|
+
*/
|
|
16811
|
+
actionIcons: boolean().optional(),
|
|
16812
|
+
levels: array(TargetKindLevelSchema),
|
|
16813
|
+
format: array(NotificationFormatSchema),
|
|
16814
|
+
clickUrl: boolean(),
|
|
16815
|
+
sound: boolean(),
|
|
16816
|
+
ttl: boolean(),
|
|
16817
|
+
bodyMaxLen: number().int().positive()
|
|
16818
|
+
});
|
|
16819
|
+
/**
|
|
16820
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
16821
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
16822
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
16823
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
16824
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
16825
|
+
*/
|
|
16826
|
+
var ConfigSchemaPassthrough$3 = unknown();
|
|
16827
|
+
var TargetKindSchema = object({
|
|
16828
|
+
kind: string(),
|
|
16829
|
+
label: string(),
|
|
16830
|
+
icon: string(),
|
|
16831
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
16832
|
+
addonId: string(),
|
|
16833
|
+
/**
|
|
16834
|
+
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
16835
|
+
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
16836
|
+
* when the addon bundles no icon for that kind — the client then falls back
|
|
16837
|
+
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
16838
|
+
*
|
|
16839
|
+
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
16840
|
+
* client, and a native client joins it onto its own hub base.
|
|
16841
|
+
*
|
|
16842
|
+
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
16843
|
+
* field that survived only because the runtime cap-router forwards provider
|
|
16844
|
+
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
16845
|
+
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
16846
|
+
* broken silently the moment output validation was tightened anywhere.
|
|
16847
|
+
*/
|
|
16848
|
+
iconUrl: string().optional(),
|
|
16849
|
+
/**
|
|
16850
|
+
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
16851
|
+
*
|
|
16852
|
+
* The server knows this and therefore says it, because the client cannot
|
|
16853
|
+
* safely guess: a React-Native client renders SVG and raster through two
|
|
16854
|
+
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
16855
|
+
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
16856
|
+
* placeholder glyph for every vector icon while the web build looked fine.
|
|
16857
|
+
*
|
|
16858
|
+
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
16859
|
+
* not been updated — a client that cannot determine the type should prefer
|
|
16860
|
+
* its raster path, which is the safe default for an unknown image.
|
|
16861
|
+
*/
|
|
16862
|
+
iconMediaType: string().optional(),
|
|
16863
|
+
configSchema: ConfigSchemaPassthrough$3,
|
|
16864
|
+
/**
|
|
16865
|
+
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
16866
|
+
* base URL, instance, chat, recipient). A save that changes any of them
|
|
16867
|
+
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
16868
|
+
* editor uses this to ask for the secret on exactly those edits instead of
|
|
16869
|
+
* on every edit.
|
|
16870
|
+
*
|
|
16871
|
+
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
16872
|
+
* kinds) and from a provider older than this field — an editor that finds
|
|
16873
|
+
* it absent must assume ANY edit may need the secret.
|
|
16874
|
+
*/
|
|
16875
|
+
destinationKeys: array(string()).optional(),
|
|
16876
|
+
supportsDiscovery: boolean(),
|
|
16877
|
+
/**
|
|
16878
|
+
* Extra delivery parameters a rule may set for a target of this kind (D938).
|
|
16879
|
+
* Absent = none; the rule editor then offers nothing.
|
|
16880
|
+
*/
|
|
16881
|
+
deliveryParams: array(DeliveryParamSchema).optional(),
|
|
16882
|
+
caps: TargetKindCapsSchema
|
|
16883
|
+
});
|
|
16884
|
+
/**
|
|
16885
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
16886
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
16887
|
+
* round-trip a stored secret to the UI.
|
|
16888
|
+
*/
|
|
16889
|
+
var TargetSchema = object({
|
|
16890
|
+
id: string(),
|
|
16891
|
+
name: string(),
|
|
16892
|
+
kind: string(),
|
|
16893
|
+
addonId: string(),
|
|
16894
|
+
enabled: boolean(),
|
|
16895
|
+
config: record(string(), unknown())
|
|
16896
|
+
});
|
|
16897
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
16898
|
+
var DiscoveredTargetSchema = object({
|
|
16899
|
+
kind: string(),
|
|
16900
|
+
suggestedName: string(),
|
|
16901
|
+
config: record(string(), unknown())
|
|
16902
|
+
});
|
|
16903
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
16904
|
+
var RenderedAsSchema = object({
|
|
16905
|
+
level: string(),
|
|
16906
|
+
format: NotificationFormatSchema,
|
|
16907
|
+
attachmentsSent: number().int().nonnegative(),
|
|
16908
|
+
actionsSent: number().int().nonnegative(),
|
|
16909
|
+
truncated: boolean(),
|
|
16910
|
+
dropped: array(string())
|
|
16911
|
+
});
|
|
16912
|
+
var SendResultSchema = object({
|
|
16913
|
+
success: boolean(),
|
|
16914
|
+
error: string().optional(),
|
|
16915
|
+
renderedAs: RenderedAsSchema.optional()
|
|
16916
|
+
});
|
|
16917
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
16918
|
+
var TestResultSchema = SendResultSchema;
|
|
16919
|
+
/**
|
|
16920
|
+
* Pre-save credential probe against a partial (unsaved) target config.
|
|
16921
|
+
* `resolvedUrl` never includes secrets (token stripped from path).
|
|
16922
|
+
*/
|
|
16923
|
+
var ProbeTargetConfigResultSchema = object({
|
|
16924
|
+
ok: boolean(),
|
|
16925
|
+
resolvedUrl: string(),
|
|
16926
|
+
httpStatus: number().int().optional(),
|
|
16927
|
+
stateInstance: string().optional(),
|
|
16928
|
+
error: string().optional()
|
|
16929
|
+
});
|
|
16930
|
+
var notificationOutputCapability = {
|
|
16931
|
+
name: "notification-output",
|
|
16932
|
+
scope: "system",
|
|
16933
|
+
mode: "collection",
|
|
16934
|
+
methods: {
|
|
16935
|
+
listTargetKinds: method(object({}), array(TargetKindSchema)),
|
|
16936
|
+
listTargets: method(object({}), array(TargetSchema), { auth: "admin" }),
|
|
16937
|
+
/**
|
|
16938
|
+
* One target WITH secrets — admin edit / duplicate hydration only.
|
|
16939
|
+
* `listTargets` stays redacted; the editor loads this so password fields
|
|
16940
|
+
* are filled (masked in the input) and a save can round-trip the token
|
|
16941
|
+
* when a destination changes (Duplicate with a new chatId, etc.).
|
|
16942
|
+
*/
|
|
16943
|
+
getTarget: method(object({ targetId: string() }), TargetSchema, { auth: "admin" }),
|
|
16944
|
+
discoverTargets: method(object({
|
|
16945
|
+
kind: string(),
|
|
16946
|
+
config: record(string(), unknown()).optional(),
|
|
16947
|
+
/**
|
|
16948
|
+
* When editing / duplicating, the blank password field never round-trips
|
|
16949
|
+
* the stored token. Pass the source target id so the provider can restore
|
|
16950
|
+
* secrets server-side before dialling (same merge as upsert).
|
|
16951
|
+
*/
|
|
16952
|
+
targetId: string().optional()
|
|
16953
|
+
}), array(DiscoveredTargetSchema), { auth: "admin" }),
|
|
16954
|
+
probeTargetConfig: method(object({
|
|
16955
|
+
kind: string(),
|
|
16956
|
+
config: record(string(), unknown()),
|
|
16957
|
+
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
16958
|
+
targetId: string().optional()
|
|
16959
|
+
}), ProbeTargetConfigResultSchema, {
|
|
16960
|
+
kind: "mutation",
|
|
16961
|
+
auth: "admin"
|
|
16962
|
+
}),
|
|
16963
|
+
send: method(object({
|
|
16964
|
+
targetId: string(),
|
|
16965
|
+
notification: NotificationSchema
|
|
16966
|
+
}), SendResultSchema, {
|
|
16967
|
+
kind: "mutation",
|
|
16968
|
+
auth: "admin"
|
|
16969
|
+
}),
|
|
16970
|
+
testTarget: method(object({
|
|
16971
|
+
targetId: string(),
|
|
16972
|
+
sample: NotificationSchema.optional()
|
|
16973
|
+
}), TestResultSchema, {
|
|
16974
|
+
kind: "mutation",
|
|
16975
|
+
auth: "admin"
|
|
16976
|
+
}),
|
|
16977
|
+
upsertTarget: method(object({
|
|
16978
|
+
target: TargetSchema,
|
|
16979
|
+
/**
|
|
16980
|
+
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
16981
|
+
* existing row. The UI never holds the stored token; blank / redacted
|
|
16982
|
+
* password fields are restored from this source (D634 still applies if
|
|
16983
|
+
* destinations diverge without a re-typed secret).
|
|
16984
|
+
*/
|
|
16985
|
+
cloneSecretsFrom: string().optional()
|
|
16986
|
+
}), TargetSchema, {
|
|
16987
|
+
kind: "mutation",
|
|
16988
|
+
auth: "admin"
|
|
16989
|
+
}),
|
|
16990
|
+
deleteTarget: method(object({ targetId: string() }), _void(), {
|
|
16991
|
+
kind: "mutation",
|
|
16992
|
+
auth: "admin"
|
|
16993
|
+
}),
|
|
16994
|
+
setTargetEnabled: method(object({
|
|
16995
|
+
targetId: string(),
|
|
16996
|
+
enabled: boolean()
|
|
16997
|
+
}), _void(), {
|
|
16998
|
+
kind: "mutation",
|
|
16999
|
+
auth: "admin"
|
|
17000
|
+
})
|
|
17001
|
+
}
|
|
17002
|
+
};
|
|
17003
|
+
/**
|
|
16380
17004
|
* `addon-pages` — system-scoped singleton aggregator cap. Public-facing
|
|
16381
17005
|
* surface that admin-ui consumes through `useAddonPagesListPages()`.
|
|
16382
17006
|
*
|
|
@@ -16408,52 +17032,6 @@ var AddonPageInfoSchema = object({
|
|
|
16408
17032
|
bundleUrl: string()
|
|
16409
17033
|
});
|
|
16410
17034
|
method(_void(), array(AddonPageInfoSchema).readonly());
|
|
16411
|
-
/**
|
|
16412
|
-
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
16413
|
-
* declarations. Every addon that contributes a UI page registers a
|
|
16414
|
-
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
16415
|
-
* see `addon-pages.cap.ts`) walks this collection, stamps versioned
|
|
16416
|
-
* `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
|
|
16417
|
-
* that admin-ui consumes.
|
|
16418
|
-
*
|
|
16419
|
-
* The split exists because the public listing has a different output
|
|
16420
|
-
* shape than the per-provider raw declarations, and we want both ends
|
|
16421
|
-
* to flow through codegen instead of relying on a hand-written wrapper.
|
|
16422
|
-
*/
|
|
16423
|
-
var AddonPageDeclarationSchema = object({
|
|
16424
|
-
id: string(),
|
|
16425
|
-
label: string(),
|
|
16426
|
-
icon: string(),
|
|
16427
|
-
path: string(),
|
|
16428
|
-
/**
|
|
16429
|
-
* Module Federation remote name — must match the `name` field on the
|
|
16430
|
-
* page addon's `federation()` plugin config. Used by admin-ui's
|
|
16431
|
-
* `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
|
|
16432
|
-
* Conventionally `addon_<id>_page` (snake_case; MF names cannot
|
|
16433
|
-
* contain hyphens).
|
|
16434
|
-
*/
|
|
16435
|
-
remoteName: string(),
|
|
16436
|
-
/**
|
|
16437
|
-
* Bundle filename inside the addon's `dist/` dir served at
|
|
16438
|
-
* `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
|
|
16439
|
-
* is always `'remoteEntry.js'`; the value is kept on the metadata so
|
|
16440
|
-
* the static-file route can compute an mtime-based cache-buster URL
|
|
16441
|
-
* without a separate filesystem stat.
|
|
16442
|
-
*/
|
|
16443
|
-
bundle: string(),
|
|
16444
|
-
/**
|
|
16445
|
-
* Sidebar section this page docks into. Well-known ids: `'detection'`,
|
|
16446
|
-
* `'cluster'`, `'administration'` — the page renders inside that group.
|
|
16447
|
-
* Any OTHER string creates (or joins) a custom section rendered after
|
|
16448
|
-
* the built-in groups; its label comes from `sectionLabel` (first
|
|
16449
|
-
* declaration wins), falling back to the id. Absent → the legacy
|
|
16450
|
-
* "Addon Pages" group.
|
|
16451
|
-
*/
|
|
16452
|
-
section: string().optional(),
|
|
16453
|
-
/** Display label for a CUSTOM `section` id (ignored for well-known ids). */
|
|
16454
|
-
sectionLabel: string().optional()
|
|
16455
|
-
});
|
|
16456
|
-
method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
|
|
16457
17035
|
var AddonHttpRouteSchema = object({
|
|
16458
17036
|
method: _enum([
|
|
16459
17037
|
"GET",
|
|
@@ -16754,9 +17332,21 @@ var AlertSeveritySchema = _enum([
|
|
|
16754
17332
|
"error",
|
|
16755
17333
|
"critical"
|
|
16756
17334
|
]);
|
|
17335
|
+
/**
|
|
17336
|
+
* An action that RUNS something instead of going somewhere: the admin UI calls
|
|
17337
|
+
* the addon's custom action (`addons.custom`) with exactly these fields. The
|
|
17338
|
+
* producer owns the meaning; the UI knows nothing about the addon or the kind.
|
|
17339
|
+
*/
|
|
17340
|
+
var AlertActionInvokeSchema = object({
|
|
17341
|
+
addonId: string().min(1),
|
|
17342
|
+
action: string().min(1),
|
|
17343
|
+
input: record(string(), unknown()).optional()
|
|
17344
|
+
});
|
|
17345
|
+
/** An action has `href` (a link) OR `invoke` (a button that runs a custom action). */
|
|
16757
17346
|
var AlertActionSchema = object({
|
|
16758
17347
|
label: string(),
|
|
16759
17348
|
href: string().optional(),
|
|
17349
|
+
invoke: AlertActionInvokeSchema.optional(),
|
|
16760
17350
|
/**
|
|
16761
17351
|
* How `href` is followed. Absent / `app`: an admin-ui route (client-side).
|
|
16762
17352
|
* `server`: a route the HUB serves (an addon HTTP route such as an OAuth
|
|
@@ -19158,7 +19748,11 @@ var CompositionProblemSchema = object({
|
|
|
19158
19748
|
"mixed-quantity",
|
|
19159
19749
|
"helper-mismatch",
|
|
19160
19750
|
"member-constraint",
|
|
19161
|
-
"retention"
|
|
19751
|
+
"retention",
|
|
19752
|
+
"command-method-unknown",
|
|
19753
|
+
"command-source-unknown",
|
|
19754
|
+
"command-input-unknown",
|
|
19755
|
+
"command-input-missing"
|
|
19162
19756
|
]),
|
|
19163
19757
|
path: string(),
|
|
19164
19758
|
message: string()
|
|
@@ -19197,7 +19791,9 @@ var CompositionFieldRoleSchema = _enum([
|
|
|
19197
19791
|
var CompositionFieldItemSchema = object({
|
|
19198
19792
|
arrayPath: string(),
|
|
19199
19793
|
key: string(),
|
|
19200
|
-
label: string()
|
|
19794
|
+
label: string(),
|
|
19795
|
+
/** True when the item carries a `reset` forward: the composer writes `resettable` from this (D967). */
|
|
19796
|
+
resettable: boolean().optional()
|
|
19201
19797
|
});
|
|
19202
19798
|
var CompositionFieldPlanSchema = object({
|
|
19203
19799
|
cap: string(),
|
|
@@ -19402,7 +19998,7 @@ Object.freeze({
|
|
|
19402
19998
|
* One catalog row per base reading a group helper can build (D944 §3).
|
|
19403
19999
|
* Data, not a form default written into the block.
|
|
19404
20000
|
*/
|
|
19405
|
-
var NOW$
|
|
20001
|
+
var NOW$3 = {
|
|
19406
20002
|
fieldPath: "lastFetchedAt",
|
|
19407
20003
|
expr: "now"
|
|
19408
20004
|
};
|
|
@@ -19454,7 +20050,7 @@ var GROUP_TEMPLATES = [
|
|
|
19454
20050
|
quantity: "temperature",
|
|
19455
20051
|
defaultAggregate: "mean",
|
|
19456
20052
|
empty: "null",
|
|
19457
|
-
companions: [NOW$
|
|
20053
|
+
companions: [NOW$3],
|
|
19458
20054
|
alsoFrom: {
|
|
19459
20055
|
deviceTypes: CLIMATE_DEVICES,
|
|
19460
20056
|
accept: {
|
|
@@ -19472,7 +20068,7 @@ var GROUP_TEMPLATES = [
|
|
|
19472
20068
|
quantity: "humidity",
|
|
19473
20069
|
defaultAggregate: "mean",
|
|
19474
20070
|
empty: "null",
|
|
19475
|
-
companions: [NOW$
|
|
20071
|
+
companions: [NOW$3],
|
|
19476
20072
|
alsoFrom: {
|
|
19477
20073
|
deviceTypes: CLIMATE_DEVICES,
|
|
19478
20074
|
accept: {
|
|
@@ -19490,7 +20086,7 @@ var GROUP_TEMPLATES = [
|
|
|
19490
20086
|
quantity: "pressure",
|
|
19491
20087
|
defaultAggregate: "mean",
|
|
19492
20088
|
empty: "null",
|
|
19493
|
-
companions: [NOW$
|
|
20089
|
+
companions: [NOW$3]
|
|
19494
20090
|
}),
|
|
19495
20091
|
numeric({
|
|
19496
20092
|
id: "illuminance",
|
|
@@ -19501,7 +20097,7 @@ var GROUP_TEMPLATES = [
|
|
|
19501
20097
|
quantity: "illuminance",
|
|
19502
20098
|
defaultAggregate: "mean",
|
|
19503
20099
|
empty: "null",
|
|
19504
|
-
companions: [NOW$
|
|
20100
|
+
companions: [NOW$3]
|
|
19505
20101
|
}),
|
|
19506
20102
|
numeric({
|
|
19507
20103
|
id: "power",
|
|
@@ -19512,7 +20108,7 @@ var GROUP_TEMPLATES = [
|
|
|
19512
20108
|
quantity: "power",
|
|
19513
20109
|
defaultAggregate: "sum",
|
|
19514
20110
|
empty: "omit",
|
|
19515
|
-
companions: [NOW$
|
|
20111
|
+
companions: [NOW$3]
|
|
19516
20112
|
}),
|
|
19517
20113
|
numeric({
|
|
19518
20114
|
id: "energy",
|
|
@@ -19523,7 +20119,7 @@ var GROUP_TEMPLATES = [
|
|
|
19523
20119
|
quantity: "energy",
|
|
19524
20120
|
defaultAggregate: "sum",
|
|
19525
20121
|
empty: "omit",
|
|
19526
|
-
companions: [NOW$
|
|
20122
|
+
companions: [NOW$3]
|
|
19527
20123
|
}),
|
|
19528
20124
|
numeric({
|
|
19529
20125
|
id: "battery",
|
|
@@ -19558,7 +20154,7 @@ var GROUP_TEMPLATES = [
|
|
|
19558
20154
|
quantity: "device",
|
|
19559
20155
|
defaultAggregate: "mean",
|
|
19560
20156
|
empty: "null",
|
|
19561
|
-
companions: [NOW$
|
|
20157
|
+
companions: [NOW$3]
|
|
19562
20158
|
}),
|
|
19563
20159
|
binary({
|
|
19564
20160
|
id: "motion",
|
|
@@ -22032,12 +22628,48 @@ var ExposeInputSchema = object({
|
|
|
22032
22628
|
capabilities: array(string()).optional()
|
|
22033
22629
|
});
|
|
22034
22630
|
var UnexposeInputSchema = object({ deviceId: string() });
|
|
22631
|
+
/**
|
|
22632
|
+
* What an exporter may expose besides devices (D968). An action and a
|
|
22633
|
+
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
22634
|
+
* exporter's own selection, not the device registry.
|
|
22635
|
+
*/
|
|
22636
|
+
var ExportEntityKindSchema = _enum([
|
|
22637
|
+
"device",
|
|
22638
|
+
"action",
|
|
22639
|
+
"notifier"
|
|
22640
|
+
]);
|
|
22641
|
+
var ExportEntityRefSchema = discriminatedUnion("kind", [
|
|
22642
|
+
object({
|
|
22643
|
+
kind: literal("device"),
|
|
22644
|
+
deviceId: string()
|
|
22645
|
+
}),
|
|
22646
|
+
object({
|
|
22647
|
+
kind: literal("action"),
|
|
22648
|
+
actionId: string().min(1)
|
|
22649
|
+
}),
|
|
22650
|
+
object({
|
|
22651
|
+
kind: literal("notifier"),
|
|
22652
|
+
targetId: string().min(1)
|
|
22653
|
+
})
|
|
22654
|
+
]);
|
|
22655
|
+
var ExposedEntitySchema = object({
|
|
22656
|
+
entity: ExportEntityRefSchema,
|
|
22657
|
+
exposedAs: string().optional()
|
|
22658
|
+
});
|
|
22659
|
+
var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
22660
|
+
var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
|
|
22035
22661
|
method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
22036
22662
|
kind: "mutation",
|
|
22037
22663
|
auth: "admin"
|
|
22038
22664
|
}), method(UnexposeInputSchema, _void(), {
|
|
22039
22665
|
kind: "mutation",
|
|
22040
22666
|
auth: "admin"
|
|
22667
|
+
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
22668
|
+
kind: "mutation",
|
|
22669
|
+
auth: "admin"
|
|
22670
|
+
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
22671
|
+
kind: "mutation",
|
|
22672
|
+
auth: "admin"
|
|
22041
22673
|
});
|
|
22042
22674
|
/** Lucide category ids, alphabetical. `ICON_META` indexes into this. */
|
|
22043
22675
|
var ICON_CATEGORY_IDS = [
|
|
@@ -43259,6 +43891,14 @@ method(object({
|
|
|
43259
43891
|
kind: "mutation",
|
|
43260
43892
|
auth: "admin"
|
|
43261
43893
|
});
|
|
43894
|
+
method(object({
|
|
43895
|
+
nodeId: string().min(1),
|
|
43896
|
+
/** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
|
|
43897
|
+
removedAt: number()
|
|
43898
|
+
}), object({ counts: record(string(), number().int().nonnegative()) }), {
|
|
43899
|
+
kind: "mutation",
|
|
43900
|
+
auth: "admin"
|
|
43901
|
+
});
|
|
43262
43902
|
/**
|
|
43263
43903
|
* `device-removal-hook` — the reliable way an addon learns a device was DELETED,
|
|
43264
43904
|
* so it can delete what it keeps for that device (D865).
|
|
@@ -43979,7 +44619,7 @@ var LlmProviderWidgetSchema = object({
|
|
|
43979
44619
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
43980
44620
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
43981
44621
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
43982
|
-
var ConfigSchemaPassthrough$
|
|
44622
|
+
var ConfigSchemaPassthrough$2 = unknown();
|
|
43983
44623
|
var LlmProfileKindDescriptorSchema = object({
|
|
43984
44624
|
kind: LlmProfileKindSchema,
|
|
43985
44625
|
label: string(),
|
|
@@ -43988,9 +44628,9 @@ var LlmProfileKindDescriptorSchema = object({
|
|
|
43988
44628
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
43989
44629
|
addonId: string(),
|
|
43990
44630
|
/** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
|
|
43991
|
-
connectionSchema: ConfigSchemaPassthrough$
|
|
44631
|
+
connectionSchema: ConfigSchemaPassthrough$2,
|
|
43992
44632
|
/** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
|
|
43993
|
-
advancedSchema: ConfigSchemaPassthrough$
|
|
44633
|
+
advancedSchema: ConfigSchemaPassthrough$2.optional(),
|
|
43994
44634
|
/** The kind's defaults; a model from `listModels` may override any of them. */
|
|
43995
44635
|
features: LlmModelFeaturesSchema,
|
|
43996
44636
|
/** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
|
|
@@ -44516,10 +45156,10 @@ var CpuBreakdownSchema = object({
|
|
|
44516
45156
|
cores: number()
|
|
44517
45157
|
});
|
|
44518
45158
|
var MemoryInfoSchema = object({
|
|
44519
|
-
percent: number(),
|
|
45159
|
+
percent: number().nullable(),
|
|
44520
45160
|
totalBytes: number(),
|
|
44521
|
-
usedBytes: number(),
|
|
44522
|
-
availableBytes: number(),
|
|
45161
|
+
usedBytes: number().nullable(),
|
|
45162
|
+
availableBytes: number().nullable(),
|
|
44523
45163
|
swapUsedBytes: number(),
|
|
44524
45164
|
swapTotalBytes: number()
|
|
44525
45165
|
});
|
|
@@ -44823,8 +45463,9 @@ var GetLoadSeriesInputSchema = object({
|
|
|
44823
45463
|
});
|
|
44824
45464
|
var SystemMetricsSchema = object({
|
|
44825
45465
|
cpuPercent: number(),
|
|
44826
|
-
|
|
44827
|
-
|
|
45466
|
+
/** `null` when the memory sample failed or was not taken yet (D393). */
|
|
45467
|
+
memoryPercent: number().nullable(),
|
|
45468
|
+
memoryUsedMB: number().nullable(),
|
|
44828
45469
|
memoryTotalMB: number(),
|
|
44829
45470
|
diskPercent: number().optional(),
|
|
44830
45471
|
temperature: number().optional(),
|
|
@@ -44944,565 +45585,122 @@ var BrokerInfoSchema$1 = object({
|
|
|
44944
45585
|
error: string().optional(),
|
|
44945
45586
|
/** Embedded brokers only: number of MQTT clients currently connected. */
|
|
44946
45587
|
connectedClients: number().int().nonnegative().optional(),
|
|
44947
|
-
/** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
|
|
44948
|
-
lastCheckedAt: number().optional()
|
|
44949
|
-
});
|
|
44950
|
-
/**
|
|
44951
|
-
* Connection details — what a consumer needs to call
|
|
44952
|
-
* `mqtt.connect(url, options)`. We split URL + credentials so the
|
|
44953
|
-
* consumer can pass them as `mqtt.connect(url, { username, password })`
|
|
44954
|
-
* instead of stuffing creds into the URL (which leaks them into logs).
|
|
44955
|
-
*/
|
|
44956
|
-
var BrokerConnectionDetailsSchema = object({
|
|
44957
|
-
url: string(),
|
|
44958
|
-
username: string().optional(),
|
|
44959
|
-
password: string().optional(),
|
|
44960
|
-
/**
|
|
44961
|
-
* Suggested prefix for `clientId`. Each consumer should suffix this
|
|
44962
|
-
* with its own discriminator (addon id, instance id) so reconnects
|
|
44963
|
-
* don't kick each other off (MQTT spec: clientId must be unique per
|
|
44964
|
-
* broker).
|
|
44965
|
-
*/
|
|
44966
|
-
clientIdPrefix: string().optional()
|
|
44967
|
-
});
|
|
44968
|
-
var AddBrokerInputSchema = object({
|
|
44969
|
-
name: string().min(1),
|
|
44970
|
-
url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
|
|
44971
|
-
username: string().optional(),
|
|
44972
|
-
password: string().optional(),
|
|
44973
|
-
clientIdPrefix: string().optional()
|
|
44974
|
-
});
|
|
44975
|
-
var AddBrokerResultSchema = object({ id: string() });
|
|
44976
|
-
var IdInputSchema = object({ id: string() });
|
|
44977
|
-
var TestResultSchema$1 = discriminatedUnion("ok", [object({
|
|
44978
|
-
ok: literal(true),
|
|
44979
|
-
latencyMs: number()
|
|
44980
|
-
}), object({
|
|
44981
|
-
ok: literal(false),
|
|
44982
|
-
error: string()
|
|
44983
|
-
})]);
|
|
44984
|
-
var StartEmbeddedInputSchema = object({
|
|
44985
|
-
port: number().int().min(1).max(65535).default(1883),
|
|
44986
|
-
/** Allow anonymous connect (no username/password). Default: false. */
|
|
44987
|
-
allowAnonymous: boolean().default(false),
|
|
44988
|
-
/** Optional shared username/password for clients. */
|
|
44989
|
-
username: string().optional(),
|
|
44990
|
-
password: string().optional()
|
|
44991
|
-
});
|
|
44992
|
-
var StartEmbeddedResultSchema = object({
|
|
44993
|
-
id: string(),
|
|
44994
|
-
url: string()
|
|
44995
|
-
});
|
|
44996
|
-
var StatusSchema = object({
|
|
44997
|
-
brokerCount: number(),
|
|
44998
|
-
embeddedRunning: boolean()
|
|
44999
|
-
});
|
|
45000
|
-
method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
45001
|
-
kind: "mutation",
|
|
45002
|
-
auth: "admin"
|
|
45003
|
-
}), method(IdInputSchema, _void(), {
|
|
45004
|
-
kind: "mutation",
|
|
45005
|
-
auth: "admin"
|
|
45006
|
-
}), method(IdInputSchema, TestResultSchema$1, {
|
|
45007
|
-
kind: "mutation",
|
|
45008
|
-
auth: "admin"
|
|
45009
|
-
}), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
|
|
45010
|
-
kind: "mutation",
|
|
45011
|
-
auth: "admin"
|
|
45012
|
-
}), method(IdInputSchema, _void(), {
|
|
45013
|
-
kind: "mutation",
|
|
45014
|
-
auth: "admin"
|
|
45015
|
-
}), method(_void(), StatusSchema);
|
|
45016
|
-
var NetworkEndpointSchema = object({
|
|
45017
|
-
url: string(),
|
|
45018
|
-
hostname: string(),
|
|
45019
|
-
port: number(),
|
|
45020
|
-
protocol: _enum(["http", "https"]),
|
|
45021
|
-
/**
|
|
45022
|
-
* Whether this address survives a restart of its provider. `false` = it is
|
|
45023
|
-
* re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
|
|
45024
|
-
* hub never selects such an address AUTOMATICALLY for a role (D860), though
|
|
45025
|
-
* it stays visible and an operator may still flag it. Absent = stable: every
|
|
45026
|
-
* provider except a Quick tunnel owns a name that does not change.
|
|
45027
|
-
*/
|
|
45028
|
-
stable: boolean().optional()
|
|
45029
|
-
});
|
|
45030
|
-
object({
|
|
45031
|
-
url: string(),
|
|
45032
|
-
/** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
|
|
45033
|
-
stable: boolean().optional(),
|
|
45034
|
-
updated: boolean().optional()
|
|
45035
|
-
});
|
|
45036
|
-
var NetworkAccessStatusSchema = object({
|
|
45037
|
-
connected: boolean(),
|
|
45038
|
-
endpoint: NetworkEndpointSchema.nullable(),
|
|
45039
|
-
error: string().optional()
|
|
45040
|
-
});
|
|
45041
|
-
/**
|
|
45042
|
-
* Optional, richer endpoint shape returned by providers that expose
|
|
45043
|
-
* MORE than one ingress concurrently (Tailscale Funnel with mixed
|
|
45044
|
-
* serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
|
|
45045
|
-
* the originating provider config (mode + sourcePort) so the
|
|
45046
|
-
* orchestrator UI can label rows distinctly. Providers that expose only
|
|
45047
|
-
* one endpoint just omit `listEndpoints` from their provider impl.
|
|
45048
|
-
*/
|
|
45049
|
-
var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
45050
|
-
/**
|
|
45051
|
-
* Stable id within the provider — typically `<mode>-<sourcePort>` so
|
|
45052
|
-
* the orchestrator can dedupe across `listEndpoints` polls.
|
|
45053
|
-
*/
|
|
45054
|
-
id: string(),
|
|
45055
|
-
/** Operator-facing label (mirrors `MeshEndpoint.label`). */
|
|
45056
|
-
label: string(),
|
|
45057
|
-
/** Optional provider-specific mode tag, used for icon/colour in admin UI. */
|
|
45058
|
-
mode: string().optional(),
|
|
45059
|
-
/** Originating local port the ingress fronts (informational). */
|
|
45060
|
-
sourcePort: number().optional()
|
|
45061
|
-
});
|
|
45062
|
-
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
45063
|
-
/**
|
|
45064
|
-
* notification-output — canonical, capability-gated notification delivery.
|
|
45065
|
-
*
|
|
45066
|
-
* Apprise-derived model (see
|
|
45067
|
-
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
45068
|
-
* callers emit ONE canonical `Notification`; each provider declares a
|
|
45069
|
-
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
45070
|
-
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
45071
|
-
* message to what the kind supports — callers never special-case a service.
|
|
45072
|
-
*
|
|
45073
|
-
* DESIGN DECISIONS (locked):
|
|
45074
|
-
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
45075
|
-
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
45076
|
-
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
45077
|
-
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
45078
|
-
* alternative would fork the UI per addon and cannot host the
|
|
45079
|
-
* discovery→adopt flow.
|
|
45080
|
-
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
45081
|
-
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
45082
|
-
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
45083
|
-
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
45084
|
-
* `addonId` the generated collection router extracts from the call input.
|
|
45085
|
-
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
45086
|
-
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
45087
|
-
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
45088
|
-
* base64 fallback needed.
|
|
45089
|
-
*
|
|
45090
|
-
* TODO (deferred, closed-set change — separate decision): add
|
|
45091
|
-
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
45092
|
-
* admin "Integrations" page.
|
|
45093
|
-
*/
|
|
45094
|
-
/**
|
|
45095
|
-
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
45096
|
-
* adapter picks what it supports and the degrade engine filters the rest.
|
|
45097
|
-
*/
|
|
45098
|
-
var AttachmentMediaTypeSchema = _enum([
|
|
45099
|
-
"image",
|
|
45100
|
-
"video",
|
|
45101
|
-
"gif",
|
|
45102
|
-
"audio",
|
|
45103
|
-
"icon"
|
|
45104
|
-
]);
|
|
45105
|
-
/**
|
|
45106
|
-
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
45107
|
-
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
45108
|
-
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
45109
|
-
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
45110
|
-
*/
|
|
45111
|
-
var AttachmentSchema = object({
|
|
45112
|
-
mediaType: AttachmentMediaTypeSchema,
|
|
45113
|
-
url: string().optional(),
|
|
45114
|
-
bytes: _instanceof(Uint8Array).optional(),
|
|
45115
|
-
mime: string().optional(),
|
|
45116
|
-
name: string().optional()
|
|
45117
|
-
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
45118
|
-
var NotificationFormatSchema = _enum([
|
|
45119
|
-
"text",
|
|
45120
|
-
"markdown",
|
|
45121
|
-
"html"
|
|
45122
|
-
]);
|
|
45123
|
-
/**
|
|
45124
|
-
* The CLOSED icon vocabulary an action button may use.
|
|
45125
|
-
*
|
|
45126
|
-
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
45127
|
-
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
45128
|
-
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
45129
|
-
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
45130
|
-
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
45131
|
-
*
|
|
45132
|
-
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
45133
|
-
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
45134
|
-
* differently.
|
|
45135
|
-
*
|
|
45136
|
-
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
45137
|
-
*
|
|
45138
|
-
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
45139
|
-
* the whole set onto its own renderer's vocabulary through a
|
|
45140
|
-
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
45141
|
-
* fallback, so adding a member here fails every adapter's build until someone
|
|
45142
|
-
* decides its glyph, which is the only place that decision can be made
|
|
45143
|
-
* honestly.
|
|
45144
|
-
*
|
|
45145
|
-
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
45146
|
-
* `disarm` straight through; iOS feeds that string to
|
|
45147
|
-
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
45148
|
-
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
45149
|
-
* mapping, and "the field is documented" is not "the value renders".
|
|
45150
|
-
*
|
|
45151
|
-
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
45152
|
-
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
45153
|
-
* so an addon that emits a token the running hub does not know does not lose an
|
|
45154
|
-
* icon — its whole `send` fails Zod validation and the notification never
|
|
45155
|
-
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
45156
|
-
*/
|
|
45157
|
-
var NotificationActionIconSchema = _enum([
|
|
45158
|
-
"acknowledge",
|
|
45159
|
-
"dismiss",
|
|
45160
|
-
"silence",
|
|
45161
|
-
"snooze",
|
|
45162
|
-
"view",
|
|
45163
|
-
"play",
|
|
45164
|
-
"open",
|
|
45165
|
-
"close",
|
|
45166
|
-
"lock",
|
|
45167
|
-
"unlock",
|
|
45168
|
-
"arm",
|
|
45169
|
-
"arm-home",
|
|
45170
|
-
"arm-away",
|
|
45171
|
-
"arm-night",
|
|
45172
|
-
"disarm",
|
|
45173
|
-
"light",
|
|
45174
|
-
"alert",
|
|
45175
|
-
"camera"
|
|
45176
|
-
]);
|
|
45177
|
-
/** A single tap-through action button. */
|
|
45178
|
-
var NotificationActionSchema = object({
|
|
45179
|
-
id: string(),
|
|
45180
|
-
label: string(),
|
|
45181
|
-
url: string().optional(),
|
|
45182
|
-
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
45183
|
-
icon: NotificationActionIconSchema.optional(),
|
|
45184
|
-
/**
|
|
45185
|
-
* Renders in a warning style where the notifier supports it.
|
|
45186
|
-
*
|
|
45187
|
-
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
45188
|
-
* else — see `notification-center/action-token.ts` for what that does and
|
|
45189
|
-
* does not buy.
|
|
45190
|
-
*/
|
|
45191
|
-
destructive: boolean().optional(),
|
|
45192
|
-
/**
|
|
45193
|
-
* How the tap should REACH the url.
|
|
45194
|
-
*
|
|
45195
|
-
* `navigate` (absent, and every button authored before this field) opens it:
|
|
45196
|
-
* the phone leaves the notification and shows whatever the callback returns.
|
|
45197
|
-
* That is right for a button whose answer the operator wants to read.
|
|
45198
|
-
*
|
|
45199
|
-
* `background` fires it as a POST and stays put. It exists for the buttons
|
|
45200
|
-
* whose whole point is not to interrupt — "silence this for 30 minutes" is
|
|
45201
|
-
* an answer to the notification, and being thrown into a browser tab to
|
|
45202
|
-
* confirm it costs more attention than the notification did. A backend that
|
|
45203
|
-
* cannot do a background call renders it as an ordinary link (the adapters
|
|
45204
|
-
* fall back rather than dropping the button), so this is a preference, never
|
|
45205
|
-
* a requirement.
|
|
45206
|
-
*/
|
|
45207
|
-
mode: _enum(["navigate", "background"]).optional()
|
|
45208
|
-
});
|
|
45209
|
-
/** A delivery parameter value: what a notifier declared it takes. */
|
|
45210
|
-
var DeliveryParamValueSchema = union([
|
|
45211
|
-
string(),
|
|
45212
|
-
number(),
|
|
45213
|
-
boolean()
|
|
45214
|
-
]);
|
|
45215
|
-
/**
|
|
45216
|
-
* One extra delivery parameter a notifier KIND declares (D938). A rule that
|
|
45217
|
-
* addresses a target of that kind may set it; a kind that declares nothing is
|
|
45218
|
-
* never handed one. The admin UI derives its control from this descriptor —
|
|
45219
|
-
* no editor knows a service by name.
|
|
45220
|
-
*
|
|
45221
|
-
* Optional fields are `.optional()`, never `.default()` (a Zod default does not
|
|
45222
|
-
* run on the addon cap path).
|
|
45223
|
-
*/
|
|
45224
|
-
var DeliveryParamSchema = object({
|
|
45225
|
-
key: string().min(1),
|
|
45226
|
-
label: string(),
|
|
45227
|
-
/** i18n key of the label, resolved by the declaring addon's namespace. */
|
|
45228
|
-
labelKey: string().optional(),
|
|
45229
|
-
type: _enum([
|
|
45230
|
-
"enum",
|
|
45231
|
-
"number",
|
|
45232
|
-
"boolean",
|
|
45233
|
-
"string"
|
|
45234
|
-
]),
|
|
45235
|
-
/** `enum` only: the closed set of accepted values. */
|
|
45236
|
-
options: array(object({
|
|
45237
|
-
value: string(),
|
|
45238
|
-
label: string()
|
|
45239
|
-
})).optional(),
|
|
45240
|
-
/** `number` only. */
|
|
45241
|
-
min: number().optional(),
|
|
45242
|
-
max: number().optional(),
|
|
45243
|
-
/** What the notifier does when the rule sets nothing; shown as the placeholder. */
|
|
45244
|
-
default: DeliveryParamValueSchema.optional()
|
|
45245
|
-
});
|
|
45246
|
-
/**
|
|
45247
|
-
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
45248
|
-
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
45249
|
-
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
45250
|
-
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
45251
|
-
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
45252
|
-
* `priority` for that one target.
|
|
45253
|
-
*/
|
|
45254
|
-
var NotificationSchema = object({
|
|
45255
|
-
body: string(),
|
|
45256
|
-
title: string().optional(),
|
|
45257
|
-
format: NotificationFormatSchema.default("text"),
|
|
45258
|
-
priority: number().int().min(1).max(5).default(3),
|
|
45259
|
-
level: string().optional(),
|
|
45260
|
-
attachments: array(AttachmentSchema).optional(),
|
|
45261
|
-
clickUrl: string().optional(),
|
|
45262
|
-
actions: array(NotificationActionSchema).optional(),
|
|
45263
|
-
sound: string().optional(),
|
|
45264
|
-
ttl: number().optional(),
|
|
45265
|
-
tag: string().optional(),
|
|
45266
|
-
deviceId: number().optional(),
|
|
45267
|
-
eventId: string().optional(),
|
|
45268
|
-
metadata: record(string(), unknown()).optional(),
|
|
45269
|
-
/**
|
|
45270
|
-
* Values for the parameters the target's kind DECLARES in
|
|
45271
|
-
* {@link TargetKindSchema} `deliveryParams`. Already validated against the
|
|
45272
|
-
* declaration by the sender; a notifier reads only the keys it declared.
|
|
45273
|
-
*/
|
|
45274
|
-
delivery: record(string(), DeliveryParamValueSchema).optional()
|
|
45275
|
-
});
|
|
45276
|
-
/** One declared native severity/priority level for a kind. */
|
|
45277
|
-
var TargetKindLevelSchema = object({
|
|
45278
|
-
id: string(),
|
|
45279
|
-
label: string(),
|
|
45280
|
-
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
45281
|
-
ordinal: number().int().min(1).max(5).nullable(),
|
|
45282
|
-
flags: object({
|
|
45283
|
-
critical: boolean().optional(),
|
|
45284
|
-
silent: boolean().optional(),
|
|
45285
|
-
noPush: boolean().optional()
|
|
45286
|
-
}).optional(),
|
|
45287
|
-
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
45288
|
-
requires: array(string()).optional(),
|
|
45289
|
-
description: string().optional()
|
|
45290
|
-
});
|
|
45291
|
-
/** The full capability block consulted before dispatch. */
|
|
45292
|
-
var TargetKindCapsSchema = object({
|
|
45293
|
-
attachments: object({
|
|
45294
|
-
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
45295
|
-
mode: _enum([
|
|
45296
|
-
"url",
|
|
45297
|
-
"bytes",
|
|
45298
|
-
"both"
|
|
45299
|
-
]),
|
|
45300
|
-
max: number().int().nonnegative(),
|
|
45301
|
-
maxBytes: number().int().positive().optional()
|
|
45302
|
-
}),
|
|
45303
|
-
/** Max action buttons (0 = none). */
|
|
45304
|
-
actions: number().int().nonnegative(),
|
|
45305
|
-
/**
|
|
45306
|
-
* Whether this kind renders a per-action ICON.
|
|
45307
|
-
*
|
|
45308
|
-
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
45309
|
-
* run on the addon cap path — three production failures in one day taught
|
|
45310
|
-
* this repo that once. Absent is read as false by the degrade engine, which
|
|
45311
|
-
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
45312
|
-
* assumed and dropped costs the operator's trust in the field.
|
|
45313
|
-
*/
|
|
45314
|
-
actionIcons: boolean().optional(),
|
|
45315
|
-
levels: array(TargetKindLevelSchema),
|
|
45316
|
-
format: array(NotificationFormatSchema),
|
|
45317
|
-
clickUrl: boolean(),
|
|
45318
|
-
sound: boolean(),
|
|
45319
|
-
ttl: boolean(),
|
|
45320
|
-
bodyMaxLen: number().int().positive()
|
|
45588
|
+
/** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
|
|
45589
|
+
lastCheckedAt: number().optional()
|
|
45321
45590
|
});
|
|
45322
45591
|
/**
|
|
45323
|
-
*
|
|
45324
|
-
*
|
|
45325
|
-
* `
|
|
45326
|
-
*
|
|
45327
|
-
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
45592
|
+
* Connection details — what a consumer needs to call
|
|
45593
|
+
* `mqtt.connect(url, options)`. We split URL + credentials so the
|
|
45594
|
+
* consumer can pass them as `mqtt.connect(url, { username, password })`
|
|
45595
|
+
* instead of stuffing creds into the URL (which leaks them into logs).
|
|
45328
45596
|
*/
|
|
45329
|
-
var
|
|
45330
|
-
|
|
45331
|
-
|
|
45332
|
-
|
|
45333
|
-
icon: string(),
|
|
45334
|
-
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
45335
|
-
addonId: string(),
|
|
45336
|
-
/**
|
|
45337
|
-
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
45338
|
-
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
45339
|
-
* when the addon bundles no icon for that kind — the client then falls back
|
|
45340
|
-
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
45341
|
-
*
|
|
45342
|
-
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
45343
|
-
* client, and a native client joins it onto its own hub base.
|
|
45344
|
-
*
|
|
45345
|
-
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
45346
|
-
* field that survived only because the runtime cap-router forwards provider
|
|
45347
|
-
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
45348
|
-
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
45349
|
-
* broken silently the moment output validation was tightened anywhere.
|
|
45350
|
-
*/
|
|
45351
|
-
iconUrl: string().optional(),
|
|
45352
|
-
/**
|
|
45353
|
-
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
45354
|
-
*
|
|
45355
|
-
* The server knows this and therefore says it, because the client cannot
|
|
45356
|
-
* safely guess: a React-Native client renders SVG and raster through two
|
|
45357
|
-
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
45358
|
-
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
45359
|
-
* placeholder glyph for every vector icon while the web build looked fine.
|
|
45360
|
-
*
|
|
45361
|
-
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
45362
|
-
* not been updated — a client that cannot determine the type should prefer
|
|
45363
|
-
* its raster path, which is the safe default for an unknown image.
|
|
45364
|
-
*/
|
|
45365
|
-
iconMediaType: string().optional(),
|
|
45366
|
-
configSchema: ConfigSchemaPassthrough$2,
|
|
45367
|
-
/**
|
|
45368
|
-
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
45369
|
-
* base URL, instance, chat, recipient). A save that changes any of them
|
|
45370
|
-
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
45371
|
-
* editor uses this to ask for the secret on exactly those edits instead of
|
|
45372
|
-
* on every edit.
|
|
45373
|
-
*
|
|
45374
|
-
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
45375
|
-
* kinds) and from a provider older than this field — an editor that finds
|
|
45376
|
-
* it absent must assume ANY edit may need the secret.
|
|
45377
|
-
*/
|
|
45378
|
-
destinationKeys: array(string()).optional(),
|
|
45379
|
-
supportsDiscovery: boolean(),
|
|
45597
|
+
var BrokerConnectionDetailsSchema = object({
|
|
45598
|
+
url: string(),
|
|
45599
|
+
username: string().optional(),
|
|
45600
|
+
password: string().optional(),
|
|
45380
45601
|
/**
|
|
45381
|
-
*
|
|
45382
|
-
*
|
|
45602
|
+
* Suggested prefix for `clientId`. Each consumer should suffix this
|
|
45603
|
+
* with its own discriminator (addon id, instance id) so reconnects
|
|
45604
|
+
* don't kick each other off (MQTT spec: clientId must be unique per
|
|
45605
|
+
* broker).
|
|
45383
45606
|
*/
|
|
45384
|
-
|
|
45385
|
-
caps: TargetKindCapsSchema
|
|
45607
|
+
clientIdPrefix: string().optional()
|
|
45386
45608
|
});
|
|
45387
|
-
|
|
45388
|
-
|
|
45389
|
-
|
|
45390
|
-
|
|
45391
|
-
|
|
45392
|
-
|
|
45609
|
+
var AddBrokerInputSchema = object({
|
|
45610
|
+
name: string().min(1),
|
|
45611
|
+
url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
|
|
45612
|
+
username: string().optional(),
|
|
45613
|
+
password: string().optional(),
|
|
45614
|
+
clientIdPrefix: string().optional()
|
|
45615
|
+
});
|
|
45616
|
+
var AddBrokerResultSchema = object({ id: string() });
|
|
45617
|
+
var IdInputSchema = object({ id: string() });
|
|
45618
|
+
var TestResultSchema$1 = discriminatedUnion("ok", [object({
|
|
45619
|
+
ok: literal(true),
|
|
45620
|
+
latencyMs: number()
|
|
45621
|
+
}), object({
|
|
45622
|
+
ok: literal(false),
|
|
45623
|
+
error: string()
|
|
45624
|
+
})]);
|
|
45625
|
+
var StartEmbeddedInputSchema = object({
|
|
45626
|
+
port: number().int().min(1).max(65535).default(1883),
|
|
45627
|
+
/** Allow anonymous connect (no username/password). Default: false. */
|
|
45628
|
+
allowAnonymous: boolean().default(false),
|
|
45629
|
+
/** Optional shared username/password for clients. */
|
|
45630
|
+
username: string().optional(),
|
|
45631
|
+
password: string().optional()
|
|
45632
|
+
});
|
|
45633
|
+
var StartEmbeddedResultSchema = object({
|
|
45393
45634
|
id: string(),
|
|
45394
|
-
|
|
45395
|
-
kind: string(),
|
|
45396
|
-
addonId: string(),
|
|
45397
|
-
enabled: boolean(),
|
|
45398
|
-
config: record(string(), unknown())
|
|
45635
|
+
url: string()
|
|
45399
45636
|
});
|
|
45400
|
-
|
|
45401
|
-
|
|
45402
|
-
|
|
45403
|
-
suggestedName: string(),
|
|
45404
|
-
config: record(string(), unknown())
|
|
45637
|
+
var StatusSchema = object({
|
|
45638
|
+
brokerCount: number(),
|
|
45639
|
+
embeddedRunning: boolean()
|
|
45405
45640
|
});
|
|
45406
|
-
|
|
45407
|
-
|
|
45408
|
-
|
|
45409
|
-
|
|
45410
|
-
|
|
45411
|
-
|
|
45412
|
-
|
|
45413
|
-
|
|
45641
|
+
method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
45642
|
+
kind: "mutation",
|
|
45643
|
+
auth: "admin"
|
|
45644
|
+
}), method(IdInputSchema, _void(), {
|
|
45645
|
+
kind: "mutation",
|
|
45646
|
+
auth: "admin"
|
|
45647
|
+
}), method(IdInputSchema, TestResultSchema$1, {
|
|
45648
|
+
kind: "mutation",
|
|
45649
|
+
auth: "admin"
|
|
45650
|
+
}), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
|
|
45651
|
+
kind: "mutation",
|
|
45652
|
+
auth: "admin"
|
|
45653
|
+
}), method(IdInputSchema, _void(), {
|
|
45654
|
+
kind: "mutation",
|
|
45655
|
+
auth: "admin"
|
|
45656
|
+
}), method(_void(), StatusSchema);
|
|
45657
|
+
var NetworkEndpointSchema = object({
|
|
45658
|
+
url: string(),
|
|
45659
|
+
hostname: string(),
|
|
45660
|
+
port: number(),
|
|
45661
|
+
protocol: _enum(["http", "https"]),
|
|
45662
|
+
/**
|
|
45663
|
+
* Whether this address survives a restart of its provider. `false` = it is
|
|
45664
|
+
* re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
|
|
45665
|
+
* hub never selects such an address AUTOMATICALLY for a role (D860), though
|
|
45666
|
+
* it stays visible and an operator may still flag it. Absent = stable: every
|
|
45667
|
+
* provider except a Quick tunnel owns a name that does not change.
|
|
45668
|
+
*/
|
|
45669
|
+
stable: boolean().optional()
|
|
45414
45670
|
});
|
|
45415
|
-
|
|
45416
|
-
|
|
45417
|
-
|
|
45418
|
-
|
|
45671
|
+
object({
|
|
45672
|
+
url: string(),
|
|
45673
|
+
/** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
|
|
45674
|
+
stable: boolean().optional(),
|
|
45675
|
+
updated: boolean().optional()
|
|
45676
|
+
});
|
|
45677
|
+
var NetworkAccessStatusSchema = object({
|
|
45678
|
+
connected: boolean(),
|
|
45679
|
+
endpoint: NetworkEndpointSchema.nullable(),
|
|
45680
|
+
error: string().optional()
|
|
45419
45681
|
});
|
|
45420
|
-
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
45421
|
-
var TestResultSchema = SendResultSchema;
|
|
45422
45682
|
/**
|
|
45423
|
-
*
|
|
45424
|
-
*
|
|
45683
|
+
* Optional, richer endpoint shape returned by providers that expose
|
|
45684
|
+
* MORE than one ingress concurrently (Tailscale Funnel with mixed
|
|
45685
|
+
* serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
|
|
45686
|
+
* the originating provider config (mode + sourcePort) so the
|
|
45687
|
+
* orchestrator UI can label rows distinctly. Providers that expose only
|
|
45688
|
+
* one endpoint just omit `listEndpoints` from their provider impl.
|
|
45425
45689
|
*/
|
|
45426
|
-
var
|
|
45427
|
-
|
|
45428
|
-
|
|
45429
|
-
|
|
45430
|
-
|
|
45431
|
-
|
|
45690
|
+
var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
45691
|
+
/**
|
|
45692
|
+
* Stable id within the provider — typically `<mode>-<sourcePort>` so
|
|
45693
|
+
* the orchestrator can dedupe across `listEndpoints` polls.
|
|
45694
|
+
*/
|
|
45695
|
+
id: string(),
|
|
45696
|
+
/** Operator-facing label (mirrors `MeshEndpoint.label`). */
|
|
45697
|
+
label: string(),
|
|
45698
|
+
/** Optional provider-specific mode tag, used for icon/colour in admin UI. */
|
|
45699
|
+
mode: string().optional(),
|
|
45700
|
+
/** Originating local port the ingress fronts (informational). */
|
|
45701
|
+
sourcePort: number().optional()
|
|
45432
45702
|
});
|
|
45433
|
-
|
|
45434
|
-
name: "notification-output",
|
|
45435
|
-
scope: "system",
|
|
45436
|
-
mode: "collection",
|
|
45437
|
-
methods: {
|
|
45438
|
-
listTargetKinds: method(object({}), array(TargetKindSchema)),
|
|
45439
|
-
listTargets: method(object({}), array(TargetSchema), { auth: "admin" }),
|
|
45440
|
-
/**
|
|
45441
|
-
* One target WITH secrets — admin edit / duplicate hydration only.
|
|
45442
|
-
* `listTargets` stays redacted; the editor loads this so password fields
|
|
45443
|
-
* are filled (masked in the input) and a save can round-trip the token
|
|
45444
|
-
* when a destination changes (Duplicate with a new chatId, etc.).
|
|
45445
|
-
*/
|
|
45446
|
-
getTarget: method(object({ targetId: string() }), TargetSchema, { auth: "admin" }),
|
|
45447
|
-
discoverTargets: method(object({
|
|
45448
|
-
kind: string(),
|
|
45449
|
-
config: record(string(), unknown()).optional(),
|
|
45450
|
-
/**
|
|
45451
|
-
* When editing / duplicating, the blank password field never round-trips
|
|
45452
|
-
* the stored token. Pass the source target id so the provider can restore
|
|
45453
|
-
* secrets server-side before dialling (same merge as upsert).
|
|
45454
|
-
*/
|
|
45455
|
-
targetId: string().optional()
|
|
45456
|
-
}), array(DiscoveredTargetSchema), { auth: "admin" }),
|
|
45457
|
-
probeTargetConfig: method(object({
|
|
45458
|
-
kind: string(),
|
|
45459
|
-
config: record(string(), unknown()),
|
|
45460
|
-
/** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
|
|
45461
|
-
targetId: string().optional()
|
|
45462
|
-
}), ProbeTargetConfigResultSchema, {
|
|
45463
|
-
kind: "mutation",
|
|
45464
|
-
auth: "admin"
|
|
45465
|
-
}),
|
|
45466
|
-
send: method(object({
|
|
45467
|
-
targetId: string(),
|
|
45468
|
-
notification: NotificationSchema
|
|
45469
|
-
}), SendResultSchema, {
|
|
45470
|
-
kind: "mutation",
|
|
45471
|
-
auth: "admin"
|
|
45472
|
-
}),
|
|
45473
|
-
testTarget: method(object({
|
|
45474
|
-
targetId: string(),
|
|
45475
|
-
sample: NotificationSchema.optional()
|
|
45476
|
-
}), TestResultSchema, {
|
|
45477
|
-
kind: "mutation",
|
|
45478
|
-
auth: "admin"
|
|
45479
|
-
}),
|
|
45480
|
-
upsertTarget: method(object({
|
|
45481
|
-
target: TargetSchema,
|
|
45482
|
-
/**
|
|
45483
|
-
* Duplicate flow: create a NEW target id while copying secrets from an
|
|
45484
|
-
* existing row. The UI never holds the stored token; blank / redacted
|
|
45485
|
-
* password fields are restored from this source (D634 still applies if
|
|
45486
|
-
* destinations diverge without a re-typed secret).
|
|
45487
|
-
*/
|
|
45488
|
-
cloneSecretsFrom: string().optional()
|
|
45489
|
-
}), TargetSchema, {
|
|
45490
|
-
kind: "mutation",
|
|
45491
|
-
auth: "admin"
|
|
45492
|
-
}),
|
|
45493
|
-
deleteTarget: method(object({ targetId: string() }), _void(), {
|
|
45494
|
-
kind: "mutation",
|
|
45495
|
-
auth: "admin"
|
|
45496
|
-
}),
|
|
45497
|
-
setTargetEnabled: method(object({
|
|
45498
|
-
targetId: string(),
|
|
45499
|
-
enabled: boolean()
|
|
45500
|
-
}), _void(), {
|
|
45501
|
-
kind: "mutation",
|
|
45502
|
-
auth: "admin"
|
|
45503
|
-
})
|
|
45504
|
-
}
|
|
45505
|
-
};
|
|
45703
|
+
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
45506
45704
|
var AUDIO_MACRO_LABELS = [
|
|
45507
45705
|
{
|
|
45508
45706
|
id: "speech",
|
|
@@ -51947,14 +52145,6 @@ var EngineProvisioningSchema = object({
|
|
|
51947
52145
|
*/
|
|
51948
52146
|
configIssues: array(string()).optional()
|
|
51949
52147
|
});
|
|
51950
|
-
var PipelineStepInputSchema = lazy(() => object({
|
|
51951
|
-
addonId: string(),
|
|
51952
|
-
modelId: string().optional(),
|
|
51953
|
-
enabled: boolean().default(true),
|
|
51954
|
-
children: array(PipelineStepInputSchema).optional(),
|
|
51955
|
-
settings: record(string(), unknown()).optional(),
|
|
51956
|
-
jumpDeviceKey: string().optional()
|
|
51957
|
-
}));
|
|
51958
52148
|
var ModelSubstitutionSchema = object({
|
|
51959
52149
|
addonId: string(),
|
|
51960
52150
|
chosen: string(),
|
|
@@ -52033,7 +52223,6 @@ var ConfigUISchemaBridge = custom();
|
|
|
52033
52223
|
var ConfigUISchemaNullableBridge = custom();
|
|
52034
52224
|
var InferenceCapabilitiesBridge = custom();
|
|
52035
52225
|
var ModelAvailabilityListBridge = custom();
|
|
52036
|
-
var PipelineRunResultBridge = custom();
|
|
52037
52226
|
method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
|
|
52038
52227
|
modelId: string(),
|
|
52039
52228
|
settings: record(string(), unknown()).readonly()
|
|
@@ -55239,6 +55428,12 @@ var ServerUpdateActionResultSchema = object({
|
|
|
55239
55428
|
*/
|
|
55240
55429
|
blockers: array(UpdateBlockerSchema).optional()
|
|
55241
55430
|
});
|
|
55431
|
+
var LaunchOverridesSchema = object({
|
|
55432
|
+
/** The persisted heap ceiling in MB, or `null` when none is set. */
|
|
55433
|
+
heapMb: number().int().nullable() });
|
|
55434
|
+
var SetLaunchOverridesResultSchema = LaunchOverridesSchema.extend({
|
|
55435
|
+
/** Always true: the override is read at launch, not by the running process. */
|
|
55436
|
+
restartRequired: boolean() });
|
|
55242
55437
|
method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
|
|
55243
55438
|
kind: "mutation",
|
|
55244
55439
|
auth: "admin"
|
|
@@ -55261,6 +55456,9 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
|
|
|
55261
55456
|
}), method(_void(), ServerUpdateActionResultSchema, {
|
|
55262
55457
|
kind: "mutation",
|
|
55263
55458
|
auth: "admin"
|
|
55459
|
+
}), method(_void(), LaunchOverridesSchema, { auth: "admin" }), method(object({ heapMb: number().int().min(512).max(65536).nullable() }), SetLaunchOverridesResultSchema, {
|
|
55460
|
+
kind: "mutation",
|
|
55461
|
+
auth: "admin"
|
|
55264
55462
|
});
|
|
55265
55463
|
/**
|
|
55266
55464
|
* `smtp-provider` — pluggable email delivery surface.
|
|
@@ -58827,6 +59025,318 @@ onBrightnessChanged: { data: object({
|
|
|
58827
59025
|
} } }
|
|
58828
59026
|
};
|
|
58829
59027
|
/**
|
|
59028
|
+
* The role vocabulary of a remote (D963): what a button MEANS, independent of
|
|
59029
|
+
* how it is wired. A layout template places buttons by role; a device binds a
|
|
59030
|
+
* role to an action; an exporter asks for a role by name. Roles are shared so
|
|
59031
|
+
* one layout fits every appliance and one voice command fits every binding.
|
|
59032
|
+
*
|
|
59033
|
+
* A role outside this list is `custom:<key>` — the operator's own button (a
|
|
59034
|
+
* SmartIR source, a channel preset). The key is a lowercase slug.
|
|
59035
|
+
*/
|
|
59036
|
+
var REMOTE_KNOWN_ROLES = [
|
|
59037
|
+
"power",
|
|
59038
|
+
"power_on",
|
|
59039
|
+
"power_off",
|
|
59040
|
+
"volume_up",
|
|
59041
|
+
"volume_down",
|
|
59042
|
+
"mute",
|
|
59043
|
+
"channel_up",
|
|
59044
|
+
"channel_down",
|
|
59045
|
+
"up",
|
|
59046
|
+
"down",
|
|
59047
|
+
"left",
|
|
59048
|
+
"right",
|
|
59049
|
+
"ok",
|
|
59050
|
+
"back",
|
|
59051
|
+
"home",
|
|
59052
|
+
"menu",
|
|
59053
|
+
"info",
|
|
59054
|
+
"guide",
|
|
59055
|
+
"exit",
|
|
59056
|
+
"input",
|
|
59057
|
+
"play",
|
|
59058
|
+
"pause",
|
|
59059
|
+
"play_pause",
|
|
59060
|
+
"stop",
|
|
59061
|
+
"next",
|
|
59062
|
+
"previous",
|
|
59063
|
+
"fast_forward",
|
|
59064
|
+
"rewind",
|
|
59065
|
+
"digit_0",
|
|
59066
|
+
"digit_1",
|
|
59067
|
+
"digit_2",
|
|
59068
|
+
"digit_3",
|
|
59069
|
+
"digit_4",
|
|
59070
|
+
"digit_5",
|
|
59071
|
+
"digit_6",
|
|
59072
|
+
"digit_7",
|
|
59073
|
+
"digit_8",
|
|
59074
|
+
"digit_9"
|
|
59075
|
+
];
|
|
59076
|
+
var CUSTOM_KEY_RE = /^[a-z0-9][a-z0-9_]{0,47}$/;
|
|
59077
|
+
var KNOWN_ROLES = new Set(REMOTE_KNOWN_ROLES);
|
|
59078
|
+
function isRemoteKnownRole(value) {
|
|
59079
|
+
return KNOWN_ROLES.has(value);
|
|
59080
|
+
}
|
|
59081
|
+
function isRemoteCustomKey(key) {
|
|
59082
|
+
return CUSTOM_KEY_RE.test(key);
|
|
59083
|
+
}
|
|
59084
|
+
/** The key of a `custom:<key>` role, or null for any other string. */
|
|
59085
|
+
function customKeyOfRole(role) {
|
|
59086
|
+
if (!role.startsWith("custom:")) return null;
|
|
59087
|
+
const key = role.slice(7);
|
|
59088
|
+
return isRemoteCustomKey(key) ? key : null;
|
|
59089
|
+
}
|
|
59090
|
+
function isRemoteRole(value) {
|
|
59091
|
+
return isRemoteKnownRole(value) || customKeyOfRole(value) !== null;
|
|
59092
|
+
}
|
|
59093
|
+
var RemoteRoleSchema = string().refine(isRemoteRole, { message: "a role is one of the known roles or custom:<key> (lowercase letters, digits, _)" });
|
|
59094
|
+
var RemoteBindingSchema = object({ action: AutomationCapStepSchema.extend({ deviceId: number().int().nonnegative() }) });
|
|
59095
|
+
var RemoteCustomButtonSchema = object({
|
|
59096
|
+
/** The slug after `custom:` in the role. */
|
|
59097
|
+
key: string().refine(isRemoteCustomKey, { message: "a lowercase slug" }),
|
|
59098
|
+
/** What the operator calls it, and what a voice command may name. */
|
|
59099
|
+
name: string().min(1).max(60)
|
|
59100
|
+
});
|
|
59101
|
+
var RemotePowerModeSchema = _enum(["toggle", "discrete"]);
|
|
59102
|
+
var RemoteConfigSchema = object({
|
|
59103
|
+
layoutTemplateId: string().min(1).nullable(),
|
|
59104
|
+
/**
|
|
59105
|
+
* The transmitter device new bindings and an import send through. A binding
|
|
59106
|
+
* names its own device, so one command may override it.
|
|
59107
|
+
*/
|
|
59108
|
+
defaultTransmitterId: number().int().nonnegative().nullable().default(null),
|
|
59109
|
+
/** Keyed by role. A role with no entry falls back to the native cap. */
|
|
59110
|
+
bindings: record(string(), RemoteBindingSchema),
|
|
59111
|
+
customButtons: array(RemoteCustomButtonSchema).max(100),
|
|
59112
|
+
power: object({ mode: RemotePowerModeSchema })
|
|
59113
|
+
}).superRefine((config, ctx) => {
|
|
59114
|
+
const roles = Object.keys(config.bindings);
|
|
59115
|
+
if (roles.length > 200) ctx.addIssue({
|
|
59116
|
+
code: "custom",
|
|
59117
|
+
message: "too many bindings",
|
|
59118
|
+
path: ["bindings"]
|
|
59119
|
+
});
|
|
59120
|
+
for (const role of roles) if (!isRemoteRole(role)) ctx.addIssue({
|
|
59121
|
+
code: "custom",
|
|
59122
|
+
message: `"${role}" is not a role`,
|
|
59123
|
+
path: ["bindings", role]
|
|
59124
|
+
});
|
|
59125
|
+
const keys = /* @__PURE__ */ new Set();
|
|
59126
|
+
config.customButtons.forEach((button, index) => {
|
|
59127
|
+
if (keys.has(button.key)) ctx.addIssue({
|
|
59128
|
+
code: "custom",
|
|
59129
|
+
message: `custom button "${button.key}" is listed twice`,
|
|
59130
|
+
path: [
|
|
59131
|
+
"customButtons",
|
|
59132
|
+
index,
|
|
59133
|
+
"key"
|
|
59134
|
+
]
|
|
59135
|
+
});
|
|
59136
|
+
keys.add(button.key);
|
|
59137
|
+
});
|
|
59138
|
+
for (const role of roles) {
|
|
59139
|
+
const key = customKeyOfRole(role);
|
|
59140
|
+
if (key !== null && !keys.has(key)) ctx.addIssue({
|
|
59141
|
+
code: "custom",
|
|
59142
|
+
message: `${role} is bound but has no entry in customButtons`,
|
|
59143
|
+
path: ["bindings", role]
|
|
59144
|
+
});
|
|
59145
|
+
}
|
|
59146
|
+
});
|
|
59147
|
+
object({
|
|
59148
|
+
power: object({
|
|
59149
|
+
state: _enum(["on", "off"]),
|
|
59150
|
+
at: number()
|
|
59151
|
+
}).nullable(),
|
|
59152
|
+
muted: object({
|
|
59153
|
+
value: boolean(),
|
|
59154
|
+
at: number()
|
|
59155
|
+
}).nullable()
|
|
59156
|
+
});
|
|
59157
|
+
var RemoteLayoutButtonSchema = object({
|
|
59158
|
+
id: string().min(1).max(40),
|
|
59159
|
+
/** 0-based grid position. */
|
|
59160
|
+
row: number().int().min(0).max(24),
|
|
59161
|
+
col: number().int().min(0).max(24),
|
|
59162
|
+
rowSpan: number().int().min(1).max(24),
|
|
59163
|
+
colSpan: number().int().min(1).max(24),
|
|
59164
|
+
icon: string().min(1).max(60).optional(),
|
|
59165
|
+
label: string().min(1).max(40).optional(),
|
|
59166
|
+
role: RemoteRoleSchema
|
|
59167
|
+
});
|
|
59168
|
+
object({
|
|
59169
|
+
columns: number().int().min(1).max(24),
|
|
59170
|
+
rows: number().int().min(1).max(24),
|
|
59171
|
+
buttons: array(RemoteLayoutButtonSchema).max(200)
|
|
59172
|
+
}).superRefine((grid, ctx) => {
|
|
59173
|
+
const ids = /* @__PURE__ */ new Set();
|
|
59174
|
+
const taken = /* @__PURE__ */ new Map();
|
|
59175
|
+
grid.buttons.forEach((button, index) => {
|
|
59176
|
+
if (ids.has(button.id)) ctx.addIssue({
|
|
59177
|
+
code: "custom",
|
|
59178
|
+
message: `button id "${button.id}" is used twice`,
|
|
59179
|
+
path: [
|
|
59180
|
+
"buttons",
|
|
59181
|
+
index,
|
|
59182
|
+
"id"
|
|
59183
|
+
]
|
|
59184
|
+
});
|
|
59185
|
+
ids.add(button.id);
|
|
59186
|
+
if (button.row + button.rowSpan > grid.rows || button.col + button.colSpan > grid.columns) {
|
|
59187
|
+
ctx.addIssue({
|
|
59188
|
+
code: "custom",
|
|
59189
|
+
message: `button "${button.id}" leaves the grid`,
|
|
59190
|
+
path: ["buttons", index]
|
|
59191
|
+
});
|
|
59192
|
+
return;
|
|
59193
|
+
}
|
|
59194
|
+
for (let r = button.row; r < button.row + button.rowSpan; r += 1) for (let c = button.col; c < button.col + button.colSpan; c += 1) {
|
|
59195
|
+
const cell = `${r}:${c}`;
|
|
59196
|
+
const other = taken.get(cell);
|
|
59197
|
+
if (other !== void 0) {
|
|
59198
|
+
ctx.addIssue({
|
|
59199
|
+
code: "custom",
|
|
59200
|
+
message: `buttons "${other}" and "${button.id}" overlap`,
|
|
59201
|
+
path: ["buttons", index]
|
|
59202
|
+
});
|
|
59203
|
+
return;
|
|
59204
|
+
}
|
|
59205
|
+
taken.set(cell, button.id);
|
|
59206
|
+
}
|
|
59207
|
+
});
|
|
59208
|
+
});
|
|
59209
|
+
var RemoteBoundCommandSchema = object({
|
|
59210
|
+
role: RemoteRoleSchema,
|
|
59211
|
+
/** The operator's name for a custom button; absent for a known role. */
|
|
59212
|
+
label: string().optional(),
|
|
59213
|
+
custom: boolean()
|
|
59214
|
+
});
|
|
59215
|
+
var RemoteCommandListSchema = object({
|
|
59216
|
+
layoutTemplateId: string().nullable(),
|
|
59217
|
+
power: object({ mode: RemotePowerModeSchema }),
|
|
59218
|
+
/** Only the roles that are BOUND — the ones a press can reach. */
|
|
59219
|
+
commands: array(RemoteBoundCommandSchema)
|
|
59220
|
+
});
|
|
59221
|
+
var RemoteRefusalReasonSchema = _enum([
|
|
59222
|
+
"no-remote",
|
|
59223
|
+
"unbound-role",
|
|
59224
|
+
"action-failed"
|
|
59225
|
+
]);
|
|
59226
|
+
var RemoteSendResultSchema = discriminatedUnion("status", [
|
|
59227
|
+
object({
|
|
59228
|
+
status: literal("sent"),
|
|
59229
|
+
/** How many times the action ran. */
|
|
59230
|
+
sends: number().int().min(1)
|
|
59231
|
+
}),
|
|
59232
|
+
object({
|
|
59233
|
+
status: literal("skipped"),
|
|
59234
|
+
reason: literal("already-in-state")
|
|
59235
|
+
}),
|
|
59236
|
+
object({
|
|
59237
|
+
status: literal("refused"),
|
|
59238
|
+
reason: RemoteRefusalReasonSchema,
|
|
59239
|
+
detail: string().optional()
|
|
59240
|
+
})
|
|
59241
|
+
]);
|
|
59242
|
+
var RemoteMediaCommandSchema = _enum([
|
|
59243
|
+
"play",
|
|
59244
|
+
"pause",
|
|
59245
|
+
"stop",
|
|
59246
|
+
"next",
|
|
59247
|
+
"previous",
|
|
59248
|
+
"setMute",
|
|
59249
|
+
"selectSource"
|
|
59250
|
+
]);
|
|
59251
|
+
/** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
|
|
59252
|
+
var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
|
|
59253
|
+
var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status: literal("saved") }), object({
|
|
59254
|
+
status: literal("refused"),
|
|
59255
|
+
reason: _enum(["unknown-template", "not-a-media-player"]),
|
|
59256
|
+
detail: string()
|
|
59257
|
+
})]);
|
|
59258
|
+
var deviceInput = object({ deviceId: number().int().nonnegative() });
|
|
59259
|
+
DeviceType.MediaPlayer;
|
|
59260
|
+
method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
|
|
59261
|
+
kind: "mutation",
|
|
59262
|
+
auth: "admin"
|
|
59263
|
+
}), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
|
|
59264
|
+
role: RemoteRoleSchema,
|
|
59265
|
+
/** Sends in a row (volume steps). */
|
|
59266
|
+
repeat: number().int().min(1).max(50).default(1)
|
|
59267
|
+
}), RemoteSendResultSchema, {
|
|
59268
|
+
kind: "mutation",
|
|
59269
|
+
auth: "protected"
|
|
59270
|
+
}), method(deviceInput.extend({
|
|
59271
|
+
command: RemoteMediaCommandSchema,
|
|
59272
|
+
muted: boolean().optional(),
|
|
59273
|
+
source: string().optional(),
|
|
59274
|
+
repeat: number().int().min(1).max(50).default(1)
|
|
59275
|
+
}), RemoteMediaCommandResultSchema, {
|
|
59276
|
+
kind: "mutation",
|
|
59277
|
+
auth: "protected"
|
|
59278
|
+
});
|
|
59279
|
+
var TemplateKindSchema = _enum(["remote"]);
|
|
59280
|
+
/**
|
|
59281
|
+
* template — the generic store of reusable, named operator data (D963). A
|
|
59282
|
+
* record is `{ id, kind, name, payload }`; the kinds (today only `remote`, the
|
|
59283
|
+
* button grid of a remote) and the schema each payload must satisfy are
|
|
59284
|
+
* declared by the system in `template-kinds.ts`. Nothing here is specific to a
|
|
59285
|
+
* kind: the engine validates a payload against its kind's schema and, on a
|
|
59286
|
+
* delete, asks the kind's owner whether anything still points at the record.
|
|
59287
|
+
*
|
|
59288
|
+
* Every refusal is a typed result — a UI says "unknown kind" or names the
|
|
59289
|
+
* devices that still use a template, without parsing text.
|
|
59290
|
+
*/
|
|
59291
|
+
var TemplateRecordSchema = object({
|
|
59292
|
+
id: string().min(1),
|
|
59293
|
+
kind: TemplateKindSchema,
|
|
59294
|
+
name: string().min(1).max(120),
|
|
59295
|
+
/** Shaped by the kind; `checkTemplatePayload(kind, payload)` reads it. */
|
|
59296
|
+
payload: unknown(),
|
|
59297
|
+
createdAt: number(),
|
|
59298
|
+
updatedAt: number()
|
|
59299
|
+
});
|
|
59300
|
+
var TemplateWriteResultSchema = discriminatedUnion("status", [object({
|
|
59301
|
+
status: literal("saved"),
|
|
59302
|
+
template: TemplateRecordSchema
|
|
59303
|
+
}), object({
|
|
59304
|
+
status: literal("refused"),
|
|
59305
|
+
reason: _enum(["invalid-payload", "not-found"]),
|
|
59306
|
+
detail: string()
|
|
59307
|
+
})]);
|
|
59308
|
+
/** A device still pointing at a template. */
|
|
59309
|
+
var TemplateUserSchema = object({
|
|
59310
|
+
deviceId: number().int().nonnegative(),
|
|
59311
|
+
name: string()
|
|
59312
|
+
});
|
|
59313
|
+
var TemplateDeleteResultSchema = discriminatedUnion("status", [
|
|
59314
|
+
object({ status: literal("deleted") }),
|
|
59315
|
+
object({ status: literal("not-found") }),
|
|
59316
|
+
object({
|
|
59317
|
+
status: literal("in-use"),
|
|
59318
|
+
devices: array(TemplateUserSchema).min(1)
|
|
59319
|
+
})
|
|
59320
|
+
]);
|
|
59321
|
+
method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSchema), { auth: "protected" }), method(object({ id: string().min(1) }), TemplateRecordSchema.nullable(), { auth: "protected" }), method(object({
|
|
59322
|
+
kind: TemplateKindSchema,
|
|
59323
|
+
name: string().min(1).max(120),
|
|
59324
|
+
payload: unknown()
|
|
59325
|
+
}), TemplateWriteResultSchema, {
|
|
59326
|
+
kind: "mutation",
|
|
59327
|
+
auth: "admin"
|
|
59328
|
+
}), method(object({
|
|
59329
|
+
id: string().min(1),
|
|
59330
|
+
name: string().min(1).max(120).optional(),
|
|
59331
|
+
payload: unknown().optional()
|
|
59332
|
+
}), TemplateWriteResultSchema, {
|
|
59333
|
+
kind: "mutation",
|
|
59334
|
+
auth: "admin"
|
|
59335
|
+
}), method(object({ id: string().min(1) }), TemplateDeleteResultSchema, {
|
|
59336
|
+
kind: "mutation",
|
|
59337
|
+
auth: "admin"
|
|
59338
|
+
});
|
|
59339
|
+
/**
|
|
58830
59340
|
* button — device-scoped capability for HA `button.*` / `input_button.*`
|
|
58831
59341
|
* fire-only actuators. A button entity has no readable state — pressing it
|
|
58832
59342
|
* triggers an action on the HA side. There is no `status` / `runtimeState`:
|
|
@@ -58848,6 +59358,56 @@ var buttonCapability = {
|
|
|
58848
59358
|
}) }
|
|
58849
59359
|
};
|
|
58850
59360
|
/**
|
|
59361
|
+
* A pulse train in signed microseconds: mark (+) first, then space (-), strictly
|
|
59362
|
+
* alternating, no zeros, 2..1024 entries.
|
|
59363
|
+
*/
|
|
59364
|
+
var IrTimingsSchema = array(number().int()).min(2).max(1024).superRefine((timings, ctx) => {
|
|
59365
|
+
timings.forEach((value, index) => {
|
|
59366
|
+
const expectMark = index % 2 === 0;
|
|
59367
|
+
if (value === 0 || value > 0 !== expectMark) ctx.addIssue({
|
|
59368
|
+
code: "custom",
|
|
59369
|
+
message: `timing ${index} must be a non-zero ${expectMark ? "mark (positive)" : "space (negative)"}`,
|
|
59370
|
+
path: [index]
|
|
59371
|
+
});
|
|
59372
|
+
});
|
|
59373
|
+
});
|
|
59374
|
+
/** Per-command transmit options. Every key is optional; a transmitter applies what it can. */
|
|
59375
|
+
var TransmitSettingsSchema = object({
|
|
59376
|
+
carrierHz: number().int().positive().optional(),
|
|
59377
|
+
dutyCyclePercent: number().int().min(1).max(100).optional(),
|
|
59378
|
+
repeat: number().int().min(0).max(20).optional(),
|
|
59379
|
+
repeatDelayMs: number().int().min(0).optional()
|
|
59380
|
+
});
|
|
59381
|
+
/** The setting keys a transmitter may declare as settable (`getInfo.settable`). */
|
|
59382
|
+
var TransmitSettingKeySchema = _enum([
|
|
59383
|
+
"carrierHz",
|
|
59384
|
+
"dutyCyclePercent",
|
|
59385
|
+
"repeat",
|
|
59386
|
+
"repeatDelayMs"
|
|
59387
|
+
]);
|
|
59388
|
+
var IrMediumSchema = _enum(["ir", "rf"]);
|
|
59389
|
+
var IrSendResultSchema = object({
|
|
59390
|
+
ok: literal(true),
|
|
59391
|
+
/** Settings the transmitter could not apply; the train was still sent. */
|
|
59392
|
+
ignoredSettings: array(string())
|
|
59393
|
+
});
|
|
59394
|
+
var IrTransmitterInfoSchema = object({
|
|
59395
|
+
media: array(IrMediumSchema),
|
|
59396
|
+
settable: array(TransmitSettingKeySchema),
|
|
59397
|
+
maxTimings: number().int().positive()
|
|
59398
|
+
});
|
|
59399
|
+
DeviceType.Generic, method(object({
|
|
59400
|
+
deviceId: number().int().nonnegative(),
|
|
59401
|
+
timings: IrTimingsSchema,
|
|
59402
|
+
settings: TransmitSettingsSchema.optional()
|
|
59403
|
+
}), IrSendResultSchema, {
|
|
59404
|
+
kind: "mutation",
|
|
59405
|
+
auth: "protected"
|
|
59406
|
+
}), method(object({ deviceId: number().int().nonnegative() }), IrTransmitterInfoSchema, {
|
|
59407
|
+
kind: "query",
|
|
59408
|
+
auth: "protected"
|
|
59409
|
+
});
|
|
59410
|
+
/**
|
|
58851
59411
|
* `calendar-source` — collection cap exposing per-provider calendars and
|
|
58852
59412
|
* their events. Every addon that contributes calendars (Home Assistant,
|
|
58853
59413
|
* a future Google Calendar addon, …) registers a provider here. The hub
|
|
@@ -59807,14 +60367,13 @@ reset: method(object({
|
|
|
59807
60367
|
auth: "admin"
|
|
59808
60368
|
}) },
|
|
59809
60369
|
/**
|
|
59810
|
-
* A
|
|
59811
|
-
*
|
|
59812
|
-
*
|
|
59813
|
-
* commands (`forward`) arrive in slice 3 (D663).
|
|
60370
|
+
* A consumable ADDED to an existing device (a graft) refuses `reset` by
|
|
60371
|
+
* name: only a composed device of a NEW type forwards an item's reset to a
|
|
60372
|
+
* source button, and marks that item `resettable` (D967).
|
|
59814
60373
|
*/
|
|
59815
60374
|
composedMethods: { reset: {
|
|
59816
60375
|
kind: "refuse",
|
|
59817
|
-
reason: "a
|
|
60376
|
+
reason: "a consumable added to an existing device is not resettable — only a new composed device forwards a reset"
|
|
59818
60377
|
} },
|
|
59819
60378
|
status: {
|
|
59820
60379
|
schema: ConsumablesStatusSchema,
|
|
@@ -60408,7 +60967,7 @@ var DishwasherStatusSchema = object({
|
|
|
60408
60967
|
/** Ms epoch of the last value received from the appliance; 0 = never. */
|
|
60409
60968
|
lastFetchedAt: number()
|
|
60410
60969
|
});
|
|
60411
|
-
var DeviceIdSchema = number().int().nonnegative();
|
|
60970
|
+
var DeviceIdSchema$1 = number().int().nonnegative();
|
|
60412
60971
|
/** A value a program option or setting can take. */
|
|
60413
60972
|
var DishwasherValueSchema = union([
|
|
60414
60973
|
boolean(),
|
|
@@ -60483,77 +61042,77 @@ var dishwasherCapability = {
|
|
|
60483
61042
|
* option feature keys; each is checked against the program's constraints.
|
|
60484
61043
|
*/
|
|
60485
61044
|
start: method(object({
|
|
60486
|
-
deviceId: DeviceIdSchema,
|
|
61045
|
+
deviceId: DeviceIdSchema$1,
|
|
60487
61046
|
program: string().min(1).optional(),
|
|
60488
61047
|
options: record(string(), DishwasherValueSchema).optional()
|
|
60489
61048
|
}), _void(), {
|
|
60490
61049
|
kind: "mutation",
|
|
60491
61050
|
auth: "protected"
|
|
60492
61051
|
}),
|
|
60493
|
-
pause: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
61052
|
+
pause: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
60494
61053
|
kind: "mutation",
|
|
60495
61054
|
auth: "protected"
|
|
60496
61055
|
}),
|
|
60497
|
-
resume: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
61056
|
+
resume: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
60498
61057
|
kind: "mutation",
|
|
60499
61058
|
auth: "protected"
|
|
60500
61059
|
}),
|
|
60501
|
-
stop: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
61060
|
+
stop: method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
|
|
60502
61061
|
kind: "mutation",
|
|
60503
61062
|
auth: "protected"
|
|
60504
61063
|
}),
|
|
60505
61064
|
/** Select (not start) a program; refused when the appliance does not offer it. */
|
|
60506
61065
|
setProgram: method(object({
|
|
60507
|
-
deviceId: DeviceIdSchema,
|
|
61066
|
+
deviceId: DeviceIdSchema$1,
|
|
60508
61067
|
program: string().min(1)
|
|
60509
61068
|
}), _void(), {
|
|
60510
61069
|
kind: "mutation",
|
|
60511
61070
|
auth: "protected"
|
|
60512
61071
|
}),
|
|
60513
61072
|
setPower: method(object({
|
|
60514
|
-
deviceId: DeviceIdSchema,
|
|
61073
|
+
deviceId: DeviceIdSchema$1,
|
|
60515
61074
|
on: boolean()
|
|
60516
61075
|
}), _void(), {
|
|
60517
61076
|
kind: "mutation",
|
|
60518
61077
|
auth: "protected"
|
|
60519
61078
|
}),
|
|
60520
61079
|
setHalfLoad: method(object({
|
|
60521
|
-
deviceId: DeviceIdSchema,
|
|
61080
|
+
deviceId: DeviceIdSchema$1,
|
|
60522
61081
|
on: boolean()
|
|
60523
61082
|
}), _void(), {
|
|
60524
61083
|
kind: "mutation",
|
|
60525
61084
|
auth: "protected"
|
|
60526
61085
|
}),
|
|
60527
61086
|
setHygienePlus: method(object({
|
|
60528
|
-
deviceId: DeviceIdSchema,
|
|
61087
|
+
deviceId: DeviceIdSchema$1,
|
|
60529
61088
|
on: boolean()
|
|
60530
61089
|
}), _void(), {
|
|
60531
61090
|
kind: "mutation",
|
|
60532
61091
|
auth: "protected"
|
|
60533
61092
|
}),
|
|
60534
61093
|
setExtraDry: method(object({
|
|
60535
|
-
deviceId: DeviceIdSchema,
|
|
61094
|
+
deviceId: DeviceIdSchema$1,
|
|
60536
61095
|
on: boolean()
|
|
60537
61096
|
}), _void(), {
|
|
60538
61097
|
kind: "mutation",
|
|
60539
61098
|
auth: "protected"
|
|
60540
61099
|
}),
|
|
60541
61100
|
setIntensiveZone: method(object({
|
|
60542
|
-
deviceId: DeviceIdSchema,
|
|
61101
|
+
deviceId: DeviceIdSchema$1,
|
|
60543
61102
|
on: boolean()
|
|
60544
61103
|
}), _void(), {
|
|
60545
61104
|
kind: "mutation",
|
|
60546
61105
|
auth: "protected"
|
|
60547
61106
|
}),
|
|
60548
61107
|
setVarioSpeedPlus: method(object({
|
|
60549
|
-
deviceId: DeviceIdSchema,
|
|
61108
|
+
deviceId: DeviceIdSchema$1,
|
|
60550
61109
|
on: boolean()
|
|
60551
61110
|
}), _void(), {
|
|
60552
61111
|
kind: "mutation",
|
|
60553
61112
|
auth: "protected"
|
|
60554
61113
|
}),
|
|
60555
61114
|
setChildLock: method(object({
|
|
60556
|
-
deviceId: DeviceIdSchema,
|
|
61115
|
+
deviceId: DeviceIdSchema$1,
|
|
60557
61116
|
on: boolean()
|
|
60558
61117
|
}), _void(), {
|
|
60559
61118
|
kind: "mutation",
|
|
@@ -60565,7 +61124,7 @@ var dishwasherCapability = {
|
|
|
60565
61124
|
* its declared range / members.
|
|
60566
61125
|
*/
|
|
60567
61126
|
setOption: method(object({
|
|
60568
|
-
deviceId: DeviceIdSchema,
|
|
61127
|
+
deviceId: DeviceIdSchema$1,
|
|
60569
61128
|
key: string().min(1),
|
|
60570
61129
|
value: DishwasherValueSchema
|
|
60571
61130
|
}), _void(), {
|
|
@@ -60573,7 +61132,7 @@ var dishwasherCapability = {
|
|
|
60573
61132
|
auth: "protected"
|
|
60574
61133
|
}),
|
|
60575
61134
|
/** What this appliance can do, from its own description — the UI is derived from it. */
|
|
60576
|
-
getOptions: method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" })
|
|
61135
|
+
getOptions: method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" })
|
|
60577
61136
|
},
|
|
60578
61137
|
status: {
|
|
60579
61138
|
schema: DishwasherStatusSchema,
|
|
@@ -62944,9 +63503,35 @@ var MediaInfoSchema = object({
|
|
|
62944
63503
|
/** Optional cover-art / thumbnail URL. */
|
|
62945
63504
|
imageUrl: string().optional()
|
|
62946
63505
|
});
|
|
63506
|
+
/**
|
|
63507
|
+
* Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
|
|
63508
|
+
* the device said so. `assumed`: the driver has no feedback (infrared) and
|
|
63509
|
+
* tracks what it last SENT; it moves only after a send was accepted, and a
|
|
63510
|
+
* human at the real remote makes it wrong. A UI shows an assumed value as a
|
|
63511
|
+
* guess, never as a reading.
|
|
63512
|
+
*/
|
|
63513
|
+
var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
|
|
63514
|
+
/** The fields of a media-player status a driver can only guess. */
|
|
63515
|
+
var MediaPlayerAssumedFieldSchema = _enum([
|
|
63516
|
+
"state",
|
|
63517
|
+
"volumeLevel",
|
|
63518
|
+
"isMuted",
|
|
63519
|
+
"source"
|
|
63520
|
+
]);
|
|
62947
63521
|
var MediaPlayerStatusSchema = object({
|
|
62948
63522
|
/** Playback lifecycle state. */
|
|
62949
63523
|
state: MediaPlayerStateSchema,
|
|
63524
|
+
/**
|
|
63525
|
+
* Whether the state fields are readings or the driver's own bookkeeping.
|
|
63526
|
+
* A slice written before this field existed parses as `reported`.
|
|
63527
|
+
*/
|
|
63528
|
+
stateSource: MediaPlayerStateSourceSchema.default("reported"),
|
|
63529
|
+
/**
|
|
63530
|
+
* Which fields are the driver's own guess. `stateSource` is `assumed` when
|
|
63531
|
+
* this is not empty; a field not listed here is a reading even then (power
|
|
63532
|
+
* from a running-state source, mute guessed from the last send).
|
|
63533
|
+
*/
|
|
63534
|
+
assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
|
|
62950
63535
|
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
62951
63536
|
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
62952
63537
|
volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
@@ -64238,7 +64823,8 @@ var TopologyNodeSchema = object({
|
|
|
64238
64823
|
isHub: boolean(),
|
|
64239
64824
|
isOnline: boolean(),
|
|
64240
64825
|
cpuPercent: number(),
|
|
64241
|
-
|
|
64826
|
+
/** `null` when the node's memory read failed or it has not reported one (D393). */
|
|
64827
|
+
memoryPercent: number().nullable(),
|
|
64242
64828
|
uptime: number(),
|
|
64243
64829
|
lastSeen: string(),
|
|
64244
64830
|
localIps: array(string()).readonly(),
|
|
@@ -64856,9 +65442,10 @@ var PetFeederStatusSchema = object({
|
|
|
64856
65442
|
* combined reading; per-hopper levels live in `food1`/`food2`. Null when
|
|
64857
65443
|
* the device has not reported yet. */
|
|
64858
65444
|
foodLevel: number().nullable().meta(readingMeta("none")),
|
|
64859
|
-
/** Food in the BOWL
|
|
64860
|
-
* feeder
|
|
64861
|
-
|
|
65445
|
+
/** Food in the BOWL, a mass (stored in kilograms, D944). Reported only by a
|
|
65446
|
+
* feeder with a bowl scale; null when the model has none or its reading is
|
|
65447
|
+
* of an unproven unit. */
|
|
65448
|
+
bowlFood: number().nullable().meta(readingMeta("mass")),
|
|
64862
65449
|
/** True while the pet is eating (`state.eating`). */
|
|
64863
65450
|
eating: boolean(),
|
|
64864
65451
|
/** Whether `markFoodReplenished` does anything on this model. When false
|
|
@@ -64874,9 +65461,14 @@ var PetFeederStatusSchema = object({
|
|
|
64874
65461
|
* (`device_class: problem`, on = low). True when the tank reports empty,
|
|
64875
65462
|
* or, on a dual-hopper model, when EITHER hopper is empty. */
|
|
64876
65463
|
lowFood: boolean(),
|
|
64877
|
-
/** Battery charge 0..100 (%).
|
|
64878
|
-
*
|
|
65464
|
+
/** Battery charge 0..100 (%). On a feeder running on `mains` this is the
|
|
65465
|
+
* BACKUP battery's charge, not the power source. Null when the device has
|
|
65466
|
+
* no battery reading. */
|
|
64879
65467
|
batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
65468
|
+
/** What the feeder is powered from right now: `mains` (plugged in, the
|
|
65469
|
+
* battery is only a backup) or `battery`. Null when the device does not
|
|
65470
|
+
* report it. */
|
|
65471
|
+
powerSource: _enum(["mains", "battery"]).nullable(),
|
|
64880
65472
|
/**
|
|
64881
65473
|
* Desiccant life remaining, stored in seconds (duration base, D944).
|
|
64882
65474
|
* Providers convert from the days the feeder reports. Null when the
|
|
@@ -64904,6 +65496,14 @@ var PetFeederStatusSchema = object({
|
|
|
64904
65496
|
feedSound: boolean(),
|
|
64905
65497
|
/** Speaker / prompt volume level (device-scaled integer). */
|
|
64906
65498
|
volume: number().meta(readingMeta("none")),
|
|
65499
|
+
/** Portion (grams) of the last manual feed sent from a single-hopper
|
|
65500
|
+
* stepper — the default the next manual feed starts from. Persisted per
|
|
65501
|
+
* device by the provider; null until a manual feed has been sent. */
|
|
65502
|
+
lastFeedGrams: gramsPortion.nullable(),
|
|
65503
|
+
/** Hopper-1 portion of the last manual feed (dual-hopper); null until sent. */
|
|
65504
|
+
lastFeedHopper1: gramsPortion.nullable(),
|
|
65505
|
+
/** Hopper-2 portion of the last manual feed (dual-hopper); null until sent. */
|
|
65506
|
+
lastFeedHopper2: gramsPortion.nullable(),
|
|
64907
65507
|
/** Ms epoch when the slice was last refreshed from the cloud. */
|
|
64908
65508
|
lastFetchedAt: number()
|
|
64909
65509
|
});
|
|
@@ -65029,6 +65629,137 @@ var petFeederCapability = {
|
|
|
65029
65629
|
}
|
|
65030
65630
|
} }
|
|
65031
65631
|
};
|
|
65632
|
+
/**
|
|
65633
|
+
* Pet water-fountain cap. Models the telemetry and the control surface of a
|
|
65634
|
+
* circulating drinking fountain: how much water is in the tank, how much the
|
|
65635
|
+
* pet drank, whether the pump runs and on what duty cycle.
|
|
65636
|
+
*
|
|
65637
|
+
* It has no native vendor provider of its own: a fountain reaches CamStack as
|
|
65638
|
+
* a container of loose entities (a pump switch, two number boxes, a "filter
|
|
65639
|
+
* changed" button), and an operator BUILDS the device from them — a composed
|
|
65640
|
+
* device of this type (D659, D967). Every method is therefore a forward to ONE
|
|
65641
|
+
* method of ONE source entity, and every reading is a field derived from a
|
|
65642
|
+
* source entity; the provider-shaped interface below exists so the composed
|
|
65643
|
+
* device and any future native provider answer the same contract.
|
|
65644
|
+
*
|
|
65645
|
+
* Quantities are stored in the base unit of their kind (D944): volumes in
|
|
65646
|
+
* LITRES, durations in SECONDS, the tank level in percent. A source that
|
|
65647
|
+
* reports millilitres or minutes is converted in the composition's expression,
|
|
65648
|
+
* never here. Every reading may be `null`: a source that is offline or does
|
|
65649
|
+
* not report is unknown, never zero.
|
|
65650
|
+
*
|
|
65651
|
+
* The filter is not a field of this cap: it is an item of the `consumables`
|
|
65652
|
+
* cap on the same device (kind `wear`), reset by the same forward mechanism.
|
|
65653
|
+
*/
|
|
65654
|
+
var PetFountainStatusSchema = object({
|
|
65655
|
+
/** Water in the tank, 0..100 (%). Null while the source has not reported. */
|
|
65656
|
+
waterLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
65657
|
+
/** Water in the tank, in litres. Null while the source has not reported. */
|
|
65658
|
+
waterVolume: number().min(0).nullable().meta(readingMeta("volume")),
|
|
65659
|
+
/** True when the tank is empty. Null while unknown. */
|
|
65660
|
+
empty: boolean().nullable(),
|
|
65661
|
+
/** True while the pump is running. Null while unknown. */
|
|
65662
|
+
pumpRunning: boolean().nullable(),
|
|
65663
|
+
/** True when the pump works on an on/off duty cycle rather than continuously. */
|
|
65664
|
+
pumpIntervalEnabled: boolean().nullable(),
|
|
65665
|
+
/** How long the pump runs per cycle, in seconds. */
|
|
65666
|
+
pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
65667
|
+
/** How long the pump rests per cycle, in seconds. */
|
|
65668
|
+
pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
|
|
65669
|
+
/** Water drunk in the last drink, in litres. */
|
|
65670
|
+
lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
65671
|
+
/** Water drunk in the current hour, in litres. */
|
|
65672
|
+
currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
65673
|
+
/** Water drunk over the last 24 hours (rolling), in litres. */
|
|
65674
|
+
last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
65675
|
+
/** Water drunk since local midnight, in litres. */
|
|
65676
|
+
todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
|
|
65677
|
+
/** Decoded fault message. Null means healthy; a non-null string is an active fault. */
|
|
65678
|
+
error: string().nullable(),
|
|
65679
|
+
/** Ms epoch when the slice was last refreshed. */
|
|
65680
|
+
lastFetchedAt: number()
|
|
65681
|
+
});
|
|
65682
|
+
var DeviceIdSchema = number().int().nonnegative();
|
|
65683
|
+
var MinutesSchema = number().int().min(0);
|
|
65684
|
+
var petFountainCapability = {
|
|
65685
|
+
name: "pet-fountain",
|
|
65686
|
+
scope: "device",
|
|
65687
|
+
deviceNative: true,
|
|
65688
|
+
mode: "singleton",
|
|
65689
|
+
deviceTypes: [DeviceType.PetFountain],
|
|
65690
|
+
methods: {
|
|
65691
|
+
/** Start or stop the pump. */
|
|
65692
|
+
setPump: method(object({
|
|
65693
|
+
deviceId: DeviceIdSchema,
|
|
65694
|
+
on: boolean()
|
|
65695
|
+
}), _void(), {
|
|
65696
|
+
kind: "mutation",
|
|
65697
|
+
auth: "protected"
|
|
65698
|
+
}),
|
|
65699
|
+
/** Switch the on/off duty cycle on or off (off = the pump runs continuously). */
|
|
65700
|
+
setPumpInterval: method(object({
|
|
65701
|
+
deviceId: DeviceIdSchema,
|
|
65702
|
+
on: boolean()
|
|
65703
|
+
}), _void(), {
|
|
65704
|
+
kind: "mutation",
|
|
65705
|
+
auth: "protected"
|
|
65706
|
+
}),
|
|
65707
|
+
/** Set how many minutes the pump runs per cycle. */
|
|
65708
|
+
setPumpOnMinutes: method(object({
|
|
65709
|
+
deviceId: DeviceIdSchema,
|
|
65710
|
+
minutes: MinutesSchema
|
|
65711
|
+
}), _void(), {
|
|
65712
|
+
kind: "mutation",
|
|
65713
|
+
auth: "protected"
|
|
65714
|
+
}),
|
|
65715
|
+
/** Set how many minutes the pump rests per cycle. */
|
|
65716
|
+
setPumpOffMinutes: method(object({
|
|
65717
|
+
deviceId: DeviceIdSchema,
|
|
65718
|
+
minutes: MinutesSchema
|
|
65719
|
+
}), _void(), {
|
|
65720
|
+
kind: "mutation",
|
|
65721
|
+
auth: "protected"
|
|
65722
|
+
}),
|
|
65723
|
+
/** Clear the rolling daily consumption counters on the device. */
|
|
65724
|
+
resetDailyConsumption: method(object({ deviceId: DeviceIdSchema }), _void(), {
|
|
65725
|
+
kind: "mutation",
|
|
65726
|
+
auth: "protected"
|
|
65727
|
+
})
|
|
65728
|
+
},
|
|
65729
|
+
status: {
|
|
65730
|
+
schema: PetFountainStatusSchema,
|
|
65731
|
+
kind: "push"
|
|
65732
|
+
},
|
|
65733
|
+
/** Runtime-state slice — mirrored by the kernel; the hero card reads it whole. */
|
|
65734
|
+
runtimeState: PetFountainStatusSchema,
|
|
65735
|
+
/**
|
|
65736
|
+
* Runtime-state durability: **session** — live appliance state re-published on connect.
|
|
65737
|
+
*
|
|
65738
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
65739
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
65740
|
+
*/
|
|
65741
|
+
durability: "session",
|
|
65742
|
+
/** History (D850): recorded series. */
|
|
65743
|
+
history: { fields: {
|
|
65744
|
+
waterLevel: {
|
|
65745
|
+
kind: "gauge",
|
|
65746
|
+
unit: "%",
|
|
65747
|
+
deadband: 1
|
|
65748
|
+
},
|
|
65749
|
+
waterVolume: {
|
|
65750
|
+
kind: "gauge",
|
|
65751
|
+
unit: "L"
|
|
65752
|
+
},
|
|
65753
|
+
todayDrink: {
|
|
65754
|
+
kind: "gauge",
|
|
65755
|
+
unit: "L"
|
|
65756
|
+
},
|
|
65757
|
+
last24hDrink: {
|
|
65758
|
+
kind: "gauge",
|
|
65759
|
+
unit: "L"
|
|
65760
|
+
}
|
|
65761
|
+
} }
|
|
65762
|
+
};
|
|
65032
65763
|
var VehicleSchema = object({
|
|
65033
65764
|
id: string(),
|
|
65034
65765
|
name: string(),
|
|
@@ -70941,9 +71672,17 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
|
|
|
70941
71672
|
kind: "mutation",
|
|
70942
71673
|
auth: "admin"
|
|
70943
71674
|
});
|
|
71675
|
+
discriminatedUnion("kind", [object({
|
|
71676
|
+
kind: literal("raw"),
|
|
71677
|
+
timings: array(number().int()),
|
|
71678
|
+
settings: TransmitSettingsSchema.optional()
|
|
71679
|
+
}), object({
|
|
71680
|
+
kind: literal("pronto"),
|
|
71681
|
+
code: string()
|
|
71682
|
+
})]);
|
|
70944
71683
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
70945
71684
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
70946
|
-
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;
|
|
71685
|
+
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;
|
|
70947
71686
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
70948
71687
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
70949
71688
|
new Set(CAP_NAMES);
|
|
@@ -71294,6 +72033,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
71294
72033
|
notifier: notifierCapability,
|
|
71295
72034
|
numericSensor: numericSensorCapability,
|
|
71296
72035
|
petFeeder: petFeederCapability,
|
|
72036
|
+
petFountain: petFountainCapability,
|
|
71297
72037
|
powerMeter: powerMeterCapability,
|
|
71298
72038
|
presence: presenceCapability,
|
|
71299
72039
|
pressureSensor: pressureSensorCapability,
|
|
@@ -72544,7 +73284,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
72544
73284
|
return toDeviceSummary(device, this.addonId);
|
|
72545
73285
|
}
|
|
72546
73286
|
};
|
|
72547
|
-
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;
|
|
73287
|
+
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;
|
|
72548
73288
|
new Set(Object.values(DeviceType));
|
|
72549
73289
|
Object.freeze({
|
|
72550
73290
|
"accessories.getStatus": {
|
|
@@ -74479,6 +75219,13 @@ Object.freeze({
|
|
|
74479
75219
|
access: "create",
|
|
74480
75220
|
role: "admin"
|
|
74481
75221
|
},
|
|
75222
|
+
"deviceExport.exposeEntity": {
|
|
75223
|
+
capName: "device-export",
|
|
75224
|
+
capScope: "system",
|
|
75225
|
+
addonId: null,
|
|
75226
|
+
access: "create",
|
|
75227
|
+
role: "admin"
|
|
75228
|
+
},
|
|
74482
75229
|
"deviceExport.getDeviceLiveContribution": {
|
|
74483
75230
|
capName: "device-export",
|
|
74484
75231
|
capScope: "system",
|
|
@@ -74507,6 +75254,13 @@ Object.freeze({
|
|
|
74507
75254
|
access: "view",
|
|
74508
75255
|
role: "admin"
|
|
74509
75256
|
},
|
|
75257
|
+
"deviceExport.listExposedEntities": {
|
|
75258
|
+
capName: "device-export",
|
|
75259
|
+
capScope: "system",
|
|
75260
|
+
addonId: null,
|
|
75261
|
+
access: "view",
|
|
75262
|
+
role: "admin"
|
|
75263
|
+
},
|
|
74510
75264
|
"deviceExport.listSupportedDeviceKinds": {
|
|
74511
75265
|
capName: "device-export",
|
|
74512
75266
|
capScope: "system",
|
|
@@ -74514,6 +75268,13 @@ Object.freeze({
|
|
|
74514
75268
|
access: "view",
|
|
74515
75269
|
role: "admin"
|
|
74516
75270
|
},
|
|
75271
|
+
"deviceExport.listSupportedEntityKinds": {
|
|
75272
|
+
capName: "device-export",
|
|
75273
|
+
capScope: "system",
|
|
75274
|
+
addonId: null,
|
|
75275
|
+
access: "view",
|
|
75276
|
+
role: "admin"
|
|
75277
|
+
},
|
|
74517
75278
|
"deviceExport.unexposeDevice": {
|
|
74518
75279
|
capName: "device-export",
|
|
74519
75280
|
capScope: "system",
|
|
@@ -74521,6 +75282,13 @@ Object.freeze({
|
|
|
74521
75282
|
access: "create",
|
|
74522
75283
|
role: "admin"
|
|
74523
75284
|
},
|
|
75285
|
+
"deviceExport.unexposeEntity": {
|
|
75286
|
+
capName: "device-export",
|
|
75287
|
+
capScope: "system",
|
|
75288
|
+
addonId: null,
|
|
75289
|
+
access: "create",
|
|
75290
|
+
role: "admin"
|
|
75291
|
+
},
|
|
74524
75292
|
"deviceExtension.listCandidates": {
|
|
74525
75293
|
capName: "device-extension",
|
|
74526
75294
|
capScope: "device",
|
|
@@ -76096,6 +76864,20 @@ Object.freeze({
|
|
|
76096
76864
|
access: "create",
|
|
76097
76865
|
role: "viewer"
|
|
76098
76866
|
},
|
|
76867
|
+
"irTransmitter.getInfo": {
|
|
76868
|
+
capName: "ir-transmitter",
|
|
76869
|
+
capScope: "device",
|
|
76870
|
+
addonId: null,
|
|
76871
|
+
access: "view",
|
|
76872
|
+
role: "viewer"
|
|
76873
|
+
},
|
|
76874
|
+
"irTransmitter.send": {
|
|
76875
|
+
capName: "ir-transmitter",
|
|
76876
|
+
capScope: "device",
|
|
76877
|
+
addonId: null,
|
|
76878
|
+
access: "create",
|
|
76879
|
+
role: "viewer"
|
|
76880
|
+
},
|
|
76099
76881
|
"keychain.delete": {
|
|
76100
76882
|
capName: "keychain",
|
|
76101
76883
|
capScope: "system",
|
|
@@ -77328,6 +78110,13 @@ Object.freeze({
|
|
|
77328
78110
|
access: "create",
|
|
77329
78111
|
role: "admin"
|
|
77330
78112
|
},
|
|
78113
|
+
"nodeRemovalHook.onNodeRemoved": {
|
|
78114
|
+
capName: "node-removal-hook",
|
|
78115
|
+
capScope: "system",
|
|
78116
|
+
addonId: null,
|
|
78117
|
+
access: "create",
|
|
78118
|
+
role: "admin"
|
|
78119
|
+
},
|
|
77331
78120
|
"nodes.clusterAddonStatus": {
|
|
77332
78121
|
capName: "nodes",
|
|
77333
78122
|
capScope: "system",
|
|
@@ -77965,6 +78754,48 @@ Object.freeze({
|
|
|
77965
78754
|
access: "create",
|
|
77966
78755
|
role: "viewer"
|
|
77967
78756
|
},
|
|
78757
|
+
"petFountain.getStatus": {
|
|
78758
|
+
capName: "pet-fountain",
|
|
78759
|
+
capScope: "device",
|
|
78760
|
+
addonId: null,
|
|
78761
|
+
access: "view",
|
|
78762
|
+
role: "viewer"
|
|
78763
|
+
},
|
|
78764
|
+
"petFountain.resetDailyConsumption": {
|
|
78765
|
+
capName: "pet-fountain",
|
|
78766
|
+
capScope: "device",
|
|
78767
|
+
addonId: null,
|
|
78768
|
+
access: "delete",
|
|
78769
|
+
role: "viewer"
|
|
78770
|
+
},
|
|
78771
|
+
"petFountain.setPump": {
|
|
78772
|
+
capName: "pet-fountain",
|
|
78773
|
+
capScope: "device",
|
|
78774
|
+
addonId: null,
|
|
78775
|
+
access: "create",
|
|
78776
|
+
role: "viewer"
|
|
78777
|
+
},
|
|
78778
|
+
"petFountain.setPumpInterval": {
|
|
78779
|
+
capName: "pet-fountain",
|
|
78780
|
+
capScope: "device",
|
|
78781
|
+
addonId: null,
|
|
78782
|
+
access: "create",
|
|
78783
|
+
role: "viewer"
|
|
78784
|
+
},
|
|
78785
|
+
"petFountain.setPumpOffMinutes": {
|
|
78786
|
+
capName: "pet-fountain",
|
|
78787
|
+
capScope: "device",
|
|
78788
|
+
addonId: null,
|
|
78789
|
+
access: "create",
|
|
78790
|
+
role: "viewer"
|
|
78791
|
+
},
|
|
78792
|
+
"petFountain.setPumpOnMinutes": {
|
|
78793
|
+
capName: "pet-fountain",
|
|
78794
|
+
capScope: "device",
|
|
78795
|
+
addonId: null,
|
|
78796
|
+
access: "create",
|
|
78797
|
+
role: "viewer"
|
|
78798
|
+
},
|
|
77968
78799
|
"pipelineAnalytics.addRetrainFrameFromRecording": {
|
|
77969
78800
|
capName: "pipeline-analytics",
|
|
77970
78801
|
capScope: "device",
|
|
@@ -80219,6 +81050,41 @@ Object.freeze({
|
|
|
80219
81050
|
access: "create",
|
|
80220
81051
|
role: "admin"
|
|
80221
81052
|
},
|
|
81053
|
+
"remote.getConfig": {
|
|
81054
|
+
capName: "remote",
|
|
81055
|
+
capScope: "system",
|
|
81056
|
+
addonId: null,
|
|
81057
|
+
access: "view",
|
|
81058
|
+
role: "admin"
|
|
81059
|
+
},
|
|
81060
|
+
"remote.listCommands": {
|
|
81061
|
+
capName: "remote",
|
|
81062
|
+
capScope: "system",
|
|
81063
|
+
addonId: null,
|
|
81064
|
+
access: "view",
|
|
81065
|
+
role: "admin"
|
|
81066
|
+
},
|
|
81067
|
+
"remote.sendCommand": {
|
|
81068
|
+
capName: "remote",
|
|
81069
|
+
capScope: "system",
|
|
81070
|
+
addonId: null,
|
|
81071
|
+
access: "create",
|
|
81072
|
+
role: "admin"
|
|
81073
|
+
},
|
|
81074
|
+
"remote.sendMediaCommand": {
|
|
81075
|
+
capName: "remote",
|
|
81076
|
+
capScope: "system",
|
|
81077
|
+
addonId: null,
|
|
81078
|
+
access: "create",
|
|
81079
|
+
role: "admin"
|
|
81080
|
+
},
|
|
81081
|
+
"remote.setConfig": {
|
|
81082
|
+
capName: "remote",
|
|
81083
|
+
capScope: "system",
|
|
81084
|
+
addonId: null,
|
|
81085
|
+
access: "create",
|
|
81086
|
+
role: "admin"
|
|
81087
|
+
},
|
|
80222
81088
|
"runningState.getStatus": {
|
|
80223
81089
|
capName: "running-state",
|
|
80224
81090
|
capScope: "device",
|
|
@@ -80331,6 +81197,13 @@ Object.freeze({
|
|
|
80331
81197
|
access: "create",
|
|
80332
81198
|
role: "admin"
|
|
80333
81199
|
},
|
|
81200
|
+
"serverManagement.getLaunchOverrides": {
|
|
81201
|
+
capName: "server-management",
|
|
81202
|
+
capScope: "system",
|
|
81203
|
+
addonId: null,
|
|
81204
|
+
access: "view",
|
|
81205
|
+
role: "admin"
|
|
81206
|
+
},
|
|
80334
81207
|
"serverManagement.getServerPackageStatus": {
|
|
80335
81208
|
capName: "server-management",
|
|
80336
81209
|
capScope: "system",
|
|
@@ -80352,6 +81225,13 @@ Object.freeze({
|
|
|
80352
81225
|
access: "create",
|
|
80353
81226
|
role: "admin"
|
|
80354
81227
|
},
|
|
81228
|
+
"serverManagement.setLaunchOverrides": {
|
|
81229
|
+
capName: "server-management",
|
|
81230
|
+
capScope: "system",
|
|
81231
|
+
addonId: null,
|
|
81232
|
+
access: "create",
|
|
81233
|
+
role: "admin"
|
|
81234
|
+
},
|
|
80355
81235
|
"settingsStore.aggregate": {
|
|
80356
81236
|
capName: "settings-store",
|
|
80357
81237
|
capScope: "system",
|
|
@@ -81409,6 +82289,41 @@ Object.freeze({
|
|
|
81409
82289
|
access: "view",
|
|
81410
82290
|
role: "viewer"
|
|
81411
82291
|
},
|
|
82292
|
+
"template.create": {
|
|
82293
|
+
capName: "template",
|
|
82294
|
+
capScope: "system",
|
|
82295
|
+
addonId: null,
|
|
82296
|
+
access: "create",
|
|
82297
|
+
role: "admin"
|
|
82298
|
+
},
|
|
82299
|
+
"template.delete": {
|
|
82300
|
+
capName: "template",
|
|
82301
|
+
capScope: "system",
|
|
82302
|
+
addonId: null,
|
|
82303
|
+
access: "delete",
|
|
82304
|
+
role: "admin"
|
|
82305
|
+
},
|
|
82306
|
+
"template.get": {
|
|
82307
|
+
capName: "template",
|
|
82308
|
+
capScope: "system",
|
|
82309
|
+
addonId: null,
|
|
82310
|
+
access: "view",
|
|
82311
|
+
role: "admin"
|
|
82312
|
+
},
|
|
82313
|
+
"template.list": {
|
|
82314
|
+
capName: "template",
|
|
82315
|
+
capScope: "system",
|
|
82316
|
+
addonId: null,
|
|
82317
|
+
access: "view",
|
|
82318
|
+
role: "admin"
|
|
82319
|
+
},
|
|
82320
|
+
"template.update": {
|
|
82321
|
+
capName: "template",
|
|
82322
|
+
capScope: "system",
|
|
82323
|
+
addonId: null,
|
|
82324
|
+
access: "create",
|
|
82325
|
+
role: "admin"
|
|
82326
|
+
},
|
|
81412
82327
|
"terminalSession.adoptLegacyMonitor": {
|
|
81413
82328
|
capName: "terminal-session",
|
|
81414
82329
|
capScope: "system",
|
|
@@ -83719,6 +84634,11 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
83719
84634
|
icon: "circle-dot"
|
|
83720
84635
|
}]
|
|
83721
84636
|
},
|
|
84637
|
+
"ir-transmitter": {
|
|
84638
|
+
scope: "device",
|
|
84639
|
+
noun: t("IR / RF transmitter", "Trasmettitore IR / RF"),
|
|
84640
|
+
notOperatorFacing: { send: "a raw pulse train is not something an operator composes in an automation; the remote that owns the command table sends it" }
|
|
84641
|
+
},
|
|
83722
84642
|
control: {
|
|
83723
84643
|
scope: "device",
|
|
83724
84644
|
noun: t("Control", "Controllo"),
|
|
@@ -84372,6 +85292,100 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
84372
85292
|
}
|
|
84373
85293
|
]
|
|
84374
85294
|
},
|
|
85295
|
+
"pet-fountain": {
|
|
85296
|
+
scope: "device",
|
|
85297
|
+
noun: t("Pet fountain", "Fontana per animali"),
|
|
85298
|
+
actions: [
|
|
85299
|
+
{
|
|
85300
|
+
id: "petFountain.pumpOn",
|
|
85301
|
+
method: "setPump",
|
|
85302
|
+
label: t("Pump on", "Pompa accesa"),
|
|
85303
|
+
phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
|
|
85304
|
+
icon: "play",
|
|
85305
|
+
args: { on: true }
|
|
85306
|
+
},
|
|
85307
|
+
{
|
|
85308
|
+
id: "petFountain.pumpOff",
|
|
85309
|
+
method: "setPump",
|
|
85310
|
+
label: t("Pump off", "Pompa spenta"),
|
|
85311
|
+
phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
|
|
85312
|
+
icon: "square",
|
|
85313
|
+
args: { on: false }
|
|
85314
|
+
},
|
|
85315
|
+
{
|
|
85316
|
+
id: "petFountain.intervalOn",
|
|
85317
|
+
method: "setPumpInterval",
|
|
85318
|
+
label: t("Pump cycle on", "Ciclo pompa attivo"),
|
|
85319
|
+
phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
|
|
85320
|
+
icon: "timer",
|
|
85321
|
+
args: { on: true }
|
|
85322
|
+
},
|
|
85323
|
+
{
|
|
85324
|
+
id: "petFountain.intervalOff",
|
|
85325
|
+
method: "setPumpInterval",
|
|
85326
|
+
label: t("Pump cycle off", "Ciclo pompa spento"),
|
|
85327
|
+
phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
|
|
85328
|
+
icon: "timer-off",
|
|
85329
|
+
args: { on: false }
|
|
85330
|
+
},
|
|
85331
|
+
{
|
|
85332
|
+
id: "petFountain.pumpOnMinutes",
|
|
85333
|
+
method: "setPumpOnMinutes",
|
|
85334
|
+
label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
|
|
85335
|
+
phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
|
|
85336
|
+
icon: "timer",
|
|
85337
|
+
openArgs: [{
|
|
85338
|
+
key: "minutes",
|
|
85339
|
+
label: t("Minutes", "Minuti"),
|
|
85340
|
+
offer: {
|
|
85341
|
+
kind: "number",
|
|
85342
|
+
min: 0,
|
|
85343
|
+
step: 1,
|
|
85344
|
+
unit: "min"
|
|
85345
|
+
}
|
|
85346
|
+
}]
|
|
85347
|
+
},
|
|
85348
|
+
{
|
|
85349
|
+
id: "petFountain.pumpOffMinutes",
|
|
85350
|
+
method: "setPumpOffMinutes",
|
|
85351
|
+
label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
|
|
85352
|
+
phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
|
|
85353
|
+
icon: "timer-off",
|
|
85354
|
+
openArgs: [{
|
|
85355
|
+
key: "minutes",
|
|
85356
|
+
label: t("Minutes", "Minuti"),
|
|
85357
|
+
offer: {
|
|
85358
|
+
kind: "number",
|
|
85359
|
+
min: 0,
|
|
85360
|
+
step: 1,
|
|
85361
|
+
unit: "min"
|
|
85362
|
+
}
|
|
85363
|
+
}]
|
|
85364
|
+
},
|
|
85365
|
+
{
|
|
85366
|
+
id: "petFountain.resetDailyConsumption",
|
|
85367
|
+
method: "resetDailyConsumption",
|
|
85368
|
+
label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
|
|
85369
|
+
phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
|
|
85370
|
+
icon: "rotate-ccw"
|
|
85371
|
+
}
|
|
85372
|
+
],
|
|
85373
|
+
state: [{
|
|
85374
|
+
kind: "boolean",
|
|
85375
|
+
field: "empty",
|
|
85376
|
+
label: t("Tank empty", "Serbatoio vuoto"),
|
|
85377
|
+
whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
|
|
85378
|
+
whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
|
|
85379
|
+
icon: "droplet"
|
|
85380
|
+
}, {
|
|
85381
|
+
kind: "numeric",
|
|
85382
|
+
field: "waterLevel",
|
|
85383
|
+
quantity: t("the water level", "il livello dell’acqua"),
|
|
85384
|
+
label: t("Water level", "Livello dell’acqua"),
|
|
85385
|
+
unit: "%",
|
|
85386
|
+
icon: "droplets"
|
|
85387
|
+
}]
|
|
85388
|
+
},
|
|
84375
85389
|
"media-player": {
|
|
84376
85390
|
scope: "device",
|
|
84377
85391
|
noun: t("Media player", "Lettore multimediale"),
|
|
@@ -86196,6 +87210,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
86196
87210
|
"device-manager": system("the device registry itself — rename, move and remove are administration"),
|
|
86197
87211
|
"device-provider": system(INTERNAL),
|
|
86198
87212
|
"device-removal-hook": system(INTERNAL),
|
|
87213
|
+
"node-removal-hook": system(INTERNAL),
|
|
86199
87214
|
"device-state": system("the runtime-state mirror every trigger already reads through"),
|
|
86200
87215
|
"display-palette": system("the cluster display palette — one colour table for every client, not a device action"),
|
|
86201
87216
|
"embedding-encoder": system(ML),
|
|
@@ -86235,6 +87250,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
86235
87250
|
"recording-archive": system(STORAGE),
|
|
86236
87251
|
"recording-export": system("recording exports — requested from the timeline, not an automation step"),
|
|
86237
87252
|
records: system(INTERNAL),
|
|
87253
|
+
remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
|
|
86238
87254
|
"server-management": system(CLUSTER),
|
|
86239
87255
|
"settings-store": system(INTERNAL),
|
|
86240
87256
|
"smtp-provider": system(INTERNAL),
|
|
@@ -86247,6 +87263,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
86247
87263
|
"storage-provider": system(STORAGE),
|
|
86248
87264
|
"stream-broker": system(PIPELINE),
|
|
86249
87265
|
system: system(CLUSTER),
|
|
87266
|
+
template: system("operator-authored templates — edited in their own editor, never actuated"),
|
|
86250
87267
|
"terminal-session": system(CLUSTER),
|
|
86251
87268
|
toast: system(UI_PLUMBING),
|
|
86252
87269
|
training: system(ML),
|
|
@@ -86306,7 +87323,7 @@ function projectAction(cap, action) {
|
|
|
86306
87323
|
};
|
|
86307
87324
|
}
|
|
86308
87325
|
Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
|
|
86309
|
-
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;
|
|
87326
|
+
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;
|
|
86310
87327
|
DeviceFeature.BatteryOperated;
|
|
86311
87328
|
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
86312
87329
|
var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";
|
|
@@ -87438,6 +88455,16 @@ Object.freeze({
|
|
|
87438
88455
|
form: "single",
|
|
87439
88456
|
optional: false
|
|
87440
88457
|
}],
|
|
88458
|
+
"irTransmitter.getInfo": [{
|
|
88459
|
+
name: "deviceId",
|
|
88460
|
+
form: "single",
|
|
88461
|
+
optional: false
|
|
88462
|
+
}],
|
|
88463
|
+
"irTransmitter.send": [{
|
|
88464
|
+
name: "deviceId",
|
|
88465
|
+
form: "single",
|
|
88466
|
+
optional: false
|
|
88467
|
+
}],
|
|
87441
88468
|
"keychain.deleteDeviceScope": [{
|
|
87442
88469
|
name: "deviceId",
|
|
87443
88470
|
form: "single",
|
|
@@ -87847,6 +88874,36 @@ Object.freeze({
|
|
|
87847
88874
|
form: "single",
|
|
87848
88875
|
optional: false
|
|
87849
88876
|
}],
|
|
88877
|
+
"petFountain.getStatus": [{
|
|
88878
|
+
name: "deviceId",
|
|
88879
|
+
form: "single",
|
|
88880
|
+
optional: false
|
|
88881
|
+
}],
|
|
88882
|
+
"petFountain.resetDailyConsumption": [{
|
|
88883
|
+
name: "deviceId",
|
|
88884
|
+
form: "single",
|
|
88885
|
+
optional: false
|
|
88886
|
+
}],
|
|
88887
|
+
"petFountain.setPump": [{
|
|
88888
|
+
name: "deviceId",
|
|
88889
|
+
form: "single",
|
|
88890
|
+
optional: false
|
|
88891
|
+
}],
|
|
88892
|
+
"petFountain.setPumpInterval": [{
|
|
88893
|
+
name: "deviceId",
|
|
88894
|
+
form: "single",
|
|
88895
|
+
optional: false
|
|
88896
|
+
}],
|
|
88897
|
+
"petFountain.setPumpOffMinutes": [{
|
|
88898
|
+
name: "deviceId",
|
|
88899
|
+
form: "single",
|
|
88900
|
+
optional: false
|
|
88901
|
+
}],
|
|
88902
|
+
"petFountain.setPumpOnMinutes": [{
|
|
88903
|
+
name: "deviceId",
|
|
88904
|
+
form: "single",
|
|
88905
|
+
optional: false
|
|
88906
|
+
}],
|
|
87850
88907
|
"pipelineAnalytics.addRetrainFrameFromRecording": [{
|
|
87851
88908
|
name: "deviceId",
|
|
87852
88909
|
form: "single",
|
|
@@ -88663,6 +89720,31 @@ Object.freeze({
|
|
|
88663
89720
|
form: "single",
|
|
88664
89721
|
optional: true
|
|
88665
89722
|
}],
|
|
89723
|
+
"remote.getConfig": [{
|
|
89724
|
+
name: "deviceId",
|
|
89725
|
+
form: "single",
|
|
89726
|
+
optional: false
|
|
89727
|
+
}],
|
|
89728
|
+
"remote.listCommands": [{
|
|
89729
|
+
name: "deviceId",
|
|
89730
|
+
form: "single",
|
|
89731
|
+
optional: false
|
|
89732
|
+
}],
|
|
89733
|
+
"remote.sendCommand": [{
|
|
89734
|
+
name: "deviceId",
|
|
89735
|
+
form: "single",
|
|
89736
|
+
optional: false
|
|
89737
|
+
}],
|
|
89738
|
+
"remote.sendMediaCommand": [{
|
|
89739
|
+
name: "deviceId",
|
|
89740
|
+
form: "single",
|
|
89741
|
+
optional: false
|
|
89742
|
+
}],
|
|
89743
|
+
"remote.setConfig": [{
|
|
89744
|
+
name: "deviceId",
|
|
89745
|
+
form: "single",
|
|
89746
|
+
optional: false
|
|
89747
|
+
}],
|
|
88666
89748
|
"runningState.getStatus": [{
|
|
88667
89749
|
name: "deviceId",
|
|
88668
89750
|
form: "single",
|
|
@@ -90897,6 +91979,81 @@ function rewriteFormBody(body, route) {
|
|
|
90897
91979
|
function filterForwardedCookies(cookieHeader) {
|
|
90898
91980
|
return cookieHeader.split(";").map((part) => part.trim()).filter((part) => part.length > 0 && !part.toLowerCase().startsWith("camstack_")).join("; ");
|
|
90899
91981
|
}
|
|
91982
|
+
/** A short stable label for the attempt: enough to group lines, useless as a capability. */
|
|
91983
|
+
function attemptLabel(attemptId) {
|
|
91984
|
+
let hash = 2166136261;
|
|
91985
|
+
for (let i = 0; i < attemptId.length; i += 1) hash = Math.imul(hash ^ attemptId.charCodeAt(i), 16777619) >>> 0;
|
|
91986
|
+
return hash.toString(16).padStart(8, "0");
|
|
91987
|
+
}
|
|
91988
|
+
/** Distinct parameter names of a `?a=1&b=2` string (or of a urlencoded body), in order. */
|
|
91989
|
+
function paramNames(query) {
|
|
91990
|
+
const names = [];
|
|
91991
|
+
const raw = query.startsWith("?") ? query.slice(1) : query;
|
|
91992
|
+
for (const pair of raw.split("&")) {
|
|
91993
|
+
if (pair.length === 0) continue;
|
|
91994
|
+
const eq = pair.indexOf("=");
|
|
91995
|
+
const rawName = eq === -1 ? pair : pair.slice(0, eq);
|
|
91996
|
+
let name;
|
|
91997
|
+
try {
|
|
91998
|
+
name = decodeURIComponent(rawName.replace(/\+/g, " "));
|
|
91999
|
+
} catch {
|
|
92000
|
+
name = rawName;
|
|
92001
|
+
}
|
|
92002
|
+
if (!names.includes(name)) names.push(name);
|
|
92003
|
+
}
|
|
92004
|
+
return names;
|
|
92005
|
+
}
|
|
92006
|
+
function hasOa2(names) {
|
|
92007
|
+
return names.some((name) => name.startsWith("openid.oa2.") || name === "openid.ns.oa2");
|
|
92008
|
+
}
|
|
92009
|
+
function cookieNamesOf(setCookies) {
|
|
92010
|
+
return setCookies.map((c) => c.split(";")[0]?.split("=")[0]?.trim() ?? "");
|
|
92011
|
+
}
|
|
92012
|
+
function mediaType(contentType) {
|
|
92013
|
+
return (contentType ?? "none").split(";")[0]?.trim().toLowerCase() || "none";
|
|
92014
|
+
}
|
|
92015
|
+
/** Host and path of a Location (no query), or 'unparsable'. */
|
|
92016
|
+
function locationShape(location, base) {
|
|
92017
|
+
try {
|
|
92018
|
+
const url = new URL(location, base);
|
|
92019
|
+
return {
|
|
92020
|
+
target: `${url.host}${url.pathname}`,
|
|
92021
|
+
names: paramNames(url.search)
|
|
92022
|
+
};
|
|
92023
|
+
} catch {
|
|
92024
|
+
return {
|
|
92025
|
+
target: "unparsable",
|
|
92026
|
+
names: []
|
|
92027
|
+
};
|
|
92028
|
+
}
|
|
92029
|
+
}
|
|
92030
|
+
/** The `meta` of one trace line. Values: never. */
|
|
92031
|
+
function flowTraceMeta(input) {
|
|
92032
|
+
const queryNames = paramNames(input.requestSearch);
|
|
92033
|
+
const formNames = input.formBody === null ? null : paramNames(input.formBody);
|
|
92034
|
+
const meta = {
|
|
92035
|
+
session: attemptLabel(input.attemptId),
|
|
92036
|
+
method: input.method,
|
|
92037
|
+
upstream: `${input.upstreamHost}${input.upstreamPath}`,
|
|
92038
|
+
queryNames: queryNames.join(",") || "none",
|
|
92039
|
+
oa2InQuery: hasOa2(queryNames),
|
|
92040
|
+
status: input.status,
|
|
92041
|
+
setCookieNames: cookieNamesOf(input.setCookies).join(",") || "none",
|
|
92042
|
+
cookiesSent: (input.cookieHeader ?? "").split(";").filter((p) => p.trim().length > 0).length
|
|
92043
|
+
};
|
|
92044
|
+
if (formNames !== null) {
|
|
92045
|
+
meta["contentType"] = mediaType(input.contentType);
|
|
92046
|
+
meta["formNames"] = formNames.join(",") || "none";
|
|
92047
|
+
meta["oa2InForm"] = hasOa2(formNames);
|
|
92048
|
+
}
|
|
92049
|
+
if (input.location !== null) {
|
|
92050
|
+
const loc = locationShape(input.location, `http://${input.upstreamHost}/`);
|
|
92051
|
+
meta["location"] = loc.target;
|
|
92052
|
+
meta["locationQueryNames"] = loc.names.join(",") || "none";
|
|
92053
|
+
meta["oa2InLocation"] = hasOa2(loc.names);
|
|
92054
|
+
}
|
|
92055
|
+
return meta;
|
|
92056
|
+
}
|
|
90900
92057
|
var MAX_REQUEST_BODY_BYTES = 2 * 1024 * 1024;
|
|
90901
92058
|
var MAX_TEXT_BODY_BYTES = 4 * 1024 * 1024;
|
|
90902
92059
|
/** Request headers that are never forwarded to the vendor. */
|
|
@@ -91001,6 +92158,8 @@ var VendorLoginProxy = class {
|
|
|
91001
92158
|
#now;
|
|
91002
92159
|
#fetch;
|
|
91003
92160
|
#attempts = /* @__PURE__ */ new Map();
|
|
92161
|
+
/** Trace lines written per attempt (bounded by FLOW_TRACE_MAX_LINES). */
|
|
92162
|
+
#traced = /* @__PURE__ */ new Map();
|
|
91004
92163
|
constructor(options) {
|
|
91005
92164
|
this.#addonId = options.addonId;
|
|
91006
92165
|
this.#logger = options.logger;
|
|
@@ -91027,6 +92186,7 @@ var VendorLoginProxy = class {
|
|
|
91027
92186
|
}
|
|
91028
92187
|
close(attemptId) {
|
|
91029
92188
|
this.#attempts.delete(attemptId);
|
|
92189
|
+
this.#traced.delete(attemptId);
|
|
91030
92190
|
}
|
|
91031
92191
|
isOpen(attemptId) {
|
|
91032
92192
|
const attempt = this.#attempts.get(attemptId);
|
|
@@ -91073,7 +92233,7 @@ var VendorLoginProxy = class {
|
|
|
91073
92233
|
const forwardedPrefix = (firstHeader(req.headers["x-forwarded-prefix"]) ?? "").replace(/\/+$/, "");
|
|
91074
92234
|
const attempt = this.#attempts.get(parsed.attemptId);
|
|
91075
92235
|
if (attempt === void 0 || this.#now() > attempt.expiresAtMs) {
|
|
91076
|
-
if (attempt !== void 0) this
|
|
92236
|
+
if (attempt !== void 0) this.close(parsed.attemptId);
|
|
91077
92237
|
this.#logger.info("vendor login proxy: attempt refused", { meta: {
|
|
91078
92238
|
addonId: this.#addonId,
|
|
91079
92239
|
reason: attempt === void 0 ? "missing" : "expired"
|
|
@@ -91098,13 +92258,17 @@ var VendorLoginProxy = class {
|
|
|
91098
92258
|
}
|
|
91099
92259
|
const method = (req.method ?? "GET").toUpperCase();
|
|
91100
92260
|
let body;
|
|
92261
|
+
let formBodyRaw = null;
|
|
91101
92262
|
if (method !== "GET" && method !== "HEAD") {
|
|
91102
92263
|
const read = await readRequestBody(req);
|
|
91103
92264
|
if (read === "too-large") {
|
|
91104
92265
|
sendPlain(res, 413, "Request too large");
|
|
91105
92266
|
return;
|
|
91106
92267
|
}
|
|
91107
|
-
|
|
92268
|
+
if (isFormEncoded(req.headers["content-type"])) {
|
|
92269
|
+
formBodyRaw = read.toString("utf8");
|
|
92270
|
+
body = Buffer.from(rewriteFormBody(formBodyRaw, route), "utf8");
|
|
92271
|
+
} else body = read;
|
|
91108
92272
|
}
|
|
91109
92273
|
const headers = this.#requestHeaders(req, route, upstream);
|
|
91110
92274
|
const upstreamRes = await this.#fetch(target, {
|
|
@@ -91129,6 +92293,19 @@ var VendorLoginProxy = class {
|
|
|
91129
92293
|
sentNames: cookieNames(headers["cookie"]),
|
|
91130
92294
|
vendorSets: setCookies.map(setCookieShape).join(" | ")
|
|
91131
92295
|
} });
|
|
92296
|
+
this.#trace(parsed.attemptId, {
|
|
92297
|
+
attemptId: parsed.attemptId,
|
|
92298
|
+
method,
|
|
92299
|
+
upstreamHost: upstream.host,
|
|
92300
|
+
upstreamPath: target.pathname,
|
|
92301
|
+
requestSearch: target.search,
|
|
92302
|
+
formBody: formBodyRaw,
|
|
92303
|
+
contentType: firstHeader(req.headers["content-type"]),
|
|
92304
|
+
status: upstreamRes.status,
|
|
92305
|
+
location,
|
|
92306
|
+
setCookies,
|
|
92307
|
+
cookieHeader: headers["cookie"]
|
|
92308
|
+
});
|
|
91132
92309
|
const settled = await attempt.evaluate({
|
|
91133
92310
|
status: upstreamRes.status,
|
|
91134
92311
|
url: target.toString(),
|
|
@@ -91137,7 +92314,7 @@ var VendorLoginProxy = class {
|
|
|
91137
92314
|
bodyText: text
|
|
91138
92315
|
});
|
|
91139
92316
|
if (settled.kind === "done") {
|
|
91140
|
-
this
|
|
92317
|
+
this.close(parsed.attemptId);
|
|
91141
92318
|
this.#logger.info("vendor login proxy: sign-in finished", { meta: {
|
|
91142
92319
|
addonId: this.#addonId,
|
|
91143
92320
|
status: settled.landing.status
|
|
@@ -91161,6 +92338,16 @@ var VendorLoginProxy = class {
|
|
|
91161
92338
|
res.writeHead(upstreamRes.status, out);
|
|
91162
92339
|
await this.#pipeBody(upstreamRes, res);
|
|
91163
92340
|
}
|
|
92341
|
+
/** One names-only line per proxied request, at most FLOW_TRACE_MAX_LINES per attempt. */
|
|
92342
|
+
#trace(attemptId, input) {
|
|
92343
|
+
const written = this.#traced.get(attemptId) ?? 0;
|
|
92344
|
+
if (written >= 200) return;
|
|
92345
|
+
this.#traced.set(attemptId, written + 1);
|
|
92346
|
+
this.#logger.info("vendor login proxy: flow", { meta: {
|
|
92347
|
+
addonId: this.#addonId,
|
|
92348
|
+
...flowTraceMeta(input)
|
|
92349
|
+
} });
|
|
92350
|
+
}
|
|
91164
92351
|
#requestHeaders(req, route, upstream) {
|
|
91165
92352
|
const headers = {};
|
|
91166
92353
|
for (const [name, value] of Object.entries(req.headers)) {
|
|
@@ -91202,6 +92389,14 @@ var VendorLoginProxy = class {
|
|
|
91202
92389
|
};
|
|
91203
92390
|
var MB = 1024 * 1024;
|
|
91204
92391
|
1024 * MB, 3072 * MB;
|
|
92392
|
+
object({
|
|
92393
|
+
manufacturer: string().optional(),
|
|
92394
|
+
supportedModels: array(string()).optional(),
|
|
92395
|
+
supportedController: string().optional(),
|
|
92396
|
+
commandsEncoding: string(),
|
|
92397
|
+
delay: number().nonnegative().optional(),
|
|
92398
|
+
commands: record(string(), unknown())
|
|
92399
|
+
});
|
|
91205
92400
|
//#endregion
|
|
91206
92401
|
//#region src/config.ts
|
|
91207
92402
|
/**
|
|
@@ -132862,6 +134057,38 @@ function alexaRegionOptions(amazonDomain) {
|
|
|
132862
134057
|
};
|
|
132863
134058
|
}
|
|
132864
134059
|
//#endregion
|
|
134060
|
+
//#region src/alexa-url-params.ts
|
|
134061
|
+
var BASE = "http://placeholder.invalid";
|
|
134062
|
+
function describeUrlParams(url) {
|
|
134063
|
+
let parsed;
|
|
134064
|
+
try {
|
|
134065
|
+
parsed = new URL(url, BASE);
|
|
134066
|
+
} catch {
|
|
134067
|
+
return {
|
|
134068
|
+
path: "",
|
|
134069
|
+
names: [],
|
|
134070
|
+
oa2: false,
|
|
134071
|
+
returnTo: false,
|
|
134072
|
+
assocHandle: false,
|
|
134073
|
+
mode: false
|
|
134074
|
+
};
|
|
134075
|
+
}
|
|
134076
|
+
const names = [...new Set(parsed.searchParams.keys())].sort();
|
|
134077
|
+
return {
|
|
134078
|
+
path: parsed.pathname,
|
|
134079
|
+
names,
|
|
134080
|
+
oa2: names.some((n) => n.startsWith("openid.oa2.")),
|
|
134081
|
+
returnTo: names.includes("openid.return_to"),
|
|
134082
|
+
assocHandle: names.includes("openid.assoc_handle"),
|
|
134083
|
+
mode: names.includes("openid.mode")
|
|
134084
|
+
};
|
|
134085
|
+
}
|
|
134086
|
+
/** One-line, value-free rendering of {@link describeUrlParams}. */
|
|
134087
|
+
function formatUrlParams(url) {
|
|
134088
|
+
const d = describeUrlParams(url);
|
|
134089
|
+
return `names=[${d.names.join(",")}] oa2=${d.oa2} return_to=${d.returnTo} assoc_handle=${d.assocHandle} mode=${d.mode}`;
|
|
134090
|
+
}
|
|
134091
|
+
//#endregion
|
|
132865
134092
|
//#region src/alexa-proxy-trace.ts
|
|
132866
134093
|
/**
|
|
132867
134094
|
* Sanitised one-line trace of the alexa-cookie2 proxy's own log lines.
|
|
@@ -132882,17 +134109,21 @@ function stripQuery(target) {
|
|
|
132882
134109
|
const cut = target.search(/[?#]/);
|
|
132883
134110
|
return cut === -1 ? target : target.slice(0, cut);
|
|
132884
134111
|
}
|
|
134112
|
+
/** The target without its query, plus the value-free parameter summary. */
|
|
134113
|
+
function withParams(target) {
|
|
134114
|
+
return `${stripQuery(target)} ${formatUrlParams(target)}`;
|
|
134115
|
+
}
|
|
132885
134116
|
function sanitizeProxyTraceLine(line) {
|
|
132886
134117
|
const router = ROUTER_RE.exec(line);
|
|
132887
|
-
if (router !== null) return `router ${router[2]} ${
|
|
134118
|
+
if (router !== null) return `router ${router[2]} ${withParams(router[1] ?? "")}`;
|
|
132888
134119
|
const request = PROXY_REQUEST_RE.exec(line);
|
|
132889
|
-
if (request !== null) return `proxy-request ${request[1]} ${
|
|
134120
|
+
if (request !== null) return `proxy-request ${request[1]} ${withParams(request[2] ?? "")}`;
|
|
132890
134121
|
const response = PROXY_RESPONSE_RE.exec(line);
|
|
132891
134122
|
if (response !== null) return `proxy-response host=${response[1]}`;
|
|
132892
134123
|
const original = REDIRECT_ORIGINAL_RE.exec(line);
|
|
132893
|
-
if (original !== null) return `redirect-original ${
|
|
134124
|
+
if (original !== null) return `redirect-original ${withParams(original[1] ?? "")}`;
|
|
132894
134125
|
const final = REDIRECT_FINAL_RE.exec(line);
|
|
132895
|
-
if (final !== null) return `redirect-final ${
|
|
134126
|
+
if (final !== null) return `redirect-final ${withParams(final[1] ?? "")}`;
|
|
132896
134127
|
return null;
|
|
132897
134128
|
}
|
|
132898
134129
|
//#endregion
|
|
@@ -133008,7 +134239,7 @@ var AlexaProxyLoginSession = class {
|
|
|
133008
134239
|
const line = args.map(String).join(" ");
|
|
133009
134240
|
const trace = sanitizeProxyTraceLine(line);
|
|
133010
134241
|
if (trace !== null) {
|
|
133011
|
-
this.#logger.info(`alexa
|
|
134242
|
+
this.#logger.info(`[alexa-login-trace] ${trace}`, { meta: { sessionId: this.#opts.sessionId } });
|
|
133012
134243
|
return;
|
|
133013
134244
|
}
|
|
133014
134245
|
if (SECRET_LINE_RE.test(line)) return;
|
|
@@ -133140,20 +134371,36 @@ var ADDON_ID = "import-alexa";
|
|
|
133140
134371
|
var ATTEMPT_TTL_MS = 600 * 1e3;
|
|
133141
134372
|
/** How long the browser waits on the Amazon page for alexa-cookie2 to finish registering. */
|
|
133142
134373
|
var CAPTURE_WAIT_MS = 6e4;
|
|
134374
|
+
/** The query parameter Amazon sets on `/ap/maplanding` when it honoured the oa2 request. */
|
|
134375
|
+
var AUTHORIZATION_CODE_PARAM = "openid.oa2.authorization_code";
|
|
133143
134376
|
/**
|
|
133144
|
-
* Terminal condition
|
|
133145
|
-
* `/cookie-success` page
|
|
133146
|
-
* `
|
|
134377
|
+
* Terminal condition. The login is over when alexa-cookie2 turns Amazon's final redirect into
|
|
134378
|
+
* a 302 to its own `/cookie-success` page (it only does so when the landing carries the oa2
|
|
134379
|
+
* authorization code), OR when Amazon's `/ap/maplanding` itself shows up as a `Location` or as
|
|
134380
|
+
* the request. A maplanding WITHOUT the authorization code is a dead end: alexa-cookie2 cannot
|
|
134381
|
+
* register a device from it and no cookie will ever come, so it ends at once as
|
|
134382
|
+
* `no_authorization_code` instead of after the capture wait. Only paths and parameter NAMES are
|
|
134383
|
+
* read, never a value (the query carries signed openid values).
|
|
133147
134384
|
*/
|
|
133148
134385
|
function cookieSuccessTerminal(view) {
|
|
133149
|
-
|
|
133150
|
-
|
|
133151
|
-
|
|
134386
|
+
for (const raw of [view.url, view.location]) {
|
|
134387
|
+
if (raw === null) continue;
|
|
134388
|
+
const end = loginEnd(raw);
|
|
134389
|
+
if (end !== null) return {
|
|
133152
134390
|
kind: "done",
|
|
133153
|
-
payload:
|
|
133154
|
-
}
|
|
134391
|
+
payload: end
|
|
134392
|
+
};
|
|
134393
|
+
}
|
|
134394
|
+
return { kind: "continue" };
|
|
134395
|
+
}
|
|
134396
|
+
function loginEnd(raw) {
|
|
134397
|
+
try {
|
|
134398
|
+
const url = new URL(raw, `http://${ALEXA_PROXY_LOOPBACK_HOST}`);
|
|
134399
|
+
if (url.pathname === "/cookie-success") return "registering";
|
|
134400
|
+
if (!url.pathname.endsWith("/ap/maplanding")) return null;
|
|
134401
|
+
return url.searchParams.has(AUTHORIZATION_CODE_PARAM) ? "registering" : "no_authorization_code";
|
|
133155
134402
|
} catch {
|
|
133156
|
-
return
|
|
134403
|
+
return null;
|
|
133157
134404
|
}
|
|
133158
134405
|
}
|
|
133159
134406
|
var AlexaProxyLoginManager = class {
|
|
@@ -133170,6 +134417,13 @@ var AlexaProxyLoginManager = class {
|
|
|
133170
134417
|
this.#getLoginProxy = deps.getLoginProxy;
|
|
133171
134418
|
this.#captureWaitMs = deps.captureWaitMs ?? CAPTURE_WAIT_MS;
|
|
133172
134419
|
}
|
|
134420
|
+
/** One value-free line per upstream hop the hub login proxy sees (names only). */
|
|
134421
|
+
#traceHop(sessionId, view) {
|
|
134422
|
+
const url = describeUrlParams(view.url);
|
|
134423
|
+
const landing = url.path.endsWith("/ap/maplanding") ? " maplanding" : "";
|
|
134424
|
+
const location = view.location === null ? "none" : `${describeUrlParams(view.location).path} ${formatUrlParams(view.location)}`;
|
|
134425
|
+
this.#logger.info(`[alexa-login-trace] hub-hop${landing} status=${view.status} url=${url.path} ${formatUrlParams(view.url)} location=${location}`, { meta: { sessionId } });
|
|
134426
|
+
}
|
|
133173
134427
|
/**
|
|
133174
134428
|
* Start a fresh proxy-login. Cancels any in-flight session first (single
|
|
133175
134429
|
* fixed port). Returns the `loginUrl` for the onboarding UI to open.
|
|
@@ -133188,8 +134442,19 @@ var AlexaProxyLoginManager = class {
|
|
|
133188
134442
|
host: upstreamHost,
|
|
133189
134443
|
scheme: "http"
|
|
133190
134444
|
}],
|
|
133191
|
-
terminal:
|
|
133192
|
-
|
|
134445
|
+
terminal: (view) => {
|
|
134446
|
+
this.#traceHop(sessionId, view);
|
|
134447
|
+
return cookieSuccessTerminal(view);
|
|
134448
|
+
},
|
|
134449
|
+
onDone: async (end) => {
|
|
134450
|
+
if (end === "no_authorization_code") {
|
|
134451
|
+
this.#logger.warn("alexa proxy-login: Amazon ended the login without an authorization code", { meta: { sessionId } });
|
|
134452
|
+
return {
|
|
134453
|
+
status: "error",
|
|
134454
|
+
addonId: ADDON_ID,
|
|
134455
|
+
reason: "no_authorization_code"
|
|
134456
|
+
};
|
|
134457
|
+
}
|
|
133193
134458
|
return await this.#session?.whenCaptured(this.#captureWaitMs) === true ? {
|
|
133194
134459
|
status: "success",
|
|
133195
134460
|
addonId: ADDON_ID
|