@camstack/addon-import-alexa 0.2.190 → 0.2.191

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 CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.191] - 2026-11-06
11
+
12
+ ### Changed
13
+
14
+ - Echo notification targets pick their Amazon account through the shared instance picker
15
+
10
16
  ## [0.2.190] - 2026-11-06
11
17
 
12
18
  ### Changed
package/dist/addon.js CHANGED
@@ -13577,6 +13577,39 @@ function buildCapturingReply(envelope) {
13577
13577
  return wrapper;
13578
13578
  }
13579
13579
  /**
13580
+ * The link primitive's declaration half (D1013 rule 5): what a link field names and how a kind
13581
+ * records it. Split from `instance-link.ts` so `RecordIntegrationInstances` can read the links of
13582
+ * its kind without importing the builders that import it.
13583
+ */
13584
+ /** The body field of a link: the id of the instance it names; the target rides in the field meta. */
13585
+ function linkField(target) {
13586
+ return string().meta({ link: target });
13587
+ }
13588
+ function linkTargetOf(meta) {
13589
+ if (typeof meta !== "object" || meta === null) return void 0;
13590
+ const link = Reflect.get(meta, "link");
13591
+ const parsed = LinkTargetSchema.safeParse(link);
13592
+ return parsed.success ? parsed.data : void 0;
13593
+ }
13594
+ var LinkTargetSchema = object({
13595
+ cap: string().min(1),
13596
+ kind: string().min(1).optional()
13597
+ });
13598
+ /** The links a kind declares, read from the meta of its body fields. */
13599
+ function linksOfKind(kind) {
13600
+ const schema = kind.schema;
13601
+ if (!(schema instanceof ZodObject)) return [];
13602
+ const links = [];
13603
+ for (const [field, fieldSchema] of Object.entries(schema.shape)) {
13604
+ const to = linkTargetOf(globalRegistry.get(fieldSchema));
13605
+ if (to !== void 0) links.push({
13606
+ field,
13607
+ to
13608
+ });
13609
+ }
13610
+ return links;
13611
+ }
13612
+ /**
13580
13613
  * `RecordIntegrationInstances` - the `integration-instances` provider over ONE
13581
13614
  * entry kind, so an addon that holds a connection serves it in ~30 lines.
13582
13615
  *
@@ -13697,9 +13730,18 @@ var RecordIntegrationInstances = class {
13697
13730
  ...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
13698
13731
  };
13699
13732
  }
13700
- describe = async () => this.hooks.mintId === void 0 ? this.hooks.description : {
13701
- ...this.hooks.description,
13702
- mintsId: true
13733
+ describe = async () => {
13734
+ const declared = linksOfKind(this.kind);
13735
+ const links = this.hooks.description.links !== void 0 || declared.length === 0 ? {} : { links: declared.map((link) => ({
13736
+ kind: this.kind.name,
13737
+ field: link.field,
13738
+ to: link.to
13739
+ })) };
13740
+ return {
13741
+ ...this.hooks.description,
13742
+ ...links,
13743
+ ...this.hooks.mintId === void 0 ? {} : { mintsId: true }
13744
+ };
13703
13745
  };
13704
13746
  instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
13705
13747
  listInstances = async () => {
@@ -13820,10 +13862,14 @@ function composeInstanceKinds(parts, base) {
13820
13862
  kind: part.kind.id
13821
13863
  });
13822
13864
  return {
13823
- describe: async () => ({
13824
- ...base,
13825
- kinds: parts.map((part) => part.kind)
13826
- }),
13865
+ describe: async () => {
13866
+ const links = (await Promise.all(parts.map(async (part) => (await part.provider.describe()).links ?? []))).flat();
13867
+ return {
13868
+ ...base,
13869
+ kinds: parts.map((part) => part.kind),
13870
+ ...links.length > 0 ? { links } : {}
13871
+ };
13872
+ },
13827
13873
  instanceFormSchema: async ({ id, kind }) => {
13828
13874
  return (id === null ? byKind(kind) : await holderOf(id)).provider.instanceFormSchema({ id });
13829
13875
  },
@@ -56524,18 +56570,38 @@ var IntegrationInstanceKindSchema = object({
56524
56570
  /** Relative hub path to disconnect that account. */
