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