@camstack/addon-training 0.1.105 → 0.1.107

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