56525
56571
  disconnectPath: string().min(1).optional()
56526
56572
  });
56573
+ /**
56574
+ * How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
56575
+ * scans the network before any instance exists (the wizard offers the candidates); `account`: the
56576
+ * devices appear after the instance signs in. `scopedToInstance`: a discovery run belongs to ONE
56577
+ * instance and its candidates are claimed by it.
56578
+ */
56579
+ var IntegrationInstanceDiscoverySchema = object({
56580
+ mode: _enum([
56581
+ "none",
56582
+ "lan",
56583
+ "account"
56584
+ ]),
56585
+ scopedToInstance: boolean()
56586
+ });
56587
+ /**
56588
+ * One link a kind of this provider declares (D1013 rule 5): the body `field` of an instance of
56589
+ * `kind` holds the id of an instance of the function cap `to.cap` (of kind `to.kind`, when set).
56590
+ * The hub indexes these so the target's delete is refused while a link names it.
56591
+ */
56592
+ var IntegrationInstanceLinkSchema = object({
56593
+ kind: string().min(1),
56594
+ field: string().min(1),
56595
+ to: object({
56596
+ cap: string().min(1),
56597
+ kind: string().min(1).optional()
56598
+ })
56599
+ });
56527
56600
  var IntegrationInstanceDescriptionSchema = object({
56528
56601
  /** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
56529
56602
  role: IntegrationInstanceRoleSchema,
56530
56603
  /** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
56531
- discovery: object({
56532
- mode: _enum([
56533
- "none",
56534
- "lan",
56535
- "account"
56536
- ]),
56537
- scopedToInstance: boolean()
56538
- }).optional(),
56604
+ discovery: IntegrationInstanceDiscoverySchema.optional(),
56539
56605
  /** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
56540
56606
  instanceMode: IntegrationInstanceModeSchema,
56541
56607
  /** What the instance is, for the UI (`destination`, `relay`, `account`). */
@@ -56556,7 +56622,9 @@ var IntegrationInstanceDescriptionSchema = object({
56556
56622
  * `createInstance` without an `id` works: the provider mints it. Absent means the caller must
56557
56623
  * send an id (the UI then mints an opaque one).
56558
56624
  */
56559
- mintsId: boolean().optional()
56625
+ mintsId: boolean().optional(),
56626
+ /** The links to other instances the provider's kinds declare; absent means none. */
56627
+ links: array(IntegrationInstanceLinkSchema).optional()
56560
56628
  });
56561
56629
  var IntegrationInstanceStatusSchema = object({
56562
56630
  state: _enum([
@@ -98442,27 +98510,51 @@ object({
98442
98510
  delay: number$1().nonnegative().optional(),
98443
98511
  commands: record(string(), unknown())
98444
98512
  });
98445
- var BrokerInUseError = class extends Error {
98446
- brokerId;
98513
+ var InstanceInUseError = class extends Error {
98514
+ instanceId;
98447
98515
  users;
98448
- name = "BrokerInUseError";
98449
- constructor(brokerId, users) {
98450
- super(`broker '${brokerId}' is still in use by ${users.map((u) => `${u.kind} '${u.name ?? u.id}' (${u.id})`).join(", ")}; unlink them first`);
98451
- this.brokerId = brokerId;
98516
+ name = "InstanceInUseError";
98517
+ constructor(instanceId, users) {
98518
+ super(`instance '${instanceId}' is still in use by ${users.map((u) => `${u.kind} '${u.name ?? u.id}' (${u.id})`).join(", ")}; unlink them first`);
98519
+ this.instanceId = instanceId;
98452
98520
  this.users = users;
98453
98521
  }
98454
98522
  };
98455
- /** Throw {@link BrokerInUseError} when any finder names a user of `brokerId`. */
98456
- async function assertBrokerUnused(brokerId, finders) {
98523
+ /** Throw {@link InstanceInUseError} when any finder names a user of `instanceId`. */
98524
+ async function assertInstanceUnused(instanceId, finders) {
98457
98525
  const users = [];
98458
- for (const finder of finders) users.push(...await finder.find(brokerId));
98459
- if (users.length > 0) throw new BrokerInUseError(brokerId, users);
98526
+ for (const finder of finders) users.push(...await finder.find(instanceId));
98527
+ if (users.length > 0) throw new InstanceInUseError(instanceId, users);
98460
98528
  }
98461
- /**
98462
- * An account provider whose `deleteInstance` is refused while anything still uses the account
98463
- * (D1012). The account is an `integration-instances` kind (D1013), so the refusal lives on the
98464
- * delete the instances page calls, not on a separate broker cap.
98465
- */
98529
+ /** The `instance-select` field of a link, keyed like the body field it fills. */
98530
+ function linkFieldDescriptor(key, target, labels) {
98531
+ return {
98532
+ type: "instance-select",
98533
+ key,
98534
+ label: labels.label,
98535
+ ...labels.description !== void 0 ? { description: labels.description } : {},
98536
+ required: labels.required ?? true,
98537
+ ...labels.noneLabel !== void 0 ? { noneLabel: labels.noneLabel } : {},
98538
+ functionCap: target.cap,
98539
+ ...target.kind !== void 0 ? { kind: target.kind } : {}
98540
+ };
98541
+ }
98542
+ /** @deprecated use {@link InstanceInUseError}; this one only adds the old `brokerId` name. */
98543
+ var BrokerInUseError = class extends InstanceInUseError {
98544
+ get brokerId() {
98545
+ return this.instanceId;
98546
+ }
98547
+ };
98548
+ /** @deprecated use {@link assertInstanceUnused}. */
98549
+ async function assertBrokerUnused(brokerId, finders) {
98550
+ try {
98551
+ await assertInstanceUnused(brokerId, finders);
98552
+ } catch (err) {
98553
+ if (err instanceof InstanceInUseError) throw new BrokerInUseError(err.instanceId, err.users);
98554
+ throw err;
98555
+ }
98556
+ }
98557
+ /** @deprecated use `guardInstanceDelete`. */
98466
98558
  function guardBrokerDelete(base, users) {
98467
98559
  return {
98468
98560
  ...base,
@@ -98616,6 +98708,35 @@ function brokerLinkPortsOver(deps) {
98616
98708
  };
98617
98709
  }
98618
98710
  //#endregion
98711
+ //#region src/speak/constants.ts
98712
+ /** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
98713
+ var ALEXA_SPEAK_ADDON_ID = "import-alexa";
98714
+ var ALEXA_SPEAK_KIND = "alexa-echo";
98715
+ /** The kind of the Amazon-account broker the Echoes are read from. */
98716
+ var ALEXA_BROKER_KIND = "alexa";
98717
+ /** The Amazon account an Echo target speaks through. */
98718
+ var ALEXA_ACCOUNT_LINK = {
98719
+ cap: "broker",
98720
+ kind: ALEXA_BROKER_KIND
98721
+ };
98722
+ var DEVICE_REFRESH_MS = 5 * 6e4;
98723
+ /** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
98724
+ var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
98725
+ /** How often an adopted Echo's player state is read (a push channel is a later phase). */
98726
+ var ECHO_POLL_MS = 3e4;
98727
+ /** One more read after a command, once Amazon has had time to apply it. */
98728
+ var ECHO_REREAD_MS = 1500;
98729
+ /** After a 429 the account is left alone for this long, doubling up to the cap. */
98730
+ var ECHO_RATE_BACKOFF_BASE_MS = 3e4;
98731
+ var ECHO_RATE_BACKOFF_MAX_MS = 5 * 6e4;
98732
+ /**
98733
+ * A command (volume, play, Do Not Disturb) that has not started within this, or not
98734
+ * finished, is answered with an error instead of running minutes late.
98735
+ */
98736
+ var ECHO_COMMAND_DEADLINE_MS = 12e3;
98737
+ /** A poll read is stale after this; it is dropped from the queue rather than sent late. */
98738
+ var ECHO_READ_DEADLINE_MS = 25e3;
98739
+ //#endregion
98619
98740
  //#region src/config.ts
98620
98741
  /**
98621
98742
  * Alexa account connection settings for ONE broker (= one Amazon account).
@@ -98672,7 +98793,7 @@ var alexaEchoTargetSchema = object({
98672
98793
  name: string(),
98673
98794
  enabled: boolean(),
98674
98795
  /** The broker (Amazon account) the Echo belongs to; '' until one is chosen. */
98675
- brokerId: string().default(""),
98796
+ brokerId: linkField(ALEXA_ACCOUNT_LINK).default(""),
98676
98797
  echoId: string(),
98677
98798
  respectDnd: boolean()
98678
98799
  });
@@ -144037,30 +144158,6 @@ function withListedVolume(status, listed) {
144037
144158
  };
144038
144159
  }
144039
144160
  //#endregion
144040
- //#region src/speak/constants.ts
144041
- /** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
144042
- var ALEXA_SPEAK_ADDON_ID = "import-alexa";
144043
- var ALEXA_SPEAK_KIND = "alexa-echo";
144044
- /** The kind of the Amazon-account broker the Echoes are read from. */
144045
- var ALEXA_BROKER_KIND = "alexa";
144046
- var DEVICE_REFRESH_MS = 5 * 6e4;
144047
- /** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
144048
- var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
144049
- /** How often an adopted Echo's player state is read (a push channel is a later phase). */
144050
- var ECHO_POLL_MS = 3e4;
144051
- /** One more read after a command, once Amazon has had time to apply it. */
144052
- var ECHO_REREAD_MS = 1500;
144053
- /** After a 429 the account is left alone for this long, doubling up to the cap. */
144054
- var ECHO_RATE_BACKOFF_BASE_MS = 3e4;
144055
- var ECHO_RATE_BACKOFF_MAX_MS = 5 * 6e4;
144056
- /**
144057
- * A command (volume, play, Do Not Disturb) that has not started within this, or not
144058
- * finished, is answered with an error instead of running minutes late.
144059
- */
144060
- var ECHO_COMMAND_DEADLINE_MS = 12e3;
144061
- /** A poll read is stale after this; it is dropped from the queue rather than sent late. */
144062
- var ECHO_READ_DEADLINE_MS = 25e3;
144063
- //#endregion
144064
144161
  //#region src/speak/worker-protocol.ts
144065
144162
  /** Line-delimited JSON the long-lived Python worker speaks. One object per line. */
144066
144163
  var EchoDeviceSchema = object({
@@ -145138,6 +145235,13 @@ function speechFromNotification(notification) {
145138
145235
  }
145139
145236
  //#endregion
145140
145237
  //#region src/speak/provider.ts
145238
+ /**
145239
+ * `notification-output` provider for Echo devices. Text is the rule template
145240
+ * as plain speech. A burst collapses or waits; it is not queued. A transport
145241
+ * failure is `success: false` so the notification outbox retries. A collapse,
145242
+ * a rate limit, or Do Not Disturb is a recorded skip (`success: true`) so the
145243
+ * outbox does not speak the same sentence again later.
145244
+ */
145141
145245
  function quietLogger$1() {
145142
145246
  const logger = {
145143
145247
  debug() {},
@@ -145193,11 +145297,8 @@ function configSchema() {
145193
145297
  title: "Echo",
145194
145298
  titleKey: "import-alexa:echoTarget.s_echo.title",
145195
145299
  fields: [...[{
145196
- type: "broker-select",
145197
- key: "brokerId",
145198
- label: "Amazon account",
145199
- labelKey: "import-alexa:echoTarget.s_echo.brokerId.label",
145200
- brokerKind: ALEXA_BROKER_KIND
145300
+ ...linkFieldDescriptor("brokerId", ALEXA_ACCOUNT_LINK, { label: "Amazon account" }),
145301
+ labelKey: "import-alexa:echoTarget.s_echo.brokerId.label"
145201
145302
  }, {
145202
145303
  type: "select",
145203
145304
  key: "echoId",
package/dist/addon.mjs CHANGED
@@ -13575,6 +13575,39 @@ function buildCapturingReply(envelope) {
13575
13575
  return wrapper;
13576
13576
  }
13577
13577
  /**
13578
+ * The link primitive's declaration half (D1013 rule 5): what a link field names and how a kind
13579
+ * records it. Split from `instance-link.ts` so `RecordIntegrationInstances` can read the links of
13580
+ * its kind without importing the builders that import it.
13581
+ */
13582
+ /** The body field of a link: the id of the instance it names; the target rides in the field meta. */
13583
+ function linkField(target) {
13584
+ return string().meta({ link: target });
13585
+ }
13586
+ function linkTargetOf(meta) {
13587
+ if (typeof meta !== "object" || meta === null) return void 0;
13588
+ const link = Reflect.get(meta, "link");
13589
+ const parsed = LinkTargetSchema.safeParse(link);
13590
+ return parsed.success ? parsed.data : void 0;
13591
+ }
13592
+ var LinkTargetSchema = object({
13593
+ cap: string().min(1),
13594
+ kind: string().min(1).optional()
13595
+ });
13596
+ /** The links a kind declares, read from the meta of its body fields. */
13597
+ function linksOfKind(kind) {
13598
+ const schema = kind.schema;
13599
+ if (!(schema instanceof ZodObject)) return [];
13600
+ const links = [];
13601
+ for (const [field, fieldSchema] of Object.entries(schema.shape)) {
13602
+ const to = linkTargetOf(globalRegistry.get(fieldSchema));
13603
+ if (to !== void 0) links.push({
13604
+ field,
13605
+ to
13606
+ });
13607
+ }
13608
+ return links;
13609
+ }
13610
+ /**
13578
13611
  * `RecordIntegrationInstances` - the `integration-instances` provider over ONE
13579
13612
  * entry kind, so an addon that holds a connection serves it in ~30 lines.
13580
13613
  *
@@ -13695,9 +13728,18 @@ var RecordIntegrationInstances = class {
13695
13728
  ...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
13696
13729
  };
13697
13730
  }
13698
- describe = async () => this.hooks.mintId === void 0 ? this.hooks.description : {
13699
- ...this.hooks.description,
13700
- mintsId: true
13731
+ describe = async () => {
13732
+ const declared = linksOfKind(this.kind);
13733
+ const links = this.hooks.description.links !== void 0 || declared.length === 0 ? {} : { links: declared.map((link) => ({
13734
+ kind: this.kind.name,
13735
+ field: link.field,
13736
+ to: link.to
13737
+ })) };
13738
+ return {
13739
+ ...this.hooks.description,
13740
+ ...links,
13741
+ ...this.hooks.mintId === void 0 ? {} : { mintsId: true }
13742
+ };
13701
13743
  };
13702
13744
  instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
13703
13745
  listInstances = async () => {
@@ -13818,10 +13860,14 @@ function composeInstanceKinds(parts, base) {
13818
13860
  kind: part.kind.id
13819
13861
  });
13820
13862
  return {
13821
- describe: async () => ({
13822
- ...base,
13823
- kinds: parts.map((part) => part.kind)
13824
- }),
13863
+ describe: async () => {
13864
+ const links = (await Promise.all(parts.map(async (part) => (await part.provider.describe()).links ?? []))).flat();
13865
+ return {
13866
+ ...base,
13867
+ kinds: parts.map((part) => part.kind),
13868
+ ...links.length > 0 ? { links } : {}
13869
+ };
13870
+ },
13825
13871
  instanceFormSchema: async ({ id, kind }) => {
13826
13872
  return (id === null ? byKind(kind) : await holderOf(id)).provider.instanceFormSchema({ id });
13827
13873
  },
@@ -56522,18 +56568,38 @@ var IntegrationInstanceKindSchema = object({
56522
56568
  /** Relative hub path to disconnect that account. */
56523
56569
  disconnectPath: string().min(1).optional()
56524
56570
  });
56571
+ /**
56572
+ * How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
56573
+ * scans the network before any instance exists (the wizard offers the candidates); `account`: the
56574
+ * devices appear after the instance signs in. `scopedToInstance`: a discovery run belongs to ONE
56575
+ * instance and its candidates are claimed by it.
56576
+ */
56577
+ var IntegrationInstanceDiscoverySchema = object({
56578
+ mode: _enum([
56579
+ "none",
56580
+ "lan",
56581
+ "account"
56582
+ ]),
56583
+ scopedToInstance: boolean()
56584
+ });
56585
+ /**
56586
+ * One link a kind of this provider declares (D1013 rule 5): the body `field` of an instance of
56587
+ * `kind` holds the id of an instance of the function cap `to.cap` (of kind `to.kind`, when set).
56588
+ * The hub indexes these so the target's delete is refused while a link names it.
56589
+ */
56590
+ var IntegrationInstanceLinkSchema = object({
56591
+ kind: string().min(1),
56592
+ field: string().min(1),
56593
+ to: object({
56594
+ cap: string().min(1),
56595
+ kind: string().min(1).optional()
56596
+ })
56597
+ });
56525
56598
  var IntegrationInstanceDescriptionSchema = object({
56526
56599
  /** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
56527
56600
  role: IntegrationInstanceRoleSchema,
56528
56601
  /** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
56529
- discovery: object({
56530
- mode: _enum([
56531
- "none",
56532
- "lan",
56533
- "account"
56534
- ]),
56535
- scopedToInstance: boolean()
56536
- }).optional(),
56602
+ discovery: IntegrationInstanceDiscoverySchema.optional(),
56537
56603
  /** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
56538
56604
  instanceMode: IntegrationInstanceModeSchema,
56539
56605
  /** What the instance is, for the UI (`destination`, `relay`, `account`). */
@@ -56554,7 +56620,9 @@ var IntegrationInstanceDescriptionSchema = object({
56554
56620
  * `createInstance` without an `id` works: the provider mints it. Absent means the caller must
56555
56621
  * send an id (the UI then mints an opaque one).
56556
56622
  */
56557
- mintsId: boolean().optional()
56623
+ mintsId: boolean().optional(),
56624
+ /** The links to other instances the provider's kinds declare; absent means none. */
56625
+ links: array(IntegrationInstanceLinkSchema).optional()
56558
56626
  });
56559
56627
  var IntegrationInstanceStatusSchema = object({
56560
56628
  state: _enum([
@@ -98440,27 +98508,51 @@ object({
98440
98508
  delay: number$1().nonnegative().optional(),
98441
98509
  commands: record(string(), unknown())
98442
98510
  });
98443
- var BrokerInUseError = class extends Error {
98444
- brokerId;
98511
+ var InstanceInUseError = class extends Error {
98512
+ instanceId;
98445
98513
  users;
98446
- name = "BrokerInUseError";
98447
- constructor(brokerId, users) {
98448
- super(`broker '${brokerId}' is still in use by ${users.map((u) => `${u.kind} '${u.name ?? u.id}' (${u.id})`).join(", ")}; unlink them first`);
98449
- this.brokerId = brokerId;
98514
+ name = "InstanceInUseError";
98515
+ constructor(instanceId, users) {
98516
+ super(`instance '${instanceId}' is still in use by ${users.map((u) => `${u.kind} '${u.name ?? u.id}' (${u.id})`).join(", ")}; unlink them first`);
98517
+ this.instanceId = instanceId;
98450
98518
  this.users = users;
98451
98519
  }
98452
98520
  };
98453
- /** Throw {@link BrokerInUseError} when any finder names a user of `brokerId`. */
98454
- async function assertBrokerUnused(brokerId, finders) {
98521
+ /** Throw {@link InstanceInUseError} when any finder names a user of `instanceId`. */
98522
+ async function assertInstanceUnused(instanceId, finders) {
98455
98523
  const users = [];
98456
- for (const finder of finders) users.push(...await finder.find(brokerId));
98457
- if (users.length > 0) throw new BrokerInUseError(brokerId, users);
98524
+ for (const finder of finders) users.push(...await finder.find(instanceId));
98525
+ if (users.length > 0) throw new InstanceInUseError(instanceId, users);
98458
98526
  }
98459
- /**
98460
- * An account provider whose `deleteInstance` is refused while anything still uses the account
98461
- * (D1012). The account is an `integration-instances` kind (D1013), so the refusal lives on the
98462
- * delete the instances page calls, not on a separate broker cap.
98463
- */
98527
+ /** The `instance-select` field of a link, keyed like the body field it fills. */
98528
+ function linkFieldDescriptor(key, target, labels) {
98529
+ return {
98530
+ type: "instance-select",
98531
+ key,
98532
+ label: labels.label,
98533
+ ...labels.description !== void 0 ? { description: labels.description } : {},
98534
+ required: labels.required ?? true,
98535
+ ...labels.noneLabel !== void 0 ? { noneLabel: labels.noneLabel } : {},
98536
+ functionCap: target.cap,
98537
+ ...target.kind !== void 0 ? { kind: target.kind } : {}
98538
+ };
98539
+ }
98540
+ /** @deprecated use {@link InstanceInUseError}; this one only adds the old `brokerId` name. */
98541
+ var BrokerInUseError = class extends InstanceInUseError {
98542
+ get brokerId() {
98543
+ return this.instanceId;
98544
+ }
98545
+ };
98546
+ /** @deprecated use {@link assertInstanceUnused}. */
98547
+ async function assertBrokerUnused(brokerId, finders) {
98548
+ try {
98549
+ await assertInstanceUnused(brokerId, finders);
98550
+ } catch (err) {
98551
+ if (err instanceof InstanceInUseError) throw new BrokerInUseError(err.instanceId, err.users);
98552
+ throw err;
98553
+ }
98554
+ }
98555
+ /** @deprecated use `guardInstanceDelete`. */
98464
98556
  function guardBrokerDelete(base, users) {
98465
98557
  return {
98466
98558
  ...base,
@@ -98614,6 +98706,35 @@ function brokerLinkPortsOver(deps) {
98614
98706
  };
98615
98707
  }
98616
98708
  //#endregion
98709
+ //#region src/speak/constants.ts
98710
+ /** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
98711
+ var ALEXA_SPEAK_ADDON_ID = "import-alexa";
98712
+ var ALEXA_SPEAK_KIND = "alexa-echo";
98713
+ /** The kind of the Amazon-account broker the Echoes are read from. */
98714
+ var ALEXA_BROKER_KIND = "alexa";
98715
+ /** The Amazon account an Echo target speaks through. */
98716
+ var ALEXA_ACCOUNT_LINK = {
98717
+ cap: "broker",
98718
+ kind: ALEXA_BROKER_KIND
98719
+ };
98720
+ var DEVICE_REFRESH_MS = 5 * 6e4;
98721
+ /** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
98722
+ var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
98723
+ /** How often an adopted Echo's player state is read (a push channel is a later phase). */
98724
+ var ECHO_POLL_MS = 3e4;
98725
+ /** One more read after a command, once Amazon has had time to apply it. */
98726
+ var ECHO_REREAD_MS = 1500;
98727
+ /** After a 429 the account is left alone for this long, doubling up to the cap. */
98728
+ var ECHO_RATE_BACKOFF_BASE_MS = 3e4;
98729
+ var ECHO_RATE_BACKOFF_MAX_MS = 5 * 6e4;
98730
+ /**
98731
+ * A command (volume, play, Do Not Disturb) that has not started within this, or not
98732
+ * finished, is answered with an error instead of running minutes late.
98733
+ */
98734
+ var ECHO_COMMAND_DEADLINE_MS = 12e3;
98735
+ /** A poll read is stale after this; it is dropped from the queue rather than sent late. */
98736
+ var ECHO_READ_DEADLINE_MS = 25e3;
98737
+ //#endregion
98617
98738
  //#region src/config.ts
98618
98739
  /**
98619
98740
  * Alexa account connection settings for ONE broker (= one Amazon account).
@@ -98670,7 +98791,7 @@ var alexaEchoTargetSchema = object({
98670
98791
  name: string(),
98671
98792
  enabled: boolean(),
98672
98793
  /** The broker (Amazon account) the Echo belongs to; '' until one is chosen. */
98673
- brokerId: string().default(""),
98794
+ brokerId: linkField(ALEXA_ACCOUNT_LINK).default(""),
98674
98795
  echoId: string(),
98675
98796
  respectDnd: boolean()
98676
98797
  });
@@ -144035,30 +144156,6 @@ function withListedVolume(status, listed) {
144035
144156
  };
144036
144157
  }
144037
144158
  //#endregion
144038
- //#region src/speak/constants.ts
144039
- /** Echo speech limits and the worker restart bound (same window as CrashSupervisor). */
144040
- var ALEXA_SPEAK_ADDON_ID = "import-alexa";
144041
- var ALEXA_SPEAK_KIND = "alexa-echo";
144042
- /** The kind of the Amazon-account broker the Echoes are read from. */
144043
- var ALEXA_BROKER_KIND = "alexa";
144044
- var DEVICE_REFRESH_MS = 5 * 6e4;
144045
- /** Where the addon serves the Alexa mark; `listTargetKinds` stamps it as `iconUrl`. */
144046
- var ALEXA_SPEAK_ICON_URL = `/addon/${ALEXA_SPEAK_ADDON_ID}/icons/${ALEXA_SPEAK_KIND}`;
144047
- /** How often an adopted Echo's player state is read (a push channel is a later phase). */
144048
- var ECHO_POLL_MS = 3e4;
144049
- /** One more read after a command, once Amazon has had time to apply it. */
144050
- var ECHO_REREAD_MS = 1500;
144051
- /** After a 429 the account is left alone for this long, doubling up to the cap. */
144052
- var ECHO_RATE_BACKOFF_BASE_MS = 3e4;
144053
- var ECHO_RATE_BACKOFF_MAX_MS = 5 * 6e4;
144054
- /**
144055
- * A command (volume, play, Do Not Disturb) that has not started within this, or not
144056
- * finished, is answered with an error instead of running minutes late.
144057
- */
144058
- var ECHO_COMMAND_DEADLINE_MS = 12e3;
144059
- /** A poll read is stale after this; it is dropped from the queue rather than sent late. */
144060
- var ECHO_READ_DEADLINE_MS = 25e3;
144061
- //#endregion
144062
144159
  //#region src/speak/worker-protocol.ts
144063
144160
  /** Line-delimited JSON the long-lived Python worker speaks. One object per line. */
144064
144161
  var EchoDeviceSchema = object({
@@ -145136,6 +145233,13 @@ function speechFromNotification(notification) {
145136
145233
  }
145137
145234
  //#endregion
145138
145235
  //#region src/speak/provider.ts
145236
+ /**
145237
+ * `notification-output` provider for Echo devices. Text is the rule template
145238
+ * as plain speech. A burst collapses or waits; it is not queued. A transport
145239
+ * failure is `success: false` so the notification outbox retries. A collapse,
145240
+ * a rate limit, or Do Not Disturb is a recorded skip (`success: true`) so the
145241
+ * outbox does not speak the same sentence again later.
145242
+ */
145139
145243
  function quietLogger$1() {
145140
145244
  const logger = {
145141
145245
  debug() {},
@@ -145191,11 +145295,8 @@ function configSchema() {
145191
145295
  title: "Echo",
145192
145296
  titleKey: "import-alexa:echoTarget.s_echo.title",
145193
145297
  fields: [...[{
145194
- type: "broker-select",
145195
- key: "brokerId",
145196
- label: "Amazon account",
145197
- labelKey: "import-alexa:echoTarget.s_echo.brokerId.label",
145198
- brokerKind: ALEXA_BROKER_KIND
145298
+ ...linkFieldDescriptor("brokerId", ALEXA_ACCOUNT_LINK, { label: "Amazon account" }),
145299
+ labelKey: "import-alexa:echoTarget.s_echo.brokerId.label"
145199
145300
  }, {
145200
145301
  type: "select",
145201
145302
  key: "echoId",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-import-alexa",
3
- "version": "0.2.190",
3
+ "version": "0.2.191",
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.780"
75
+ "server": ">=1.2.784"
76
76
  }
77
77
  },
78
78
  "files": [