@camstack/addon-admin-ui 1.2.516 → 1.2.517

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 (140) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/assets/{AddonAssetIcon-wdlOrMF-.js → AddonAssetIcon-DaAStKC8.js} +1 -1
  3. package/dist/assets/Addons-Do9U7LcM.js +5 -0
  4. package/dist/assets/{AdminPage-DUKfPFuL.js → AdminPage-CgFhd3My.js} +1 -1
  5. package/dist/assets/{AdminSectionedPage-BMXoex-B.js → AdminSectionedPage-C9702pKD.js} +1 -1
  6. package/dist/assets/AiProfiles-CZMD4KM-.js +3 -0
  7. package/dist/assets/{BrokerEditorDialog-WnG36n1k.js → BrokerEditorDialog-BCtpOmtb.js} +1 -1
  8. package/dist/assets/Build-BgRIdgDr.js +40 -0
  9. package/dist/assets/{Calendars-C_8vzrin.js → Calendars-CvhWz72R.js} +1 -1
  10. package/dist/assets/Cluster-GaxjV7h4.js +1 -0
  11. package/dist/assets/{ComposedDeviceEditDialog-rbCIhZXk.js → ComposedDeviceEditDialog-B1R5Dm6Q.js} +1 -1
  12. package/dist/assets/CompositionBlockEditor-CKIEoswd.js +2 -0
  13. package/dist/assets/{CompositionEditorPage-CQCrEQw2.js → CompositionEditorPage-CZQd9oj4.js} +1 -1
  14. package/dist/assets/{Data-Bisulqr8.js → Data-CZij0oVZ.js} +2 -2
  15. package/dist/assets/DeviceDetail-DIqK-Pxm.js +2 -0
  16. package/dist/assets/{DeviceDoActionFields-TwLfyb2p.js → DeviceDoActionFields-DAsmzytT.js} +1 -1
  17. package/dist/assets/{Devices-gBCvRVji.js → Devices-60E3KVab.js} +1 -1
  18. package/dist/assets/{EmbedPlayerPage-D0QvOZGA.js → EmbedPlayerPage-BCjssXwM.js} +1 -1
  19. package/dist/assets/{History-DYGuX8Zk.js → History-QHsS9OSz.js} +1 -1
  20. package/dist/assets/{Identity-CuuiBKU3.js → Identity-Ck9bdqfT.js} +1 -1
  21. package/dist/assets/IntegrationDetail-Cggj8UuG.js +1 -0
  22. package/dist/assets/{IntegrationHub-1cXcL__Z.js → IntegrationHub-YFOb5Q5f.js} +1 -1
  23. package/dist/assets/{Integrations-BojVUe5H.js → Integrations-CAGCgM4m.js} +1 -1
  24. package/dist/assets/Integrations-CM_r2esc.js +1 -0
  25. package/dist/assets/Library-CDBh4UY7.js +1 -0
  26. package/dist/assets/{Locations-1aK4-jN_.js → Locations-C5J-xGpv.js} +2 -2
  27. package/dist/assets/{Logs-DoT0wYVp.js → Logs-BzR8S8Az.js} +1 -1
  28. package/dist/assets/{MaskShapeCanvas-DMR0FYMA.js → MaskShapeCanvas-v1VFSWGn.js} +1 -1
  29. package/dist/assets/{MotionZonesSettings-BV32HBBi.js → MotionZonesSettings-sNv6UeyN.js} +1 -1
  30. package/dist/assets/{MyAccess-DFaQ4CF1.js → MyAccess-d7f5aurQ.js} +1 -1
  31. package/dist/assets/{Network-BW1X6R0h.js → Network-D5kLXsF4.js} +1 -1
  32. package/dist/assets/{NodeAddonsSettingsPanel-B57PJ6f1.js → NodeAddonsSettingsPanel-Dul-TVez.js} +1 -1
  33. package/dist/assets/Notifications-BCXKW0H1.js +1 -0
  34. package/dist/assets/Pipeline-DQ0gg-q3.js +1 -0
  35. package/dist/assets/{PrivacyMaskSettings-CoG7nIQV.js → PrivacyMaskSettings-BOFfKtZS.js} +1 -1
  36. package/dist/assets/{Recordings-C1sihbEr.js → Recordings-CSEh6SKS.js} +1 -1
  37. package/dist/assets/{RemoteGrid-BaCgEnAr.js → RemoteGrid-DwD_77yd.js} +1 -1
  38. package/dist/assets/{ScanNetworkModal-Dh75fAJC.js → ScanNetworkModal-DfCSNZP7.js} +1 -1
  39. package/dist/assets/{SceneMonitorEditor-CQBo-z7Q.js → SceneMonitorEditor-CYSd3nvA.js} +1 -1
  40. package/dist/assets/{SessionsList-DPdW7DeP.js → SessionsList-e-yTK3Ce.js} +2 -2
  41. package/dist/assets/{Showroom-B18dxFEP.js → Showroom-BW0s66nO.js} +1 -1
  42. package/dist/assets/{System-DtQ3rFAR.js → System-lEmnBarz.js} +1 -1
  43. package/dist/assets/{Templates-JthGtbfM.js → Templates-oi3TGvJ_.js} +1 -1
  44. package/dist/assets/{Terminal-C-yOaJpd.js → Terminal-DSohGtV9.js} +2 -2
  45. package/dist/assets/{Viewer-BxkIehW8.js → Viewer-B6FpTku6.js} +1 -1
  46. package/dist/assets/{__vite-browser-external-BlMfPwfH.js → __vite-browser-external-BCAtXlNQ.js} +1 -1
  47. package/dist/assets/{_virtual_mf-localSharedImportMap___mfe_internal__admin_ui_host-DU36Wvlj.js → _virtual_mf-localSharedImportMap___mfe_internal__admin_ui_host-DqPNLVRX.js} +1 -1
  48. package/dist/assets/{addon-icons-context-wgZpmfzx.js → addon-icons-context-BAMZN6v3.js} +1 -1
  49. package/dist/assets/{archive-YDjR82i1.js → archive-CQSA3no9.js} +1 -1
  50. package/dist/assets/{assignment-candidates-ND1LvoHh.js → assignment-candidates-CX47iOH5.js} +1 -1
  51. package/dist/assets/{authed-media-COC4bGHT.js → authed-media-Bk6ZG2lV.js} +1 -1
  52. package/dist/assets/{bell-off-CTp6ZU3t.js → bell-off-ByVFjmad.js} +1 -1
  53. package/dist/assets/{board-model-yAwJydYz.js → board-model-Cij0wyiz.js} +1 -1
  54. package/dist/assets/{boxes-Hzq1wpVE.js → boxes-B8shBGyA.js} +1 -1
  55. package/dist/assets/circle-slash-CYJP5692.js +1 -0
  56. package/dist/assets/client-B3Uh4h4a.js +674 -0
  57. package/dist/assets/{cloud-upload-D5WlxZcT.js → cloud-upload-knnOuFf0.js} +1 -1
  58. package/dist/assets/crop-CO-cCOHc.js +1 -0
  59. package/dist/assets/{device-integration-icon-D04VS09_.js → device-integration-icon-CXZW9un_.js} +1 -1
  60. package/dist/assets/device-picker-model-CLwbjCLX.js +1 -0
  61. package/dist/assets/dist-2sWhwHC7.js +674 -0
  62. package/dist/assets/{dist-CwcciPBA.js → dist-DWGbpqHP.js} +1 -1
  63. package/dist/assets/{file-up-DJmrUG3J.js → file-up-DsJKDfNx.js} +1 -1
  64. package/dist/assets/{hard-drive-download-0sxiHlL4.js → hard-drive-download-3fltBasv.js} +1 -1
  65. package/dist/assets/{hostInit-BD4yf3zA.js → hostInit-Djg3zVjo.js} +1 -1
  66. package/dist/assets/i18n-qDGVD_PF.js +1 -0
  67. package/dist/assets/icons-yMzKblPs.js +1 -0
  68. package/dist/assets/{index-i7JLUpAu.css → index-DrGgMc78.css} +1 -1
  69. package/dist/assets/index-Dvd8RKBK.js +4 -0
  70. package/dist/assets/{key-DO3VGEAR.js → key-DCYw0FNE.js} +1 -1
  71. package/dist/assets/{key-round-CpIaS7OV.js → key-round-CTD-QhVl.js} +1 -1
  72. package/dist/assets/{layers-FeOXu02L.js → layers-7Ihtoyz5.js} +1 -1
  73. package/dist/assets/{linked-devices-model-BgNLlCIM.js → linked-devices-model-n5gH_HKu.js} +1 -1
  74. package/dist/assets/{list-ordered-DvAiDV6i.js → list-ordered-oYfsQM3w.js} +1 -1
  75. package/dist/assets/location-owned-usage-CT1Figq_.js +1 -0
  76. package/dist/assets/{log-in-DWggJ3bf.js → log-in-BzAjDKMU.js} +1 -1
  77. package/dist/assets/{memory-stick-BJxQ51Yi.js → memory-stick-XgJPnz3h.js} +1 -1
  78. package/dist/assets/{message-square-plus-Orzl6MNN.js → message-square-plus-DObXI5UO.js} +1 -1
  79. package/dist/assets/{mf-entry-bootstrap-0-ca45ed49.js → mf-entry-bootstrap-0-8ecb92b3.js} +2 -2
  80. package/dist/assets/{mic-off-CNt3XlBZ.js → mic-off-CFZpIFfk.js} +1 -1
  81. package/dist/assets/{monitor-off-B0u7qbqC.js → monitor-off-610T_Gdj.js} +1 -1
  82. package/dist/assets/{monitor-smartphone-Cc_MH7g-.js → monitor-smartphone-Dn0zJRqy.js} +1 -1
  83. package/dist/assets/mount-CLwbjCLX.js +1 -0
  84. package/dist/assets/player-overlays-CJHEGkWA.js +1 -0
  85. package/dist/assets/{recycle-BvDLKxY2.js → recycle-BDhcl0YV.js} +1 -1
  86. package/dist/assets/remoteEntry-BkNPWH4W.js +1 -0
  87. package/dist/assets/{ruler-LNAbII9z.js → ruler-DRBBjPda.js} +1 -1
  88. package/dist/assets/{scan-eye-D60BuXld.js → scan-eye-Cq1A21Jj.js} +1 -1
  89. package/dist/assets/{scissors-BheHuK2k.js → scissors-_YyUi2Wc.js} +1 -1
  90. package/dist/assets/{search-x-R5djNvpR.js → search-x-0cpMKp48.js} +1 -1
  91. package/dist/assets/{send-COKBz_do.js → send-BPU5cRG9.js} +1 -1
  92. package/dist/assets/{settings-2-TZTP5vyr.js → settings-2-L8TMaIKQ.js} +1 -1
  93. package/dist/assets/{shapes-DZDepNEy.js → shapes-DJVtgjGZ.js} +1 -1
  94. package/dist/assets/{sigma-Cp9fnIxm.js → sigma-DPXOWC0b.js} +1 -1
  95. package/dist/assets/{speaker-QCRYKZuE.js → speaker-CokRNflr.js} +1 -1
  96. package/dist/assets/src-CzDAA2fz.js +67 -0
  97. package/dist/assets/{step-inspector-model-KNZhI4OB.js → step-inspector-model-8FwvV-vd.js} +1 -1
  98. package/dist/assets/{table-2-WATiAftX.js → table-2-DBJuUbJh.js} +1 -1
  99. package/dist/assets/{timer-reset-BfKS6r-R.js → timer-reset-D9k5wmF9.js} +1 -1
  100. package/dist/assets/{unlink-BEt9UZ6m.js → unlink-Bs2RzKEu.js} +1 -1
  101. package/dist/assets/{usb-D5_mfNc4.js → usb-BjhR3doW.js} +1 -1
  102. package/dist/assets/{use-scenes-feed-CKKPja8m.js → use-scenes-feed-CT1HkH56.js} +1 -1
  103. package/dist/assets/{use-stationary-objects-e05PRLQS.js → use-stationary-objects-HuzwOvOv.js} +1 -1
  104. package/dist/assets/useEventInvalidation-CLwbjCLX.js +1 -0
  105. package/dist/assets/{useNodeUrlSelection-BFjZg5LE.js → useNodeUrlSelection-B82QsLnx.js} +1 -1
  106. package/dist/assets/user-round-reErwwaE.js +1 -0
  107. package/dist/assets/{user-x-Ccr0zwNw.js → user-x-lD1eMNGr.js} +1 -1
  108. package/dist/assets/{variable-Cy8dJZee.js → variable-f5kvdTkN.js} +1 -1
  109. package/dist/assets/{video-Lh0c5NC7.js → video-DNdx4dbt.js} +1 -1
  110. package/dist/assets/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__admin_ui_host__remoteEntry-_hash_-QsYrQXzX.js → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__admin_ui_host__remoteEntry-_hash_-BwnfAs-v.js} +2 -2
  111. package/dist/index.html +11 -11
  112. package/dist/server/addon.js +1259 -865
  113. package/package.json +1 -1
  114. package/dist/assets/Addons-DKqOx8Kq.js +0 -5
  115. package/dist/assets/AiProfiles-CrkIAZPw.js +0 -3
  116. package/dist/assets/Build-BV-grwVO.js +0 -40
  117. package/dist/assets/Cluster-hC2lEbOm.js +0 -1
  118. package/dist/assets/CompositionBlockEditor-C88rsNrw.js +0 -2
  119. package/dist/assets/DeviceDetail-Bt1QutgI.js +0 -2
  120. package/dist/assets/IntegrationDetail-CWljL8Xy.js +0 -1
  121. package/dist/assets/Integrations-C8h8k0rF.js +0 -1
  122. package/dist/assets/Library-CJSmtNq_.js +0 -1
  123. package/dist/assets/Notifications-LwQndL16.js +0 -1
  124. package/dist/assets/Pipeline-CPrqhWRG.js +0 -1
  125. package/dist/assets/circle-slash-7y1eeJ49.js +0 -1
  126. package/dist/assets/client-CAYEEPBa.js +0 -674
  127. package/dist/assets/crop-BRsNIUeq.js +0 -1
  128. package/dist/assets/device-picker-model-YTa9ZDF0.js +0 -1
  129. package/dist/assets/dist-DTmE_ROI.js +0 -674
  130. package/dist/assets/eraser-BFUBbWzl.js +0 -1
  131. package/dist/assets/i18n-DGjkSRRU.js +0 -1
  132. package/dist/assets/icons-DEHAwoO2.js +0 -1
  133. package/dist/assets/index-CC39SkXY.js +0 -4
  134. package/dist/assets/location-owned-usage-CuhEoux2.js +0 -1
  135. package/dist/assets/mount-YTa9ZDF0.js +0 -1
  136. package/dist/assets/player-overlays-CebwjD-V.js +0 -1
  137. package/dist/assets/remoteEntry-suQfm0xS.js +0 -1
  138. package/dist/assets/src-DrrkhYev.js +0 -67
  139. package/dist/assets/useEventInvalidation-YTa9ZDF0.js +0 -1
  140. package/dist/assets/user-round-DRg2liPL.js +0 -1
@@ -5069,6 +5069,14 @@ function object(shape, params) {
5069
5069
  ...normalizeParams(params)
5070
5070
  });
5071
5071
  }
