@camstack/addon-import-alexa 0.2.185 → 0.2.187
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 +287 -91
- package/dist/addon.mjs +287 -91
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.187] - 2026-11-05
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- The integration-settings drop no longer refuses on an Alexa account; its legacy broker link is carried onto the broker record first
|
|
15
|
+
|
|
16
|
+
## [0.2.186] - 2026-11-05
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- An Alexa integration links to its account through the account record
|
|
21
|
+
|
|
10
22
|
## [0.2.185] - 2026-11-04
|
|
11
23
|
|
|
12
24
|
### Changed
|
package/dist/addon.js
CHANGED
|
@@ -6044,7 +6044,7 @@ function number(params) {
|
|
|
6044
6044
|
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
6045
6045
|
}
|
|
6046
6046
|
//#endregion
|
|
6047
|
-
//#region ../types/dist/sleep-
|
|
6047
|
+
//#region ../types/dist/sleep-9TIHFj1w.mjs
|
|
6048
6048
|
/**
|
|
6049
6049
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6050
6050
|
* window to float samples (D455).
|
|
@@ -7988,10 +7988,9 @@ var BaseAddon = class {
|
|
|
7988
7988
|
*
|
|
7989
7989
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7990
7990
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7991
|
-
* SHAPE of the form only — the values live
|
|
7992
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7991
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7993
7992
|
*/
|
|
7994
|
-
async
|
|
7993
|
+
async integrationFormSchema(_instance) {
|
|
7995
7994
|
return null;
|
|
7996
7995
|
}
|
|
7997
7996
|
/**
|
|
@@ -8037,7 +8036,7 @@ var BaseAddon = class {
|
|
|
8037
8036
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
8038
8037
|
*/
|
|
8039
8038
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
8040
|
-
const scoped = await this.
|
|
8039
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
8041
8040
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
8042
8041
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
8043
8042
|
}
|
|
@@ -8842,6 +8841,13 @@ var RecordsError = class extends Error {
|
|
|
8842
8841
|
this.detail = detail;
|
|
8843
8842
|
}
|
|
8844
8843
|
};
|
|
8844
|
+
/** A write guarded by `ifRev` found another revision (or an existing row for `ifRev: 0`). */
|
|
8845
|
+
var RevConflictError = class extends RecordsError {
|
|
8846
|
+
constructor(detail) {
|
|
8847
|
+
super("rev-conflict", detail);
|
|
8848
|
+
this.name = "RevConflictError";
|
|
8849
|
+
}
|
|
8850
|
+
};
|
|
8845
8851
|
/** The keychain could not store or serve a value: explicit, never an empty string. */
|
|
8846
8852
|
var RecordSecretUnavailableError = class extends RecordsError {
|
|
8847
8853
|
constructor(detail) {
|
|
@@ -9311,6 +9317,7 @@ function defineEntryKind(name, spec) {
|
|
|
9311
9317
|
class: "config",
|
|
9312
9318
|
writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
|
|
9313
9319
|
retention: { none: "an entry goes only when the operator deletes it" },
|
|
9320
|
+
...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
|
|
9314
9321
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9315
9322
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9316
9323
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
@@ -11980,7 +11987,7 @@ object({
|
|
|
11980
11987
|
params: record(string(), union([string(), number$1()])).optional()
|
|
11981
11988
|
});
|
|
11982
11989
|
//#endregion
|
|
11983
|
-
//#region ../types/dist/composition-
|
|
11990
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11984
11991
|
/**
|
|
11985
11992
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11986
11993
|
*
|
|
@@ -14770,6 +14777,72 @@ function flattenPortsOver(handle) {
|
|
|
14770
14777
|
};
|
|
14771
14778
|
}
|
|
14772
14779
|
/**
|
|
14780
|
+
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
|
|
14781
|
+
* from its own process, so the leftover rows are reached through the migration-only door
|
|
14782
|
+
* (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
|
|
14783
|
+
* The door returns markers. This process is the owner, so it resolves and releases them with
|
|
14784
|
+
* `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
|
|
14785
|
+
* door to delete the rows. The addon's own record kind is its records handle.
|
|
14786
|
+
*/
|
|
14787
|
+
async function resolvedRows(stored, secrets) {
|
|
14788
|
+
if (secrets === null) return stored;
|
|
14789
|
+
const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
|
|
14790
|
+
if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
|
|
14791
|
+
return value;
|
|
14792
|
+
}
|
|
14793
|
+
function integrationRowsPortsOverApi(deps) {
|
|
14794
|
+
const secrets = deps.secrets ?? null;
|
|
14795
|
+
return {
|
|
14796
|
+
warn: (message) => deps.log.warn(message),
|
|
14797
|
+
listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
|
|
14798
|
+
id: i.id,
|
|
14799
|
+
name: i.name
|
|
14800
|
+
})),
|
|
14801
|
+
readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
|
|
14802
|
+
putRecord: async (id, body) => {
|
|
14803
|
+
if (await deps.records.get(id) !== null) return "exists";
|
|
14804
|
+
try {
|
|
14805
|
+
await deps.records.put({
|
|
14806
|
+
id,
|
|
14807
|
+
data: body
|
|
14808
|
+
}, { ifRev: 0 });
|
|
14809
|
+
} catch (err) {
|
|
14810
|
+
if (err instanceof RevConflictError) return "exists";
|
|
14811
|
+
throw err;
|
|
14812
|
+
}
|
|
14813
|
+
return "created";
|
|
14814
|
+
},
|
|
14815
|
+
readRecord: async (id) => (await deps.records.getResolved(id))?.data ?? null,
|
|
14816
|
+
deleteRecord: async (id) => {
|
|
14817
|
+
await deps.records.delete(id);
|
|
14818
|
+
},
|
|
14819
|
+
clearRows: async (integrationId) => {
|
|
14820
|
+
const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
|
|
14821
|
+
if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
|
|
14822
|
+
await deps.api.clearLegacyIntegrationRows({ integrationId });
|
|
14823
|
+
}
|
|
14824
|
+
};
|
|
14825
|
+
}
|
|
14826
|
+
function defaultIntegrationApiOver(api) {
|
|
14827
|
+
return {
|
|
14828
|
+
list: () => api.integrations.list.query(),
|
|
14829
|
+
readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
|
|
14830
|
+
clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
|
|
14831
|
+
listDevices: (addonId) => api.deviceManager.listAll.query({
|
|
14832
|
+
addonId,
|
|
14833
|
+
projection: "slim"
|
|
14834
|
+
}),
|
|
14835
|
+
setDeviceIntegrationId: async (deviceId, integrationId) => {
|
|
14836
|
+
await api.deviceManager.setIntegrationId.mutate({
|
|
14837
|
+
deviceId,
|
|
14838
|
+
integrationId
|
|
14839
|
+
});
|
|
14840
|
+
},
|
|
14841
|
+
readDeviceHasConfig: async (deviceId) => Object.keys((await api.deviceManager.getDevice.query({ deviceId }))?.config ?? {}).length > 0,
|
|
14842
|
+
readDeviceIntegrationId: async (deviceId) => (await api.deviceManager.getDevice.query({ deviceId }))?.integrationId ?? null
|
|
14843
|
+
};
|
|
14844
|
+
}
|
|
14845
|
+
/**
|
|
14773
14846
|
* The trainer contract — what the hub hands the cloud trainer and what it
|
|
14774
14847
|
* gets back (cloud training, D805+).
|
|
14775
14848
|
*
|
|
@@ -50460,6 +50533,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50460
50533
|
runsFound: number$1().int().nonnegative(),
|
|
50461
50534
|
pausesFound: number$1().int().nonnegative(),
|
|
50462
50535
|
longestPauseMs: number$1().nonnegative(),
|
|
50536
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50537
|
+
pulsesFound: number$1().int().nonnegative(),
|
|
50538
|
+
longestPulseMs: number$1().nonnegative(),
|
|
50463
50539
|
idleFloorW: number$1(),
|
|
50464
50540
|
noiseCeilingW: number$1(),
|
|
50465
50541
|
plateauW: number$1(),
|
|
@@ -56305,11 +56381,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56305
56381
|
info: record(string(), unknown()).optional(),
|
|
56306
56382
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56307
56383
|
originStableId: string().optional(),
|
|
56308
|
-
settings: record(string(), unknown()).optional()
|
|
56309
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56310
|
-
* the keychain, not the table (D787). */
|
|
56311
|
-
secretKeys: array(string()).optional(),
|
|
56312
|
-
skipRestart: boolean().optional()
|
|
56384
|
+
settings: record(string(), unknown()).optional()
|
|
56313
56385
|
});
|
|
56314
56386
|
var UpdateIntegrationInputSchema = object({
|
|
56315
56387
|
id: string(),
|
|
@@ -56330,16 +56402,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56330
56402
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
56331
56403
|
kind: "mutation",
|
|
56332
56404
|
auth: "admin"
|
|
56333
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
56334
|
-
id: string(),
|
|
56335
|
-
settings: record(string(), unknown()),
|
|
56336
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56337
|
-
secretKeys: array(string()).optional(),
|
|
56338
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56339
|
-
skipRestart: boolean().optional()
|
|
56340
|
-
}), object({ success: boolean() }), {
|
|
56341
|
-
kind: "mutation",
|
|
56342
|
-
auth: "admin"
|
|
56343
56405
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56344
56406
|
addonId: string(),
|
|
56345
56407
|
settings: record(string(), unknown())
|
|
@@ -59316,6 +59378,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59316
59378
|
}), {
|
|
59317
59379
|
kind: "mutation",
|
|
59318
59380
|
auth: "admin"
|
|
59381
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
59382
|
+
rows: number$1().int().nonnegative(),
|
|
59383
|
+
secrets: number$1().int().nonnegative()
|
|
59384
|
+
}), {
|
|
59385
|
+
kind: "mutation",
|
|
59386
|
+
auth: "admin"
|
|
59387
|
+
}), method(object({
|
|
59388
|
+
integrationId: string().min(1),
|
|
59389
|
+
patch: record(string(), unknown())
|
|
59390
|
+
}), object({ written: boolean() }), {
|
|
59391
|
+
kind: "mutation",
|
|
59392
|
+
auth: "admin"
|
|
59393
|
+
}), method(object({
|
|
59394
|
+
integrationId: string().min(1),
|
|
59395
|
+
keys: array(string().min(1))
|
|
59396
|
+
}), object({
|
|
59397
|
+
present: boolean(),
|
|
59398
|
+
removed: record(string(), unknown())
|
|
59399
|
+
}), {
|
|
59400
|
+
kind: "mutation",
|
|
59401
|
+
auth: "admin"
|
|
59319
59402
|
});
|
|
59320
59403
|
method(object({
|
|
59321
59404
|
sourceUrl: string(),
|
|
@@ -80642,11 +80725,15 @@ discriminatedUnion("kind", [object({
|
|
|
80642
80725
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80643
80726
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80644
80727
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80645
|
-
* 3.
|
|
80646
|
-
*
|
|
80728
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80729
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80730
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80731
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80732
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80733
|
+
* is no run.
|
|
80647
80734
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80648
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80649
|
-
* covers the longest standby blip above T.
|
|
80735
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80736
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80650
80737
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80651
80738
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80652
80739
|
*/
|
|
@@ -80657,6 +80744,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80657
80744
|
30 * SECOND_MS;
|
|
80658
80745
|
60 * SECOND_MS;
|
|
80659
80746
|
20 * MINUTE_MS$1;
|
|
80747
|
+
90 * SECOND_MS;
|
|
80748
|
+
30 * MINUTE_MS$1;
|
|
80749
|
+
10 * MINUTE_MS$1;
|
|
80750
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80660
80751
|
10 * SECOND_MS;
|
|
80661
80752
|
10 * SECOND_MS;
|
|
80662
80753
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -85976,13 +86067,6 @@ Object.freeze({
|
|
|
85976
86067
|
access: "view",
|
|
85977
86068
|
role: "admin"
|
|
85978
86069
|
},
|
|
85979
|
-
"integrations.getSettings": {
|
|
85980
|
-
capName: "integrations",
|
|
85981
|
-
capScope: "system",
|
|
85982
|
-
addonId: null,
|
|
85983
|
-
access: "view",
|
|
85984
|
-
role: "admin"
|
|
85985
|
-
},
|
|
85986
86070
|
"integrations.list": {
|
|
85987
86071
|
capName: "integrations",
|
|
85988
86072
|
capScope: "system",
|
|
@@ -85990,13 +86074,6 @@ Object.freeze({
|
|
|
85990
86074
|
access: "view",
|
|
85991
86075
|
role: "admin"
|
|
85992
86076
|
},
|
|
85993
|
-
"integrations.setSettings": {
|
|
85994
|
-
capName: "integrations",
|
|
85995
|
-
capScope: "system",
|
|
85996
|
-
addonId: null,
|
|
85997
|
-
access: "create",
|
|
85998
|
-
role: "admin"
|
|
85999
|
-
},
|
|
86000
86077
|
"integrations.testConnection": {
|
|
86001
86078
|
capName: "integrations",
|
|
86002
86079
|
capScope: "system",
|
|
@@ -86991,6 +87068,13 @@ Object.freeze({
|
|
|
86991
87068
|
access: "view",
|
|
86992
87069
|
role: "admin"
|
|
86993
87070
|
},
|
|
87071
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
87072
|
+
capName: "migrations",
|
|
87073
|
+
capScope: "system",
|
|
87074
|
+
addonId: null,
|
|
87075
|
+
access: "delete",
|
|
87076
|
+
role: "admin"
|
|
87077
|
+
},
|
|
86994
87078
|
"migrations.declareOwner": {
|
|
86995
87079
|
capName: "migrations",
|
|
86996
87080
|
capScope: "system",
|
|
@@ -86998,6 +87082,13 @@ Object.freeze({
|
|
|
86998
87082
|
access: "create",
|
|
86999
87083
|
role: "admin"
|
|
87000
87084
|
},
|
|
87085
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
87086
|
+
capName: "migrations",
|
|
87087
|
+
capScope: "system",
|
|
87088
|
+
addonId: null,
|
|
87089
|
+
access: "delete",
|
|
87090
|
+
role: "admin"
|
|
87091
|
+
},
|
|
87001
87092
|
"migrations.dryRun": {
|
|
87002
87093
|
capName: "migrations",
|
|
87003
87094
|
capScope: "system",
|
|
@@ -87033,6 +87124,20 @@ Object.freeze({
|
|
|
87033
87124
|
access: "create",
|
|
87034
87125
|
role: "admin"
|
|
87035
87126
|
},
|
|
87127
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
87128
|
+
capName: "migrations",
|
|
87129
|
+
capScope: "system",
|
|
87130
|
+
addonId: null,
|
|
87131
|
+
access: "create",
|
|
87132
|
+
role: "admin"
|
|
87133
|
+
},
|
|
87134
|
+
"migrations.readLegacyIntegrationRows": {
|
|
87135
|
+
capName: "migrations",
|
|
87136
|
+
capScope: "system",
|
|
87137
|
+
addonId: null,
|
|
87138
|
+
access: "view",
|
|
87139
|
+
role: "admin"
|
|
87140
|
+
},
|
|
87036
87141
|
"migrations.restorePreMigration": {
|
|
87037
87142
|
capName: "migrations",
|
|
87038
87143
|
capScope: "system",
|
|
@@ -140564,7 +140669,7 @@ function isStringOrObject(value) {
|
|
|
140564
140669
|
//#endregion
|
|
140565
140670
|
//#region src/alexa-proxy-login-manager.ts
|
|
140566
140671
|
/** The addon id the result page names to return to. */
|
|
140567
|
-
var ADDON_ID = "import-alexa";
|
|
140672
|
+
var ADDON_ID$1 = "import-alexa";
|
|
140568
140673
|
/** An attempt lives as long as a sign-in attempt does elsewhere (10 min). */
|
|
140569
140674
|
var ATTEMPT_TTL_MS = 600 * 1e3;
|
|
140570
140675
|
/** How long the browser waits on the Amazon page for alexa-cookie2 to finish registering. */
|
|
@@ -140649,16 +140754,16 @@ var AlexaProxyLoginManager = class {
|
|
|
140649
140754
|
this.#logger.warn("alexa proxy-login: Amazon ended the login without an authorization code", { meta: { sessionId } });
|
|
140650
140755
|
return {
|
|
140651
140756
|
status: "error",
|
|
140652
|
-
addonId: ADDON_ID,
|
|
140757
|
+
addonId: ADDON_ID$1,
|
|
140653
140758
|
reason: "no_authorization_code"
|
|
140654
140759
|
};
|
|
140655
140760
|
}
|
|
140656
140761
|
return await this.#session?.whenCaptured(this.#captureWaitMs) === true ? {
|
|
140657
140762
|
status: "success",
|
|
140658
|
-
addonId: ADDON_ID
|
|
140763
|
+
addonId: ADDON_ID$1
|
|
140659
140764
|
} : {
|
|
140660
140765
|
status: "error",
|
|
140661
|
-
addonId: ADDON_ID,
|
|
140766
|
+
addonId: ADDON_ID$1,
|
|
140662
140767
|
reason: "exchange_failed"
|
|
140663
140768
|
};
|
|
140664
140769
|
}
|
|
@@ -144934,6 +145039,79 @@ var AlexaSpeakController = class {
|
|
|
144934
145039
|
}
|
|
144935
145040
|
};
|
|
144936
145041
|
//#endregion
|
|
145042
|
+
//#region src/migrations/integration-rows-to-record.ts
|
|
145043
|
+
/**
|
|
145044
|
+
* The Alexa integration's last legacy row, `brokerId` (the integration to broker link), moves onto
|
|
145045
|
+
* the broker record (planning#416): the runtime reads `broker.integrationId`, so the row is only
|
|
145046
|
+
* stamped across when the broker has no link yet, then every legacy row of the integration is
|
|
145047
|
+
* cleared (the door `integration_settings` is dropped through accepts an addon only once this
|
|
145048
|
+
* ran). Nothing is overwritten: a broker that already names an integration, or a `brokerId` that
|
|
145049
|
+
* names no broker, is left as it is and said so in a warning (ids only, never a value).
|
|
145050
|
+
* An empty door is a no-op, so a rerun is too.
|
|
145051
|
+
*/
|
|
145052
|
+
var INTEGRATION_ROWS_TO_RECORD_ID = "import-alexa/2026-11-05-integration-rows-to-record";
|
|
145053
|
+
var ADDON_ID = "import-alexa";
|
|
145054
|
+
async function moveAlexaIntegrationRows(ports) {
|
|
145055
|
+
const integrations = await ports.listIntegrations(ADDON_ID);
|
|
145056
|
+
let stamped = 0;
|
|
145057
|
+
let cleared = 0;
|
|
145058
|
+
for (const integration of integrations) {
|
|
145059
|
+
const rows = await ports.readIntegrationRows(integration.id);
|
|
145060
|
+
if (Object.keys(rows).length === 0) continue;
|
|
145061
|
+
const brokerId = rows["brokerId"];
|
|
145062
|
+
if (typeof brokerId === "string" && brokerId !== "") {
|
|
145063
|
+
const broker = await ports.readBrokerLink(brokerId);
|
|
145064
|
+
if (broker === null) ports.warn(`integration '${integration.id}': brokerId '${brokerId}' names no broker record; nothing linked`);
|
|
145065
|
+
else if (broker.integrationId !== void 0) ports.warn(`integration '${integration.id}': broker '${brokerId}' already names integration '${broker.integrationId}'; the link is not changed`);
|
|
145066
|
+
else {
|
|
145067
|
+
await ports.stampBrokerLink(brokerId, integration.id);
|
|
145068
|
+
if ((await ports.readBrokerLink(brokerId))?.integrationId !== integration.id) throw new Error(`integration '${integration.id}': the link did not read back from broker '${brokerId}' - the legacy settings are kept`);
|
|
145069
|
+
stamped += 1;
|
|
145070
|
+
}
|
|
145071
|
+
}
|
|
145072
|
+
await ports.clearIntegrationRows(integration.id);
|
|
145073
|
+
cleared += 1;
|
|
145074
|
+
}
|
|
145075
|
+
return cleared === 0 ? {
|
|
145076
|
+
outcome: "already-done",
|
|
145077
|
+
summary: { integrations: integrations.length }
|
|
145078
|
+
} : {
|
|
145079
|
+
outcome: "done",
|
|
145080
|
+
summary: {
|
|
145081
|
+
integrations: integrations.length,
|
|
145082
|
+
stamped,
|
|
145083
|
+
cleared
|
|
145084
|
+
}
|
|
145085
|
+
};
|
|
145086
|
+
}
|
|
145087
|
+
/** The hub side (the registry list, the migration-only door) and the broker records side. */
|
|
145088
|
+
function alexaIntegrationRowsPorts(deps) {
|
|
145089
|
+
const brokers = deps.records.kind(alexaBrokersEntryKind);
|
|
145090
|
+
const rows = integrationRowsPortsOverApi({
|
|
145091
|
+
api: deps.api,
|
|
145092
|
+
secrets: deps.secrets,
|
|
145093
|
+
records: deps.records.kind({
|
|
145094
|
+
name: alexaBrokersEntryKind.name,
|
|
145095
|
+
declaration: alexaBrokersEntryKind.declaration,
|
|
145096
|
+
schema: record(string(), unknown())
|
|
145097
|
+
}),
|
|
145098
|
+
log: { warn: deps.warn }
|
|
145099
|
+
});
|
|
145100
|
+
return {
|
|
145101
|
+
warn: deps.warn,
|
|
145102
|
+
listIntegrations: rows.listIntegrations,
|
|
145103
|
+
readIntegrationRows: rows.readRows,
|
|
145104
|
+
clearIntegrationRows: rows.clearRows,
|
|
145105
|
+
readBrokerLink: async (id) => {
|
|
145106
|
+
const row = await brokers.get(id);
|
|
145107
|
+
return row === null ? null : { integrationId: row.data.integrationId };
|
|
145108
|
+
},
|
|
145109
|
+
stampBrokerLink: async (id, integrationId) => {
|
|
145110
|
+
await brokers.patch(id, { integrationId });
|
|
145111
|
+
}
|
|
145112
|
+
};
|
|
145113
|
+
}
|
|
145114
|
+
//#endregion
|
|
144937
145115
|
//#region src/migrations/flatten-broker-records.ts
|
|
144938
145116
|
/**
|
|
144939
145117
|
* The Alexa broker records keep their account flat (planning#408 P3): the nested `connection`
|
|
@@ -144996,37 +145174,56 @@ async function stripEchoTargetVolumeAndMode(ports) {
|
|
|
144996
145174
|
//#endregion
|
|
144997
145175
|
//#region src/migrations/index.ts
|
|
144998
145176
|
/** The `import-alexa` catalogue (D987): offered to the engine when the addon initialises. */
|
|
144999
|
-
var ADDON_MIGRATIONS = [
|
|
145000
|
-
|
|
145001
|
-
|
|
145002
|
-
|
|
145003
|
-
|
|
145004
|
-
|
|
145005
|
-
|
|
145006
|
-
|
|
145007
|
-
|
|
145008
|
-
|
|
145009
|
-
|
|
145010
|
-
|
|
145011
|
-
|
|
145012
|
-
|
|
145013
|
-
|
|
145014
|
-
|
|
145015
|
-
|
|
145016
|
-
|
|
145017
|
-
|
|
145018
|
-
|
|
145019
|
-
|
|
145020
|
-
|
|
145021
|
-
|
|
145022
|
-
|
|
145023
|
-
|
|
145024
|
-
|
|
145025
|
-
|
|
145026
|
-
|
|
145027
|
-
|
|
145028
|
-
|
|
145029
|
-
}
|
|
145177
|
+
var ADDON_MIGRATIONS = [
|
|
145178
|
+
{
|
|
145179
|
+
meta: {
|
|
145180
|
+
id: "import-alexa/2026-11-01-strip-echo-target-volume-and-mode",
|
|
145181
|
+
owner: "import-alexa",
|
|
145182
|
+
level: "addon-state",
|
|
145183
|
+
seq: 1,
|
|
145184
|
+
dependsOn: [],
|
|
145185
|
+
store: "settings",
|
|
145186
|
+
scope: "cluster",
|
|
145187
|
+
onFailure: "tolerate",
|
|
145188
|
+
bodyKind: "procedure",
|
|
145189
|
+
description: "Remove the retired volume and mode from every stored Echo target",
|
|
145190
|
+
fixtureMark: "the Echo id and the name the migration does not know"
|
|
145191
|
+
},
|
|
145192
|
+
procedure: stripEchoTargetVolumeAndMode
|
|
145193
|
+
},
|
|
145194
|
+
{
|
|
145195
|
+
meta: {
|
|
145196
|
+
id: FLATTEN_BROKER_RECORDS_ID,
|
|
145197
|
+
owner: "import-alexa",
|
|
145198
|
+
level: "addon-state",
|
|
145199
|
+
seq: 2,
|
|
145200
|
+
dependsOn: [],
|
|
145201
|
+
store: "settings",
|
|
145202
|
+
scope: "cluster",
|
|
145203
|
+
onFailure: "block-owner",
|
|
145204
|
+
bodyKind: "procedure",
|
|
145205
|
+
description: "Move each Alexa account from a nested connection object to flat top-level fields under the same id, reading every secret back first",
|
|
145206
|
+
fixtureMark: "the integration link and the amazon domain of each account"
|
|
145207
|
+
},
|
|
145208
|
+
procedure: flattenBrokerRecords
|
|
145209
|
+
},
|
|
145210
|
+
{
|
|
145211
|
+
meta: {
|
|
145212
|
+
id: INTEGRATION_ROWS_TO_RECORD_ID,
|
|
145213
|
+
owner: "import-alexa",
|
|
145214
|
+
level: "addon-state",
|
|
145215
|
+
seq: 3,
|
|
145216
|
+
dependsOn: [FLATTEN_BROKER_RECORDS_ID],
|
|
145217
|
+
store: "settings",
|
|
145218
|
+
scope: "cluster",
|
|
145219
|
+
onFailure: "block-owner",
|
|
145220
|
+
bodyKind: "procedure",
|
|
145221
|
+
description: "Stamp the integration link from the legacy brokerId row onto its broker record when the broker has none, then clear the legacy integration settings rows",
|
|
145222
|
+
fixtureMark: "the name of the broker and the integration it already names"
|
|
145223
|
+
},
|
|
145224
|
+
procedure: moveAlexaIntegrationRows
|
|
145225
|
+
}
|
|
145226
|
+
];
|
|
145030
145227
|
//#endregion
|
|
145031
145228
|
//#region src/speak/icon-routes.ts
|
|
145032
145229
|
var ALEXA_ICON_SVG = "<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"#00CAFF\"><title>Amazon Alexa</title><path d=\"M12 0C5.37 0 0 5.37 0 12C0 18.09 4.53 23.11 10.4 23.9V21.5A1.59 1.59 0 0 0 9.32 19.97A8.41 8.41 0 0 1 3.6 11.8A8.37 8.37 0 0 1 12.09 3.6A8.4 8.4 0 0 1 20.4 12.31L20.39 12.38A8.68 8.68 0 0 1 20.36 12.76C20.36 12.83 20.35 12.9 20.34 12.96C20.34 13.04 20.33 13.12 20.32 13.19L20.3 13.29C19.27 20.07 10.45 23.87 10.4 23.9C10.92 23.97 11.46 24 12 24C18.63 24 24 18.63 24 12S18.63 0 12 0Z\"/></svg>";
|
|
@@ -145238,13 +145435,20 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145238
145435
|
] };
|
|
145239
145436
|
}
|
|
145240
145437
|
migrationPorts(records) {
|
|
145241
|
-
|
|
145438
|
+
const secrets = this.ctx.secrets;
|
|
145439
|
+
if (secrets === void 0) throw new Error("import-alexa: ctx.secrets is required");
|
|
145242
145440
|
return {
|
|
145243
145441
|
readRows: () => Array.isArray(this.config.echoTargets) ? this.config.echoTargets : [],
|
|
145244
145442
|
writeRows: async (rows) => {
|
|
145245
145443
|
await this.updateGlobalSettings({ echoTargets: [...readEchoTargets({ echoTargets: rows })] });
|
|
145246
145444
|
},
|
|
145247
|
-
...flattenPortsOver(records.kind(alexaBrokersEntryKind))
|
|
145445
|
+
...flattenPortsOver(records.kind(alexaBrokersEntryKind)),
|
|
145446
|
+
...alexaIntegrationRowsPorts({
|
|
145447
|
+
api: defaultIntegrationApiOver(this.ctx.api),
|
|
145448
|
+
records,
|
|
145449
|
+
secrets,
|
|
145450
|
+
warn: (message) => this.ctx.logger.warn(message)
|
|
145451
|
+
})
|
|
145248
145452
|
};
|
|
145249
145453
|
}
|
|
145250
145454
|
/**
|
|
@@ -145507,18 +145711,10 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145507
145711
|
try {
|
|
145508
145712
|
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
145509
145713
|
const surviving = new Set(mine.map((i) => i.id));
|
|
145510
|
-
for (const
|
|
145511
|
-
const
|
|
145512
|
-
|
|
145513
|
-
|
|
145514
|
-
if (!this.activeBrokers.some((b) => b.id === brokerId)) {
|
|
145515
|
-
this.ctx.logger.warn("Alexa integration→broker: linked broker not found", { meta: {
|
|
145516
|
-
integrationId: integration.id,
|
|
145517
|
-
brokerId
|
|
145518
|
-
} });
|
|
145519
|
-
continue;
|
|
145520
|
-
}
|
|
145521
|
-
this.requireRegistry().linkIntegration(integration.id, brokerId);
|
|
145714
|
+
for (const broker of this.activeBrokers) {
|
|
145715
|
+
const integrationId = broker.integrationId;
|
|
145716
|
+
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
145717
|
+
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
145522
145718
|
}
|
|
145523
145719
|
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
145524
145720
|
if (toRemove.length === 0) return;
|
|
@@ -145560,9 +145756,9 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145560
145756
|
},
|
|
145561
145757
|
logger: this.ctx.logger,
|
|
145562
145758
|
getBrokerIdForIntegration: async (id) => {
|
|
145563
|
-
const
|
|
145564
|
-
if (
|
|
145565
|
-
return
|
|
145759
|
+
const broker = this.activeBrokers.find((entry) => entry.integrationId === id);
|
|
145760
|
+
if (broker === void 0) throw new Error(`integration ${id} has no linked brokerId`);
|
|
145761
|
+
return broker.id;
|
|
145566
145762
|
},
|
|
145567
145763
|
listAdoptedAlexa: async () => {
|
|
145568
145764
|
const reg = this.ctx.kernel.deviceRegistry;
|
package/dist/addon.mjs
CHANGED
|
@@ -6042,7 +6042,7 @@ function number(params) {
|
|
|
6042
6042
|
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
6043
6043
|
}
|
|
6044
6044
|
//#endregion
|
|
6045
|
-
//#region ../types/dist/sleep-
|
|
6045
|
+
//#region ../types/dist/sleep-9TIHFj1w.mjs
|
|
6046
6046
|
/**
|
|
6047
6047
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6048
6048
|
* window to float samples (D455).
|
|
@@ -7986,10 +7986,9 @@ var BaseAddon = class {
|
|
|
7986
7986
|
*
|
|
7987
7987
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7988
7988
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7989
|
-
* SHAPE of the form only — the values live
|
|
7990
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7989
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7991
7990
|
*/
|
|
7992
|
-
async
|
|
7991
|
+
async integrationFormSchema(_instance) {
|
|
7993
7992
|
return null;
|
|
7994
7993
|
}
|
|
7995
7994
|
/**
|
|
@@ -8035,7 +8034,7 @@ var BaseAddon = class {
|
|
|
8035
8034
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
8036
8035
|
*/
|
|
8037
8036
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
8038
|
-
const scoped = await this.
|
|
8037
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
8039
8038
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
8040
8039
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
8041
8040
|
}
|
|
@@ -8840,6 +8839,13 @@ var RecordsError = class extends Error {
|
|
|
8840
8839
|
this.detail = detail;
|
|
8841
8840
|
}
|
|
8842
8841
|
};
|
|
8842
|
+
/** A write guarded by `ifRev` found another revision (or an existing row for `ifRev: 0`). */
|
|
8843
|
+
var RevConflictError = class extends RecordsError {
|
|
8844
|
+
constructor(detail) {
|
|
8845
|
+
super("rev-conflict", detail);
|
|
8846
|
+
this.name = "RevConflictError";
|
|
8847
|
+
}
|
|
8848
|
+
};
|
|
8843
8849
|
/** The keychain could not store or serve a value: explicit, never an empty string. */
|
|
8844
8850
|
var RecordSecretUnavailableError = class extends RecordsError {
|
|
8845
8851
|
constructor(detail) {
|
|
@@ -9309,6 +9315,7 @@ function defineEntryKind(name, spec) {
|
|
|
9309
9315
|
class: "config",
|
|
9310
9316
|
writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
|
|
9311
9317
|
retention: { none: "an entry goes only when the operator deletes it" },
|
|
9318
|
+
...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
|
|
9312
9319
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9313
9320
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9314
9321
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
@@ -11978,7 +11985,7 @@ object({
|
|
|
11978
11985
|
params: record(string(), union([string(), number$1()])).optional()
|
|
11979
11986
|
});
|
|
11980
11987
|
//#endregion
|
|
11981
|
-
//#region ../types/dist/composition-
|
|
11988
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11982
11989
|
/**
|
|
11983
11990
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11984
11991
|
*
|
|
@@ -14768,6 +14775,72 @@ function flattenPortsOver(handle) {
|
|
|
14768
14775
|
};
|
|
14769
14776
|
}
|
|
14770
14777
|
/**
|
|
14778
|
+
* The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
|
|
14779
|
+
* from its own process, so the leftover rows are reached through the migration-only door
|
|
14780
|
+
* (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
|
|
14781
|
+
* The door returns markers. This process is the owner, so it resolves and releases them with
|
|
14782
|
+
* `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
|
|
14783
|
+
* door to delete the rows. The addon's own record kind is its records handle.
|
|
14784
|
+
*/
|
|
14785
|
+
async function resolvedRows(stored, secrets) {
|
|
14786
|
+
if (secrets === null) return stored;
|
|
14787
|
+
const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
|
|
14788
|
+
if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
|
|
14789
|
+
return value;
|
|
14790
|
+
}
|
|
14791
|
+
function integrationRowsPortsOverApi(deps) {
|
|
14792
|
+
const secrets = deps.secrets ?? null;
|
|
14793
|
+
return {
|
|
14794
|
+
warn: (message) => deps.log.warn(message),
|
|
14795
|
+
listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
|
|
14796
|
+
id: i.id,
|
|
14797
|
+
name: i.name
|
|
14798
|
+
})),
|
|
14799
|
+
readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
|
|
14800
|
+
putRecord: async (id, body) => {
|
|
14801
|
+
if (await deps.records.get(id) !== null) return "exists";
|
|
14802
|
+
try {
|
|
14803
|
+
await deps.records.put({
|
|
14804
|
+
id,
|
|
14805
|
+
data: body
|
|
14806
|
+
}, { ifRev: 0 });
|
|
14807
|
+
} catch (err) {
|
|
14808
|
+
if (err instanceof RevConflictError) return "exists";
|
|
14809
|
+
throw err;
|
|
14810
|
+
}
|
|
14811
|
+
return "created";
|
|
14812
|
+
},
|
|
14813
|
+
readRecord: async (id) => (await deps.records.getResolved(id))?.data ?? null,
|
|
14814
|
+
deleteRecord: async (id) => {
|
|
14815
|
+
await deps.records.delete(id);
|
|
14816
|
+
},
|
|
14817
|
+
clearRows: async (integrationId) => {
|
|
14818
|
+
const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
|
|
14819
|
+
if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
|
|
14820
|
+
await deps.api.clearLegacyIntegrationRows({ integrationId });
|
|
14821
|
+
}
|
|
14822
|
+
};
|
|
14823
|
+
}
|
|
14824
|
+
function defaultIntegrationApiOver(api) {
|
|
14825
|
+
return {
|
|
14826
|
+
list: () => api.integrations.list.query(),
|
|
14827
|
+
readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
|
|
14828
|
+
clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
|
|
14829
|
+
listDevices: (addonId) => api.deviceManager.listAll.query({
|
|
14830
|
+
addonId,
|
|
14831
|
+
projection: "slim"
|
|
14832
|
+
}),
|
|
14833
|
+
setDeviceIntegrationId: async (deviceId, integrationId) => {
|
|
14834
|
+
await api.deviceManager.setIntegrationId.mutate({
|
|
14835
|
+
deviceId,
|
|
14836
|
+
integrationId
|
|
14837
|
+
});
|
|
14838
|
+
},
|
|
14839
|
+
readDeviceHasConfig: async (deviceId) => Object.keys((await api.deviceManager.getDevice.query({ deviceId }))?.config ?? {}).length > 0,
|
|
14840
|
+
readDeviceIntegrationId: async (deviceId) => (await api.deviceManager.getDevice.query({ deviceId }))?.integrationId ?? null
|
|
14841
|
+
};
|
|
14842
|
+
}
|
|
14843
|
+
/**
|
|
14771
14844
|
* The trainer contract — what the hub hands the cloud trainer and what it
|
|
14772
14845
|
* gets back (cloud training, D805+).
|
|
14773
14846
|
*
|
|
@@ -50458,6 +50531,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
50458
50531
|
runsFound: number$1().int().nonnegative(),
|
|
50459
50532
|
pausesFound: number$1().int().nonnegative(),
|
|
50460
50533
|
longestPauseMs: number$1().nonnegative(),
|
|
50534
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
50535
|
+
pulsesFound: number$1().int().nonnegative(),
|
|
50536
|
+
longestPulseMs: number$1().nonnegative(),
|
|
50461
50537
|
idleFloorW: number$1(),
|
|
50462
50538
|
noiseCeilingW: number$1(),
|
|
50463
50539
|
plateauW: number$1(),
|
|
@@ -56303,11 +56379,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
56303
56379
|
info: record(string(), unknown()).optional(),
|
|
56304
56380
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
56305
56381
|
originStableId: string().optional(),
|
|
56306
|
-
settings: record(string(), unknown()).optional()
|
|
56307
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
56308
|
-
* the keychain, not the table (D787). */
|
|
56309
|
-
secretKeys: array(string()).optional(),
|
|
56310
|
-
skipRestart: boolean().optional()
|
|
56382
|
+
settings: record(string(), unknown()).optional()
|
|
56311
56383
|
});
|
|
56312
56384
|
var UpdateIntegrationInputSchema = object({
|
|
56313
56385
|
id: string(),
|
|
@@ -56328,16 +56400,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
56328
56400
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
56329
56401
|
kind: "mutation",
|
|
56330
56402
|
auth: "admin"
|
|
56331
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
56332
|
-
id: string(),
|
|
56333
|
-
settings: record(string(), unknown()),
|
|
56334
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
56335
|
-
secretKeys: array(string()).optional(),
|
|
56336
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
56337
|
-
skipRestart: boolean().optional()
|
|
56338
|
-
}), object({ success: boolean() }), {
|
|
56339
|
-
kind: "mutation",
|
|
56340
|
-
auth: "admin"
|
|
56341
56403
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
56342
56404
|
addonId: string(),
|
|
56343
56405
|
settings: record(string(), unknown())
|
|
@@ -59314,6 +59376,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
59314
59376
|
}), {
|
|
59315
59377
|
kind: "mutation",
|
|
59316
59378
|
auth: "admin"
|
|
59379
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
59380
|
+
rows: number$1().int().nonnegative(),
|
|
59381
|
+
secrets: number$1().int().nonnegative()
|
|
59382
|
+
}), {
|
|
59383
|
+
kind: "mutation",
|
|
59384
|
+
auth: "admin"
|
|
59385
|
+
}), method(object({
|
|
59386
|
+
integrationId: string().min(1),
|
|
59387
|
+
patch: record(string(), unknown())
|
|
59388
|
+
}), object({ written: boolean() }), {
|
|
59389
|
+
kind: "mutation",
|
|
59390
|
+
auth: "admin"
|
|
59391
|
+
}), method(object({
|
|
59392
|
+
integrationId: string().min(1),
|
|
59393
|
+
keys: array(string().min(1))
|
|
59394
|
+
}), object({
|
|
59395
|
+
present: boolean(),
|
|
59396
|
+
removed: record(string(), unknown())
|
|
59397
|
+
}), {
|
|
59398
|
+
kind: "mutation",
|
|
59399
|
+
auth: "admin"
|
|
59317
59400
|
});
|
|
59318
59401
|
method(object({
|
|
59319
59402
|
sourceUrl: string(),
|
|
@@ -80640,11 +80723,15 @@ discriminatedUnion("kind", [object({
|
|
|
80640
80723
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
80641
80724
|
* near F. A device that is not near one level most of the time has no floor.
|
|
80642
80725
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
80643
|
-
* 3.
|
|
80644
|
-
*
|
|
80726
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
80727
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
80728
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
80729
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
80730
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
80731
|
+
* is no run.
|
|
80645
80732
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
80646
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
80647
|
-
* covers the longest standby blip above T.
|
|
80733
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
80734
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
80648
80735
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
80649
80736
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
80650
80737
|
*/
|
|
@@ -80655,6 +80742,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
80655
80742
|
30 * SECOND_MS;
|
|
80656
80743
|
60 * SECOND_MS;
|
|
80657
80744
|
20 * MINUTE_MS$1;
|
|
80745
|
+
90 * SECOND_MS;
|
|
80746
|
+
30 * MINUTE_MS$1;
|
|
80747
|
+
10 * MINUTE_MS$1;
|
|
80748
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
80658
80749
|
10 * SECOND_MS;
|
|
80659
80750
|
10 * SECOND_MS;
|
|
80660
80751
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -85974,13 +86065,6 @@ Object.freeze({
|
|
|
85974
86065
|
access: "view",
|
|
85975
86066
|
role: "admin"
|
|
85976
86067
|
},
|
|
85977
|
-
"integrations.getSettings": {
|
|
85978
|
-
capName: "integrations",
|
|
85979
|
-
capScope: "system",
|
|
85980
|
-
addonId: null,
|
|
85981
|
-
access: "view",
|
|
85982
|
-
role: "admin"
|
|
85983
|
-
},
|
|
85984
86068
|
"integrations.list": {
|
|
85985
86069
|
capName: "integrations",
|
|
85986
86070
|
capScope: "system",
|
|
@@ -85988,13 +86072,6 @@ Object.freeze({
|
|
|
85988
86072
|
access: "view",
|
|
85989
86073
|
role: "admin"
|
|
85990
86074
|
},
|
|
85991
|
-
"integrations.setSettings": {
|
|
85992
|
-
capName: "integrations",
|
|
85993
|
-
capScope: "system",
|
|
85994
|
-
addonId: null,
|
|
85995
|
-
access: "create",
|
|
85996
|
-
role: "admin"
|
|
85997
|
-
},
|
|
85998
86075
|
"integrations.testConnection": {
|
|
85999
86076
|
capName: "integrations",
|
|
86000
86077
|
capScope: "system",
|
|
@@ -86989,6 +87066,13 @@ Object.freeze({
|
|
|
86989
87066
|
access: "view",
|
|
86990
87067
|
role: "admin"
|
|
86991
87068
|
},
|
|
87069
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
87070
|
+
capName: "migrations",
|
|
87071
|
+
capScope: "system",
|
|
87072
|
+
addonId: null,
|
|
87073
|
+
access: "delete",
|
|
87074
|
+
role: "admin"
|
|
87075
|
+
},
|
|
86992
87076
|
"migrations.declareOwner": {
|
|
86993
87077
|
capName: "migrations",
|
|
86994
87078
|
capScope: "system",
|
|
@@ -86996,6 +87080,13 @@ Object.freeze({
|
|
|
86996
87080
|
access: "create",
|
|
86997
87081
|
role: "admin"
|
|
86998
87082
|
},
|
|
87083
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
87084
|
+
capName: "migrations",
|
|
87085
|
+
capScope: "system",
|
|
87086
|
+
addonId: null,
|
|
87087
|
+
access: "delete",
|
|
87088
|
+
role: "admin"
|
|
87089
|
+
},
|
|
86999
87090
|
"migrations.dryRun": {
|
|
87000
87091
|
capName: "migrations",
|
|
87001
87092
|
capScope: "system",
|
|
@@ -87031,6 +87122,20 @@ Object.freeze({
|
|
|
87031
87122
|
access: "create",
|
|
87032
87123
|
role: "admin"
|
|
87033
87124
|
},
|
|
87125
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
87126
|
+
capName: "migrations",
|
|
87127
|
+
capScope: "system",
|
|
87128
|
+
addonId: null,
|
|
87129
|
+
access: "create",
|
|
87130
|
+
role: "admin"
|
|
87131
|
+
},
|
|
87132
|
+
"migrations.readLegacyIntegrationRows": {
|
|
87133
|
+
capName: "migrations",
|
|
87134
|
+
capScope: "system",
|
|
87135
|
+
addonId: null,
|
|
87136
|
+
access: "view",
|
|
87137
|
+
role: "admin"
|
|
87138
|
+
},
|
|
87034
87139
|
"migrations.restorePreMigration": {
|
|
87035
87140
|
capName: "migrations",
|
|
87036
87141
|
capScope: "system",
|
|
@@ -140562,7 +140667,7 @@ function isStringOrObject(value) {
|
|
|
140562
140667
|
//#endregion
|
|
140563
140668
|
//#region src/alexa-proxy-login-manager.ts
|
|
140564
140669
|
/** The addon id the result page names to return to. */
|
|
140565
|
-
var ADDON_ID = "import-alexa";
|
|
140670
|
+
var ADDON_ID$1 = "import-alexa";
|
|
140566
140671
|
/** An attempt lives as long as a sign-in attempt does elsewhere (10 min). */
|
|
140567
140672
|
var ATTEMPT_TTL_MS = 600 * 1e3;
|
|
140568
140673
|
/** How long the browser waits on the Amazon page for alexa-cookie2 to finish registering. */
|
|
@@ -140647,16 +140752,16 @@ var AlexaProxyLoginManager = class {
|
|
|
140647
140752
|
this.#logger.warn("alexa proxy-login: Amazon ended the login without an authorization code", { meta: { sessionId } });
|
|
140648
140753
|
return {
|
|
140649
140754
|
status: "error",
|
|
140650
|
-
addonId: ADDON_ID,
|
|
140755
|
+
addonId: ADDON_ID$1,
|
|
140651
140756
|
reason: "no_authorization_code"
|
|
140652
140757
|
};
|
|
140653
140758
|
}
|
|
140654
140759
|
return await this.#session?.whenCaptured(this.#captureWaitMs) === true ? {
|
|
140655
140760
|
status: "success",
|
|
140656
|
-
addonId: ADDON_ID
|
|
140761
|
+
addonId: ADDON_ID$1
|
|
140657
140762
|
} : {
|
|
140658
140763
|
status: "error",
|
|
140659
|
-
addonId: ADDON_ID,
|
|
140764
|
+
addonId: ADDON_ID$1,
|
|
140660
140765
|
reason: "exchange_failed"
|
|
140661
140766
|
};
|
|
140662
140767
|
}
|
|
@@ -144932,6 +145037,79 @@ var AlexaSpeakController = class {
|
|
|
144932
145037
|
}
|
|
144933
145038
|
};
|
|
144934
145039
|
//#endregion
|
|
145040
|
+
//#region src/migrations/integration-rows-to-record.ts
|
|
145041
|
+
/**
|
|
145042
|
+
* The Alexa integration's last legacy row, `brokerId` (the integration to broker link), moves onto
|
|
145043
|
+
* the broker record (planning#416): the runtime reads `broker.integrationId`, so the row is only
|
|
145044
|
+
* stamped across when the broker has no link yet, then every legacy row of the integration is
|
|
145045
|
+
* cleared (the door `integration_settings` is dropped through accepts an addon only once this
|
|
145046
|
+
* ran). Nothing is overwritten: a broker that already names an integration, or a `brokerId` that
|
|
145047
|
+
* names no broker, is left as it is and said so in a warning (ids only, never a value).
|
|
145048
|
+
* An empty door is a no-op, so a rerun is too.
|
|
145049
|
+
*/
|
|
145050
|
+
var INTEGRATION_ROWS_TO_RECORD_ID = "import-alexa/2026-11-05-integration-rows-to-record";
|
|
145051
|
+
var ADDON_ID = "import-alexa";
|
|
145052
|
+
async function moveAlexaIntegrationRows(ports) {
|
|
145053
|
+
const integrations = await ports.listIntegrations(ADDON_ID);
|
|
145054
|
+
let stamped = 0;
|
|
145055
|
+
let cleared = 0;
|
|
145056
|
+
for (const integration of integrations) {
|
|
145057
|
+
const rows = await ports.readIntegrationRows(integration.id);
|
|
145058
|
+
if (Object.keys(rows).length === 0) continue;
|
|
145059
|
+
const brokerId = rows["brokerId"];
|
|
145060
|
+
if (typeof brokerId === "string" && brokerId !== "") {
|
|
145061
|
+
const broker = await ports.readBrokerLink(brokerId);
|
|
145062
|
+
if (broker === null) ports.warn(`integration '${integration.id}': brokerId '${brokerId}' names no broker record; nothing linked`);
|
|
145063
|
+
else if (broker.integrationId !== void 0) ports.warn(`integration '${integration.id}': broker '${brokerId}' already names integration '${broker.integrationId}'; the link is not changed`);
|
|
145064
|
+
else {
|
|
145065
|
+
await ports.stampBrokerLink(brokerId, integration.id);
|
|
145066
|
+
if ((await ports.readBrokerLink(brokerId))?.integrationId !== integration.id) throw new Error(`integration '${integration.id}': the link did not read back from broker '${brokerId}' - the legacy settings are kept`);
|
|
145067
|
+
stamped += 1;
|
|
145068
|
+
}
|
|
145069
|
+
}
|
|
145070
|
+
await ports.clearIntegrationRows(integration.id);
|
|
145071
|
+
cleared += 1;
|
|
145072
|
+
}
|
|
145073
|
+
return cleared === 0 ? {
|
|
145074
|
+
outcome: "already-done",
|
|
145075
|
+
summary: { integrations: integrations.length }
|
|
145076
|
+
} : {
|
|
145077
|
+
outcome: "done",
|
|
145078
|
+
summary: {
|
|
145079
|
+
integrations: integrations.length,
|
|
145080
|
+
stamped,
|
|
145081
|
+
cleared
|
|
145082
|
+
}
|
|
145083
|
+
};
|
|
145084
|
+
}
|
|
145085
|
+
/** The hub side (the registry list, the migration-only door) and the broker records side. */
|
|
145086
|
+
function alexaIntegrationRowsPorts(deps) {
|
|
145087
|
+
const brokers = deps.records.kind(alexaBrokersEntryKind);
|
|
145088
|
+
const rows = integrationRowsPortsOverApi({
|
|
145089
|
+
api: deps.api,
|
|
145090
|
+
secrets: deps.secrets,
|
|
145091
|
+
records: deps.records.kind({
|
|
145092
|
+
name: alexaBrokersEntryKind.name,
|
|
145093
|
+
declaration: alexaBrokersEntryKind.declaration,
|
|
145094
|
+
schema: record(string(), unknown())
|
|
145095
|
+
}),
|
|
145096
|
+
log: { warn: deps.warn }
|
|
145097
|
+
});
|
|
145098
|
+
return {
|
|
145099
|
+
warn: deps.warn,
|
|
145100
|
+
listIntegrations: rows.listIntegrations,
|
|
145101
|
+
readIntegrationRows: rows.readRows,
|
|
145102
|
+
clearIntegrationRows: rows.clearRows,
|
|
145103
|
+
readBrokerLink: async (id) => {
|
|
145104
|
+
const row = await brokers.get(id);
|
|
145105
|
+
return row === null ? null : { integrationId: row.data.integrationId };
|
|
145106
|
+
},
|
|
145107
|
+
stampBrokerLink: async (id, integrationId) => {
|
|
145108
|
+
await brokers.patch(id, { integrationId });
|
|
145109
|
+
}
|
|
145110
|
+
};
|
|
145111
|
+
}
|
|
145112
|
+
//#endregion
|
|
144935
145113
|
//#region src/migrations/flatten-broker-records.ts
|
|
144936
145114
|
/**
|
|
144937
145115
|
* The Alexa broker records keep their account flat (planning#408 P3): the nested `connection`
|
|
@@ -144994,37 +145172,56 @@ async function stripEchoTargetVolumeAndMode(ports) {
|
|
|
144994
145172
|
//#endregion
|
|
144995
145173
|
//#region src/migrations/index.ts
|
|
144996
145174
|
/** The `import-alexa` catalogue (D987): offered to the engine when the addon initialises. */
|
|
144997
|
-
var ADDON_MIGRATIONS = [
|
|
144998
|
-
|
|
144999
|
-
|
|
145000
|
-
|
|
145001
|
-
|
|
145002
|
-
|
|
145003
|
-
|
|
145004
|
-
|
|
145005
|
-
|
|
145006
|
-
|
|
145007
|
-
|
|
145008
|
-
|
|
145009
|
-
|
|
145010
|
-
|
|
145011
|
-
|
|
145012
|
-
|
|
145013
|
-
|
|
145014
|
-
|
|
145015
|
-
|
|
145016
|
-
|
|
145017
|
-
|
|
145018
|
-
|
|
145019
|
-
|
|
145020
|
-
|
|
145021
|
-
|
|
145022
|
-
|
|
145023
|
-
|
|
145024
|
-
|
|
145025
|
-
|
|
145026
|
-
|
|
145027
|
-
}
|
|
145175
|
+
var ADDON_MIGRATIONS = [
|
|
145176
|
+
{
|
|
145177
|
+
meta: {
|
|
145178
|
+
id: "import-alexa/2026-11-01-strip-echo-target-volume-and-mode",
|
|
145179
|
+
owner: "import-alexa",
|
|
145180
|
+
level: "addon-state",
|
|
145181
|
+
seq: 1,
|
|
145182
|
+
dependsOn: [],
|
|
145183
|
+
store: "settings",
|
|
145184
|
+
scope: "cluster",
|
|
145185
|
+
onFailure: "tolerate",
|
|
145186
|
+
bodyKind: "procedure",
|
|
145187
|
+
description: "Remove the retired volume and mode from every stored Echo target",
|
|
145188
|
+
fixtureMark: "the Echo id and the name the migration does not know"
|
|
145189
|
+
},
|
|
145190
|
+
procedure: stripEchoTargetVolumeAndMode
|
|
145191
|
+
},
|
|
145192
|
+
{
|
|
145193
|
+
meta: {
|
|
145194
|
+
id: FLATTEN_BROKER_RECORDS_ID,
|
|
145195
|
+
owner: "import-alexa",
|
|
145196
|
+
level: "addon-state",
|
|
145197
|
+
seq: 2,
|
|
145198
|
+
dependsOn: [],
|
|
145199
|
+
store: "settings",
|
|
145200
|
+
scope: "cluster",
|
|
145201
|
+
onFailure: "block-owner",
|
|
145202
|
+
bodyKind: "procedure",
|
|
145203
|
+
description: "Move each Alexa account from a nested connection object to flat top-level fields under the same id, reading every secret back first",
|
|
145204
|
+
fixtureMark: "the integration link and the amazon domain of each account"
|
|
145205
|
+
},
|
|
145206
|
+
procedure: flattenBrokerRecords
|
|
145207
|
+
},
|
|
145208
|
+
{
|
|
145209
|
+
meta: {
|
|
145210
|
+
id: INTEGRATION_ROWS_TO_RECORD_ID,
|
|
145211
|
+
owner: "import-alexa",
|
|
145212
|
+
level: "addon-state",
|
|
145213
|
+
seq: 3,
|
|
145214
|
+
dependsOn: [FLATTEN_BROKER_RECORDS_ID],
|
|
145215
|
+
store: "settings",
|
|
145216
|
+
scope: "cluster",
|
|
145217
|
+
onFailure: "block-owner",
|
|
145218
|
+
bodyKind: "procedure",
|
|
145219
|
+
description: "Stamp the integration link from the legacy brokerId row onto its broker record when the broker has none, then clear the legacy integration settings rows",
|
|
145220
|
+
fixtureMark: "the name of the broker and the integration it already names"
|
|
145221
|
+
},
|
|
145222
|
+
procedure: moveAlexaIntegrationRows
|
|
145223
|
+
}
|
|
145224
|
+
];
|
|
145028
145225
|
//#endregion
|
|
145029
145226
|
//#region src/speak/icon-routes.ts
|
|
145030
145227
|
var ALEXA_ICON_SVG = "<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"#00CAFF\"><title>Amazon Alexa</title><path d=\"M12 0C5.37 0 0 5.37 0 12C0 18.09 4.53 23.11 10.4 23.9V21.5A1.59 1.59 0 0 0 9.32 19.97A8.41 8.41 0 0 1 3.6 11.8A8.37 8.37 0 0 1 12.09 3.6A8.4 8.4 0 0 1 20.4 12.31L20.39 12.38A8.68 8.68 0 0 1 20.36 12.76C20.36 12.83 20.35 12.9 20.34 12.96C20.34 13.04 20.33 13.12 20.32 13.19L20.3 13.29C19.27 20.07 10.45 23.87 10.4 23.9C10.92 23.97 11.46 24 12 24C18.63 24 24 18.63 24 12S18.63 0 12 0Z\"/></svg>";
|
|
@@ -145236,13 +145433,20 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145236
145433
|
] };
|
|
145237
145434
|
}
|
|
145238
145435
|
migrationPorts(records) {
|
|
145239
|
-
|
|
145436
|
+
const secrets = this.ctx.secrets;
|
|
145437
|
+
if (secrets === void 0) throw new Error("import-alexa: ctx.secrets is required");
|
|
145240
145438
|
return {
|
|
145241
145439
|
readRows: () => Array.isArray(this.config.echoTargets) ? this.config.echoTargets : [],
|
|
145242
145440
|
writeRows: async (rows) => {
|
|
145243
145441
|
await this.updateGlobalSettings({ echoTargets: [...readEchoTargets({ echoTargets: rows })] });
|
|
145244
145442
|
},
|
|
145245
|
-
...flattenPortsOver(records.kind(alexaBrokersEntryKind))
|
|
145443
|
+
...flattenPortsOver(records.kind(alexaBrokersEntryKind)),
|
|
145444
|
+
...alexaIntegrationRowsPorts({
|
|
145445
|
+
api: defaultIntegrationApiOver(this.ctx.api),
|
|
145446
|
+
records,
|
|
145447
|
+
secrets,
|
|
145448
|
+
warn: (message) => this.ctx.logger.warn(message)
|
|
145449
|
+
})
|
|
145246
145450
|
};
|
|
145247
145451
|
}
|
|
145248
145452
|
/**
|
|
@@ -145505,18 +145709,10 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145505
145709
|
try {
|
|
145506
145710
|
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
145507
145711
|
const surviving = new Set(mine.map((i) => i.id));
|
|
145508
|
-
for (const
|
|
145509
|
-
const
|
|
145510
|
-
|
|
145511
|
-
|
|
145512
|
-
if (!this.activeBrokers.some((b) => b.id === brokerId)) {
|
|
145513
|
-
this.ctx.logger.warn("Alexa integration→broker: linked broker not found", { meta: {
|
|
145514
|
-
integrationId: integration.id,
|
|
145515
|
-
brokerId
|
|
145516
|
-
} });
|
|
145517
|
-
continue;
|
|
145518
|
-
}
|
|
145519
|
-
this.requireRegistry().linkIntegration(integration.id, brokerId);
|
|
145712
|
+
for (const broker of this.activeBrokers) {
|
|
145713
|
+
const integrationId = broker.integrationId;
|
|
145714
|
+
if (integrationId === void 0 || !surviving.has(integrationId)) continue;
|
|
145715
|
+
this.requireRegistry().linkIntegration(integrationId, broker.id);
|
|
145520
145716
|
}
|
|
145521
145717
|
const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
145522
145718
|
if (toRemove.length === 0) return;
|
|
@@ -145558,9 +145754,9 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
|
|
|
145558
145754
|
},
|
|
145559
145755
|
logger: this.ctx.logger,
|
|
145560
145756
|
getBrokerIdForIntegration: async (id) => {
|
|
145561
|
-
const
|
|
145562
|
-
if (
|
|
145563
|
-
return
|
|
145757
|
+
const broker = this.activeBrokers.find((entry) => entry.integrationId === id);
|
|
145758
|
+
if (broker === void 0) throw new Error(`integration ${id} has no linked brokerId`);
|
|
145759
|
+
return broker.id;
|
|
145564
145760
|
},
|
|
145565
145761
|
listAdoptedAlexa: async () => {
|
|
145566
145762
|
const reg = this.ctx.kernel.deviceRegistry;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-import-alexa",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.187",
|
|
4
4
|
"description": "Alexa device-import provider for CamStack — imports the smart-home devices in a user's Alexa account via the unofficial alexa-remote2 cookie/token client (the inverse of the Alexa exporter)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
}
|
|
73
73
|
],
|
|
74
74
|
"requires": {
|
|
75
|
-
"server": ">=1.2.
|
|
75
|
+
"server": ">=1.2.770"
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
78
|
"files": [
|