@camstack/addon-training 0.1.104 → 0.1.106

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.
Files changed (25) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{MotionZonesSettings-Bm3-aVd_.mjs → MotionZonesSettings-B-HW_hQS.mjs} +2 -2
  3. package/dist/{PrivacyMaskSettings-CHKqJ1Jl.mjs → PrivacyMaskSettings-D24QEivW.mjs} +2 -2
  4. package/dist/{SceneMonitorEditor-BLeDd1kR.mjs → SceneMonitorEditor-BJhJhwU_.mjs} +2 -2
  5. package/dist/_stub.js +6453 -6114
  6. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-DsiAbTAk.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-DhOREDNB.mjs} +3 -3
  7. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CV7yw1Ol.mjs +26 -0
  8. package/dist/addon-training.css +1 -1
  9. package/dist/{dist-DkRYepCT.js → dist-BifGWu-4.js} +1265 -854
  10. package/dist/{dist-CXZDrfkr.mjs → dist-CSPYj4Bg.mjs} +1266 -855
  11. package/dist/{hostInit-CkFf4SAO.mjs → hostInit-jYho1Uin.mjs} +2 -2
  12. package/dist/{icons-CXruMajc.mjs → icons-tE-y9KlM.mjs} +521 -527
  13. package/dist/index.js +1 -1
  14. package/dist/orchestrator/training.addon.js +1 -1
  15. package/dist/orchestrator/training.addon.mjs +1 -1
  16. package/dist/{player-overlays-DxLTXMBd.mjs → player-overlays-CAEteoxw.mjs} +1 -1
  17. package/dist/providers/modal/training-modal.addon.js +1 -1
  18. package/dist/providers/modal/training-modal.addon.mjs +1 -1
  19. package/dist/remoteEntry.js +1 -1
  20. package/dist/{responsive-1P4H2DQA.mjs → responsive-BJqCg1mL.mjs} +1 -1
  21. package/dist/{scene-monitor-copy-DmazWKcQ.mjs → scene-monitor-copy-CkZtRtGd.mjs} +1 -1
  22. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-BBdigjaL.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-Bmx0UoLI.mjs} +1 -1
  23. package/dist/{zoom-out-D4rm3GoJ.mjs → zoom-out-CWDOvUsr.mjs} +133 -127
  24. package/package.json +1 -1
  25. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-rtY7yvpT.mjs +0 -26
@@ -5201,6 +5201,14 @@ function object(shape, params) {
5201
5201
  ...normalizeParams(params)
5202
5202
  });
5203
5203
  }