5072
+ function strictObject(shape, params) {
5073
+ return new ZodObject({
5074
+ type: "object",
5075
+ shape,
5076
+ catchall: never(),
5077
+ ...normalizeParams(params)
5078
+ });
5079
+ }
5072
5080
  function looseObject(shape, params) {
5073
5081
  return new ZodObject({
5074
5082
  type: "object",
@@ -5448,7 +5456,7 @@ var ZodIssueCode = {
5448
5456
  var ZodFirstPartyTypeKind;
5449
5457
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5450
5458
  //#endregion
5451
- //#region ../types/dist/sleep-C_u6cEAJ.mjs
5459
+ //#region ../types/dist/sleep-6eG9C8Xl.mjs
5452
5460
  /**
5453
5461
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5454
5462
  * window to float samples (D455).
@@ -8424,6 +8432,52 @@ function optionalMethod(input, output, options) {
8424
8432
  providerOptional: true
8425
8433
  };
8426
8434
  }
8435
+ /**
8436
+ * `addon-pages-source` — collection cap exposing per-provider raw page
8437
+ * declarations. Every addon that contributes a UI page registers a
8438
+ * provider here. The hub-side singleton aggregator (`addon-pages` cap,
8439
+ * see `addon-pages.cap.ts`) walks this collection, stamps versioned
8440
+ * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
8441
+ * that admin-ui consumes.
8442
+ *
8443
+ * The split exists because the public listing has a different output
8444
+ * shape than the per-provider raw declarations, and we want both ends
8445
+ * to flow through codegen instead of relying on a hand-written wrapper.
8446
+ */
8447
+ var AddonPageDeclarationSchema = object({
8448
+ id: string(),
8449
+ label: string(),
8450
+ icon: string(),
8451
+ path: string(),
8452
+ /**
8453
+ * Module Federation remote name — must match the `name` field on the
8454
+ * page addon's `federation()` plugin config. Used by admin-ui's
8455
+ * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
8456
+ * Conventionally `addon_<id>_page` (snake_case; MF names cannot
8457
+ * contain hyphens).
8458
+ */
8459
+ remoteName: string(),
8460
+ /**
8461
+ * Bundle filename inside the addon's `dist/` dir served at
8462
+ * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
8463
+ * is always `'remoteEntry.js'`; the value is kept on the metadata so
8464
+ * the static-file route can compute an mtime-based cache-buster URL
8465
+ * without a separate filesystem stat.
8466
+ */
8467
+ bundle: string(),
8468
+ /**
8469
+ * Sidebar section this page docks into. Well-known ids: `'detection'`,
8470
+ * `'cluster'`, `'administration'` — the page renders inside that group.
8471
+ * Any OTHER string creates (or joins) a custom section rendered after
8472
+ * the built-in groups; its label comes from `sectionLabel` (first
8473
+ * declaration wins), falling back to the id. Absent → the legacy
8474
+ * "Addon Pages" group.
8475
+ */
8476
+ section: string().optional(),
8477
+ /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
8478
+ sectionLabel: string().optional()
8479
+ });
8480
+ method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
8427
8481
  var StaticDirOutputSchema$1 = object({ staticDir: string() });
8428
8482
  var VersionOutputSchema$1 = object({ version: string() });
8429
8483
  var adminUiCapability = {
@@ -8445,6 +8499,27 @@ var adminUiCapability = {
8445
8499
  getVersion: method(_void(), VersionOutputSchema$1, { auth: "admin" })
8446
8500
  }
8447
8501
  };
8502
+ /**
8503
+ * The pipeline step-input shape and the run-result bridge, in a module that
8504
+ * imports nothing but zod and types.
8505
+ *
8506
+ * WHY A SEPARATE FILE (addon footprint, D28): both were declared in
8507
+ * `pipeline-executor.cap.ts`, whose import evaluates the whole executor schema
8508
+ * graph. An addon that only needs these two (the benchmark's `runPipelineTest`
8509
+ * action) paid ~130 MB of heap at load. `pipeline-executor.cap.ts` re-exports
8510
+ * them, so every existing import keeps working; addon server code takes them
8511
+ * from `@camstack/types/addon`.
8512
+ */
8513
+ var PipelineStepInputSchema = lazy(() => object({
8514
+ addonId: string(),
8515
+ modelId: string().optional(),
8516
+ enabled: boolean().default(true),
8517
+ children: array(PipelineStepInputSchema).optional(),
8518
+ settings: record(string(), unknown()).optional(),
8519
+ jumpDeviceKey: string().optional()
8520
+ }));
8521
+ /** Typed bridge to the recursive `FrameResult` graph; validation is structural. */
8522
+ var PipelineRunResultBridge = custom();
8448
8523
  var StaticDirOutputSchema = object({ staticDir: string() });
8449
8524
  var VersionOutputSchema = object({ version: string() });
8450
8525
  method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
@@ -8640,6 +8715,7 @@ var CAP_NAMES = Object.freeze([
8640
8715
  "osd",
8641
8716
  "osd-manager",
8642
8717
  "pet-feeder",
8718
+ "pet-fountain",
8643
8719
  "pipeline-analytics",
8644
8720
  "pipeline-executor",
8645
8721
  "pipeline-orchestrator",
@@ -9919,6 +9995,56 @@ Object.freeze({
9919
9995
  "valueType": "number"
9920
9996
  }
9921
9997
  ],
9998
+ "pet-fountain": [
9999
+ {
10000
+ "path": "waterLevel",
10001
+ "valueType": "number"
10002
+ },
10003
+ {
10004
+ "path": "waterVolume",
10005
+ "valueType": "number"
10006
+ },
10007
+ {
10008
+ "path": "empty",
10009
+ "valueType": "boolean"
10010
+ },
10011
+ {
10012
+ "path": "pumpRunning",
10013
+ "valueType": "boolean"
10014
+ },
10015
+ {
10016
+ "path": "pumpIntervalEnabled",
10017
+ "valueType": "boolean"
10018
+ },
10019
+ {
10020
+ "path": "pumpOnTime",
10021
+ "valueType": "number"
10022
+ },
10023
+ {
10024
+ "path": "pumpOffTime",
10025
+ "valueType": "number"
10026
+ },
10027
+ {
10028
+ "path": "lastDrink",
10029
+ "valueType": "number"
10030
+ },
10031
+ {
10032
+ "path": "currentHourDrink",
10033
+ "valueType": "number"
10034
+ },
10035
+ {
10036
+ "path": "last24hDrink",
10037
+ "valueType": "number"
10038
+ },
10039
+ {
10040
+ "path": "todayDrink",
10041
+ "valueType": "number"
10042
+ },
10043
+ {
10044
+ "path": "error",
10045
+ "valueType": "string"
10046
+ }
10047
+ ],
9922
10048
  "power-meter": [
9923
10049
  {
9924
10050
  "path": "power",
@@ -10456,7 +10582,7 @@ object({
10456
10582
  params: record(string(), union([string(), number()])).optional()
10457
10583
  });
10458
10584
  //#endregion
10459
- //#region ../types/dist/err-msg-BCdlR2Li.mjs
10585
+ //#region ../types/dist/err-msg-DWXWc0Di.mjs
10460
10586
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
10461
10587
  DeviceType["Camera"] = "camera";
10462
10588
  DeviceType["Hub"] = "hub";
@@ -10548,6 +10674,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
10548
10674
  * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
10549
10675
  * integrations sharing the same food/desiccant/hopper surface. */
10550
10676
  DeviceType["PetFeeder"] = "pet-feeder";
10677
+ /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
10678
+ * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
10679
+ * of a container and is BUILT as a composed device (D967). */
10680
+ DeviceType["PetFountain"] = "pet-fountain";
10551
10681
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
10552
10682
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
10553
10683
  * Sources: native Bosch Home Connect, reusable by similar integrations.
@@ -10808,7 +10938,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
10808
10938
  return DeviceRole;
10809
10939
  }({});
10810
10940
  //#endregion
10811
- //#region ../types/dist/composition-bIBW5_13.mjs
10941
+ //#region ../types/dist/composition-kSuOifuG.mjs
10812
10942
  /**
10813
10943
  * The ffmpeg build every CamStack node downloads, per platform and arch.
10814
10944
  *
@@ -11893,12 +12023,28 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11893
12023
  ])
11894
12024
  })
11895
12025
  ]);
11896
- var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
12026
+ var forwardArgUnion = union([
12027
+ string(),
12028
+ number(),
12029
+ boolean(),
12030
+ strictObject({ ["$input"]: string().min(1) }),
12031
+ record(string().min(1), lazy(() => forwardArgUnion))
12032
+ ]);
12033
+ var CompositionForwardArgSchema = forwardArgUnion;
12034
+ /**
12035
+ * `forward` (D659, amended by D967): one method of a BUILT capability is one
12036
+ * call of `cap.method` on `source`. Without `args` the built method's input
12037
+ * passes through minus `deviceId`; with `args` the template IS the source's
12038
+ * whole input. `deviceId` is always the source's own, never an argument.
12039
+ */
12040
+ var CompositionForwardCommandSchema = object({
11897
12041
  kind: literal("forward"),
11898
12042
  source: CompositionSourceRefSchema,
11899
12043
  cap: string().min(1),
11900
- method: string().min(1)
11901
- }), CompositionCodeSourceSchema]);
12044
+ method: string().min(1),
12045
+ args: record(string().min(1), CompositionForwardArgSchema).optional()
12046
+ });
12047
+ var CompositionCommandTargetSchema = discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
11902
12048
  /** An item key inside an item-array cap (`consumables.items[<key>]`). */
11903
12049
  var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
11904
12050
  /**
@@ -11908,7 +12054,13 @@ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
11908
12054
  */
11909
12055
  var CompositionItemEntrySchema = object({
11910
12056
  label: string().min(1).max(80),
11911
- fields: record(string().min(1), CompositionFieldSourceSchema)
12057
+ fields: record(string().min(1), CompositionFieldSourceSchema),
12058
+ /**
12059
+ * What the cap's `reset` does for THIS item (consumables). Present = the item
12060
+ * is resettable and a `reset` call naming its key forwards here. The item key
12061
+ * selects the command, so it is never an argument: there is no pass-through.
12062
+ */
12063
+ reset: CompositionForwardCommandSchema.optional()
11912
12064
  });
11913
12065
  var CompositionFeatureSchema = discriminatedUnion("kind", [object({
11914
12066
  kind: literal("fields"),
@@ -11920,7 +12072,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
11920
12072
  * owns the WHOLE array field: the composed value holds exactly these items.
11921
12073
  */
11922
12074
  items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
11923
- /** RESERVED for slice 3; refused by the validator when non-empty. */
12075
+ /** Method name → what serves it. Only a BUILT capability takes commands (D967). */
11924
12076
  commands: record(string().min(1), CompositionCommandTargetSchema).optional()
11925
12077
  }), object({
11926
12078
  kind: literal("passthrough"),
@@ -15860,179 +16012,554 @@ object({
15860
16012
  lastError: string().nullable()
15861
16013
  });
15862
16014
  /**
15863
- * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
15864
- * surface that admin-ui consumes through `useAddonPagesListPages()`.
16015
+ * notification-output — canonical, capability-gated notification delivery.
15865
16016
  *
15866
- * The provider iterates every `addon-pages-source` (collection) provider
15867
- * and emits `AddonPageInfo[]` enriched with versioned `bundleUrl` strings
15868
- * pointing at `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. The
15869
- * filesystem `mtime` cache-buster lets the browser pick up addon
15870
- * rebuilds without manual reload.
16017
+ * Apprise-derived model (see
16018
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
16019
+ * callers emit ONE canonical `Notification`; each provider declares a
16020
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
16021
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
16022
+ * message to what the kind supports — callers never special-case a service.
15871
16023
  *
15872
- * The hub-local builtin `addon-pages-aggregator` (see
15873
- * `@camstack/system/builtins/addon-pages-aggregator`) registers the
15874
- * provider. Splitting the public aggregator from the raw collection
15875
- * keeps both ends in codegen — there's no hand-written
15876
- * `addon-pages.router.ts` wrapper anymore.
16024
+ * DESIGN DECISIONS (locked):
16025
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
16026
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
16027
+ * cap. Rationale: the admin UI needs one uniform surface across the
16028
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
16029
+ * alternative would fork the UI per addon and cannot host the
16030
+ * discovery→adopt flow.
16031
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
16032
+ * the generated cap-mount auto-`concatCollection`-fans them across every
16033
+ * registered provider (notifiers addon + HA addon) so one catalog is
16034
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
16035
+ * `addonId` the generated collection router extracts from the call input.
16036
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
16037
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
16038
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
16039
+ * base64 fallback needed.
16040
+ *
16041
+ * TODO (deferred, closed-set change — separate decision): add
16042
+ * `providerKind: 'notify'` so notification providers surface on the unified
16043
+ * admin "Integrations" page.
15877
16044
  */
15878
- var AddonPageDeclarationSchema$1 = object({
15879
- id: string(),
15880
- label: string(),
15881
- icon: string(),
15882
- path: string(),
15883
- remoteName: string(),
15884
- bundle: string(),
15885
- section: string().optional(),
15886
- sectionLabel: string().optional()
15887
- });
15888
- var AddonPageInfoSchema = object({
15889
- addonId: string(),
15890
- page: AddonPageDeclarationSchema$1,
15891
- bundleUrl: string()
15892
- });
15893
- method(_void(), array(AddonPageInfoSchema).readonly());
15894
16045
  /**
15895
- * `addon-pages-source` — collection cap exposing per-provider raw page
15896
- * declarations. Every addon that contributes a UI page registers a
15897
- * provider here. The hub-side singleton aggregator (`addon-pages` cap,
15898
- * see `addon-pages.cap.ts`) walks this collection, stamps versioned
15899
- * `bundleUrl` values, and returns the enriched `AddonPageInfo[]` list
15900
- * that admin-ui consumes.
16046
+ * Zentik-derived typed-media enum — the superset across every kind. Each
16047
+ * adapter picks what it supports and the degrade engine filters the rest.
16048
+ */
16049
+ var AttachmentMediaTypeSchema = _enum([
16050
+ "image",
16051
+ "video",
16052
+ "gif",
16053
+ "audio",
16054
+ "icon"
16055
+ ]);
16056
+ /**
16057
+ * A single attachment. Exactly one of `url` (remote source, most adapters
16058
+ * prefer this) or `bytes` (inline source; required for Pushover-style
16059
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
16060
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
16061
+ */
16062
+ var AttachmentSchema = object({
16063
+ mediaType: AttachmentMediaTypeSchema,
16064
+ url: string().optional(),
16065
+ bytes: _instanceof(Uint8Array).optional(),
16066
+ mime: string().optional(),
16067
+ name: string().optional()
16068
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
16069
+ var NotificationFormatSchema = _enum([
16070
+ "text",
16071
+ "markdown",
16072
+ "html"
16073
+ ]);
16074
+ /**
16075
+ * The CLOSED icon vocabulary an action button may use.
15901
16076
  *
15902
- * The split exists because the public listing has a different output
15903
- * shape than the per-provider raw declarations, and we want both ends
15904
- * to flow through codegen instead of relying on a hand-written wrapper.
16077
+ * A closed set, not a free string, and that is the whole point: an arbitrary
16078
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
16079
+ * notices — the same class of gap as a zone vocabulary nothing produced
16080
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
16081
+ * declares `actionIcons: false` and the degrade engine strips the field.
16082
+ *
16083
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
16084
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
16085
+ * differently.
16086
+ *
16087
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
16088
+ *
16089
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
16090
+ * the whole set onto its own renderer's vocabulary through a
16091
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
16092
+ * fallback, so adding a member here fails every adapter's build until someone
16093
+ * decides its glyph, which is the only place that decision can be made
16094
+ * honestly.
16095
+ *
16096
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
16097
+ * `disarm` straight through; iOS feeds that string to
16098
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
16099
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
16100
+ * mapping, and "the field is documented" is not "the value renders".
16101
+ *
16102
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
16103
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
16104
+ * so an addon that emits a token the running hub does not know does not lose an
16105
+ * icon — its whole `send` fails Zod validation and the notification never
16106
+ * arrives. Never emit a new token from an addon before the train carrying it.
15905
16107
  */
15906
- var AddonPageDeclarationSchema = object({
16108
+ var NotificationActionIconSchema = _enum([
16109
+ "acknowledge",
16110
+ "dismiss",
16111
+ "silence",
16112
+ "snooze",
16113
+ "view",
16114
+ "play",
16115
+ "open",
16116
+ "close",
16117
+ "lock",
16118
+ "unlock",
16119
+ "arm",
16120
+ "arm-home",
16121
+ "arm-away",
16122
+ "arm-night",
16123
+ "disarm",
16124
+ "light",
16125
+ "alert",
16126
+ "camera"
16127
+ ]);
16128
+ /** A single tap-through action button. */
16129
+ var NotificationActionSchema = object({
15907
16130
  id: string(),
15908
16131
  label: string(),
15909
- icon: string(),
15910
- path: string(),
15911
- /**
15912
- * Module Federation remote name — must match the `name` field on the
15913
- * page addon's `federation()` plugin config. Used by admin-ui's
15914
- * `<AddonPageLoader>` to call `loadRemote('<remoteName>/page')`.
15915
- * Conventionally `addon_<id>_page` (snake_case; MF names cannot
15916
- * contain hyphens).
15917
- */
15918
- remoteName: string(),
16132
+ url: string().optional(),
16133
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
16134
+ icon: NotificationActionIconSchema.optional(),
15919
16135
  /**
15920
- * Bundle filename inside the addon's `dist/` dir served at
15921
- * `/api/addon-pages/<addonId>/<bundle>`. With Module Federation this
15922
- * is always `'remoteEntry.js'`; the value is kept on the metadata so
15923
- * the static-file route can compute an mtime-based cache-buster URL
15924
- * without a separate filesystem stat.
16136
+ * Renders in a warning style where the notifier supports it.
16137
+ *
16138
+ * A HINT, never a gate. The callback's authority is its token and nothing
16139
+ * else — see `notification-center/action-token.ts` for what that does and
16140
+ * does not buy.
15925
16141
  */
15926
- bundle: string(),
16142
+ destructive: boolean().optional(),
15927
16143
  /**
15928
- * Sidebar section this page docks into. Well-known ids: `'detection'`,
15929
- * `'cluster'`, `'administration'` — the page renders inside that group.
15930
- * Any OTHER string creates (or joins) a custom section rendered after
15931
- * the built-in groups; its label comes from `sectionLabel` (first
15932
- * declaration wins), falling back to the id. Absent → the legacy
15933
- * "Addon Pages" group.
16144
+ * How the tap should REACH the url.
16145
+ *
16146
+ * `navigate` (absent, and every button authored before this field) opens it:
16147
+ * the phone leaves the notification and shows whatever the callback returns.
16148
+ * That is right for a button whose answer the operator wants to read.
16149
+ *
16150
+ * `background` fires it as a POST and stays put. It exists for the buttons
16151
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
16152
+ * an answer to the notification, and being thrown into a browser tab to
16153
+ * confirm it costs more attention than the notification did. A backend that
16154
+ * cannot do a background call renders it as an ordinary link (the adapters
16155
+ * fall back rather than dropping the button), so this is a preference, never
16156
+ * a requirement.
15934
16157
  */
15935
- section: string().optional(),
15936
- /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
15937
- sectionLabel: string().optional()
16158
+ mode: _enum(["navigate", "background"]).optional()
15938
16159
  });
15939
- method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" });
15940
- var AddonHttpRouteSchema = object({
15941
- method: _enum([
15942
- "GET",
15943
- "POST",
15944
- "PUT",
15945
- "DELETE",
15946
- "PATCH"
16160
+ /** A delivery parameter value: what a notifier declared it takes. */
16161
+ var DeliveryParamValueSchema = union([
16162
+ string(),
16163
+ number(),
16164
+ boolean()
16165
+ ]);
16166
+ /**
16167
+ * One extra delivery parameter a notifier KIND declares (D938). A rule that
16168
+ * addresses a target of that kind may set it; a kind that declares nothing is
16169
+ * never handed one. The admin UI derives its control from this descriptor —
16170
+ * no editor knows a service by name.
16171
+ *
16172
+ * Optional fields are `.optional()`, never `.default()` (a Zod default does not
16173
+ * run on the addon cap path).
16174
+ */
16175
+ var DeliveryParamSchema = object({
16176
+ key: string().min(1),
16177
+ label: string(),
16178
+ /** i18n key of the label, resolved by the declaring addon's namespace. */
16179
+ labelKey: string().optional(),
16180
+ type: _enum([
16181
+ "enum",
16182
+ "number",
16183
+ "boolean",
16184
+ "string"
15947
16185
  ]),
15948
- path: string(),
15949
- access: _enum([
15950
- "public",
15951
- "authenticated",
15952
- "admin"
15953
- ]).optional(),
15954
- description: string().optional(),
15955
- /** The hub limits this route per client address before the handler runs. */
15956
- rateLimit: _enum(["auth"]).optional()
16186
+ /** `enum` only: the closed set of accepted values. */
16187
+ options: array(object({
16188
+ value: string(),
16189
+ label: string()
16190
+ })).optional(),
16191
+ /** `number` only. */
16192
+ min: number().optional(),
16193
+ max: number().optional(),
16194
+ /** What the notifier does when the rule sets nothing; shown as the placeholder. */
16195
+ default: DeliveryParamValueSchema.optional()
15957
16196
  });
15958
16197
  /**
15959
- * Cross-process route invocation envelope. The hub captures the
15960
- * request as plain data, ships it to the worker via Moleculer, and
15961
- * the worker runs the local handler against a capturing reply. The
15962
- * envelope returned describes what the handler intended (status,
15963
- * headers, body, or a redirect) so the hub can translate it back to
15964
- * the Fastify reply that's actually wired to the socket.
16198
+ * The canonical notification. `body` is the only hard field (Apprise model).
16199
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
16200
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
16201
+ * the adapter maps this ordinal onto its native level. `level?` is an
16202
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
16203
+ * `priority` for that one target.
15965
16204
  */
15966
- var InvokeRequestSchema = object({
15967
- method: string(),
15968
- path: string(),
15969
- params: record(string(), string()),
15970
- query: record(string(), string()),
15971
- body: unknown(),
15972
- headers: record(string(), string()),
15973
- user: object({
15974
- id: string(),
15975
- username: string(),
15976
- isAdmin: boolean()
15977
- }).optional(),
15978
- scopedToken: unknown().optional()
15979
- });
15980
- var InvokeReplyEnvelopeSchema = object({
15981
- status: number().int(),
15982
- headers: record(string(), string()),
15983
- /** When set, the hub MUST `reply.redirect(redirectUrl)` instead of
15984
- * sending `body`. Status defaults to 302 when this is set unless
15985
- * the handler called `reply.code(...)` explicitly. */
15986
- redirectUrl: string().nullable(),
15987
- /** JSON-serializable body. `undefined` is treated as "no body". */
15988
- body: unknown().optional(),
15989
- /** Set when the handler called `reply.type(mime)`. */
15990
- contentType: string().optional()
16205
+ var NotificationSchema = object({
16206
+ body: string(),
16207
+ title: string().optional(),
16208
+ format: NotificationFormatSchema.default("text"),
16209
+ priority: number().int().min(1).max(5).default(3),
16210
+ level: string().optional(),
16211
+ attachments: array(AttachmentSchema).optional(),
16212
+ clickUrl: string().optional(),
16213
+ actions: array(NotificationActionSchema).optional(),
16214
+ sound: string().optional(),
16215
+ ttl: number().optional(),
16216
+ tag: string().optional(),
16217
+ deviceId: number().optional(),
16218
+ eventId: string().optional(),
16219
+ metadata: record(string(), unknown()).optional(),
16220
+ /**
16221
+ * Values for the parameters the target's kind DECLARES in
16222
+ * {@link TargetKindSchema} `deliveryParams`. Already validated against the
16223
+ * declaration by the sender; a notifier reads only the keys it declared.
16224
+ */
16225
+ delivery: record(string(), DeliveryParamValueSchema).optional()
15991
16226
  });
15992
- method(_void(), array(AddonHttpRouteSchema)), method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" });
15993
- var ConfigTabDeclarationSchema = object({
16227
+ /** One declared native severity/priority level for a kind. */
16228
+ var TargetKindLevelSchema = object({
15994
16229
  id: string(),
15995
16230
  label: string(),
15996
- icon: string(),
15997
- order: number().optional()
15998
- });
15999
- var ConfigSectionWithValuesSchema = object({
16000
- id: string(),
16001
- title: string(),
16002
- titleKey: string().optional(),
16003
- description: string().optional(),
16004
- descriptionKey: string().optional(),
16005
- style: _enum(["card", "accordion"]).optional(),
16006
- defaultCollapsed: boolean().optional(),
16007
- columns: union([
16008
- literal(1),
16009
- literal(2),
16010
- literal(3),
16011
- literal(4)
16012
- ]).optional(),
16013
- tab: string().optional(),
16014
- location: _enum([
16015
- "settings",
16016
- "top-tab",
16017
- "hero"
16018
- ]).optional(),
16019
- order: number().optional(),
16020
- fields: array(any())
16231
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
16232
+ ordinal: number().int().min(1).max(5).nullable(),
16233
+ flags: object({
16234
+ critical: boolean().optional(),
16235
+ silent: boolean().optional(),
16236
+ noPush: boolean().optional()
16237
+ }).optional(),
16238
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
16239
+ requires: array(string()).optional(),
16240
+ description: string().optional()
16021
16241
  });
16022
- var SettingsSchemaWithValuesSchema = object({
16023
- tabs: array(ConfigTabDeclarationSchema).optional(),
16024
- sections: array(ConfigSectionWithValuesSchema)
16242
+ /** The full capability block consulted before dispatch. */
16243
+ var TargetKindCapsSchema = object({
16244
+ attachments: object({
16245
+ mediaTypes: array(AttachmentMediaTypeSchema),
16246
+ mode: _enum([
16247
+ "url",
16248
+ "bytes",
16249
+ "both"
16250
+ ]),
16251
+ max: number().int().nonnegative(),
16252
+ maxBytes: number().int().positive().optional()
16253
+ }),
16254
+ /** Max action buttons (0 = none). */
16255
+ actions: number().int().nonnegative(),
16256
+ /**
16257
+ * Whether this kind renders a per-action ICON.
16258
+ *
16259
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
16260
+ * run on the addon cap path — three production failures in one day taught
16261
+ * this repo that once. Absent is read as false by the degrade engine, which
16262
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
16263
+ * assumed and dropped costs the operator's trust in the field.
16264
+ */
16265
+ actionIcons: boolean().optional(),
16266
+ levels: array(TargetKindLevelSchema),
16267
+ format: array(NotificationFormatSchema),
16268
+ clickUrl: boolean(),
16269
+ sound: boolean(),
16270
+ ttl: boolean(),
16271
+ bodyMaxLen: number().int().positive()
16025
16272
  });
16026
- /** Patch object — keys are field names, values are the new field values. */
16027
- var SettingsPatchSchema = record(string(), unknown());
16028
- /** Standard success response for update operations. */
16029
- var SettingsUpdateResultSchema = object({ success: literal(true) });
16030
- method(object({
16031
- addonId: string(),
16032
- nodeId: string().optional(),
16033
- overlay: record(string(), unknown()).optional(),
16034
- cap: string().optional()
16035
- }), SettingsSchemaWithValuesSchema.nullable()), method(object({
16273
+ /**
16274
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
16275
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
16276
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
16277
+ * the union is large and not meant for runtime validation here; the exported
16278
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
16279
+ */
16280
+ var ConfigSchemaPassthrough$3 = unknown();
16281
+ var TargetKindSchema = object({
16282
+ kind: string(),
16283
+ label: string(),
16284
+ icon: string(),
16285
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
16286
+ addonId: string(),
16287
+ /**
16288
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
16289
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
16290
+ * when the addon bundles no icon for that kind — the client then falls back
16291
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
16292
+ *
16293
+ * Root-relative on purpose: it resolves against whatever origin serves a web
16294
+ * client, and a native client joins it onto its own hub base.
16295
+ *
16296
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
16297
+ * field that survived only because the runtime cap-router forwards provider
16298
+ * output verbatim — so every consumer had to re-declare it by hand to stop
16299
+ * its own Zod parse from stripping it, and the whole arrangement would have
16300
+ * broken silently the moment output validation was tightened anywhere.
16301
+ */
16302
+ iconUrl: string().optional(),
16303
+ /**
16304
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
16305
+ *
16306
+ * The server knows this and therefore says it, because the client cannot
16307
+ * safely guess: a React-Native client renders SVG and raster through two
16308
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
16309
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
16310
+ * placeholder glyph for every vector icon while the web build looked fine.
16311
+ *
16312
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
16313
+ * not been updated — a client that cannot determine the type should prefer
16314
+ * its raster path, which is the safe default for an unknown image.
16315
+ */
16316
+ iconMediaType: string().optional(),
16317
+ configSchema: ConfigSchemaPassthrough$3,
16318
+ /**
16319
+ * The config keys that decide WHERE this kind's secrets are sent (server,
16320
+ * base URL, instance, chat, recipient). A save that changes any of them
16321
+ * while leaving a secret redacted is refused by the provider (D634), so an
16322
+ * editor uses this to ask for the secret on exactly those edits instead of
16323
+ * on every edit.
16324
+ *
16325
+ * Optional: absent from a provider that holds no secret (the Home Assistant
16326
+ * kinds) and from a provider older than this field — an editor that finds
16327
+ * it absent must assume ANY edit may need the secret.
16328
+ */
16329
+ destinationKeys: array(string()).optional(),
16330
+ supportsDiscovery: boolean(),
16331
+ /**
16332
+ * Extra delivery parameters a rule may set for a target of this kind (D938).
16333
+ * Absent = none; the rule editor then offers nothing.
16334
+ */
16335
+ deliveryParams: array(DeliveryParamSchema).optional(),
16336
+ caps: TargetKindCapsSchema
16337
+ });
16338
+ /**
16339
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
16340
+ * (return a presence marker only) when serving `listTargets` — never
16341
+ * round-trip a stored secret to the UI.
16342
+ */
16343
+ var TargetSchema = object({
16344
+ id: string(),
16345
+ name: string(),
16346
+ kind: string(),
16347
+ addonId: string(),
16348
+ enabled: boolean(),
16349
+ config: record(string(), unknown())
16350
+ });
16351
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
16352
+ var DiscoveredTargetSchema = object({
16353
+ kind: string(),
16354
+ suggestedName: string(),
16355
+ config: record(string(), unknown())
16356
+ });
16357
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
16358
+ var RenderedAsSchema = object({
16359
+ level: string(),
16360
+ format: NotificationFormatSchema,
16361
+ attachmentsSent: number().int().nonnegative(),
16362
+ actionsSent: number().int().nonnegative(),
16363
+ truncated: boolean(),
16364
+ dropped: array(string())
16365
+ });
16366
+ var SendResultSchema = object({
16367
+ success: boolean(),
16368
+ error: string().optional(),
16369
+ renderedAs: RenderedAsSchema.optional()
16370
+ });
16371
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
16372
+ var TestResultSchema = SendResultSchema;
16373
+ /**
16374
+ * Pre-save credential probe against a partial (unsaved) target config.
16375
+ * `resolvedUrl` never includes secrets (token stripped from path).
16376
+ */
16377
+ var ProbeTargetConfigResultSchema = object({
16378
+ ok: boolean(),
16379
+ resolvedUrl: string(),
16380
+ httpStatus: number().int().optional(),
16381
+ stateInstance: string().optional(),
16382
+ error: string().optional()
16383
+ });
16384
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
16385
+ kind: string(),
16386
+ config: record(string(), unknown()).optional(),
16387
+ /**
16388
+ * When editing / duplicating, the blank password field never round-trips
16389
+ * the stored token. Pass the source target id so the provider can restore
16390
+ * secrets server-side before dialling (same merge as upsert).
16391
+ */
16392
+ targetId: string().optional()
16393
+ }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
16394
+ kind: string(),
16395
+ config: record(string(), unknown()),
16396
+ /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
16397
+ targetId: string().optional()
16398
+ }), ProbeTargetConfigResultSchema, {
16399
+ kind: "mutation",
16400
+ auth: "admin"
16401
+ }), method(object({
16402
+ targetId: string(),
16403
+ notification: NotificationSchema
16404
+ }), SendResultSchema, {
16405
+ kind: "mutation",
16406
+ auth: "admin"
16407
+ }), method(object({
16408
+ targetId: string(),
16409
+ sample: NotificationSchema.optional()
16410
+ }), TestResultSchema, {
16411
+ kind: "mutation",
16412
+ auth: "admin"
16413
+ }), method(object({
16414
+ target: TargetSchema,
16415
+ /**
16416
+ * Duplicate flow: create a NEW target id while copying secrets from an
16417
+ * existing row. The UI never holds the stored token; blank / redacted
16418
+ * password fields are restored from this source (D634 still applies if
16419
+ * destinations diverge without a re-typed secret).
16420
+ */
16421
+ cloneSecretsFrom: string().optional()
16422
+ }), TargetSchema, {
16423
+ kind: "mutation",
16424
+ auth: "admin"
16425
+ }), method(object({ targetId: string() }), _void(), {
16426
+ kind: "mutation",
16427
+ auth: "admin"
16428
+ }), method(object({
16429
+ targetId: string(),
16430
+ enabled: boolean()
16431
+ }), _void(), {
16432
+ kind: "mutation",
16433
+ auth: "admin"
16434
+ });
16435
+ /**
16436
+ * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
16437
+ * surface that admin-ui consumes through `useAddonPagesListPages()`.
16438
+ *
16439
+ * The provider iterates every `addon-pages-source` (collection) provider
16440
+ * and emits `AddonPageInfo[]` enriched with versioned `bundleUrl` strings
16441
+ * pointing at `/api/addon-pages/<addonId>/<bundle>?v=<mtime>`. The
16442
+ * filesystem `mtime` cache-buster lets the browser pick up addon
16443
+ * rebuilds without manual reload.
16444
+ *
16445
+ * The hub-local builtin `addon-pages-aggregator` (see
16446
+ * `@camstack/system/builtins/addon-pages-aggregator`) registers the
16447
+ * provider. Splitting the public aggregator from the raw collection
16448
+ * keeps both ends in codegen — there's no hand-written
16449
+ * `addon-pages.router.ts` wrapper anymore.
16450
+ */
16451
+ var AddonPageDeclarationSchema$1 = object({
16452
+ id: string(),
16453
+ label: string(),
16454
+ icon: string(),
16455
+ path: string(),
16456
+ remoteName: string(),
16457
+ bundle: string(),
16458
+ section: string().optional(),
16459
+ sectionLabel: string().optional()
16460
+ });
16461
+ var AddonPageInfoSchema = object({
16462
+ addonId: string(),
16463
+ page: AddonPageDeclarationSchema$1,
16464
+ bundleUrl: string()
16465
+ });
16466
+ method(_void(), array(AddonPageInfoSchema).readonly());
16467
+ var AddonHttpRouteSchema = object({
16468
+ method: _enum([
16469
+ "GET",
16470
+ "POST",
16471
+ "PUT",
16472
+ "DELETE",
16473
+ "PATCH"
16474
+ ]),
16475
+ path: string(),
16476
+ access: _enum([
16477
+ "public",
16478
+ "authenticated",
16479
+ "admin"
16480
+ ]).optional(),
16481
+ description: string().optional(),
16482
+ /** The hub limits this route per client address before the handler runs. */
16483
+ rateLimit: _enum(["auth"]).optional()
16484
+ });
16485
+ /**
16486
+ * Cross-process route invocation envelope. The hub captures the
16487
+ * request as plain data, ships it to the worker via Moleculer, and
16488
+ * the worker runs the local handler against a capturing reply. The
16489
+ * envelope returned describes what the handler intended (status,
16490
+ * headers, body, or a redirect) so the hub can translate it back to
16491
+ * the Fastify reply that's actually wired to the socket.
16492
+ */
16493
+ var InvokeRequestSchema = object({
16494
+ method: string(),
16495
+ path: string(),
16496
+ params: record(string(), string()),
16497
+ query: record(string(), string()),
16498
+ body: unknown(),
16499
+ headers: record(string(), string()),
16500
+ user: object({
16501
+ id: string(),
16502
+ username: string(),
16503
+ isAdmin: boolean()
16504
+ }).optional(),
16505
+ scopedToken: unknown().optional()
16506
+ });
16507
+ var InvokeReplyEnvelopeSchema = object({
16508
+ status: number().int(),
16509
+ headers: record(string(), string()),
16510
+ /** When set, the hub MUST `reply.redirect(redirectUrl)` instead of
16511
+ * sending `body`. Status defaults to 302 when this is set unless
16512
+ * the handler called `reply.code(...)` explicitly. */
16513
+ redirectUrl: string().nullable(),
16514
+ /** JSON-serializable body. `undefined` is treated as "no body". */
16515
+ body: unknown().optional(),
16516
+ /** Set when the handler called `reply.type(mime)`. */
16517
+ contentType: string().optional()
16518
+ });
16519
+ method(_void(), array(AddonHttpRouteSchema)), method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" });
16520
+ var ConfigTabDeclarationSchema = object({
16521
+ id: string(),
16522
+ label: string(),
16523
+ icon: string(),
16524
+ order: number().optional()
16525
+ });
16526
+ var ConfigSectionWithValuesSchema = object({
16527
+ id: string(),
16528
+ title: string(),
16529
+ titleKey: string().optional(),
16530
+ description: string().optional(),
16531
+ descriptionKey: string().optional(),
16532
+ style: _enum(["card", "accordion"]).optional(),
16533
+ defaultCollapsed: boolean().optional(),
16534
+ columns: union([
16535
+ literal(1),
16536
+ literal(2),
16537
+ literal(3),
16538
+ literal(4)
16539
+ ]).optional(),
16540
+ tab: string().optional(),
16541
+ location: _enum([
16542
+ "settings",
16543
+ "top-tab",
16544
+ "hero"
16545
+ ]).optional(),
16546
+ order: number().optional(),
16547
+ fields: array(any())
16548
+ });
16549
+ var SettingsSchemaWithValuesSchema = object({
16550
+ tabs: array(ConfigTabDeclarationSchema).optional(),
16551
+ sections: array(ConfigSectionWithValuesSchema)
16552
+ });
16553
+ /** Patch object — keys are field names, values are the new field values. */
16554
+ var SettingsPatchSchema = record(string(), unknown());
16555
+ /** Standard success response for update operations. */
16556
+ var SettingsUpdateResultSchema = object({ success: literal(true) });
16557
+ method(object({
16558
+ addonId: string(),
16559
+ nodeId: string().optional(),
16560
+ overlay: record(string(), unknown()).optional(),
16561
+ cap: string().optional()
16562
+ }), SettingsSchemaWithValuesSchema.nullable()), method(object({
16036
16563
  addonId: string(),
16037
16564
  nodeId: string().optional(),
16038
16565
  patch: SettingsPatchSchema
@@ -18479,7 +19006,11 @@ var CompositionProblemSchema = object({
18479
19006
  "mixed-quantity",
18480
19007
  "helper-mismatch",
18481
19008
  "member-constraint",
18482
- "retention"
19009
+ "retention",
19010
+ "command-method-unknown",
19011
+ "command-source-unknown",
19012
+ "command-input-unknown",
19013
+ "command-input-missing"
18483
19014
  ]),
18484
19015
  path: string(),
18485
19016
  message: string()
@@ -18518,7 +19049,9 @@ var CompositionFieldRoleSchema = _enum([
18518
19049
  var CompositionFieldItemSchema = object({
18519
19050
  arrayPath: string(),
18520
19051
  key: string(),
18521
- label: string()
19052
+ label: string(),
19053
+ /** True when the item carries a `reset` forward: the composer writes `resettable` from this (D967). */
19054
+ resettable: boolean().optional()
18522
19055
  });
18523
19056
  var CompositionFieldPlanSchema = object({
18524
19057
  cap: string(),
@@ -18723,7 +19256,7 @@ Object.freeze({
18723
19256
  * One catalog row per base reading a group helper can build (D944 §3).
18724
19257
  * Data, not a form default written into the block.
18725
19258
  */
18726
- var NOW$2 = {
19259
+ var NOW$3 = {
18727
19260
  fieldPath: "lastFetchedAt",
18728
19261
  expr: "now"
18729
19262
  };
@@ -18775,7 +19308,7 @@ var GROUP_TEMPLATES = [
18775
19308
  quantity: "temperature",
18776
19309
  defaultAggregate: "mean",
18777
19310
  empty: "null",
18778
- companions: [NOW$2],
19311
+ companions: [NOW$3],
18779
19312
  alsoFrom: {
18780
19313
  deviceTypes: CLIMATE_DEVICES,
18781
19314
  accept: {
@@ -18793,7 +19326,7 @@ var GROUP_TEMPLATES = [
18793
19326
  quantity: "humidity",
18794
19327
  defaultAggregate: "mean",
18795
19328
  empty: "null",
18796
- companions: [NOW$2],
19329
+ companions: [NOW$3],
18797
19330
  alsoFrom: {
18798
19331
  deviceTypes: CLIMATE_DEVICES,
18799
19332
  accept: {
@@ -18811,7 +19344,7 @@ var GROUP_TEMPLATES = [
18811
19344
  quantity: "pressure",
18812
19345
  defaultAggregate: "mean",
18813
19346
  empty: "null",
18814
- companions: [NOW$2]
19347
+ companions: [NOW$3]
18815
19348
  }),
18816
19349
  numeric({
18817
19350
  id: "illuminance",
@@ -18822,7 +19355,7 @@ var GROUP_TEMPLATES = [
18822
19355
  quantity: "illuminance",
18823
19356
  defaultAggregate: "mean",
18824
19357
  empty: "null",
18825
- companions: [NOW$2]
19358
+ companions: [NOW$3]
18826
19359
  }),
18827
19360
  numeric({
18828
19361
  id: "power",
@@ -18833,7 +19366,7 @@ var GROUP_TEMPLATES = [
18833
19366
  quantity: "power",
18834
19367
  defaultAggregate: "sum",
18835
19368
  empty: "omit",
18836
- companions: [NOW$2]
19369
+ companions: [NOW$3]
18837
19370
  }),
18838
19371
  numeric({
18839
19372
  id: "energy",
@@ -18844,7 +19377,7 @@ var GROUP_TEMPLATES = [
18844
19377
  quantity: "energy",
18845
19378
  defaultAggregate: "sum",
18846
19379
  empty: "omit",
18847
- companions: [NOW$2]
19380
+ companions: [NOW$3]
18848
19381
  }),
18849
19382
  numeric({
18850
19383
  id: "battery",
@@ -18879,7 +19412,7 @@ var GROUP_TEMPLATES = [
18879
19412
  quantity: "device",
18880
19413
  defaultAggregate: "mean",
18881
19414
  empty: "null",
18882
- companions: [NOW$2]
19415
+ companions: [NOW$3]
18883
19416
  }),
18884
19417
  binary({
18885
19418
  id: "motion",
@@ -21225,12 +21758,48 @@ var ExposeInputSchema = object({
21225
21758
  capabilities: array(string()).optional()
21226
21759
  });
21227
21760
  var UnexposeInputSchema = object({ deviceId: string() });
21761
+ /**
21762
+ * What an exporter may expose besides devices (D968). An action and a
21763
+ * notification target are LIST ENTRIES, never devices (D954): they enter an
21764
+ * exporter's own selection, not the device registry.
21765
+ */
21766
+ var ExportEntityKindSchema = _enum([
21767
+ "device",
21768
+ "action",
21769
+ "notifier"
21770
+ ]);
21771
+ var ExportEntityRefSchema = discriminatedUnion("kind", [
21772
+ object({
21773
+ kind: literal("device"),
21774
+ deviceId: string()
21775
+ }),
21776
+ object({
21777
+ kind: literal("action"),
21778
+ actionId: string().min(1)
21779
+ }),
21780
+ object({
21781
+ kind: literal("notifier"),
21782
+ targetId: string().min(1)
21783
+ })
21784
+ ]);
21785
+ var ExposedEntitySchema = object({
21786
+ entity: ExportEntityRefSchema,
21787
+ exposedAs: string().optional()
21788
+ });
21789
+ var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
21790
+ var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
21228
21791
  method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
21229
21792
  kind: "mutation",
21230
21793
  auth: "admin"
21231
21794
  }), method(UnexposeInputSchema, _void(), {
21232
21795
  kind: "mutation",
21233
21796
  auth: "admin"
21797
+ }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
21798
+ kind: "mutation",
21799
+ auth: "admin"
21800
+ }), optionalMethod(UnexposeEntityInputSchema, _void(), {
21801
+ kind: "mutation",
21802
+ auth: "admin"
21234
21803
  });
21235
21804
  /** Lucide category ids, alphabetical. `ICON_META` indexes into this. */
21236
21805
  var ICON_CATEGORY_IDS = [
@@ -43099,7 +43668,7 @@ var LlmProviderWidgetSchema = object({
43099
43668
  /** ConfigUISchema tree passed through untyped on the wire (the
43100
43669
  * notification-output `ConfigSchemaPassthrough` precedent at
43101
43670
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
43102
- var ConfigSchemaPassthrough$3 = unknown();
43671
+ var ConfigSchemaPassthrough$2 = unknown();
43103
43672
  var LlmProfileKindDescriptorSchema = object({
43104
43673
  kind: LlmProfileKindSchema,
43105
43674
  label: string(),
@@ -43108,9 +43677,9 @@ var LlmProfileKindDescriptorSchema = object({
43108
43677
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
43109
43678
  addonId: string(),
43110
43679
  /** The provider's own fields (`providerConfig`); `password` fields are keychain-held. */
43111
- connectionSchema: ConfigSchemaPassthrough$3,
43680
+ connectionSchema: ConfigSchemaPassthrough$2,
43112
43681
  /** Provider fields that belong under "Advanced" (extra headers, runtime tuning). */
43113
- advancedSchema: ConfigSchemaPassthrough$3.optional(),
43682
+ advancedSchema: ConfigSchemaPassthrough$2.optional(),
43114
43683
  /** The kind's defaults; a model from `listModels` may override any of them. */
43115
43684
  features: LlmModelFeaturesSchema,
43116
43685
  /** `false` = the provider stamps `supportsVision` itself (from what a node reports). */
@@ -43636,10 +44205,10 @@ var CpuBreakdownSchema = object({
43636
44205
  cores: number()
43637
44206
  });
43638
44207
  var MemoryInfoSchema = object({
43639
- percent: number(),
44208
+ percent: number().nullable(),
43640
44209
  totalBytes: number(),
43641
- usedBytes: number(),
43642
- availableBytes: number(),
44210
+ usedBytes: number().nullable(),
44211
+ availableBytes: number().nullable(),
43643
44212
  swapUsedBytes: number(),
43644
44213
  swapTotalBytes: number()
43645
44214
  });
@@ -43914,693 +44483,273 @@ var NodeLoadSeriesSchema = object({
43914
44483
  */
43915
44484
  durable: boolean()
43916
44485
  });
43917
- var GetLoadSeriesInputSchema = object({
43918
- /**
43919
- * The node whose series is wanted.
43920
- *
43921
- * NOT named `nodeId`: the generated cap router strips a top-level
43922
- * `nodeId` from every method input and uses it to ROUTE the call to
43923
- * that node's provider (`generated-cap-routers.ts`). A series target
43924
- * called `nodeId` would silently become a routing pin and never reach
43925
- * the provider. The hub holds every node it hears from, so the
43926
- * ordinary call is unpinned — answered by the hub, for any node.
43927
- */
43928
- forNodeId: string(),
43929
- /**
43930
- * EXCLUSIVE lower bound. A caller passes the newest `atMs` it already
43931
- * holds and receives only what it is missing, so seeding a live chart
43932
- * from this method cannot double a point already drawn.
43933
- */
43934
- sinceMs: number().optional(),
43935
- /**
43936
- * Most points the caller wants PER FUNCTION. The window is reduced to fit,
43937
- * preserving min and max per bucket.
43938
- *
43939
- * Absent means NO reduction — legitimate for a short window and a trap for a
43940
- * long one, which is why a chart passes its own pixel width.
43941
- */
43942
- maxPoints: number().int().positive().optional()
43943
- });
43944
- var SystemMetricsSchema = object({
43945
- cpuPercent: number(),
43946
- memoryPercent: number(),
43947
- memoryUsedMB: number(),
43948
- memoryTotalMB: number(),
43949
- diskPercent: number().optional(),
43950
- temperature: number().optional(),
43951
- gpuPercent: number().optional(),
43952
- gpuMemoryPercent: number().optional()
43953
- });
43954
- 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);
43955
- method(object({
43956
- sourceUrl: string(),
43957
- metadata: ModelConvertMetadataSchema,
43958
- targets: array(ConvertTargetSchema).min(1).readonly(),
43959
- calibrationRef: string().optional(),
43960
- sessionId: string().optional()
43961
- }), ConvertResultSchema, {
43962
- kind: "mutation",
43963
- auth: "admin",
43964
- timeoutMs: 6e5
43965
- });
43966
- method(object({
43967
- targetNodeId: string(),
43968
- modelId: string(),
43969
- format: _enum(MODEL_FORMATS),
43970
- entry: ModelCatalogEntrySchema
43971
- }), object({
43972
- ok: boolean(),
43973
- /** sha256 of the staged tarball (empty for a hub-local no-op). */
43974
- sha256: string(),
43975
- bytes: number(),
43976
- /** The target node's modelsDir the artifact landed in. */
43977
- path: string()
43978
- }), {
43979
- kind: "mutation",
43980
- auth: "admin"
43981
- });
43982
- var MotionRegionSchema = object({
43983
- bbox: BoundingBoxSchema,
43984
- pixelCount: number(),
43985
- intensity: number()
43986
- });
43987
- var MotionAnalysisResultSchema = object({
43988
- detected: boolean(),
43989
- regionCount: number(),
43990
- /** Regions passing minArea filter — used for pipeline triggering. */
43991
- regions: array(MotionRegionSchema).readonly(),
43992
- /** All raw regions from CCL before minArea filter — used for UI debug overlay. */
43993
- rawRegions: array(MotionRegionSchema).readonly(),
43994
- frameWidth: number(),
43995
- frameHeight: number(),
43996
- analysisMs: number()
43997
- });
43998
- DeviceType.Camera, method(object({
43999
- deviceId: number(),
44000
- frame: FrameInputSchema.optional(),
44001
- frameHandle: FrameHandleSchema.optional()
44002
- }), MotionAnalysisResultSchema, {
44003
- role: "admin",
44004
- kind: "mutation"
44005
- }), method(object({ deviceId: number() }), _void(), {
44006
- role: "admin",
44007
- kind: "mutation"
44008
- }), method(_void(), _void(), {
44009
- role: "admin",
44010
- kind: "mutation"
44011
- }), object({
44012
- deviceId: number(),
44013
- detected: boolean(),
44014
- regionCount: number()
44015
- });
44016
- /**
44017
- * `mqtt-broker` — broker-registry cap.
44018
- *
44019
- * NOT a pub/sub proxy. The cap exposes (a) a registry of configured
44020
- * MQTT brokers (external + optionally an embedded `aedes`-backed one)
44021
- * and (b) the connection details a consumer addon needs to spin up
44022
- * its OWN `mqtt.js` client.
44023
- *
44024
- * Why: pub/sub routing over the system event-bus loses fidelity
44025
- * (callback shape, QoS guarantees, will/retain semantics) and adds
44026
- * refcount bookkeeping that addons would rather own themselves. The
44027
- * canonical consumer needs raw `mqtt.js`
44028
- * features anyway — give it the connection config, get out of the way.
44029
- *
44030
- * Consumer flow:
44031
- * const cfg = await ctx.api.mqttBroker.getBrokerConfig({ id })
44032
- * const client = mqtt.connect(cfg.url, { username: cfg.username, … })
44033
- * client.subscribe('zigbee2mqtt/+')
44034
- *
44035
- * Collection mode: multiple brokers (e.g. one local mosquitto + one
44036
- * cloud bridge). The "embedded" entry (when present) is just another
44037
- * broker in the registry — its lifecycle is owned by the addon that
44038
- * spawned it.
44039
- */
44040
- var BrokerKindSchema = _enum(["external", "embedded"]);
44041
- /**
44042
- * Broker live-probe status.
44043
- *
44044
- * - `connected` — last probe completed a clean CONNACK
44045
- * - `disconnected` — no probe has run yet (cold cache)
44046
- * - `auth-failed` — CONNACK refused with auth error (RC 4 / 5)
44047
- * - `unreachable` — TCP connect timed out / refused
44048
- * - `tls-error` — TLS handshake failed (cert / SNI / cipher)
44049
- */
44050
- var BrokerStatusSchema$1 = _enum([
44051
- "connected",
44052
- "disconnected",
44053
- "auth-failed",
44054
- "unreachable",
44055
- "tls-error"
44056
- ]);
44057
- var BrokerInfoSchema$1 = object({
44058
- id: string(),
44059
- name: string(),
44060
- url: string(),
44061
- kind: BrokerKindSchema,
44062
- status: BrokerStatusSchema$1,
44063
- latencyMs: number().nullable(),
44064
- error: string().optional(),
44065
- /** Embedded brokers only: number of MQTT clients currently connected. */
44066
- connectedClients: number().int().nonnegative().optional(),
44067
- /** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
44068
- lastCheckedAt: number().optional()
44069
- });
44070
- /**
44071
- * Connection details — what a consumer needs to call
44072
- * `mqtt.connect(url, options)`. We split URL + credentials so the
44073
- * consumer can pass them as `mqtt.connect(url, { username, password })`
44074
- * instead of stuffing creds into the URL (which leaks them into logs).
44075
- */
44076
- var BrokerConnectionDetailsSchema = object({
44077
- url: string(),
44078
- username: string().optional(),
44079
- password: string().optional(),
44080
- /**
44081
- * Suggested prefix for `clientId`. Each consumer should suffix this
44082
- * with its own discriminator (addon id, instance id) so reconnects
44083
- * don't kick each other off (MQTT spec: clientId must be unique per
44084
- * broker).
44085
- */
44086
- clientIdPrefix: string().optional()
44087
- });
44088
- var AddBrokerInputSchema = object({
44089
- name: string().min(1),
44090
- url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
44091
- username: string().optional(),
44092
- password: string().optional(),
44093
- clientIdPrefix: string().optional()
44094
- });
44095
- var AddBrokerResultSchema = object({ id: string() });
44096
- var IdInputSchema = object({ id: string() });
44097
- var TestResultSchema$1 = discriminatedUnion("ok", [object({
44098
- ok: literal(true),
44099
- latencyMs: number()
44100
- }), object({
44101
- ok: literal(false),
44102
- error: string()
44103
- })]);
44104
- var StartEmbeddedInputSchema = object({
44105
- port: number().int().min(1).max(65535).default(1883),
44106
- /** Allow anonymous connect (no username/password). Default: false. */
44107
- allowAnonymous: boolean().default(false),
44108
- /** Optional shared username/password for clients. */
44109
- username: string().optional(),
44110
- password: string().optional()
44111
- });
44112
- var StartEmbeddedResultSchema = object({
44113
- id: string(),
44114
- url: string()
44115
- });
44116
- var StatusSchema = object({
44117
- brokerCount: number(),
44118
- embeddedRunning: boolean()
44119
- });
44120
- method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
44121
- kind: "mutation",
44122
- auth: "admin"
44123
- }), method(IdInputSchema, _void(), {
44124
- kind: "mutation",
44125
- auth: "admin"
44126
- }), method(IdInputSchema, TestResultSchema$1, {
44127
- kind: "mutation",
44128
- auth: "admin"
44129
- }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
44130
- kind: "mutation",
44131
- auth: "admin"
44132
- }), method(IdInputSchema, _void(), {
44133
- kind: "mutation",
44134
- auth: "admin"
44135
- }), method(_void(), StatusSchema);
44136
- var NetworkEndpointSchema = object({
44137
- url: string(),
44138
- hostname: string(),
44139
- port: number(),
44140
- protocol: _enum(["http", "https"]),
44141
- /**
44142
- * Whether this address survives a restart of its provider. `false` = it is
44143
- * re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
44144
- * hub never selects such an address AUTOMATICALLY for a role (D860), though
44145
- * it stays visible and an operator may still flag it. Absent = stable: every
44146
- * provider except a Quick tunnel owns a name that does not change.
44147
- */
44148
- stable: boolean().optional()
44149
- });
44150
- object({
44151
- url: string(),
44152
- /** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
44153
- stable: boolean().optional(),
44154
- updated: boolean().optional()
44155
- });
44156
- var NetworkAccessStatusSchema = object({
44157
- connected: boolean(),
44158
- endpoint: NetworkEndpointSchema.nullable(),
44159
- error: string().optional()
44160
- });
44161
- /**
44162
- * Optional, richer endpoint shape returned by providers that expose
44163
- * MORE than one ingress concurrently (Tailscale Funnel with mixed
44164
- * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
44165
- * the originating provider config (mode + sourcePort) so the
44166
- * orchestrator UI can label rows distinctly. Providers that expose only
44167
- * one endpoint just omit `listEndpoints` from their provider impl.
44168
- */
44169
- var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
44170
- /**
44171
- * Stable id within the provider — typically `<mode>-<sourcePort>` so
44172
- * the orchestrator can dedupe across `listEndpoints` polls.
44173
- */
44174
- id: string(),
44175
- /** Operator-facing label (mirrors `MeshEndpoint.label`). */
44176
- label: string(),
44177
- /** Optional provider-specific mode tag, used for icon/colour in admin UI. */
44178
- mode: string().optional(),
44179
- /** Originating local port the ingress fronts (informational). */
44180
- sourcePort: number().optional()
44181
- });
44182
- method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
44183
- /**
44184
- * notification-output — canonical, capability-gated notification delivery.
44185
- *
44186
- * Apprise-derived model (see
44187
- * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
44188
- * callers emit ONE canonical `Notification`; each provider declares a
44189
- * per-kind capability descriptor (`TargetKind`), and the pure degrade
44190
- * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
44191
- * message to what the kind supports — callers never special-case a service.
44192
- *
44193
- * DESIGN DECISIONS (locked):
44194
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
44195
- * `setTargetEnabled`), each provider persisting via the `settings-store`
44196
- * cap. Rationale: the admin UI needs one uniform surface across the
44197
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
44198
- * alternative would fork the UI per addon and cannot host the
44199
- * discovery→adopt flow.
44200
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
44201
- * the generated cap-mount auto-`concatCollection`-fans them across every
44202
- * registered provider (notifiers addon + HA addon) so one catalog is
44203
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
44204
- * `addonId` the generated collection router extracts from the call input.
44205
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
44206
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
44207
- * `storage` / `storage-provider` / `recording` caps over the same path. No
44208
- * base64 fallback needed.
44209
- *
44210
- * TODO (deferred, closed-set change — separate decision): add
44211
- * `providerKind: 'notify'` so notification providers surface on the unified
44212
- * admin "Integrations" page.
44213
- */
44214
- /**
44215
- * Zentik-derived typed-media enum — the superset across every kind. Each
44216
- * adapter picks what it supports and the degrade engine filters the rest.
44217
- */
44218
- var AttachmentMediaTypeSchema = _enum([
44219
- "image",
44220
- "video",
44221
- "gif",
44222
- "audio",
44223
- "icon"
44224
- ]);
44225
- /**
44226
- * A single attachment. Exactly one of `url` (remote source, most adapters
44227
- * prefer this) or `bytes` (inline source; required for Pushover-style
44228
- * bytes-only kinds) MUST be present — the degrade engine expresses a
44229
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
44230
- */
44231
- var AttachmentSchema = object({
44232
- mediaType: AttachmentMediaTypeSchema,
44233
- url: string().optional(),
44234
- bytes: _instanceof(Uint8Array).optional(),
44235
- mime: string().optional(),
44236
- name: string().optional()
44237
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
44238
- var NotificationFormatSchema = _enum([
44239
- "text",
44240
- "markdown",
44241
- "html"
44242
- ]);
44243
- /**
44244
- * The CLOSED icon vocabulary an action button may use.
44245
- *
44246
- * A closed set, not a free string, and that is the whole point: an arbitrary
44247
- * icon name is one that ntfy renders, zentik silently drops, and nobody
44248
- * notices — the same class of gap as a zone vocabulary nothing produced
44249
- * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
44250
- * declares `actionIcons: false` and the degrade engine strips the field.
44251
- *
44252
- * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
44253
- * renderer's icon set; "acknowledge" survives an adapter that draws it
44254
- * differently.
44255
- *
44256
- * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
44257
- *
44258
- * These names are for US. **No adapter may forward one verbatim.** Each maps
44259
- * the whole set onto its own renderer's vocabulary through a
44260
- * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
44261
- * fallback, so adding a member here fails every adapter's build until someone
44262
- * decides its glyph, which is the only place that decision can be made
44263
- * honestly.
44264
- *
44265
- * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
44266
- * `disarm` straight through; iOS feeds that string to
44267
- * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
44268
- * and every snooze and alarm button arrived BLANK. A pass-through is not a
44269
- * mapping, and "the field is documented" is not "the value renders".
44270
- *
44271
- * Adding a member is TRAIN-BOUND. The enum lives in the published
44272
- * `@camstack/server` closure and the cap seam validates against the HUB's copy,
44273
- * so an addon that emits a token the running hub does not know does not lose an
44274
- * icon — its whole `send` fails Zod validation and the notification never
44275
- * arrives. Never emit a new token from an addon before the train carrying it.
44276
- */
44277
- var NotificationActionIconSchema = _enum([
44278
- "acknowledge",
44279
- "dismiss",
44280
- "silence",
44281
- "snooze",
44282
- "view",
44283
- "play",
44284
- "open",
44285
- "close",
44286
- "lock",
44287
- "unlock",
44288
- "arm",
44289
- "arm-home",
44290
- "arm-away",
44291
- "arm-night",
44292
- "disarm",
44293
- "light",
44294
- "alert",
44295
- "camera"
44296
- ]);
44297
- /** A single tap-through action button. */
44298
- var NotificationActionSchema = object({
44299
- id: string(),
44300
- label: string(),
44301
- url: string().optional(),
44302
- /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
44303
- icon: NotificationActionIconSchema.optional(),
44304
- /**
44305
- * Renders in a warning style where the notifier supports it.
44306
- *
44307
- * A HINT, never a gate. The callback's authority is its token and nothing
44308
- * else — see `notification-center/action-token.ts` for what that does and
44309
- * does not buy.
44310
- */
44311
- destructive: boolean().optional(),
44312
- /**
44313
- * How the tap should REACH the url.
44314
- *
44315
- * `navigate` (absent, and every button authored before this field) opens it:
44316
- * the phone leaves the notification and shows whatever the callback returns.
44317
- * That is right for a button whose answer the operator wants to read.
44318
- *
44319
- * `background` fires it as a POST and stays put. It exists for the buttons
44320
- * whose whole point is not to interrupt — "silence this for 30 minutes" is
44321
- * an answer to the notification, and being thrown into a browser tab to
44322
- * confirm it costs more attention than the notification did. A backend that
44323
- * cannot do a background call renders it as an ordinary link (the adapters
44324
- * fall back rather than dropping the button), so this is a preference, never
44325
- * a requirement.
44326
- */
44327
- mode: _enum(["navigate", "background"]).optional()
44328
- });
44329
- /** A delivery parameter value: what a notifier declared it takes. */
44330
- var DeliveryParamValueSchema = union([
44331
- string(),
44332
- number(),
44333
- boolean()
44334
- ]);
44335
- /**
44336
- * One extra delivery parameter a notifier KIND declares (D938). A rule that
44337
- * addresses a target of that kind may set it; a kind that declares nothing is
44338
- * never handed one. The admin UI derives its control from this descriptor —
44339
- * no editor knows a service by name.
44340
- *
44341
- * Optional fields are `.optional()`, never `.default()` (a Zod default does not
44342
- * run on the addon cap path).
44343
- */
44344
- var DeliveryParamSchema = object({
44345
- key: string().min(1),
44346
- label: string(),
44347
- /** i18n key of the label, resolved by the declaring addon's namespace. */
44348
- labelKey: string().optional(),
44349
- type: _enum([
44350
- "enum",
44351
- "number",
44352
- "boolean",
44353
- "string"
44354
- ]),
44355
- /** `enum` only: the closed set of accepted values. */
44356
- options: array(object({
44357
- value: string(),
44358
- label: string()
44359
- })).optional(),
44360
- /** `number` only. */
44361
- min: number().optional(),
44362
- max: number().optional(),
44363
- /** What the notifier does when the rule sets nothing; shown as the placeholder. */
44364
- default: DeliveryParamValueSchema.optional()
44365
- });
44366
- /**
44367
- * The canonical notification. `body` is the only hard field (Apprise model).
44368
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
44369
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
44370
- * the adapter maps this ordinal onto its native level. `level?` is an
44371
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
44372
- * `priority` for that one target.
44373
- */
44374
- var NotificationSchema = object({
44375
- body: string(),
44376
- title: string().optional(),
44377
- format: NotificationFormatSchema.default("text"),
44378
- priority: number().int().min(1).max(5).default(3),
44379
- level: string().optional(),
44380
- attachments: array(AttachmentSchema).optional(),
44381
- clickUrl: string().optional(),
44382
- actions: array(NotificationActionSchema).optional(),
44383
- sound: string().optional(),
44384
- ttl: number().optional(),
44385
- tag: string().optional(),
44386
- deviceId: number().optional(),
44387
- eventId: string().optional(),
44388
- metadata: record(string(), unknown()).optional(),
44389
- /**
44390
- * Values for the parameters the target's kind DECLARES in
44391
- * {@link TargetKindSchema} `deliveryParams`. Already validated against the
44392
- * declaration by the sender; a notifier reads only the keys it declared.
44393
- */
44394
- delivery: record(string(), DeliveryParamValueSchema).optional()
44395
- });
44396
- /** One declared native severity/priority level for a kind. */
44397
- var TargetKindLevelSchema = object({
44398
- id: string(),
44399
- label: string(),
44400
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
44401
- ordinal: number().int().min(1).max(5).nullable(),
44402
- flags: object({
44403
- critical: boolean().optional(),
44404
- silent: boolean().optional(),
44405
- noPush: boolean().optional()
44406
- }).optional(),
44407
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
44408
- requires: array(string()).optional(),
44409
- description: string().optional()
44410
- });
44411
- /** The full capability block consulted before dispatch. */
44412
- var TargetKindCapsSchema = object({
44413
- attachments: object({
44414
- mediaTypes: array(AttachmentMediaTypeSchema),
44415
- mode: _enum([
44416
- "url",
44417
- "bytes",
44418
- "both"
44419
- ]),
44420
- max: number().int().nonnegative(),
44421
- maxBytes: number().int().positive().optional()
44422
- }),
44423
- /** Max action buttons (0 = none). */
44424
- actions: number().int().nonnegative(),
44425
- /**
44426
- * Whether this kind renders a per-action ICON.
44427
- *
44428
- * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
44429
- * run on the addon cap path — three production failures in one day taught
44430
- * this repo that once. Absent is read as false by the degrade engine, which
44431
- * is the safe direction: an icon that is not rendered costs nothing, an icon
44432
- * assumed and dropped costs the operator's trust in the field.
44433
- */
44434
- actionIcons: boolean().optional(),
44435
- levels: array(TargetKindLevelSchema),
44436
- format: array(NotificationFormatSchema),
44437
- clickUrl: boolean(),
44438
- sound: boolean(),
44439
- ttl: boolean(),
44440
- bodyMaxLen: number().int().positive()
44441
- });
44442
- /**
44443
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
44444
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
44445
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
44446
- * the union is large and not meant for runtime validation here; the exported
44447
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
44448
- */
44449
- var ConfigSchemaPassthrough$2 = unknown();
44450
- var TargetKindSchema = object({
44451
- kind: string(),
44452
- label: string(),
44453
- icon: string(),
44454
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
44455
- addonId: string(),
44456
- /**
44457
- * URL of the kind's bundled BRAND icon, served by the providing addon over
44458
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
44459
- * when the addon bundles no icon for that kind — the client then falls back
44460
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
44461
- *
44462
- * Root-relative on purpose: it resolves against whatever origin serves a web
44463
- * client, and a native client joins it onto its own hub base.
44464
- *
44465
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
44466
- * field that survived only because the runtime cap-router forwards provider
44467
- * output verbatim — so every consumer had to re-declare it by hand to stop
44468
- * its own Zod parse from stripping it, and the whole arrangement would have
44469
- * broken silently the moment output validation was tightened anywhere.
44470
- */
44471
- iconUrl: string().optional(),
44486
+ var GetLoadSeriesInputSchema = object({
44472
44487
  /**
44473
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
44474
- *
44475
- * The server knows this and therefore says it, because the client cannot
44476
- * safely guess: a React-Native client renders SVG and raster through two
44477
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
44478
- * not decode SVG on iOS/Android), so without this it silently fell back to a
44479
- * placeholder glyph for every vector icon while the web build looked fine.
44488
+ * The node whose series is wanted.
44480
44489
  *
44481
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
44482
- * not been updated — a client that cannot determine the type should prefer
44483
- * its raster path, which is the safe default for an unknown image.
44490
+ * NOT named `nodeId`: the generated cap router strips a top-level
44491
+ * `nodeId` from every method input and uses it to ROUTE the call to
44492
+ * that node's provider (`generated-cap-routers.ts`). A series target
44493
+ * called `nodeId` would silently become a routing pin and never reach
44494
+ * the provider. The hub holds every node it hears from, so the
44495
+ * ordinary call is unpinned — answered by the hub, for any node.
44484
44496
  */
44485
- iconMediaType: string().optional(),
44486
- configSchema: ConfigSchemaPassthrough$2,
44497
+ forNodeId: string(),
44487
44498
  /**
44488
- * The config keys that decide WHERE this kind's secrets are sent (server,
44489
- * base URL, instance, chat, recipient). A save that changes any of them
44490
- * while leaving a secret redacted is refused by the provider (D634), so an
44491
- * editor uses this to ask for the secret on exactly those edits instead of
44492
- * on every edit.
44493
- *
44494
- * Optional: absent from a provider that holds no secret (the Home Assistant
44495
- * kinds) and from a provider older than this field — an editor that finds
44496
- * it absent must assume ANY edit may need the secret.
44499
+ * EXCLUSIVE lower bound. A caller passes the newest `atMs` it already
44500
+ * holds and receives only what it is missing, so seeding a live chart
44501
+ * from this method cannot double a point already drawn.
44497
44502
  */
44498
- destinationKeys: array(string()).optional(),
44499
- supportsDiscovery: boolean(),
44503
+ sinceMs: number().optional(),
44500
44504
  /**
44501
- * Extra delivery parameters a rule may set for a target of this kind (D938).
44502
- * Absent = none; the rule editor then offers nothing.
44505
+ * Most points the caller wants PER FUNCTION. The window is reduced to fit,
44506
+ * preserving min and max per bucket.
44507
+ *
44508
+ * Absent means NO reduction — legitimate for a short window and a trap for a
44509
+ * long one, which is why a chart passes its own pixel width.
44503
44510
  */
44504
- deliveryParams: array(DeliveryParamSchema).optional(),
44505
- caps: TargetKindCapsSchema
44511
+ maxPoints: number().int().positive().optional()
44512
+ });
44513
+ var SystemMetricsSchema = object({
44514
+ cpuPercent: number(),
44515
+ /** `null` when the memory sample failed or was not taken yet (D393). */
44516
+ memoryPercent: number().nullable(),
44517
+ memoryUsedMB: number().nullable(),
44518
+ memoryTotalMB: number(),
44519
+ diskPercent: number().optional(),
44520
+ temperature: number().optional(),
44521
+ gpuPercent: number().optional(),
44522
+ gpuMemoryPercent: number().optional()
44523
+ });
44524
+ 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);
44525
+ method(object({
44526
+ sourceUrl: string(),
44527
+ metadata: ModelConvertMetadataSchema,
44528
+ targets: array(ConvertTargetSchema).min(1).readonly(),
44529
+ calibrationRef: string().optional(),
44530
+ sessionId: string().optional()
44531
+ }), ConvertResultSchema, {
44532
+ kind: "mutation",
44533
+ auth: "admin",
44534
+ timeoutMs: 6e5
44535
+ });
44536
+ method(object({
44537
+ targetNodeId: string(),
44538
+ modelId: string(),
44539
+ format: _enum(MODEL_FORMATS),
44540
+ entry: ModelCatalogEntrySchema
44541
+ }), object({
44542
+ ok: boolean(),
44543
+ /** sha256 of the staged tarball (empty for a hub-local no-op). */
44544
+ sha256: string(),
44545
+ bytes: number(),
44546
+ /** The target node's modelsDir the artifact landed in. */
44547
+ path: string()
44548
+ }), {
44549
+ kind: "mutation",
44550
+ auth: "admin"
44551
+ });
44552
+ var MotionRegionSchema = object({
44553
+ bbox: BoundingBoxSchema,
44554
+ pixelCount: number(),
44555
+ intensity: number()
44556
+ });
44557
+ var MotionAnalysisResultSchema = object({
44558
+ detected: boolean(),
44559
+ regionCount: number(),
44560
+ /** Regions passing minArea filter — used for pipeline triggering. */
44561
+ regions: array(MotionRegionSchema).readonly(),
44562
+ /** All raw regions from CCL before minArea filter — used for UI debug overlay. */
44563
+ rawRegions: array(MotionRegionSchema).readonly(),
44564
+ frameWidth: number(),
44565
+ frameHeight: number(),
44566
+ analysisMs: number()
44567
+ });
44568
+ DeviceType.Camera, method(object({
44569
+ deviceId: number(),
44570
+ frame: FrameInputSchema.optional(),
44571
+ frameHandle: FrameHandleSchema.optional()
44572
+ }), MotionAnalysisResultSchema, {
44573
+ role: "admin",
44574
+ kind: "mutation"
44575
+ }), method(object({ deviceId: number() }), _void(), {
44576
+ role: "admin",
44577
+ kind: "mutation"
44578
+ }), method(_void(), _void(), {
44579
+ role: "admin",
44580
+ kind: "mutation"
44581
+ }), object({
44582
+ deviceId: number(),
44583
+ detected: boolean(),
44584
+ regionCount: number()
44506
44585
  });
44507
44586
  /**
44508
- * A persisted target. `config` holds secrets; providers REDACT secret fields
44509
- * (return a presence marker only) when serving `listTargets` — never
44510
- * round-trip a stored secret to the UI.
44587
+ * `mqtt-broker` — broker-registry cap.
44588
+ *
44589
+ * NOT a pub/sub proxy. The cap exposes (a) a registry of configured
44590
+ * MQTT brokers (external + optionally an embedded `aedes`-backed one)
44591
+ * and (b) the connection details a consumer addon needs to spin up
44592
+ * its OWN `mqtt.js` client.
44593
+ *
44594
+ * Why: pub/sub routing over the system event-bus loses fidelity
44595
+ * (callback shape, QoS guarantees, will/retain semantics) and adds
44596
+ * refcount bookkeeping that addons would rather own themselves. The
44597
+ * canonical consumer needs raw `mqtt.js`
44598
+ * features anyway — give it the connection config, get out of the way.
44599
+ *
44600
+ * Consumer flow:
44601
+ * const cfg = await ctx.api.mqttBroker.getBrokerConfig({ id })
44602
+ * const client = mqtt.connect(cfg.url, { username: cfg.username, … })
44603
+ * client.subscribe('zigbee2mqtt/+')
44604
+ *
44605
+ * Collection mode: multiple brokers (e.g. one local mosquitto + one
44606
+ * cloud bridge). The "embedded" entry (when present) is just another
44607
+ * broker in the registry — its lifecycle is owned by the addon that
44608
+ * spawned it.
44511
44609
  */
44512
- var TargetSchema = object({
44610
+ var BrokerKindSchema = _enum(["external", "embedded"]);
44611
+ /**
44612
+ * Broker live-probe status.
44613
+ *
44614
+ * - `connected` — last probe completed a clean CONNACK
44615
+ * - `disconnected` — no probe has run yet (cold cache)
44616
+ * - `auth-failed` — CONNACK refused with auth error (RC 4 / 5)
44617
+ * - `unreachable` — TCP connect timed out / refused
44618
+ * - `tls-error` — TLS handshake failed (cert / SNI / cipher)
44619
+ */
44620
+ var BrokerStatusSchema$1 = _enum([
44621
+ "connected",
44622
+ "disconnected",
44623
+ "auth-failed",
44624
+ "unreachable",
44625
+ "tls-error"
44626
+ ]);
44627
+ var BrokerInfoSchema$1 = object({
44513
44628
  id: string(),
44514
44629
  name: string(),
44515
- kind: string(),
44516
- addonId: string(),
44517
- enabled: boolean(),
44518
- config: record(string(), unknown())
44519
- });
44520
- /** A discovery-surfaced candidate (config is partial + non-secret). */
44521
- var DiscoveredTargetSchema = object({
44522
- kind: string(),
44523
- suggestedName: string(),
44524
- config: record(string(), unknown())
44525
- });
44526
- /** The degrade engine's report — what was resolved / dropped / degraded. */
44527
- var RenderedAsSchema = object({
44528
- level: string(),
44529
- format: NotificationFormatSchema,
44530
- attachmentsSent: number().int().nonnegative(),
44531
- actionsSent: number().int().nonnegative(),
44532
- truncated: boolean(),
44533
- dropped: array(string())
44534
- });
44535
- var SendResultSchema = object({
44536
- success: boolean(),
44630
+ url: string(),
44631
+ kind: BrokerKindSchema,
44632
+ status: BrokerStatusSchema$1,
44633
+ latencyMs: number().nullable(),
44537
44634
  error: string().optional(),
44538
- renderedAs: RenderedAsSchema.optional()
44635
+ /** Embedded brokers only: number of MQTT clients currently connected. */
44636
+ connectedClients: number().int().nonnegative().optional(),
44637
+ /** Epoch ms of the last live probe (external) or aedes snapshot (embedded). */
44638
+ lastCheckedAt: number().optional()
44539
44639
  });
44540
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
44541
- var TestResultSchema = SendResultSchema;
44542
44640
  /**
44543
- * Pre-save credential probe against a partial (unsaved) target config.
44544
- * `resolvedUrl` never includes secrets (token stripped from path).
44641
+ * Connection details — what a consumer needs to call
44642
+ * `mqtt.connect(url, options)`. We split URL + credentials so the
44643
+ * consumer can pass them as `mqtt.connect(url, { username, password })`
44644
+ * instead of stuffing creds into the URL (which leaks them into logs).
44545
44645
  */
44546
- var ProbeTargetConfigResultSchema = object({
44547
- ok: boolean(),
44548
- resolvedUrl: string(),
44549
- httpStatus: number().int().optional(),
44550
- stateInstance: string().optional(),
44551
- error: string().optional()
44552
- });
44553
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
44554
- kind: string(),
44555
- config: record(string(), unknown()).optional(),
44646
+ var BrokerConnectionDetailsSchema = object({
44647
+ url: string(),
44648
+ username: string().optional(),
44649
+ password: string().optional(),
44556
44650
  /**
44557
- * When editing / duplicating, the blank password field never round-trips
44558
- * the stored token. Pass the source target id so the provider can restore
44559
- * secrets server-side before dialling (same merge as upsert).
44651
+ * Suggested prefix for `clientId`. Each consumer should suffix this
44652
+ * with its own discriminator (addon id, instance id) so reconnects
44653
+ * don't kick each other off (MQTT spec: clientId must be unique per
44654
+ * broker).
44560
44655
  */
44561
- targetId: string().optional()
44562
- }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
44563
- kind: string(),
44564
- config: record(string(), unknown()),
44565
- /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
44566
- targetId: string().optional()
44567
- }), ProbeTargetConfigResultSchema, {
44568
- kind: "mutation",
44569
- auth: "admin"
44570
- }), method(object({
44571
- targetId: string(),
44572
- notification: NotificationSchema
44573
- }), SendResultSchema, {
44656
+ clientIdPrefix: string().optional()
44657
+ });
44658
+ var AddBrokerInputSchema = object({
44659
+ name: string().min(1),
44660
+ url: string().regex(/^(mqtt|mqtts|ws|wss):\/\//, "URL must start with mqtt(s):// or ws(s)://"),
44661
+ username: string().optional(),
44662
+ password: string().optional(),
44663
+ clientIdPrefix: string().optional()
44664
+ });
44665
+ var AddBrokerResultSchema = object({ id: string() });
44666
+ var IdInputSchema = object({ id: string() });
44667
+ var TestResultSchema$1 = discriminatedUnion("ok", [object({
44668
+ ok: literal(true),
44669
+ latencyMs: number()
44670
+ }), object({
44671
+ ok: literal(false),
44672
+ error: string()
44673
+ })]);
44674
+ var StartEmbeddedInputSchema = object({
44675
+ port: number().int().min(1).max(65535).default(1883),
44676
+ /** Allow anonymous connect (no username/password). Default: false. */
44677
+ allowAnonymous: boolean().default(false),
44678
+ /** Optional shared username/password for clients. */
44679
+ username: string().optional(),
44680
+ password: string().optional()
44681
+ });
44682
+ var StartEmbeddedResultSchema = object({
44683
+ id: string(),
44684
+ url: string()
44685
+ });
44686
+ var StatusSchema = object({
44687
+ brokerCount: number(),
44688
+ embeddedRunning: boolean()
44689
+ });
44690
+ method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
44574
44691
  kind: "mutation",
44575
44692
  auth: "admin"
44576
- }), method(object({
44577
- targetId: string(),
44578
- sample: NotificationSchema.optional()
44579
- }), TestResultSchema, {
44693
+ }), method(IdInputSchema, _void(), {
44580
44694
  kind: "mutation",
44581
44695
  auth: "admin"
44582
- }), method(object({
44583
- target: TargetSchema,
44584
- /**
44585
- * Duplicate flow: create a NEW target id while copying secrets from an
44586
- * existing row. The UI never holds the stored token; blank / redacted
44587
- * password fields are restored from this source (D634 still applies if
44588
- * destinations diverge without a re-typed secret).
44589
- */
44590
- cloneSecretsFrom: string().optional()
44591
- }), TargetSchema, {
44696
+ }), method(IdInputSchema, TestResultSchema$1, {
44592
44697
  kind: "mutation",
44593
44698
  auth: "admin"
44594
- }), method(object({ targetId: string() }), _void(), {
44699
+ }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
44595
44700
  kind: "mutation",
44596
44701
  auth: "admin"
44597
- }), method(object({
44598
- targetId: string(),
44599
- enabled: boolean()
44600
- }), _void(), {
44702
+ }), method(IdInputSchema, _void(), {
44601
44703
  kind: "mutation",
44602
44704
  auth: "admin"
44705
+ }), method(_void(), StatusSchema);
44706
+ var NetworkEndpointSchema = object({
44707
+ url: string(),
44708
+ hostname: string(),
44709
+ port: number(),
44710
+ protocol: _enum(["http", "https"]),
44711
+ /**
44712
+ * Whether this address survives a restart of its provider. `false` = it is
44713
+ * re-issued every time (a Quick tunnel's random `*.trycloudflare.com`); the
44714
+ * hub never selects such an address AUTOMATICALLY for a role (D860), though
44715
+ * it stays visible and an operator may still flag it. Absent = stable: every
44716
+ * provider except a Quick tunnel owns a name that does not change.
44717
+ */
44718
+ stable: boolean().optional()
44719
+ });
44720
+ object({
44721
+ url: string(),
44722
+ /** Mirrors {@link NetworkEndpointSchema} `stable`; absent = stable. */
44723
+ stable: boolean().optional(),
44724
+ updated: boolean().optional()
44725
+ });
44726
+ var NetworkAccessStatusSchema = object({
44727
+ connected: boolean(),
44728
+ endpoint: NetworkEndpointSchema.nullable(),
44729
+ error: string().optional()
44730
+ });
44731
+ /**
44732
+ * Optional, richer endpoint shape returned by providers that expose
44733
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
44734
+ * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
44735
+ * the originating provider config (mode + sourcePort) so the
44736
+ * orchestrator UI can label rows distinctly. Providers that expose only
44737
+ * one endpoint just omit `listEndpoints` from their provider impl.
44738
+ */
44739
+ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
44740
+ /**
44741
+ * Stable id within the provider — typically `<mode>-<sourcePort>` so
44742
+ * the orchestrator can dedupe across `listEndpoints` polls.
44743
+ */
44744
+ id: string(),
44745
+ /** Operator-facing label (mirrors `MeshEndpoint.label`). */
44746
+ label: string(),
44747
+ /** Optional provider-specific mode tag, used for icon/colour in admin UI. */
44748
+ mode: string().optional(),
44749
+ /** Originating local port the ingress fronts (informational). */
44750
+ sourcePort: number().optional()
44603
44751
  });
44752
+ method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
44604
44753
  var AUDIO_MACRO_LABELS = [
44605
44754
  {
44606
44755
  id: "speech",
@@ -51001,14 +51150,6 @@ var EngineProvisioningSchema = object({
51001
51150
  */
51002
51151
  configIssues: array(string()).optional()
51003
51152
  });
51004
- var PipelineStepInputSchema = lazy(() => object({
51005
- addonId: string(),
51006
- modelId: string().optional(),
51007
- enabled: boolean().default(true),
51008
- children: array(PipelineStepInputSchema).optional(),
51009
- settings: record(string(), unknown()).optional(),
51010
- jumpDeviceKey: string().optional()
51011
- }));
51012
51153
  var ModelSubstitutionSchema = object({
51013
51154
  addonId: string(),
51014
51155
  chosen: string(),
@@ -51087,7 +51228,6 @@ var ConfigUISchemaBridge = custom();
51087
51228
  var ConfigUISchemaNullableBridge = custom();
51088
51229
  var InferenceCapabilitiesBridge = custom();
51089
51230
  var ModelAvailabilityListBridge = custom();
51090
- var PipelineRunResultBridge = custom();
51091
51231
  method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
51092
51232
  modelId: string(),
51093
51233
  settings: record(string(), unknown()).readonly()
@@ -58761,7 +58901,7 @@ object({
58761
58901
  /** Ms epoch of the last value received from the appliance; 0 = never. */
58762
58902
  lastFetchedAt: number()
58763
58903
  });
58764
- var DeviceIdSchema = number().int().nonnegative();
58904
+ var DeviceIdSchema$1 = number().int().nonnegative();
58765
58905
  /** A value a program option or setting can take. */
58766
58906
  var DishwasherValueSchema = union([
58767
58907
  boolean(),
@@ -58825,77 +58965,77 @@ var DishwasherOptionsSchema = object({
58825
58965
  options: array(DishwasherOptionDescriptorSchema)
58826
58966
  });
58827
58967
  DeviceType.Dishwasher, method(object({
58828
- deviceId: DeviceIdSchema,
58968
+ deviceId: DeviceIdSchema$1,
58829
58969
  program: string().min(1).optional(),
58830
58970
  options: record(string(), DishwasherValueSchema).optional()
58831
58971
  }), _void(), {
58832
58972
  kind: "mutation",
58833
58973
  auth: "protected"
58834
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
58974
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
58835
58975
  kind: "mutation",
58836
58976
  auth: "protected"
58837
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
58977
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
58838
58978
  kind: "mutation",
58839
58979
  auth: "protected"
58840
- }), method(object({ deviceId: DeviceIdSchema }), _void(), {
58980
+ }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
58841
58981
  kind: "mutation",
58842
58982
  auth: "protected"
58843
58983
  }), method(object({
58844
- deviceId: DeviceIdSchema,
58984
+ deviceId: DeviceIdSchema$1,
58845
58985
  program: string().min(1)
58846
58986
  }), _void(), {
58847
58987
  kind: "mutation",
58848
58988
  auth: "protected"
58849
58989
  }), method(object({
58850
- deviceId: DeviceIdSchema,
58990
+ deviceId: DeviceIdSchema$1,
58851
58991
  on: boolean()
58852
58992
  }), _void(), {
58853
58993
  kind: "mutation",
58854
58994
  auth: "protected"
58855
58995
  }), method(object({
58856
- deviceId: DeviceIdSchema,
58996
+ deviceId: DeviceIdSchema$1,
58857
58997
  on: boolean()
58858
58998
  }), _void(), {
58859
58999
  kind: "mutation",
58860
59000
  auth: "protected"
58861
59001
  }), method(object({
58862
- deviceId: DeviceIdSchema,
59002
+ deviceId: DeviceIdSchema$1,
58863
59003
  on: boolean()
58864
59004
  }), _void(), {
58865
59005
  kind: "mutation",
58866
59006
  auth: "protected"
58867
59007
  }), method(object({
58868
- deviceId: DeviceIdSchema,
59008
+ deviceId: DeviceIdSchema$1,
58869
59009
  on: boolean()
58870
59010
  }), _void(), {
58871
59011
  kind: "mutation",
58872
59012
  auth: "protected"
58873
59013
  }), method(object({
58874
- deviceId: DeviceIdSchema,
59014
+ deviceId: DeviceIdSchema$1,
58875
59015
  on: boolean()
58876
59016
  }), _void(), {
58877
59017
  kind: "mutation",
58878
59018
  auth: "protected"
58879
59019
  }), method(object({
58880
- deviceId: DeviceIdSchema,
59020
+ deviceId: DeviceIdSchema$1,
58881
59021
  on: boolean()
58882
59022
  }), _void(), {
58883
59023
  kind: "mutation",
58884
59024
  auth: "protected"
58885
59025
  }), method(object({
58886
- deviceId: DeviceIdSchema,
59026
+ deviceId: DeviceIdSchema$1,
58887
59027
  on: boolean()
58888
59028
  }), _void(), {
58889
59029
  kind: "mutation",
58890
59030
  auth: "protected"
58891
59031
  }), method(object({
58892
- deviceId: DeviceIdSchema,
59032
+ deviceId: DeviceIdSchema$1,
58893
59033
  key: string().min(1),
58894
59034
  value: DishwasherValueSchema
58895
59035
  }), _void(), {
58896
59036
  kind: "mutation",
58897
59037
  auth: "protected"
58898
- }), method(object({ deviceId: DeviceIdSchema }), DishwasherOptionsSchema, { auth: "protected" });
59038
+ }), method(object({ deviceId: DeviceIdSchema$1 }), DishwasherOptionsSchema, { auth: "protected" });
58899
59039
  DeviceType.Camera;
58900
59040
  object({
58901
59041
  /** Ms epoch of the last press. null = never observed since this provider started. */
@@ -61333,7 +61473,8 @@ var TopologyNodeSchema = object({
61333
61473
  isHub: boolean(),
61334
61474
  isOnline: boolean(),
61335
61475
  cpuPercent: number(),
61336
- memoryPercent: number(),
61476
+ /** `null` when the node's memory read failed or it has not reported one (D393). */
61477
+ memoryPercent: number().nullable(),
61337
61478
  uptime: number(),
61338
61479
  lastSeen: string(),
61339
61480
  localIps: array(string()).readonly(),
@@ -61868,9 +62009,10 @@ object({
61868
62009
  * combined reading; per-hopper levels live in `food1`/`food2`. Null when
61869
62010
  * the device has not reported yet. */
61870
62011
  foodLevel: number().nullable().meta(readingMeta("none")),
61871
- /** Food in the BOWL as the feeder reports it (`state.bowl`), in the
61872
- * feeder's own units. Null when the model reports none. */
61873
- bowlFood: number().nullable().meta(readingMeta("none")),
62012
+ /** Food in the BOWL, a mass (stored in kilograms, D944). Reported only by a
62013
+ * feeder with a bowl scale; null when the model has none or its reading is
62014
+ * of an unproven unit. */
62015
+ bowlFood: number().nullable().meta(readingMeta("mass")),
61874
62016
  /** True while the pet is eating (`state.eating`). */
61875
62017
  eating: boolean(),
61876
62018
  /** Whether `markFoodReplenished` does anything on this model. When false
@@ -61983,6 +62125,64 @@ DeviceType.PetFeeder, method(object({
61983
62125
  kind: "mutation",
61984
62126
  auth: "protected"
61985
62127
  });
62128
+ object({
62129
+ /** Water in the tank, 0..100 (%). Null while the source has not reported. */
62130
+ waterLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62131
+ /** Water in the tank, in litres. Null while the source has not reported. */
62132
+ waterVolume: number().min(0).nullable().meta(readingMeta("volume")),
62133
+ /** True when the tank is empty. Null while unknown. */
62134
+ empty: boolean().nullable(),
62135
+ /** True while the pump is running. Null while unknown. */
62136
+ pumpRunning: boolean().nullable(),
62137
+ /** True when the pump works on an on/off duty cycle rather than continuously. */
62138
+ pumpIntervalEnabled: boolean().nullable(),
62139
+ /** How long the pump runs per cycle, in seconds. */
62140
+ pumpOnTime: number().min(0).nullable().meta(readingMeta("duration")),
62141
+ /** How long the pump rests per cycle, in seconds. */
62142
+ pumpOffTime: number().min(0).nullable().meta(readingMeta("duration")),
62143
+ /** Water drunk in the last drink, in litres. */
62144
+ lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
62145
+ /** Water drunk in the current hour, in litres. */
62146
+ currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
62147
+ /** Water drunk over the last 24 hours (rolling), in litres. */
62148
+ last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
62149
+ /** Water drunk since local midnight, in litres. */
62150
+ todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
62151
+ /** Decoded fault message. Null means healthy; a non-null string is an active fault. */
62152
+ error: string().nullable(),
62153
+ /** Ms epoch when the slice was last refreshed. */
62154
+ lastFetchedAt: number()
62155
+ });
62156
+ var DeviceIdSchema = number().int().nonnegative();
62157
+ var MinutesSchema = number().int().min(0);
62158
+ DeviceType.PetFountain, method(object({
62159
+ deviceId: DeviceIdSchema,
62160
+ on: boolean()
62161
+ }), _void(), {
62162
+ kind: "mutation",
62163
+ auth: "protected"
62164
+ }), method(object({
62165
+ deviceId: DeviceIdSchema,
62166
+ on: boolean()
62167
+ }), _void(), {
62168
+ kind: "mutation",
62169
+ auth: "protected"
62170
+ }), method(object({
62171
+ deviceId: DeviceIdSchema,
62172
+ minutes: MinutesSchema
62173
+ }), _void(), {
62174
+ kind: "mutation",
62175
+ auth: "protected"
62176
+ }), method(object({
62177
+ deviceId: DeviceIdSchema,
62178
+ minutes: MinutesSchema
62179
+ }), _void(), {
62180
+ kind: "mutation",
62181
+ auth: "protected"
62182
+ }), method(object({ deviceId: DeviceIdSchema }), _void(), {
62183
+ kind: "mutation",
62184
+ auth: "protected"
62185
+ });
61986
62186
  var VehicleSchema = object({
61987
62187
  id: string(),
61988
62188
  name: string(),
@@ -66580,7 +66780,7 @@ discriminatedUnion("kind", [object({
66580
66780
  })]);
66581
66781
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
66582
66782
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
66583
- 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;
66783
+ 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;
66584
66784
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
66585
66785
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
66586
66786
  new Set(CAP_NAMES);
@@ -66633,7 +66833,7 @@ var AccessoryKind = {
66633
66833
  PrivacyMask: DeviceRole.PrivacyMask
66634
66834
  };
66635
66835
  AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
66636
- 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;
66836
+ 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;
66637
66837
  new Set(Object.values(DeviceType));
66638
66838
  Object.freeze({
66639
66839
  "accessories.getStatus": {
@@ -68568,6 +68768,13 @@ Object.freeze({
68568
68768
  access: "create",
68569
68769
  role: "admin"
68570
68770
  },
68771
+ "deviceExport.exposeEntity": {
68772
+ capName: "device-export",
68773
+ capScope: "system",
68774
+ addonId: null,
68775
+ access: "create",
68776
+ role: "admin"
68777
+ },
68571
68778
  "deviceExport.getDeviceLiveContribution": {
68572
68779
  capName: "device-export",
68573
68780
  capScope: "system",
@@ -68596,6 +68803,13 @@ Object.freeze({
68596
68803
  access: "view",
68597
68804
  role: "admin"
68598
68805
  },
68806
+ "deviceExport.listExposedEntities": {
68807
+ capName: "device-export",
68808
+ capScope: "system",
68809
+ addonId: null,
68810
+ access: "view",
68811
+ role: "admin"
68812
+ },
68599
68813
  "deviceExport.listSupportedDeviceKinds": {
68600
68814
  capName: "device-export",
68601
68815
  capScope: "system",
@@ -68603,6 +68817,13 @@ Object.freeze({
68603
68817
  access: "view",
68604
68818
  role: "admin"
68605
68819
  },
68820
+ "deviceExport.listSupportedEntityKinds": {
68821
+ capName: "device-export",
68822
+ capScope: "system",
68823
+ addonId: null,
68824
+ access: "view",
68825
+ role: "admin"
68826
+ },
68606
68827
  "deviceExport.unexposeDevice": {
68607
68828
  capName: "device-export",
68608
68829
  capScope: "system",
@@ -68610,6 +68831,13 @@ Object.freeze({
68610
68831
  access: "create",
68611
68832
  role: "admin"
68612
68833
  },
68834
+ "deviceExport.unexposeEntity": {
68835
+ capName: "device-export",
68836
+ capScope: "system",
68837
+ addonId: null,
68838
+ access: "create",
68839
+ role: "admin"
68840
+ },
68613
68841
  "deviceExtension.listCandidates": {
68614
68842
  capName: "device-extension",
68615
68843
  capScope: "device",
@@ -72075,6 +72303,48 @@ Object.freeze({
72075
72303
  access: "create",
72076
72304
  role: "viewer"
72077
72305
  },
72306
+ "petFountain.getStatus": {
72307
+ capName: "pet-fountain",
72308
+ capScope: "device",
72309
+ addonId: null,
72310
+ access: "view",
72311
+ role: "viewer"
72312
+ },
72313
+ "petFountain.resetDailyConsumption": {
72314
+ capName: "pet-fountain",
72315
+ capScope: "device",
72316
+ addonId: null,
72317
+ access: "delete",
72318
+ role: "viewer"
72319
+ },
72320
+ "petFountain.setPump": {
72321
+ capName: "pet-fountain",
72322
+ capScope: "device",
72323
+ addonId: null,
72324
+ access: "create",
72325
+ role: "viewer"
72326
+ },
72327
+ "petFountain.setPumpInterval": {
72328
+ capName: "pet-fountain",
72329
+ capScope: "device",
72330
+ addonId: null,
72331
+ access: "create",
72332
+ role: "viewer"
72333
+ },
72334
+ "petFountain.setPumpOffMinutes": {
72335
+ capName: "pet-fountain",
72336
+ capScope: "device",
72337
+ addonId: null,
72338
+ access: "create",
72339
+ role: "viewer"
72340
+ },
72341
+ "petFountain.setPumpOnMinutes": {
72342
+ capName: "pet-fountain",
72343
+ capScope: "device",
72344
+ addonId: null,
72345
+ access: "create",
72346
+ role: "viewer"
72347
+ },
72078
72348
  "pipelineAnalytics.addRetrainFrameFromRecording": {
72079
72349
  capName: "pipeline-analytics",
72080
72350
  capScope: "device",
@@ -78557,6 +78827,100 @@ var APPLIANCE_CAP_VOCABULARY = {
78557
78827
  }
78558
78828
  ]
78559
78829
  },
78830
+ "pet-fountain": {
78831
+ scope: "device",
78832
+ noun: t("Pet fountain", "Fontana per animali"),
78833
+ actions: [
78834
+ {
78835
+ id: "petFountain.pumpOn",
78836
+ method: "setPump",
78837
+ label: t("Pump on", "Pompa accesa"),
78838
+ phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
78839
+ icon: "play",
78840
+ args: { on: true }
78841
+ },
78842
+ {
78843
+ id: "petFountain.pumpOff",
78844
+ method: "setPump",
78845
+ label: t("Pump off", "Pompa spenta"),
78846
+ phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
78847
+ icon: "square",
78848
+ args: { on: false }
78849
+ },
78850
+ {
78851
+ id: "petFountain.intervalOn",
78852
+ method: "setPumpInterval",
78853
+ label: t("Pump cycle on", "Ciclo pompa attivo"),
78854
+ phrase: t("Run the pump of {device} on its on/off cycle", "Fai lavorare la pompa di {device} a ciclo acceso/spento"),
78855
+ icon: "timer",
78856
+ args: { on: true }
78857
+ },
78858
+ {
78859
+ id: "petFountain.intervalOff",
78860
+ method: "setPumpInterval",
78861
+ label: t("Pump cycle off", "Ciclo pompa spento"),
78862
+ phrase: t("Run the pump of {device} continuously", "Fai lavorare la pompa di {device} in continuo"),
78863
+ icon: "timer-off",
78864
+ args: { on: false }
78865
+ },
78866
+ {
78867
+ id: "petFountain.pumpOnMinutes",
78868
+ method: "setPumpOnMinutes",
78869
+ label: t("Set pump run time", "Imposta il tempo di lavoro della pompa"),
78870
+ phrase: t("Set the pump of {device} to run {minutes} minutes per cycle", "Imposta la pompa di {device} a lavorare {minutes} minuti per ciclo"),
78871
+ icon: "timer",
78872
+ openArgs: [{
78873
+ key: "minutes",
78874
+ label: t("Minutes", "Minuti"),
78875
+ offer: {
78876
+ kind: "number",
78877
+ min: 0,
78878
+ step: 1,
78879
+ unit: "min"
78880
+ }
78881
+ }]
78882
+ },
78883
+ {
78884
+ id: "petFountain.pumpOffMinutes",
78885
+ method: "setPumpOffMinutes",
78886
+ label: t("Set pump rest time", "Imposta il tempo di riposo della pompa"),
78887
+ phrase: t("Set the pump of {device} to rest {minutes} minutes per cycle", "Imposta la pompa di {device} a riposare {minutes} minuti per ciclo"),
78888
+ icon: "timer-off",
78889
+ openArgs: [{
78890
+ key: "minutes",
78891
+ label: t("Minutes", "Minuti"),
78892
+ offer: {
78893
+ kind: "number",
78894
+ min: 0,
78895
+ step: 1,
78896
+ unit: "min"
78897
+ }
78898
+ }]
78899
+ },
78900
+ {
78901
+ id: "petFountain.resetDailyConsumption",
78902
+ method: "resetDailyConsumption",
78903
+ label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
78904
+ phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
78905
+ icon: "rotate-ccw"
78906
+ }
78907
+ ],
78908
+ state: [{
78909
+ kind: "boolean",
78910
+ field: "empty",
78911
+ label: t("Tank empty", "Serbatoio vuoto"),
78912
+ whenTrue: t("When the tank of {device} is empty", "Quando il serbatoio di {device} è vuoto"),
78913
+ whenFalse: t("When the tank of {device} is refilled", "Quando il serbatoio di {device} viene riempito"),
78914
+ icon: "droplet"
78915
+ }, {
78916
+ kind: "numeric",
78917
+ field: "waterLevel",
78918
+ quantity: t("the water level", "il livello dell’acqua"),
78919
+ label: t("Water level", "Livello dell’acqua"),
78920
+ unit: "%",
78921
+ icon: "droplets"
78922
+ }]
78923
+ },
78560
78924
  "media-player": {
78561
78925
  scope: "device",
78562
78926
  noun: t("Media player", "Lettore multimediale"),
@@ -80494,7 +80858,7 @@ function projectAction(cap, action) {
80494
80858
  };
80495
80859
  }
80496
80860
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
80497
- 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;
80861
+ 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;
80498
80862
  DeviceFeature.BatteryOperated;
80499
80863
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
80500
80864
  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.";
@@ -82045,6 +82409,36 @@ Object.freeze({
82045
82409
  form: "single",
82046
82410
  optional: false
82047
82411
  }],
82412
+ "petFountain.getStatus": [{
82413
+ name: "deviceId",
82414
+ form: "single",
82415
+ optional: false
82416
+ }],
82417
+ "petFountain.resetDailyConsumption": [{
82418
+ name: "deviceId",
82419
+ form: "single",
82420
+ optional: false
82421
+ }],
82422
+ "petFountain.setPump": [{
82423
+ name: "deviceId",
82424
+ form: "single",
82425
+ optional: false
82426
+ }],
82427
+ "petFountain.setPumpInterval": [{
82428
+ name: "deviceId",
82429
+ form: "single",
82430
+ optional: false
82431
+ }],
82432
+ "petFountain.setPumpOffMinutes": [{
82433
+ name: "deviceId",
82434
+ form: "single",
82435
+ optional: false
82436
+ }],
82437
+ "petFountain.setPumpOnMinutes": [{
82438
+ name: "deviceId",
82439
+ form: "single",
82440
+ optional: false
82441
+ }],
82048
82442
  "pipelineAnalytics.addRetrainFrameFromRecording": [{
82049
82443
  name: "deviceId",
82050
82444
  form: "single",
@@ -84721,7 +85115,7 @@ var AdminUIAddon = class extends BaseAddon {
84721
85115
  capability: adminUiCapability,
84722
85116
  provider: {
84723
85117
  getStaticDir: async () => ({ staticDir: resolveAdminUiDistDir() }),
84724
- getVersion: async () => ({ version: "1.2.516" })
85118
+ getVersion: async () => ({ version: "1.2.517" })
84725
85119
  }
84726
85120
  }];
84727
85121
  }