@camstack/addon-export-alexa 1.2.147 → 1.2.148

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.
@@ -5,7 +5,7 @@ import "node:fs";
5
5
  import "node:path";
6
6
  import "node:child_process";
7
7
  import "node:zlib";
8
- //#region ../types/dist/event-category-BvOFi6PY.mjs
8
+ //#region ../types/dist/event-category-DOl51-Ij.mjs
9
9
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
10
10
  EventCategory["SystemBoot"] = "system.boot";
11
11
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -84,9 +84,18 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
84
84
  /**
85
85
  * The node's one-shot runner boot check (D877): planned runners against the
86
86
  * process service, `degraded` when any is failed, refused or missing. The
87
- * alert centre turns it into one alert and a direct critical push.
87
+ * alert centre turns it into one critical alert.
88
88
  */
89
89
  EventCategory["SystemRunnerCheck"] = "system.runner-check";
90
+ /**
91
+ * An alert EPISODE opened or closed, emitted by the alert centre for alerts
92
+ * of severity `critical` or `error` (`AlertEpisodePayload`). The alert
93
+ * centre sends no notification of its own: Notification Center turns this
94
+ * into the `alert-critical` / `alert-error` / `alert-critical-resolved`
95
+ * system-event kinds, and reconciles from the alerts store because a bus
96
+ * event may be dropped (D8, D11).
97
+ */
98
+ EventCategory["AlertEpisode"] = "alert.episode";
90
99
  EventCategory["AddonError"] = "addon.error";
91
100
  EventCategory["AddonPageReady"] = "addon.page-ready";
92
101
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -5411,7 +5420,7 @@ var ZodIssueCode = {
5411
5420
  var ZodFirstPartyTypeKind;
5412
5421
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5413
5422
  //#endregion
5414
- //#region ../types/dist/sleep-BegKjwUu.mjs
5423
+ //#region ../types/dist/sleep-BxjU_fJM.mjs
5415
5424
  /**
5416
5425
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5417
5426
  * window to float samples (D455).
@@ -13408,7 +13417,14 @@ var AlertSeveritySchema = _enum([
13408
13417
  ]);
13409
13418
  var AlertActionSchema = object({
13410
13419
  label: string(),
13411
- href: string().optional()
13420
+ href: string().optional(),
13421
+ /**
13422
+ * How `href` is followed. Absent / `app`: an admin-ui route (client-side).
13423
+ * `server`: a route the HUB serves (an addon HTTP route such as an OAuth
13424
+ * start) — the browser must leave the SPA with a full navigation, because
13425
+ * the SPA has no such route and would bounce to the home page.
13426
+ */
13427
+ navigation: _enum(["app", "server"]).optional()
13412
13428
  });