5204
+ function strictObject(shape, params) {
5205
+ return new ZodObject({
5206
+ type: "object",
5207
+ shape,
5208
+ catchall: never(),
5209
+ ...normalizeParams(params)
5210
+ });
5211
+ }
5204
5212
  function looseObject(shape, params) {
5205
5213
  return new ZodObject({
5206
5214
  type: "object",
@@ -5580,7 +5588,7 @@ var ZodIssueCode = {
5580
5588
  var ZodFirstPartyTypeKind;
5581
5589
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5582
5590
  //#endregion
5583
- //#region ../types/dist/sleep-DJws6ho7.mjs
5591
+ //#region ../types/dist/sleep-6eG9C8Xl.mjs
5584
5592
  /**
5585
5593
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5586
5594
  * window to float samples (D455).
@@ -8804,9 +8812,93 @@ function optionalMethod(input, output, options) {
8804
8812
  providerOptional: true
8805
8813
  };
8806
8814
  }
8815
+ /**
8816
+ * `addon-pages-source` — collection cap exposing per-provider raw page
8817
+ * declarations. Every addon that contributes a UI page registers a
8818
+ * provider here. The hub-side singleton aggregator (`addon-pages` cap,
8819
+ * see `addon-pages.cap.ts`) walks this collection, stamps versioned
8820
+ * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
8821
+ * that admin-ui consumes.
8822
+ *
8823
+ * The split exists because the public listing has a different output
8824
+ * shape than the per-provider raw declarations, and we want both ends
8825
+ * to flow through codegen instead of relying on a hand-written wrapper.
8826
+ */
8827
+ var AddonPageDeclarationSchema = object({
8828
+ id: string(),
8829
+ label: string(),
8830
+ icon: string(),
8831
+ path: string(),
8832
+ /**
8833
+ * Module Federation remote name — must match the `name` field on the
8834
+ * page addon's `federation()` plugin config. Used by admin-ui's
8835
+ * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
8836
+ * Conventionally `addon_<id>_page` (snake_case; MF names cannot
8837
+ * contain hyphens).
8838
+ */
8839
+ remoteName: string(),
8840
+ /**
8841
+ * Bundle filename inside the addon's `dist/` dir served at
8842
+ * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
8843
+ * is always `'remoteEntry.js'`; the value is kept on the metadata so
8844
+ * the static-file route can compute an mtime-based cache-buster URL
8845
+ * without a separate filesystem stat.
8846
+ */
8847
+ bundle: string(),
8848
+ /**
8849
+ * Sidebar section this page docks into. Well-known ids: `'detection'`,
8850
+ * `'cluster'`, `'administration'` — the page renders inside that group.
8851
+ * Any OTHER string creates (or joins) a custom section rendered after
8852
+ * the built-in groups; its label comes from `sectionLabel` (first
8853
+ * declaration wins), falling back to the id. Absent → the legacy
8854
+ * "Addon Pages" group.
8855
+ */
8856
+ section: string().optional(),
8857
+ /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
8858
+ sectionLabel: string().optional()
8859
+ });
8860
+ var addonPagesSourceCapability = {
8861
+ name: "addon-pages-source",
8862
+ scope: "system",
8863
+ mode: "collection",
8864
+ internal: true,
8865
+ methods: {
8866
+ /**
8867
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
8868
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
8869
+ * authenticated session at the default `auth: 'protected'`. The only
8870
+ * consumer, the `addon-pages-aggregator` builtin, reads it via
8871
+ * `ctx.capabilities.getCollection('addon-pages-source')` — LOCAL-PROCESS
8872
+ * only, never tRPC (per `ctx.capabilities` seeing only the local
8873
+ * process). The public, enriched listing admin-ui actually consumes is
8874
+ * the separate `addon-pages` cap, unaffected by this change.
8875
+ */
8876
+ listPages: method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" }) }
8877
+ };
8807
8878
  var StaticDirOutputSchema$1 = object({ staticDir: string() });
8808
8879
  var VersionOutputSchema$1 = object({ version: string() });
8809
8880
  method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
8881
+ /**
8882
+ * The pipeline step-input shape and the run-result bridge, in a module that
8883
+ * imports nothing but zod and types.
8884
+ *
8885
+ * WHY A SEPARATE FILE (addon footprint, D28): both were declared in
8886
+ * `pipeline-executor.cap.ts`, whose import evaluates the whole executor schema
8887
+ * graph. An addon that only needs these two (the benchmark's `runPipelineTest`
8888
+ * action) paid ~130 MB of heap at load. `pipeline-executor.cap.ts` re-exports
8889
+ * them, so every existing import keeps working; addon server code takes them
8890
+ * from `@camstack/types/addon`.
8891
+ */
8892
+ var PipelineStepInputSchema = lazy(() => object({
8893
+ addonId: string(),
8894
+ modelId: string().optional(),
8895
+ enabled: boolean().default(true),
8896
+ children: array(PipelineStepInputSchema).optional(),
8897
+ settings: record(string(), unknown()).optional(),
8898
+ jumpDeviceKey: string().optional()
8899
+ }));
8900
+ /** Typed bridge to the recursive `FrameResult` graph; validation is structural. */
8901
+ var PipelineRunResultBridge = custom();
8810
8902
  var StaticDirOutputSchema = object({ staticDir: string() });
8811
8903
  var VersionOutputSchema = object({ version: string() });
8812
8904
  method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
@@ -8991,6 +9083,7 @@ var CAP_NAMES = Object.freeze([
8991
9083
  "network-link",
8992
9084
  "network-quality",
8993
9085
  "node-certificates",
9086
+ "node-removal-hook",
8994
9087
  "nodes",
8995
9088
  "notification-output",
8996
9089
  "notification-rules",
@@ -9001,6 +9094,7 @@ var CAP_NAMES = Object.freeze([
9001
9094
  "osd",
9002
9095
  "osd-manager",
9003
9096
  "pet-feeder",
9097
+ "pet-fountain",
9004
9098
  "pipeline-analytics",
9005
9099
  "pipeline-executor",
9006
9100
  "pipeline-orchestrator",
@@ -10280,6 +10374,56 @@ Object.freeze({
10280
10374
  "valueType": "number"
10281
10375
  }
10282
10376
  ],
10377
+ "pet-fountain": [
10378
+ {
10379
+ "path": "waterLevel",
10380
+ "valueType": "number"
10381
+ },
10382
+ {
10383
+ "path": "waterVolume",
10384
+ "valueType": "number"
10385
+ },
10386
+ {
10387
+ "path": "empty",
10388
+ "valueType": "boolean"
10389
+ },
10390
+ {
10391
+ "path": "pumpRunning",
10392
+ "valueType": "boolean"
10393
+ },
10394
+ {
10395
+ "path": "pumpIntervalEnabled",
10396
+ "valueType": "boolean"
10397
+ },
10398
+ {
10399
+ "path": "pumpOnTime",
10400
+ "valueType": "number"
10401
+ },
10402
+ {
10403
+ "path": "pumpOffTime",
10404
+ "valueType": "number"
10405
+ },
10406
+ {
10407
+ "path": "lastDrink",
10408
+ "valueType": "number"
10409
+ },
10410
+ {
10411
+ "path": "currentHourDrink",
10412
+ "valueType": "number"
10413
+ },
10414
+ {
10415
+ "path": "last24hDrink",
10416
+ "valueType": "number"
10417
+ },
10418
+ {
10419
+ "path": "todayDrink",
10420
+ "valueType": "number"
10421
+ },
10422
+ {
10423
+ "path": "error",
10424
+ "valueType": "string"
10425
+ }
10426
+ ],
10283
10427
  "power-meter": [
10284
10428
  {
10285
10429
  "path": "power",
@@ -10817,7 +10961,7 @@ object({
10817
10961
  params: record(string(), union([string(), number()])).optional()
10818
10962
  });
10819
10963
  //#endregion
10820
- //#region ../types/dist/err-msg-BCdlR2Li.mjs
10964
+ //#region ../types/dist/err-msg-DWXWc0Di.mjs
10821
10965
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
10822
10966
  DeviceType["Camera"] = "camera";
10823
10967
  DeviceType["Hub"] = "hub";
@@ -10909,6 +11053,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
10909
11053
  * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
10910
11054
  * integrations sharing the same food/desiccant/hopper surface. */
10911
11055
  DeviceType["PetFeeder"] = "pet-feeder";
11056
+ /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
11057
+ * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
11058
+ * of a container and is BUILT as a composed device (D967). */
11059
+ DeviceType["PetFountain"] = "pet-fountain";
10912
11060
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
10913
11061
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
10914
11062
  * Sources: native Bosch Home Connect, reusable by similar integrations.
@@ -11169,7 +11317,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11169
11317
  return DeviceRole;
11170
11318
  }({});
11171
11319
  //#endregion
11172
- //#region ../types/dist/composition-DpFv_J6D.mjs
11320
+ //#region ../types/dist/composition-kSuOifuG.mjs
11173
11321
  /**
11174
11322
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11175
11323
  *
@@ -12254,12 +12402,28 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
12254
12402
  ])
12255
12403
  })
12256
12404
  ]);
12257
- var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
12405
+ var forwardArgUnion = union([
12406
+ string(),
12407
+ number(),
12408
+ boolean(),
12409
+ strictObject({ ["$input"]: string().min(1) }),
12410
+ record(string().min(1), lazy(() => forwardArgUnion))
12411
+ ]);
12412
+ var CompositionForwardArgSchema = forwardArgUnion;
12413
+ /**
12414
+ * `forward` (D659, amended by D967): one method of a BUILT capability is one
12415
+ * call of `cap.method` on `source`. Without `args` the built method's input
12416
+ * passes through minus `deviceId`; with `args` the template IS the source's
12417
+ * whole input. `deviceId` is always the source's own, never an argument.
12418
+ */
12419
+ var CompositionForwardCommandSchema = object({
12258
12420
  kind: literal("forward"),
12259
12421
  source: CompositionSourceRefSchema,
12260
12422
  cap: string().min(1),
12261
- method: string().min(1)
12262
- }), CompositionCodeSourceSchema]);
12423
+ method: string().min(1),
12424
+ args: record(string().min(1), CompositionForwardArgSchema).optional()
12425
+ });
12426
+ var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
12263
12427
  /** An item key inside an item-array cap (`consumables.items[<key>]`). */
12264
12428
  var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
12265
12429
  /**
@@ -12269,7 +12433,13 @@ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
12269
12433
  */
12270
12434
  var CompositionItemEntrySchema = object({
12271
12435
  label: string().min(1).max(80),
12272
- fields: record(string().min(1), CompositionFieldSourceSchema)
12436
+ fields: record(string().min(1), CompositionFieldSourceSchema),
12437
+ /**
12438
+ * What the cap's `reset` does for THIS item (consumables). Present = the item
12439
+ * is resettable and a `reset` call naming its key forwards here. The item key
12440
+ * selects the command, so it is never an argument: there is no pass-through.
12441
+ */
12442
+ reset: CompositionForwardCommandSchema.optional()
12273
12443
  });
12274
12444
  var CompositionFeatureSchema = discriminatedUnion("kind", [object({
12275
12445
  kind: literal("fields"),
@@ -12281,7 +12451,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
12281
12451
  * owns the WHOLE array field: the composed value holds exactly these items.
12282
12452
  */
12283
12453
  items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
12284
- /** RESERVED for slice 3; refused by the validator when non-empty. */
12454
+ /** Method name → what serves it. Only a BUILT capability takes commands (D967). */
12285
12455
  commands: record(string().min(1), CompositionCommandTargetSchema).optional()
12286
12456
  }), object({
12287
12457
  kind: literal("passthrough"),
@@ -16239,163 +16409,521 @@ object({
16239
16409
  lastError: string().nullable()
16240
16410
  });
16241
16411
  /**
16242
- * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
16243
- * surface that admin-ui consumes through `useAddonPagesListPages()`.
16412
+ * notification-output — canonical, capability-gated notification delivery.
16244
16413
  *
16245
- * The provider iterates every `addon-pages-source` (collection) provider
16246
- * and emits `AddonPageInfo[]` enriched with versioned `bundleUrl` strings
16247
- * pointing at `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. The
16248
- * filesystem `mtime` cache-buster lets the browser pick up addon
16249
- * rebuilds without manual reload.
16414
+ * Apprise-derived model (see
16415
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
16416
+ * callers emit ONE canonical `Notification`; each provider declares a
16417
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
16418
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
16419
+ * message to what the kind supports — callers never special-case a service.
16250
16420
  *
16251
- * The hub-local builtin `addon-pages-aggregator` (see
16252
- * `@camstack/system/builtins/addon-pages-aggregator`) registers the
16253
- * provider. Splitting the public aggregator from the raw collection
16254
- * keeps both ends in codegen — there's no hand-written
16255
- * `addon-pages.router.ts` wrapper anymore.
16421
+ * DESIGN DECISIONS (locked):
16422
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
16423
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
16424
+ * cap. Rationale: the admin UI needs one uniform surface across the
16425
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
16426
+ * alternative would fork the UI per addon and cannot host the
16427
+ * discovery→adopt flow.
16428
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
16429
+ * the generated cap-mount auto-`concatCollection`-fans them across every
16430
+ * registered provider (notifiers addon + HA addon) so one catalog is
16431
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
16432
+ * `addonId` the generated collection router extracts from the call input.
16433
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
16434
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
16435
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
16436
+ * base64 fallback needed.
16437
+ *
16438
+ * TODO (deferred, closed-set change — separate decision): add
16439
+ * `providerKind: 'notify'` so notification providers surface on the unified
16440
+ * admin "Integrations" page.
16256
16441
  */
16257
- var AddonPageDeclarationSchema$1 = object({
16258
- id: string(),
16259
- label: string(),
16260
- icon: string(),
16261
- path: string(),
16262
- remoteName: string(),
16263
- bundle: string(),
16264
- section: string().optional(),
16265
- sectionLabel: string().optional()
16266
- });
16267
- var AddonPageInfoSchema = object({
16268
- addonId: string(),
16269
- page: AddonPageDeclarationSchema$1,
16270
- bundleUrl: string()
16271
- });
16272
- method(_void(), array(AddonPageInfoSchema).readonly());
16273
16442
  /**
16274
- * `addon-pages-source` — collection cap exposing per-provider raw page
16275
- * declarations. Every addon that contributes a UI page registers a
16276
- * provider here. The hub-side singleton aggregator (`addon-pages` cap,
16277
- * see `addon-pages.cap.ts`) walks this collection, stamps versioned
16278
- * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
16279
- * that admin-ui consumes.
16443
+ * Zentik-derived typed-media enum — the superset across every kind. Each
16444
+ * adapter picks what it supports and the degrade engine filters the rest.
16445
+ */
16446
+ var AttachmentMediaTypeSchema = _enum([
16447
+ "image",
16448
+ "video",
16449
+ "gif",
16450
+ "audio",
16451
+ "icon"
16452
+ ]);
16453
+ /**
16454
+ * A single attachment. Exactly one of `url` (remote source, most adapters
16455
+ * prefer this) or `bytes` (inline source; required for Pushover-style
16456
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
16457
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
16458
+ */
16459
+ var AttachmentSchema = object({
16460
+ mediaType: AttachmentMediaTypeSchema,
16461
+ url: string().optional(),
16462
+ bytes: _instanceof(Uint8Array).optional(),
16463
+ mime: string().optional(),
16464
+ name: string().optional()
16465
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
16466
+ var NotificationFormatSchema = _enum([
16467
+ "text",
16468
+ "markdown",
16469
+ "html"
16470
+ ]);
16471
+ /**
16472
+ * The CLOSED icon vocabulary an action button may use.
16280
16473
  *
16281
- * The split exists because the public listing has a different output
16282
- * shape than the per-provider raw declarations, and we want both ends
16283
- * to flow through codegen instead of relying on a hand-written wrapper.
16474
+ * A closed set, not a free string, and that is the whole point: an arbitrary
16475
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
16476
+ * notices — the same class of gap as a zone vocabulary nothing produced
16477
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
16478
+ * declares `actionIcons: false` and the degrade engine strips the field.
16479
+ *
16480
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
16481
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
16482
+ * differently.
16483
+ *
16484
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
16485
+ *
16486
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
16487
+ * the whole set onto its own renderer's vocabulary through a
16488
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
16489
+ * fallback, so adding a member here fails every adapter's build until someone
16490
+ * decides its glyph, which is the only place that decision can be made
16491
+ * honestly.
16492
+ *
16493
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
16494
+ * `disarm` straight through; iOS feeds that string to
16495
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
16496
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
16497
+ * mapping, and "the field is documented" is not "the value renders".
16498
+ *
16499
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
16500
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
16501
+ * so an addon that emits a token the running hub does not know does not lose an
16502
+ * icon — its whole `send` fails Zod validation and the notification never
16503
+ * arrives. Never emit a new token from an addon before the train carrying it.
16284
16504
  */
16285
- var AddonPageDeclarationSchema = object({
16505
+ var NotificationActionIconSchema = _enum([
16506
+ "acknowledge",
16507
+ "dismiss",
16508
+ "silence",
16509
+ "snooze",
16510
+ "view",
16511
+ "play",
16512
+ "open",
16513
+ "close",
16514
+ "lock",
16515
+ "unlock",
16516
+ "arm",
16517
+ "arm-home",
16518
+ "arm-away",
16519
+ "arm-night",
16520
+ "disarm",
16521
+ "light",
16522
+ "alert",
16523
+ "camera"
16524
+ ]);
16525
+ /** A single tap-through action button. */
16526
+ var NotificationActionSchema = object({
16286
16527
  id: string(),
16287
16528
  label: string(),
16288
- icon: string(),
16289
- path: string(),
16290
- /**
16291
- * Module Federation remote name — must match the `name` field on the
16292
- * page addon's `federation()` plugin config. Used by admin-ui's
16293
- * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
16294
- * Conventionally `addon_<id>_page` (snake_case; MF names cannot
16295
- * contain hyphens).
16296
- */
16297
- remoteName: string(),
16529
+ url: string().optional(),
16530
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
16531
+ icon: NotificationActionIconSchema.optional(),
16298
16532
  /**
16299
- * Bundle filename inside the addon's `dist/` dir served at
16300
- * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
16301
- * is always `'remoteEntry.js'`; the value is kept on the metadata so
16302
- * the static-file route can compute an mtime-based cache-buster URL
16303
- * without a separate filesystem stat.
16533
+ * Renders in a warning style where the notifier supports it.
16534
+ *
16535
+ * A HINT, never a gate. The callback's authority is its token and nothing
16536
+ * else — see `notification-center/action-token.ts` for what that does and
16537
+ * does not buy.
16304
16538
  */
16305
- bundle: string(),
16539
+ destructive: boolean().optional(),
16306
16540
  /**
16307
- * Sidebar section this page docks into. Well-known ids: `'detection'`,
16308
- * `'cluster'`, `'administration'` — the page renders inside that group.
16309
- * Any OTHER string creates (or joins) a custom section rendered after
16310
- * the built-in groups; its label comes from `sectionLabel` (first
16311
- * declaration wins), falling back to the id. Absent → the legacy
16312
- * "Addon Pages" group.
16541
+ * How the tap should REACH the url.
16542
+ *
16543
+ * `navigate` (absent, and every button authored before this field) opens it:
16544
+ * the phone leaves the notification and shows whatever the callback returns.
16545
+ * That is right for a button whose answer the operator wants to read.
16546
+ *
16547
+ * `background` fires it as a POST and stays put. It exists for the buttons
16548
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
16549
+ * an answer to the notification, and being thrown into a browser tab to
16550
+ * confirm it costs more attention than the notification did. A backend that
16551
+ * cannot do a background call renders it as an ordinary link (the adapters
16552
+ * fall back rather than dropping the button), so this is a preference, never
16553
+ * a requirement.
16313
16554
  */
16314
- section: string().optional(),
16315
- /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
16316
- sectionLabel: string().optional()
16555
+ mode: _enum(["navigate", "background"]).optional()
16317
16556
  });
16318
- var addonPagesSourceCapability = {
16319
- name: "addon-pages-source",
16320
- scope: "system",
16321
- mode: "collection",
16322
- internal: true,
16323
- methods: {
16324
- /**
16325
- * `internal: true` did not gate the mount (see the 2026-08-26 note on
16326
- * `data-store-provider`) — this was reachable on the AppRouter by ANY
16327
- * authenticated session at the default `auth: 'protected'`. The only
16328
- * consumer, the `addon-pages-aggregator` builtin, reads it via
16329
- * `ctx.capabilities.getCollection('addon-pages-source')` — LOCAL-PROCESS
16330
- * only, never tRPC (per `ctx.capabilities` seeing only the local
16331
- * process). The public, enriched listing admin-ui actually consumes is
16332
- * the separate `addon-pages` cap, unaffected by this change.
16333
- */
16334
- listPages: method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" }) }
16335
- };
16336
- var AddonHttpRouteSchema = object({
16337
- method: _enum([
16338
- "GET",
16339
- "POST",
16340
- "PUT",
16341
- "DELETE",
16342
- "PATCH"
16557
+ /** A delivery parameter value: what a notifier declared it takes. */
16558
+ var DeliveryParamValueSchema = union([
16559
+ string(),
16560
+ number(),
16561
+ boolean()
16562
+ ]);
16563
+ /**
16564
+ * One extra delivery parameter a notifier KIND declares (D938). A rule that
16565
+ * addresses a target of that kind may set it; a kind that declares nothing is
16566
+ * never handed one. The admin UI derives its control from this descriptor —
16567
+ * no editor knows a service by name.
16568
+ *
16569
+ * Optional fields are `.optional()`, never `.default()` (a Zod default does not
16570
+ * run on the addon cap path).
16571
+ */
16572
+ var DeliveryParamSchema = object({
16573
+ key: string().min(1),
16574
+ label: string(),
16575
+ /** i18n key of the label, resolved by the declaring addon's namespace. */
16576
+ labelKey: string().optional(),
16577
+ type: _enum([
16578
+ "enum",
16579
+ "number",
16580
+ "boolean",
16581
+ "string"
16343
16582
  ]),
16344
- path: string(),
16345
- access: _enum([
16346
- "public",
16347
- "authenticated",
16348
- "admin"
16349
- ]).optional(),
16350
- description: string().optional(),
16351
- /** The hub limits this route per client address before the handler runs. */
16352
- rateLimit: _enum(["auth"]).optional()
16583
+ /** `enum` only: the closed set of accepted values. */
16584
+ options: array(object({
16585
+ value: string(),
16586
+ label: string()
16587
+ })).optional(),
16588
+ /** `number` only. */
16589
+ min: number().optional(),
16590
+ max: number().optional(),
16591
+ /** What the notifier does when the rule sets nothing; shown as the placeholder. */
16592
+ default: DeliveryParamValueSchema.optional()
16353
16593
  });
16354
16594
  /**
16355
- * Cross-process route invocation envelope. The hub captures the
16356
- * request as plain data, ships it to the worker via Moleculer, and
16357
- * the worker runs the local handler against a capturing reply. The
16358
- * envelope returned describes what the handler intended (status,
16359
- * headers, body, or a redirect) so the hub can translate it back to
16360
- * the Fastify reply that's actually wired to the socket.
16595
+ * The canonical notification. `body` is the only hard field (Apprise model).
16596
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
16597
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
16598
+ * the adapter maps this ordinal onto its native level. `level?` is an
16599
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
16600
+ * `priority` for that one target.
16361
16601
  */
16362
- var InvokeRequestSchema = object({
16363
- method: string(),
16364
- path: string(),
16365
- params: record(string(), string()),
16366
- query: record(string(), string()),
16367
- body: unknown(),
16368
- headers: record(string(), string()),
16369
- user: object({
16370
- id: string(),
16371
- username: string(),
16372
- isAdmin: boolean()
16602
+ var NotificationSchema = object({
16603
+ body: string(),
16604
+ title: string().optional(),
16605
+ format: NotificationFormatSchema.default("text"),
16606
+ priority: number().int().min(1).max(5).default(3),
16607
+ level: string().optional(),
16608
+ attachments: array(AttachmentSchema).optional(),
16609
+ clickUrl: string().optional(),
16610
+ actions: array(NotificationActionSchema).optional(),
16611
+ sound: string().optional(),
16612
+ ttl: number().optional(),
16613
+ tag: string().optional(),
16614
+ deviceId: number().optional(),
16615
+ eventId: string().optional(),
16616
+ metadata: record(string(), unknown()).optional(),
16617
+ /**
16618
+ * Values for the parameters the target's kind DECLARES in
16619
+ * {@link TargetKindSchema} `deliveryParams`. Already validated against the
16620
+ * declaration by the sender; a notifier reads only the keys it declared.
16621
+ */
16622
+ delivery: record(string(), DeliveryParamValueSchema).optional()
16623
+ });
16624
+ /** One declared native severity/priority level for a kind. */
16625
+ var TargetKindLevelSchema = object({
16626
+ id: string(),
16627
+ label: string(),
16628
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
16629
+ ordinal: number().int().min(1).max(5).nullable(),
16630
+ flags: object({
16631
+ critical: boolean().optional(),
16632
+ silent: boolean().optional(),
16633
+ noPush: boolean().optional()
16373
16634
  }).optional(),
16374
- scopedToken: unknown().optional()
16635
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
16636
+ requires: array(string()).optional(),
16637
+ description: string().optional()
16375
16638
  });
16376
- var InvokeReplyEnvelopeSchema = object({
16377
- status: number().int(),
16378
- headers: record(string(), string()),
16379
- /** When set, the hub MUST `reply.redirect(redirectUrl)` instead of
16380
- * sending `body`. Status defaults to 302 when this is set unless
16381
- * the handler called `reply.code(...)` explicitly. */
16382
- redirectUrl: string().nullable(),
16383
- /** JSON-serializable body. `undefined` is treated as "no body". */
16384
- body: unknown().optional(),
16385
- /** Set when the handler called `reply.type(mime)`. */
16386
- contentType: string().optional()
16639
+ /** The full capability block consulted before dispatch. */
16640
+ var TargetKindCapsSchema = object({
16641
+ attachments: object({
16642
+ mediaTypes: array(AttachmentMediaTypeSchema),
16643
+ mode: _enum([
16644
+ "url",
16645
+ "bytes",
16646
+ "both"
16647
+ ]),
16648
+ max: number().int().nonnegative(),
16649
+ maxBytes: number().int().positive().optional()
16650
+ }),
16651
+ /** Max action buttons (0 = none). */
16652
+ actions: number().int().nonnegative(),
16653
+ /**
16654
+ * Whether this kind renders a per-action ICON.
16655
+ *
16656
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
16657
+ * run on the addon cap path — three production failures in one day taught
16658
+ * this repo that once. Absent is read as false by the degrade engine, which
16659
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
16660
+ * assumed and dropped costs the operator's trust in the field.
16661
+ */
16662
+ actionIcons: boolean().optional(),
16663
+ levels: array(TargetKindLevelSchema),
16664
+ format: array(NotificationFormatSchema),
16665
+ clickUrl: boolean(),
16666
+ sound: boolean(),
16667
+ ttl: boolean(),
16668
+ bodyMaxLen: number().int().positive()
16387
16669
  });
16388
- method(_void(), array(AddonHttpRouteSchema)), method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" });
16389
- var ConfigTabDeclarationSchema = object({
16390
- id: string(),
16670
+ /**
16671
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
16672
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
16673
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
16674
+ * the union is large and not meant for runtime validation here; the exported
16675
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
16676
+ */
16677
+ var ConfigSchemaPassthrough$3 = unknown();
16678
+ var TargetKindSchema = object({
16679
+ kind: string(),
16391
16680
  label: string(),
16392
16681
  icon: string(),
16393
- order: number().optional()
16394
- });
16395
- var ConfigSectionWithValuesSchema = object({
16396
- id: string(),
16397
- title: string(),
16398
- titleKey: string().optional(),
16682
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
16683
+ addonId: string(),
16684
+ /**
16685
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
16686
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
16687
+ * when the addon bundles no icon for that kind — the client then falls back
16688
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
16689
+ *
16690
+ * Root-relative on purpose: it resolves against whatever origin serves a web
16691
+ * client, and a native client joins it onto its own hub base.
16692
+ *
16693
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
16694
+ * field that survived only because the runtime cap-router forwards provider
16695
+ * output verbatim — so every consumer had to re-declare it by hand to stop
16696
+ * its own Zod parse from stripping it, and the whole arrangement would have
16697
+ * broken silently the moment output validation was tightened anywhere.
16698
+ */
16699
+ iconUrl: string().optional(),
16700
+ /**
16701
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
16702
+ *
16703
+ * The server knows this and therefore says it, because the client cannot
16704
+ * safely guess: a React-Native client renders SVG and raster through two
16705
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
16706
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
16707
+ * placeholder glyph for every vector icon while the web build looked fine.
16708
+ *
16709
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
16710
+ * not been updated — a client that cannot determine the type should prefer
16711
+ * its raster path, which is the safe default for an unknown image.
16712
+ */
16713
+ iconMediaType: string().optional(),
16714
+ configSchema: ConfigSchemaPassthrough$3,
16715
+ /**
16716
+ * The config keys that decide WHERE this kind's secrets are sent (server,
16717
+ * base URL, instance, chat, recipient). A save that changes any of them
16718
+ * while leaving a secret redacted is refused by the provider (D634), so an
16719
+ * editor uses this to ask for the secret on exactly those edits instead of
16720
+ * on every edit.
16721
+ *
16722
+ * Optional: absent from a provider that holds no secret (the Home Assistant
16723
+ * kinds) and from a provider older than this field — an editor that finds
16724
+ * it absent must assume ANY edit may need the secret.
16725
+ */
16726
+ destinationKeys: array(string()).optional(),
16727
+ supportsDiscovery: boolean(),
16728
+ /**
16729
+ * Extra delivery parameters a rule may set for a target of this kind (D938).
16730
+ * Absent = none; the rule editor then offers nothing.
16731
+ */
16732
+ deliveryParams: array(DeliveryParamSchema).optional(),
16733
+ caps: TargetKindCapsSchema
16734
+ });
16735
+ /**
16736
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
16737
+ * (return a presence marker only) when serving `listTargets` — never
16738
+ * round-trip a stored secret to the UI.
16739
+ */
16740
+ var TargetSchema = object({
16741
+ id: string(),
16742
+ name: string(),
16743
+ kind: string(),
16744
+ addonId: string(),
16745
+ enabled: boolean(),
16746
+ config: record(string(), unknown())
16747
+ });
16748
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
16749
+ var DiscoveredTargetSchema = object({
16750
+ kind: string(),
16751
+ suggestedName: string(),
16752
+ config: record(string(), unknown())
16753
+ });
16754
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
16755
+ var RenderedAsSchema = object({
16756
+ level: string(),
16757
+ format: NotificationFormatSchema,
16758
+ attachmentsSent: number().int().nonnegative(),
16759
+ actionsSent: number().int().nonnegative(),
16760
+ truncated: boolean(),
16761
+ dropped: array(string())
16762
+ });
16763
+ var SendResultSchema = object({
16764
+ success: boolean(),
16765
+ error: string().optional(),
16766
+ renderedAs: RenderedAsSchema.optional()
16767
+ });
16768
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
16769
+ var TestResultSchema = SendResultSchema;
16770
+ /**
16771
+ * Pre-save credential probe against a partial (unsaved) target config.
16772
+ * `resolvedUrl` never includes secrets (token stripped from path).
16773
+ */
16774
+ var ProbeTargetConfigResultSchema = object({
16775
+ ok: boolean(),
16776
+ resolvedUrl: string(),
16777
+ httpStatus: number().int().optional(),
16778
+ stateInstance: string().optional(),
16779
+ error: string().optional()
16780
+ });
16781
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
16782
+ kind: string(),
16783
+ config: record(string(), unknown()).optional(),
16784
+ /**
16785
+ * When editing / duplicating, the blank password field never round-trips
16786
+ * the stored token. Pass the source target id so the provider can restore
16787
+ * secrets server-side before dialling (same merge as upsert).
16788
+ */
16789
+ targetId: string().optional()
16790
+ }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
16791
+ kind: string(),
16792
+ config: record(string(), unknown()),
16793
+ /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
16794
+ targetId: string().optional()
16795
+ }), ProbeTargetConfigResultSchema, {
16796
+ kind: "mutation",
16797
+ auth: "admin"
16798
+ }), method(object({
16799
+ targetId: string(),
16800
+ notification: NotificationSchema
16801
+ }), SendResultSchema, {
16802
+ kind: "mutation",
16803
+ auth: "admin"
16804
+ }), method(object({
16805
+ targetId: string(),
16806
+ sample: NotificationSchema.optional()
16807
+ }), TestResultSchema, {
16808
+ kind: "mutation",
16809
+ auth: "admin"
16810
+ }), method(object({
16811
+ target: TargetSchema,
16812
+ /**
16813
+ * Duplicate flow: create a NEW target id while copying secrets from an
16814
+ * existing row. The UI never holds the stored token; blank / redacted
16815
+ * password fields are restored from this source (D634 still applies if
16816
+ * destinations diverge without a re-typed secret).
16817
+ */
16818
+ cloneSecretsFrom: string().optional()
16819
+ }), TargetSchema, {
16820
+ kind: "mutation",
16821
+ auth: "admin"
16822
+ }), method(object({ targetId: string() }), _void(), {
16823
+ kind: "mutation",
16824
+ auth: "admin"
16825
+ }), method(object({
16826
+ targetId: string(),
16827
+ enabled: boolean()
16828
+ }), _void(), {
16829
+ kind: "mutation",
16830
+ auth: "admin"
16831
+ });
16832
+ /**
16833
+ * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
16834
+ * surface that admin-ui consumes through `useAddonPagesListPages()`.
16835
+ *
16836
+ * The provider iterates every `addon-pages-source` (collection) provider
16837
+ * and emits `AddonPageInfo[]` enriched with versioned `bundleUrl` strings
16838
+ * pointing at `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. The
16839
+ * filesystem `mtime` cache-buster lets the browser pick up addon
16840
+ * rebuilds without manual reload.
16841
+ *
16842
+ * The hub-local builtin `addon-pages-aggregator` (see
16843
+ * `@camstack/system/builtins/addon-pages-aggregator`) registers the
16844
+ * provider. Splitting the public aggregator from the raw collection
16845
+ * keeps both ends in codegen — there's no hand-written
16846
+ * `addon-pages.router.ts` wrapper anymore.
16847
+ */
16848
+ var AddonPageDeclarationSchema$1 = object({
16849
+ id: string(),
16850
+ label: string(),
16851
+ icon: string(),
16852
+ path: string(),
16853
+ remoteName: string(),
16854
+ bundle: string(),
16855
+ section: string().optional(),
16856
+ sectionLabel: string().optional()
16857
+ });
16858
+ var AddonPageInfoSchema = object({
16859
+ addonId: string(),
16860
+ page: AddonPageDeclarationSchema$1,
16861
+ bundleUrl: string()
16862
+ });
16863
+ method(_void(), array(AddonPageInfoSchema).readonly());
16864
+ var AddonHttpRouteSchema = object({
16865
+ method: _enum([
16866
+ "GET",
16867
+ "POST",
16868
+ "PUT",
16869
+ "DELETE",
16870
+ "PATCH"
16871
+ ]),
16872
+ path: string(),
16873
+ access: _enum([
16874
+ "public",
16875
+ "authenticated",
16876
+ "admin"
16877
+ ]).optional(),
16878
+ description: string().optional(),
16879
+ /** The hub limits this route per client address before the handler runs. */
16880
+ rateLimit: _enum(["auth"]).optional()
16881
+ });
16882
+ /**
16883
+ * Cross-process route invocation envelope. The hub captures the
16884
+ * request as plain data, ships it to the worker via Moleculer, and
16885
+ * the worker runs the local handler against a capturing reply. The
16886
+ * envelope returned describes what the handler intended (status,
16887
+ * headers, body, or a redirect) so the hub can translate it back to
16888
+ * the Fastify reply that's actually wired to the socket.
16889
+ */
16890
+ var InvokeRequestSchema = object({
16891
+ method: string(),
16892
+ path: string(),
16893
+ params: record(string(), string()),
16894
+ query: record(string(), string()),
16895
+ body: unknown(),
16896
+ headers: record(string(), string()),
16897
+ user: object({
16898
+ id: string(),
16899
+ username: string(),
16900
+ isAdmin: boolean()
16901
+ }).optional(),
16902
+ scopedToken: unknown().optional()
16903
+ });
16904
+ var InvokeReplyEnvelopeSchema = object({
16905
+ status: number().int(),
16906
+ headers: record(string(), string()),
16907
+ /** When set, the hub MUST `reply.redirect(redirectUrl)` instead of
16908
+ * sending `body`. Status defaults to 302 when this is set unless
16909
+ * the handler called `reply.code(...)` explicitly. */
16910
+ redirectUrl: string().nullable(),
16911
+ /** JSON-serializable body. `undefined` is treated as "no body". */
16912
+ body: unknown().optional(),
16913
+ /** Set when the handler called `reply.type(mime)`. */
16914
+ contentType: string().optional()
16915
+ });
16916
+ method(_void(), array(AddonHttpRouteSchema)), method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" });
16917
+ var ConfigTabDeclarationSchema = object({
16918
+ id: string(),
16919
+ label: string(),
16920
+ icon: string(),
16921
+ order: number().optional()
16922
+ });
16923
+ var ConfigSectionWithValuesSchema = object({
16924
+ id: string(),
16925
+ title: string(),
16926
+ titleKey: string().optional(),
16399
16927
  description: string().optional(),
16400
16928
  descriptionKey: string().optional(),
16401
16929
  style: _enum(["card", "accordion"]).optional(),
@@ -18875,7 +19403,11 @@ var CompositionProblemSchema = object({
18875
19403
  "mixed-quantity",
18876
19404
  "helper-mismatch",
18877
19405
  "member-constraint",
18878
- "retention"
19406
+ "retention",
19407
+ "command-method-unknown",
19408
+ "command-source-unknown",
19409
+ "command-input-unknown",
19410
+ "command-input-missing"
18879
19411
  ]),
18880
19412
  path: string(),
18881
19413
  message: string()
@@ -18914,7 +19446,9 @@ var CompositionFieldRoleSchema = _enum([
18914
19446
  var CompositionFieldItemSchema = object({
18915
19447
  arrayPath: string(),
18916
19448
  key: string(),
18917
- label: string()
19449
+ label: string(),
19450
+ /** True when the item carries a `reset` forward: the composer writes `resettable` from this (D967). */
19451
+ resettable: boolean().optional()
18918
19452
  });
18919
19453
  var CompositionFieldPlanSchema = object({
18920
19454
  cap: string(),
@@ -19119,7 +19653,7 @@ Object.freeze({
19119
19653
  * One catalog row per base reading a group helper can build (D944 §3).
19120
19654
  * Data, not a form default written into the block.
19121
19655
  */
19122
- var NOW$2 = {
19656
+ var NOW$3 = {
19123
19657
  fieldPath: "lastFetchedAt",
19124
19658
  expr: "now"
19125
19659
  };
@@ -19171,7 +19705,7 @@ var GROUP_TEMPLATES = [
19171
19705
  quantity: "temperature",
19172
19706
  defaultAggregate: "mean",
19173
19707
  empty: "null",
19174
- companions: [NOW$2],
19708
+ companions: [NOW$3],
19175
19709
  alsoFrom: {
19176
19710
  deviceTypes: CLIMATE_DEVICES,
19177
19711
  accept: {
@@ -19189,7 +19723,7 @@ var GROUP_TEMPLATES = [
19189
19723
  quantity: "humidity",
19190
19724
  defaultAggregate: "mean",
19191
19725
  empty: "null",
19192
- companions: [NOW$2],
19726
+ companions: [NOW$3],
19193
19727
  alsoFrom: {
19194
19728
  deviceTypes: CLIMATE_DEVICES,
19195
19729
  accept: {
@@ -19207,7 +19741,7 @@ var GROUP_TEMPLATES = [
19207
19741
  quantity: "pressure",
19208
19742
  defaultAggregate: "mean",
19209
19743
  empty: "null",
19210
- companions: [NOW$2]
19744
+ companions: [NOW$3]
19211
19745
  }),
19212
19746
  numeric({
19213
19747
  id: "illuminance",
@@ -19218,7 +19752,7 @@ var GROUP_TEMPLATES = [
19218
19752
  quantity: "illuminance",
19219
19753
  defaultAggregate: "mean",
19220
19754
  empty: "null",
19221
- companions: [NOW$2]
19755
+ companions: [NOW$3]
19222
19756
  }),
19223
19757
  numeric({
19224
19758
  id: "power",
@@ -19229,7 +19763,7 @@ var GROUP_TEMPLATES = [
19229
19763
  quantity: "power",
19230
19764
  defaultAggregate: "sum",
19231
19765
  empty: "omit",
19232
- companions: [NOW$2]
19766
+ companions: [NOW$3]
19233
19767
  }),
19234
19768
  numeric({
19235
19769
  id: "energy",
@@ -19240,7 +19774,7 @@ var GROUP_TEMPLATES = [
19240
19774
  quantity: "energy",
19241
19775
  defaultAggregate: "sum",
19242
19776
  empty: "omit",
19243
- companions: [NOW$2]
19777
+ companions: [NOW$3]
19244
19778
  }),
19245
19779
  numeric({
19246
19780
  id: "battery",
@@ -19275,7 +19809,7 @@ var GROUP_TEMPLATES = [
19275
19809
  quantity: "device",
19276
19810
  defaultAggregate: "mean",
19277
19811
  empty: "null",
19278
- companions: [NOW$2]
19812
+ companions: [NOW$3]
19279
19813
  }),
19280
19814
  binary({
19281
19815
  id: "motion",
@@ -21621,12 +22155,48 @@ var ExposeInputSchema = object({
21621
22155
  capabilities: array(string()).optional()
21622
22156
  });
21623
22157
  var UnexposeInputSchema = object({ deviceId: string() });
22158
+ /**
22159
+ * What an exporter may expose besides devices (D968). An action and a
22160
+ * notification target are LIST ENTRIES, never devices (D954): they enter an
22161
+ * exporter's own selection, not the device registry.
22162
+ */
22163
+ var ExportEntityKindSchema = _enum([
22164
+ "device",
22165
+ "action",
22166
+ "notifier"
22167
+ ]);
22168
+ var ExportEntityRefSchema = discriminatedUnion("kind", [
22169
+ object({
22170
+ kind: literal("device"),
22171
+ deviceId: string()
22172
+ }),
22173
+ object({
22174
+ kind: literal("action"),
22175
+ actionId: string().min(1)
22176
+ }),
22177
+ object({
22178
+ kind: literal("notifier"),
22179
+ targetId: string().min(1)
22180
+ })
22181
+ ]);
22182
+ var ExposedEntitySchema = object({
22183
+ entity: ExportEntityRefSchema,
22184
+ exposedAs: string().optional()
22185
+ });
22186
+ var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
22187
+ var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
21624
22188
  method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
21625
22189
  kind: "mutation",
21626
22190
  auth: "admin"
21627
22191
  }), method(UnexposeInputSchema, _void(), {
21628
22192
  kind: "mutation",
21629
22193
  auth: "admin"
22194
+ }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
22195
+ kind: "mutation",
22196
+ auth: "admin"
22197
+ }), optionalMethod(UnexposeEntityInputSchema, _void(), {
22198
+ kind: "mutation",
22199
+ auth: "admin"
21630
22200
  });
21631
22201
  /** Lucide category ids, alphabetical. `ICON_META` indexes into this. */
21632
22202
  var ICON_CATEGORY_IDS = [
@@ -42767,6 +43337,14 @@ method(object({
42767
43337
  kind: "mutation",
42768
43338
  auth: "admin"
42769
43339
  });
43340
+ method(object({
43341
+ nodeId: string().min(1),
43342
+ /** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
43343
+ removedAt: number()
43344
+ }), object({ counts: record(string(), number().int().nonnegative()) }), {
43345
+ kind: "mutation",
43346
+ auth: "admin"
43347
+ });
42770
43348
  /**
42771
43349
  * `device-removal-hook` — the reliable way an addon learns a device was DELETED,
42772
43350
  * so it can delete what it keeps for that device (D865).
@@ -43487,7 +44065,7 @@ var LlmProviderWidgetSchema = object({
43487
44065
  /** ConfigUISchema tree passed through untyped on the wire (the
43488
44066
  * notification-output `ConfigSchemaPassthrough` precedent at
43489
44067
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
43490
- var ConfigSchemaPassthrough$3 = unknown();
44068
+ var ConfigSchemaPassthrough$2 = unknown();
43491
44069
  var LlmProfileKindDescriptorSchema = object({
43492
44070
  kind: LlmProfileKindSchema,
43493
44071
  label: string(),
@@ -43496,9 +44074,9 @@ var LlmProfileKindDescriptorSchema = object({
43496
44074
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
43497
44075
  addonId: string(),
43498
44076
  /** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
43499
- connectionSchema: ConfigSchemaPassthrough$3,
44077
+ connectionSchema: ConfigSchemaPassthrough$2,
43500
44078
  /** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
43501
- advancedSchema: ConfigSchemaPassthrough$3.optional(),
44079
+ advancedSchema: ConfigSchemaPassthrough$2.optional(),
43502
44080
  /** The kind's defaults; a model from `listModels` may override any of them. */
43503
44081
  features: LlmModelFeaturesSchema,
43504
44082
  /** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
@@ -44024,10 +44602,10 @@ var CpuBreakdownSchema = object({
44024
44602
  cores: number()
44025
44603
  });
44026
44604
  var MemoryInfoSchema = object({
44027
- percent: number(),
44605
+ percent: number().nullable(),
44028
44606
  totalBytes: number(),
44029
- usedBytes: number(),
44030
- availableBytes: number(),
44607
+ usedBytes: number().nullable(),
44608
+ availableBytes: number().nullable(),
44031
44609
  swapUsedBytes: number(),
44032
44610
  swapTotalBytes: number()
44033
44611
  });
@@ -44297,698 +44875,278 @@ var NodeLoadSeriesSchema = object({
44297
44875
  /**
44298
44876
  * Did the DURABLE tier contribute? `false` means the answer is the hot ring
44299
44877
  * alone — an agent (which holds no table), or a store that refused.
44300
- * Reported because "the last hour" and "the last six hours" are different
44301
- * questions and an operator must not have to guess which was answered.
44302
- */
44303
- durable: boolean()
44304
- });
44305
- var GetLoadSeriesInputSchema = object({
44306
- /**
44307
- * The node whose series is wanted.
44308
- *
44309
- * NOT named `nodeId`: the generated cap router strips a top-level
44310
- * `nodeId` from every method input and uses it to ROUTE the call to
44311
- * that node's provider (`generated-cap-routers.ts`). A series target
44312
- * called `nodeId` would silently become a routing pin and never reach
44313
- * the provider. The hub holds every node it hears from, so the
44314
- * ordinary call is unpinned — answered by the hub, for any node.
44315
- */
44316
- forNodeId: string(),
44317
- /**
44318
- * EXCLUSIVE lower bound. A caller passes the newest `atMs` it already
44319
- * holds and receives only what it is missing, so seeding a live chart
44320
- * from this method cannot double a point already drawn.
44321
- */
44322
- sinceMs: number().optional(),
44323
- /**
44324
- * Most points the caller wants PER FUNCTION. The window is reduced to fit,
44325
- * preserving min and max per bucket.
44326
- *
44327
- * Absent means NO reduction — legitimate for a short window and a trap for a
44328
- * long one, which is why a chart passes its own pixel width.
44329
- */
44330
- maxPoints: number().int().positive().optional()
44331
- });
44332
- var SystemMetricsSchema = object({
44333
- cpuPercent: number(),
44334
- memoryPercent: number(),
44335
- memoryUsedMB: number(),
44336
- memoryTotalMB: number(),
44337
- diskPercent: number().optional(),
44338
- temperature: number().optional(),
44339
- gpuPercent: number().optional(),
44340
- gpuMemoryPercent: number().optional()
44341
- });
44342
- method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema);
44343
- method(object({
44344
- sourceUrl: string(),
44345
- metadata: ModelConvertMetadataSchema,
44346
- targets: array(ConvertTargetSchema).min(1).readonly(),
44347
- calibrationRef: string().optional(),
44348
- sessionId: string().optional()
44349
- }), ConvertResultSchema, {
44350
- kind: "mutation",
44351
- auth: "admin",
44352
- timeoutMs: 6e5
44353
- });
44354
- method(object({
44355
- targetNodeId: string(),
44356
- modelId: string(),
44357
- format: _enum(MODEL_FORMATS),
44358
- entry: ModelCatalogEntrySchema
44359
- }), object({
44360
- ok: boolean(),
44361
- /** sha256 of the staged tarball (empty for a hub-local no-op). */
44362
- sha256: string(),
44363
- bytes: number(),
44364
- /** The target node's modelsDir the artifact landed in. */
44365
- path: string()
44366
- }), {
44367
- kind: "mutation",
44368
- auth: "admin"
44369
- });
44370
- var MotionRegionSchema = object({
44371
- bbox: BoundingBoxSchema,
44372
- pixelCount: number(),
44373
- intensity: number()
44374
- });
44375
- var MotionAnalysisResultSchema = object({
44376
- detected: boolean(),
44377
- regionCount: number(),
44378
- /** Regions passing minArea filter — used for pipeline triggering. */
44379
- regions: array(MotionRegionSchema).readonly(),
44380
- /** All raw regions from CCL before minArea filter — used for UI debug overlay. */
44381
- rawRegions: array(MotionRegionSchema).readonly(),
44382
- frameWidth: number(),
44383
- frameHeight: number(),
44384
- analysisMs: number()
44385
- });
44386
- DeviceType.Camera, method(object({
44387
- deviceId: number(),
44388
- frame: FrameInputSchema.optional(),
44389
- frameHandle: FrameHandleSchema.optional()
44390
- }), MotionAnalysisResultSchema, {
44391
- role: "admin",
44392
- kind: "mutation"
44393
- }), method(object({ deviceId: number() }), _void(), {
44394
- role: "admin",
44395
- kind: "mutation"
44396
- }), method(_void(), _void(), {
44397
- role: "admin",
44398
- kind: "mutation"
44399
- }), object({
44400
- deviceId: number(),
44401
- detected: boolean(),
44402
- regionCount: number()
44403
- });
44404
- /**
44405
- * `mqtt-broker` — broker-registry cap.
44406
- *
44407
- * NOT a pub/sub proxy. The cap exposes (a) a registry of configured
44408
- * MQTT brokers (external + optionally an embedded `aedes`-backed one)
44409
- * and (b) the connection details a consumer addon needs to spin up
44410
- * its OWN `mqtt.js` client.
44411
- *
44412
- * Why: pub/sub routing over the system event-bus loses fidelity
44413
- * (callback shape, QoS guarantees, will/retain semantics) and adds
44414
- * refcount bookkeeping that addons would rather own themselves. The
44415
- * canonical consumer needs raw `mqtt.js`
44416
- * features anyway — give it the connection config, get out of the way.
44417
- *
44418
- * Consumer flow:
44419
- * const cfg = await ctx.api.mqttBroker.getBrokerConfig({ id })
44420
- * const client = mqtt.connect(cfg.url, { username: cfg.username, … })
44421
- * client.subscribe('zigbee2mqtt/+')
44422
- *
44423
- * Collection mode: multiple brokers (e.g. one local mosquitto + one
44424
- * cloud bridge). The "embedded" entry (when present) is just another
44425
- * broker in the registry — its lifecycle is owned by the addon that
44426
- * spawned it.
44427
- */
44428
- var BrokerKindSchema = _enum(["external", "embedded"]);
44429
- /**
44430
- * Broker live-probe status.
44431
- *
44432
- * - `connected` — last probe completed a clean CONNACK
44433
- * - `disconnected` — no probe has run yet (cold cache)
44434
- * - `auth-failed` — CONNACK refused with auth error (RC 4 / 5)
44435
- * - `unreachable` — TCP connect timed out / refused
44436
- * - `tls-error` — TLS handshake failed (cert / SNI / cipher)
44437
- */
44438
- var BrokerStatusSchema$1 = _enum([
44439
- "connected",
44440
- "disconnected",
44441
- "auth-failed",
44442
- "unreachable",
44443
- "tls-error"
44444
- ]);
44445
- var BrokerInfoSchema$1 = object({
44446
- id: string(),
44447
- name: string(),
44448
- url: string(),
44449
- kind: BrokerKindSchema,
44450
- status: BrokerStatusSchema$1,
44451
- latencyMs: number().nullable(),
44452
- error: string().optional(),
44453
- /** Embedded brokers only: number of MQTT clients currently connected. */
44454
- connectedClients: number().int().nonnegative().optional(),
44455
- /** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
44456
- lastCheckedAt: number().optional()
44457
- });
44458
- /**
44459
- * Connection details — what a consumer needs to call
44460
- * `mqtt.connect(url, options)`. We split URL + credentials so the
44461
- * consumer can pass them as `mqtt.connect(url, { username, password })`
44462
- * instead of stuffing creds into the URL (which leaks them into logs).
44463
- */
44464
- var BrokerConnectionDetailsSchema = object({
44465
- url: string(),
44466
- username: string().optional(),
44467
- password: string().optional(),
44468
- /**
44469
- * Suggested prefix for `clientId`. Each consumer should suffix this
44470
- * with its own discriminator (addon id, instance id) so reconnects
44471
- * don't kick each other off (MQTT spec: clientId must be unique per
44472
- * broker).
44473
- */
44474
- clientIdPrefix: string().optional()
44475
- });
44476
- var AddBrokerInputSchema = object({
44477
- name: string().min(1),
44478
- url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
44479
- username: string().optional(),
44480
- password: string().optional(),
44481
- clientIdPrefix: string().optional()
44482
- });
44483
- var AddBrokerResultSchema = object({ id: string() });
44484
- var IdInputSchema = object({ id: string() });
44485
- var TestResultSchema$1 = discriminatedUnion("ok", [object({
44486
- ok: literal(true),
44487
- latencyMs: number()
44488
- }), object({
44489
- ok: literal(false),
44490
- error: string()
44491
- })]);
44492
- var StartEmbeddedInputSchema = object({
44493
- port: number().int().min(1).max(65535).default(1883),
44494
- /** Allow anonymous connect (no username/password). Default: false. */
44495
- allowAnonymous: boolean().default(false),
44496
- /** Optional shared username/password for clients. */
44497
- username: string().optional(),
44498
- password: string().optional()
44499
- });
44500
- var StartEmbeddedResultSchema = object({
44501
- id: string(),
44502
- url: string()
44503
- });
44504
- var StatusSchema = object({
44505
- brokerCount: number(),
44506
- embeddedRunning: boolean()
44507
- });
44508
- method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
44509
- kind: "mutation",
44510
- auth: "admin"
44511
- }), method(IdInputSchema, _void(), {
44512
- kind: "mutation",
44513
- auth: "admin"
44514
- }), method(IdInputSchema, TestResultSchema$1, {
44515
- kind: "mutation",
44516
- auth: "admin"
44517
- }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
44518
- kind: "mutation",
44519
- auth: "admin"
44520
- }), method(IdInputSchema, _void(), {
44521
- kind: "mutation",
44522
- auth: "admin"
44523
- }), method(_void(), StatusSchema);
44524
- var NetworkEndpointSchema = object({
44525
- url: string(),
44526
- hostname: string(),
44527
- port: number(),
44528
- protocol: _enum(["http", "https"]),
44529
- /**
44530
- * Whether this address survives a restart of its provider. `false` = it is
44531
- * re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
44532
- * hub never selects such an address AUTOMATICALLY for a role (D860), though
44533
- * it stays visible and an operator may still flag it. Absent = stable: every
44534
- * provider except a Quick tunnel owns a name that does not change.
44535
- */
44536
- stable: boolean().optional()
44537
- });
44538
- object({
44539
- url: string(),
44540
- /** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
44541
- stable: boolean().optional(),
44542
- updated: boolean().optional()
44543
- });
44544
- var NetworkAccessStatusSchema = object({
44545
- connected: boolean(),
44546
- endpoint: NetworkEndpointSchema.nullable(),
44547
- error: string().optional()
44548
- });
44549
- /**
44550
- * Optional, richer endpoint shape returned by providers that expose
44551
- * MORE than one ingress concurrently (Tailscale Funnel with mixed
44552
- * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
44553
- * the originating provider config (mode + sourcePort) so the
44554
- * orchestrator UI can label rows distinctly. Providers that expose only
44555
- * one endpoint just omit `listEndpoints` from their provider impl.
44556
- */
44557
- var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
44558
- /**
44559
- * Stable id within the provider — typically `<mode>-<sourcePort>` so
44560
- * the orchestrator can dedupe across `listEndpoints` polls.
44561
- */
44562
- id: string(),
44563
- /** Operator-facing label (mirrors `MeshEndpoint.label`). */
44564
- label: string(),
44565
- /** Optional provider-specific mode tag, used for icon/colour in admin UI. */
44566
- mode: string().optional(),
44567
- /** Originating local port the ingress fronts (informational). */
44568
- sourcePort: number().optional()
44569
- });
44570
- method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
44571
- /**
44572
- * notification-output — canonical, capability-gated notification delivery.
44573
- *
44574
- * Apprise-derived model (see
44575
- * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
44576
- * callers emit ONE canonical `Notification`; each provider declares a
44577
- * per-kind capability descriptor (`TargetKind`), and the pure degrade
44578
- * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
44579
- * message to what the kind supports — callers never special-case a service.
44580
- *
44581
- * DESIGN DECISIONS (locked):
44582
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
44583
- * `setTargetEnabled`), each provider persisting via the `settings-store`
44584
- * cap. Rationale: the admin UI needs one uniform surface across the
44585
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
44586
- * alternative would fork the UI per addon and cannot host the
44587
- * discovery→adopt flow.
44588
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
44589
- * the generated cap-mount auto-`concatCollection`-fans them across every
44590
- * registered provider (notifiers addon + HA addon) so one catalog is
44591
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
44592
- * `addonId` the generated collection router extracts from the call input.
44593
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
44594
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
44595
- * `storage` / `storage-provider` / `recording` caps over the same path. No
44596
- * base64 fallback needed.
44597
- *
44598
- * TODO (deferred, closed-set change — separate decision): add
44599
- * `providerKind: 'notify'` so notification providers surface on the unified
44600
- * admin "Integrations" page.
44601
- */
44602
- /**
44603
- * Zentik-derived typed-media enum — the superset across every kind. Each
44604
- * adapter picks what it supports and the degrade engine filters the rest.
44605
- */
44606
- var AttachmentMediaTypeSchema = _enum([
44607
- "image",
44608
- "video",
44609
- "gif",
44610
- "audio",
44611
- "icon"
44612
- ]);
44613
- /**
44614
- * A single attachment. Exactly one of `url` (remote source, most adapters
44615
- * prefer this) or `bytes` (inline source; required for Pushover-style
44616
- * bytes-only kinds) MUST be present — the degrade engine expresses a
44617
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
44618
- */
44619
- var AttachmentSchema = object({
44620
- mediaType: AttachmentMediaTypeSchema,
44621
- url: string().optional(),
44622
- bytes: _instanceof(Uint8Array).optional(),
44623
- mime: string().optional(),
44624
- name: string().optional()
44625
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
44626
- var NotificationFormatSchema = _enum([
44627
- "text",
44628
- "markdown",
44629
- "html"
44630
- ]);
44631
- /**
44632
- * The CLOSED icon vocabulary an action button may use.
44633
- *
44634
- * A closed set, not a free string, and that is the whole point: an arbitrary
44635
- * icon name is one that ntfy renders, zentik silently drops, and nobody
44636
- * notices — the same class of gap as a zone vocabulary nothing produced
44637
- * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
44638
- * declares `actionIcons: false` and the degrade engine strips the field.
44639
- *
44640
- * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
44641
- * renderer's icon set; "acknowledge" survives an adapter that draws it
44642
- * differently.
44643
- *
44644
- * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
44645
- *
44646
- * These names are for US. **No adapter may forward one verbatim.** Each maps
44647
- * the whole set onto its own renderer's vocabulary through a
44648
- * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
44649
- * fallback, so adding a member here fails every adapter's build until someone
44650
- * decides its glyph, which is the only place that decision can be made
44651
- * honestly.
44652
- *
44653
- * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
44654
- * `disarm` straight through; iOS feeds that string to
44655
- * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
44656
- * and every snooze and alarm button arrived BLANK. A pass-through is not a
44657
- * mapping, and "the field is documented" is not "the value renders".
44658
- *
44659
- * Adding a member is TRAIN-BOUND. The enum lives in the published
44660
- * `@camstack/server` closure and the cap seam validates against the HUB's copy,
44661
- * so an addon that emits a token the running hub does not know does not lose an
44662
- * icon — its whole `send` fails Zod validation and the notification never
44663
- * arrives. Never emit a new token from an addon before the train carrying it.
44664
- */
44665
- var NotificationActionIconSchema = _enum([
44666
- "acknowledge",
44667
- "dismiss",
44668
- "silence",
44669
- "snooze",
44670
- "view",
44671
- "play",
44672
- "open",
44673
- "close",
44674
- "lock",
44675
- "unlock",
44676
- "arm",
44677
- "arm-home",
44678
- "arm-away",
44679
- "arm-night",
44680
- "disarm",
44681
- "light",
44682
- "alert",
44683
- "camera"
44684
- ]);
44685
- /** A single tap-through action button. */
44686
- var NotificationActionSchema = object({
44687
- id: string(),
44688
- label: string(),
44689
- url: string().optional(),
44690
- /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
44691
- icon: NotificationActionIconSchema.optional(),
44692
- /**
44693
- * Renders in a warning style where the notifier supports it.
44694
- *
44695
- * A HINT, never a gate. The callback's authority is its token and nothing
44696
- * else — see `notification-center/action-token.ts` for what that does and
44697
- * does not buy.
44698
- */
44699
- destructive: boolean().optional(),
44700
- /**
44701
- * How the tap should REACH the url.
44702
- *
44703
- * `navigate` (absent, and every button authored before this field) opens it:
44704
- * the phone leaves the notification and shows whatever the callback returns.
44705
- * That is right for a button whose answer the operator wants to read.
44706
- *
44707
- * `background` fires it as a POST and stays put. It exists for the buttons
44708
- * whose whole point is not to interrupt — "silence this for 30 minutes" is
44709
- * an answer to the notification, and being thrown into a browser tab to
44710
- * confirm it costs more attention than the notification did. A backend that
44711
- * cannot do a background call renders it as an ordinary link (the adapters
44712
- * fall back rather than dropping the button), so this is a preference, never
44713
- * a requirement.
44714
- */
44715
- mode: _enum(["navigate", "background"]).optional()
44716
- });
44717
- /** A delivery parameter value: what a notifier declared it takes. */
44718
- var DeliveryParamValueSchema = union([
44719
- string(),
44720
- number(),
44721
- boolean()
44722
- ]);
44723
- /**
44724
- * One extra delivery parameter a notifier KIND declares (D938). A rule that
44725
- * addresses a target of that kind may set it; a kind that declares nothing is
44726
- * never handed one. The admin UI derives its control from this descriptor —
44727
- * no editor knows a service by name.
44728
- *
44729
- * Optional fields are `.optional()`, never `.default()` (a Zod default does not
44730
- * run on the addon cap path).
44731
- */
44732
- var DeliveryParamSchema = object({
44733
- key: string().min(1),
44734
- label: string(),
44735
- /** i18n key of the label, resolved by the declaring addon's namespace. */
44736
- labelKey: string().optional(),
44737
- type: _enum([
44738
- "enum",
44739
- "number",
44740
- "boolean",
44741
- "string"
44742
- ]),
44743
- /** `enum` only: the closed set of accepted values. */
44744
- options: array(object({
44745
- value: string(),
44746
- label: string()
44747
- })).optional(),
44748
- /** `number` only. */
44749
- min: number().optional(),
44750
- max: number().optional(),
44751
- /** What the notifier does when the rule sets nothing; shown as the placeholder. */
44752
- default: DeliveryParamValueSchema.optional()
44753
- });
44754
- /**
44755
- * The canonical notification. `body` is the only hard field (Apprise model).
44756
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
44757
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
44758
- * the adapter maps this ordinal onto its native level. `level?` is an
44759
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
44760
- * `priority` for that one target.
44761
- */
44762
- var NotificationSchema = object({
44763
- body: string(),
44764
- title: string().optional(),
44765
- format: NotificationFormatSchema.default("text"),
44766
- priority: number().int().min(1).max(5).default(3),
44767
- level: string().optional(),
44768
- attachments: array(AttachmentSchema).optional(),
44769
- clickUrl: string().optional(),
44770
- actions: array(NotificationActionSchema).optional(),
44771
- sound: string().optional(),
44772
- ttl: number().optional(),
44773
- tag: string().optional(),
44774
- deviceId: number().optional(),
44775
- eventId: string().optional(),
44776
- metadata: record(string(), unknown()).optional(),
44777
- /**
44778
- * Values for the parameters the target's kind DECLARES in
44779
- * {@link TargetKindSchema} `deliveryParams`. Already validated against the
44780
- * declaration by the sender; a notifier reads only the keys it declared.
44781
- */
44782
- delivery: record(string(), DeliveryParamValueSchema).optional()
44783
- });
44784
- /** One declared native severity/priority level for a kind. */
44785
- var TargetKindLevelSchema = object({
44786
- id: string(),
44787
- label: string(),
44788
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
44789
- ordinal: number().int().min(1).max(5).nullable(),
44790
- flags: object({
44791
- critical: boolean().optional(),
44792
- silent: boolean().optional(),
44793
- noPush: boolean().optional()
44794
- }).optional(),
44795
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
44796
- requires: array(string()).optional(),
44797
- description: string().optional()
44798
- });
44799
- /** The full capability block consulted before dispatch. */
44800
- var TargetKindCapsSchema = object({
44801
- attachments: object({
44802
- mediaTypes: array(AttachmentMediaTypeSchema),
44803
- mode: _enum([
44804
- "url",
44805
- "bytes",
44806
- "both"
44807
- ]),
44808
- max: number().int().nonnegative(),
44809
- maxBytes: number().int().positive().optional()
44810
- }),
44811
- /** Max action buttons (0 = none). */
44812
- actions: number().int().nonnegative(),
44813
- /**
44814
- * Whether this kind renders a per-action ICON.
44815
- *
44816
- * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
44817
- * run on the addon cap path — three production failures in one day taught
44818
- * this repo that once. Absent is read as false by the degrade engine, which
44819
- * is the safe direction: an icon that is not rendered costs nothing, an icon
44820
- * assumed and dropped costs the operator's trust in the field.
44821
- */
44822
- actionIcons: boolean().optional(),
44823
- levels: array(TargetKindLevelSchema),
44824
- format: array(NotificationFormatSchema),
44825
- clickUrl: boolean(),
44826
- sound: boolean(),
44827
- ttl: boolean(),
44828
- bodyMaxLen: number().int().positive()
44829
- });
44830
- /**
44831
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
44832
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
44833
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
44834
- * the union is large and not meant for runtime validation here; the exported
44835
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
44836
- */
44837
- var ConfigSchemaPassthrough$2 = unknown();
44838
- var TargetKindSchema = object({
44839
- kind: string(),
44840
- label: string(),
44841
- icon: string(),
44842
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
44843
- addonId: string(),
44844
- /**
44845
- * URL of the kind's bundled BRAND icon, served by the providing addon over
44846
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
44847
- * when the addon bundles no icon for that kind — the client then falls back
44848
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
44849
- *
44850
- * Root-relative on purpose: it resolves against whatever origin serves a web
44851
- * client, and a native client joins it onto its own hub base.
44852
- *
44853
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
44854
- * field that survived only because the runtime cap-router forwards provider
44855
- * output verbatim — so every consumer had to re-declare it by hand to stop
44856
- * its own Zod parse from stripping it, and the whole arrangement would have
44857
- * broken silently the moment output validation was tightened anywhere.
44858
- */
44859
- iconUrl: string().optional(),
44860
- /**
44861
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
44862
- *
44863
- * The server knows this and therefore says it, because the client cannot
44864
- * safely guess: a React-Native client renders SVG and raster through two
44865
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
44866
- * not decode SVG on iOS/Android), so without this it silently fell back to a
44867
- * placeholder glyph for every vector icon while the web build looked fine.
44868
- *
44869
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
44870
- * not been updated — a client that cannot determine the type should prefer
44871
- * its raster path, which is the safe default for an unknown image.
44878
+ * Reported because "the last hour" and "the last six hours" are different
44879
+ * questions and an operator must not have to guess which was answered.
44872
44880
  */
44873
- iconMediaType: string().optional(),
44874
- configSchema: ConfigSchemaPassthrough$2,
44881
+ durable: boolean()
44882
+ });
44883
+ var GetLoadSeriesInputSchema = object({
44875
44884
  /**
44876
- * The config keys that decide WHERE this kind's secrets are sent (server,
44877
- * base URL, instance, chat, recipient). A save that changes any of them
44878
- * while leaving a secret redacted is refused by the provider (D634), so an
44879
- * editor uses this to ask for the secret on exactly those edits instead of
44880
- * on every edit.
44885
+ * The node whose series is wanted.
44881
44886
  *
44882
- * Optional: absent from a provider that holds no secret (the Home Assistant
44883
- * kinds) and from a provider older than this field — an editor that finds
44884
- * it absent must assume ANY edit may need the secret.
44887
+ * NOT named `nodeId`: the generated cap router strips a top-level
44888
+ * `nodeId` from every method input and uses it to ROUTE the call to
44889
+ * that node's provider (`generated-cap-routers.ts`). A series target
44890
+ * called `nodeId` would silently become a routing pin and never reach
44891
+ * the provider. The hub holds every node it hears from, so the
44892
+ * ordinary call is unpinned — answered by the hub, for any node.
44885
44893
  */
44886
- destinationKeys: array(string()).optional(),
44887
- supportsDiscovery: boolean(),
44894
+ forNodeId: string(),
44888
44895
  /**
44889
- * Extra delivery parameters a rule may set for a target of this kind (D938).
44890
- * Absent = none; the rule editor then offers nothing.
44896
+ * EXCLUSIVE lower bound. A caller passes the newest `atMs` it already
44897
+ * holds and receives only what it is missing, so seeding a live chart
44898
+ * from this method cannot double a point already drawn.
44891
44899
  */
44892
- deliveryParams: array(DeliveryParamSchema).optional(),
44893
- caps: TargetKindCapsSchema
44900
+ sinceMs: number().optional(),
44901
+ /**
44902
+ * Most points the caller wants PER FUNCTION. The window is reduced to fit,
44903
+ * preserving min and max per bucket.
44904
+ *
44905
+ * Absent means NO reduction — legitimate for a short window and a trap for a
44906
+ * long one, which is why a chart passes its own pixel width.
44907
+ */
44908
+ maxPoints: number().int().positive().optional()
44909
+ });
44910
+ var SystemMetricsSchema = object({
44911
+ cpuPercent: number(),
44912
+ /** `null` when the memory sample failed or was not taken yet (D393). */
44913
+ memoryPercent: number().nullable(),
44914
+ memoryUsedMB: number().nullable(),
44915
+ memoryTotalMB: number(),
44916
+ diskPercent: number().optional(),
44917
+ temperature: number().optional(),
44918
+ gpuPercent: number().optional(),
44919
+ gpuMemoryPercent: number().optional()
44920
+ });
44921
+ method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema);
44922
+ method(object({
44923
+ sourceUrl: string(),
44924
+ metadata: ModelConvertMetadataSchema,
44925
+ targets: array(ConvertTargetSchema).min(1).readonly(),
44926
+ calibrationRef: string().optional(),
44927
+ sessionId: string().optional()
44928
+ }), ConvertResultSchema, {
44929
+ kind: "mutation",
44930
+ auth: "admin",
44931
+ timeoutMs: 6e5
44932
+ });
44933
+ method(object({
44934
+ targetNodeId: string(),
44935
+ modelId: string(),
44936
+ format: _enum(MODEL_FORMATS),
44937
+ entry: ModelCatalogEntrySchema
44938
+ }), object({
44939
+ ok: boolean(),
44940
+ /** sha256 of the staged tarball (empty for a hub-local no-op). */
44941
+ sha256: string(),
44942
+ bytes: number(),
44943
+ /** The target node's modelsDir the artifact landed in. */
44944
+ path: string()
44945
+ }), {
44946
+ kind: "mutation",
44947
+ auth: "admin"
44948
+ });
44949
+ var MotionRegionSchema = object({
44950
+ bbox: BoundingBoxSchema,
44951
+ pixelCount: number(),
44952
+ intensity: number()
44953
+ });
44954
+ var MotionAnalysisResultSchema = object({
44955
+ detected: boolean(),
44956
+ regionCount: number(),
44957
+ /** Regions passing minArea filter — used for pipeline triggering. */
44958
+ regions: array(MotionRegionSchema).readonly(),
44959
+ /** All raw regions from CCL before minArea filter — used for UI debug overlay. */
44960
+ rawRegions: array(MotionRegionSchema).readonly(),
44961
+ frameWidth: number(),
44962
+ frameHeight: number(),
44963
+ analysisMs: number()
44964
+ });
44965
+ DeviceType.Camera, method(object({
44966
+ deviceId: number(),
44967
+ frame: FrameInputSchema.optional(),
44968
+ frameHandle: FrameHandleSchema.optional()
44969
+ }), MotionAnalysisResultSchema, {
44970
+ role: "admin",
44971
+ kind: "mutation"
44972
+ }), method(object({ deviceId: number() }), _void(), {
44973
+ role: "admin",
44974
+ kind: "mutation"
44975
+ }), method(_void(), _void(), {
44976
+ role: "admin",
44977
+ kind: "mutation"
44978
+ }), object({
44979
+ deviceId: number(),
44980
+ detected: boolean(),
44981
+ regionCount: number()
44894
44982
  });
44895
44983
  /**
44896
- * A persisted target. `config` holds secrets; providers REDACT secret fields
44897
- * (return a presence marker only) when serving `listTargets` — never
44898
- * round-trip a stored secret to the UI.
44984
+ * `mqtt-broker` — broker-registry cap.
44985
+ *
44986
+ * NOT a pub/sub proxy. The cap exposes (a) a registry of configured
44987
+ * MQTT brokers (external + optionally an embedded `aedes`-backed one)
44988
+ * and (b) the connection details a consumer addon needs to spin up
44989
+ * its OWN `mqtt.js` client.
44990
+ *
44991
+ * Why: pub/sub routing over the system event-bus loses fidelity
44992
+ * (callback shape, QoS guarantees, will/retain semantics) and adds
44993
+ * refcount bookkeeping that addons would rather own themselves. The
44994
+ * canonical consumer needs raw `mqtt.js`
44995
+ * features anyway — give it the connection config, get out of the way.
44996
+ *
44997
+ * Consumer flow:
44998
+ * const cfg = await ctx.api.mqttBroker.getBrokerConfig({ id })
44999
+ * const client = mqtt.connect(cfg.url, { username: cfg.username, … })
45000
+ * client.subscribe('zigbee2mqtt/+')
45001
+ *
45002
+ * Collection mode: multiple brokers (e.g. one local mosquitto + one
45003
+ * cloud bridge). The "embedded" entry (when present) is just another
45004
+ * broker in the registry — its lifecycle is owned by the addon that
45005
+ * spawned it.
44899
45006
  */
44900
- var TargetSchema = object({
45007
+ var BrokerKindSchema = _enum(["external", "embedded"]);
45008
+ /**
45009
+ * Broker live-probe status.
45010
+ *
45011
+ * - `connected` — last probe completed a clean CONNACK
45012
+ * - `disconnected` — no probe has run yet (cold cache)
45013
+ * - `auth-failed` — CONNACK refused with auth error (RC 4 / 5)
45014
+ * - `unreachable` — TCP connect timed out / refused
45015
+ * - `tls-error` — TLS handshake failed (cert / SNI / cipher)
45016
+ */
45017
+ var BrokerStatusSchema$1 = _enum([
45018
+ "connected",
45019
+ "disconnected",
45020
+ "auth-failed",
45021
+ "unreachable",
45022
+ "tls-error"
45023
+ ]);
45024
+ var BrokerInfoSchema$1 = object({
44901
45025
  id: string(),
44902
45026
  name: string(),
44903
- kind: string(),
44904
- addonId: string(),
44905
- enabled: boolean(),
44906
- config: record(string(), unknown())
44907
- });
44908
- /** A discovery-surfaced candidate (config is partial + non-secret). */
44909
- var DiscoveredTargetSchema = object({
44910
- kind: string(),
44911
- suggestedName: string(),
44912
- config: record(string(), unknown())
44913
- });
44914
- /** The degrade engine's report — what was resolved / dropped / degraded. */
44915
- var RenderedAsSchema = object({
44916
- level: string(),
44917
- format: NotificationFormatSchema,
44918
- attachmentsSent: number().int().nonnegative(),
44919
- actionsSent: number().int().nonnegative(),
44920
- truncated: boolean(),
44921
- dropped: array(string())
44922
- });
44923
- var SendResultSchema = object({
44924
- success: boolean(),
45027
+ url: string(),
45028
+ kind: BrokerKindSchema,
45029
+ status: BrokerStatusSchema$1,
45030
+ latencyMs: number().nullable(),
44925
45031
  error: string().optional(),
44926
- renderedAs: RenderedAsSchema.optional()
45032
+ /** Embedded brokers only: number of MQTT clients currently connected. */
45033
+ connectedClients: number().int().nonnegative().optional(),
45034
+ /** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
45035
+ lastCheckedAt: number().optional()
44927
45036
  });
44928
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
44929
- var TestResultSchema = SendResultSchema;
44930
45037
  /**
44931
- * Pre-save credential probe against a partial (unsaved) target config.
44932
- * `resolvedUrl` never includes secrets (token stripped from path).
45038
+ * Connection details — what a consumer needs to call
45039
+ * `mqtt.connect(url, options)`. We split URL + credentials so the
45040
+ * consumer can pass them as `mqtt.connect(url, { username, password })`
45041
+ * instead of stuffing creds into the URL (which leaks them into logs).
44933
45042
  */
44934
- var ProbeTargetConfigResultSchema = object({
44935
- ok: boolean(),
44936
- resolvedUrl: string(),
44937
- httpStatus: number().int().optional(),
44938
- stateInstance: string().optional(),
44939
- error: string().optional()
44940
- });
44941
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
44942
- kind: string(),
44943
- config: record(string(), unknown()).optional(),
45043
+ var BrokerConnectionDetailsSchema = object({
45044
+ url: string(),
45045
+ username: string().optional(),
45046
+ password: string().optional(),
44944
45047
  /**
44945
- * When editing / duplicating, the blank password field never round-trips
44946
- * the stored token. Pass the source target id so the provider can restore
44947
- * secrets server-side before dialling (same merge as upsert).
45048
+ * Suggested prefix for `clientId`. Each consumer should suffix this
45049
+ * with its own discriminator (addon id, instance id) so reconnects
45050
+ * don't kick each other off (MQTT spec: clientId must be unique per
45051
+ * broker).
44948
45052
  */
44949
- targetId: string().optional()
44950
- }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
44951
- kind: string(),
44952
- config: record(string(), unknown()),
44953
- /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
44954
- targetId: string().optional()
44955
- }), ProbeTargetConfigResultSchema, {
44956
- kind: "mutation",
44957
- auth: "admin"
44958
- }), method(object({
44959
- targetId: string(),
44960
- notification: NotificationSchema
44961
- }), SendResultSchema, {
45053
+ clientIdPrefix: string().optional()
45054
+ });
45055
+ var AddBrokerInputSchema = object({
45056
+ name: string().min(1),
45057
+ url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
45058
+ username: string().optional(),
45059
+ password: string().optional(),
45060
+ clientIdPrefix: string().optional()
45061
+ });
45062
+ var AddBrokerResultSchema = object({ id: string() });
45063
+ var IdInputSchema = object({ id: string() });
45064
+ var TestResultSchema$1 = discriminatedUnion("ok", [object({
45065
+ ok: literal(true),
45066
+ latencyMs: number()
45067
+ }), object({
45068
+ ok: literal(false),
45069
+ error: string()
45070
+ })]);
45071
+ var StartEmbeddedInputSchema = object({
45072
+ port: number().int().min(1).max(65535).default(1883),
45073
+ /** Allow anonymous connect (no username/password). Default: false. */
45074
+ allowAnonymous: boolean().default(false),
45075
+ /** Optional shared username/password for clients. */
45076
+ username: string().optional(),
45077
+ password: string().optional()
45078
+ });
45079
+ var StartEmbeddedResultSchema = object({
45080
+ id: string(),
45081
+ url: string()
45082
+ });
45083
+ var StatusSchema = object({
45084
+ brokerCount: number(),
45085
+ embeddedRunning: boolean()
45086
+ });
45087
+ method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
44962
45088
  kind: "mutation",
44963
45089
  auth: "admin"
44964
- }), method(object({
44965
- targetId: string(),
44966
- sample: NotificationSchema.optional()
44967
- }), TestResultSchema, {
45090
+ }), method(IdInputSchema, _void(), {
44968
45091
  kind: "mutation",
44969
45092
  auth: "admin"
44970
- }), method(object({
44971
- target: TargetSchema,
44972
- /**
44973
- * Duplicate flow: create a NEW target id while copying secrets from an
44974
- * existing row. The UI never holds the stored token; blank / redacted
44975
- * password fields are restored from this source (D634 still applies if
44976
- * destinations diverge without a re-typed secret).
44977
- */
44978
- cloneSecretsFrom: string().optional()
44979
- }), TargetSchema, {
45093
+ }), method(IdInputSchema, TestResultSchema$1, {
44980
45094
  kind: "mutation",
44981
45095
  auth: "admin"
44982
- }), method(object({ targetId: string() }), _void(), {
45096
+ }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
44983
45097
  kind: "mutation",
44984
45098
  auth: "admin"
44985
- }), method(object({
44986
- targetId: string(),
44987
- enabled: boolean()
44988
- }), _void(), {
45099
+ }), method(IdInputSchema, _void(), {
44989
45100
  kind: "mutation",
44990
45101
  auth: "admin"
45102
+ }), method(_void(), StatusSchema);
45103
+ var NetworkEndpointSchema = object({
45104
+ url: string(),
45105
+ hostname: string(),
45106
+ port: number(),
45107
+ protocol: _enum(["http", "https"]),
45108
+ /**
45109
+ * Whether this address survives a restart of its provider. `false` = it is
45110
+ * re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
45111
+ * hub never selects such an address AUTOMATICALLY for a role (D860), though
45112
+ * it stays visible and an operator may still flag it. Absent = stable: every
45113
+ * provider except a Quick tunnel owns a name that does not change.
45114
+ */
45115
+ stable: boolean().optional()
45116
+ });
45117
+ object({
45118
+ url: string(),
45119
+ /** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
45120
+ stable: boolean().optional(),
45121
+ updated: boolean().optional()
45122
+ });
45123
+ var NetworkAccessStatusSchema = object({
45124
+ connected: boolean(),
45125
+ endpoint: NetworkEndpointSchema.nullable(),
45126
+ error: string().optional()
45127
+ });
45128
+ /**
45129
+ * Optional, richer endpoint shape returned by providers that expose
45130
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
45131
+ * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
45132
+ * the originating provider config (mode + sourcePort) so the
45133
+ * orchestrator UI can label rows distinctly. Providers that expose only
45134
+ * one endpoint just omit `listEndpoints` from their provider impl.
45135
+ */
45136
+ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
45137
+ /**
45138
+ * Stable id within the provider — typically `<mode>-<sourcePort>` so
45139
+ * the orchestrator can dedupe across `listEndpoints` polls.
45140
+ */
45141
+ id: string(),
45142
+ /** Operator-facing label (mirrors `MeshEndpoint.label`). */
45143
+ label: string(),
45144
+ /** Optional provider-specific mode tag, used for icon/colour in admin UI. */
45145
+ mode: string().optional(),
45146
+ /** Originating local port the ingress fronts (informational). */
45147
+ sourcePort: number().optional()
44991
45148
  });
45149
+ method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
44992
45150
  var AUDIO_MACRO_LABELS = [
44993
45151
  {
44994
45152
  id: "speech",
@@ -51389,14 +51547,6 @@ var EngineProvisioningSchema = object({
51389
51547
  */
51390
51548
  configIssues: array(string()).optional()
51391
51549
  });
51392
- var PipelineStepInputSchema = lazy(() => object({
51393
- addonId: string(),
51394
- modelId: string().optional(),
51395
- enabled: boolean().default(true),
51396
- children: array(PipelineStepInputSchema).optional(),
51397
- settings: record(string(), unknown()).optional(),
51398
- jumpDeviceKey: string().optional()
51399
- }));
51400
51550
  var ModelSubstitutionSchema = object({
51401
51551
  addonId: string(),
51402
51552
  chosen: string(),
@@ -51475,7 +51625,6 @@ var ConfigUISchemaBridge = custom();
51475
51625
  var ConfigUISchemaNullableBridge = custom();
51476
51626
  var InferenceCapabilitiesBridge = custom();
51477
51627
  var ModelAvailabilityListBridge = custom();
51478
- var PipelineRunResultBridge = custom();
51479
51628
  method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
51480
51629
  modelId: string(),
51481
51630
  settings: record(string(), unknown()).readonly()
@@ -59149,7 +59298,7 @@ object({
59149
59298
  /** Ms epoch of the last value received from the appliance; 0 = never. */
59150
59299
  lastFetchedAt: number()
59151
59300
  });
59152
- var DeviceIdSchema = number().int().nonnegative();
59301
+ var DeviceIdSchema$1 = number().int().nonnegative();
59153
59302
  /** A value a program option or setting can take. */
59154
59303
  var DishwasherValueSchema = union([
59155
59304
  boolean(),
@@ -59213,77 +59362,77 @@ var DishwasherOptionsSchema = object({
59213
59362
  options: array(DishwasherOptionDescriptorSchema)
59214
59363
  });
59215
59364
  DeviceType.Dishwasher, method(object({
59216
- deviceId: DeviceIdSchema,
59365
+ deviceId: DeviceIdSchema$1,
59217
59366
  program: string().min(1).optional(),
59218
59367
  options: record(string(), DishwasherValueSchema).optional()
59219
59368
  }), _void(), {
59220
59369
  kind: "mutation",
59221
59370
  auth: "protected"
59222
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
59371
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
59223
59372
  kind: "mutation",
59224
59373
  auth: "protected"
59225
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
59374
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
59226
59375
  kind: "mutation",
59227
59376
  auth: "protected"
59228
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
59377
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
59229
59378
  kind: "mutation",
59230
59379
  auth: "protected"
59231
59380
  }), method(object({
59232
- deviceId: DeviceIdSchema,
59381
+ deviceId: DeviceIdSchema$1,
59233
59382
  program: string().min(1)
59234
59383
  }), _void(), {
59235
59384
  kind: "mutation",
59236
59385
  auth: "protected"
59237
59386
  }), method(object({
59238
- deviceId: DeviceIdSchema,
59387
+ deviceId: DeviceIdSchema$1,
59239
59388
  on: boolean()
59240
59389
  }), _void(), {
59241
59390
  kind: "mutation",
59242
59391
  auth: "protected"
59243
59392
  }), method(object({
59244
- deviceId: DeviceIdSchema,
59393
+ deviceId: DeviceIdSchema$1,
59245
59394
  on: boolean()
59246
59395
  }), _void(), {
59247
59396
  kind: "mutation",
59248
59397
  auth: "protected"
59249
59398
  }), method(object({
59250
- deviceId: DeviceIdSchema,
59399
+ deviceId: DeviceIdSchema$1,
59251
59400
  on: boolean()
59252
59401
  }), _void(), {
59253
59402
  kind: "mutation",
59254
59403
  auth: "protected"
59255
59404
  }), method(object({
59256
- deviceId: DeviceIdSchema,
59405
+ deviceId: DeviceIdSchema$1,
59257
59406
  on: boolean()
59258
59407
  }), _void(), {
59259
59408
  kind: "mutation",
59260
59409
  auth: "protected"
59261
59410
  }), method(object({
59262
- deviceId: DeviceIdSchema,
59411
+ deviceId: DeviceIdSchema$1,
59263
59412
  on: boolean()
59264
59413
  }), _void(), {
59265
59414
  kind: "mutation",
59266
59415
  auth: "protected"
59267
59416
  }), method(object({
59268
- deviceId: DeviceIdSchema,
59417
+ deviceId: DeviceIdSchema$1,
59269
59418
  on: boolean()
59270
59419
  }), _void(), {
59271
59420
  kind: "mutation",
59272
59421
  auth: "protected"
59273
59422
  }), method(object({
59274
- deviceId: DeviceIdSchema,
59423
+ deviceId: DeviceIdSchema$1,
59275
59424
  on: boolean()
59276
59425
  }), _void(), {
59277
59426
  kind: "mutation",
59278
59427
  auth: "protected"
59279
59428
  }), method(object({
59280
- deviceId: DeviceIdSchema,
59429
+ deviceId: DeviceIdSchema$1,
59281
59430
  key: string().min(1),
59282
59431
  value: DishwasherValueSchema
59283
59432
  }), _void(), {
59284
59433
  kind: "mutation",
59285
59434
  auth: "protected"
59286
- }), method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" });
59435
+ }), method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" });
59287
59436
  DeviceType.Camera;
59288
59437
  object({
59289
59438
  /** Ms epoch of the last press. null = never observed since this provider started. */
@@ -61721,7 +61870,8 @@ var TopologyNodeSchema = object({
61721
61870
  isHub: boolean(),
61722
61871
  isOnline: boolean(),
61723
61872
  cpuPercent: number(),
61724
- memoryPercent: number(),
61873
+ /** `null` when the node's memory read failed or it has not reported one (D393). */
61874
+ memoryPercent: number().nullable(),
61725
61875
  uptime: number(),
61726
61876
  lastSeen: string(),
61727
61877
  localIps: array(string()).readonly(),
@@ -62256,9 +62406,10 @@ object({
62256
62406
  * combined reading; per-hopper levels live in `food1`/`food2`. Null when
62257
62407
  * the device has not reported yet. */
62258
62408
  foodLevel: number().nullable().meta(readingMeta("none")),
62259
- /** Food in the BOWL as the feeder reports it (`state.bowl`), in the
62260
- * feeder's own units. Null when the model reports none. */
62261
- bowlFood: number().nullable().meta(readingMeta("none")),
62409
+ /** Food in the BOWL, a mass (stored in kilograms, D944). Reported only by a
62410
+ * feeder with a bowl scale; null when the model has none or its reading is
62411
+ * of an unproven unit. */
62412
+ bowlFood: number().nullable().meta(readingMeta("mass")),
62262
62413
  /** True while the pet is eating (`state.eating`). */
62263
62414
  eating: boolean(),
62264
62415
  /** Whether `markFoodReplenished` does anything on this model. When false
@@ -62371,6 +62522,64 @@ DeviceType.PetFeeder, method(object({
62371
62522
  kind: "mutation",
62372
62523
  auth: "protected"
62373
62524
  });
62525
+ object({
62526
+ /** Water in the tank, 0..100 (%). Null while the source has not reported. */
62527
+ waterLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62528
+ /** Water in the tank, in litres. Null while the source has not reported. */
62529
+ waterVolume: number().min(0).nullable().meta(readingMeta("volume")),
62530
+ /** True when the tank is empty. Null while unknown. */
62531
+ empty: boolean().nullable(),
62532
+ /** True while the pump is running. Null while unknown. */
62533
+ pumpRunning: boolean().nullable(),
62534
+ /** True when the pump works on an on/off duty cycle rather than continuously. */
62535
+ pumpIntervalEnabled: boolean().nullable(),
62536
+ /** How long the pump runs per cycle, in seconds. */
62537
+ pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
62538
+ /** How long the pump rests per cycle, in seconds. */
62539
+ pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
62540
+ /** Water drunk in the last drink, in litres. */
62541
+ lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
62542
+ /** Water drunk in the current hour, in litres. */
62543
+ currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
62544
+ /** Water drunk over the last 24 hours (rolling), in litres. */
62545
+ last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
62546
+ /** Water drunk since local midnight, in litres. */
62547
+ todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
62548
+ /** Decoded fault message. Null means healthy; a non-null string is an active fault. */
62549
+ error: string().nullable(),
62550
+ /** Ms epoch when the slice was last refreshed. */
62551
+ lastFetchedAt: number()
62552
+ });
62553
+ var DeviceIdSchema = number().int().nonnegative();
62554
+ var MinutesSchema = number().int().min(0);
62555
+ DeviceType.PetFountain, method(object({
62556
+ deviceId: DeviceIdSchema,
62557
+ on: boolean()
62558
+ }), _void(), {
62559
+ kind: "mutation",
62560
+ auth: "protected"
62561
+ }), method(object({
62562
+ deviceId: DeviceIdSchema,
62563
+ on: boolean()
62564
+ }), _void(), {
62565
+ kind: "mutation",
62566
+ auth: "protected"
62567
+ }), method(object({
62568
+ deviceId: DeviceIdSchema,
62569
+ minutes: MinutesSchema
62570
+ }), _void(), {
62571
+ kind: "mutation",
62572
+ auth: "protected"
62573
+ }), method(object({
62574
+ deviceId: DeviceIdSchema,
62575
+ minutes: MinutesSchema
62576
+ }), _void(), {
62577
+ kind: "mutation",
62578
+ auth: "protected"
62579
+ }), method(object({ deviceId: DeviceIdSchema }), _void(), {
62580
+ kind: "mutation",
62581
+ auth: "protected"
62582
+ });
62374
62583
  var VehicleSchema = object({
62375
62584
  id: string(),
62376
62585
  name: string(),
@@ -67070,7 +67279,7 @@ discriminatedUnion("kind", [object({
67070
67279
  })]);
67071
67280
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
67072
67281
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
67073
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
67282
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
67074
67283
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
67075
67284
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
67076
67285
  new Set(CAP_NAMES);
@@ -67123,7 +67332,7 @@ var AccessoryKind = {
67123
67332
  PrivacyMask: DeviceRole.PrivacyMask
67124
67333
  };
67125
67334
  AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
67126
- DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
67335
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
67127
67336
  new Set(Object.values(DeviceType));
67128
67337
  Object.freeze({
67129
67338
  "accessories.getStatus": {
@@ -69058,6 +69267,13 @@ Object.freeze({
69058
69267
  access: "create",
69059
69268
  role: "admin"
69060
69269
  },
69270
+ "deviceExport.exposeEntity": {
69271
+ capName: "device-export",
69272
+ capScope: "system",
69273
+ addonId: null,
69274
+ access: "create",
69275
+ role: "admin"
69276
+ },
69061
69277
  "deviceExport.getDeviceLiveContribution": {
69062
69278
  capName: "device-export",
69063
69279
  capScope: "system",
@@ -69086,6 +69302,13 @@ Object.freeze({
69086
69302
  access: "view",
69087
69303
  role: "admin"
69088
69304
  },
69305
+ "deviceExport.listExposedEntities": {
69306
+ capName: "device-export",
69307
+ capScope: "system",
69308
+ addonId: null,
69309
+ access: "view",
69310
+ role: "admin"
69311
+ },
69089
69312
  "deviceExport.listSupportedDeviceKinds": {
69090
69313
  capName: "device-export",
69091
69314
  capScope: "system",
@@ -69093,6 +69316,13 @@ Object.freeze({
69093
69316
  access: "view",
69094
69317
  role: "admin"
69095
69318
  },
69319
+ "deviceExport.listSupportedEntityKinds": {
69320
+ capName: "device-export",
69321
+ capScope: "system",
69322
+ addonId: null,
69323
+ access: "view",
69324
+ role: "admin"
69325
+ },
69096
69326
  "deviceExport.unexposeDevice": {
69097
69327
  capName: "device-export",
69098
69328
  capScope: "system",
@@ -69100,6 +69330,13 @@ Object.freeze({
69100
69330
  access: "create",
69101
69331
  role: "admin"
69102
69332
  },
69333
+ "deviceExport.unexposeEntity": {
69334
+ capName: "device-export",
69335
+ capScope: "system",
69336
+ addonId: null,
69337
+ access: "create",
69338
+ role: "admin"
69339
+ },
69103
69340
  "deviceExtension.listCandidates": {
69104
69341
  capName: "device-extension",
69105
69342
  capScope: "device",
@@ -71921,6 +72158,13 @@ Object.freeze({
71921
72158
  access: "create",
71922
72159
  role: "admin"
71923
72160
  },
72161
+ "nodeRemovalHook.onNodeRemoved": {
72162
+ capName: "node-removal-hook",
72163
+ capScope: "system",
72164
+ addonId: null,
72165
+ access: "create",
72166
+ role: "admin"
72167
+ },
71924
72168
  "nodes.clusterAddonStatus": {
71925
72169
  capName: "nodes",
71926
72170
  capScope: "system",
@@ -72558,6 +72802,48 @@ Object.freeze({
72558
72802
  access: "create",
72559
72803
  role: "viewer"
72560
72804
  },
72805
+ "petFountain.getStatus": {
72806
+ capName: "pet-fountain",
72807
+ capScope: "device",
72808
+ addonId: null,
72809
+ access: "view",
72810
+ role: "viewer"
72811
+ },
72812
+ "petFountain.resetDailyConsumption": {
72813
+ capName: "pet-fountain",
72814
+ capScope: "device",
72815
+ addonId: null,
72816
+ access: "delete",
72817
+ role: "viewer"
72818
+ },
72819
+ "petFountain.setPump": {
72820
+ capName: "pet-fountain",
72821
+ capScope: "device",
72822
+ addonId: null,
72823
+ access: "create",
72824
+ role: "viewer"
72825
+ },
72826
+ "petFountain.setPumpInterval": {
72827
+ capName: "pet-fountain",
72828
+ capScope: "device",
72829
+ addonId: null,
72830
+ access: "create",
72831
+ role: "viewer"
72832
+ },
72833
+ "petFountain.setPumpOffMinutes": {
72834
+ capName: "pet-fountain",
72835
+ capScope: "device",
72836
+ addonId: null,
72837
+ access: "create",
72838
+ role: "viewer"
72839
+ },
72840
+ "petFountain.setPumpOnMinutes": {
72841
+ capName: "pet-fountain",
72842
+ capScope: "device",
72843
+ addonId: null,
72844
+ access: "create",
72845
+ role: "viewer"
72846
+ },
72561
72847
  "pipelineAnalytics.addRetrainFrameFromRecording": {
72562
72848
  capName: "pipeline-analytics",
72563
72849
  capScope: "device",
@@ -79040,6 +79326,100 @@ var APPLIANCE_CAP_VOCABULARY = {
79040
79326
  }
79041
79327
  ]
79042
79328
  },
79329
+ "pet-fountain": {
79330
+ scope: "device",
79331
+ noun: t("Pet fountain", "Fontana per animali"),
79332
+ actions: [
79333
+ {
79334
+ id: "petFountain.pumpOn",
79335
+ method: "setPump",
79336
+ label: t("Pump on", "Pompa accesa"),
79337
+ phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
79338
+ icon: "play",
79339
+ args: { on: true }
79340
+ },
79341
+ {
79342
+ id: "petFountain.pumpOff",
79343
+ method: "setPump",
79344
+ label: t("Pump off", "Pompa spenta"),
79345
+ phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
79346
+ icon: "square",
79347
+ args: { on: false }
79348
+ },
79349
+ {
79350
+ id: "petFountain.intervalOn",
79351
+ method: "setPumpInterval",
79352
+ label: t("Pump cycle on", "Ciclo pompa attivo"),
79353
+ phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
79354
+ icon: "timer",
79355
+ args: { on: true }
79356
+ },
79357
+ {
79358
+ id: "petFountain.intervalOff",
79359
+ method: "setPumpInterval",
79360
+ label: t("Pump cycle off", "Ciclo pompa spento"),
79361
+ phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
79362
+ icon: "timer-off",
79363
+ args: { on: false }
79364
+ },
79365
+ {
79366
+ id: "petFountain.pumpOnMinutes",
79367
+ method: "setPumpOnMinutes",
79368
+ label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
79369
+ phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
79370
+ icon: "timer",
79371
+ openArgs: [{
79372
+ key: "minutes",
79373
+ label: t("Minutes", "Minuti"),
79374
+ offer: {
79375
+ kind: "number",
79376
+ min: 0,
79377
+ step: 1,
79378
+ unit: "min"
79379
+ }
79380
+ }]
79381
+ },
79382
+ {
79383
+ id: "petFountain.pumpOffMinutes",
79384
+ method: "setPumpOffMinutes",
79385
+ label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
79386
+ phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
79387
+ icon: "timer-off",
79388
+ openArgs: [{
79389
+ key: "minutes",
79390
+ label: t("Minutes", "Minuti"),
79391
+ offer: {
79392
+ kind: "number",
79393
+ min: 0,
79394
+ step: 1,
79395
+ unit: "min"
79396
+ }
79397
+ }]
79398
+ },
79399
+ {
79400
+ id: "petFountain.resetDailyConsumption",
79401
+ method: "resetDailyConsumption",
79402
+ label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
79403
+ phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
79404
+ icon: "rotate-ccw"
79405
+ }
79406
+ ],
79407
+ state: [{
79408
+ kind: "boolean",
79409
+ field: "empty",
79410
+ label: t("Tank empty", "Serbatoio vuoto"),
79411
+ whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
79412
+ whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
79413
+ icon: "droplet"
79414
+ }, {
79415
+ kind: "numeric",
79416
+ field: "waterLevel",
79417
+ quantity: t("the water level", "il livello dell’acqua"),
79418
+ label: t("Water level", "Livello dell’acqua"),
79419
+ unit: "%",
79420
+ icon: "droplets"
79421
+ }]
79422
+ },
79043
79423
  "media-player": {
79044
79424
  scope: "device",
79045
79425
  noun: t("Media player", "Lettore multimediale"),
@@ -80864,6 +81244,7 @@ var SYSTEM_CAP_VOCABULARY = {
80864
81244
  "device-manager": system("the device registry itself — rename, move and remove are administration"),
80865
81245
  "device-provider": system(INTERNAL),
80866
81246
  "device-removal-hook": system(INTERNAL),
81247
+ "node-removal-hook": system(INTERNAL),
80867
81248
  "device-state": system("the runtime-state mirror every trigger already reads through"),
80868
81249
  "display-palette": system("the cluster display palette — one colour table for every client, not a device action"),
80869
81250
  "embedding-encoder": system(ML),
@@ -80976,7 +81357,7 @@ function projectAction(cap, action) {
80976
81357
  };
80977
81358
  }
80978
81359
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
80979
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
81360
+ DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
80980
81361
  DeviceFeature.BatteryOperated;
80981
81362
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
80982
81363
  var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";
@@ -82527,6 +82908,36 @@ Object.freeze({
82527
82908
  form: "single",
82528
82909
  optional: false
82529
82910
  }],
82911
+ "petFountain.getStatus": [{
82912
+ name: "deviceId",
82913
+ form: "single",
82914
+ optional: false
82915
+ }],
82916
+ "petFountain.resetDailyConsumption": [{
82917
+ name: "deviceId",
82918
+ form: "single",
82919
+ optional: false
82920
+ }],
82921
+ "petFountain.setPump": [{
82922
+ name: "deviceId",
82923
+ form: "single",
82924
+ optional: false
82925
+ }],
82926
+ "petFountain.setPumpInterval": [{
82927
+ name: "deviceId",
82928
+ form: "single",
82929
+ optional: false
82930
+ }],
82931
+ "petFountain.setPumpOffMinutes": [{
82932
+ name: "deviceId",
82933
+ form: "single",
82934
+ optional: false
82935
+ }],
82936
+ "petFountain.setPumpOnMinutes": [{
82937
+ name: "deviceId",
82938
+ form: "single",
82939
+ optional: false
82940
+ }],
82530
82941
  "pipelineAnalytics.addRetrainFrameFromRecording": [{
82531
82942
  name: "deviceId",
82532
82943
  form: "single",
@@ -85185,4 +85596,4 @@ object({
85185
85596
  commands: record(string(), unknown())
85186
85597
  });
85187
85598
  //#endregion
85188
- export { record as C, object as S, isSecretClear as _, addonPagesSourceCapability as a, boolean as b, trainingProviderCapability as c, SECRET_CLEAR as d, arrayItemFieldPath as f, globalSecretScope as g, entryBody as h, TrainingResultSchema as i, BaseAddon as l, defineRecordKinds as m, TrainingJobSchema as n, parseTrainingEventLine as o, defineEntryKind as p, TrainingJobSpecSchema as r, trainingCapability as s, TRAINING_JOB_SCHEMA as t, RECORD_MAX_DATA_BYTES as u, isSecretMarker as v, string as w, number as x, toSecretFieldView as y };
85599
+ export { record as C, object as S, isSecretClear as _, parseTrainingEventLine as a, boolean as b, BaseAddon as c, addonPagesSourceCapability as d, arrayItemFieldPath as f, globalSecretScope as g, entryBody as h, TrainingResultSchema as i, RECORD_MAX_DATA_BYTES as l, defineRecordKinds as m, TrainingJobSchema as n, trainingCapability as o, defineEntryKind as p, TrainingJobSpecSchema as r, trainingProviderCapability as s, TRAINING_JOB_SCHEMA as t, SECRET_CLEAR as u, isSecretMarker as v, string as w, number as x, toSecretFieldView as y };