@camstack/addon-import-alexa 0.2.165 → 0.2.166
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 +6 -0
- package/dist/addon.js +748 -19
- package/dist/addon.mjs +748 -19
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -5627,7 +5627,7 @@ var ZodIssueCode = {
|
|
|
5627
5627
|
var ZodFirstPartyTypeKind;
|
|
5628
5628
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5629
5629
|
//#endregion
|
|
5630
|
-
//#region ../types/dist/sleep-
|
|
5630
|
+
//#region ../types/dist/sleep-C_u6cEAJ.mjs
|
|
5631
5631
|
/**
|
|
5632
5632
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5633
5633
|
* window to float samples (D455).
|
|
@@ -9020,6 +9020,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9020
9020
|
"image-settings",
|
|
9021
9021
|
"integrations",
|
|
9022
9022
|
"intercom",
|
|
9023
|
+
"ir-transmitter",
|
|
9023
9024
|
"keychain",
|
|
9024
9025
|
"keychain-admin",
|
|
9025
9026
|
"lawn-mower-control",
|
|
@@ -9048,6 +9049,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9048
9049
|
"network-link",
|
|
9049
9050
|
"network-quality",
|
|
9050
9051
|
"node-certificates",
|
|
9052
|
+
"node-removal-hook",
|
|
9051
9053
|
"nodes",
|
|
9052
9054
|
"notification-output",
|
|
9053
9055
|
"notification-rules",
|
|
@@ -9078,6 +9080,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9078
9080
|
"recording-onboard",
|
|
9079
9081
|
"recording-signal",
|
|
9080
9082
|
"records",
|
|
9083
|
+
"remote",
|
|
9081
9084
|
"running-state",
|
|
9082
9085
|
"scene-monitor",
|
|
9083
9086
|
"script-runner",
|
|
@@ -9101,6 +9104,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9101
9104
|
"system",
|
|
9102
9105
|
"tamper",
|
|
9103
9106
|
"temperature-sensor",
|
|
9107
|
+
"template",
|
|
9104
9108
|
"terminal-session",
|
|
9105
9109
|
"toast",
|
|
9106
9110
|
"training",
|
|
@@ -10041,6 +10045,11 @@ Object.freeze({
|
|
|
10041
10045
|
"standby"
|
|
10042
10046
|
]
|
|
10043
10047
|
},
|
|
10048
|
+
{
|
|
10049
|
+
"path": "stateSource",
|
|
10050
|
+
"valueType": "enum",
|
|
10051
|
+
"enumValues": ["reported", "assumed"]
|
|
10052
|
+
},
|
|
10044
10053
|
{
|
|
10045
10054
|
"path": "volumeLevel",
|
|
10046
10055
|
"valueType": "number"
|
|
@@ -10267,6 +10276,11 @@ Object.freeze({
|
|
|
10267
10276
|
"path": "batteryPower",
|
|
10268
10277
|
"valueType": "number"
|
|
10269
10278
|
},
|
|
10279
|
+
{
|
|
10280
|
+
"path": "powerSource",
|
|
10281
|
+
"valueType": "enum",
|
|
10282
|
+
"enumValues": ["mains", "battery"]
|
|
10283
|
+
},
|
|
10270
10284
|
{
|
|
10271
10285
|
"path": "desiccantLeftDays",
|
|
10272
10286
|
"valueType": "number"
|
|
@@ -10311,6 +10325,18 @@ Object.freeze({
|
|
|
10311
10325
|
{
|
|
10312
10326
|
"path": "volume",
|
|
10313
10327
|
"valueType": "number"
|
|
10328
|
+
},
|
|
10329
|
+
{
|
|
10330
|
+
"path": "lastFeedGrams",
|
|
10331
|
+
"valueType": "number"
|
|
10332
|
+
},
|
|
10333
|
+
{
|
|
10334
|
+
"path": "lastFeedHopper1",
|
|
10335
|
+
"valueType": "number"
|
|
10336
|
+
},
|
|
10337
|
+
{
|
|
10338
|
+
"path": "lastFeedHopper2",
|
|
10339
|
+
"valueType": "number"
|
|
10314
10340
|
}
|
|
10315
10341
|
],
|
|
10316
10342
|
"power-meter": [
|
|
@@ -10850,7 +10876,7 @@ object({
|
|
|
10850
10876
|
params: record(string(), union([string(), number()])).optional()
|
|
10851
10877
|
});
|
|
10852
10878
|
//#endregion
|
|
10853
|
-
//#region ../types/dist/err-msg-
|
|
10879
|
+
//#region ../types/dist/err-msg-BCdlR2Li.mjs
|
|
10854
10880
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
10855
10881
|
DeviceType["Camera"] = "camera";
|
|
10856
10882
|
DeviceType["Hub"] = "hub";
|
|
@@ -11196,6 +11222,9 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
|
11196
11222
|
/** Fallback when the notifier service name doesn't match a known
|
|
11197
11223
|
* pattern. */
|
|
11198
11224
|
DeviceRole["GenericNotifier"] = "generic-notifier";
|
|
11225
|
+
/** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
|
|
11226
|
+
* cap. A `DeviceType.Generic` device. */
|
|
11227
|
+
DeviceRole["IrTransmitter"] = "ir-transmitter";
|
|
11199
11228
|
return DeviceRole;
|
|
11200
11229
|
}({});
|
|
11201
11230
|
/**
|
|
@@ -11209,7 +11238,7 @@ function errMsg(err) {
|
|
|
11209
11238
|
return String(err);
|
|
11210
11239
|
}
|
|
11211
11240
|
//#endregion
|
|
11212
|
-
//#region ../types/dist/composition-
|
|
11241
|
+
//#region ../types/dist/composition-bIBW5_13.mjs
|
|
11213
11242
|
/**
|
|
11214
11243
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11215
11244
|
*
|
|
@@ -16755,9 +16784,21 @@ var AlertSeveritySchema = _enum([
|
|
|
16755
16784
|
"error",
|
|
16756
16785
|
"critical"
|
|
16757
16786
|
]);
|
|
16787
|
+
/**
|
|
16788
|
+
* An action that RUNS something instead of going somewhere: the admin UI calls
|
|
16789
|
+
* the addon's custom action (`addons.custom`) with exactly these fields. The
|
|
16790
|
+
* producer owns the meaning; the UI knows nothing about the addon or the kind.
|
|
16791
|
+
*/
|
|
16792
|
+
var AlertActionInvokeSchema = object({
|
|
16793
|
+
addonId: string().min(1),
|
|
16794
|
+
action: string().min(1),
|
|
16795
|
+
input: record(string(), unknown()).optional()
|
|
16796
|
+
});
|
|
16797
|
+
/** An action has `href` (a link) OR `invoke` (a button that runs a custom action). */
|
|
16758
16798
|
var AlertActionSchema = object({
|
|
16759
16799
|
label: string(),
|
|
16760
16800
|
href: string().optional(),
|
|
16801
|
+
invoke: AlertActionInvokeSchema.optional(),
|
|
16761
16802
|
/**
|
|
16762
16803
|
* How `href` is followed. Absent / `app`: an admin-ui route (client-side).
|
|
16763
16804
|
* `server`: a route the HUB serves (an addon HTTP route such as an OAuth
|
|
@@ -43260,6 +43301,14 @@ method(object({
|
|
|
43260
43301
|
kind: "mutation",
|
|
43261
43302
|
auth: "admin"
|
|
43262
43303
|
});
|
|
43304
|
+
method(object({
|
|
43305
|
+
nodeId: string().min(1),
|
|
43306
|
+
/** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
|
|
43307
|
+
removedAt: number()
|
|
43308
|
+
}), object({ counts: record(string(), number().int().nonnegative()) }), {
|
|
43309
|
+
kind: "mutation",
|
|
43310
|
+
auth: "admin"
|
|
43311
|
+
});
|
|
43263
43312
|
/**
|
|
43264
43313
|
* `device-removal-hook` — the reliable way an addon learns a device was DELETED,
|
|
43265
43314
|
* so it can delete what it keeps for that device (D865).
|
|
@@ -58828,6 +58877,318 @@ onBrightnessChanged: { data: object({
|
|
|
58828
58877
|
} } }
|
|
58829
58878
|
};
|
|
58830
58879
|
/**
|
|
58880
|
+
* The role vocabulary of a remote (D963): what a button MEANS, independent of
|
|
58881
|
+
* how it is wired. A layout template places buttons by role; a device binds a
|
|
58882
|
+
* role to an action; an exporter asks for a role by name. Roles are shared so
|
|
58883
|
+
* one layout fits every appliance and one voice command fits every binding.
|
|
58884
|
+
*
|
|
58885
|
+
* A role outside this list is `custom:<key>` — the operator's own button (a
|
|
58886
|
+
* SmartIR source, a channel preset). The key is a lowercase slug.
|
|
58887
|
+
*/
|
|
58888
|
+
var REMOTE_KNOWN_ROLES = [
|
|
58889
|
+
"power",
|
|
58890
|
+
"power_on",
|
|
58891
|
+
"power_off",
|
|
58892
|
+
"volume_up",
|
|
58893
|
+
"volume_down",
|
|
58894
|
+
"mute",
|
|
58895
|
+
"channel_up",
|
|
58896
|
+
"channel_down",
|
|
58897
|
+
"up",
|
|
58898
|
+
"down",
|
|
58899
|
+
"left",
|
|
58900
|
+
"right",
|
|
58901
|
+
"ok",
|
|
58902
|
+
"back",
|
|
58903
|
+
"home",
|
|
58904
|
+
"menu",
|
|
58905
|
+
"info",
|
|
58906
|
+
"guide",
|
|
58907
|
+
"exit",
|
|
58908
|
+
"input",
|
|
58909
|
+
"play",
|
|
58910
|
+
"pause",
|
|
58911
|
+
"play_pause",
|
|
58912
|
+
"stop",
|
|
58913
|
+
"next",
|
|
58914
|
+
"previous",
|
|
58915
|
+
"fast_forward",
|
|
58916
|
+
"rewind",
|
|
58917
|
+
"digit_0",
|
|
58918
|
+
"digit_1",
|
|
58919
|
+
"digit_2",
|
|
58920
|
+
"digit_3",
|
|
58921
|
+
"digit_4",
|
|
58922
|
+
"digit_5",
|
|
58923
|
+
"digit_6",
|
|
58924
|
+
"digit_7",
|
|
58925
|
+
"digit_8",
|
|
58926
|
+
"digit_9"
|
|
58927
|
+
];
|
|
58928
|
+
var CUSTOM_KEY_RE = /^[a-z0-9][a-z0-9_]{0,47}$/;
|
|
58929
|
+
var KNOWN_ROLES = new Set(REMOTE_KNOWN_ROLES);
|
|
58930
|
+
function isRemoteKnownRole(value) {
|
|
58931
|
+
return KNOWN_ROLES.has(value);
|
|
58932
|
+
}
|
|
58933
|
+
function isRemoteCustomKey(key) {
|
|
58934
|
+
return CUSTOM_KEY_RE.test(key);
|
|
58935
|
+
}
|
|
58936
|
+
/** The key of a `custom:<key>` role, or null for any other string. */
|
|
58937
|
+
function customKeyOfRole(role) {
|
|
58938
|
+
if (!role.startsWith("custom:")) return null;
|
|
58939
|
+
const key = role.slice(7);
|
|
58940
|
+
return isRemoteCustomKey(key) ? key : null;
|
|
58941
|
+
}
|
|
58942
|
+
function isRemoteRole(value) {
|
|
58943
|
+
return isRemoteKnownRole(value) || customKeyOfRole(value) !== null;
|
|
58944
|
+
}
|
|
58945
|
+
var RemoteRoleSchema = string().refine(isRemoteRole, { message: "a role is one of the known roles or custom:<key> (lowercase letters, digits, _)" });
|
|
58946
|
+
var RemoteBindingSchema = object({ action: AutomationCapStepSchema.extend({ deviceId: number().int().nonnegative() }) });
|
|
58947
|
+
var RemoteCustomButtonSchema = object({
|
|
58948
|
+
/** The slug after `custom:` in the role. */
|
|
58949
|
+
key: string().refine(isRemoteCustomKey, { message: "a lowercase slug" }),
|
|
58950
|
+
/** What the operator calls it, and what a voice command may name. */
|
|
58951
|
+
name: string().min(1).max(60)
|
|
58952
|
+
});
|
|
58953
|
+
var RemotePowerModeSchema = _enum(["toggle", "discrete"]);
|
|
58954
|
+
var RemoteConfigSchema = object({
|
|
58955
|
+
layoutTemplateId: string().min(1).nullable(),
|
|
58956
|
+
/**
|
|
58957
|
+
* The transmitter device new bindings and an import send through. A binding
|
|
58958
|
+
* names its own device, so one command may override it.
|
|
58959
|
+
*/
|
|
58960
|
+
defaultTransmitterId: number().int().nonnegative().nullable().default(null),
|
|
58961
|
+
/** Keyed by role. A role with no entry falls back to the native cap. */
|
|
58962
|
+
bindings: record(string(), RemoteBindingSchema),
|
|
58963
|
+
customButtons: array(RemoteCustomButtonSchema).max(100),
|
|
58964
|
+
power: object({ mode: RemotePowerModeSchema })
|
|
58965
|
+
}).superRefine((config, ctx) => {
|
|
58966
|
+
const roles = Object.keys(config.bindings);
|
|
58967
|
+
if (roles.length > 200) ctx.addIssue({
|
|
58968
|
+
code: "custom",
|
|
58969
|
+
message: "too many bindings",
|
|
58970
|
+
path: ["bindings"]
|
|
58971
|
+
});
|
|
58972
|
+
for (const role of roles) if (!isRemoteRole(role)) ctx.addIssue({
|
|
58973
|
+
code: "custom",
|
|
58974
|
+
message: `"${role}" is not a role`,
|
|
58975
|
+
path: ["bindings", role]
|
|
58976
|
+
});
|
|
58977
|
+
const keys = /* @__PURE__ */ new Set();
|
|
58978
|
+
config.customButtons.forEach((button, index) => {
|
|
58979
|
+
if (keys.has(button.key)) ctx.addIssue({
|
|
58980
|
+
code: "custom",
|
|
58981
|
+
message: `custom button "${button.key}" is listed twice`,
|
|
58982
|
+
path: [
|
|
58983
|
+
"customButtons",
|
|
58984
|
+
index,
|
|
58985
|
+
"key"
|
|
58986
|
+
]
|
|
58987
|
+
});
|
|
58988
|
+
keys.add(button.key);
|
|
58989
|
+
});
|
|
58990
|
+
for (const role of roles) {
|
|
58991
|
+
const key = customKeyOfRole(role);
|
|
58992
|
+
if (key !== null && !keys.has(key)) ctx.addIssue({
|
|
58993
|
+
code: "custom",
|
|
58994
|
+
message: `${role} is bound but has no entry in customButtons`,
|
|
58995
|
+
path: ["bindings", role]
|
|
58996
|
+
});
|
|
58997
|
+
}
|
|
58998
|
+
});
|
|
58999
|
+
object({
|
|
59000
|
+
power: object({
|
|
59001
|
+
state: _enum(["on", "off"]),
|
|
59002
|
+
at: number()
|
|
59003
|
+
}).nullable(),
|
|
59004
|
+
muted: object({
|
|
59005
|
+
value: boolean(),
|
|
59006
|
+
at: number()
|
|
59007
|
+
}).nullable()
|
|
59008
|
+
});
|
|
59009
|
+
var RemoteLayoutButtonSchema = object({
|
|
59010
|
+
id: string().min(1).max(40),
|
|
59011
|
+
/** 0-based grid position. */
|
|
59012
|
+
row: number().int().min(0).max(24),
|
|
59013
|
+
col: number().int().min(0).max(24),
|
|
59014
|
+
rowSpan: number().int().min(1).max(24),
|
|
59015
|
+
colSpan: number().int().min(1).max(24),
|
|
59016
|
+
icon: string().min(1).max(60).optional(),
|
|
59017
|
+
label: string().min(1).max(40).optional(),
|
|
59018
|
+
role: RemoteRoleSchema
|
|
59019
|
+
});
|
|
59020
|
+
object({
|
|
59021
|
+
columns: number().int().min(1).max(24),
|
|
59022
|
+
rows: number().int().min(1).max(24),
|
|
59023
|
+
buttons: array(RemoteLayoutButtonSchema).max(200)
|
|
59024
|
+
}).superRefine((grid, ctx) => {
|
|
59025
|
+
const ids = /* @__PURE__ */ new Set();
|
|
59026
|
+
const taken = /* @__PURE__ */ new Map();
|
|
59027
|
+
grid.buttons.forEach((button, index) => {
|
|
59028
|
+
if (ids.has(button.id)) ctx.addIssue({
|
|
59029
|
+
code: "custom",
|
|
59030
|
+
message: `button id "${button.id}" is used twice`,
|
|
59031
|
+
path: [
|
|
59032
|
+
"buttons",
|
|
59033
|
+
index,
|
|
59034
|
+
"id"
|
|
59035
|
+
]
|
|
59036
|
+
});
|
|
59037
|
+
ids.add(button.id);
|
|
59038
|
+
if (button.row + button.rowSpan > grid.rows || button.col + button.colSpan > grid.columns) {
|
|
59039
|
+
ctx.addIssue({
|
|
59040
|
+
code: "custom",
|
|
59041
|
+
message: `button "${button.id}" leaves the grid`,
|
|
59042
|
+
path: ["buttons", index]
|
|
59043
|
+
});
|
|
59044
|
+
return;
|
|
59045
|
+
}
|
|
59046
|
+
for (let r = button.row; r < button.row + button.rowSpan; r += 1) for (let c = button.col; c < button.col + button.colSpan; c += 1) {
|
|
59047
|
+
const cell = `${r}:${c}`;
|
|
59048
|
+
const other = taken.get(cell);
|
|
59049
|
+
if (other !== void 0) {
|
|
59050
|
+
ctx.addIssue({
|
|
59051
|
+
code: "custom",
|
|
59052
|
+
message: `buttons "${other}" and "${button.id}" overlap`,
|
|
59053
|
+
path: ["buttons", index]
|
|
59054
|
+
});
|
|
59055
|
+
return;
|
|
59056
|
+
}
|
|
59057
|
+
taken.set(cell, button.id);
|
|
59058
|
+
}
|
|
59059
|
+
});
|
|
59060
|
+
});
|
|
59061
|
+
var RemoteBoundCommandSchema = object({
|
|
59062
|
+
role: RemoteRoleSchema,
|
|
59063
|
+
/** The operator's name for a custom button; absent for a known role. */
|
|
59064
|
+
label: string().optional(),
|
|
59065
|
+
custom: boolean()
|
|
59066
|
+
});
|
|
59067
|
+
var RemoteCommandListSchema = object({
|
|
59068
|
+
layoutTemplateId: string().nullable(),
|
|
59069
|
+
power: object({ mode: RemotePowerModeSchema }),
|
|
59070
|
+
/** Only the roles that are BOUND — the ones a press can reach. */
|
|
59071
|
+
commands: array(RemoteBoundCommandSchema)
|
|
59072
|
+
});
|
|
59073
|
+
var RemoteRefusalReasonSchema = _enum([
|
|
59074
|
+
"no-remote",
|
|
59075
|
+
"unbound-role",
|
|
59076
|
+
"action-failed"
|
|
59077
|
+
]);
|
|
59078
|
+
var RemoteSendResultSchema = discriminatedUnion("status", [
|
|
59079
|
+
object({
|
|
59080
|
+
status: literal("sent"),
|
|
59081
|
+
/** How many times the action ran. */
|
|
59082
|
+
sends: number().int().min(1)
|
|
59083
|
+
}),
|
|
59084
|
+
object({
|
|
59085
|
+
status: literal("skipped"),
|
|
59086
|
+
reason: literal("already-in-state")
|
|
59087
|
+
}),
|
|
59088
|
+
object({
|
|
59089
|
+
status: literal("refused"),
|
|
59090
|
+
reason: RemoteRefusalReasonSchema,
|
|
59091
|
+
detail: string().optional()
|
|
59092
|
+
})
|
|
59093
|
+
]);
|
|
59094
|
+
var RemoteMediaCommandSchema = _enum([
|
|
59095
|
+
"play",
|
|
59096
|
+
"pause",
|
|
59097
|
+
"stop",
|
|
59098
|
+
"next",
|
|
59099
|
+
"previous",
|
|
59100
|
+
"setMute",
|
|
59101
|
+
"selectSource"
|
|
59102
|
+
]);
|
|
59103
|
+
/** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
|
|
59104
|
+
var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
|
|
59105
|
+
var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status: literal("saved") }), object({
|
|
59106
|
+
status: literal("refused"),
|
|
59107
|
+
reason: _enum(["unknown-template", "not-a-media-player"]),
|
|
59108
|
+
detail: string()
|
|
59109
|
+
})]);
|
|
59110
|
+
var deviceInput = object({ deviceId: number().int().nonnegative() });
|
|
59111
|
+
DeviceType.MediaPlayer;
|
|
59112
|
+
method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
|
|
59113
|
+
kind: "mutation",
|
|
59114
|
+
auth: "admin"
|
|
59115
|
+
}), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
|
|
59116
|
+
role: RemoteRoleSchema,
|
|
59117
|
+
/** Sends in a row (volume steps). */
|
|
59118
|
+
repeat: number().int().min(1).max(50).default(1)
|
|
59119
|
+
}), RemoteSendResultSchema, {
|
|
59120
|
+
kind: "mutation",
|
|
59121
|
+
auth: "protected"
|
|
59122
|
+
}), method(deviceInput.extend({
|
|
59123
|
+
command: RemoteMediaCommandSchema,
|
|
59124
|
+
muted: boolean().optional(),
|
|
59125
|
+
source: string().optional(),
|
|
59126
|
+
repeat: number().int().min(1).max(50).default(1)
|
|
59127
|
+
}), RemoteMediaCommandResultSchema, {
|
|
59128
|
+
kind: "mutation",
|
|
59129
|
+
auth: "protected"
|
|
59130
|
+
});
|
|
59131
|
+
var TemplateKindSchema = _enum(["remote"]);
|
|
59132
|
+
/**
|
|
59133
|
+
* template — the generic store of reusable, named operator data (D963). A
|
|
59134
|
+
* record is `{ id, kind, name, payload }`; the kinds (today only `remote`, the
|
|
59135
|
+
* button grid of a remote) and the schema each payload must satisfy are
|
|
59136
|
+
* declared by the system in `template-kinds.ts`. Nothing here is specific to a
|
|
59137
|
+
* kind: the engine validates a payload against its kind's schema and, on a
|
|
59138
|
+
* delete, asks the kind's owner whether anything still points at the record.
|
|
59139
|
+
*
|
|
59140
|
+
* Every refusal is a typed result — a UI says "unknown kind" or names the
|
|
59141
|
+
* devices that still use a template, without parsing text.
|
|
59142
|
+
*/
|
|
59143
|
+
var TemplateRecordSchema = object({
|
|
59144
|
+
id: string().min(1),
|
|
59145
|
+
kind: TemplateKindSchema,
|
|
59146
|
+
name: string().min(1).max(120),
|
|
59147
|
+
/** Shaped by the kind; `checkTemplatePayload(kind, payload)` reads it. */
|
|
59148
|
+
payload: unknown(),
|
|
59149
|
+
createdAt: number(),
|
|
59150
|
+
updatedAt: number()
|
|
59151
|
+
});
|
|
59152
|
+
var TemplateWriteResultSchema = discriminatedUnion("status", [object({
|
|
59153
|
+
status: literal("saved"),
|
|
59154
|
+
template: TemplateRecordSchema
|
|
59155
|
+
}), object({
|
|
59156
|
+
status: literal("refused"),
|
|
59157
|
+
reason: _enum(["invalid-payload", "not-found"]),
|
|
59158
|
+
detail: string()
|
|
59159
|
+
})]);
|
|
59160
|
+
/** A device still pointing at a template. */
|
|
59161
|
+
var TemplateUserSchema = object({
|
|
59162
|
+
deviceId: number().int().nonnegative(),
|
|
59163
|
+
name: string()
|
|
59164
|
+
});
|
|
59165
|
+
var TemplateDeleteResultSchema = discriminatedUnion("status", [
|
|
59166
|
+
object({ status: literal("deleted") }),
|
|
59167
|
+
object({ status: literal("not-found") }),
|
|
59168
|
+
object({
|
|
59169
|
+
status: literal("in-use"),
|
|
59170
|
+
devices: array(TemplateUserSchema).min(1)
|
|
59171
|
+
})
|
|
59172
|
+
]);
|
|
59173
|
+
method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSchema), { auth: "protected" }), method(object({ id: string().min(1) }), TemplateRecordSchema.nullable(), { auth: "protected" }), method(object({
|
|
59174
|
+
kind: TemplateKindSchema,
|
|
59175
|
+
name: string().min(1).max(120),
|
|
59176
|
+
payload: unknown()
|
|
59177
|
+
}), TemplateWriteResultSchema, {
|
|
59178
|
+
kind: "mutation",
|
|
59179
|
+
auth: "admin"
|
|
59180
|
+
}), method(object({
|
|
59181
|
+
id: string().min(1),
|
|
59182
|
+
name: string().min(1).max(120).optional(),
|
|
59183
|
+
payload: unknown().optional()
|
|
59184
|
+
}), TemplateWriteResultSchema, {
|
|
59185
|
+
kind: "mutation",
|
|
59186
|
+
auth: "admin"
|
|
59187
|
+
}), method(object({ id: string().min(1) }), TemplateDeleteResultSchema, {
|
|
59188
|
+
kind: "mutation",
|
|
59189
|
+
auth: "admin"
|
|
59190
|
+
});
|
|
59191
|
+
/**
|
|
58831
59192
|
* button — device-scoped capability for HA `button.*` / `input_button.*`
|
|
58832
59193
|
* fire-only actuators. A button entity has no readable state — pressing it
|
|
58833
59194
|
* triggers an action on the HA side. There is no `status` / `runtimeState`:
|
|
@@ -58849,6 +59210,56 @@ var buttonCapability = {
|
|
|
58849
59210
|
}) }
|
|
58850
59211
|
};
|
|
58851
59212
|
/**
|
|
59213
|
+
* A pulse train in signed microseconds: mark (+) first, then space (-), strictly
|
|
59214
|
+
* alternating, no zeros, 2..1024 entries.
|
|
59215
|
+
*/
|
|
59216
|
+
var IrTimingsSchema = array(number().int()).min(2).max(1024).superRefine((timings, ctx) => {
|
|
59217
|
+
timings.forEach((value, index) => {
|
|
59218
|
+
const expectMark = index % 2 === 0;
|
|
59219
|
+
if (value === 0 || value > 0 !== expectMark) ctx.addIssue({
|
|
59220
|
+
code: "custom",
|
|
59221
|
+
message: `timing ${index} must be a non-zero ${expectMark ? "mark (positive)" : "space (negative)"}`,
|
|
59222
|
+
path: [index]
|
|
59223
|
+
});
|
|
59224
|
+
});
|
|
59225
|
+
});
|
|
59226
|
+
/** Per-command transmit options. Every key is optional; a transmitter applies what it can. */
|
|
59227
|
+
var TransmitSettingsSchema = object({
|
|
59228
|
+
carrierHz: number().int().positive().optional(),
|
|
59229
|
+
dutyCyclePercent: number().int().min(1).max(100).optional(),
|
|
59230
|
+
repeat: number().int().min(0).max(20).optional(),
|
|
59231
|
+
repeatDelayMs: number().int().min(0).optional()
|
|
59232
|
+
});
|
|
59233
|
+
/** The setting keys a transmitter may declare as settable (`getInfo.settable`). */
|
|
59234
|
+
var TransmitSettingKeySchema = _enum([
|
|
59235
|
+
"carrierHz",
|
|
59236
|
+
"dutyCyclePercent",
|
|
59237
|
+
"repeat",
|
|
59238
|
+
"repeatDelayMs"
|
|
59239
|
+
]);
|
|
59240
|
+
var IrMediumSchema = _enum(["ir", "rf"]);
|
|
59241
|
+
var IrSendResultSchema = object({
|
|
59242
|
+
ok: literal(true),
|
|
59243
|
+
/** Settings the transmitter could not apply; the train was still sent. */
|
|
59244
|
+
ignoredSettings: array(string())
|
|
59245
|
+
});
|
|
59246
|
+
var IrTransmitterInfoSchema = object({
|
|
59247
|
+
media: array(IrMediumSchema),
|
|
59248
|
+
settable: array(TransmitSettingKeySchema),
|
|
59249
|
+
maxTimings: number().int().positive()
|
|
59250
|
+
});
|
|
59251
|
+
DeviceType.Generic, method(object({
|
|
59252
|
+
deviceId: number().int().nonnegative(),
|
|
59253
|
+
timings: IrTimingsSchema,
|
|
59254
|
+
settings: TransmitSettingsSchema.optional()
|
|
59255
|
+
}), IrSendResultSchema, {
|
|
59256
|
+
kind: "mutation",
|
|
59257
|
+
auth: "protected"
|
|
59258
|
+
}), method(object({ deviceId: number().int().nonnegative() }), IrTransmitterInfoSchema, {
|
|
59259
|
+
kind: "query",
|
|
59260
|
+
auth: "protected"
|
|
59261
|
+
});
|
|
59262
|
+
/**
|
|
58852
59263
|
* `calendar-source` — collection cap exposing per-provider calendars and
|
|
58853
59264
|
* their events. Every addon that contributes calendars (Home Assistant,
|
|
58854
59265
|
* a future Google Calendar addon, …) registers a provider here. The hub
|
|
@@ -62945,9 +63356,35 @@ var MediaInfoSchema = object({
|
|
|
62945
63356
|
/** Optional cover-art / thumbnail URL. */
|
|
62946
63357
|
imageUrl: string().optional()
|
|
62947
63358
|
});
|
|
63359
|
+
/**
|
|
63360
|
+
* Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
|
|
63361
|
+
* the device said so. `assumed`: the driver has no feedback (infrared) and
|
|
63362
|
+
* tracks what it last SENT; it moves only after a send was accepted, and a
|
|
63363
|
+
* human at the real remote makes it wrong. A UI shows an assumed value as a
|
|
63364
|
+
* guess, never as a reading.
|
|
63365
|
+
*/
|
|
63366
|
+
var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
|
|
63367
|
+
/** The fields of a media-player status a driver can only guess. */
|
|
63368
|
+
var MediaPlayerAssumedFieldSchema = _enum([
|
|
63369
|
+
"state",
|
|
63370
|
+
"volumeLevel",
|
|
63371
|
+
"isMuted",
|
|
63372
|
+
"source"
|
|
63373
|
+
]);
|
|
62948
63374
|
var MediaPlayerStatusSchema = object({
|
|
62949
63375
|
/** Playback lifecycle state. */
|
|
62950
63376
|
state: MediaPlayerStateSchema,
|
|
63377
|
+
/**
|
|
63378
|
+
* Whether the state fields are readings or the driver's own bookkeeping.
|
|
63379
|
+
* A slice written before this field existed parses as `reported`.
|
|
63380
|
+
*/
|
|
63381
|
+
stateSource: MediaPlayerStateSourceSchema.default("reported"),
|
|
63382
|
+
/**
|
|
63383
|
+
* Which fields are the driver's own guess. `stateSource` is `assumed` when
|
|
63384
|
+
* this is not empty; a field not listed here is a reading even then (power
|
|
63385
|
+
* from a running-state source, mute guessed from the last send).
|
|
63386
|
+
*/
|
|
63387
|
+
assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
|
|
62951
63388
|
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
62952
63389
|
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
62953
63390
|
volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
@@ -64875,9 +65312,14 @@ var PetFeederStatusSchema = object({
|
|
|
64875
65312
|
* (`device_class: problem`, on = low). True when the tank reports empty,
|
|
64876
65313
|
* or, on a dual-hopper model, when EITHER hopper is empty. */
|
|
64877
65314
|
lowFood: boolean(),
|
|
64878
|
-
/** Battery charge 0..100 (%).
|
|
64879
|
-
*
|
|
65315
|
+
/** Battery charge 0..100 (%). On a feeder running on `mains` this is the
|
|
65316
|
+
* BACKUP battery's charge, not the power source. Null when the device has
|
|
65317
|
+
* no battery reading. */
|
|
64880
65318
|
batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
65319
|
+
/** What the feeder is powered from right now: `mains` (plugged in, the
|
|
65320
|
+
* battery is only a backup) or `battery`. Null when the device does not
|
|
65321
|
+
* report it. */
|
|
65322
|
+
powerSource: _enum(["mains", "battery"]).nullable(),
|
|
64881
65323
|
/**
|
|
64882
65324
|
* Desiccant life remaining, stored in seconds (duration base, D944).
|
|
64883
65325
|
* Providers convert from the days the feeder reports. Null when the
|
|
@@ -64905,6 +65347,14 @@ var PetFeederStatusSchema = object({
|
|
|
64905
65347
|
feedSound: boolean(),
|
|
64906
65348
|
/** Speaker / prompt volume level (device-scaled integer). */
|
|
64907
65349
|
volume: number().meta(readingMeta("none")),
|
|
65350
|
+
/** Portion (grams) of the last manual feed sent from a single-hopper
|
|
65351
|
+
* stepper — the default the next manual feed starts from. Persisted per
|
|
65352
|
+
* device by the provider; null until a manual feed has been sent. */
|
|
65353
|
+
lastFeedGrams: gramsPortion.nullable(),
|
|
65354
|
+
/** Hopper-1 portion of the last manual feed (dual-hopper); null until sent. */
|
|
65355
|
+
lastFeedHopper1: gramsPortion.nullable(),
|
|
65356
|
+
/** Hopper-2 portion of the last manual feed (dual-hopper); null until sent. */
|
|
65357
|
+
lastFeedHopper2: gramsPortion.nullable(),
|
|
64908
65358
|
/** Ms epoch when the slice was last refreshed from the cloud. */
|
|
64909
65359
|
lastFetchedAt: number()
|
|
64910
65360
|
});
|
|
@@ -70942,9 +71392,17 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
|
|
|
70942
71392
|
kind: "mutation",
|
|
70943
71393
|
auth: "admin"
|
|
70944
71394
|
});
|
|
71395
|
+
discriminatedUnion("kind", [object({
|
|
71396
|
+
kind: literal("raw"),
|
|
71397
|
+
timings: array(number().int()),
|
|
71398
|
+
settings: TransmitSettingsSchema.optional()
|
|
71399
|
+
}), object({
|
|
71400
|
+
kind: literal("pronto"),
|
|
71401
|
+
code: string()
|
|
71402
|
+
})]);
|
|
70945
71403
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
70946
71404
|
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;
|
|
71405
|
+
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.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
71406
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
70949
71407
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
70950
71408
|
new Set(CAP_NAMES);
|
|
@@ -76097,6 +76555,20 @@ Object.freeze({
|
|
|
76097
76555
|
access: "create",
|
|
76098
76556
|
role: "viewer"
|
|
76099
76557
|
},
|
|
76558
|
+
"irTransmitter.getInfo": {
|
|
76559
|
+
capName: "ir-transmitter",
|
|
76560
|
+
capScope: "device",
|
|
76561
|
+
addonId: null,
|
|
76562
|
+
access: "view",
|
|
76563
|
+
role: "viewer"
|
|
76564
|
+
},
|
|
76565
|
+
"irTransmitter.send": {
|
|
76566
|
+
capName: "ir-transmitter",
|
|
76567
|
+
capScope: "device",
|
|
76568
|
+
addonId: null,
|
|
76569
|
+
access: "create",
|
|
76570
|
+
role: "viewer"
|
|
76571
|
+
},
|
|
76100
76572
|
"keychain.delete": {
|
|
76101
76573
|
capName: "keychain",
|
|
76102
76574
|
capScope: "system",
|
|
@@ -77329,6 +77801,13 @@ Object.freeze({
|
|
|
77329
77801
|
access: "create",
|
|
77330
77802
|
role: "admin"
|
|
77331
77803
|
},
|
|
77804
|
+
"nodeRemovalHook.onNodeRemoved": {
|
|
77805
|
+
capName: "node-removal-hook",
|
|
77806
|
+
capScope: "system",
|
|
77807
|
+
addonId: null,
|
|
77808
|
+
access: "create",
|
|
77809
|
+
role: "admin"
|
|
77810
|
+
},
|
|
77332
77811
|
"nodes.clusterAddonStatus": {
|
|
77333
77812
|
capName: "nodes",
|
|
77334
77813
|
capScope: "system",
|
|
@@ -80220,6 +80699,41 @@ Object.freeze({
|
|
|
80220
80699
|
access: "create",
|
|
80221
80700
|
role: "admin"
|
|
80222
80701
|
},
|
|
80702
|
+
"remote.getConfig": {
|
|
80703
|
+
capName: "remote",
|
|
80704
|
+
capScope: "system",
|
|
80705
|
+
addonId: null,
|
|
80706
|
+
access: "view",
|
|
80707
|
+
role: "admin"
|
|
80708
|
+
},
|
|
80709
|
+
"remote.listCommands": {
|
|
80710
|
+
capName: "remote",
|
|
80711
|
+
capScope: "system",
|
|
80712
|
+
addonId: null,
|
|
80713
|
+
access: "view",
|
|
80714
|
+
role: "admin"
|
|
80715
|
+
},
|
|
80716
|
+
"remote.sendCommand": {
|
|
80717
|
+
capName: "remote",
|
|
80718
|
+
capScope: "system",
|
|
80719
|
+
addonId: null,
|
|
80720
|
+
access: "create",
|
|
80721
|
+
role: "admin"
|
|
80722
|
+
},
|
|
80723
|
+
"remote.sendMediaCommand": {
|
|
80724
|
+
capName: "remote",
|
|
80725
|
+
capScope: "system",
|
|
80726
|
+
addonId: null,
|
|
80727
|
+
access: "create",
|
|
80728
|
+
role: "admin"
|
|
80729
|
+
},
|
|
80730
|
+
"remote.setConfig": {
|
|
80731
|
+
capName: "remote",
|
|
80732
|
+
capScope: "system",
|
|
80733
|
+
addonId: null,
|
|
80734
|
+
access: "create",
|
|
80735
|
+
role: "admin"
|
|
80736
|
+
},
|
|
80223
80737
|
"runningState.getStatus": {
|
|
80224
80738
|
capName: "running-state",
|
|
80225
80739
|
capScope: "device",
|
|
@@ -81410,6 +81924,41 @@ Object.freeze({
|
|
|
81410
81924
|
access: "view",
|
|
81411
81925
|
role: "viewer"
|
|
81412
81926
|
},
|
|
81927
|
+
"template.create": {
|
|
81928
|
+
capName: "template",
|
|
81929
|
+
capScope: "system",
|
|
81930
|
+
addonId: null,
|
|
81931
|
+
access: "create",
|
|
81932
|
+
role: "admin"
|
|
81933
|
+
},
|
|
81934
|
+
"template.delete": {
|
|
81935
|
+
capName: "template",
|
|
81936
|
+
capScope: "system",
|
|
81937
|
+
addonId: null,
|
|
81938
|
+
access: "delete",
|
|
81939
|
+
role: "admin"
|
|
81940
|
+
},
|
|
81941
|
+
"template.get": {
|
|
81942
|
+
capName: "template",
|
|
81943
|
+
capScope: "system",
|
|
81944
|
+
addonId: null,
|
|
81945
|
+
access: "view",
|
|
81946
|
+
role: "admin"
|
|
81947
|
+
},
|
|
81948
|
+
"template.list": {
|
|
81949
|
+
capName: "template",
|
|
81950
|
+
capScope: "system",
|
|
81951
|
+
addonId: null,
|
|
81952
|
+
access: "view",
|
|
81953
|
+
role: "admin"
|
|
81954
|
+
},
|
|
81955
|
+
"template.update": {
|
|
81956
|
+
capName: "template",
|
|
81957
|
+
capScope: "system",
|
|
81958
|
+
addonId: null,
|
|
81959
|
+
access: "create",
|
|
81960
|
+
role: "admin"
|
|
81961
|
+
},
|
|
81413
81962
|
"terminalSession.adoptLegacyMonitor": {
|
|
81414
81963
|
capName: "terminal-session",
|
|
81415
81964
|
capScope: "system",
|
|
@@ -83720,6 +84269,11 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
83720
84269
|
icon: "circle-dot"
|
|
83721
84270
|
}]
|
|
83722
84271
|
},
|
|
84272
|
+
"ir-transmitter": {
|
|
84273
|
+
scope: "device",
|
|
84274
|
+
noun: t("IR / RF transmitter", "Trasmettitore IR / RF"),
|
|
84275
|
+
notOperatorFacing: { send: "a raw pulse train is not something an operator composes in an automation; the remote that owns the command table sends it" }
|
|
84276
|
+
},
|
|
83723
84277
|
control: {
|
|
83724
84278
|
scope: "device",
|
|
83725
84279
|
noun: t("Control", "Controllo"),
|
|
@@ -86197,6 +86751,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
86197
86751
|
"device-manager": system("the device registry itself — rename, move and remove are administration"),
|
|
86198
86752
|
"device-provider": system(INTERNAL),
|
|
86199
86753
|
"device-removal-hook": system(INTERNAL),
|
|
86754
|
+
"node-removal-hook": system(INTERNAL),
|
|
86200
86755
|
"device-state": system("the runtime-state mirror every trigger already reads through"),
|
|
86201
86756
|
"display-palette": system("the cluster display palette — one colour table for every client, not a device action"),
|
|
86202
86757
|
"embedding-encoder": system(ML),
|
|
@@ -86236,6 +86791,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
86236
86791
|
"recording-archive": system(STORAGE),
|
|
86237
86792
|
"recording-export": system("recording exports — requested from the timeline, not an automation step"),
|
|
86238
86793
|
records: system(INTERNAL),
|
|
86794
|
+
remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
|
|
86239
86795
|
"server-management": system(CLUSTER),
|
|
86240
86796
|
"settings-store": system(INTERNAL),
|
|
86241
86797
|
"smtp-provider": system(INTERNAL),
|
|
@@ -86248,6 +86804,7 @@ var SYSTEM_CAP_VOCABULARY = {
|
|
|
86248
86804
|
"storage-provider": system(STORAGE),
|
|
86249
86805
|
"stream-broker": system(PIPELINE),
|
|
86250
86806
|
system: system(CLUSTER),
|
|
86807
|
+
template: system("operator-authored templates — edited in their own editor, never actuated"),
|
|
86251
86808
|
"terminal-session": system(CLUSTER),
|
|
86252
86809
|
toast: system(UI_PLUMBING),
|
|
86253
86810
|
training: system(ML),
|
|
@@ -87439,6 +87996,16 @@ Object.freeze({
|
|
|
87439
87996
|
form: "single",
|
|
87440
87997
|
optional: false
|
|
87441
87998
|
}],
|
|
87999
|
+
"irTransmitter.getInfo": [{
|
|
88000
|
+
name: "deviceId",
|
|
88001
|
+
form: "single",
|
|
88002
|
+
optional: false
|
|
88003
|
+
}],
|
|
88004
|
+
"irTransmitter.send": [{
|
|
88005
|
+
name: "deviceId",
|
|
88006
|
+
form: "single",
|
|
88007
|
+
optional: false
|
|
88008
|
+
}],
|
|
87442
88009
|
"keychain.deleteDeviceScope": [{
|
|
87443
88010
|
name: "deviceId",
|
|
87444
88011
|
form: "single",
|
|
@@ -88664,6 +89231,31 @@ Object.freeze({
|
|
|
88664
89231
|
form: "single",
|
|
88665
89232
|
optional: true
|
|
88666
89233
|
}],
|
|
89234
|
+
"remote.getConfig": [{
|
|
89235
|
+
name: "deviceId",
|
|
89236
|
+
form: "single",
|
|
89237
|
+
optional: false
|
|
89238
|
+
}],
|
|
89239
|
+
"remote.listCommands": [{
|
|
89240
|
+
name: "deviceId",
|
|
89241
|
+
form: "single",
|
|
89242
|
+
optional: false
|
|
89243
|
+
}],
|
|
89244
|
+
"remote.sendCommand": [{
|
|
89245
|
+
name: "deviceId",
|
|
89246
|
+
form: "single",
|
|
89247
|
+
optional: false
|
|
89248
|
+
}],
|
|
89249
|
+
"remote.sendMediaCommand": [{
|
|
89250
|
+
name: "deviceId",
|
|
89251
|
+
form: "single",
|
|
89252
|
+
optional: false
|
|
89253
|
+
}],
|
|
89254
|
+
"remote.setConfig": [{
|
|
89255
|
+
name: "deviceId",
|
|
89256
|
+
form: "single",
|
|
89257
|
+
optional: false
|
|
89258
|
+
}],
|
|
88667
89259
|
"runningState.getStatus": [{
|
|
88668
89260
|
name: "deviceId",
|
|
88669
89261
|
form: "single",
|
|
@@ -90898,6 +91490,81 @@ function rewriteFormBody(body, route) {
|
|
|
90898
91490
|
function filterForwardedCookies(cookieHeader) {
|
|
90899
91491
|
return cookieHeader.split(";").map((part) => part.trim()).filter((part) => part.length > 0 && !part.toLowerCase().startsWith("camstack_")).join("; ");
|
|
90900
91492
|
}
|
|
91493
|
+
/** A short stable label for the attempt: enough to group lines, useless as a capability. */
|
|
91494
|
+
function attemptLabel(attemptId) {
|
|
91495
|
+
let hash = 2166136261;
|
|
91496
|
+
for (let i = 0; i < attemptId.length; i += 1) hash = Math.imul(hash ^ attemptId.charCodeAt(i), 16777619) >>> 0;
|
|
91497
|
+
return hash.toString(16).padStart(8, "0");
|
|
91498
|
+
}
|
|
91499
|
+
/** Distinct parameter names of a `?a=1&b=2` string (or of a urlencoded body), in order. */
|
|
91500
|
+
function paramNames(query) {
|
|
91501
|
+
const names = [];
|
|
91502
|
+
const raw = query.startsWith("?") ? query.slice(1) : query;
|
|
91503
|
+
for (const pair of raw.split("&")) {
|
|
91504
|
+
if (pair.length === 0) continue;
|
|
91505
|
+
const eq = pair.indexOf("=");
|
|
91506
|
+
const rawName = eq === -1 ? pair : pair.slice(0, eq);
|
|
91507
|
+
let name;
|
|
91508
|
+
try {
|
|
91509
|
+
name = decodeURIComponent(rawName.replace(/\+/g, " "));
|
|
91510
|
+
} catch {
|
|
91511
|
+
name = rawName;
|
|
91512
|
+
}
|
|
91513
|
+
if (!names.includes(name)) names.push(name);
|
|
91514
|
+
}
|
|
91515
|
+
return names;
|
|
91516
|
+
}
|
|
91517
|
+
function hasOa2(names) {
|
|
91518
|
+
return names.some((name) => name.startsWith("openid.oa2.") || name === "openid.ns.oa2");
|
|
91519
|
+
}
|
|
91520
|
+
function cookieNamesOf(setCookies) {
|
|
91521
|
+
return setCookies.map((c) => c.split(";")[0]?.split("=")[0]?.trim() ?? "");
|
|
91522
|
+
}
|
|
91523
|
+
function mediaType(contentType) {
|
|
91524
|
+
return (contentType ?? "none").split(";")[0]?.trim().toLowerCase() || "none";
|
|
91525
|
+
}
|
|
91526
|
+
/** Host and path of a Location (no query), or 'unparsable'. */
|
|
91527
|
+
function locationShape(location, base) {
|
|
91528
|
+
try {
|
|
91529
|
+
const url = new URL(location, base);
|
|
91530
|
+
return {
|
|
91531
|
+
target: `${url.host}${url.pathname}`,
|
|
91532
|
+
names: paramNames(url.search)
|
|
91533
|
+
};
|
|
91534
|
+
} catch {
|
|
91535
|
+
return {
|
|
91536
|
+
target: "unparsable",
|
|
91537
|
+
names: []
|
|
91538
|
+
};
|
|
91539
|
+
}
|
|
91540
|
+
}
|
|
91541
|
+
/** The `meta` of one trace line. Values: never. */
|
|
91542
|
+
function flowTraceMeta(input) {
|
|
91543
|
+
const queryNames = paramNames(input.requestSearch);
|
|
91544
|
+
const formNames = input.formBody === null ? null : paramNames(input.formBody);
|
|
91545
|
+
const meta = {
|
|
91546
|
+
session: attemptLabel(input.attemptId),
|
|
91547
|
+
method: input.method,
|
|
91548
|
+
upstream: `${input.upstreamHost}${input.upstreamPath}`,
|
|
91549
|
+
queryNames: queryNames.join(",") || "none",
|
|
91550
|
+
oa2InQuery: hasOa2(queryNames),
|
|
91551
|
+
status: input.status,
|
|
91552
|
+
setCookieNames: cookieNamesOf(input.setCookies).join(",") || "none",
|
|
91553
|
+
cookiesSent: (input.cookieHeader ?? "").split(";").filter((p) => p.trim().length > 0).length
|
|
91554
|
+
};
|
|
91555
|
+
if (formNames !== null) {
|
|
91556
|
+
meta["contentType"] = mediaType(input.contentType);
|
|
91557
|
+
meta["formNames"] = formNames.join(",") || "none";
|
|
91558
|
+
meta["oa2InForm"] = hasOa2(formNames);
|
|
91559
|
+
}
|
|
91560
|
+
if (input.location !== null) {
|
|
91561
|
+
const loc = locationShape(input.location, `http://${input.upstreamHost}/`);
|
|
91562
|
+
meta["location"] = loc.target;
|
|
91563
|
+
meta["locationQueryNames"] = loc.names.join(",") || "none";
|
|
91564
|
+
meta["oa2InLocation"] = hasOa2(loc.names);
|
|
91565
|
+
}
|
|
91566
|
+
return meta;
|
|
91567
|
+
}
|
|
90901
91568
|
var MAX_REQUEST_BODY_BYTES = 2 * 1024 * 1024;
|
|
90902
91569
|
var MAX_TEXT_BODY_BYTES = 4 * 1024 * 1024;
|
|
90903
91570
|
/** Request headers that are never forwarded to the vendor. */
|
|
@@ -91002,6 +91669,8 @@ var VendorLoginProxy = class {
|
|
|
91002
91669
|
#now;
|
|
91003
91670
|
#fetch;
|
|
91004
91671
|
#attempts = /* @__PURE__ */ new Map();
|
|
91672
|
+
/** Trace lines written per attempt (bounded by FLOW_TRACE_MAX_LINES). */
|
|
91673
|
+
#traced = /* @__PURE__ */ new Map();
|
|
91005
91674
|
constructor(options) {
|
|
91006
91675
|
this.#addonId = options.addonId;
|
|
91007
91676
|
this.#logger = options.logger;
|
|
@@ -91028,6 +91697,7 @@ var VendorLoginProxy = class {
|
|
|
91028
91697
|
}
|
|
91029
91698
|
close(attemptId) {
|
|
91030
91699
|
this.#attempts.delete(attemptId);
|
|
91700
|
+
this.#traced.delete(attemptId);
|
|
91031
91701
|
}
|
|
91032
91702
|
isOpen(attemptId) {
|
|
91033
91703
|
const attempt = this.#attempts.get(attemptId);
|
|
@@ -91074,7 +91744,7 @@ var VendorLoginProxy = class {
|
|
|
91074
91744
|
const forwardedPrefix = (firstHeader(req.headers["x-forwarded-prefix"]) ?? "").replace(/\/+$/, "");
|
|
91075
91745
|
const attempt = this.#attempts.get(parsed.attemptId);
|
|
91076
91746
|
if (attempt === void 0 || this.#now() > attempt.expiresAtMs) {
|
|
91077
|
-
if (attempt !== void 0) this
|
|
91747
|
+
if (attempt !== void 0) this.close(parsed.attemptId);
|
|
91078
91748
|
this.#logger.info("vendor login proxy: attempt refused", { meta: {
|
|
91079
91749
|
addonId: this.#addonId,
|
|
91080
91750
|
reason: attempt === void 0 ? "missing" : "expired"
|
|
@@ -91099,13 +91769,17 @@ var VendorLoginProxy = class {
|
|
|
91099
91769
|
}
|
|
91100
91770
|
const method = (req.method ?? "GET").toUpperCase();
|
|
91101
91771
|
let body;
|
|
91772
|
+
let formBodyRaw = null;
|
|
91102
91773
|
if (method !== "GET" && method !== "HEAD") {
|
|
91103
91774
|
const read = await readRequestBody(req);
|
|
91104
91775
|
if (read === "too-large") {
|
|
91105
91776
|
sendPlain(res, 413, "Request too large");
|
|
91106
91777
|
return;
|
|
91107
91778
|
}
|
|
91108
|
-
|
|
91779
|
+
if (isFormEncoded(req.headers["content-type"])) {
|
|
91780
|
+
formBodyRaw = read.toString("utf8");
|
|
91781
|
+
body = Buffer.from(rewriteFormBody(formBodyRaw, route), "utf8");
|
|
91782
|
+
} else body = read;
|
|
91109
91783
|
}
|
|
91110
91784
|
const headers = this.#requestHeaders(req, route, upstream);
|
|
91111
91785
|
const upstreamRes = await this.#fetch(target, {
|
|
@@ -91130,6 +91804,19 @@ var VendorLoginProxy = class {
|
|
|
91130
91804
|
sentNames: cookieNames(headers["cookie"]),
|
|
91131
91805
|
vendorSets: setCookies.map(setCookieShape).join(" | ")
|
|
91132
91806
|
} });
|
|
91807
|
+
this.#trace(parsed.attemptId, {
|
|
91808
|
+
attemptId: parsed.attemptId,
|
|
91809
|
+
method,
|
|
91810
|
+
upstreamHost: upstream.host,
|
|
91811
|
+
upstreamPath: target.pathname,
|
|
91812
|
+
requestSearch: target.search,
|
|
91813
|
+
formBody: formBodyRaw,
|
|
91814
|
+
contentType: firstHeader(req.headers["content-type"]),
|
|
91815
|
+
status: upstreamRes.status,
|
|
91816
|
+
location,
|
|
91817
|
+
setCookies,
|
|
91818
|
+
cookieHeader: headers["cookie"]
|
|
91819
|
+
});
|
|
91133
91820
|
const settled = await attempt.evaluate({
|
|
91134
91821
|
status: upstreamRes.status,
|
|
91135
91822
|
url: target.toString(),
|
|
@@ -91138,7 +91825,7 @@ var VendorLoginProxy = class {
|
|
|
91138
91825
|
bodyText: text
|
|
91139
91826
|
});
|
|
91140
91827
|
if (settled.kind === "done") {
|
|
91141
|
-
this
|
|
91828
|
+
this.close(parsed.attemptId);
|
|
91142
91829
|
this.#logger.info("vendor login proxy: sign-in finished", { meta: {
|
|
91143
91830
|
addonId: this.#addonId,
|
|
91144
91831
|
status: settled.landing.status
|
|
@@ -91162,6 +91849,16 @@ var VendorLoginProxy = class {
|
|
|
91162
91849
|
res.writeHead(upstreamRes.status, out);
|
|
91163
91850
|
await this.#pipeBody(upstreamRes, res);
|
|
91164
91851
|
}
|
|
91852
|
+
/** One names-only line per proxied request, at most FLOW_TRACE_MAX_LINES per attempt. */
|
|
91853
|
+
#trace(attemptId, input) {
|
|
91854
|
+
const written = this.#traced.get(attemptId) ?? 0;
|
|
91855
|
+
if (written >= 200) return;
|
|
91856
|
+
this.#traced.set(attemptId, written + 1);
|
|
91857
|
+
this.#logger.info("vendor login proxy: flow", { meta: {
|
|
91858
|
+
addonId: this.#addonId,
|
|
91859
|
+
...flowTraceMeta(input)
|
|
91860
|
+
} });
|
|
91861
|
+
}
|
|
91165
91862
|
#requestHeaders(req, route, upstream) {
|
|
91166
91863
|
const headers = {};
|
|
91167
91864
|
for (const [name, value] of Object.entries(req.headers)) {
|
|
@@ -91203,6 +91900,14 @@ var VendorLoginProxy = class {
|
|
|
91203
91900
|
};
|
|
91204
91901
|
var MB = 1024 * 1024;
|
|
91205
91902
|
1024 * MB, 3072 * MB;
|
|
91903
|
+
object({
|
|
91904
|
+
manufacturer: string().optional(),
|
|
91905
|
+
supportedModels: array(string()).optional(),
|
|
91906
|
+
supportedController: string().optional(),
|
|
91907
|
+
commandsEncoding: string(),
|
|
91908
|
+
delay: number().nonnegative().optional(),
|
|
91909
|
+
commands: record(string(), unknown())
|
|
91910
|
+
});
|
|
91206
91911
|
//#endregion
|
|
91207
91912
|
//#region src/config.ts
|
|
91208
91913
|
/**
|
|
@@ -133141,20 +133846,36 @@ var ADDON_ID = "import-alexa";
|
|
|
133141
133846
|
var ATTEMPT_TTL_MS = 600 * 1e3;
|
|
133142
133847
|
/** How long the browser waits on the Amazon page for alexa-cookie2 to finish registering. */
|
|
133143
133848
|
var CAPTURE_WAIT_MS = 6e4;
|
|
133849
|
+
/** The query parameter Amazon sets on `/ap/maplanding` when it honoured the oa2 request. */
|
|
133850
|
+
var AUTHORIZATION_CODE_PARAM = "openid.oa2.authorization_code";
|
|
133144
133851
|
/**
|
|
133145
|
-
* Terminal condition
|
|
133146
|
-
* `/cookie-success` page
|
|
133147
|
-
* `
|
|
133852
|
+
* Terminal condition. The login is over when alexa-cookie2 turns Amazon's final redirect into
|
|
133853
|
+
* a 302 to its own `/cookie-success` page (it only does so when the landing carries the oa2
|
|
133854
|
+
* authorization code), OR when Amazon's `/ap/maplanding` itself shows up as a `Location` or as
|
|
133855
|
+
* the request. A maplanding WITHOUT the authorization code is a dead end: alexa-cookie2 cannot
|
|
133856
|
+
* register a device from it and no cookie will ever come, so it ends at once as
|
|
133857
|
+
* `no_authorization_code` instead of after the capture wait. Only paths and parameter NAMES are
|
|
133858
|
+
* read, never a value (the query carries signed openid values).
|
|
133148
133859
|
*/
|
|
133149
133860
|
function cookieSuccessTerminal(view) {
|
|
133150
|
-
|
|
133151
|
-
|
|
133152
|
-
|
|
133861
|
+
for (const raw of [view.url, view.location]) {
|
|
133862
|
+
if (raw === null) continue;
|
|
133863
|
+
const end = loginEnd(raw);
|
|
133864
|
+
if (end !== null) return {
|
|
133153
133865
|
kind: "done",
|
|
133154
|
-
payload:
|
|
133155
|
-
}
|
|
133866
|
+
payload: end
|
|
133867
|
+
};
|
|
133868
|
+
}
|
|
133869
|
+
return { kind: "continue" };
|
|
133870
|
+
}
|
|
133871
|
+
function loginEnd(raw) {
|
|
133872
|
+
try {
|
|
133873
|
+
const url = new URL(raw, `http://${ALEXA_PROXY_LOOPBACK_HOST}`);
|
|
133874
|
+
if (url.pathname === "/cookie-success") return "registering";
|
|
133875
|
+
if (!url.pathname.endsWith("/ap/maplanding")) return null;
|
|
133876
|
+
return url.searchParams.has(AUTHORIZATION_CODE_PARAM) ? "registering" : "no_authorization_code";
|
|
133156
133877
|
} catch {
|
|
133157
|
-
return
|
|
133878
|
+
return null;
|
|
133158
133879
|
}
|
|
133159
133880
|
}
|
|
133160
133881
|
var AlexaProxyLoginManager = class {
|
|
@@ -133190,7 +133911,15 @@ var AlexaProxyLoginManager = class {
|
|
|
133190
133911
|
scheme: "http"
|
|
133191
133912
|
}],
|
|
133192
133913
|
terminal: cookieSuccessTerminal,
|
|
133193
|
-
onDone: async () => {
|
|
133914
|
+
onDone: async (end) => {
|
|
133915
|
+
if (end === "no_authorization_code") {
|
|
133916
|
+
this.#logger.warn("alexa proxy-login: Amazon ended the login without an authorization code", { meta: { sessionId } });
|
|
133917
|
+
return {
|
|
133918
|
+
status: "error",
|
|
133919
|
+
addonId: ADDON_ID,
|
|
133920
|
+
reason: "no_authorization_code"
|
|
133921
|
+
};
|
|
133922
|
+
}
|
|
133194
133923
|
return await this.#session?.whenCaptured(this.#captureWaitMs) === true ? {
|
|
133195
133924
|
status: "success",
|
|
133196
133925
|
addonId: ADDON_ID
|