13413
13429
  var AlertStatusSchema = _enum([
13414
13430
  "active",
@@ -41809,6 +41825,9 @@ var NcSystemEventKindSchema = _enum([
41809
41825
  "server-updated",
41810
41826
  "export-completed",
41811
41827
  "backup-completed",
41828
+ "alert-critical",
41829
+ "alert-error",
41830
+ "alert-critical-resolved",
41812
41831
  "camera-online",
41813
41832
  "camera-offline",
41814
41833
  "camera-disabled",
@@ -50857,7 +50876,10 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
50857
50876
  }), method(object({ jobId: string().optional() }), StorageMigrationJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
50858
50877
  kind: "mutation",
50859
50878
  auth: "admin"
50860
- }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
50879
+ }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({ locationIds: array(string()).readonly() }), object({
50880
+ clear: boolean(),
50881
+ repointLocationId: string().optional()
50882
+ }), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
50861
50883
  kind: "mutation",
50862
50884
  auth: "admin"
50863
50885
  }), method(StorageCleanupInputSchema, object({ jobId: string() }), {
@@ -54316,6 +54338,11 @@ object({
54316
54338
  *
54317
54339
  * `kind: 'poll'` — cloud REST APIs typically have no push channel; the
54318
54340
  * provider refreshes the slice on an interval and eagerly after commands.
54341
+ *
54342
+ * UNKNOWN IS NOT OFF. Every field the appliance reports separately is
54343
+ * nullable: `null` means "not known right now" (the appliance has not said,
54344
+ * the link is down, or the description does not carry it) and is never
54345
+ * rendered or exported as `false` / `0`. `availability` says why.
54319
54346
  */
54320
54347
  var DishwasherStateSchema = _enum([
54321
54348
  "idle",
@@ -54325,112 +54352,227 @@ var DishwasherStateSchema = _enum([
54325
54352
  "error",
54326
54353
  "unavailable"
54327
54354
  ]);
54355
+ /**
54356
+ * The appliance's own operation state, uncollapsed. `state` above is the coarse
54357
+ * view for compact surfaces; this is what the appliance said.
54358
+ */
54359
+ var DishwasherOperationStateSchema = _enum([
54360
+ "Inactive",
54361
+ "Ready",
54362
+ "DelayedStart",
54363
+ "Run",
54364
+ "Pause",
54365
+ "ActionRequired",
54366
+ "Finished",
54367
+ "Error",
54368
+ "Aborting"
54369
+ ]);
54370
+ /**
54371
+ * Why fields may be `null`:
54372
+ * - `available` — the link is up and the description is loaded;
54373
+ * - `offline` — the local link is down (values are unknown, not last-known);
54374
+ * - `description-missing` — the link is up but the appliance's description could not
54375
+ * be loaded, so its values cannot be decoded.
54376
+ */
54377
+ var DishwasherAvailabilitySchema = _enum([
54378
+ "available",
54379
+ "offline",
54380
+ "description-missing"
54381
+ ]);
54328
54382
  var DishwasherConsumableLevelSchema = _enum([
54329
54383
  "ok",
54330
54384
  "low",
54331
54385
  "empty"
54332
54386
  ]);
54333
54387
  object({
54388
+ availability: DishwasherAvailabilitySchema,
54389
+ /** Coarse state for compact surfaces; `unavailable` whenever the appliance state is not known. */
54334
54390
  state: DishwasherStateSchema,
54335
- /** Active program id/name, or null when none selected / unknown. */
54391
+ /** The appliance's operation state as reported; null when not known. */
54392
+ operationState: DishwasherOperationStateSchema.nullable(),
54393
+ /** Feature key of the ACTIVE (running) program, or null when none runs / not known. */
54336
54394
  program: string().nullable(),
54337
- /** Program tokens the hardware accepts — drives the UI selector. */
54395
+ /** Feature key of the SELECTED program (what Start would run), or null when none / not known. */
54396
+ selectedProgram: string().nullable(),
54397
+ /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
54338
54398
  availablePrograms: array(string()),
54339
- /** Minutes remaining on the active cycle, or null when not running. */
54340
- remainingMinutes: number().int().nonnegative().nullable(),
54399
+ /** Seconds remaining on the active cycle, or null when not running / not known. */
54400
+ remainingSeconds: number().int().nonnegative().nullable(),
54401
+ /** Seconds the delayed start is set to (time until the cycle starts), or null. */
54402
+ delayedStartSeconds: number().int().nonnegative().nullable(),
54341
54403
  /** Cycle completion 0..100, or null when not applicable. */
54342
54404
  progressPercent: number().min(0).max(100).nullable(),
54343
- /** True when the door is reported open. */
54344
- doorOpen: boolean(),
54345
- /** True when remote start is allowed (door closed, power on, etc.). */
54346
- remoteStartAllowed: boolean(),
54347
- /** Main power / switched-on state. */
54348
- powerOn: boolean(),
54405
+ /** True when the door is reported open; null when the door state is not known. */
54406
+ doorOpen: boolean().nullable(),
54407
+ /** True when the door is latched (locked) during a cycle; null when not reported. */
54408
+ doorLocked: boolean().nullable(),
54409
+ /** True when remote start is allowed (door closed, power on, etc.); null when not known. */
54410
+ remoteStartAllowed: boolean().nullable(),
54411
+ /** True when the appliance accepts remote control now; null when not known. */
54412
+ remoteControlActive: boolean().nullable(),
54413
+ /** True while somebody operates the appliance at its panel; null when not reported. */
54414
+ localControlActive: boolean().nullable(),
54415
+ /** Main power / switched-on state; null when not known. */
54416
+ powerOn: boolean().nullable(),
54417
+ /** The raw power state (`On`, `Off`, `Standby`, …); null when not known. */
54418
+ powerState: string().nullable(),
54349
54419
  saltLevel: DishwasherConsumableLevelSchema.nullable(),
54350
54420
  rinseAidLevel: DishwasherConsumableLevelSchema.nullable(),
54351
- halfLoad: boolean(),
54352
- hygienePlus: boolean(),
54421
+ /** Option states: null when the appliance does not expose the option or has not said. */
54422
+ halfLoad: boolean().nullable(),
54423
+ hygienePlus: boolean().nullable(),
54353
54424
  /** Extra dry option when the appliance exposes it. */
54354
- extraDry: boolean(),
54425
+ extraDry: boolean().nullable(),
54355
54426
  /** Intensive zone (lower basket) when the appliance exposes it. */
54356
- intensiveZone: boolean(),
54427
+ intensiveZone: boolean().nullable(),
54357
54428
  /** VarioSpeed / VarioSpeed+ when the appliance exposes it. */
54358
- varioSpeedPlus: boolean(),
54359
- /** Child lock (controls disabled on the appliance). */
54360
- childLock: boolean(),
54429
+ varioSpeedPlus: boolean().nullable(),
54430
+ /** Child lock (controls disabled on the appliance); null when not known. */
54431
+ childLock: boolean().nullable(),
54432
+ /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
54433
+ activeEvents: array(string()),
54434
+ /** Numeric error code when the appliance reports one; null otherwise. */
54361
54435
  errorCode: number().nullable(),
54436
+ /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
54362
54437
  errorLabel: string().nullable(),
54438
+ /** Ms epoch of the last value received from the appliance; 0 = never. */
54363
54439
  lastFetchedAt: number()
54364
54440
  });
54365
- DeviceType.Dishwasher, method(object({ deviceId: number().int().nonnegative() }), _void(), {
54441
+ var DeviceIdSchema = number().int().nonnegative();
54442
+ /** A value a program option or setting can take. */
54443
+ var DishwasherValueSchema = union([
54444
+ boolean(),
54445
+ string(),
54446
+ number()
54447
+ ]);
54448
+ var DishwasherAccessSchema = _enum([
54449
+ "none",
54450
+ "read",
54451
+ "readWrite",
54452
+ "writeOnly",
54453
+ "readStatic"
54454
+ ]);
54455
+ /**
54456
+ * One option or setting the appliance declares, with the constraints its description
54457
+ * gives. A `readWrite` descriptor that is `available` is what `setOption` accepts.
54458
+ */
54459
+ var DishwasherOptionDescriptorSchema = object({
54460
+ /** Feature key (`Dishcare.Dishwasher.Option.ExtraDry`). */
54461
+ key: string(),
54462
+ /** Last segment of the key, for display. */
54463
+ name: string(),
54464
+ type: _enum([
54465
+ "boolean",
54466
+ "number",
54467
+ "enum",
54468
+ "string"
54469
+ ]),
54470
+ access: DishwasherAccessSchema,
54471
+ /** The appliance reports this entity as currently available. */
54472
+ available: boolean(),
54473
+ /** Unit the description declares for a number; null when it declares none. */
54474
+ unit: string().nullable(),
54475
+ min: number().nullable(),
54476
+ max: number().nullable(),
54477
+ step: number().nullable(),
54478
+ /** Allowed members of an enum; null for other types. */
54479
+ enumValues: array(string()).nullable(),
54480
+ defaultValue: DishwasherValueSchema.nullable()
54481
+ });
54482
+ var DishwasherProgramSchema = object({
54483
+ /** Feature key (`Dishcare.Dishwasher.Program.Eco50`) — what `setProgram` / `start` take. */
54484
+ key: string(),
54485
+ name: string(),
54486
+ /** The appliance currently offers this program. */
54487
+ available: boolean(),
54488
+ /** The options this program lists, with the program-level access / availability / default. */
54489
+ options: array(DishwasherOptionDescriptorSchema)
54490
+ });
54491
+ var DishwasherOptionsSchema = object({
54492
+ availability: DishwasherAvailabilitySchema,
54493
+ /** What the appliance can be told to do, from the commands its description declares. */
54494
+ supportsStart: boolean(),
54495
+ supportsPause: boolean(),
54496
+ supportsResume: boolean(),
54497
+ supportsStop: boolean(),
54498
+ supportsPower: boolean(),
54499
+ /** Every program the description lists, with its options. */
54500
+ programs: array(DishwasherProgramSchema),
54501
+ /** Every option and setting entity the description lists (programs reference these by key). */
54502
+ options: array(DishwasherOptionDescriptorSchema)
54503
+ });
54504
+ DeviceType.Dishwasher, method(object({
54505
+ deviceId: DeviceIdSchema,
54506
+ program: string().min(1).optional(),
54507
+ options: record(string(), DishwasherValueSchema).optional()
54508
+ }), _void(), {
54366
54509
  kind: "mutation",
54367
54510
  auth: "protected"
54368
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
54511
+ }), method(object({ deviceId: DeviceIdSchema }), _void(), {
54369
54512
  kind: "mutation",
54370
54513
  auth: "protected"
54371
- }), method(object({ deviceId: number().int().nonnegative() }), _void(), {
54514
+ }), method(object({ deviceId: DeviceIdSchema }), _void(), {
54515
+ kind: "mutation",
54516
+ auth: "protected"
54517
+ }), method(object({ deviceId: DeviceIdSchema }), _void(), {
54372
54518
  kind: "mutation",
54373
54519
  auth: "protected"
54374
54520
  }), method(object({
54375
- deviceId: number().int().nonnegative(),
54521
+ deviceId: DeviceIdSchema,
54376
54522
  program: string().min(1)
54377
54523
  }), _void(), {
54378
54524
  kind: "mutation",
54379
54525
  auth: "protected"
54380
54526
  }), method(object({
54381
- deviceId: number().int().nonnegative(),
54527
+ deviceId: DeviceIdSchema,
54382
54528
  on: boolean()
54383
54529
  }), _void(), {
54384
54530
  kind: "mutation",
54385
54531
  auth: "protected"
54386
54532
  }), method(object({
54387
- deviceId: number().int().nonnegative(),
54533
+ deviceId: DeviceIdSchema,
54388
54534
  on: boolean()
54389
54535
  }), _void(), {
54390
54536
  kind: "mutation",
54391
54537
  auth: "protected"
54392
54538
  }), method(object({
54393
- deviceId: number().int().nonnegative(),
54539
+ deviceId: DeviceIdSchema,
54394
54540
  on: boolean()
54395
54541
  }), _void(), {
54396
54542
  kind: "mutation",
54397
54543
  auth: "protected"
54398
54544
  }), method(object({
54399
- deviceId: number().int().nonnegative(),
54545
+ deviceId: DeviceIdSchema,
54400
54546
  on: boolean()
54401
54547
  }), _void(), {
54402
54548
  kind: "mutation",
54403
54549
  auth: "protected"
54404
54550
  }), method(object({
54405
- deviceId: number().int().nonnegative(),
54551
+ deviceId: DeviceIdSchema,
54406
54552
  on: boolean()
54407
54553
  }), _void(), {
54408
54554
  kind: "mutation",
54409
54555
  auth: "protected"
54410
54556
  }), method(object({
54411
- deviceId: number().int().nonnegative(),
54557
+ deviceId: DeviceIdSchema,
54412
54558
  on: boolean()
54413
54559
  }), _void(), {
54414
54560
  kind: "mutation",
54415
54561
  auth: "protected"
54416
54562
  }), method(object({
54417
- deviceId: number().int().nonnegative(),
54563
+ deviceId: DeviceIdSchema,
54418
54564
  on: boolean()
54419
54565
  }), _void(), {
54420
54566
  kind: "mutation",
54421
54567
  auth: "protected"
54422
54568
  }), method(object({
54423
- deviceId: number().int().nonnegative(),
54569
+ deviceId: DeviceIdSchema,
54424
54570
  key: string().min(1),
54425
- value: union([
54426
- boolean(),
54427
- string(),
54428
- number()
54429
- ])
54571
+ value: DishwasherValueSchema
54430
54572
  }), _void(), {
54431
54573
  kind: "mutation",
54432
54574
  auth: "protected"
54433
- });
54575
+ }), method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" });
54434
54576
  object({
54435
54577
  /** Ms epoch of the last press. null = never observed since this provider started. */
54436
54578
  lastPressedAt: number().nullable(),
@@ -64521,6 +64663,12 @@ Object.freeze({
64521
64663
  addonId: null,
64522
64664
  access: "view"
64523
64665
  },
64666
+ "dishwasher.getOptions": {
64667
+ capName: "dishwasher",
64668
+ capScope: "device",
64669
+ addonId: null,
64670
+ access: "view"
64671
+ },
64524
64672
  "dishwasher.getStatus": {
64525
64673
  capName: "dishwasher",
64526
64674
  capScope: "device",
@@ -64533,6 +64681,12 @@ Object.freeze({
64533
64681
  addonId: null,
64534
64682
  access: "create"
64535
64683
  },
64684
+ "dishwasher.resume": {
64685
+ capName: "dishwasher",
64686
+ capScope: "device",
64687
+ addonId: null,
64688
+ access: "create"
64689
+ },
64536
64690
  "dishwasher.setChildLock": {
64537
64691
  capName: "dishwasher",
64538
64692
  capScope: "device",
@@ -69033,6 +69187,12 @@ Object.freeze({
69033
69187
  addonId: null,
69034
69188
  access: "view"
69035
69189
  },
69190
+ "storageMigration.moverClearance": {
69191
+ capName: "storage-migration",
69192
+ capScope: "system",
69193
+ addonId: null,
69194
+ access: "view"
69195
+ },
69036
69196
  "storageMigration.movers": {
69037
69197
  capName: "storage-migration",
69038
69198
  capScope: "system",
@@ -71226,6 +71386,11 @@ Object.freeze({
71226
71386
  form: "single",
71227
71387
  optional: false
71228
71388
  }],
71389
+ "dishwasher.getOptions": [{
71390
+ name: "deviceId",
71391
+ form: "single",
71392
+ optional: false
71393
+ }],
71229
71394
  "dishwasher.getStatus": [{
71230
71395
  name: "deviceId",
71231
71396
  form: "single",
@@ -71236,6 +71401,11 @@ Object.freeze({
71236
71401
  form: "single",
71237
71402
  optional: false
71238
71403
  }],
71404
+ "dishwasher.resume": [{
71405
+ name: "deviceId",
71406
+ form: "single",
71407
+ optional: false
71408
+ }],
71239
71409
  "dishwasher.setChildLock": [{
71240
71410
  name: "deviceId",
71241
71411
  form: "single",
@@ -73322,6 +73492,9 @@ new Map(Object.entries({
73322
73492
  "wrapper-update-available": "system",
73323
73493
  "backup-completed": "system",
73324
73494
  "export-completed": "system",
73495
+ "alert-critical": "system",
73496
+ "alert-error": "system",
73497
+ "alert-critical-resolved": "system",
73325
73498
  "storage-location-low": "system",
73326
73499
  "storage-location-critical": "system",
73327
73500
  "storage-location-ok": "system",
@@ -73446,6 +73619,16 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
73446
73619
  "storage-location-ok"
73447
73620
  ]
73448
73621
  },
73622
+ {
73623
+ id: "alerts",
73624
+ family: "system",
73625
+ label: "Alerts",
73626
+ kinds: [
73627
+ "alert-critical",
73628
+ "alert-error",
73629
+ "alert-critical-resolved"
73630
+ ]
73631
+ },
73449
73632
  {
73450
73633
  id: "alarm",
73451
73634
  family: "alarm",
@@ -73490,6 +73673,21 @@ var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.ent
73490
73673
  return [[descriptor.kind, field]];
73491
73674
  })));
73492
73675
  Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
73676
+ object({
73677
+ phase: _enum(["raised", "resolved"]),
73678
+ episodeId: string().min(1),
73679
+ alertId: string().min(1),
73680
+ severity: AlertSeveritySchema,
73681
+ category: string(),
73682
+ title: string(),
73683
+ message: string(),
73684
+ sourceType: string().optional(),
73685
+ sourceId: string().optional(),
73686
+ nodeId: string().optional(),
73687
+ /** The alert's own action link (where the operator acts on it), when it has one. */
73688
+ actionUrl: string().optional(),
73689
+ at: number()
73690
+ });
73493
73691
  var RULE_BODY_ONLY = ["body"];
73494
73692
  var RULE_TEXT = ["title", "body"];
73495
73693
  var BATTERY = ["device-battery-low", "device-battery-normal"];
@@ -73499,6 +73697,11 @@ var TANK = [
73499
73697
  "device-tank-full",
73500
73698
  "device-tank-normal"
73501
73699
  ];
73700
+ var ALERT_EPISODE = [
73701
+ "alert-critical",
73702
+ "alert-error",
73703
+ "alert-critical-resolved"
73704
+ ];
73502
73705
  var UPDATES = [
73503
73706
  "addon-update-available",
73504
73707
  "server-update-available",
@@ -73691,14 +73894,14 @@ var STATE_SUMMARY_VARS = [
73691
73894
  ([...RULE_TEXT], [...CONSUMABLE]),
73692
73895
  ([...RULE_TEXT], [...TANK]),
73693
73896
  [...RULE_TEXT],
73694
- ([...RULE_TEXT], [...UPDATES]),
73695
- ([...RULE_TEXT], [...UPDATES]),
73696
- [...RULE_TEXT],
73897
+ ([...RULE_TEXT], [...UPDATES, ...ALERT_EPISODE]),
73898
+ ([...RULE_TEXT], [...UPDATES, ...ALERT_EPISODE]),
73697
73899
  [...RULE_TEXT],
73698
73900
  [...RULE_TEXT],
73699
73901
  [...RULE_TEXT],
73700
73902
  [...RULE_TEXT],
73701
73903
  [...RULE_TEXT],
73904
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
73702
73905
  [...RULE_TEXT],
73703
73906
  [...RULE_TEXT],
73704
73907
  [...RULE_TEXT],
@@ -73716,7 +73919,11 @@ var STATE_SUMMARY_VARS = [
73716
73919
  [...RULE_TEXT],
73717
73920
  [...RULE_TEXT],
73718
73921
  [...RULE_TEXT],
73719
- [...RULE_TEXT],
73922
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
73923
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
73924
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
73925
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
73926
+ ([...RULE_TEXT], [...ALERT_EPISODE]),
73720
73927
  [...RULE_TEXT],
73721
73928
  [...RULE_TEXT],
73722
73929
  [...RULE_TEXT],
@@ -83605,13 +83812,20 @@ function buildAlexaOauthIntegration(publicHubUrl) {
83605
83812
  function buildAlexaSetupBlock(input) {
83606
83813
  const base = input.publicHubUrl.replace(/\/+$/, "");
83607
83814
  return {
83608
- fields: [...base ? [{
83609
- label: "Directive endpoint",
83610
- value: `${base}/addon/export-alexa/directive`
83611
- }] : [], ...input.ingressToken && input.skillMode === "custom" ? [{
83612
- label: "Ingress token (Lambda HUB_INGRESS_TOKEN) — use \"Show ingress token\" in settings",
83613
- value: maskSecretTail(input.ingressToken)
83614
- }] : []],
83815
+ fields: [
83816
+ ...base ? [{
83817
+ label: "Hub address to enter in the Alexa app",
83818
+ value: base
83819
+ }] : [],
83820
+ ...base ? [{
83821
+ label: "Directive endpoint",
83822
+ value: `${base}/addon/export-alexa/directive`
83823
+ }] : [],
83824
+ ...input.ingressToken && input.skillMode === "custom" ? [{
83825
+ label: "Ingress token (Lambda HUB_INGRESS_TOKEN) — use \"Show ingress token\" in settings",
83826
+ value: maskSecretTail(input.ingressToken)
83827
+ }] : []
83828
+ ],
83615
83829
  note: base ? "Account linking is handled by the core OAuth server. Use the hub's /api/oauth2/authorize endpoint with integration=export-alexa." : "Set the public hub URL on the settings form first — the directive endpoint URL is derived from it."
83616
83830
  };
83617
83831
  }
@@ -13,7 +13,7 @@ The same code runs the built-in CamStack skill and any operator's own
13
13
 
14
14
  | Invocation | Action |
15
15
  | --- | --- |
16
- | `GET <FUNCTION_URL>/authorize` | HTML form asking for the hub URL, then redirects to the hub's OAuth `/authorize`. |
16
+ | `GET <FUNCTION_URL>/authorize` | The hub-address step of account linking (below): a form in the CamStack page style (en/it), then — after checking the hub — a redirect to the hub's OAuth `/authorize`. |
17
17
  | `POST <FUNCTION_URL>/token` | Checks the account-linking client id/secret, decodes the `hubUrl` claim, proxies the token request to the hub. |
18
18
  | Smart Home directive | Forwards to `${hubUrl}/addon/export-alexa/directive`, adding `X-CamStack-Broker: <broker origin>` when the broker is on. |
19
19
  | `Alexa.Authorization/AcceptGrant` | Exchanges the grant code at `api.amazon.com` **here**, stores the grant in DynamoDB, gives the hub a per-link broker credential (`POST /addon/export-alexa/oauth/broker-link`). |
@@ -30,6 +30,32 @@ reached (network error, or a tunnel's 502–504 / 52x page) is
30
30
  `ACCEPT_GRANT_FAILED`. The `/authorize` page accepts only an `https://` hub URL
31
31
  and shows its error on the page.
32
32
 
33
+ ### The hub-address step (`/authorize`, `authorize-page.js`)
34
+
35
+ Alexa opens this URL in the user's browser; the Lambda cannot know the hub, so
36
+ the operator types its public HTTPS address (CamStack shows it under
37
+ Connectivity → Alexa → "Hub address to enter in the Alexa app"). The page is the
38
+ CamStack page family — `page-style.js` is generated from the hub's page shell
39
+ (`npx tsx scripts/gen-alexa-lambda-page-style.ts`; a spec fails when it drifts) —
40
+ and runs no script. The address is **checked before the browser leaves**:
41
+
42
+ | Reason code shown on the page | Meaning |
43
+ | --- | --- |
44
+ | `invalid_url` / `not_https` | not a web address / not `https://` |
45
+ | `local_address` | `localhost`, a private or tailnet IP, `.local` … — the Lambda runs in the cloud |
46
+ | `unreachable` | no answer to `GET /api/oauth2/integrations` (6 s) |
47
+ | `tls_error` | answered, but the certificate is not trusted by the Lambda's CA store |
48
+ | `not_camstack` | answered, but not with a CamStack discovery document |
49
+ | `integration_missing` / `hub_starting` | the hub does not serve `export-alexa` / is still registering it |
50
+
51
+ A good hub gets a 302 to `<hub>/api/oauth2/authorize?…&integration=export-alexa`.
52
+ From there the hub answers: consent on its own page; a refused request lands on
53
+ the hub's `/sso-complete` page with a reason code. **Changing the destination
54
+ hub** is the same step again: unlink on the old hub (Alexa export → Account link
55
+ → Unlink now), disable/enable the skill in the Alexa app, and enter the new
56
+ address when asked. Tokens carry their `hubUrl` claim, so an existing link keeps
57
+ its old hub until it is re-linked.
58
+
33
59
  ### The two credential pairs
34
60
 
35
61
  | Pair | SSM parameter (SecureString) | Authenticates | Who else holds it |
@@ -63,7 +89,7 @@ re-link, and lapses after 180 days without a refresh (DynamoDB TTL).
63
89
  runtime. Build:
64
90
 
65
91
  ```bash
66
- cd lambda-template && rm -f ../camstack-alexa-lambda.zip && zip -j ../camstack-alexa-lambda.zip *.js
92
+ cd lambda-template && rm -f ../camstack-alexa-lambda.zip && zip -j ../camstack-alexa-lambda.zip *.js # includes authorize-page.js and the generated page-style.js
67
93
  ```
68
94
 
69
95
  ## Deployment